feat: recreate Achievements tab with category sections, progress tracking, and hidden achievement logic
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

This commit is contained in:
2026-05-19 14:44:27 +02:00
parent 50a9a62060
commit 639d396f80
15 changed files with 1396 additions and 4 deletions
@@ -0,0 +1,83 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Trophy, CheckCircle, RotateCcw } from 'lucide-react';
import { useCombatStore } from '@/lib/game/stores';
import { ACHIEVEMENTS } from '@/lib/game/data/achievements';
export function AchievementDebugSection() {
const achievements = useCombatStore((s) => s.achievements);
const unlockedCount = achievements?.unlocked?.length || 0;
const totalCount = Object.keys(ACHIEVEMENTS).length;
const handleUnlockAll = () => {
useCombatStore.setState({
achievements: {
unlocked: Object.keys(ACHIEVEMENTS),
progress: Object.fromEntries(
Object.keys(ACHIEVEMENTS).map((id) => [id, ACHIEVEMENTS[id].requirement.value])
),
},
});
};
const handleResetAll = () => {
useCombatStore.setState({
achievements: {
unlocked: [],
progress: {},
},
});
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-yellow-400 text-sm flex items-center gap-2">
<Trophy className="w-4 h-4" />
Achievement Debug
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="text-xs text-gray-400">
Unlocked: {unlockedCount} / {totalCount}
</div>
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={handleUnlockAll}>
<CheckCircle className="w-3 h-3 mr-1" /> Unlock All
</Button>
<Button size="sm" variant="destructive" onClick={handleResetAll}>
<RotateCcw className="w-3 h-3 mr-1" /> Reset All
</Button>
</div>
<div className="space-y-1 max-h-48 overflow-y-auto">
{Object.entries(ACHIEVEMENTS).map(([id, def]) => {
const isUnlocked = achievements?.unlocked?.includes(id);
return (
<div
key={id}
className={`flex items-center justify-between p-2 rounded text-xs ${
isUnlocked ? 'bg-green-900/20 border border-green-600/50' : 'bg-gray-800/50'
}`}
>
<div>
<span className="font-medium">{def.name}</span>
<span className="text-gray-500 ml-2">({def.category})</span>
</div>
{isUnlocked && (
<CheckCircle className="w-3 h-3 text-green-400" />
)}
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
AchievementDebugSection.displayName = "AchievementDebugSection";
@@ -0,0 +1,88 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Sparkles, Unlock } from 'lucide-react';
import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements';
import { useAttunementStore } from '@/lib/game/stores';
import { useManaStore } from '@/lib/game/stores';
export function AttunementDebugSection() {
const attunements = useAttunementStore((s) => s.attunements);
const debugUnlockAttunement = useAttunementStore((s) => s.debugUnlockAttunement);
const addAttunementXP = useAttunementStore((s) => s.addAttunementXP);
const handleUnlockAttunement = (id: string) => {
if (debugUnlockAttunement) {
debugUnlockAttunement(id);
if (id === 'enchanter') {
useManaStore.getState().unlockElement('transference', 0);
}
}
};
const handleAddAttunementXP = (id: string, amount: number) => {
if (addAttunementXP) {
addAttunementXP(id, amount);
}
};
const handleUnlockAll = () => {
Object.keys(ATTUNEMENTS_DEF).forEach((id) => {
handleUnlockAttunement(id);
});
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-purple-400 text-sm flex items-center gap-2">
<Sparkles className="w-4 h-4" />
Attunements
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<Button size="sm" variant="outline" onClick={handleUnlockAll}>
<Unlock className="w-3 h-3 mr-1" /> Unlock All
</Button>
{Object.entries(ATTUNEMENTS_DEF).map(([id, def]) => {
const isActive = attunements?.[id]?.active;
const level = attunements?.[id]?.level || 1;
const xp = attunements?.[id]?.experience || 0;
return (
<div key={id} className="flex items-center justify-between p-2 bg-gray-800/50 rounded">
<div className="flex items-center gap-2">
<span>{def.icon}</span>
<div>
<div className="text-sm font-medium">{def.name}</div>
{isActive && (
<div className="text-xs text-gray-400">Lv.{level} {xp} XP</div>
)}
</div>
</div>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleUnlockAttunement(id)}
>
<Unlock className="w-3 h-3 mr-1" /> Unlock
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleAddAttunementXP(id, 100)}
>
+100 XP
</Button>
</div>
</div>
);
})}
</CardContent>
</Card>
);
}
AttunementDebugSection.displayName = "AttunementDebugSection";
@@ -0,0 +1,135 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { BookOpen, Plus, Pause, Play } from 'lucide-react';
import { useDisciplineStore } from '@/lib/game/stores/discipline-slice';
import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines';
export function DisciplineDebugSection() {
const disciplines = useDisciplineStore((s) => s.disciplines);
const activeIds = useDisciplineStore((s) => s.activeIds);
const concurrentLimit = useDisciplineStore((s) => s.concurrentLimit);
const activate = useDisciplineStore((s) => s.activate);
const deactivate = useDisciplineStore((s) => s.deactivate);
const handleTogglePause = (id: string) => {
const disc = disciplines[id];
if (!disc) return;
if (disc.paused) {
activate(id);
} else {
deactivate(id);
// Re-activate with paused false — just activate again
activate(id);
}
};
const handleAddXP = (id: string, amount: number) => {
useDisciplineStore.setState((s) => {
const disc = s.disciplines[id];
if (!disc) return s;
return {
disciplines: {
...s.disciplines,
[id]: { ...disc, xp: disc.xp + amount },
},
};
});
};
const handleActivateAll = () => {
ALL_DISCIPLINES.forEach((d) => {
if (!activeIds.includes(d.id)) {
activate(d.id);
}
});
};
const handleDeactivateAll = () => {
activeIds.forEach((id) => {
deactivate(id);
});
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-indigo-400 text-sm flex items-center gap-2">
<BookOpen className="w-4 h-4" />
Disciplines
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex gap-2 flex-wrap mb-2">
<Button size="sm" variant="outline" onClick={handleActivateAll}>
<Play className="w-3 h-3 mr-1" /> Activate All
</Button>
<Button size="sm" variant="outline" onClick={handleDeactivateAll}>
<Pause className="w-3 h-3 mr-1" /> Deactivate All
</Button>
</div>
<div className="text-xs text-gray-400">
Active: {activeIds.length} / {concurrentLimit}
</div>
<div className="space-y-2 max-h-64 overflow-y-auto">
{ALL_DISCIPLINES.map((def) => {
const disc = disciplines[def.id];
const isActive = activeIds.includes(def.id);
const xp = disc?.xp || 0;
const isPaused = disc?.paused ?? true;
return (
<div
key={def.id}
className="flex items-center justify-between p-2 bg-gray-800/50 rounded"
>
<div>
<div className="text-sm font-medium">{def.name}</div>
<div className="text-xs text-gray-400">
{isActive ? `XP: ${xp}` : 'Inactive'}
</div>
</div>
<div className="flex gap-1">
<Button
size="sm"
variant="outline"
onClick={() => handleAddXP(def.id, 100)}
>
<Plus className="w-3 h-3 mr-1" /> +100
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleAddXP(def.id, 1000)}
>
+1K
</Button>
<Button
size="sm"
variant={isActive ? 'default' : 'outline'}
onClick={() => {
if (isActive) {
deactivate(def.id);
} else {
activate(def.id);
}
}}
>
{isActive ? (
<Pause className="w-3 h-3" />
) : (
<Play className="w-3 h-3" />
)}
</Button>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
DisciplineDebugSection.displayName = "DisciplineDebugSection";
@@ -0,0 +1,92 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Star, Lock } from 'lucide-react';
import { useManaStore } from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants';
export function ElementDebugSection() {
const elements = useManaStore((s) => s.elements);
const handleUnlockElement = (element: string) => {
useManaStore.getState().unlockElement(element, 0);
};
const handleAddElementalMana = (element: string, amount: number) => {
const elem = elements?.[element];
if (elem?.unlocked) {
useManaStore.getState().addElementMana(element, amount, elem.max);
}
};
const handleUnlockAll = () => {
Object.keys(elements || {}).forEach((id) => {
if (!elements[id]?.unlocked) {
handleUnlockElement(id);
}
});
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-green-400 text-sm flex items-center gap-2">
<Star className="w-4 h-4" />
Elemental Mana
</CardTitle>
</CardHeader>
<CardContent>
<div className="mb-3">
<Button size="sm" variant="outline" onClick={handleUnlockAll}>
<Lock className="w-3 h-3 mr-1" /> Unlock All Elements
</Button>
</div>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2">
{Object.entries(elements || {}).map(([id, elem]) => {
const def = ELEMENTS[id];
return (
<div
key={id}
className={`p-2 rounded border text-center ${
elem.unlocked ? 'border-gray-600 bg-gray-800/50' : 'border-gray-800 opacity-60'
}`}
style={{
borderColor: elem.unlocked ? def?.color : undefined
}}
>
<div className="text-lg">{def?.sym}</div>
<div className="text-xs text-gray-400">{def?.name}</div>
<div className="text-xs text-gray-300 mt-1">
{elem.current}/{elem.max}
</div>
{!elem.unlocked && (
<Button
size="sm"
variant="outline"
className="mt-2"
onClick={() => handleUnlockElement(id)}
>
<Lock className="w-3 h-3 mr-1" /> Unlock
</Button>
)}
{elem.unlocked && (
<Button
size="sm"
variant="outline"
className="mt-2"
onClick={() => handleAddElementalMana(id, 10)}
>
+10
</Button>
)}
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
ElementDebugSection.displayName = "ElementDebugSection";
@@ -0,0 +1,235 @@
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import {
RotateCcw, AlertTriangle, Zap, Clock, Eye,
} from 'lucide-react';
import { useDebug } from '@/components/game/debug/debug-context';
import { useGameStore, useManaStore, useUIStore, useCombatStore } from '@/lib/game/stores';
import { computeMaxMana } from '@/lib/game/stores';
export function GameStateDebugSection() {
const [confirmReset, setConfirmReset] = useState(false);
const { showComponentNames, toggleComponentNames } = useDebug();
const rawMana = useManaStore((s) => s.rawMana);
const day = useGameStore((s) => s.day);
const hour = useGameStore((s) => s.hour);
const paused = useUIStore((s) => s.paused);
const togglePause = useUIStore((s) => s.togglePause);
const resetGame = useGameStore((s) => s.resetGame);
const gatherMana = useGameStore((s) => s.gatherMana);
const debugSetFloor = useCombatStore((s) => s.debugSetFloor);
const resetFloorHP = useCombatStore((s) => s.resetFloorHP);
const elements = useManaStore((s) => s.elements);
const unlockElement = useManaStore((s) => s.unlockElement);
const handleReset = () => {
if (confirmReset) {
resetGame();
setConfirmReset(false);
} else {
setConfirmReset(true);
setTimeout(() => setConfirmReset(false), 3000);
}
};
const handleAddMana = (amount: number) => {
for (let i = 0; i < amount; i++) {
gatherMana();
}
};
const getMaxMana = () => {
return computeMaxMana(
{ skills: {}, prestigeUpgrades: {}, skillUpgrades: {}, skillTiers: {} }
);
};
return (
<div className="space-y-4">
{/* Display Options */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
<Eye className="w-4 h-4" />
Display Options
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="show-component-names" className="text-sm">Show Component Names</Label>
<p className="text-xs text-gray-400">
Display component names at the top of each component for debugging
</p>
</div>
<Switch
id="show-component-names"
checked={showComponentNames}
onCheckedChange={toggleComponentNames}
/>
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Game Reset */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-red-400 text-sm flex items-center gap-2">
<RotateCcw className="w-4 h-4" />
Game Reset
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-gray-400">
Reset all game progress and start fresh. This cannot be undone.
</p>
<Button
className={`w-full ${confirmReset ? 'bg-red-600 hover:bg-red-700' : 'bg-gray-700 hover:bg-gray-600'}`}
onClick={handleReset}
>
{confirmReset ? (
<>
<AlertTriangle className="w-4 h-4 mr-2" />
Click Again to Confirm Reset
</>
) : (
<>
<RotateCcw className="w-4 h-4 mr-2" />
Reset Game
</>
)}
</Button>
</CardContent>
</Card>
{/* Mana Debug */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-blue-400 text-sm flex items-center gap-2">
<Zap className="w-4 h-4" />
Mana Debug
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="text-xs text-gray-400 mb-2">
Current: {rawMana} / {getMaxMana() || '?'}
</div>
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={() => handleAddMana(10)}>
<Zap className="w-3 h-3 mr-1" /> +10
</Button>
<Button size="sm" variant="outline" onClick={() => handleAddMana(100)}>
<Zap className="w-3 h-3 mr-1" /> +100
</Button>
<Button size="sm" variant="outline" onClick={() => handleAddMana(1000)}>
<Zap className="w-3 h-3 mr-1" /> +1K
</Button>
<Button size="sm" variant="outline" onClick={() => handleAddMana(10000)}>
<Zap className="w-3 h-3 mr-1" /> +10K
</Button>
</div>
<Separator className="bg-gray-700" />
<div className="text-xs text-gray-400 mb-2">Fill to max:</div>
<Button
size="sm"
className="w-full bg-blue-600 hover:bg-blue-700"
onClick={() => {
const max = getMaxMana() || 100;
useManaStore.setState((s) => ({ rawMana: Math.max(s.rawMana, max) }));
}}
>
Fill Mana
</Button>
</CardContent>
</Card>
{/* Time Control */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm flex items-center gap-2">
<Clock className="w-4 h-4" />
Time Control
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="text-xs text-gray-400">
Current: Day {day}, Hour {hour}
</div>
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 1, hour: 0 })}>
Day 1
</Button>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 10, hour: 0 })}>
Day 10
</Button>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 20, hour: 0 })}>
Day 20
</Button>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 30, hour: 0 })}>
Day 30
</Button>
</div>
<Separator className="bg-gray-700" />
<div className="flex gap-2">
<Button size="sm" variant="outline" onClick={togglePause}>
{paused ? '▶ Resume' : '⏸ Pause'}
</Button>
</div>
</CardContent>
</Card>
{/* Quick Actions */}
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-cyan-400 text-sm flex items-center gap-2">
<Zap className="w-4 h-4" />
Quick Actions
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex gap-2 flex-wrap">
<Button
size="sm"
variant="outline"
onClick={() => {
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
if (!elements[e]?.unlocked) {
unlockElement(e, 0);
}
});
}}
>
Unlock All Base Elements
</Button>
<Button
size="sm"
variant="outline"
onClick={() => debugSetFloor?.(100)}
>
Skip to Floor 100
</Button>
<Button
size="sm"
variant="outline"
onClick={() => resetFloorHP?.()}
>
Reset Floor HP
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
);
}
GameStateDebugSection.displayName = "GameStateDebugSection";
@@ -0,0 +1,81 @@
'use client';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Bug, Wand2 } from 'lucide-react';
import { useCombatStore } from '@/lib/game/stores';
import { GOLEMS_DEF } from '@/lib/game/data/golems';
export function GolemDebugSection() {
const golemancy = useCombatStore((s) => s.golemancy);
const setEnabledGolems = useCombatStore((s) => s.setEnabledGolems);
const enabledGolems = golemancy?.enabledGolems || [];
const handleEnableAll = () => {
setEnabledGolems(Object.keys(GOLEMS_DEF));
};
const handleDisableAll = () => {
setEnabledGolems([]);
};
const handleToggleGolem = (golemId: string) => {
if (enabledGolems.includes(golemId)) {
setEnabledGolems(enabledGolems.filter(id => id !== golemId));
} else {
setEnabledGolems([...enabledGolems, golemId]);
}
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-orange-400 text-sm flex items-center gap-2">
<Bug className="w-4 h-4" />
Golem Debug
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={handleEnableAll}>
<Wand2 className="w-3 h-3 mr-1" /> Enable All Golems
</Button>
<Button size="sm" variant="outline" onClick={handleDisableAll}>
Disable All Golems
</Button>
</div>
<div className="text-xs text-gray-400">
Enabled: {enabledGolems.length} / {Object.keys(GOLEMS_DEF).length}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 max-h-48 overflow-y-auto">
{Object.entries(GOLEMS_DEF).map(([id, def]) => {
const isEnabled = enabledGolems.includes(id);
return (
<div
key={id}
className={`p-2 rounded border flex items-center justify-between ${
isEnabled ? 'border-orange-600/50 bg-orange-900/20' : 'border-gray-700'
}`}
>
<div>
<div className="text-sm font-medium">{def.name}</div>
<div className="text-xs text-gray-400">{def.element}</div>
</div>
<Button
size="sm"
variant={isEnabled ? 'default' : 'outline'}
onClick={() => handleToggleGolem(id)}
>
{isEnabled ? 'On' : 'Off'}
</Button>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}
GolemDebugSection.displayName = "GolemDebugSection";
@@ -0,0 +1,161 @@
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Bug } from 'lucide-react';
import { usePrestigeStore, useManaStore, useUIStore, useGameStore } from '@/lib/game/stores';
import { GUARDIANS, ELEMENTS } from '@/lib/game/constants';
export function PactDebugSection() {
const signedPacts = usePrestigeStore((s) => s.signedPacts);
const signedPactDetails = usePrestigeStore((s) => s.signedPactDetails);
const elements = useManaStore((s) => s.elements);
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
const addSignedPact = usePrestigeStore((s) => s.addSignedPact);
const removePact = usePrestigeStore((s) => s.removePact);
const debugSetSignedPacts = usePrestigeStore((s) => s.debugSetSignedPacts);
const debugSetPactDetails = usePrestigeStore((s) => s.debugSetPactDetails);
const unlockElement = useManaStore((s) => s.unlockElement);
const addLog = useUIStore((s) => s.addLog);
const guardianFloors = Object.keys(GUARDIANS || {}).map(Number).sort((a, b) => a - b);
const forcePact = (floor: number) => {
const guardian = GUARDIANS[floor];
if (!guardian) return;
if (signedPacts.includes(floor)) {
addLog(`⚠️ Already signed pact with ${guardian.name}!`);
return;
}
const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0);
if (signedPacts.length >= maxPacts) {
addLog(`⚠️ Cannot sign more pacts! Maximum: ${maxPacts}.`);
return;
}
addSignedPact(floor);
const newSignedPactDetails = {
...signedPactDetails,
[floor]: {
floor,
guardianId: guardian.element,
signedAt: { day: useGameStore.getState().day, hour: useGameStore.getState().hour },
skillLevels: {} as Record<string, number>,
},
};
debugSetPactDetails(newSignedPactDetails);
addLog(`📜 DEBUG: Pact with ${guardian.name} force-signed!`);
};
const removePactHandler = (floor: number) => {
const guardian = GUARDIANS[floor];
removePact(floor);
const newSignedPactDetails = { ...signedPactDetails };
delete newSignedPactDetails[floor];
debugSetPactDetails(newSignedPactDetails);
addLog(`📜 DEBUG: Removed pact with ${guardian?.name || 'Unknown'}!`);
};
const signAllPacts = () => {
guardianFloors.forEach((floor) => {
if (!signedPacts.includes(floor)) {
forcePact(floor);
}
});
};
const clearAllPacts = () => {
addLog(`📜 DEBUG: Cleared all pacts!`);
debugSetSignedPacts([]);
debugSetPactDetails({});
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-orange-400 text-sm flex items-center gap-2">
<Bug className="w-4 h-4" />
Pact Debug
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={signAllPacts}>
Sign All Pacts
</Button>
<Button size="sm" variant="destructive" onClick={clearAllPacts}>
Clear All Pacts ({signedPacts.length})
</Button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{guardianFloors.map((floor) => {
const guardian = GUARDIANS[floor];
const isSigned = signedPacts.includes(floor);
return (
<div
key={floor}
className={`p-2 rounded border flex items-center justify-between ${
isSigned ? 'border-green-600/50 bg-green-900/20' : 'border-gray-700'
}`}
style={{ borderColor: isSigned ? undefined : guardian.color, borderWidth: '1px' }}
>
<div>
<div className="text-sm font-semibold" style={{ color: guardian.color }}>
{guardian.name}
</div>
<div className="text-xs text-gray-400">
Floor {floor} | {guardian.pact}x multiplier
</div>
<div className="text-xs text-gray-500">
Element: {ELEMENTS[guardian.element]?.name || guardian.element}
</div>
</div>
<div className="flex gap-1">
{isSigned ? (
<Button
size="sm"
variant="destructive"
onClick={() => removePactHandler(floor)}
className="text-xs"
>
Remove
</Button>
) : (
<Button
size="sm"
variant="default"
onClick={() => forcePact(floor)}
className="text-xs bg-amber-600 hover:bg-amber-700"
>
Force Sign
</Button>
)}
</div>
</div>
);
})}
</div>
<div className="text-xs text-gray-400 pt-2 border-t border-gray-700">
Signed Pacts: {signedPacts.length} |
Max Pacts: {1 + (prestigeUpgrades?.pactCapacity || 0)}
</div>
</div>
</CardContent>
</Card>
);
}
PactDebugSection.displayName = "PactDebugSection";
@@ -0,0 +1,106 @@
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Castle, ArrowUp, Eye } from 'lucide-react';
import { useCombatStore } from '@/lib/game/stores';
export function SpireDebugSection() {
const [floorInput, setFloorInput] = useState('50');
const currentFloor = useCombatStore((s) => s.currentFloor);
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const spireMode = useCombatStore((s) => s.spireMode);
const debugSetFloor = useCombatStore((s) => s.debugSetFloor);
const resetFloorHP = useCombatStore((s) => s.resetFloorHP);
const enterSpireMode = useCombatStore((s) => s.enterSpireMode);
const exitSpireMode = useCombatStore((s) => s.exitSpireMode);
const setMaxFloorReached = useCombatStore((s) => s.setMaxFloorReached);
const handleJumpToFloor = () => {
const floor = parseInt(floorInput, 10);
if (isNaN(floor) || floor < 1 || floor > 100) return;
debugSetFloor(floor);
setMaxFloorReached(floor);
};
const handleClearFloor = () => {
resetFloorHP();
};
const handleToggleSpireMode = () => {
if (spireMode) {
exitSpireMode();
} else {
enterSpireMode();
}
};
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-teal-400 text-sm flex items-center gap-2">
<Castle className="w-4 h-4" />
Spire Debug
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="text-xs text-gray-400">
Current Floor: {currentFloor} | Max Reached: {maxFloorReached} | Spire Mode: {spireMode ? 'ON' : 'OFF'}
</div>
<div className="flex gap-2 items-end">
<div className="flex-1">
<label className="text-xs text-gray-400 mb-1 block">Floor (1-100)</label>
<Input
type="number"
min={1}
max={100}
value={floorInput}
onChange={(e) => setFloorInput(e.target.value)}
className="h-8"
/>
</div>
<Button size="sm" variant="outline" onClick={handleJumpToFloor}>
<ArrowUp className="w-3 h-3 mr-1" /> Jump
</Button>
</div>
<div className="flex gap-2 flex-wrap">
<Button size="sm" variant="outline" onClick={handleClearFloor}>
Reset Floor HP
</Button>
<Button
size="sm"
variant={spireMode ? 'default' : 'outline'}
onClick={handleToggleSpireMode}
>
<Eye className="w-3 h-3 mr-1" />
{spireMode ? 'Exit Spire Mode' : 'Enter Spire Mode'}
</Button>
</div>
<div className="flex gap-2 flex-wrap">
{[10, 25, 50, 75, 100].map((f) => (
<Button
key={f}
size="sm"
variant="outline"
onClick={() => {
setFloorInput(String(f));
debugSetFloor(f);
setMaxFloorReached(f);
}}
>
Floor {f}
</Button>
))}
</div>
</CardContent>
</Card>
);
}
SpireDebugSection.displayName = "SpireDebugSection";