108 lines
4.4 KiB
TypeScript
Executable File
108 lines
4.4 KiB
TypeScript
Executable File
"use client";
|
|
// 回响星核 / Echo Nexus — 成就系统面板
|
|
import { useGameStore } from "@/store/gameStore";
|
|
import { ACHIEVEMENTS } from "@/lib/game/achievements";
|
|
import { achievementBonuses } from "@/lib/game/achievements";
|
|
import { Trophy, Lock } from "lucide-react";
|
|
|
|
export function AchievementsPanel() {
|
|
const achievements = useGameStore((s) => s.achievements);
|
|
const unlockedCount = Object.values(achievements).filter(Boolean).length;
|
|
const bonus = achievementBonuses(achievements);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2.5 h-full">
|
|
{/* 顶部:进度 + 加成总览 */}
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-sm font-semibold flex items-center gap-1.5">
|
|
<Trophy className="h-4 w-4 text-amber-400" />
|
|
成就
|
|
</h3>
|
|
<span className="text-[10px] text-muted-foreground font-mono">
|
|
{unlockedCount} / {ACHIEVEMENTS.length}
|
|
</span>
|
|
</div>
|
|
|
|
{/* 永久加成条 */}
|
|
<div className="rounded-lg border border-amber-400/20 bg-amber-950/20 px-2.5 py-1.5 flex items-center gap-3 text-[10px]">
|
|
<span className="text-amber-300/80">永久加成</span>
|
|
{bonus.crystalsPerSecPct > 0 && (
|
|
<span className="text-emerald-300">产能 +{bonus.crystalsPerSecPct}%</span>
|
|
)}
|
|
{bonus.insightPct > 0 && (
|
|
<span className="text-fuchsia-300">洞见 +{bonus.insightPct}%</span>
|
|
)}
|
|
{bonus.crystalsPerSecPct === 0 && bonus.insightPct === 0 && (
|
|
<span className="text-muted-foreground/60">暂无(达成成就解锁)</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* 成就网格 — 移除固定 max-h,用 flex-1 min-h-0 自适应父容器,避免小屏挤压重叠 */}
|
|
<div className="flex-1 min-h-0 grid grid-cols-1 sm:grid-cols-2 gap-1.5 content-start overflow-y-auto echo-scroll pr-1">
|
|
{ACHIEVEMENTS.map((a) => {
|
|
const unlocked = !!achievements[a.id];
|
|
return (
|
|
<div
|
|
key={a.id}
|
|
className={`relative rounded-lg border px-2.5 py-2 transition-all duration-300 overflow-hidden ${
|
|
unlocked
|
|
? "border-white/15 bg-black/30"
|
|
: "border-white/5 bg-black/15 opacity-60"
|
|
}`}
|
|
style={
|
|
unlocked
|
|
? { boxShadow: `inset 0 0 16px ${a.color}11` }
|
|
: undefined
|
|
}
|
|
>
|
|
{unlocked && (
|
|
<div
|
|
className="absolute -top-6 -right-6 h-16 w-16 rounded-full blur-2xl opacity-40"
|
|
style={{ background: a.color }}
|
|
/>
|
|
)}
|
|
<div className="relative flex items-start gap-2">
|
|
{/* 图标徽章 */}
|
|
<div
|
|
className="shrink-0 h-8 w-8 rounded-lg flex items-center justify-center text-base"
|
|
style={{
|
|
background: unlocked ? `${a.color}22` : "rgba(255,255,255,0.04)",
|
|
border: `1px solid ${unlocked ? a.color + "55" : "rgba(255,255,255,0.08)"}`,
|
|
color: unlocked ? a.color : "#666",
|
|
boxShadow: unlocked ? `0 0 10px ${a.color}33` : "none",
|
|
}}
|
|
>
|
|
{unlocked ? a.icon : <Lock className="h-3.5 w-3.5" />}
|
|
</div>
|
|
{/* 文本 */}
|
|
<div className="min-w-0 flex-1">
|
|
<div
|
|
className="text-[11px] font-semibold truncate"
|
|
style={{ color: unlocked ? a.color : "rgba(255,255,255,0.5)" }}
|
|
>
|
|
{a.name}
|
|
</div>
|
|
<div className="text-[10px] text-muted-foreground/80 leading-tight mt-0.5">
|
|
{a.desc}
|
|
</div>
|
|
{unlocked && (
|
|
<div className="text-[9px] mt-1 font-mono" style={{ color: a.color + "cc" }}>
|
|
✦ {a.rewardText}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<style jsx global>{`
|
|
.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; }
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|