d9404a31-1970-4e79-ac9a-f4f9db60350b

This commit is contained in:
2026-06-23 12:47:49 +00:00
parent e193530f24
commit f73b96026c
101 changed files with 12685 additions and 2 deletions
+128
View File
@@ -0,0 +1,128 @@
"use client";
// 回响星核 / Echo Nexus — 记忆图谱(碎片叙事)
import { useState } from "react";
import { useGameStore, FRAGMENTS } from "@/store/gameStore";
import { nextFragmentThreshold } from "@/store/gameStore";
import { Card } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { BookOpen, Lock, Sparkles } from "lucide-react";
import type { Fragment } from "@/lib/game/types";
const ERA_NAMES = ["", "寂灭前夜", "谐振纪", "飞升潮", "回响纪", "接触纪"];
export function Codex() {
const fragments = useGameStore((s) => s.fragments);
const totalDecoded = useGameStore((s) => s.totalDecoded);
const nextThreshold = nextFragmentThreshold(totalDecoded);
const [selected, setSelected] = useState<Fragment | null>(null);
// v0.1 只展示第一纪元
const era1 = FRAGMENTS.filter((f) => f.era === 1);
const unlockedCount = era1.filter((f) => fragments[f.id]).length;
return (
<div className="flex flex-col gap-2 h-full">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold flex items-center gap-1.5">
<BookOpen className="h-4 w-4 text-fuchsia-400" />
</h3>
<span className="text-xs text-muted-foreground">
{unlockedCount}/{era1.length} ·
</span>
</div>
{nextThreshold && (
<div className="text-[10px] text-muted-foreground/80 px-1">
<span className="text-fuchsia-300 font-mono">{nextThreshold - totalDecoded}</span>
</div>
)}
<ScrollArea className="flex-1 min-h-0">
<div className="grid grid-cols-2 gap-2 pr-2">
{era1.map((f, i) => {
const unlocked = !!fragments[f.id];
return (
<button
key={f.id}
onClick={() => unlocked && setSelected(f)}
disabled={!unlocked}
className={`relative aspect-[4/3] rounded-xl border text-left p-2 transition-all duration-300 overflow-hidden ${
unlocked
? "border-fuchsia-400/40 bg-gradient-to-br from-fuchsia-950/40 to-black/40 hover:border-fuchsia-400/70 hover:scale-[1.02] cursor-pointer"
: "border-white/5 bg-black/30 cursor-not-allowed"
}`}
style={
unlocked
? { boxShadow: "0 0 16px rgba(232,121,249,0.15)" }
: undefined
}
>
{/* 编号 */}
<span
className={`absolute top-1.5 right-1.5 text-[9px] font-mono ${
unlocked ? "text-fuchsia-300/70" : "text-white/20"
}`}
>
{String(i + 1).padStart(2, "0")}
</span>
{unlocked ? (
<>
<div className="absolute -top-3 -right-3 h-12 w-12 rounded-full bg-fuchsia-500/20 blur-xl" />
<Sparkles className="h-3 w-3 text-fuchsia-300 mb-1" />
<div className="text-[11px] font-semibold text-fuchsia-100 leading-tight">
{f.title}
</div>
<div className="text-[9px] text-muted-foreground mt-1 line-clamp-2">
{f.echo.slice(0, 28)}
</div>
</>
) : (
<div className="flex flex-col items-center justify-center h-full gap-1">
<Lock className="h-4 w-4 text-white/20" />
<span className="text-[9px] text-white/30">
{f.threshold}
</span>
</div>
)}
</button>
);
})}
</div>
</ScrollArea>
{/* 详情弹层 */}
{selected && (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm"
onClick={() => setSelected(null)}
>
<Card
className="max-w-md w-full p-5 bg-gradient-to-br from-fuchsia-950/60 to-black/80 border-fuchsia-400/40"
onClick={(e) => e.stopPropagation()}
style={{ boxShadow: "0 0 40px rgba(232,121,249,0.25)" }}
>
<div className="flex items-center gap-2 mb-3">
<Sparkles className="h-4 w-4 text-fuchsia-300" />
<span className="text-[10px] text-fuchsia-300/70 font-mono">
{selected.era} · {ERA_NAMES[selected.era] || ""}
</span>
</div>
<h4 className="text-lg font-bold text-fuchsia-100 mb-3">
{selected.title}
</h4>
<p className="text-sm text-foreground/90 leading-relaxed italic">
{selected.echo}
</p>
<button
onClick={() => setSelected(null)}
className="mt-4 text-xs text-muted-foreground hover:text-foreground transition"
>
</button>
</Card>
</div>
)}
</div>
);
}