1→"use client"; 2→// 回响星核 / Echo Nexus — 游戏主入口 3→import { useState, useEffect } from "react"; 4→import { StarfieldCanvas } from "@/components/game/StarfieldCanvas"; 5→import { ResourceBar } from "@/components/game/ResourceBar"; 6→import { CrystalOrb } from "@/components/game/CrystalOrb"; 7→import { DecodePanel } from "@/components/game/DecodeArray"; 8→import { TechTree } from "@/components/game/TechTree"; 9→import { Codex } from "@/components/game/Codex"; 10→import { PrestigeDialog } from "@/components/game/PrestigeDialog"; 11→import { SettingsDialog } from "@/components/game/SettingsDialog"; 12→import { ExpeditionPanel } from "@/components/game/ExpeditionPanel"; 13→import { AchievementsPanel } from "@/components/game/AchievementsPanel"; 14→import { AchievementNotifier } from "@/components/game/AchievementNotifier"; 15→import { ConstellationPanel } from "@/components/game/ConstellationPanel"; 16→import { ConstellationDialog } from "@/components/game/ConstellationDialog"; 17→import { ChronicleDialog } from "@/components/game/ChronicleDialog"; 18→import { BeaconPanel } from "@/components/game/BeaconPanel"; 19→import { TutorialOverlay } from "@/components/game/TutorialOverlay"; 20→import { OfflineReportDialog } from "@/components/game/OfflineReportDialog"; 21→import { CruiseMode } from "@/components/game/CruiseMode"; 22→import { AttributesPanel } from "@/components/game/AttributesPanel"; 23→import { 24→ StarTideNotifier, 25→ StarTideIndicator, 26→ StarTideOverlay, 27→} from "@/components/game/StarTide"; 28→import { useGameLoop } from "@/hooks/useGameLoop"; 29→import { useAudioSync } from "@/hooks/useAudio"; 30→import { useGlobalTide } from "@/hooks/useGlobalTide"; 31→import { useGameStore } from "@/store/gameStore"; 32→import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 33→import { Button } from "@/components/ui/button"; 34→import { 35→ Settings, 36→ RotateCcw, 37→ Sparkles, 38→ Cpu, 39→ BookOpen, 40→ BarChart3, 41→ Rocket, 42→ Github, 43→ Trophy, 44→ Star, 45→ Radio, 46→ Navigation, 47→ User, 48→ Gem, 49→ Zap, 50→ Flame, 51→ Database, 52→} from "lucide-react"; 53→import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config"; 54→import { ACHIEVEMENTS } from "@/lib/game/achievements"; 55→import { TIDE_EVENTS } from "@/lib/game/starTide"; 56→import { CONSTELLATION_PERKS } from "@/lib/game/constellation"; 57→import { generateDailyChallenge, loadDailyProgress, loadLeaderboard } from "@/lib/game/beacon"; 58→ 59→export default function Page() { 60→ useGameLoop(); 61→ useAudioSync(); 62→ useGlobalTide(); 63→ const [prestigeOpen, setPrestigeOpen] = useState(false); 64→ const [settingsOpen, setSettingsOpen] = useState(false); 65→ const [constellationOpen, setConstellationOpen] = useState(false); 66→ const [chronicleOpen, setChronicleOpen] = useState(false); 67→ const [cruiseOpen, setCruiseOpen] = useState(false); 68→ const [mounted, setMounted] = useState(false); 69→ 70→ const contact = useGameStore((s) => s.contact); 71→ const totalDecoded = useGameStore((s) => s.totalDecoded); 72→ const crystalsPerSec = useGameStore((s) => s.crystalsPerSec); 73→ const ascensions = useGameStore((s) => s.ascensions); 74→ const ownedTech = useGameStore((s) => s.tech); 75→ const ownedFragments = useGameStore((s) => s.fragments); 76→ const ownedAchievements = useGameStore((s) => s.achievements); 77→ const ownedConstellation = useGameStore((s) => s.constellation ?? []); 78→ const pendingPerkChoices = useGameStore((s) => s.pendingPerkChoices); 79→ const crystals = useGameStore((s) => s.crystals); 80→ const crystalCap = useGameStore((s) => s.crystalCap); 81→ const activeTide = useGameStore((s) => s.activeTide); 82→ const globalTide = useGameStore((s) => s.globalTide); 83→ const energy = useGameStore((s) => s.energy); 84→ const hasActiveExpedition = useGameStore((s) => !!s.activeExpedition && !s.activeExpedition.finished); 85→ const chronicleCount = useGameStore((s) => s.chronicle?.length ?? 0); 86→ const pendingAttrPoints = useGameStore((s) => s.pendingAttrPoints ?? 0); 87→ 88→ // 深空信标:检测是否有可领取的奖励(独立 localStorage) 89→ const [beaconClaimable, setBeaconClaimable] = useState(false); 90→ useEffect(() => { 91→ let cancelled = false; 92→ const check = () => { 93→ try { 94→ const c = generateDailyChallenge(); 95→ const p = loadDailyProgress(); 96→ if (!cancelled) { 97→ setBeaconClaimable(p.completedAt !== null && !p.claimed && p.dateKey === c.dateKey); 98→ } 99→ } catch { 100→ if (!cancelled) setBeaconClaimable(false); 101→ } 102→ }; 103→ check(); 104→ const id = setInterval(check, 2000); 105→ return () => { 106→ cancelled = true; 107→ clearInterval(id); 108→ }; 109→ }, []); 110→ 111→ // 挂载检测:避免持久化 store 在 SSR/CSR 间产生 hydration 不一致 112→ useEffect(() => { 113→ // eslint-disable-next-line react-hooks/set-state-in-effect 114→ setMounted(true); 115→ }, []); 116→ 117→ // 防止 SSR/CSR 不一致 118→ if (!mounted) { 119→ return ( 120→
121→
唤醒回响中…
122→
123→ ); 124→ } 125→ 126→ const ownedTechCount = Object.values(ownedTech).filter((v) => v > 0).length; 127→ const ownedFragCount = Object.values(ownedFragments).filter(Boolean).length; 128→ const ownedAchCount = Object.values(ownedAchievements).filter(Boolean).length; 129→ const canPrestige = contact >= 100; 130→ const hasPendingPerk = pendingPerkChoices && pendingPerkChoices.length > 0; 131→ 132→ // 仓库满仓警告 133→ const warehouseFull = crystals >= crystalCap * 0.98; 134→ 135→ // 目标提示 136→ let goal = "点击中央晶体发起脉冲,累积记忆晶体"; 137→ if (totalDecoded === 0 && crystals >= 5) { 138→ goal = "右侧「待解码晶体」点击一颗晶体,开始解码"; 139→ } else if (totalDecoded > 0 && ownedTechCount === 0) { 140→ goal = "用洞见解锁技术树,提升产能"; 141→ } else if (totalDecoded > 0 && ownedFragCount < FRAGMENTS.length) { 142→ goal = `继续解码,拼凑记忆图谱(${ownedFragCount}/${FRAGMENTS.length})`; 143→ } 144→ if (energy >= 1 && totalDecoded >= 3) { 145→ goal = "「探险」标签可深入遗迹,获取丰厚奖励"; 146→ } 147→ if (ascensions >= 1 && ownedConstellation.length > 0) { 148→ goal = `星图天赋已觉醒 ${ownedConstellation.length}/18,继续飞升获取更多`; 149→ } 150→ if (warehouseFull) { 151→ goal = "⚠ 仓库已满,产能浪费中!请解码晶体或升级仓库"; 152→ } 153→ if (hasPendingPerk) { 154→ goal = "✦ 星图觉醒!点击顶部「星图」按钮选择一道天赋"; 155→ } 156→ if (activeTide) { 157→ const tm = TIDE_EVENTS[activeTide.type]; 158→ const prefix = globalTide ? "🌐 全球星潮 · " : ""; 159→ goal = `${prefix}${tm.icon} 星潮「${tm.name}」进行中 · ${tm.desc}`; 160→ } 161→ if (canPrestige) goal = "✦ 接触进度已满,可发起飞升进入新周目"; 162→ 163→ return ( 164→
165→ {/* 星空背景 */} 166→ 167→ {/* 星潮背景叠层 */} 168→ 169→ 170→ {/* 顶部 Header */} 171→
172→
173→
174→
175→
176→
177→ 178→
179→
180→
181→

