ce084a61a3
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
- GuardianPactsTab: extracted GuardianCard, PactHeaderSummary, TierFilter + 5 helper components into guardian-pacts-components.tsx - SpireSummaryTab: extracted TopStatsRow, NextGuardianCard, GuardianRoster, GuardianRosterItem, FloorLegend - PrestigeTab: extracted InsightSummary, MemoriesCard, PactsCard, ResetLoopSection - GameStateDebug: extracted WarningBanner, DisplayOptions, GameResetSection, ManaDebugSection, TimeControlSection, QuickActionsSection - EquipmentCrafter: extracted CraftingProgress, BlueprintCard, BlueprintList, MaterialCard, MaterialsInventory - PactDebug: extracted GuardianPactRow, GuardianPactList - GameStateDebugSection: extracted DisplayOptions, GameResetSection, ManaDebugSection, TimeControlSection, QuickActionsSection - PactDebugSection: extracted GuardianPactRow - SpireCombatPage: extracted useSpireStats hook - page.tsx: extracted GrimoireTab to separate file, useGameDerivedStats hook, TabTriggers, LazyTab wrapper All files now under 400 lines. Build passes. All 639 tests pass.
78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { DebugName } from '@/components/game/debug/debug-context';
|
|
import { SPELLS_DEF } from '@/lib/game/constants';
|
|
import type { SpellDef } from '@/lib/game/types';
|
|
|
|
export function GrimoireTab() {
|
|
const [grimoireSpells, setGrimoireSpells] = useState<[string, SpellDef][]>([]);
|
|
const [loaded, setLoaded] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined' && SPELLS_DEF) {
|
|
setGrimoireSpells(
|
|
Object.entries(SPELLS_DEF).filter((entry): entry is [string, SpellDef] => !!entry[1].grimoire)
|
|
);
|
|
}
|
|
setLoaded(true);
|
|
}, []);
|
|
|
|
if (!loaded) {
|
|
return <div className="p-4 text-center text-gray-400">Loading grimoire...</div>;
|
|
}
|
|
|
|
if (grimoireSpells.length === 0) {
|
|
return (
|
|
<div className="p-4 text-center text-gray-400">
|
|
No grimoire spells available yet. Defeat guardians to unlock spells.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const availablePages = Math.ceil(grimoireSpells.length / 12);
|
|
|
|
return (
|
|
<DebugName name="GrimoireTab">
|
|
<div className="space-y-4">
|
|
<div className="text-sm text-gray-400">
|
|
<p className="mb-2">A vast tome of arcane knowledge. Study carefully — each spell costs insight to transcribe into your repertoire.</p>
|
|
<p>Available pages: {availablePages}. Spells in grimoire: {grimoireSpells.length}.</p>
|
|
</div>
|
|
|
|
<ScrollArea className="h-[600px] rounded border border-gray-700 p-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{grimoireSpells.map(([id, spell]) => (
|
|
<div
|
|
key={id}
|
|
className="p-4 bg-gray-800/50 rounded border border-gray-600 hover:border-gray-500 transition-colors"
|
|
>
|
|
<div className="flex items-start justify-between mb-2">
|
|
<span className="font-bold text-gray-100">{spell.name}</span>
|
|
<Badge variant="outline" className="border-gray-600">
|
|
{spell.elem}
|
|
</Badge>
|
|
</div>
|
|
{spell.desc && <p className="text-sm text-gray-400 mb-3">{spell.desc}</p>}
|
|
<div className="text-xs text-gray-500 space-y-1">
|
|
<div>Cost: {spell.cost.amount} {
|
|
spell.cost.type === 'element'
|
|
? spell.cost.element
|
|
: 'raw mana'
|
|
}</div>
|
|
<div>Power: {spell.dmg}</div>
|
|
{spell.effects && spell.effects.length > 0 && (
|
|
<div>Effects: {spell.effects.map(e => e.type).join(', ')}</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
</DebugName>
|
|
);
|
|
}
|