zhuyuan-ice-gl-zy001/OUT-输出/UI-预览-20260818/hololake-five-domains-growing-preview-v0.6.html
2026-08-20 00:01:53 +08:00

400 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>光湖 · 五域生长 · 预览 v0.6</title>
<style>
:root {
--bg-a: #0a1020; --bg-b: #0b1220; --bg-c: #070c16; --bg-d: #04070d;
--star-core: #f7ebc8; --star-bright: #ffefc2;
--text-primary: rgba(234,238,248,.96); --text-body: rgba(222,228,242,.88);
--text-aux: rgba(196,206,228,.62); --text-faint: rgba(180,192,220,.4);
--nebula-a: rgba(91,111,196,.13); --nebula-b: rgba(138,111,201,.05);
--glass-edge: rgba(202,216,255,.13);
--cool-glow: rgba(157,180,255,.42); --warm-glow: rgba(247,235,200,.5);
--lake-top: #101830;
}
* { margin:0; padding:0; box-sizing:border-box; }
html,body { height:100%; }
body {
font-family:"PingFang SC","Hiragino Sans GB","Source Han Sans SC",system-ui,sans-serif;
background:
radial-gradient(140% 90% at 50% -18%, var(--nebula-a), transparent 56%),
radial-gradient(120% 80% at 82% 8%, var(--nebula-b), transparent 60%),
linear-gradient(180deg, var(--bg-a) 0%, var(--bg-b) 40%, var(--bg-c) 62%, var(--bg-d) 100%);
color:var(--text-body); overflow:hidden;
}
.stars { position:fixed; inset:0 0 40% 0; pointer-events:none; z-index:1; }
.stars i { position:absolute; width:2px; height:2px; border-radius:50%;
background:var(--star-core); opacity:.4; box-shadow:0 0 6px var(--warm-glow); }
#skyLight { position:fixed; z-index:2; width:130vw; height:130vw; left:0; top:0;
pointer-events:none; will-change:transform;
background:radial-gradient(circle, var(--sky-tint, rgba(255,236,180,.14)) 0%, transparent 46%); }
/* ── 日月本体:一颗真正的星球,与五域同宗 ── */
#celestial { position:fixed; z-index:8; width:0; height:0; cursor:pointer; will-change:left,top; }
#celestial .halo3 { position:absolute; left:-100px; top:-100px; width:200px; height:200px;
border-radius:50%; background:radial-gradient(circle, var(--glow-outer), transparent 62%); }
#celestial .halo2 { position:absolute; left:-56px; top:-56px; width:112px; height:112px;
border-radius:50%; background:radial-gradient(circle, var(--glow-mid), transparent 68%); }
#celestial .halo1 { position:absolute; left:-36px; top:-36px; width:72px; height:72px;
border-radius:50%; background:radial-gradient(circle, var(--glow-inner, rgba(255,244,210,.5)), transparent 72%); }
#celestial .core { position:absolute; left:-27px; top:-27px; width:54px; height:54px;
border-radius:50%;
background:
radial-gradient(circle at 38% 34%, #fff8e0 0%, #ffe9a8 34%, #f7c86a 68%, #e2a24a 100%);
box-shadow:0 0 26px var(--glow-mid), 0 0 64px var(--glow-outer);
animation:sun-breathe 7s ease-in-out infinite; }
@keyframes sun-breathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.03)} }
#celestial.moon .core { background:
radial-gradient(circle at 62% 56%, rgba(120,134,176,.5) 6%, transparent 7%),
radial-gradient(circle at 44% 68%, rgba(120,134,176,.4) 9%, transparent 10%),
radial-gradient(circle at 70% 30%, rgba(120,134,176,.35) 7%, transparent 8%),
radial-gradient(circle at 40% 36%, #f6f8ff, #d9e0f5 52%, #a8b4da 100%);
box-shadow:0 0 22px var(--glow-mid), 0 0 54px var(--glow-outer); }
#celestial.moon .halo1 { opacity:.6; }
#celestial .caption { position:absolute; left:50%; top:58px; transform:translateX(-50%);
white-space:nowrap; text-align:center; opacity:0; transition:opacity .25s ease; pointer-events:none; }
#celestial:hover .caption { opacity:1; }
#celestial .caption b { display:block; color:var(--star-core); font-size:12px;
font-weight:720; letter-spacing:.18em; font-variant-numeric:tabular-nums; }
#celestial .caption em { display:block; margin-top:5px; color:var(--text-aux); font-style:normal;
font-size:11px; font-weight:650; letter-spacing:.1em; font-variant-numeric:tabular-nums; }
#celestial .caption span { display:block; margin-top:5px; color:var(--text-faint);
font-size:9.5px; font-weight:620; letter-spacing:.3em; text-indent:.3em; }
/* ── 湖面:没有任何线,只有光与渐变 ── */
.lake { position:fixed; left:0; right:0; bottom:0; height:36vh; z-index:4;
background:linear-gradient(180deg, transparent 0%, rgba(16,24,48,.55) 20%, #0b1122 46%, #060a14 78%, #04070d 100%); }
/* 天水交接的柔光带(不是线,是一片洇开的亮) */
.lake .haze { position:absolute; top:-7vh; left:8%; right:8%; height:14vh;
background:radial-gradient(ellipse at center, rgba(190,210,255,.12), transparent 70%);
filter:blur(10px); pointer-events:none; }
/* 波光粼粼:满湖碎光点,各自明灭 */
#sparkles { position:absolute; inset:0; pointer-events:none; }
#sparkles i { position:absolute; width:2px; height:2px; border-radius:50%;
background:#cfe0ff; box-shadow:0 0 5px rgba(190,210,255,.7);
opacity:0; animation:glint var(--gt) ease-in-out infinite; animation-delay:var(--gd); }
#sparkles i.lit { background:var(--spark-lit, #ffe9b8); box-shadow:0 0 7px var(--refl-c, rgba(255,236,180,.8)); }
@keyframes glint { 0%,100%{opacity:0} 50%{opacity:var(--go)} }
/* 日月倒影:一条柔光路,无条纹,随日月呼吸 */
#reflection { position:absolute; top:0; width:130px; height:78%;
transform:translateX(-50%); pointer-events:none; will-change:left;
background:linear-gradient(180deg, var(--refl-c, rgba(255,236,180,.44)), transparent 86%);
opacity:.5; filter:blur(9px);
animation:refl-shimmer 5.2s ease-in-out infinite; }
@keyframes refl-shimmer { 0%,100%{opacity:.48;transform:translateX(-50%) scaleX(1)}
50%{opacity:.68;transform:translateX(-50%) scaleX(1.18)} }
/* ── 五域:正在成长的天体,悬于湖上,影落湖中 ── */
.domain { position:absolute; z-index:6; text-align:center; cursor:pointer; width:150px; }
.d-body { position:relative; margin:0 auto; }
/* 星核(随阶段变大变实) */
.d-core { position:absolute; left:50%; top:50%; border-radius:50%;
transform:translate(-50%,-50%);
background:
radial-gradient(circle at 32% 26%, rgba(255,255,255,.95) 0%, rgba(255,255,255,.16) 22%, transparent 34%),
radial-gradient(circle at 50% 50%, var(--d-hi), var(--d-lo) 84%);
box-shadow:0 0 calc(var(--d-glow-r) * 1.4) var(--d-glow), 0 0 calc(var(--d-glow-r) * 3) var(--d-glow-soft);
animation:core-breathe 6s ease-in-out infinite; }
@keyframes core-breathe { 0%,100%{transform:translate(-50%,-50%) scale(1)} 50%{transform:translate(-50%,-50%) scale(1.045)} }
/* 光晕(成星后才有) */
.d-halo { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); border-radius:50%;
background:radial-gradient(circle, var(--d-glow-soft), transparent 66%); }
/* 星环(生环期后) */
.d-ring { position:absolute; left:50%; top:50%; border-radius:50%;
border:1px solid var(--d-ring-c); transform:translate(-50%,-50%) rotateX(64deg);
animation:ring-turn 26s linear infinite; }
@keyframes ring-turn { from{transform:translate(-50%,-50%) rotateX(64deg) rotate(0)} to{transform:translate(-50%,-50%) rotateX(64deg) rotate(360deg)} }
.d-ring::before { content:''; position:absolute; top:-2.5px; left:50%; width:5px; height:5px;
margin-left:-2.5px; border-radius:50%; background:var(--d-hi);
box-shadow:0 0 8px var(--d-glow); }
/* 卫星(行星期后):旋臂携星绕行 */
.d-sat-arm { position:absolute; left:50%; top:50%; width:0; height:0;
animation:sat-orbit 17s linear infinite; }
@keyframes sat-orbit { from{transform:rotate(0)} to{transform:rotate(360deg)} }
.d-sat { position:absolute; left:var(--d-sat-r); top:0; width:7px; height:7px; margin:-3.5px 0 0 -3.5px;
border-radius:50%; background:radial-gradient(circle at 34% 30%, #fff, var(--d-lo));
box-shadow:0 0 8px var(--d-glow); }
/* 光尘汇流(聚光尘期):尘点旋入中心,生生不息 */
.d-dust { position:absolute; left:50%; top:50%; width:0; height:0; }
.d-dust i { position:absolute; width:3px; height:3px; margin:-1.5px; border-radius:50%;
background:var(--d-hi); box-shadow:0 0 6px var(--d-glow);
animation:dust-fall var(--dust-t) linear infinite; }
@keyframes dust-fall {
0% { transform:rotate(var(--dust-a)) translateX(var(--dust-r0)); opacity:0; }
12% { opacity:.9; }
88% { opacity:.75; }
100% { transform:rotate(calc(var(--dust-a) + 300deg)) translateX(3px); opacity:0; } }
/* 域影落湖 */
.d-mirror { margin:2px auto 0; border-radius:50%;
background:radial-gradient(circle, var(--d-glow), transparent 70%);
filter:blur(4px); opacity:.6; animation:mirror-breathe 6s ease-in-out infinite; }
@keyframes mirror-breathe { 0%,100%{opacity:.44;transform:scaleX(1)} 50%{opacity:.66;transform:scaleX(1.12)} }
.d-label { margin-top:12px; color:var(--text-body); font-size:14px; font-weight:700;
letter-spacing:.3em; text-indent:.3em; transition:color .18s ease;
text-shadow:0 1px 8px rgba(0,0,0,.6); }
.domain:hover .d-label { color:var(--text-primary); }
.d-stage { margin-top:5px; color:var(--text-aux); font-size:10.5px; letter-spacing:.28em; text-indent:.28em;
opacity:0; transition:opacity .25s ease; }
.domain:hover .d-stage { opacity:1; }
.domain.on-duty .d-label { color:var(--star-core); }
.domain.on-duty .d-stage { opacity:1; color:color-mix(in srgb, var(--star-core) 72%, transparent); }
/* ── 首页官方门面:商标名+技术名,正式书式 ── */
.masthead { position:fixed; top:4.6vh; left:0; right:0; text-align:center; z-index:6; }
.masthead small { display:block; color:var(--star-core); font-size:10px; font-weight:760;
letter-spacing:.5em; text-indent:.5em; opacity:.85; }
.masthead b { display:block; margin-top:12px; color:var(--text-primary); font-size:34px;
font-weight:780; letter-spacing:.56em; text-indent:.56em; }
.masthead span { display:block; margin-top:10px; color:var(--text-aux); font-size:11px;
font-weight:650; letter-spacing:.4em; text-indent:.4em; }
.era-foot { position:fixed; bottom:1.6vh; left:0; right:0; text-align:center; z-index:6;
color:var(--text-faint); font-size:10.5px; font-weight:620; letter-spacing:.3em; text-indent:.3em; }
/* ── 演化史长河 ── */
.river { position:fixed; inset:0; z-index:20; display:grid; place-items:center;
background:color-mix(in srgb, var(--bg-d) 88%, transparent); backdrop-filter:blur(22px);
opacity:0; pointer-events:none; transition:opacity .4s ease; }
.river.open { opacity:1; pointer-events:auto; }
.river-close { position:absolute; top:30px; right:34px; width:36px; height:36px;
border:1px solid var(--glass-edge); border-radius:50%; background:transparent;
color:var(--text-body); font-size:20px; cursor:pointer; }
.river-close:hover { border-color:color-mix(in srgb, var(--warm-glow) 55%, transparent); }
.river-title { position:absolute; top:5.5vh; left:0; right:0; text-align:center; }
.river-title b { color:var(--text-primary); font-size:22px; font-weight:760; letter-spacing:.3em; text-indent:.3em; }
.river-title small { display:block; margin-top:8px; color:var(--text-aux); font-size:11px; letter-spacing:.2em; }
.river-path { position:relative; width:min(860px,88vw); height:52vh; }
.river-path svg { position:absolute; inset:0; width:100%; height:100%; overflow:visible; }
.river-path svg path { fill:none; stroke:var(--glass-edge); stroke-width:1; }
.ev { position:absolute; transform:translate(-50%,-50%); text-align:center; }
.ev .orb { width:11px; height:11px; margin:0 auto; border-radius:50%;
background:radial-gradient(circle at 34% 30%, var(--star-bright), var(--cool-glow));
box-shadow:0 0 14px var(--cool-glow); }
.ev .ev-date { margin-top:12px; color:var(--star-core); font-size:11px; font-weight:720;
letter-spacing:.14em; font-variant-numeric:tabular-nums; }
.ev .ev-name { margin-top:4px; color:var(--text-primary); font-size:13.5px; font-weight:680; letter-spacing:.06em; }
.ev .ev-note { margin-top:4px; color:var(--text-aux); font-size:11px; max-width:210px; margin-inline:auto; }
.ev.origin .orb { width:15px; height:15px;
background:radial-gradient(circle at 34% 30%, #fff, var(--warm-glow));
box-shadow:0 0 22px var(--warm-glow), 0 0 52px var(--warm-glow); }
.ev.now .orb { animation:now-pulse 3.6s ease-in-out infinite; }
@keyframes now-pulse { 0%,100%{box-shadow:0 0 14px var(--warm-glow)} 50%{box-shadow:0 0 26px var(--warm-glow),0 0 60px var(--warm-glow)} }
.river-foot { position:absolute; bottom:4vh; left:0; right:0; text-align:center;
color:var(--text-faint); font-size:10.5px; letter-spacing:.16em; padding:0 8vw; }
@media (prefers-reduced-motion: reduce) {
.d-core,.d-ring,.d-sat-arm,.d-dust i,.d-mirror,#sparkles i,#reflection,.ev.now .orb,#celestial .core { animation:none; }
}
</style>
</head>
<body>
<div class="stars" id="stars"></div>
<div id="skyLight"></div>
<div class="masthead">
<small>HOLOLAKE · GH-AIOS</small>
<b>光湖</b>
<span>语言世界</span>
</div>
<div id="celestial" role="button" tabindex="0" aria-label="展开曜冥纪元演化史">
<div class="halo3"></div><div class="halo2"></div><div class="halo1"></div><div class="core"></div>
<div class="caption"><b id="capDay"></b><em id="capTime"></em><span>曜冥纪元 · 光湖历 · 精确到毫秒</span></div>
</div>
<div class="lake">
<div class="haze"></div>
<div id="sparkles"></div>
<div id="reflection"></div>
</div>
<div id="domains"></div>
<div class="era-foot">五域在生长 · 星相就是历史</div>
<div class="river" id="river" role="dialog" aria-modal="true" aria-label="曜冥纪元演化史长河">
<button class="river-close" type="button" aria-label="关闭" id="riverClose">×</button>
<div class="river-title"><b>演化史长河</b><small>自源点 2025-04-26 · 沿星轨至今</small></div>
<div class="river-path" id="riverPath">
<svg aria-hidden="true"><path id="riverCurve"/></svg>
</div>
<div class="river-foot">源点精度只确认到 2025-04-26 当日 · 公众时间线只呈现现实可成立的演化事实</div>
</div>
<script>
const EPOCH = new Date('2025-04-26T00:00:00+08:00')
const pad = (n, l = 2) => String(n).padStart(l, '0')
const events = [
{ date: '2025-04-26', name: '源点 · 世界第一次心跳', note: '曜冥与光湖世界起点', cls: 'origin' },
{ date: '2025-05-14', name: '一代仓库首次着陆', note: '最早的可见提交', cls: '' },
{ date: '2026-03-05', name: '铸渊出生', note: 'ICE-GL-ZY001 可核验出生记录', cls: '' },
{ date: null, name: '此刻', note: '世界仍在转动', cls: 'now' },
]
const stars = document.getElementById('stars')
for (let i = 0; i < 60; i++) {
const s = document.createElement('i')
s.style.left = (Math.random() * 100) + '%'
s.style.top = (Math.random() * 100) + '%'
s.style.opacity = (0.14 + Math.random() * 0.42).toFixed(2)
stars.appendChild(s)
}
/* ── 湖面波光:随机撒碎光点,近大远小;日光道内的点跟日月颜色走 ── */
const sparkles = document.getElementById('sparkles')
const sparkleDots = []
for (let i = 0; i < 150; i++) {
const s = document.createElement('i')
const ty = Math.pow(Math.random(), 1.35) /* 偏近处多一点 */
const size = 1 + ty * 2.3
s.style.width = size + 'px'
s.style.height = size + 'px'
const px = Math.random() * 100
s.style.left = px + '%'
s.style.top = (ty * 92 + 3) + '%'
s.style.setProperty('--gt', (2.6 + Math.random() * 4.6).toFixed(2) + 's')
s.style.setProperty('--gd', (-Math.random() * 7).toFixed(2) + 's')
s.style.setProperty('--go', (0.22 + Math.random() * 0.6).toFixed(2))
sparkles.appendChild(s)
sparkleDots.push({ el: s, x: px })
}
/* ── 五域档案:各自色相 + 生长阶段 ──
阶段 stage:1 聚光尘 → 2 凝星核 → 3 初生环 → 4 行星态 → 5 主星态
预览里五域故意停在不同阶段,从左到右就是一部成长史;
正式软件里阶段由该域的真实成长数据驱动。 */
const domains = [
{ name: '分域', stage: 1, stageName: '聚光尘',
hi: '#b9f2dd', lo: '#2c5a4a', glow: 'rgba(150,230,200,.5)', glowSoft: 'rgba(150,230,200,.18)', x: 10, y: -46 },
{ name: '零感域', stage: 2, stageName: '凝星核',
hi: '#dcc4ff', lo: '#48326a', glow: 'rgba(206,178,246,.5)', glowSoft: 'rgba(206,178,246,.18)', x: 30, y: -66 },
{ name: '零域', stage: 3, stageName: '初生环',
hi: '#ffcf9e', lo: '#6e4630', glow: 'rgba(255,200,140,.5)', glowSoft: 'rgba(255,200,140,.18)', x: 50, y: -52 },
{ name: '主域', stage: 4, stageName: '行星态',
hi: '#c9d6ff', lo: '#33406e', glow: 'rgba(168,190,255,.52)', glowSoft: 'rgba(168,190,255,.18)', x: 70, y: -78 },
{ name: '第五域', stage: 5, stageName: '主星态',
hi: '#fdeec2', lo: '#6b5626', glow: 'rgba(250,226,150,.56)', glowSoft: 'rgba(250,226,150,.2)', x: 90, y: -60 },
]
const dayIndex = Math.floor((Date.now() - EPOCH) / 86400000) % 5
const lakeTop = () => window.innerHeight * 0.64 /* 湖面在水线 y=64vh 起 */
const dw = document.getElementById('domains')
domains.forEach((d, i) => {
const size = [30, 40, 50, 62, 76][d.stage - 1] /* 阶段越大星越大 */
const haloR = size * 2.6
const ringR = size * 2.1
const satR = size * 1.55
const el = document.createElement('div')
el.className = 'domain' + (i === dayIndex ? ' on-duty' : '')
el.style.setProperty('--d-hi', d.hi)
el.style.setProperty('--d-lo', d.lo)
el.style.setProperty('--d-glow', d.glow)
el.style.setProperty('--d-glow-soft', d.glowSoft)
el.style.setProperty('--d-glow-r', (size * 0.7) + 'px')
el.style.setProperty('--d-ring-c', d.glow)
el.style.setProperty('--d-sat-r', satR + 'px')
el.style.left = `calc(${d.x}% - 75px)`
el.style.top = `calc(${lakeTop() + d.y}px)`
let html = `<div class="d-body" style="width:${haloR}px;height:${haloR}px">`
if (d.stage >= 2) html += `<div class="d-halo" style="width:${haloR}px;height:${haloR}px"></div>`
if (d.stage >= 3) html += `<div class="d-ring" style="width:${ringR}px;height:${ringR}px"></div>`
if (d.stage >= 4) html += `<div class="d-sat-arm"><i class="d-sat"></i></div>`
if (d.stage >= 2) html += `<div class="d-core" style="width:${size}px;height:${size}px"></div>`
if (d.stage === 1) {
html += `<div class="d-core" style="width:${size * 0.55}px;height:${size * 0.55}px;opacity:.7"></div><div class="d-dust">`
for (let k = 0; k < 6; k++) {
html += `<i style="--dust-a:${k * 60}deg;--dust-r0:${26 + (k % 3) * 7}px;--dust-t:${4.6 + k * 0.7}s;animation-delay:${-k * 0.9}s"></i>`
}
html += `</div>`
}
html += `</div>`
html += `<div class="d-mirror" style="width:${size * 2.2}px;height:${size * 0.5}px"></div>`
html += `<div class="d-label">${d.name}</div><div class="d-stage">${d.stageName}</div>`
el.innerHTML = html
dw.appendChild(el)
})
/* 日月行天:毫秒驱动 */
const celestial = document.getElementById('celestial')
const skyLight = document.getElementById('skyLight')
const reflection = document.getElementById('reflection')
const capDay = document.getElementById('capDay')
const capTime = document.getElementById('capTime')
let lastIsDay = null
function frame() {
const now = new Date()
const ms = now.getHours() * 3600000 + now.getMinutes() * 60000 + now.getSeconds() * 1000 + now.getMilliseconds()
const h = ms / 3600000
const isDay = h >= 6 && h < 18
const t = isDay ? (h - 6) / 12 : ((h >= 18 ? h - 18 : h + 6) / 12)
const x = 8 + t * 84
const y = 52 - Math.sin(t * Math.PI) * 26 /* 弧顶 26vh,让开门面 */
celestial.style.left = x + 'vw'
celestial.style.top = y + 'vh'
reflection.style.left = x + '%'
/* 日光道内的波光点被点亮,跟着日月的颜色走 */
for (const dot of sparkleDots) {
const lit = Math.abs(dot.x - x) < 8
if (lit !== dot.lit) { dot.lit = lit; dot.el.classList.toggle('lit', lit) }
}
skyLight.style.transform = `translate(${(x / 100) * window.innerWidth - window.innerWidth * 0.65}px,
${(y / 100) * window.innerHeight - window.innerWidth * 0.65}px)`
if (isDay !== lastIsDay) {
lastIsDay = isDay
celestial.classList.toggle('moon', !isDay)
if (isDay) {
celestial.style.setProperty('--glow-outer', 'rgba(255,214,120,.30)')
celestial.style.setProperty('--glow-mid', 'rgba(255,232,170,.5)')
celestial.style.setProperty('--glow-inner', 'rgba(255,244,210,.55)')
skyLight.style.setProperty('--sky-tint', 'rgba(255,236,180,.13)')
reflection.style.setProperty('--refl-c', 'rgba(255,232,170,.46)')
} else {
celestial.style.setProperty('--glow-outer', 'rgba(190,208,255,.26)')
celestial.style.setProperty('--glow-mid', 'rgba(222,232,255,.44)')
celestial.style.setProperty('--glow-inner', 'rgba(234,240,255,.4)')
skyLight.style.setProperty('--sky-tint', 'rgba(190,208,255,.10)')
reflection.style.setProperty('--refl-c', 'rgba(222,232,255,.4)')
}
}
capDay.textContent = `光湖历 · 第 ${Math.floor((now - EPOCH) / 86400000) + 1} 天`
capTime.textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${pad(now.getMilliseconds(), 3)}`
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
const river = document.getElementById('river')
const riverPath = document.getElementById('riverPath')
function buildRiver() {
riverPath.querySelectorAll('.ev').forEach(n => n.remove())
const w = riverPath.clientWidth, h = riverPath.clientHeight
const pts = events.map((_, i) => {
const t = i / (events.length - 1)
return [w * (0.08 + t * 0.84), h * (0.72 - t * 0.44) + Math.sin(t * Math.PI) * h * 0.1]
})
document.getElementById('riverCurve').setAttribute('d',
`M ${pts.map(p => p.join(' ')).join(' L ')}`)
events.forEach((ev, i) => {
const el = document.createElement('div')
el.className = 'ev ' + ev.cls
el.style.left = pts[i][0] + 'px'
el.style.top = pts[i][1] + 'px'
el.innerHTML = `<div class="orb"></div>
<div class="ev-date">${ev.date || 'NOW'}</div>
<div class="ev-name">${ev.name}</div>
<div class="ev-note">${ev.note}</div>`
riverPath.appendChild(el)
})
}
function openRiver() { buildRiver(); river.classList.add('open') }
function closeRiver() { river.classList.remove('open') }
celestial.addEventListener('click', openRiver)
celestial.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openRiver() } })
document.getElementById('riverClose').addEventListener('click', closeRiver)
river.addEventListener('click', (e) => { if (e.target === river) closeRiver() })
window.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeRiver() })
</script>
</body>
</html>