回响星核

182→

183→ ECHO NEXUS · v0.8 184→

185→
186→
187→
188→ 189→ 200→ 215→ {hasPendingPerk && ( 216→ 225→ )} 226→ {canPrestige && ( 227→ 237→ )} 238→ 247→
248→
249→ setPrestigeOpen(true)} /> 250→
251→ 252→ {/* 主体 — 小屏允许自然滚动,避免 min-h 总和超过视口导致挤压重叠 */} 253→
254→
255→ {/* 左侧:脉冲晶体 */} 256→
257→ {/* 装饰光圈 */} 258→
259→
260→ {/* v0.8.2 装饰全息环(填充留白,提升视觉层次) */} 261→
262→
263→
264→ {/* 四角全息标记 */} 265→
266→
267→
268→
269→ {/* 顶部状态条 */} 270→
271→ 272→ CRYSTAL CORE · ONLINE 273→ 274→
275→ {/* 底部铭文 */} 276→
277→ echo · nexus · archaeology 278→
279→
280→ 281→
282→ {/* v0.9 工单 #2:全息收益信息面板 — 让晶体球价值可视化 */} 283→ 284→
285→ 286→ {/* 右侧:解码 + 标签面板 */} 287→
288→ {/* 解码面板 */} 289→
290→ 291→
292→ {/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 / 角色 */} 293→ {/* v0.9 工单修复:移除 max-h 硬限制,改用 min-h + flex 自适应,避免成就/技术树内容被挤压遮挡 */} 294→
295→ 296→ {/* v0.9 工单修复:标签页响应式布局,小屏 4 列 2 行避免拥挤遮挡 */} 297→ 298→ 299→ 300→ 探险 301→ {energy >= 1 && !hasActiveExpedition && ( 302→ 303→ )} 304→ {hasActiveExpedition && ( 305→ 306→ )} 307→ 308→ 309→ 310→ 技术 311→ 312→ 313→ 314→ 星图 315→ {hasPendingPerk && ( 316→ 317→ )} 318→ 319→ 320→ 321→ 图谱 322→ 323→ 324→ 325→ 成就 326→ {ownedAchCount < ACHIEVEMENTS.length && ( 327→ 328→ )} 329→ 330→ 331→ 332→ 信标 333→ {beaconClaimable && ( 334→ 335→ )} 336→ 337→ 338→ 339→ 统计 340→ 341→ 342→ 343→ 角色 344→ {pendingAttrPoints > 0 && ( 345→ 346→ )} 347→ 348→ 349→ 350→ 351→ 352→ 353→ 354→ 355→ 356→ 357→ 358→ 359→ 360→ 361→ 362→ 363→ 364→ 365→ 366→ 367→ 368→ 369→ 370→ 371→ 372→ 373→ 374→
375→
376→
377→
378→ 379→ {/* 底部 Footer */} 380→
381→
391→ 401→ ▶ 402→ 403→ 408→ {goal} 409→ 410→ | 411→ 412→ 产能 {formatNum(crystalsPerSec)}/s 413→ 414→ 421→ 422→ 423→
424→
425→ 426→ 427→ 428→ 429→ 430→ 431→ 432→ 433→ 434→ {cruiseOpen && setCruiseOpen(false)} />} 435→
436→ ); 437→} 438→ 439→/** 440→ * v0.9 工单 #2:全息收益信息面板 441→ * 在中央晶体球下方展示 4 项核心收益指标(产能 / 脉冲 / 连击 / 仓库), 442→ * 让玩家明确感知晶体球的价值,缓解"占地方又没用"的反馈。 443→ * 严格四色全息配色:emerald / rose / amber / fuchsia(禁止蓝色/靛色)。 444→ */ 445→function CrystalYieldPanel() { 446→ const crystalsPerSec = useGameStore((s) => s.crystalsPerSec); 447→ const pulsePower = useGameStore((s) => s.pulsePower); 448→ const combo = useGameStore((s) => s._combo); 449→ const crystals = useGameStore((s) => s.crystals); 450→ const crystalCap = useGameStore((s) => s.crystalCap); 451→ 452→ // 连击倍率:1 + (combo - 1) * 0.15(仅 combo≥1 时生效) 453→ const comboMult = combo >= 1 ? 1 + (combo - 1) * 0.15 : 1; 454→ const comboHot = combo >= 3; // 连击≥3 高亮闪烁 455→ 456→ // 仓库容量百分比 457→ const fillPct = crystalCap > 0 ? (crystals / crystalCap) * 100 : 0; 458→ const warehouseWarn = fillPct >= 90; // ≥90% 警告色 + pulse 459→ 460→ return ( 461→
462→ {/* 晶体产能 — emerald */} 463→
464→ 465→
466→
467→ 晶体产能 468→
469→
470→ {formatNum(crystalsPerSec)} 471→ /s 472→
473→
474→
475→ 476→ {/* 主动脉冲 — rose */} 477→
478→ 479→
480→
481→ 主动脉冲 482→
483→
484→ {formatNum(pulsePower)} 485→ {combo >= 1 && ( 486→ ×{comboMult.toFixed(2)} 487→ )} 488→
489→
490→
491→ 492→ {/* 当前连击 — amber,≥3 时高亮闪烁 */} 493→
498→ 503→
504→
505→ 当前连击 506→
507→
512→ {combo} 513→ /10 514→
515→
516→
517→ 518→ {/* 仓库容量 — fuchsia,≥90% 切 rose 警告色 + pulse */} 519→
524→ 529→
530→
535→ 仓库容量 536→
537→
542→ {formatNum(crystals)} 543→ 548→ /{formatNum(crystalCap)} 549→ 550→
551→
552→
553→
554→ ); 555→} 556→ 557→function StatsPanel() { 558→ const s = useGameStore(); 559→ const achCount = Object.values(s.achievements).filter(Boolean).length; 560→ // 深空信标本地排行榜最高分(独立 localStorage) 561→ const [beaconBest, setBeaconBest] = useState(null); 562→ useEffect(() => { 563→ try { 564→ const lb = loadLeaderboard(); 565→ // eslint-disable-next-line react-hooks/set-state-in-effect 566→ setBeaconBest(lb.length > 0 ? lb[0].score : null); 567→ } catch { 568→ setBeaconBest(null); 569→ } 570→ }, []); 571→ const rows = [ 572→ { label: "累计解码晶体", value: `${s.totalDecoded} 颗` }, 573→ { label: "飞升周目", value: `${s.ascensions}` }, 574→ { label: "持有蓝图", value: `${s.blueprints.length} / 6` }, 575→ { label: "已学技术", value: `${Object.values(s.tech).filter((v) => v > 0).length} / ${TECH_TREE.length}` }, 576→ { label: "已获碎片", value: `${Object.values(s.fragments).filter(Boolean).length} / ${FRAGMENTS.length}` }, 577→ { label: "已解锁成就", value: `${achCount} / ${ACHIEVEMENTS.length}` }, 578→ { label: "星图天赋", value: `${s.constellation?.length ?? 0} / ${CONSTELLATION_PERKS.length}` }, 579→ { label: "接触进度", value: `${s.contact.toFixed(1)}%` }, 580→ { label: "晶体产能", value: `${s.crystalsPerSec.toFixed(2)} /s` }, 581→ { label: "仓库上限", value: `${formatNum(s.crystalCap)}` }, 582→ { label: "洞见倍率", value: `×${s.insightMult.toFixed(2)}` }, 583→ { label: "累计探险", value: `${s.totalExpeditions} 次` }, 584→ { label: "BOSS 击破", value: `${s.bossKills ?? 0} 次` }, 585→ { label: "星潮亲历", value: `${(s.starTidesEncountered ?? []).length} / 9` }, 586→ { label: "编年史条目", value: `${(s.chronicle ?? []).length} 纪元` }, 587→ { label: "探险能量", value: `${Math.floor(s.energy)} / ${s.energyMax}` }, 588→ { label: "信标最高分", value: beaconBest !== null ? formatNum(beaconBest) : "—" }, 589→ { label: "探索力", value: `${s.attributes?.exploration ?? 0} / 100` }, 590→ { label: "智慧", value: `${s.attributes?.wisdom ?? 0} / 100` }, 591→ { label: "勇气", value: `${s.attributes?.courage ?? 0} / 100` }, 592→ { label: "灵感", value: `${s.attributes?.inspiration ?? 0} / 100` }, 593→ { label: "待分配属性点", value: `${s.pendingAttrPoints ?? 0}` }, 594→ ]; 595→ return ( 596→
597→ {rows.map((r) => ( 598→
602→ {r.label} 603→ {r.value} 604→
605→ ))} 606→
607→ ); 608→} 609→