'use client'; import React from 'react'; import { ELEMENTS } from '@/lib/game/constants'; import type { GuardianDef, GuardianBoon } from '@/lib/game/types'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Shield, Swords, Clock, Sparkles, Check, Lock, ChevronRight, Heart, Hexagon } from 'lucide-react'; import clsx from 'clsx'; import { DebugName } from '@/components/game/debug/debug-context'; // ─── Types ─────────────────────────────────────────────────────────────────── export type GuardianStatus = 'undefeated' | 'defeated' | 'signed'; export interface FloorTier { label: string; floors: number[]; } // ─── Element Display Helper ────────────────────────────────────────────────── interface ElementDisplay { sym: string; name: string; color: string; } function getElementDisplays(element: string | string[]): ElementDisplay[] { const parts = Array.isArray(element) ? element : element.split('+'); return parts.map((el) => { const def = ELEMENTS[el]; return { sym: def?.sym ?? '?', name: def?.name ?? el, color: def?.color ?? '#888', }; }); } // ─── Guardian Card ─────────────────────────────────────────────────────────── interface GuardianCardProps { floor: number; guardian: GuardianDef; status: GuardianStatus; canAfford: boolean; hasSlot: boolean; isRitualActive: boolean; ritualProgress: number; onStartRitual: (floor: number) => void; } export const GuardianCard: React.FC = React.memo(({ floor, guardian, status, canAfford, hasSlot, isRitualActive, ritualProgress, onStartRitual, }) => { const elemDisplays = getElementDisplays(guardian.element); const primaryColor = elemDisplays[0]?.color ?? '#888'; const isCombo = elemDisplays.length > 1; const statusConfig: Record = { undefeated: { label: 'Undefeated', color: 'text-gray-400', bg: 'bg-gray-800/50' }, defeated: { label: 'Pact Available', color: 'text-amber-400', bg: 'bg-amber-900/20' }, signed: { label: 'Pact Signed', color: 'text-green-400', bg: 'bg-green-900/20' }, }; const sc = statusConfig[status]; const ritualTime = guardian.pactTime; const ritualComplete = ritualProgress >= ritualTime; // Build element label: single element name, or "Fire + Water" for combos const elementLabel = isCombo ? elemDisplays.map(e => e.name).join(' + ') : elemDisplays[0]?.name ?? guardian.element.join(' + '); return (
{elemDisplays.map((e, i) => ( {e.sym} ))} {guardian.name}
Floor {floor} · {elementLabel} {isCombo && ✦ Combo}
{sc.label}
Perk: {guardian.uniquePerk}
{ritualTime}h
Cost: {guardian.pactCost.toLocaleString()} mana
{isRitualActive && ( )} {status === 'defeated' && !isRitualActive && ( onStartRitual(floor)} /> )}
); }); GuardianCard.displayName = 'GuardianCard'; // ─── Guardian Stats ────────────────────────────────────────────────────────── function GuardianStats({ guardian }: { guardian: GuardianDef }) { const hasShield = !!(guardian.shield && guardian.shield > 0); const hasBarrier = !!(guardian.barrier && guardian.barrier > 0); const hasHealthRegen = !!(guardian.healthRegen && guardian.healthRegen > 0); return (
Health: {guardian.hp.toLocaleString()}
Power: {guardian.power.toLocaleString()}
Armor: {Math.round((guardian.armor ?? 0) * 100)}%
{(hasShield || hasBarrier || hasHealthRegen) && (
{hasShield && (
Shield: {guardian.shield!.toLocaleString()}
)} {hasBarrier && (
Barrier: {Math.round(guardian.barrier! * 100)}%
)} {hasHealthRegen && (
Regen: {guardian.healthRegenIsPercent ? guardian.healthRegen + '%/tick' : guardian.healthRegen + '/tick'}
)}
)}
); } // ─── Guardian Boons ────────────────────────────────────────────────────────── function GuardianBoons({ guardian }: { guardian: GuardianDef }) { return (
Boons
{guardian.boons.map((boon: GuardianBoon, i: number) => ( {boon.desc} ))}
); } // ─── Ritual Progress ───────────────────────────────────────────────────────── function RitualProgress({ ritualProgress, ritualTime, ritualComplete }: { ritualProgress: number; ritualTime: number; ritualComplete: boolean }) { return (
Ritual in progress… {ritualProgress}/{ritualTime}h
{ritualComplete && (
Ritual complete — pact will be signed on next tick
)}
); } // ─── Pact Action Button ────────────────────────────────────────────────────── function PactActionButton({ canAfford, hasSlot, onStartRitual }: { canAfford: boolean; hasSlot: boolean; onStartRitual: () => void }) { const disabled = !canAfford || !hasSlot; return ( ); } // ─── Pact Header Summary ──────────────────────────────────────────────────── export function PactHeaderSummary({ signedCount, pactSlots, defeatedCount, cumulativeBoons, }: { signedCount: number; pactSlots: number; defeatedCount: number; cumulativeBoons: Record; }) { return (
Pact Slots: {signedCount} / {pactSlots}
Signed: {signedCount}
Defeated: {defeatedCount}
{signedCount > 0 && (
Active Boon Effects:
{Object.entries(cumulativeBoons).map(([type, value]) => ( {type}: +{value} ))}
)}
); } // ─── Tier Filter ───────────────────────────────────────────────────────────── export function TierFilter({ tiers, activeTier, guardianFloors, onSelectTier, }: { tiers: FloorTier[]; activeTier: string; guardianFloors: number[]; onSelectTier: (tier: string) => void; }) { return (
{tiers.map((tier) => ( ))}
); }