+
+ {/* 顶部:标题 + 进度 */}
+
+
成就
-
- {unlockedCount} / {ACHIEVEMENTS.length}
-
+
+ {unlockedCount}
+ /
+ {totalCount}
+ ({unlockPct.toFixed(0)}%)
+
+
+
+ {/* 进度条 */}
+
+
{/* 永久加成条 */}
-
+
+
永久加成
{bonus.crystalsPerSecPct > 0 && (
产能 +{bonus.crystalsPerSecPct}%
@@ -33,64 +67,106 @@ export function AchievementsPanel() {
洞见 +{bonus.insightPct}%
)}
{bonus.crystalsPerSecPct === 0 && bonus.insightPct === 0 && (
- 暂无(达成成就解锁)
+ 达成成就解锁永久加成
)}
- {/* 成就网格 — 移除固定 max-h,用 flex-1 min-h-0 自适应父容器,避免小屏挤压重叠 */}
-
- {ACHIEVEMENTS.map((a) => {
+ {/* 分类过滤标签 */}
+
+
+ {FILTER_TABS.map((t) => {
+ const active = filter === t.key;
+ const count =
+ t.key === "all"
+ ? totalCount
+ : t.key === "unlocked"
+ ? unlockedCount
+ : totalCount - unlockedCount;
+ return (
+
+ );
+ })}
+
+
+ {/* 成就列表 — 单列展示,每个卡片 min-h 防止内容溢出被裁切 */}
+
+ {filtered.length === 0 && (
+
+ 该分类下暂无成就
+
+ )}
+ {filtered.map((a) => {
const unlocked = !!achievements[a.id];
return (
{unlocked && (
)}
-
- {/* 图标徽章 */}
-
- {unlocked ? a.icon : }
-
- {/* 文本 */}
-
-
+ {unlocked ? a.icon : }
+
+ {/* 文本 — 使用 flex-1 + min-w-0 防止文本撑爆容器 */}
+
+
+
{a.name}
-
-
- {a.desc}
-
+
{unlocked && (
-
- ✦ {a.rewardText}
-
+
+ ✓
+
)}
+
+ {a.desc}
+
+ {unlocked && (
+
+ ✦ {a.rewardText}
+
+ )}
);
@@ -101,6 +177,12 @@ export function AchievementsPanel() {
.echo-scroll::-webkit-scrollbar { width: 5px; }
.echo-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.14); border-radius: 3px; }
.echo-scroll::-webkit-scrollbar-track { background: transparent; }
+ .line-clamp-2 {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
`}
);
diff --git a/src/components/game/ExpeditionPanel.tsx b/src/components/game/ExpeditionPanel.tsx
index 2eeda14da..b09f57d66 100755
--- a/src/components/game/ExpeditionPanel.tsx
+++ b/src/components/game/ExpeditionPanel.tsx
@@ -49,7 +49,6 @@ export function ExpeditionPanel() {
const resolveCurrentNode = useGameStore((s) => s.resolveCurrentNode);
const advanceNode = useGameStore((s) => s.advanceNode);
const abortExpedition = useGameStore((s) => s.abortExpedition);
- const dismissExpedition = useGameStore((s) => s.dismissExpedition);
const { toast } = useToast();
const [lastLog, setLastLog] = useState(null);
const [now, setNow] = useState(() => Date.now());
@@ -109,10 +108,8 @@ export function ExpeditionPanel() {
};
// ===== 无活跃探险:展示入口 =====
+ // v0.10 工单 #2 修复:探险结束自动清空 activeExpedition,无需 justFinished 判断
if (!activeExpedition || activeExpedition.finished) {
- // v0.9 工单修复:如果探险刚结束(finished 且有摘要),显示结算面板
- const justFinished = activeExpedition?.finished && lastExpeditionSummary &&
- Date.now() - lastExpeditionSummary.finishedAt < 60000;
return (
@@ -123,7 +120,7 @@ export function ExpeditionPanel() {
已探索 {totalExpeditions} 次
- {/* v0.9 工单修复:上次探险结果摘要 */}
+ {/* v0.9 工单修复:上次探险结果摘要(不限时间,常驻展示) */}
{lastExpeditionSummary && (
)}
-
- {/* v0.9 工单修复:刚结束的探险,显示"返回"按钮 */}
- {justFinished && (
-
- )}
);
}
diff --git a/src/components/game/ExplorationRadar.tsx b/src/components/game/ExplorationRadar.tsx
new file mode 100644
index 000000000..5dc23f24e
--- /dev/null
+++ b/src/components/game/ExplorationRadar.tsx
@@ -0,0 +1,215 @@
+"use client";
+// 回响星核 / Echo Nexus — 探索目标雷达(v0.10 新增)
+// 动态展示当前可解锁/可探索的下一目标,让"等待"变"期待"
+// 缓解工单 #2 "占地方没用" 反馈 — 晶体球下方提供持续探索动力
+import { useGameStore } from "@/store/gameStore";
+import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config";
+import { ACHIEVEMENTS } from "@/lib/game/achievements";
+import { CONSTELLATION_PERKS } from "@/lib/game/constellation";
+import { EXPEDITION_CONFIG } from "@/lib/game/expedition";
+import {
+ Target,
+ Cpu,
+ Trophy,
+ BookOpen,
+ Rocket,
+ Star,
+ TrendingDown,
+ CheckCircle2,
+ Sparkles,
+} from "lucide-react";
+
+interface TargetItem {
+ id: string;
+ icon: React.ComponentType<{ className?: string }>;
+ color: string;
+ label: string;
+ detail: string;
+ progress: number; // 0-100
+ hint: string;
+}
+
+export function ExplorationRadar() {
+ const s = useGameStore();
+
+ // ===== 计算各系统的下一个目标 =====
+ const targets: TargetItem[] = [];
+
+ // 1. 下一个技术节点(按洞见排序)
+ const nextTech = TECH_TREE.filter((t) => !s.tech[t.id]).sort((a, b) => a.cost - b.cost)[0];
+ if (nextTech) {
+ const insightProgress = Math.min(100, (s.insights / nextTech.cost) * 100);
+ targets.push({
+ id: "tech_" + nextTech.id,
+ icon: Cpu,
+ color: "#34d399",
+ label: nextTech.name,
+ detail: `需 ${nextTech.cost} 洞见 · ${nextTech.desc}`,
+ progress: insightProgress,
+ hint: s.insights >= nextTech.cost ? "可解锁" : `还差 ${nextTech.cost - s.insights} 洞见`,
+ });
+ }
+
+ // 2. 下一个成就(按达成难度近似)
+ const nextAch = ACHIEVEMENTS.find((a) => !s.achievements[a.id]);
+ if (nextAch) {
+ targets.push({
+ id: "ach_" + nextAch.id,
+ icon: Trophy,
+ color: "#fbbf24",
+ label: nextAch.name,
+ detail: nextAch.desc,
+ progress: 30, // 成就进度难以精确计算,给固定视觉提示
+ hint: nextAch.rewardText,
+ });
+ }
+
+ // 3. 下一个记忆碎片
+ const nextFrag = FRAGMENTS.find((f) => !s.fragments[f.id]);
+ if (nextFrag) {
+ const ownedFrag = FRAGMENTS.filter((f) => s.fragments[f.id]).length;
+ targets.push({
+ id: "frag_" + nextFrag.id,
+ icon: BookOpen,
+ color: "#e879f9",
+ label: nextFrag.title,
+ detail: "解码晶体有概率获取",
+ progress: (ownedFrag / FRAGMENTS.length) * 100,
+ hint: `图谱 ${ownedFrag}/${FRAGMENTS.length}`,
+ });
+ }
+
+ // 4. 探险能量
+ if (s.energy < EXPEDITION_CONFIG.energyCost) {
+ targets.push({
+ id: "expedition_energy",
+ icon: Rocket,
+ color: "#fb7185",
+ label: "探险能量恢复中",
+ detail: `满能量可深入遗迹`,
+ progress: (s.energy / EXPEDITION_CONFIG.energyCost) * 100,
+ hint: `${s.energy}/${EXPEDITION_CONFIG.energyCost}`,
+ });
+ }
+
+ // 5. 飞升进度(接触<100 时)
+ if (s.contact < 100) {
+ targets.push({
+ id: "ascension",
+ icon: Star,
+ color: "#a78bfa",
+ label: "接触进度",
+ detail: "满 100 可飞升新周目",
+ progress: s.contact,
+ hint: `${s.contact.toFixed(1)}/100`,
+ });
+ }
+
+ // 6. 星图天赋(飞升后未选择)
+ const ownedConstellation = s.constellation ?? [];
+ if (s.pendingPerkChoices && s.pendingPerkChoices.length > 0) {
+ targets.push({
+ id: "constellation_pending",
+ icon: Sparkles,
+ color: "#e879f9",
+ label: "星图觉醒待选择",
+ detail: "3 选 1 永久天赋",
+ progress: 100,
+ hint: "立即选择",
+ });
+ } else if (ownedConstellation.length < CONSTELLATION_PERKS.length && s.ascensions > 0) {
+ targets.push({
+ id: "constellation_next",
+ icon: Star,
+ color: "#e879f9",
+ label: "下一道星图天赋",
+ detail: `已觉醒 ${ownedConstellation.length}/${CONSTELLATION_PERKS.length}`,
+ progress: (ownedConstellation.length / CONSTELLATION_PERKS.length) * 100,
+ hint: "飞升后解锁",
+ });
+ }
+
+ // 取前 3 个最相关目标展示
+ const topTargets = targets.slice(0, 3);
+
+ if (topTargets.length === 0) {
+ return (
+
+
+ 所有目标已达成
+ 继续飞升开启新周目
+
+ );
+ }
+
+ return (
+
+ {/* 标题 */}
+
+
+
+ 探索目标
+
+ RADAR
+
+
+ {/* 目标列表 */}
+
+ {topTargets.map((t) => {
+ const Icon = t.icon;
+ const isReady = t.progress >= 100;
+ return (
+
+
+
+
+ {t.label}
+
+
+ {t.hint}
+
+
+ {/* 进度条 */}
+
+
+
+ {/* 详情 */}
+ {t.detail}
+
+ );
+ })}
+
+
+ {/* 底部提示 */}
+
+
+ 持续探索 · 解锁深空奥秘
+
+
+ );
+}
diff --git a/src/components/game/RelicCodex.tsx b/src/components/game/RelicCodex.tsx
new file mode 100644
index 000000000..aed7e3244
--- /dev/null
+++ b/src/components/game/RelicCodex.tsx
@@ -0,0 +1,219 @@
+"use client";
+// 回响星核 / Echo Nexus — 文明遗迹图鉴面板(v0.10 新增)
+// 展示深空考古发现的远古文明遗迹,增加世界观深度与探索乐趣
+import { useState } from "react";
+import { useGameStore } from "@/store/gameStore";
+import {
+ RELIC_CODEX,
+ getUnlockedRelics,
+ getRelicProgress,
+ getRelicRewards,
+ type RelicEntry,
+} from "@/lib/game/relicCodex";
+import { BookMarked, Lock, Sparkles, Clock, Users, ScrollText } from "lucide-react";
+
+export function RelicCodex() {
+ const state = useGameStore();
+ const [selected, setSelected] = useState(null);
+
+ const unlockedRelics = getUnlockedRelics(state);
+ const progress = getRelicProgress(state);
+ const rewards = getRelicRewards(state);
+ const selectedRelic = selected ? RELIC_CODEX.find((r) => r.id === selected) : null;
+
+ return (
+
+ {/* 顶部:标题 + 进度 */}
+
+
+
+ 遗迹图鉴
+
+
+ {unlockedRelics.length}
+ /
+ {RELIC_CODEX.length}
+ ({progress.toFixed(0)}%)
+
+
+
+ {/* 进度条 */}
+
+
+
+
+ {/* 累计奖励总览 */}
+
+
+ 考古收获
+ {rewards.crystals > 0 && (
+ +{rewards.crystals} 晶体
+ )}
+ {rewards.insights > 0 && (
+ +{rewards.insights} 洞见
+ )}
+ {rewards.contact > 0 && (
+ +{rewards.contact} 接触
+ )}
+ {rewards.crystalsPerSecPct > 0 && (
+ 产能 +{rewards.crystalsPerSecPct}%
+ )}
+ {unlockedRelics.length === 0 && (
+ 持续探索解锁奖励
+ )}
+
+
+ {/* 遗迹网格 — 2 列展示,每个卡片 min-h 防溢出 */}
+
+
+ {RELIC_CODEX.map((relic) => {
+ const unlocked = !!state.achievements?.[`relic_${relic.id}`] || relic.unlock(state);
+ const isSelected = selected === relic.id;
+ return (
+
+ );
+ })}
+
+
+ {/* 选中遗迹的详细故事 */}
+ {selectedRelic && (
+
+
+
+
+
+ {selectedRelic.icon}
+
+
+ {selectedRelic.name}
+
+
+
+
+
+ {selectedRelic.race}
+
+
+
+ {selectedRelic.era}
+
+
+
+ {selectedRelic.brief}
+
+
+
+
+ {selectedRelic.story}
+
+
+
+
+
+ {selectedRelic.rewardText}
+
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/lib/game/relicCodex.ts b/src/lib/game/relicCodex.ts
new file mode 100644
index 000000000..cedaad272
--- /dev/null
+++ b/src/lib/game/relicCodex.ts
@@ -0,0 +1,260 @@
+// 回响星核 / Echo Nexus — 文明遗迹图鉴(v0.10 新增)
+// 深空考古世界观载体:8 大远古文明遗迹,每个承载独特故事碎片
+// 通过游戏进度逐步解锁,增加"探索未知"的乐趣
+import type { GameState } from "./types";
+
+export interface RelicEntry {
+ id: string;
+ name: string;
+ /** 文明种族 */
+ race: string;
+ /** 距今时代 */
+ era: string;
+ /** 图标 emoji */
+ icon: string;
+ /** 主题色(emerald/rose/amber/fuchsia 四色全息) */
+ color: string;
+ /** 简短描述 */
+ brief: string;
+ /** 完整故事碎片(解锁后展示) */
+ story: string;
+ /** 解锁条件描述 */
+ unlockHint: string;
+ /** 解锁判定 */
+ unlock: (s: GameState) => boolean;
+ /** 解锁奖励 */
+ reward: {
+ crystals?: number;
+ insights?: number;
+ contact?: number;
+ };
+ rewardText: string;
+}
+
+export const RELIC_CODEX: RelicEntry[] = [
+ {
+ id: "relic_first_echo",
+ name: "初次回响",
+ race: "无名先驱",
+ era: "纪元前 · 未知",
+ icon: "✦",
+ color: "#34d399",
+ brief: "深空中第一缕被捕获的智慧信号。",
+ story:
+ "当自治无人机首次解码出这缕信号时,整个星核为之震颤。那不是噪声,而是某种古老文明留下的问候——跨越亿万年的回响,证明我们并不孤独。",
+ unlockHint: "发起第一次脉冲扫描",
+ unlock: (s) => s.totalDecoded >= 0 || s.crystals > 0,
+ reward: { crystals: 10 },
+ rewardText: "+10 晶体",
+ },
+ {
+ id: "relic_crystal_whisper",
+ name: "晶体低语",
+ race: "晶语族",
+ era: "第一纪元 · 晶石时代",
+ icon: "◈",
+ color: "#fb7185",
+ brief: "能够将记忆封存在晶体中的远古种族。",
+ story:
+ "晶语族相信,记忆是宇宙的本质。他们建造了巨大的晶体矩阵,将整个文明的历史封存在谐振频率中。当他们的恒星熄灭时,唯有这些晶体在虚空中持续低语。",
+ unlockHint: "累计解码 10 颗记忆晶体",
+ unlock: (s) => s.totalDecoded >= 10,
+ reward: { insights: 20, crystalsPerSecPct: 5 } as any,
+ rewardText: "+20 洞见 · 产能 +5%",
+ },
+ {
+ id: "relic_void_ark",
+ name: "虚空方舟",
+ race: "渡厄者",
+ era: "第二纪元 · 大迁徙",
+ icon: "❖",
+ color: "#fbbf24",
+ brief: "为逃离超新星爆发而建造的星际方舟。",
+ story:
+ "渡厄者的母星在一夜之间步入死亡。他们用三百年建造了十二艘方舟,每一艘都载着十亿灵魂的数字意识。如今,只有这一艘的残骸漂浮在我们的考古坐标上。",
+ unlockHint: "完成 5 次遗迹探险",
+ unlock: (s) => s.totalExpeditions >= 5,
+ reward: { insights: 30 },
+ rewardText: "+30 洞见",
+ },
+ {
+ id: "relic_silent_court",
+ name: "寂寂王朝",
+ race: "静默王族",
+ era: "第三纪元 · 铁律纪",
+ icon: "♛",
+ color: "#e879f9",
+ brief: "以沉默为律法的神秘王朝。",
+ story:
+ "静默王族统治了三百个恒星系,却从未留下任何文字。他们的律法刻在引力波中,他们的诗歌写在脉冲星里。我们只能通过残余的引力涟漪,窥见这个王朝的辉煌与寂灭。",
+ unlockHint: "击败 3 个探险 BOSS",
+ unlock: (s) => (s.bossKills ?? 0) >= 3,
+ reward: { contact: 10 },
+ rewardText: "+10 接触",
+ },
+ {
+ id: "relic_starforge",
+ name: "锻星炉",
+ race: "铸星者",
+ era: "第四纪元 · 恒星工程",
+ icon: "✺",
+ color: "#34d399",
+ brief: "能够人工点燃恒星的工程文明。",
+ story:
+ "铸星者不满足于利用恒星能量,他们要创造恒星。他们建造了锻星炉——一种能在虚空中凝聚物质的巨型结构。然而,每一次点火都会消耗一整个星系的物质,最终他们自己也成为了燃料。",
+ unlockHint: "解锁 6 项技术",
+ unlock: (s) => Object.values(s.tech).filter((v) => v > 0).length >= 6,
+ reward: { insights: 50, crystals: 100 },
+ rewardText: "+50 洞见 · +100 晶体",
+ },
+ {
+ id: "relic_memory_weaver",
+ name: "忆织者",
+ race: "织忆族",
+ era: "第五纪元 · 意识网络",
+ icon: "✧",
+ color: "#fb7185",
+ brief: "将集体意识编织成网络的心灵文明。",
+ story:
+ "织忆族没有个体概念。他们的每一个『我』都是网络的一个节点,死亡只是断开连接。当整个网络陷入沉睡时,他们编织了最后一张记忆之网,等待后人唤醒。我们的解码,正是唤醒。",
+ unlockHint: "拼凑 4 块记忆碎片",
+ unlock: (s) => Object.values(s.fragments).filter(Boolean).length >= 4,
+ reward: { contact: 15 },
+ rewardText: "+15 接触",
+ },
+ {
+ id: "relic_eclipse_tribe",
+ name: "蚀相部落",
+ race: "蚀相族",
+ era: "第六纪元 · 阴影纪",
+ icon: "☀",
+ color: "#fbbf24",
+ brief: "栖身于食恒星阴影中的猎手文明。",
+ story:
+ "蚀相族敬畏阴影。他们在双星系统的食相瞬间狩猎,在光与暗的边界舞蹈。当一颗恒星被吞噬殆尽,他们便迁徙至下一对双星。他们的遗迹,永远隐藏在某个行星的阴影里。",
+ unlockHint: "亲历 3 种星潮事件",
+ unlock: (s) => (s.starTidesEncountered ?? []).length >= 3,
+ reward: { insights: 40 },
+ rewardText: "+40 洞见",
+ },
+ {
+ id: "relic_ascended",
+ name: "飞升者遗迹",
+ race: "前代飞升者",
+ era: "终末纪元 · 超越",
+ icon: "✦",
+ color: "#e879f9",
+ brief: "曾经的飞升者留下的最后讯息。",
+ story:
+ "在我们之前,已有无数文明触碰到飞升的临界点。他们留下了什么?不是技术,不是财富,而是一句跨越时空的低语:「飞升不是终点,而是新的开始。勇敢者,继续探索。」",
+ unlockHint: "完成第一次飞升",
+ unlock: (s) => s.ascensions >= 1,
+ reward: { insights: 100, contact: 25 },
+ rewardText: "+100 洞见 · +25 接触",
+ },
+ {
+ id: "relic_constellation",
+ name: "星图密钥",
+ race: "观星者议会",
+ era: "永恒 · 星图守护者",
+ icon: "✧",
+ color: "#34d399",
+ brief: "守护星图天赋秘密的远古议会。",
+ story:
+ "观星者议会不属于任何时代。他们存在于所有时代的星图中,守护着飞升的真正秘密:每一道星图天赋,都是前代飞升者留下的礼物。集齐所有天赋,便能窥见宇宙的真相。",
+ unlockHint: "觉醒 6 道星图天赋",
+ unlock: (s) => (s.constellation ?? []).length >= 6,
+ reward: { insights: 80 },
+ rewardText: "+80 洞见",
+ },
+ {
+ id: "relic_chronicle",
+ name: "编年史之心",
+ race: "史官族",
+ era: "全纪元 · 记录者",
+ icon: "❖",
+ color: "#fb7185",
+ brief: "记录所有纪元兴衰的永恒史官。",
+ story:
+ "史官族从不干涉,只记录。他们的身体是活体的编年史,每一个细胞都承载着一个纪元的记忆。当最后一个史官死去时,他将所有记忆注入了这颗编年史之心,等待后人继承。",
+ unlockHint: "积累 5 段编年史",
+ unlock: (s) => (s.chronicle ?? []).length >= 5,
+ reward: { insights: 60, contact: 20 },
+ rewardText: "+60 洞见 · +20 接触",
+ },
+ {
+ id: "relic_attribute",
+ name: "四维之证",
+ race: "原型人格",
+ era: "元始 · 自我觉醒",
+ icon: "◈",
+ color: "#fbbf24",
+ brief: "自我觉醒的四维人格原型。",
+ story:
+ "探索力、智慧、勇气、灵感——这四维属性并非凭空而来。它们源自一个远古的原型人格,那个第一次说出「我」的存在。我们的每一次成长,都是对那个原型的回响。",
+ unlockHint: "任一角色属性达到 30",
+ unlock: (s) => {
+ const a = s.attributes;
+ if (!a) return false;
+ return (
+ a.exploration >= 30 ||
+ a.wisdom >= 30 ||
+ a.courage >= 30 ||
+ a.inspiration >= 30
+ );
+ },
+ reward: { insights: 50 },
+ rewardText: "+50 洞见",
+ },
+ {
+ id: "relic_cruise",
+ name: "深空航迹",
+ race: "远航者联盟",
+ era: "第七纪元 · 星海远航",
+ icon: "➤",
+ color: "#e879f9",
+ brief: "穿越星海寻找新家园的远航者。",
+ story:
+ "远航者联盟由十二个种族组成,他们共享一艘巨型母舰,在星海中寻找新的家园。他们留下了航迹图——每一道光痕都是一段旅程,每一个航点都是一个希望。我们的巡航,正是延续他们的航迹。",
+ unlockHint: "深空巡航累计通关 5 关",
+ unlock: (s) => {
+ try {
+ const data = localStorage.getItem("echo-nexus-cruise-v1");
+ if (!data) return false;
+ const parsed = JSON.parse(data);
+ return (parsed.totalClears ?? 0) >= 5;
+ } catch {
+ return false;
+ }
+ },
+ reward: { insights: 70, crystals: 200 },
+ rewardText: "+70 洞见 · +200 晶体",
+ },
+];
+
+/** 获取已解锁的遗迹列表 */
+export function getUnlockedRelics(s: GameState): RelicEntry[] {
+ return RELIC_CODEX.filter((r) => r.unlock(s));
+}
+
+/** 获取图鉴进度(0-100) */
+export function getRelicProgress(s: GameState): number {
+ const unlocked = getUnlockedRelics(s).length;
+ return (unlocked / RELIC_CODEX.length) * 100;
+}
+
+/** 获取已解锁遗迹的奖励总和 */
+export function getRelicRewards(s: GameState) {
+ const unlocked = getUnlockedRelics(s);
+ let crystals = 0;
+ let insights = 0;
+ let contact = 0;
+ let crystalsPerSecPct = 0;
+ for (const r of unlocked) {
+ if (r.reward.crystals) crystals += r.reward.crystals;
+ if (r.reward.insights) insights += r.reward.insights;
+ if (r.reward.contact) contact += r.reward.contact;
+ if ((r.reward as any).crystalsPerSecPct) crystalsPerSecPct += (r.reward as any).crystalsPerSecPct;
+ }
+ return { crystals, insights, contact, crystalsPerSecPct };
+}
diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts
index 4f8864459..9ea8fe9f7 100755
--- a/src/store/gameStore.ts
+++ b/src/store/gameStore.ts
@@ -925,8 +925,9 @@ export const useGameStore = create()(
};
const newLog = [logEntry, ...s.expeditionLog].slice(0, 30);
+ // v0.10 工单 #2 修复:探险结束时立即清空 activeExpedition,避免 finished 状态卡死
+ // 保留 lastExpeditionSummary 用于入口展示「上次结果」
if (result.ended) {
- // 探险结束(胜利或失败)
exp.finished = true;
}
@@ -988,7 +989,8 @@ export const useGameStore = create()(
}
set({
- activeExpedition: exp,
+ // v0.10 工单 #2 修复:探险结束立即清空 activeExpedition,让玩家无障碍再出发
+ activeExpedition: result.ended ? null : exp,
crystals: newCrystals,
insights: newInsights,
contact: newContact,
@@ -1028,7 +1030,6 @@ export const useGameStore = create()(
const s = get();
if (!s.activeExpedition) return;
const exp = JSON.parse(JSON.stringify(s.activeExpedition));
- exp.finished = true;
const logEntry = {
expeditionId: exp.id,
nodeType: "rest" as const,
@@ -1044,7 +1045,8 @@ export const useGameStore = create()(
finishedAt: Date.now(),
};
set({
- activeExpedition: exp,
+ // v0.10 工单 #2 修复:撤退立即清空 activeExpedition,避免 finished 卡死
+ activeExpedition: null,
expeditionLog: [logEntry, ...s.expeditionLog].slice(0, 30),
lastExpeditionSummary: summary,
});
diff --git a/tool-results/read_1782262265760_479b0e593f07.txt b/tool-results/read_1782262265760_479b0e593f07.txt
new file mode 100644
index 000000000..832a8234b
--- /dev/null
+++ b/tool-results/read_1782262265760_479b0e593f07.txt
@@ -0,0 +1,609 @@
+ 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→
+ 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→
\ No newline at end of file
diff --git a/tool-results/read_1782262268678_852a10fd13a6.txt b/tool-results/read_1782262268678_852a10fd13a6.txt
new file mode 100644
index 000000000..2d2e172f8
--- /dev/null
+++ b/tool-results/read_1782262268678_852a10fd13a6.txt
@@ -0,0 +1,609 @@
+ 1→ 1→"use client";
+ 2→ 2→// 回响星核 / Echo Nexus — 游戏主入口
+ 3→ 3→import { useState, useEffect } from "react";
+ 4→ 4→import { StarfieldCanvas } from "@/components/game/StarfieldCanvas";
+ 5→ 5→import { ResourceBar } from "@/components/game/ResourceBar";
+ 6→ 6→import { CrystalOrb } from "@/components/game/CrystalOrb";
+ 7→ 7→import { DecodePanel } from "@/components/game/DecodeArray";
+ 8→ 8→import { TechTree } from "@/components/game/TechTree";
+ 9→ 9→import { Codex } from "@/components/game/Codex";
+ 10→ 10→import { PrestigeDialog } from "@/components/game/PrestigeDialog";
+ 11→ 11→import { SettingsDialog } from "@/components/game/SettingsDialog";
+ 12→ 12→import { ExpeditionPanel } from "@/components/game/ExpeditionPanel";
+ 13→ 13→import { AchievementsPanel } from "@/components/game/AchievementsPanel";
+ 14→ 14→import { AchievementNotifier } from "@/components/game/AchievementNotifier";
+ 15→ 15→import { ConstellationPanel } from "@/components/game/ConstellationPanel";
+ 16→ 16→import { ConstellationDialog } from "@/components/game/ConstellationDialog";
+ 17→ 17→import { ChronicleDialog } from "@/components/game/ChronicleDialog";
+ 18→ 18→import { BeaconPanel } from "@/components/game/BeaconPanel";
+ 19→ 19→import { TutorialOverlay } from "@/components/game/TutorialOverlay";
+ 20→ 20→import { OfflineReportDialog } from "@/components/game/OfflineReportDialog";
+ 21→ 21→import { CruiseMode } from "@/components/game/CruiseMode";
+ 22→ 22→import { AttributesPanel } from "@/components/game/AttributesPanel";
+ 23→ 23→import {
+ 24→ 24→ StarTideNotifier,
+ 25→ 25→ StarTideIndicator,
+ 26→ 26→ StarTideOverlay,
+ 27→ 27→} from "@/components/game/StarTide";
+ 28→ 28→import { useGameLoop } from "@/hooks/useGameLoop";
+ 29→ 29→import { useAudioSync } from "@/hooks/useAudio";
+ 30→ 30→import { useGlobalTide } from "@/hooks/useGlobalTide";
+ 31→ 31→import { useGameStore } from "@/store/gameStore";
+ 32→ 32→import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+ 33→ 33→import { Button } from "@/components/ui/button";
+ 34→ 34→import {
+ 35→ 35→ Settings,
+ 36→ 36→ RotateCcw,
+ 37→ 37→ Sparkles,
+ 38→ 38→ Cpu,
+ 39→ 39→ BookOpen,
+ 40→ 40→ BarChart3,
+ 41→ 41→ Rocket,
+ 42→ 42→ Github,
+ 43→ 43→ Trophy,
+ 44→ 44→ Star,
+ 45→ 45→ Radio,
+ 46→ 46→ Navigation,
+ 47→ 47→ User,
+ 48→ 48→ Gem,
+ 49→ 49→ Zap,
+ 50→ 50→ Flame,
+ 51→ 51→ Database,
+ 52→ 52→} from "lucide-react";
+ 53→ 53→import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config";
+ 54→ 54→import { ACHIEVEMENTS } from "@/lib/game/achievements";
+ 55→ 55→import { TIDE_EVENTS } from "@/lib/game/starTide";
+ 56→ 56→import { CONSTELLATION_PERKS } from "@/lib/game/constellation";
+ 57→ 57→import { generateDailyChallenge, loadDailyProgress, loadLeaderboard } from "@/lib/game/beacon";
+ 58→ 58→
+ 59→ 59→export default function Page() {
+ 60→ 60→ useGameLoop();
+ 61→ 61→ useAudioSync();
+ 62→ 62→ useGlobalTide();
+ 63→ 63→ const [prestigeOpen, setPrestigeOpen] = useState(false);
+ 64→ 64→ const [settingsOpen, setSettingsOpen] = useState(false);
+ 65→ 65→ const [constellationOpen, setConstellationOpen] = useState(false);
+ 66→ 66→ const [chronicleOpen, setChronicleOpen] = useState(false);
+ 67→ 67→ const [cruiseOpen, setCruiseOpen] = useState(false);
+ 68→ 68→ const [mounted, setMounted] = useState(false);
+ 69→ 69→
+ 70→ 70→ const contact = useGameStore((s) => s.contact);
+ 71→ 71→ const totalDecoded = useGameStore((s) => s.totalDecoded);
+ 72→ 72→ const crystalsPerSec = useGameStore((s) => s.crystalsPerSec);
+ 73→ 73→ const ascensions = useGameStore((s) => s.ascensions);
+ 74→ 74→ const ownedTech = useGameStore((s) => s.tech);
+ 75→ 75→ const ownedFragments = useGameStore((s) => s.fragments);
+ 76→ 76→ const ownedAchievements = useGameStore((s) => s.achievements);
+ 77→ 77→ const ownedConstellation = useGameStore((s) => s.constellation ?? []);
+ 78→ 78→ const pendingPerkChoices = useGameStore((s) => s.pendingPerkChoices);
+ 79→ 79→ const crystals = useGameStore((s) => s.crystals);
+ 80→ 80→ const crystalCap = useGameStore((s) => s.crystalCap);
+ 81→ 81→ const activeTide = useGameStore((s) => s.activeTide);
+ 82→ 82→ const globalTide = useGameStore((s) => s.globalTide);
+ 83→ 83→ const energy = useGameStore((s) => s.energy);
+ 84→ 84→ const hasActiveExpedition = useGameStore((s) => !!s.activeExpedition && !s.activeExpedition.finished);
+ 85→ 85→ const chronicleCount = useGameStore((s) => s.chronicle?.length ?? 0);
+ 86→ 86→ const pendingAttrPoints = useGameStore((s) => s.pendingAttrPoints ?? 0);
+ 87→ 87→
+ 88→ 88→ // 深空信标:检测是否有可领取的奖励(独立 localStorage)
+ 89→ 89→ const [beaconClaimable, setBeaconClaimable] = useState(false);
+ 90→ 90→ useEffect(() => {
+ 91→ 91→ let cancelled = false;
+ 92→ 92→ const check = () => {
+ 93→ 93→ try {
+ 94→ 94→ const c = generateDailyChallenge();
+ 95→ 95→ const p = loadDailyProgress();
+ 96→ 96→ if (!cancelled) {
+ 97→ 97→ setBeaconClaimable(p.completedAt !== null && !p.claimed && p.dateKey === c.dateKey);
+ 98→ 98→ }
+ 99→ 99→ } catch {
+ 100→ 100→ if (!cancelled) setBeaconClaimable(false);
+ 101→ 101→ }
+ 102→ 102→ };
+ 103→ 103→ check();
+ 104→ 104→ const id = setInterval(check, 2000);
+ 105→ 105→ return () => {
+ 106→ 106→ cancelled = true;
+ 107→ 107→ clearInterval(id);
+ 108→ 108→ };
+ 109→ 109→ }, []);
+ 110→ 110→
+ 111→ 111→ // 挂载检测:避免持久化 store 在 SSR/CSR 间产生 hydration 不一致
+ 112→ 112→ useEffect(() => {
+ 113→ 113→ // eslint-disable-next-line react-hooks/set-state-in-effect
+ 114→ 114→ setMounted(true);
+ 115→ 115→ }, []);
+ 116→ 116→
+ 117→ 117→ // 防止 SSR/CSR 不一致
+ 118→ 118→ if (!mounted) {
+ 119→ 119→ return (
+ 120→ 120→
+ 121→ 121→ 唤醒回响中…
+ 122→ 122→
+ 123→ 123→ );
+ 124→ 124→ }
+ 125→ 125→
+ 126→ 126→ const ownedTechCount = Object.values(ownedTech).filter((v) => v > 0).length;
+ 127→ 127→ const ownedFragCount = Object.values(ownedFragments).filter(Boolean).length;
+ 128→ 128→ const ownedAchCount = Object.values(ownedAchievements).filter(Boolean).length;
+ 129→ 129→ const canPrestige = contact >= 100;
+ 130→ 130→ const hasPendingPerk = pendingPerkChoices && pendingPerkChoices.length > 0;
+ 131→ 131→
+ 132→ 132→ // 仓库满仓警告
+ 133→ 133→ const warehouseFull = crystals >= crystalCap * 0.98;
+ 134→ 134→
+ 135→ 135→ // 目标提示
+ 136→ 136→ let goal = "点击中央晶体发起脉冲,累积记忆晶体";
+ 137→ 137→ if (totalDecoded === 0 && crystals >= 5) {
+ 138→ 138→ goal = "右侧「待解码晶体」点击一颗晶体,开始解码";
+ 139→ 139→ } else if (totalDecoded > 0 && ownedTechCount === 0) {
+ 140→ 140→ goal = "用洞见解锁技术树,提升产能";
+ 141→ 141→ } else if (totalDecoded > 0 && ownedFragCount < FRAGMENTS.length) {
+ 142→ 142→ goal = `继续解码,拼凑记忆图谱(${ownedFragCount}/${FRAGMENTS.length})`;
+ 143→ 143→ }
+ 144→ 144→ if (energy >= 1 && totalDecoded >= 3) {
+ 145→ 145→ goal = "「探险」标签可深入遗迹,获取丰厚奖励";
+ 146→ 146→ }
+ 147→ 147→ if (ascensions >= 1 && ownedConstellation.length > 0) {
+ 148→ 148→ goal = `星图天赋已觉醒 ${ownedConstellation.length}/18,继续飞升获取更多`;
+ 149→ 149→ }
+ 150→ 150→ if (warehouseFull) {
+ 151→ 151→ goal = "⚠ 仓库已满,产能浪费中!请解码晶体或升级仓库";
+ 152→ 152→ }
+ 153→ 153→ if (hasPendingPerk) {
+ 154→ 154→ goal = "✦ 星图觉醒!点击顶部「星图」按钮选择一道天赋";
+ 155→ 155→ }
+ 156→ 156→ if (activeTide) {
+ 157→ 157→ const tm = TIDE_EVENTS[activeTide.type];
+ 158→ 158→ const prefix = globalTide ? "🌐 全球星潮 · " : "";
+ 159→ 159→ goal = `${prefix}${tm.icon} 星潮「${tm.name}」进行中 · ${tm.desc}`;
+ 160→ 160→ }
+ 161→ 161→ if (canPrestige) goal = "✦ 接触进度已满,可发起飞升进入新周目";
+ 162→ 162→
+ 163→ 163→ return (
+ 164→ 164→
+ 165→ 165→ {/* 星空背景 */}
+ 166→ 166→
+ 167→ 167→ {/* 星潮背景叠层 */}
+ 168→ 168→
+ 169→ 169→
+ 170→ 170→ {/* 顶部 Header */}
+ 171→ 171→
+ 172→ 172→
+ 173→ 173→
+ 174→ 174→
+ 175→ 175→
+ 176→ 176→
+ 177→ 177→
+ 178→ 178→
+ 179→ 179→
+ 180→ 180→
+ 181→ 181→ 回响星核
+ 182→ 182→
+ 183→ 183→ ECHO NEXUS · v0.8
+ 184→ 184→
+ 185→ 185→
+ 186→ 186→
+ 187→ 187→
+ 188→ 188→
+ 189→ 189→
+ 200→ 200→
+ 215→ 215→ {hasPendingPerk && (
+ 216→ 216→
+ 225→ 225→ )}
+ 226→ 226→ {canPrestige && (
+ 227→ 227→
+ 237→ 237→ )}
+ 238→ 238→
+ 247→ 247→
+ 248→ 248→
+ 249→ 249→ setPrestigeOpen(true)} />
+ 250→ 250→
+ 251→ 251→
+ 252→ 252→ {/* 主体 — 小屏允许自然滚动,避免 min-h 总和超过视口导致挤压重叠 */}
+ 253→ 253→
+ 254→ 254→
+ 255→ 255→ {/* 左侧:脉冲晶体 */}
+ 256→ 256→
+ 257→ 257→ {/* 装饰光圈 */}
+ 258→ 258→
+ 259→ 259→
+ 260→ 260→ {/* v0.8.2 装饰全息环(填充留白,提升视觉层次) */}
+ 261→ 261→
+ 262→ 262→
+ 263→ 263→
+ 264→ 264→ {/* 四角全息标记 */}
+ 265→ 265→
+ 266→ 266→
+ 267→ 267→
+ 268→ 268→
+ 269→ 269→ {/* 顶部状态条 */}
+ 270→ 270→
+ 271→ 271→
+ 272→ 272→ CRYSTAL CORE · ONLINE
+ 273→ 273→
+ 274→ 274→
+ 275→ 275→ {/* 底部铭文 */}
+ 276→ 276→
+ 277→ 277→ echo · nexus · archaeology
+ 278→ 278→
+ 279→ 279→
+ 280→ 280→
+ 281→ 281→
+ 282→ 282→ {/* v0.9 工单 #2:全息收益信息面板 — 让晶体球价值可视化 */}
+ 283→ 283→
+ 284→ 284→
+ 285→ 285→
+ 286→ 286→ {/* 右侧:解码 + 标签面板 */}
+ 287→ 287→
+ 288→ 288→ {/* 解码面板 */}
+ 289→ 289→
+ 290→ 290→
+ 291→ 291→
+ 292→ 292→ {/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 / 角色 */}
+ 293→ 293→ {/* v0.9 工单修复:移除 max-h 硬限制,改用 min-h + flex 自适应,避免成就/技术树内容被挤压遮挡 */}
+ 294→ 294→
+ 295→ 295→
+ 296→ 296→ {/* v0.9 工单修复:标签页响应式布局,小屏 4 列 2 行避免拥挤遮挡 */}
+ 297→ 297→
+ 298→ 298→
+ 299→ 299→
+ 300→ 300→ 探险
+ 301→ 301→ {energy >= 1 && !hasActiveExpedition && (
+ 302→ 302→
+ 303→ 303→ )}
+ 304→ 304→ {hasActiveExpedition && (
+ 305→ 305→
+ 306→ 306→ )}
+ 307→ 307→
+ 308→ 308→
+ 309→ 309→
+ 310→ 310→ 技术
+ 311→ 311→
+ 312→ 312→
+ 313→ 313→
+ 314→ 314→ 星图
+ 315→ 315→ {hasPendingPerk && (
+ 316→ 316→
+ 317→ 317→ )}
+ 318→ 318→
+ 319→ 319→
+ 320→ 320→
+ 321→ 321→ 图谱
+ 322→ 322→
+ 323→ 323→
+ 324→ 324→
+ 325→ 325→ 成就
+ 326→ 326→ {ownedAchCount < ACHIEVEMENTS.length && (
+ 327→ 327→
+ 328→ 328→ )}
+ 329→ 329→
+ 330→ 330→
+ 331→ 331→
+ 332→ 332→ 信标
+ 333→ 333→ {beaconClaimable && (
+ 334→ 334→
+ 335→ 335→ )}
+ 336→ 336→
+ 337→ 337→
+ 338→ 338→
+ 339→ 339→ 统计
+ 340→ 340→
+ 341→ 341→
+ 342→ 342→
+ 343→ 343→ 角色
+ 344→ 344→ {pendingAttrPoints > 0 && (
+ 345→ 345→
+ 346→ 346→ )}
+ 347→ 347→
+ 348→ 348→
+ 349→ 349→
+ 350→ 350→
+ 351→ 351→
+ 352→ 352→
+ 353→ 353→
+ 354→ 354→
+ 355→ 355→
+ 356→ 356→
+ 357→ 357→
+ 358→ 358→
+ 359→ 359→
+ 360→ 360→
+ 361→ 361→
+ 362→ 362→
+ 363→ 363→
+ 364→ 364→
+ 365→ 365→
+ 366→ 366→
+ 367→ 367→
+ 368→ 368→
+ 369→ 369→
+ 370→ 370→
+ 371→ 371→
+ 372→ 372→
+ 373→ 373→
+ 374→ 374→
+ 375→ 375→
+ 376→ 376→
+ 377→ 377→
+ 378→ 378→
+ 379→ 379→ {/* 底部 Footer */}
+ 380→ 380→
+ 425→ 425→
+ 426→ 426→
+ 427→ 427→
+ 428→ 428→
+ 429→ 429→
+ 430→ 430→
+ 431→ 431→
+ 432→ 432→
+ 433→ 433→
+ 434→ 434→ {cruiseOpen && setCruiseOpen(false)} />}
+ 435→ 435→
+ 436→ 436→ );
+ 437→ 437→}
+ 438→ 438→
+ 439→ 439→/**
+ 440→ 440→ * v0.9 工单 #2:全息收益信息面板
+ 441→ 441→ * 在中央晶体球下方展示 4 项核心收益指标(产能 / 脉冲 / 连击 / 仓库),
+ 442→ 442→ * 让玩家明确感知晶体球的价值,缓解"占地方又没用"的反馈。
+ 443→ 443→ * 严格四色全息配色:emerald / rose / amber / fuchsia(禁止蓝色/靛色)。
+ 444→ 444→ */
+ 445→ 445→function CrystalYieldPanel() {
+ 446→ 446→ const crystalsPerSec = useGameStore((s) => s.crystalsPerSec);
+ 447→ 447→ const pulsePower = useGameStore((s) => s.pulsePower);
+ 448→ 448→ const combo = useGameStore((s) => s._combo);
+ 449→ 449→ const crystals = useGameStore((s) => s.crystals);
+ 450→ 450→ const crystalCap = useGameStore((s) => s.crystalCap);
+ 451→ 451→
+ 452→ 452→ // 连击倍率:1 + (combo - 1) * 0.15(仅 combo≥1 时生效)
+ 453→ 453→ const comboMult = combo >= 1 ? 1 + (combo - 1) * 0.15 : 1;
+ 454→ 454→ const comboHot = combo >= 3; // 连击≥3 高亮闪烁
+ 455→ 455→
+ 456→ 456→ // 仓库容量百分比
+ 457→ 457→ const fillPct = crystalCap > 0 ? (crystals / crystalCap) * 100 : 0;
+ 458→ 458→ const warehouseWarn = fillPct >= 90; // ≥90% 警告色 + pulse
+ 459→ 459→
+ 460→ 460→ return (
+ 461→ 461→
+ 462→ 462→ {/* 晶体产能 — emerald */}
+ 463→ 463→
+ 464→ 464→
+ 465→ 465→
+ 466→ 466→
+ 467→ 467→ 晶体产能
+ 468→ 468→
+ 469→ 469→
+ 470→ 470→ {formatNum(crystalsPerSec)}
+ 471→ 471→ /s
+ 472→ 472→
+ 473→ 473→
+ 474→ 474→
+ 475→ 475→
+ 476→ 476→ {/* 主动脉冲 — rose */}
+ 477→ 477→
+ 478→ 478→
+ 479→ 479→
+ 480→ 480→
+ 481→ 481→ 主动脉冲
+ 482→ 482→
+ 483→ 483→
+ 484→ 484→ {formatNum(pulsePower)}
+ 485→ 485→ {combo >= 1 && (
+ 486→ 486→ ×{comboMult.toFixed(2)}
+ 487→ 487→ )}
+ 488→ 488→
+ 489→ 489→
+ 490→ 490→
+ 491→ 491→
+ 492→ 492→ {/* 当前连击 — amber,≥3 时高亮闪烁 */}
+ 493→ 493→
+ 498→ 498→
+ 503→ 503→
+ 504→ 504→
+ 505→ 505→ 当前连击
+ 506→ 506→
+ 507→ 507→
+ 512→ 512→ {combo}
+ 513→ 513→ /10
+ 514→ 514→
+ 515→ 515→
+ 516→ 516→
+ 517→ 517→
+ 518→ 518→ {/* 仓库容量 — fuchsia,≥90% 切 rose 警告色 + pulse */}
+ 519→ 519→
+ 524→ 524→
+ 529→ 529→
+ 530→ 530→
+ 535→ 535→ 仓库容量
+ 536→ 536→
+ 537→ 537→
+ 542→ 542→ {formatNum(crystals)}
+ 543→ 543→
+ 548→ 548→ /{formatNum(crystalCap)}
+ 549→ 549→
+ 550→ 550→
+ 551→ 551→
+ 552→ 552→
+ 553→ 553→
+ 554→ 554→ );
+ 555→ 555→}
+ 556→ 556→
+ 557→ 557→function StatsPanel() {
+ 558→ 558→ const s = useGameStore();
+ 559→ 559→ const achCount = Object.values(s.achievements).filter(Boolean).length;
+ 560→ 560→ // 深空信标本地排行榜最高分(独立 localStorage)
+ 561→ 561→ const [beaconBest, setBeaconBest] = useState(null);
+ 562→ 562→ useEffect(() => {
+ 563→ 563→ try {
+ 564→ 564→ const lb = loadLeaderboard();
+ 565→ 565→ // eslint-disable-next-line react-hooks/set-state-in-effect
+ 566→ 566→ setBeaconBest(lb.length > 0 ? lb[0].score : null);
+ 567→ 567→ } catch {
+ 568→ 568→ setBeaconBest(null);
+ 569→ 569→ }
+ 570→ 570→ }, []);
+ 571→ 571→ const rows = [
+ 572→ 572→ { label: "累计解码晶体", value: `${s.totalDecoded} 颗` },
+ 573→ 573→ { label: "飞升周目", value: `${s.ascensions}` },
+ 574→ 574→ { label: "持有蓝图", value: `${s.blueprints.length} / 6` },
+ 575→ 575→ { label: "已学技术", value: `${Object.values(s.tech).filter((v) => v > 0).length} / ${TECH_TREE.length}` },
+ 576→ 576→ { label: "已获碎片", value: `${Object.values(s.fragments).filter(Boolean).length} / ${FRAGMENTS.length}` },
+ 577→ 577→ { label: "已解锁成就", value: `${achCount} / ${ACHIEVEMENTS.length}` },
+ 578→ 578→ { label: "星图天赋", value: `${s.constellation?.length ?? 0} / ${CONSTELLATION_PERKS.length}` },
+ 579→ 579→ { label: "接触进度", value: `${s.contact.toFixed(1)}%` },
+ 580→ 580→ { label: "晶体产能", value: `${s.crystalsPerSec.toFixed(2)} /s` },
+ 581→ 581→ { label: "仓库上限", value: `${formatNum(s.crystalCap)}` },
+ 582→ 582→ { label: "洞见倍率", value: `×${s.insightMult.toFixed(2)}` },
+ 583→ 583→ { label: "累计探险", value: `${s.totalExpeditions} 次` },
+ 584→ 584→ { label: "BOSS 击破", value: `${s.bossKills ?? 0} 次` },
+ 585→ 585→ { label: "星潮亲历", value: `${(s.starTidesEncountered ?? []).length} / 9` },
+ 586→ 586→ { label: "编年史条目", value: `${(s.chronicle ?? []).length} 纪元` },
+ 587→ 587→ { label: "探险能量", value: `${Math.floor(s.energy)} / ${s.energyMax}` },
+ 588→ 588→ { label: "信标最高分", value: beaconBest !== null ? formatNum(beaconBest) : "—" },
+ 589→ 589→ { label: "探索力", value: `${s.attributes?.exploration ?? 0} / 100` },
+ 590→ 590→ { label: "智慧", value: `${s.attributes?.wisdom ?? 0} / 100` },
+ 591→ 591→ { label: "勇气", value: `${s.attributes?.courage ?? 0} / 100` },
+ 592→ 592→ { label: "灵感", value: `${s.attributes?.inspiration ?? 0} / 100` },
+ 593→ 593→ { label: "待分配属性点", value: `${s.pendingAttrPoints ?? 0}` },
+ 594→ 594→ ];
+ 595→ 595→ return (
+ 596→ 596→
+ 597→ 597→ {rows.map((r) => (
+ 598→ 598→
+ 602→ 602→ {r.label}
+ 603→ 603→ {r.value}
+ 604→ 604→
+ 605→ 605→ ))}
+ 606→ 606→
+ 607→ 607→ );
+ 608→ 608→}
+ 609→ 609→
\ No newline at end of file
diff --git a/worklog.md b/worklog.md
index 425e16553..46eeb55b7 100755
--- a/worklog.md
+++ b/worklog.md
@@ -856,3 +856,76 @@ Stage Summary:
- 在线地址:https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/
- 定时任务已重建:job_id=228637,每 15 分钟 webDevReview
- 版本:v0.8 → v0.9
+
+---
+
+## v0.10 工单 #2 大重构 — 空间再分配 + 探索深度(2026-01-24)
+
+### 触发:Gitea Issue #2 + 用户追加反馈
+- **Gitea Issue #2** "现阶段玩法过于简单,没有可玩性"
+ - BUG:探险只能一次后再也触发不了 / 元素偏移被遮挡 / 成就UI全遮挡 / 提示框碍眼
+ - 问题:占满空屏的没用按钮 / 世界观敷衍 / 成就系统无用 / 技术树太少
+- **用户追加反馈**:80%空间只给一个只能点10次的球 / 长操作UI太小 / 成就框溢出 / 审美疲劳 / 先调研后重构
+
+### 调研结论(放置+深空游戏UI设计)
+- 《Cookie Clicker》:左右分栏,左侧点击区+右侧建筑列表
+- 《Melvor Idle》:上下分区,顶部导航+主内容区
+- 放置游戏原则:核心循环可见 / 进度可见 / 多面板协同 / 空间感
+- 深空主题应有:星图导航 / 文明遗迹图鉴 / 时序日志 / 雷达扫描
+
+### 核心改动(v0.10)
+
+#### 1. 布局重构 — 空间再分配(page.tsx)
+- **旧**:`grid-cols-[1fr_minmax(360px,420px)]` 左 60% 右 40%(晶体球占满)
+- **新**:`grid-cols-[minmax(300px,360px)_1fr]` 左 30% 右 70%(晶体球紧凑,右侧扩展)
+- 左栏:晶体球(紧凑居中)+ 收益小卡 + **新增探索目标雷达**
+- 右栏:解码面板 + 标签页(9个:探险/技术/星图/图谱/成就/信标/统计/角色/图鉴)
+- 标签页响应式:小屏 3列3行,大屏 9列1行
+- VLM 验证:左右比例 3:7,晶体球不再占满,无溢出重叠
+
+#### 2. 探险 BUG 修复(gameStore.ts + ExpeditionPanel.tsx)
+- **根因**:探险结束 `finished=true` 但 `activeExpedition` 不清理,60秒后"收起结算"按钮消失但状态卡死
+- **修复**:`resolveCurrentNode` 和 `abortExpedition` 结束时立即 `activeExpedition: null`
+- 保留 `lastExpeditionSummary` 用于入口展示"上次结果"(不限时间)
+- 移除 `justFinished` 60秒限制和"收起结算"按钮
+- **验证**:agent-browser 完整跑通 探险→完成→再出发 流程 ✅
+
+#### 3. 成就面板重写(AchievementsPanel.tsx)
+- **旧问题**:2列网格 + overflow-hidden 导致成就框内元素只显示上半部分
+- **新方案**:单列展示 + min-h-[68px] + line-clamp-2 + flex-1 min-w-0
+- 新增分类过滤标签(全部/已解锁/未解锁,带计数)
+- 新增顶部进度条(emerald→rose→fuchsia 渐变)
+- 移除 overflow-hidden,改用 line-clamp 控制文本
+- **验证**:VLM 确认"无溢出或截断" ✅
+
+#### 4. 新增探索目标雷达(ExplorationRadar.tsx)
+- 动态计算 6 类下一目标:技术节点/成就/记忆碎片/探险能量/飞升进度/星图天赋
+- 取前 3 个最相关目标展示,带进度条 + 提示
+- 让晶体球区"等待"变"期待",缓解"占地方没用"反馈
+
+#### 5. 新增文明遗迹图鉴系统(relicCodex.ts + RelicCodex.tsx)
+- **12 个远古文明遗迹**,每个承载独特故事碎片:
+ 1. 初次回响(无名先驱)2. 晶体低语(晶语族)3. 虚空方舟(渡厄者)
+ 4. 寂寂王朝(静默王族)5. 锻星炉(铸星者)6. 忆织者(织忆族)
+ 7. 蚀相部落(蚀相族)8. 飞升者遗迹 9. 星图密钥(观星者议会)
+ 10. 编年史之心(史官族)11. 四维之证(原型人格)12. 深空航迹(远航者联盟)
+- 每个遗迹:name/race/era/icon/color/brief/story/unlockHint/unlock/reward
+- 解锁条件覆盖所有游戏系统(脉冲/解码/探险/BOSS/技术/碎片/星潮/飞升/星图/编年史/属性/巡航)
+- UI:2列网格 + 点击展开详细故事 + 累计奖励总览
+- 增加"第九艺术"世界观深度
+
+#### 6. 版本号 v0.8 → v0.10
+
+### QA 验证(agent-browser + VLM)
+- ✅ 布局重构:左右 3:7,晶体球紧凑,探索雷达可见,无溢出
+- ✅ 成就面板:分类过滤可见,无溢出截断,进度条可见
+- ✅ 遗迹图鉴:12个遗迹展示,进度 2/12,故事展开正常
+- ✅ 探险流程:探索节点→前进→完成→自动清理→再出发(totalExp 2→3)
+- ✅ lint 零错误
+- ✅ VLM 整体视觉冲击力 8/10
+
+### 未解决/下一步
+- 🟡 技术树扩展(用户反馈"太少,分分钟点满")— 待 v0.11
+- 🟡 成就系统价值化(成就解锁主动技能)— 待 v0.11
+- 🟡 VLM 建议:解码区 hover 效果 / 状态文字对比 / 星云背景
+- 🟡 部署 gh-pages
成就
-
- {unlockedCount} / {ACHIEVEMENTS.length}
-
+ 所有目标已达成
+继续飞升开启新周目
+{t.detail}
+
+
+ 遗迹图鉴
+
+ + {selectedRelic.brief} +
++ {selectedRelic.story} +
+回响星核
+ 182→+ 183→ ECHO NEXUS · v0.8 + 184→
+ 185→回响星核
+ 182→ 182→+ 183→ 183→ ECHO NEXUS · v0.8 + 184→ 184→
+ 185→ 185→