"use client"; // 回响星核 / Echo Nexus — 角色属性面板(v0.7 P1) // // 无人机驾驶员四维属性可视化: // • 探索力 emerald / 智慧 fuchsia / 勇气 amber / 灵感 rose // • 显示数值(0-100)、加成百分比、经验进度条、等级 badge // • 待分配属性点时高亮 + 闪烁提示,「+」按钮可分配 1 点 // • 顶部待分配点数提示;底部总览 // • 小屏 2×2 网格,大屏 1×4 横排 import { useMemo } from "react"; import { useGameStore } from "@/store/gameStore"; import { ATTRIBUTE_CONFIG, ATTRIBUTE_KEYS, ATTRIBUTE_HARD_CAP, ATTRIBUTE_LINEAR_CAP, getAttributeBonus, getAllBonuses, totalAttributeLevel, totalAttributeBonusPct, expRequiredForLevel, type AttributeKey, } from "@/lib/game/attributes"; import { Compass, Brain, Swords, Sparkles, Plus, ChevronUp, type LucideIcon, } from "lucide-react"; const ICON_MAP: Record = { Compass, Brain, Swords, Sparkles, }; /** 主题色 → Tailwind/CSS 颜色映射 */ const COLOR_STYLES: Record< AttributeKey, { text: string; border: string; borderActive: string; bg: string; ring: string; barFrom: string; barTo: string; glow: string; } > = { exploration: { text: "text-emerald-300", border: "border-emerald-400/30", borderActive: "border-emerald-400/70", bg: "bg-emerald-500/10", ring: "ring-emerald-400/40", barFrom: "from-emerald-400", barTo: "to-emerald-500", glow: "rgba(52,211,153,0.45)", }, wisdom: { text: "text-fuchsia-300", border: "border-fuchsia-400/30", borderActive: "border-fuchsia-400/70", bg: "bg-fuchsia-500/10", ring: "ring-fuchsia-400/40", barFrom: "from-fuchsia-400", barTo: "to-fuchsia-500", glow: "rgba(232,121,249,0.45)", }, courage: { text: "text-amber-300", border: "border-amber-400/30", borderActive: "border-amber-400/70", bg: "bg-amber-500/10", ring: "ring-amber-400/40", barFrom: "from-amber-400", barTo: "to-amber-500", glow: "rgba(251,191,36,0.45)", }, inspiration: { text: "text-rose-300", border: "border-rose-400/30", borderActive: "border-rose-400/70", bg: "bg-rose-500/10", ring: "ring-rose-400/40", barFrom: "from-rose-400", barTo: "to-rose-500", glow: "rgba(251,113,133,0.45)", }, }; export function AttributesPanel() { const attributes = useGameStore((s) => s.attributes); const attributeProgress = useGameStore((s) => s.attributeProgress); const pendingAttrPoints = useGameStore((s) => s.pendingAttrPoints ?? 0); const allocateAttribute = useGameStore((s) => s.allocateAttribute); const bonuses = useMemo(() => getAllBonuses(attributes), [attributes]); const totalLvl = useMemo(() => totalAttributeLevel(attributes), [attributes]); const totalBonus = useMemo( () => totalAttributeBonusPct(attributes), [attributes] ); return (
{/* 顶部:标题 + 待分配点数 */}

驾驶员属性

0 ? "border-fuchsia-400/60 bg-fuchsia-500/15 text-fuchsia-200 echo-pending-pulse font-bold" : "border-white/15 bg-black/25 text-muted-foreground" }`} > 待分配 {pendingAttrPoints}
{/* 属性卡片网格:小屏 2×2,大屏 1×4 */}
{ATTRIBUTE_KEYS.map((key) => { const meta = ATTRIBUTE_CONFIG[key]; const cs = COLOR_STYLES[key]; const Icon = ICON_MAP[meta.icon] ?? Sparkles; const value = attributes?.[key] ?? 0; const prog = attributeProgress?.[key] ?? { exp: 0, level: value }; const bonusPct = getAttributeBonus(value) * 100; const nextExp = expRequiredForLevel(value); const curExp = prog.exp; const expPct = nextExp > 0 ? Math.min(100, (curExp / nextExp) * 100) : 100; const isMax = value >= ATTRIBUTE_HARD_CAP; const canAllocate = pendingAttrPoints > 0 && !isMax; // 区域标记 const inDiminishing = value > ATTRIBUTE_LINEAR_CAP; return (
{/* 顶部光晕装饰 */}
{/* 标题行:图标 + 名称 + 等级 badge */}
{meta.name}
{meta.enName}
Lv.{value}
{/* 数值 + 加成 */}
{value} /{ATTRIBUTE_HARD_CAP} +{bonusPct.toFixed(1)}%
{/* 经验进度条 */}
{isMax ? "已满级" : `${curExp} / ${nextExp} EXP`} {inDiminishing && !isMax && ( 递减区 )}
{/* 加成列表 */}
{meta.effects.map((eff, i) => (
· {eff}
))}
{/* 分配按钮 */}
); })}
{/* 底部:总览 */}
总等级 {totalLvl} /{ATTRIBUTE_HARD_CAP * 4}
总加成 +{totalBonus.toFixed(1)}%
{/* 当前生效修饰器(细节展示) */}
{/* 飞升获得属性点提示 */}
每次飞升获得 飞升次数 × 2 + 1 点属性点;完成对应活动自动累积经验
); } function DetailLine({ label, value, color, }: { label: string; value: string; color: string; }) { return (
{label} {value}
); }