c8a01acda3
1. Fixed exitSpireMode in store.ts to allow exit at any floor for re-entry resume 2. Removed floor restriction on Exit Spire button in page.tsx 3. Updated descend button label to use currentAction for 'Climbing' status
621 lines
27 KiB
TypeScript
Executable File
621 lines
27 KiB
TypeScript
Executable File
'use client';
|
||
|
||
import { useEffect, useState, lazy, Suspense } from 'react';
|
||
import { useGameStore, useGameLoop, fmt, getFloorElement, computeMaxMana, computeRegen, computeClickMana, getMeditationBonus, getIncursionStrength, canAffordSpellCost } from '@/lib/game/store';
|
||
import { ActivityLogEntry } from '@/lib/game/types';
|
||
import { getActiveEquipmentSpells, getTotalDPS } from '@/lib/game/computed-stats';
|
||
|
||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, PRESTIGE_DEF, getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/constants';
|
||
|
||
import { getUnifiedEffects, hasSpecial, SPECIAL_EFFECTS } from '@/lib/game/effects';
|
||
|
||
import { Button } from '@/components/ui/button';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||
import { RotateCcw, Mountain, ChevronDown } from 'lucide-react';
|
||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||
import { DebugName } from '@/lib/game/debug-context';
|
||
// Non-tab component imports
|
||
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
||
// Loot and Achievements moved to separate tabs
|
||
|
||
// Lazy load tab components
|
||
const SpireTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.SpireTab })));
|
||
const SkillsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.SkillsTab })));
|
||
const SpellsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.SpellsTab })));
|
||
const LabTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.LabTab })));
|
||
const StatsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.StatsTab })));
|
||
const EquipmentTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.EquipmentTab })));
|
||
const AttunementsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.AttunementsTab })));
|
||
const DebugTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.DebugTab })));
|
||
const LootTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.LootTab })));
|
||
const AchievementsTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.AchievementsTab })));
|
||
const GolemancyTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.GolemancyTab })));
|
||
const CraftingTab = lazy(() => import('@/components/game/tabs').then(module => ({ default: module.CraftingTab })));
|
||
|
||
// Loading fallback component
|
||
const TabLoadingFallback = () => <div className="p-4 text-center text-gray-400">Loading...</div>;
|
||
|
||
export default function ManaLoopGame() {
|
||
const [activeTab, setActiveTab] = useState('spire');
|
||
const [isGathering, setIsGathering] = useState(false);
|
||
|
||
// Game store
|
||
const store = useGameStore();
|
||
const gameLoop = useGameLoop();
|
||
|
||
// Computed effects from upgrades and equipment
|
||
const upgradeEffects = getUnifiedEffects(store);
|
||
|
||
// Derived stats
|
||
const maxMana = computeMaxMana(store, upgradeEffects);
|
||
const baseRegen = computeRegen(store, upgradeEffects);
|
||
const clickMana = computeClickMana(store);
|
||
const floorElem = getFloorElement(store.currentFloor);
|
||
const floorElemDef = ELEMENTS[floorElem];
|
||
const isGuardianFloor = !!GUARDIANS[store.currentFloor];
|
||
const currentGuardian = GUARDIANS[store.currentFloor];
|
||
const meditationMultiplier = getMeditationBonus(store.meditateTicks, store.skills, upgradeEffects.meditationEfficiency);
|
||
const incursionStrength = getIncursionStrength(store.day, store.hour);
|
||
const studySpeedMult = getStudySpeedMultiplier(store.skills);
|
||
const studyCostMult = getStudyCostMultiplier(store.skills);
|
||
|
||
// Effective regen with incursion penalty
|
||
const effectiveRegenWithSpecials = baseRegen * (1 - incursionStrength);
|
||
|
||
// Mana Cascade bonus
|
||
const manaCascadeBonus = hasSpecial(upgradeEffects, SPECIAL_EFFECTS.MANA_CASCADE)
|
||
? Math.floor(maxMana / 100) * 0.1
|
||
: 0;
|
||
|
||
// Effective regen
|
||
const effectiveRegen = (effectiveRegenWithSpecials + manaCascadeBonus) * meditationMultiplier;
|
||
|
||
// Get all active spells from equipment
|
||
const activeEquipmentSpells = getActiveEquipmentSpells(store.equippedInstances, store.equipmentInstances);
|
||
|
||
// Compute total DPS
|
||
const totalDPS = getTotalDPS(store, upgradeEffects, floorElem);
|
||
|
||
// Auto-gather while holding
|
||
useEffect(() => {
|
||
if (!isGathering) return;
|
||
|
||
let lastGatherTime = 0;
|
||
const minGatherInterval = 100;
|
||
let animationFrameId: number;
|
||
|
||
const gatherLoop = (timestamp: number) => {
|
||
if (timestamp - lastGatherTime >= minGatherInterval) {
|
||
store.gatherMana();
|
||
lastGatherTime = timestamp;
|
||
}
|
||
animationFrameId = requestAnimationFrame(gatherLoop);
|
||
};
|
||
|
||
animationFrameId = requestAnimationFrame(gatherLoop);
|
||
return () => cancelAnimationFrame(animationFrameId);
|
||
}, [isGathering, store]);
|
||
|
||
// Handle gather button events
|
||
const handleGatherStart = () => {
|
||
setIsGathering(true);
|
||
store.gatherMana();
|
||
};
|
||
|
||
const handleGatherEnd = () => {
|
||
setIsGathering(false);
|
||
};
|
||
|
||
// Start game loop
|
||
useEffect(() => {
|
||
const cleanup = gameLoop.start();
|
||
return cleanup;
|
||
}, [gameLoop]);
|
||
|
||
// Check if spell can be cast
|
||
const canCastSpell = (spellId: string): boolean => {
|
||
const spell = SPELLS_DEF[spellId];
|
||
if (!spell) return false;
|
||
return canAffordSpellCost(spell.cost, store.rawMana, store.elements);
|
||
};
|
||
|
||
// Game Over Screen
|
||
if (store.gameOver) {
|
||
return (
|
||
<div className="fixed inset-0 game-overlay flex items-center justify-center z-50">
|
||
<Card className="bg-gray-900 border-gray-600 max-w-md w-full mx-4 shadow-2xl">
|
||
<CardHeader>
|
||
<CardTitle className={`text-3xl text-center game-title ${store.victory ? 'text-amber-400' : 'text-red-400'}`}>
|
||
{store.victory ? 'VICTORY!' : 'LOOP ENDS'}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<p className="text-center text-gray-400">
|
||
{store.victory
|
||
? 'The Awakened One falls! Your power echoes through eternity.'
|
||
: 'The time loop resets... but you remember.'}
|
||
</p>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="p-3 bg-gray-800 rounded">
|
||
<div className="text-xl font-bold text-amber-400 game-mono">{fmt(store.loopInsight)}</div>
|
||
<div className="text-xs text-gray-400">Insight Gained</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800 rounded">
|
||
<div className="text-xl font-bold text-blue-400 game-mono">{store.maxFloorReached}</div>
|
||
<div className="text-xs text-gray-400">Best Floor</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800 rounded">
|
||
<div className="text-xl font-bold text-purple-400 game-mono">{store.signedPacts.length}</div>
|
||
<div className="text-xs text-gray-400">Pacts Signed</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800 rounded">
|
||
<div className="text-xl font-bold text-green-400 game-mono">{store.loopCount + 1}</div>
|
||
<div className="text-xs text-gray-400">Total Loops</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
|
||
size="lg"
|
||
onClick={() => store.startNewLoop()}
|
||
>
|
||
Begin New Loop
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<TooltipProvider>
|
||
<div className="game-root min-h-screen flex flex-col">
|
||
{/* Header */}
|
||
<header className="sticky top-0 z-50 bg-gradient-to-b from-gray-900 to-gray-900/80 border-b border-gray-700 px-4 py-2">
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-xl font-bold game-title tracking-wider">MANA LOOP</h1>
|
||
|
||
<div className="flex items-center gap-4">
|
||
<TimeDisplay
|
||
day={store.day}
|
||
hour={store.hour}
|
||
insight={store.insight}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Main Content */}
|
||
<main className="flex-1 flex flex-col md:flex-row gap-4 p-4">
|
||
{/* Left Panel - Mana & Actions */}
|
||
<div className="md:w-80 space-y-4 flex-shrink-0">
|
||
{/* Mana Display */}
|
||
<DebugName name="ManaDisplay">
|
||
<ManaDisplay
|
||
rawMana={store.rawMana}
|
||
maxMana={maxMana}
|
||
effectiveRegen={effectiveRegen}
|
||
meditationMultiplier={meditationMultiplier}
|
||
clickMana={clickMana}
|
||
isGathering={isGathering}
|
||
onGatherStart={handleGatherStart}
|
||
onGatherEnd={handleGatherEnd}
|
||
elements={store.elements}
|
||
/>
|
||
</DebugName>
|
||
|
||
{/* Climb the Spire Button - only show when not in Spire Mode */}
|
||
{!store.spireMode && (
|
||
<DebugName name="ClimbSpireButton">
|
||
<Button
|
||
className="w-full bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700"
|
||
size="lg"
|
||
onClick={() => store.enterSpireMode()}
|
||
>
|
||
<Mountain className="w-5 h-5 mr-2" />
|
||
Climb the Spire
|
||
</Button>
|
||
</DebugName>
|
||
)}
|
||
|
||
{/* Action Buttons - only show when not in Spire Mode */}
|
||
{!store.spireMode && (
|
||
<DebugName name="ActionButtons">
|
||
<ActionButtons
|
||
currentAction={store.currentAction}
|
||
currentStudyTarget={store.currentStudyTarget}
|
||
designProgress={store.designProgress}
|
||
designProgress2={store.designProgress2}
|
||
preparationProgress={store.preparationProgress}
|
||
applicationProgress={store.applicationProgress}
|
||
equipmentCraftingProgress={store.equipmentCraftingProgress}
|
||
/>
|
||
</DebugName>
|
||
)}
|
||
|
||
{/* Calendar */}
|
||
<DebugName name="CalendarDisplay">
|
||
<CalendarDisplay
|
||
day={store.day}
|
||
hour={store.hour}
|
||
incursionStrength={incursionStrength}
|
||
/>
|
||
</DebugName>
|
||
|
||
{/* Loot and Achievements moved to tabs */}
|
||
</div>
|
||
|
||
{/* Right Panel - Conditional rendering based on Spire Mode */}
|
||
{store.spireMode ? (
|
||
/* Spire Mode - Simplified UI */
|
||
<div className="flex-1 min-w-0 space-y-4">
|
||
<DebugName name="SpireModeUI">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h2 className="text-2xl font-bold game-title text-amber-400">
|
||
🏔️ Spire Mode - Floor {store.currentFloor}
|
||
</h2>
|
||
<div className="flex gap-2 items-center">
|
||
{/* Show Climbing indicator when actively climbing */}
|
||
{store.currentAction === 'climb' && !store.isDescending && (
|
||
<Badge className="bg-green-900/50 text-green-300 border-green-600">
|
||
Climbing
|
||
</Badge>
|
||
)}
|
||
<Button
|
||
variant="outline"
|
||
className="border-blue-600/50 text-blue-400 hover:bg-blue-900/20"
|
||
onClick={() => store.climbDownFloor()}
|
||
disabled={store.isDescending}
|
||
>
|
||
<ChevronDown className="w-4 h-4 mr-2" />
|
||
{store.isDescending ? 'Descending…' :
|
||
store.currentAction === 'climb' ? 'Climbing' :
|
||
'Begin Descent'}
|
||
</Button>
|
||
<Button
|
||
variant="default"
|
||
className="bg-green-600 hover:bg-green-700"
|
||
onClick={() => store.exitSpireMode()}
|
||
>
|
||
Exit Spire
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<SpireTab store={store} simpleMode={true} />
|
||
</Suspense>
|
||
|
||
{/* Activity Log for Spire Mode */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs">Activity Log</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<ScrollArea className="h-48">
|
||
<div className="space-y-1">
|
||
{(store.activityLog || []).slice(0, 50).map((entry: ActivityLogEntry, i) => {
|
||
// Style based on event type
|
||
const getEventStyle = (eventType: string) => {
|
||
switch (eventType) {
|
||
case 'enemy_defeated':
|
||
case 'floor_cleared':
|
||
return 'text-green-400';
|
||
case 'damage_dealt':
|
||
return 'text-red-400';
|
||
case 'dodge':
|
||
return 'text-yellow-400';
|
||
case 'armor_proc':
|
||
return 'text-blue-400';
|
||
case 'special_effect':
|
||
return 'text-purple-400';
|
||
case 'floor_transition':
|
||
return 'text-cyan-400';
|
||
case 'spell_cast':
|
||
return 'text-amber-400';
|
||
case 'golem_attack':
|
||
return 'text-orange-400';
|
||
case 'puzzle_solved':
|
||
return 'text-pink-400';
|
||
default:
|
||
return 'text-gray-300';
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div
|
||
key={entry.id}
|
||
className={`text-xs ${i === 0 ? 'text-gray-200 font-semibold' : getEventStyle(entry.eventType)}`}
|
||
>
|
||
{entry.message}
|
||
</div>
|
||
);
|
||
})}
|
||
{(store.activityLog || []).length === 0 && (
|
||
<div className="text-xs text-gray-500 italic">No activity yet...</div>
|
||
)}
|
||
</div>
|
||
</ScrollArea>
|
||
</CardContent>
|
||
</Card>
|
||
</DebugName>
|
||
</div>
|
||
) : (
|
||
/* Normal Mode - Tabs */
|
||
<div className="flex-1 min-w-0">
|
||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
||
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
||
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
||
<TabsTrigger value="golemancy" className="text-xs px-2 py-1">🗿 Golems</TabsTrigger>
|
||
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
||
<TabsTrigger value="crafting" className="text-xs px-2 py-1">🔧 Craft</TabsTrigger>
|
||
<TabsTrigger value="loot" className="text-xs px-2 py-1">💎 Loot</TabsTrigger>
|
||
<TabsTrigger value="achievements" className="text-xs px-2 py-1">🏆 Achieve</TabsTrigger>
|
||
<TabsTrigger value="lab" className="text-xs px-2 py-1">🔬 Lab</TabsTrigger>
|
||
<TabsTrigger value="stats" className="text-xs px-2 py-1">📊 Stats</TabsTrigger>
|
||
<TabsTrigger value="debug" className="text-xs px-2 py-1">🔧 Debug</TabsTrigger>
|
||
<TabsTrigger value="grimoire" className="text-xs px-2 py-1">📖 Grimoire</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="spire">
|
||
<DebugName name="SpireTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<SpireTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="attunements">
|
||
<DebugName name="AttunementsTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<AttunementsTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="golemancy">
|
||
<DebugName name="GolemancyTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<GolemancyTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="skills">
|
||
<DebugName name="SkillsTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<SkillsTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="spells">
|
||
<DebugName name="SpellsTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<SpellsTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="equipment">
|
||
<DebugName name="EquipmentTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<EquipmentTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="crafting">
|
||
<DebugName name="CraftingTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<CraftingTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="loot">
|
||
<DebugName name="LootTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<LootTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="achievements">
|
||
<DebugName name="AchievementsTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<AchievementsTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="lab">
|
||
<DebugName name="LabTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<LabTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="stats">
|
||
<DebugName name="StatsTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<StatsTab
|
||
store={store}
|
||
upgradeEffects={upgradeEffects}
|
||
maxMana={maxMana}
|
||
baseRegen={baseRegen}
|
||
clickMana={clickMana}
|
||
meditationMultiplier={meditationMultiplier}
|
||
effectiveRegen={effectiveRegen}
|
||
incursionStrength={incursionStrength}
|
||
manaCascadeBonus={manaCascadeBonus}
|
||
studySpeedMult={studySpeedMult}
|
||
studyCostMult={studyCostMult}
|
||
/>
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="grimoire">
|
||
<DebugName name="GrimoireTab">
|
||
{renderGrimoireTab()}
|
||
</DebugName>
|
||
</TabsContent>
|
||
|
||
<TabsContent value="debug">
|
||
<DebugName name="DebugTab">
|
||
<Suspense fallback={<TabLoadingFallback />}>
|
||
<DebugTab store={store} />
|
||
</Suspense>
|
||
</DebugName>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
)}
|
||
</main>
|
||
</div>
|
||
</TooltipProvider>
|
||
);
|
||
|
||
// Grimoire Tab (Prestige)
|
||
function renderGrimoireTab() {
|
||
return (
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||
{/* Current Status */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs">Loop Status</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-3">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="p-3 bg-gray-800/50 rounded">
|
||
<div className="text-2xl font-bold text-amber-400 game-mono">{store.loopCount}</div>
|
||
<div className="text-xs text-gray-400">Loops Completed</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded">
|
||
<div className="text-2xl font-bold text-purple-400 game-mono">{fmt(store.insight)}</div>
|
||
<div className="text-xs text-gray-400">Current Insight</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded">
|
||
<div className="text-2xl font-bold text-blue-400 game-mono">{fmt(store.totalInsight)}</div>
|
||
<div className="text-xs text-gray-400">Total Insight</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded">
|
||
<div className="text-2xl font-bold text-green-400 game-mono">{store.memorySlots}</div>
|
||
<div className="text-xs text-gray-400">Memory Slots</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Signed Pacts */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs">Signed Pacts</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{store.signedPacts.length === 0 ? (
|
||
<div className="text-gray-500 text-sm">No pacts signed yet. Defeat guardians to earn pacts.</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
{store.signedPacts.map((floor) => {
|
||
const guardian = GUARDIANS[floor];
|
||
if (!guardian) return null;
|
||
return (
|
||
<div
|
||
key={floor}
|
||
className="flex items-center justify-between p-2 rounded border"
|
||
style={{ borderColor: guardian.color, backgroundColor: `${guardian.color}15` }}
|
||
>
|
||
<div>
|
||
<div className="font-semibold text-sm" style={{ color: guardian.color }}>
|
||
{guardian.name}
|
||
</div>
|
||
<div className="text-xs text-gray-400">Floor {floor}</div>
|
||
</div>
|
||
<Badge className="bg-amber-900/50 text-amber-300">
|
||
{guardian.pact}x multiplier
|
||
</Badge>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Prestige Upgrades */}
|
||
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs">Insight Upgrades (Permanent)</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
{Object.entries(PRESTIGE_DEF).map(([id, def]) => {
|
||
const level = store.prestigeUpgrades[id] || 0;
|
||
const maxed = level >= def.max;
|
||
const canBuy = !maxed && store.insight >= def.cost;
|
||
|
||
return (
|
||
<div
|
||
key={id}
|
||
className="p-3 rounded border border-gray-700 bg-gray-800/50"
|
||
>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div className="font-semibold text-amber-400 text-sm">{def.name}</div>
|
||
<Badge variant="outline" className="text-xs">
|
||
{level}/{def.max}
|
||
</Badge>
|
||
</div>
|
||
<div className="text-xs text-gray-400 italic mb-2">{def.desc}</div>
|
||
<Button
|
||
size="sm"
|
||
variant={canBuy ? 'default' : 'outline'}
|
||
className="w-full"
|
||
disabled={!canBuy}
|
||
onClick={() => store.doPrestige(id)}
|
||
>
|
||
{maxed ? 'Maxed' : `Upgrade (${fmt(def.cost)} insight)`}
|
||
</Button>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Reset Game Button */}
|
||
<div className="mt-4 pt-4 border-t border-gray-700">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-sm text-gray-400">Reset All Progress</div>
|
||
<div className="text-xs text-gray-500">Clear all data and start fresh</div>
|
||
</div>
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
className="border-red-600/50 text-red-400 hover:bg-red-900/20"
|
||
onClick={() => {
|
||
if (confirm('Are you sure you want to reset ALL progress? This cannot be undone!')) {
|
||
store.resetGame();
|
||
}
|
||
}}
|
||
>
|
||
<RotateCcw className="w-4 h-4 mr-1" />
|
||
Reset
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
}
|