diff --git a/src/components/game/BeaconPanel.tsx b/src/components/game/BeaconPanel.tsx
new file mode 100644
index 000000000..0295e60b1
--- /dev/null
+++ b/src/components/game/BeaconPanel.tsx
@@ -0,0 +1,301 @@
+"use client";
+// 回响星核 / Echo Nexus — 深空信标面板(v0.5 每日挑战 + 本地排行榜)
+import { useState, useEffect, useCallback } from "react";
+import { useGameStore } from "@/store/gameStore";
+import { useToast } from "@/hooks/use-toast";
+import { sfx } from "@/hooks/useAudio";
+import {
+ generateDailyChallenge,
+ loadDailyProgress,
+ loadLeaderboard,
+ claimBeaconReward,
+ msUntilNextDay,
+ formatCountdown,
+ getTodayKey,
+ BEACON_DIFFICULTY,
+ BEACON_TYPE_META,
+ type BeaconDailyChallenge,
+ type BeaconDailyProgress,
+ type BeaconScoreEntry,
+ type BeaconChallengeType,
+ type BeaconDifficulty,
+} from "@/lib/game/beacon";
+import { formatNum } from "@/lib/game/config";
+import { Button } from "@/components/ui/button";
+import { Progress } from "@/components/ui/progress";
+import { Radio, Clock, Trophy, Sparkles, Award, Crown, Medal } from "lucide-react";
+
+const DIFFICULTY_ORDER: BeaconDifficulty[] = ["routine", "anomaly", "singular"];
+
+function rankBadge(rank: number) {
+ if (rank === 0) return { icon: Crown, color: "#fbbf24", label: "1st" };
+ if (rank === 1) return { icon: Medal, color: "#cbd5e1", label: "2nd" };
+ if (rank === 2) return { icon: Award, color: "#f97316", label: "3rd" };
+ return null;
+}
+
+export function BeaconPanel() {
+ const insights = useGameStore((s) => s.insights);
+ const grantBeaconReward = useGameStore((s) => s.grantBeaconReward);
+ const { toast } = useToast();
+
+ const [challenge, setChallenge] = useState
(null);
+ const [progress, setProgress] = useState(null);
+ const [leaderboard, setLeaderboard] = useState([]);
+ const [countdown, setCountdown] = useState("00:00:00");
+ const [now, setNow] = useState(Date.now());
+
+ // 初始化 + 每秒刷新(进度 + 倒计时)
+ useEffect(() => {
+ setChallenge(generateDailyChallenge());
+ setProgress(loadDailyProgress());
+ setLeaderboard(loadLeaderboard());
+ const id = setInterval(() => {
+ setNow(Date.now());
+ setProgress(loadDailyProgress());
+ setChallenge((c) => c ?? generateDailyChallenge());
+ }, 1000);
+ return () => clearInterval(id);
+ }, []);
+
+ useEffect(() => {
+ setCountdown(formatCountdown(msUntilNextDay(new Date(now))));
+ }, [now]);
+
+ const handleClaim = useCallback(() => {
+ if (!challenge || !progress) return;
+ if (progress.completedAt === null || progress.claimed) return;
+ const res = claimBeaconReward(challenge, progress);
+ setLeaderboard(res.leaderboard);
+ setProgress(loadDailyProgress());
+ // 发放奖励到游戏状态
+ grantBeaconReward(res.rewardInsight, res.rewardContact);
+ sfx("achievement");
+ toast({
+ title: "✦ 信标奖励已领取",
+ description: `+${res.rewardInsight} 洞见 · +${res.rewardContact.toFixed(1)} 接触 · 得分 ${res.score}`,
+ });
+ }, [challenge, progress, toast, grantBeaconReward]);
+
+ if (!challenge || !progress) {
+ return (
+
+ 正在校准深空信标…
+
+ );
+ }
+
+ const diffMeta = BEACON_DIFFICULTY[challenge.difficulty];
+ const typeMeta = BEACON_TYPE_META[challenge.type];
+ const pct = Math.min(100, (progress.progress / challenge.goal) * 100);
+ const isCompleted = progress.completedAt !== null;
+ const isClaimed = progress.claimed;
+ const canClaim = isCompleted && !isClaimed;
+
+ return (
+
+
+
+ {/* 头部:信标 + 倒计时 */}
+
+
+
+ 深空信标
+
+
+
+ 次日重置
+ {countdown}
+
+
+
+ {/* 每日挑战卡片 */}
+
+ {/* 背景装饰:脉冲环 */}
+ {!isCompleted && (
+ <>
+
+
+ >
+ )}
+
+
+ {/* 难度 + 类型 标签 */}
+
+
+ {diffMeta.icon}
+ {diffMeta.label}
+
+
+ {typeMeta.icon} {typeMeta.label}
+
+ {isCompleted && (
+
+ 已完成
+
+ )}
+
+
+ {/* 挑战标题 */}
+
+ {challenge.title}
+
+
+ {challenge.desc}
+
+
+ {/* 进度条 */}
+
+
+ 进度
+
+ {Math.min(progress.progress, challenge.goal)} / {challenge.goal} {typeMeta.unit}
+
+
+
+
+
+ {/* 奖励 + 领取按钮 */}
+
+
+ 奖励:
+ {challenge.rewardInsight > 0 && (
+ +{formatNum(challenge.rewardInsight)}洞见
+ )}
+ +{challenge.rewardContact.toFixed(1)}接触
+
+
+
+
+ {/* 完成时长 */}
+ {isCompleted && progress.durationSec > 0 && (
+
+ 完成用时 {Math.floor(progress.durationSec / 60)}分{progress.durationSec % 60}秒
+
+ )}
+
+
+
+ {/* 本地排行榜 */}
+
+
+
+ 深空排行榜
+ 本地 · Top {leaderboard.length || 0}
+
+
+ {leaderboard.length === 0 ? (
+
+
+ 尚无记录。完成今日信标即可登榜。
+
+ ) : (
+
+ {leaderboard.map((entry, i) => {
+ const rb = rankBadge(i);
+ const eDiff = BEACON_DIFFICULTY[entry.difficulty];
+ const eType = BEACON_TYPE_META[entry.challenge as BeaconChallengeType];
+ const isMine = entry.dateKey === getTodayKey();
+ return (
+
+ {/* 排名 */}
+
+ {rb ? (
+
+ ) : (
+ {i + 1}
+ )}
+
+ {/* 类型 + 难度 */}
+
+ {eDiff.icon}
+ {eType.label}
+ {entry.progress >= 1 && ✓}
+
+ {/* 用时 */}
+
+ {Math.floor(entry.durationSec / 60)}m{entry.durationSec % 60}s
+
+ {/* 分数 */}
+
+ {formatNum(entry.score)}
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* 难度图例 */}
+
+ {DIFFICULTY_ORDER.map((d) => {
+ const m = BEACON_DIFFICULTY[d];
+ return (
+
+ {m.icon}
+ {m.label} ×{m.mult}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/game/ChronicleDialog.tsx b/src/components/game/ChronicleDialog.tsx
index 718ea0183..deeba2ec7 100644
--- a/src/components/game/ChronicleDialog.tsx
+++ b/src/components/game/ChronicleDialog.tsx
@@ -14,6 +14,7 @@ import {
getCategoryColor,
getCategoryName,
getPerkCategoryBreakdown,
+ regenerateLoreFromEntry,
} from "@/lib/game/chronicle";
import { getPerk } from "@/lib/game/constellation";
import { TIDE_EVENTS } from "@/lib/game/starTide";
@@ -289,9 +290,9 @@ function ChronicleCard({