All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 3m9s
- Increase attunement mana conversion rates (0.2 -> 2 for Enchanter) - Hide mana types with current < 1 in ManaDisplay and LabTab - Only show owned equipment types when designing enchantments
552 lines
28 KiB
TypeScript
Executable File
552 lines
28 KiB
TypeScript
Executable File
'use client';
|
||
|
||
import { useGameStore, fmt, fmtDec, calcDamage, computePactMultiplier, computePactInsightMultiplier } from '@/lib/game/store';
|
||
import { ELEMENTS, GUARDIANS, SPELLS_DEF } from '@/lib/game/constants';
|
||
import { SKILL_EVOLUTION_PATHS, getTierMultiplier } from '@/lib/game/skill-evolution';
|
||
import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Separator } from '@/components/ui/separator';
|
||
import { Droplet, Swords, BookOpen, FlaskConical, RotateCcw, Trophy, Star } from 'lucide-react';
|
||
import type { SkillUpgradeChoice } from '@/lib/game/types';
|
||
|
||
export function StatsTab() {
|
||
const store = useGameStore();
|
||
const {
|
||
upgradeEffects, maxMana, baseRegen, clickMana,
|
||
meditationMultiplier, incursionStrength, manaCascadeBonus, effectiveRegen,
|
||
hasSteadyStream, hasManaTorrent, hasDesperateWells
|
||
} = useManaStats();
|
||
const { activeSpellDef, pactMultiplier, pactInsightMultiplier } = useCombatStats();
|
||
const { studySpeedMult, studyCostMult } = useStudyStats();
|
||
|
||
// Compute element max
|
||
const elemMax = (() => {
|
||
const ea = store.skillTiers?.elemAttune || 1;
|
||
const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune';
|
||
const level = store.skills[tieredSkillId] || store.skills.elemAttune || 0;
|
||
const tierMult = getTierMultiplier(tieredSkillId);
|
||
return 10 + level * 50 * tierMult + (store.prestigeUpgrades.elementalAttune || 0) * 25;
|
||
})();
|
||
|
||
// Get all selected skill upgrades
|
||
const getAllSelectedUpgrades = () => {
|
||
const upgrades: { skillId: string; upgrade: SkillUpgradeChoice }[] = [];
|
||
for (const [skillId, selectedIds] of Object.entries(store.skillUpgrades)) {
|
||
const baseSkillId = skillId.includes('_t') ? skillId.split('_t')[0] : skillId;
|
||
const path = SKILL_EVOLUTION_PATHS[baseSkillId];
|
||
if (!path) continue;
|
||
for (const tier of path.tiers) {
|
||
if (tier.skillId === skillId) {
|
||
for (const upgradeId of selectedIds) {
|
||
const upgrade = tier.upgrades.find(u => u.id === upgradeId);
|
||
if (upgrade) {
|
||
upgrades.push({ skillId, upgrade });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return upgrades;
|
||
};
|
||
|
||
const selectedUpgrades = getAllSelectedUpgrades();
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* Mana Stats */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-blue-400 game-panel-title text-xs flex items-center gap-2">
|
||
<Droplet className="w-4 h-4" />
|
||
Mana Stats
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Base Max Mana:</span>
|
||
<span className="text-gray-200">100</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Well Bonus:</span>
|
||
<span className="text-blue-300">
|
||
{(() => {
|
||
const mw = store.skillTiers?.manaWell || 1;
|
||
const tieredSkillId = mw > 1 ? `manaWell_t${mw}` : 'manaWell';
|
||
const level = store.skills[tieredSkillId] || store.skills.manaWell || 0;
|
||
const tierMult = getTierMultiplier(tieredSkillId);
|
||
return `+${fmt(level * 100 * tierMult)} (${level} lvl × 100 × ${tierMult}x tier)`;
|
||
})()}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Prestige Mana Well:</span>
|
||
<span className="text-blue-300">+{fmt((store.prestigeUpgrades.manaWell || 0) * 500)}</span>
|
||
</div>
|
||
{upgradeEffects.maxManaBonus > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-amber-400">Upgrade Mana Bonus:</span>
|
||
<span className="text-amber-300">+{fmt(upgradeEffects.maxManaBonus)}</span>
|
||
</div>
|
||
)}
|
||
{upgradeEffects.maxManaMultiplier > 1 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-amber-400">Upgrade Mana Multiplier:</span>
|
||
<span className="text-amber-300">×{fmtDec(upgradeEffects.maxManaMultiplier, 2)}</span>
|
||
</div>
|
||
)}
|
||
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
||
<span className="text-gray-300">Total Max Mana:</span>
|
||
<span className="text-blue-400">{fmt(maxMana)}</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Base Regen:</span>
|
||
<span className="text-gray-200">2/hr</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Flow Bonus:</span>
|
||
<span className="text-blue-300">
|
||
{(() => {
|
||
const mf = store.skillTiers?.manaFlow || 1;
|
||
const tieredSkillId = mf > 1 ? `manaFlow_t${mf}` : 'manaFlow';
|
||
const level = store.skills[tieredSkillId] || store.skills.manaFlow || 0;
|
||
const tierMult = getTierMultiplier(tieredSkillId);
|
||
return `+${fmtDec(level * 1 * tierMult)}/hr (${level} lvl × 1 × ${tierMult}x tier)`;
|
||
})()}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Spring Bonus:</span>
|
||
<span className="text-blue-300">+{(store.skills.manaSpring || 0) * 2}/hr</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Prestige Mana Flow:</span>
|
||
<span className="text-blue-300">+{fmtDec((store.prestigeUpgrades.manaFlow || 0) * 0.5)}/hr</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Temporal Echo:</span>
|
||
<span className="text-blue-300">×{fmtDec(1 + (store.prestigeUpgrades.temporalEcho || 0) * 0.1, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
||
<span className="text-gray-300">Base Regen:</span>
|
||
<span className="text-blue-400">{fmtDec(baseRegen, 2)}/hr</span>
|
||
</div>
|
||
{upgradeEffects.regenBonus > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-amber-400">Upgrade Regen Bonus:</span>
|
||
<span className="text-amber-300">+{fmtDec(upgradeEffects.regenBonus, 2)}/hr</span>
|
||
</div>
|
||
)}
|
||
{upgradeEffects.permanentRegenBonus > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-amber-400">Permanent Regen Bonus:</span>
|
||
<span className="text-amber-300">+{fmtDec(upgradeEffects.permanentRegenBonus, 2)}/hr</span>
|
||
</div>
|
||
)}
|
||
{upgradeEffects.regenMultiplier > 1 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-amber-400">Upgrade Regen Multiplier:</span>
|
||
<span className="text-amber-300">×{fmtDec(upgradeEffects.regenMultiplier, 2)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<Separator className="bg-gray-700 my-3" />
|
||
{/* Skill Upgrade Effects Summary */}
|
||
{upgradeEffects.activeUpgrades.length > 0 && (
|
||
<>
|
||
<div className="mb-2">
|
||
<span className="text-xs text-amber-400 game-panel-title">Active Skill Upgrades</span>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mb-3">
|
||
{upgradeEffects.activeUpgrades.map((upgrade, idx) => (
|
||
<div key={idx} className="flex justify-between text-xs bg-gray-800/50 rounded px-2 py-1">
|
||
<span className="text-gray-300">{upgrade.name}</span>
|
||
<span className="text-gray-400">{upgrade.desc}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<Separator className="bg-gray-700 my-3" />
|
||
</>
|
||
)}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Click Mana Value:</span>
|
||
<span className="text-purple-300">+{clickMana}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Tap Bonus:</span>
|
||
<span className="text-purple-300">+{store.skills.manaTap || 0}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Surge Bonus:</span>
|
||
<span className="text-purple-300">+{(store.skills.manaSurge || 0) * 3}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Mana Overflow:</span>
|
||
<span className="text-purple-300">×{fmtDec(1 + (store.skills.manaOverflow || 0) * 0.25, 2)}</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Meditation Multiplier:</span>
|
||
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-purple-400' : 'text-gray-300'}`}>
|
||
{fmtDec(meditationMultiplier, 2)}x
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Effective Regen:</span>
|
||
<span className="text-green-400 font-semibold">{fmtDec(effectiveRegen, 2)}/hr</span>
|
||
</div>
|
||
{incursionStrength > 0 && !hasSteadyStream && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-red-400">Incursion Penalty:</span>
|
||
<span className="text-red-400">-{Math.round(incursionStrength * 100)}%</span>
|
||
</div>
|
||
)}
|
||
{hasSteadyStream && incursionStrength > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-green-400">Steady Stream:</span>
|
||
<span className="text-green-400">Immune to incursion</span>
|
||
</div>
|
||
)}
|
||
{manaCascadeBonus > 0 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-cyan-400">Mana Cascade Bonus:</span>
|
||
<span className="text-cyan-400">+{fmtDec(manaCascadeBonus, 2)}/hr</span>
|
||
</div>
|
||
)}
|
||
{hasManaTorrent && store.rawMana > maxMana * 0.75 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-cyan-400">Mana Torrent:</span>
|
||
<span className="text-cyan-400">+50% regen (high mana)</span>
|
||
</div>
|
||
)}
|
||
{hasDesperateWells && store.rawMana < maxMana * 0.25 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-cyan-400">Desperate Wells:</span>
|
||
<span className="text-cyan-400">+50% regen (low mana)</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Combat Stats */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-red-400 game-panel-title text-xs flex items-center gap-2">
|
||
<Swords className="w-4 h-4" />
|
||
Combat Stats
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Active Spell Base Damage:</span>
|
||
<span className="text-gray-200">{activeSpellDef?.dmg || 5}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Combat Training Bonus:</span>
|
||
<span className="text-red-300">+{(store.skills.combatTrain || 0) * 5}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Arcane Fury Multiplier:</span>
|
||
<span className="text-red-300">×{fmtDec(1 + (store.skills.arcaneFury || 0) * 0.1, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Elemental Mastery:</span>
|
||
<span className="text-red-300">×{fmtDec(1 + (store.skills.elementalMastery || 0) * 0.15, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Guardian Bane:</span>
|
||
<span className="text-red-300">×{fmtDec(1 + (store.skills.guardianBane || 0) * 0.2, 2)} (vs guardians)</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Critical Hit Chance:</span>
|
||
<span className="text-amber-300">{((store.skills.precision || 0) * 5)}%</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Critical Multiplier:</span>
|
||
<span className="text-amber-300">1.5x</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Spell Echo Chance:</span>
|
||
<span className="text-amber-300">{((store.skills.spellEcho || 0) * 10)}%</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Pact Multiplier:</span>
|
||
<span className="text-amber-300">×{fmtDec(pactMultiplier, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm font-semibold border-t border-gray-700 pt-2">
|
||
<span className="text-gray-300">Total Damage:</span>
|
||
<span className="text-red-400">{fmt(calcDamage(store, store.activeSpell))}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Pact Status */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||
<Trophy className="w-4 h-4" />
|
||
Pact Status
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Pact Slots:</span>
|
||
<span className="text-amber-300">{store.signedPacts.length} / {1 + (store.prestigeUpgrades.pactCapacity || 0)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Damage Multiplier:</span>
|
||
<span className="text-amber-300">×{fmtDec(pactMultiplier, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Insight Multiplier:</span>
|
||
<span className="text-purple-300">×{fmtDec(pactInsightMultiplier, 2)}</span>
|
||
</div>
|
||
{store.signedPacts.length > 1 && (
|
||
<>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Interference Mitigation:</span>
|
||
<span className="text-green-300">{Math.min(store.pactInterferenceMitigation || 0, 5) * 10}%</span>
|
||
</div>
|
||
{(store.pactInterferenceMitigation || 0) >= 5 && (
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-cyan-400">Synergy Bonus:</span>
|
||
<span className="text-cyan-300">+{((store.pactInterferenceMitigation || 0) - 5) * 10}%</span>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="text-sm text-gray-400 mb-2">Unlocked Mana Types:</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{Object.entries(store.elements)
|
||
.filter(([, state]) => state.unlocked)
|
||
.map(([id]) => {
|
||
const elem = ELEMENTS[id];
|
||
return (
|
||
<Badge key={id} variant="outline" className="text-xs" style={{ borderColor: elem?.color, color: elem?.color }}>
|
||
{elem?.sym} {elem?.name}
|
||
</Badge>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Study Stats */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-purple-400 game-panel-title text-xs flex items-center gap-2">
|
||
<BookOpen className="w-4 h-4" />
|
||
Study Stats
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Study Speed:</span>
|
||
<span className="text-purple-300">×{fmtDec(studySpeedMult, 2)}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Quick Learner Bonus:</span>
|
||
<span className="text-purple-300">+{((store.skills.quickLearner || 0) * 10)}%</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Study Cost:</span>
|
||
<span className="text-purple-300">{Math.round(studyCostMult * 100)}%</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Focused Mind Bonus:</span>
|
||
<span className="text-purple-300">-{((store.skills.focusedMind || 0) * 5)}%</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Progress Retention:</span>
|
||
<span className="text-purple-300">{Math.round((1 + (store.skills.knowledgeRetention || 0) * 0.2) * 100)}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Element Stats */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-green-400 game-panel-title text-xs flex items-center gap-2">
|
||
<FlaskConical className="w-4 h-4" />
|
||
Element Stats
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Element Capacity:</span>
|
||
<span className="text-green-300">{elemMax}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Elem. Attunement Bonus:</span>
|
||
<span className="text-green-300">
|
||
{(() => {
|
||
const ea = store.skillTiers?.elemAttune || 1;
|
||
const tieredSkillId = ea > 1 ? `elemAttune_t${ea}` : 'elemAttune';
|
||
const level = store.skills[tieredSkillId] || store.skills.elemAttune || 0;
|
||
const tierMult = getTierMultiplier(tieredSkillId);
|
||
return `+${level * 50 * tierMult}`;
|
||
})()}
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Prestige Attunement:</span>
|
||
<span className="text-green-300">+{(store.prestigeUpgrades.elementalAttune || 0) * 25}</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Unlocked Elements:</span>
|
||
<span className="text-green-300">{Object.values(store.elements).filter(e => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
||
</div>
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Elem. Crafting Bonus:</span>
|
||
<span className="text-green-300">×{fmtDec(1 + (store.skills.elemCrafting || 0) * 0.25, 2)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Separator className="bg-gray-700 my-3" />
|
||
<div className="text-xs text-gray-400 mb-2">Elemental Mana Pools:</div>
|
||
<div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2">
|
||
{Object.entries(store.elements)
|
||
.filter(([, state]) => state.unlocked)
|
||
.map(([id, state]) => {
|
||
const def = ELEMENTS[id];
|
||
return (
|
||
<div key={id} className="p-2 rounded border border-gray-700 bg-gray-800/50 text-center">
|
||
<div className="text-lg">{def?.sym}</div>
|
||
<div className="text-xs text-gray-400">{state.current}/{state.max}</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Active Upgrades */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-amber-400 game-panel-title text-xs flex items-center gap-2">
|
||
<Star className="w-4 h-4" />
|
||
Active Skill Upgrades ({selectedUpgrades.length})
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{selectedUpgrades.length === 0 ? (
|
||
<div className="text-gray-500 text-sm">No skill upgrades selected yet. Level skills to 5 or 10 to choose upgrades.</div>
|
||
) : (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||
{selectedUpgrades.map(({ skillId, upgrade }) => (
|
||
<div key={upgrade.id} className="p-2 rounded border border-amber-600/30 bg-amber-900/10">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-amber-300 text-sm font-semibold">{upgrade.name}</span>
|
||
<Badge variant="outline" className="text-xs text-gray-400">
|
||
{SKILLS_DEF[skillId]?.name || skillId}
|
||
</Badge>
|
||
</div>
|
||
<div className="text-xs text-gray-400 mt-1">{upgrade.desc}</div>
|
||
{upgrade.effect.type === 'multiplier' && (
|
||
<div className="text-xs text-green-400 mt-1">
|
||
+{Math.round((upgrade.effect.value! - 1) * 100)}% {upgrade.effect.stat}
|
||
</div>
|
||
)}
|
||
{upgrade.effect.type === 'bonus' && (
|
||
<div className="text-xs text-blue-400 mt-1">
|
||
+{upgrade.effect.value} {upgrade.effect.stat}
|
||
</div>
|
||
)}
|
||
{upgrade.effect.type === 'special' && (
|
||
<div className="text-xs text-cyan-400 mt-1">
|
||
⚡ {upgrade.effect.specialDesc || 'Special effect active'}
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Loop Stats */}
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-purple-400 game-panel-title text-xs flex items-center gap-2">
|
||
<RotateCcw className="w-4 h-4" />
|
||
Loop Stats
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||
<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 text-center">
|
||
<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 text-center">
|
||
<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 text-center">
|
||
<div className="text-2xl font-bold text-green-400 game-mono">{store.maxFloorReached}</div>
|
||
<div className="text-xs text-gray-400">Max Floor</div>
|
||
</div>
|
||
</div>
|
||
<Separator className="bg-gray-700 my-3" />
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||
<div className="text-xl font-bold text-gray-300 game-mono">{Object.values(store.spells).filter(s => s.learned).length}</div>
|
||
<div className="text-xs text-gray-400">Spells Learned</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||
<div className="text-xl font-bold text-gray-300 game-mono">{Object.values(store.skills).reduce((a, b) => a + b, 0)}</div>
|
||
<div className="text-xs text-gray-400">Total Skill Levels</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||
<div className="text-xl font-bold text-gray-300 game-mono">{fmt(store.totalManaGathered)}</div>
|
||
<div className="text-xs text-gray-400">Total Mana Gathered</div>
|
||
</div>
|
||
<div className="p-3 bg-gray-800/50 rounded text-center">
|
||
<div className="text-xl font-bold text-gray-300 game-mono">{store.memorySlots}</div>
|
||
<div className="text-xs text-gray-400">Memory Slots</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|