refactor: consolidate all tab components into src/components/game/tabs/
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s
This commit is contained in:
Executable
+128
@@ -0,0 +1,128 @@
|
||||
'use client';
|
||||
|
||||
import { canAffordSpellCost, fmt } from '@/lib/game/stores';
|
||||
import { useCombatStore, useManaStore } from '@/lib/game/stores';
|
||||
import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
// Format spell cost for display
|
||||
function formatSpellCost(cost: { type: 'raw' | 'element'; element?: string; amount: number }): string {
|
||||
if (cost.type === 'raw') {
|
||||
return `${cost.amount} raw`;
|
||||
}
|
||||
const elemDef = ELEMENTS[cost.element || ''];
|
||||
return `${cost.amount} ${elemDef?.sym || '?'}`;
|
||||
}
|
||||
|
||||
// Get cost color
|
||||
function getSpellCostColor(cost: { type: 'raw' | 'element'; element?: string; amount: number }): string {
|
||||
if (cost.type === 'raw') {
|
||||
return '#60A5FA';
|
||||
}
|
||||
return ELEMENTS[cost.element || '']?.color || '#9CA3AF';
|
||||
}
|
||||
|
||||
export function SpellsTab() {
|
||||
const spells = useCombatStore((s) => s.spells);
|
||||
const activeSpell = useCombatStore((s) => s.activeSpell);
|
||||
const setSpell = useCombatStore((s) => s.setSpell);
|
||||
const rawMana = useManaStore((s) => s.rawMana);
|
||||
const elements = useManaStore((s) => s.elements);
|
||||
|
||||
const spellTiers = [0, 1, 2, 3, 4];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{spellTiers.map(tier => {
|
||||
const spellsInTier = Object.entries(SPELLS_DEF).filter(([, def]) => def.tier === tier);
|
||||
if (spellsInTier.length === 0) return null;
|
||||
|
||||
const tierNames = ['Basic Spells (Raw Mana)', 'Tier 1 - Elemental', 'Tier 2 - Advanced', 'Tier 3 - Master', 'Tier 4 - Legendary'];
|
||||
const tierColors = ['text-gray-400', 'text-green-400', 'text-blue-400', 'text-purple-400', 'text-amber-400'];
|
||||
|
||||
return (
|
||||
<div key={tier}>
|
||||
<h3 className={`text-lg font-semibold mb-3 ${tierColors[tier]}`}>{tierNames[tier]}</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{spellsInTier.map(([id, def]) => {
|
||||
const state = spells?.[id];
|
||||
const learned = state?.learned;
|
||||
const elemDef = def.elem === 'raw' ? null : ELEMENTS[def.elem];
|
||||
const isActive = activeSpell === id;
|
||||
const canCast = learned && canAffordSpellCost(def.cost, rawMana, elements);
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={id}
|
||||
className={`bg-gray-900/80 border-gray-700 ${learned ? '' : 'opacity-75'} ${canCast ? 'ring-1 ring-green-500/30' : ''}`}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm game-panel-title" style={{ color: def.elem === 'raw' ? '#60A5FA' : elemDef?.color }}>
|
||||
{def.name}
|
||||
</CardTitle>
|
||||
{def.tier > 0 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
T{def.tier}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="text-xs text-gray-400">
|
||||
{def.elem !== 'raw' && <span className="mr-2">{elemDef?.sym} {elemDef?.name}</span>}
|
||||
<span className="mr-2">⚔️ {def.dmg} dmg</span>
|
||||
</div>
|
||||
|
||||
{/* Cost display */}
|
||||
<div className="text-xs game-mono" style={{ color: getSpellCostColor(def.cost) }}>
|
||||
Cost: {formatSpellCost(def.cost)}
|
||||
</div>
|
||||
|
||||
{def.desc && (
|
||||
<div className="text-xs text-gray-500 italic">{def.desc}</div>
|
||||
)}
|
||||
|
||||
{def.effects && Array.isArray(def.effects) && def.effects.length > 0 && (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{def.effects.map((eff, i) => (
|
||||
<Badge key={i} variant="outline" className="text-xs">
|
||||
{eff.type === 'burn' && `🔥 Burn`}
|
||||
{eff.type === 'stun' && `⚡ Stun`}
|
||||
{eff.type === 'pierce' && `🎯 Pierce`}
|
||||
{eff.type === 'multicast' && `✨ Multicast`}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{learned ? (
|
||||
<div className="flex gap-2">
|
||||
<Badge className="bg-green-900/50 text-green-300">Learned</Badge>
|
||||
{isActive && <Badge className="bg-amber-900/50 text-amber-300">Active</Badge>}
|
||||
{!isActive && (
|
||||
<Button size="sm" variant="outline" onClick={() => setSpell(id)}>
|
||||
Set Active
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-gray-500">
|
||||
Not yet learned
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SpellsTab.displayName = "SpellsTab";
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import { usePrestigeStore, fmt, fmtDec } from '@/lib/game/stores';
|
||||
import { ELEMENTS } from '@/lib/game/constants';
|
||||
import { useManaStats, useCombatStats, useStudyStats } from '@/lib/game/hooks/useGameDerived';
|
||||
import { ManaStatsSection } from './StatsTab/ManaStatsSection';
|
||||
import { CombatStatsSection } from './StatsTab/CombatStatsSection';
|
||||
import { PactStatusSection } from './StatsTab/PactStatusSection';
|
||||
import { StudyStatsSection } from './StatsTab/StudyStatsSection';
|
||||
import { ElementStatsSection } from './StatsTab/ElementStatsSection';
|
||||
import { LoopStatsSection } from './StatsTab/LoopStatsSection';
|
||||
|
||||
export function StatsTab() {
|
||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||
|
||||
const manaStats = useManaStats();
|
||||
const combatStats = useCombatStats();
|
||||
const studyStats = useStudyStats();
|
||||
|
||||
// Compute element max (base + prestige only)
|
||||
const elemMax = 10 + (prestigeUpgrades.elementalAttune || 0) * 25;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<ManaStatsSection
|
||||
maxMana={manaStats.maxMana}
|
||||
baseRegen={manaStats.baseRegen}
|
||||
effectiveRegen={manaStats.effectiveRegen}
|
||||
clickMana={manaStats.clickMana}
|
||||
meditationMultiplier={manaStats.meditationMultiplier}
|
||||
upgradeEffects={manaStats.upgradeEffects}
|
||||
elemMax={elemMax}
|
||||
/>
|
||||
<CombatStatsSection
|
||||
activeSpellDef={combatStats.activeSpellDef}
|
||||
pactMultiplier={combatStats.pactMultiplier}
|
||||
/>
|
||||
<PactStatusSection
|
||||
pactMultiplier={combatStats.pactMultiplier}
|
||||
pactInsightMultiplier={combatStats.pactInsightMultiplier}
|
||||
/>
|
||||
<StudyStatsSection
|
||||
studySpeedMult={studyStats.studySpeedMult}
|
||||
studyCostMult={studyStats.studyCostMult}
|
||||
/>
|
||||
<ElementStatsSection
|
||||
elemMax={elemMax}
|
||||
/>
|
||||
<LoopStatsSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StatsTab.displayName = "StatsTab";
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Swords } from 'lucide-react';
|
||||
import { fmt, fmtDec } from '@/lib/game/stores';
|
||||
|
||||
interface CombatStatsSectionProps {
|
||||
activeSpellDef: any;
|
||||
pactMultiplier: number;
|
||||
}
|
||||
|
||||
export function CombatStatsSection({ activeSpellDef, pactMultiplier }: CombatStatsSectionProps) {
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--mana-fire)] 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 style={{ color: 'var(--text-muted)' }}>Active Spell Base Damage:</span>
|
||||
<span style={{ color: 'var(--text-secondary)' }}>{activeSpellDef?.dmg || 5}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Pact Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>×{fmtDec(pactMultiplier, 2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Total Damage:</span>
|
||||
<span style={{ color: 'var(--mana-fire)' }}>{fmt(activeSpellDef ? activeSpellDef.dmg * pactMultiplier : 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Critical Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>1.5x</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { FlaskConical } from 'lucide-react';
|
||||
import { ELEMENTS } from '@/lib/game/constants';
|
||||
import { fmt, fmtDec } from '@/lib/game/stores';
|
||||
import { usePrestigeStore, useManaStore } from '@/lib/game/stores';
|
||||
|
||||
interface ElementStatsSectionProps {
|
||||
elemMax: number;
|
||||
}
|
||||
|
||||
export function ElementStatsSection({ elemMax }: ElementStatsSectionProps) {
|
||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||
const elements = useManaStore((s) => s.elements);
|
||||
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--color-success)] 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 style={{ color: 'var(--text-muted)' }}>Element Capacity:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>{elemMax}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Prestige Attunement:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>+{(prestigeUpgrades.elementalAttune || 0) * 25}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Unlocked Elements:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>{Object.values(elements || {}).filter((e: any) => e.unlocked).length} / {Object.keys(ELEMENTS).length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="bg-[var(--border-subtle)] my-3" />
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Elemental Mana Pools:</div>
|
||||
<div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2">
|
||||
{Object.entries(elements)
|
||||
.filter(([, state]: [string, any]) => state.unlocked)
|
||||
.map(([id, state]: [string, any]) => {
|
||||
const def = ELEMENTS[id];
|
||||
return (
|
||||
<div key={id} className="p-2 rounded transition-colors" style={{ border: `1px solid ${def?.color}30`, background: 'var(--bg-sunken)/50', textAlign: 'center' }}>
|
||||
<div className="text-lg" style={{ color: def?.color }}>{def?.sym}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>{state.current}/{state.max}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
import { fmt } from '@/lib/game/stores';
|
||||
import { useCombatStore, usePrestigeStore, useManaStore } from '@/lib/game/stores';
|
||||
|
||||
export function LoopStatsSection() {
|
||||
const spells = useCombatStore((s) => s.spells);
|
||||
const insight = usePrestigeStore((s) => s.insight);
|
||||
const totalInsight = usePrestigeStore((s) => s.totalInsight);
|
||||
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
|
||||
const totalManaGathered = useManaStore((s) => s.totalManaGathered);
|
||||
const loopCount = usePrestigeStore((s) => s.loopCount);
|
||||
const memorySlots = usePrestigeStore((s) => s.memorySlots);
|
||||
|
||||
const spellsLearned = Object.values(spells || {}).filter((s: any) => s.learned).length;
|
||||
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--mana-light)] 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-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-2xl font-bold text-[var(--mana-light)] game-mono">{loopCount}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Loops Completed</div>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-2xl font-bold text-[var(--mana-crystal)] game-mono">{fmt(insight)}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Current Insight</div>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-2xl font-bold text-[var(--mana-death)] game-mono">{fmt(totalInsight)}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Total Insight</div>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-2xl font-bold text-[var(--color-success)] game-mono">{maxFloorReached}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Max Floor</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="bg-[var(--border-subtle)] my-3" />
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-xl font-bold text-[var(--text-secondary)] game-mono">{spellsLearned}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Spells Learned</div>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-xl font-bold text-[var(--text-secondary)] game-mono">{fmt(totalManaGathered)}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Total Mana Gathered</div>
|
||||
</div>
|
||||
<div className="p-3 bg-[var(--bg-sunken)]/50 rounded text-center">
|
||||
<div className="text-xl font-bold text-[var(--text-secondary)] game-mono">{memorySlots}</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-muted)' }}>Memory Slots</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
'use client';
|
||||
|
||||
import { fmt, fmtDec } from '@/lib/game/stores';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Droplet } from 'lucide-react';
|
||||
|
||||
interface ManaStatsSectionProps {
|
||||
maxMana: number;
|
||||
baseRegen: number;
|
||||
effectiveRegen: number;
|
||||
clickMana: number;
|
||||
meditationMultiplier: number;
|
||||
upgradeEffects: any;
|
||||
elemMax: number;
|
||||
}
|
||||
|
||||
export function ManaStatsSection({
|
||||
maxMana,
|
||||
baseRegen,
|
||||
effectiveRegen,
|
||||
clickMana,
|
||||
meditationMultiplier,
|
||||
upgradeEffects,
|
||||
elemMax,
|
||||
}: ManaStatsSectionProps) {
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--mana-water)] 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 style={{ color: 'var(--text-muted)' }}>Base Max Mana:</span>
|
||||
<span style={{ color: 'var(--text-secondary)' }}>100</span>
|
||||
</div>
|
||||
{upgradeEffects.maxManaBonus > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-light)' }}>Upgrade Mana Bonus:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>+{fmt(upgradeEffects.maxManaBonus)}</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.maxManaMultiplier > 1 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-light)' }}>Upgrade Mana Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>×{fmtDec(upgradeEffects.maxManaMultiplier, 2)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Total Max Mana:</span>
|
||||
<span style={{ color: 'var(--mana-water)' }}>{fmt(maxMana)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Base Regen:</span>
|
||||
<span style={{ color: 'var(--text-secondary)' }}>2/hr</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Base Regen:</span>
|
||||
<span style={{ color: 'var(--mana-water)' }}>{fmtDec(baseRegen, 2)}/hr</span>
|
||||
</div>
|
||||
{upgradeEffects.regenBonus > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-light)' }}>Upgrade Regen Bonus:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>+{fmtDec(upgradeEffects.regenBonus, 2)}/hr</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.permanentRegenBonus > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-light)' }}>Permanent Regen Bonus:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>+{fmtDec(upgradeEffects.permanentRegenBonus, 2)}/hr</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.regenMultiplier > 1 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-light)' }}>Upgrade Regen Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>×{fmtDec(upgradeEffects.regenMultiplier, 2)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Click Mana Value:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+{clickMana}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Meditation Multiplier:</span>
|
||||
<span className={`font-semibold ${meditationMultiplier > 1.5 ? 'text-[var(--mana-stellar)]' : 'text-[var(--text-secondary)]'}`}>
|
||||
{fmtDec(meditationMultiplier, 2)}x
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Incursion Strength:</span>
|
||||
<span style={{ color: 'var(--color-danger)' }}>{Math.round(upgradeEffects.incursionStrength * 100)}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm font-semibold border-t border-[var(--border-subtle)] pt-2">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Effective Regen:</span>
|
||||
<span style={{ color: 'var(--color-success)' }} className="font-semibold">{fmtDec(effectiveRegen, 2)}/hr</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Special Effects */}
|
||||
{(upgradeEffects.hasSteadyStream || upgradeEffects.hasManaTorrent ||
|
||||
upgradeEffects.hasDesperateWells || upgradeEffects.manaCascadeBonus > 0 ||
|
||||
upgradeEffects.manaWaterfallBonus > 0) && (
|
||||
<>
|
||||
<div className="mt-3 mb-2"><span className="text-xs text-[var(--mana-light)] game-panel-title">Special Effects</span></div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{upgradeEffects.hasSteadyStream && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Steady Stream:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>Immune to incursion</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.manaCascadeBonus > 0 && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Mana Cascade:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+{fmtDec(upgradeEffects.manaCascadeBonus, 2)}/hr</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.manaWaterfallBonus > 0 && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Mana Waterfall:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+{fmtDec(upgradeEffects.manaWaterfallBonus, 2)}/hr</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.hasFlowSurge && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Flow Surge:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>Clicks +100% regen for 1hr</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.hasManaOverflow && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Mana Overflow:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>Raw can exceed max by 20%</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.hasEternalFlow && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Eternal Flow:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>Regen immune to ALL penalties</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.hasManaTorrent && upgradeEffects.rawMana > maxMana * 0.75 && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Mana Torrent:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+50% regen (high mana)</span>
|
||||
</div>
|
||||
)}
|
||||
{upgradeEffects.hasDesperateWells && upgradeEffects.rawMana < maxMana * 0.25 && (
|
||||
<div className="flex justify-between text-xs bg-[var(--bg-sunken)]/50 rounded px-2 py-1">
|
||||
<span style={{ color: 'var(--text-secondary)' }}>Desperate Wells:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+50% regen (low mana)</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* Element Max */}
|
||||
<div className="mt-3 pt-3 border-t border-[var(--border-subtle)]">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Element Capacity:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>{elemMax}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Trophy } from 'lucide-react';
|
||||
import { fmtDec } from '@/lib/game/stores';
|
||||
import { ELEMENTS } from '@/lib/game/constants';
|
||||
import { usePrestigeStore, useManaStore } from '@/lib/game/stores';
|
||||
|
||||
interface PactStatusSectionProps {
|
||||
pactMultiplier: number;
|
||||
pactInsightMultiplier: number;
|
||||
}
|
||||
|
||||
export function PactStatusSection({ pactMultiplier, pactInsightMultiplier }: PactStatusSectionProps) {
|
||||
const signedPacts = usePrestigeStore((s) => s.signedPacts);
|
||||
const prestigeUpgrades = usePrestigeStore((s) => s.prestigeUpgrades);
|
||||
const elements = useManaStore((s) => s.elements);
|
||||
|
||||
const pactInterferenceMitigation = prestigeUpgrades?.pactInterferenceMitigation || 0;
|
||||
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--mana-light)] 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 style={{ color: 'var(--text-muted)' }}>Pact Slots:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>{signedPacts.length} / {1 + (prestigeUpgrades.pactCapacity || 0)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Damage Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-light)' }}>×{fmtDec(pactMultiplier, 2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Insight Multiplier:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>×{fmtDec(pactInsightMultiplier, 2)}</span>
|
||||
</div>
|
||||
{signedPacts.length > 1 && (
|
||||
<>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Interference Mitigation:</span>
|
||||
<span style={{ color: 'var(--color-success)' }}>{Math.min(pactInterferenceMitigation, 5) * 10}%</span>
|
||||
</div>
|
||||
{pactInterferenceMitigation >= 5 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>Synergy Bonus:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>+{(pactInterferenceMitigation - 5) * 10}%</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm" style={{ color: 'var(--text-muted)' }}>Unlocked Mana Types:</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{Object.keys(elements).map((id) => {
|
||||
const state = elements[id];
|
||||
if (!state.unlocked) return null;
|
||||
const elem = ELEMENTS[id];
|
||||
return (
|
||||
<span key={id} className="px-2 py-1 text-xs rounded border transition-colors" style={{ borderColor: `${elem?.color}60`, color: elem?.color }}>
|
||||
{elem?.sym} {elem?.name}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { BookOpen } from 'lucide-react';
|
||||
import { fmtDec } from '@/lib/game/stores';
|
||||
|
||||
interface StudyStatsSectionProps {
|
||||
studySpeedMult: number;
|
||||
studyCostMult: number;
|
||||
}
|
||||
|
||||
export function StudyStatsSection({ studySpeedMult, studyCostMult }: StudyStatsSectionProps) {
|
||||
return (
|
||||
<Card className="bg-[var(--bg-panel)] border-[var(--border-subtle)]">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-[var(--mana-crystal)] 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 style={{ color: 'var(--text-muted)' }}>Study Speed:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>×{fmtDec(studySpeedMult, 2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Study Cost:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>{Math.round(studyCostMult * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span style={{ color: 'var(--text-muted)' }}>Progress Retention:</span>
|
||||
<span style={{ color: 'var(--mana-crystal)' }}>100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
// Re-exports all existing tab components for lazy loading from page.tsx
|
||||
|
||||
export { DisciplinesTab } from './DisciplinesTab';
|
||||
export { SpellsTab } from '../SpellsTab';
|
||||
export { StatsTab } from '../StatsTab';
|
||||
export { SpellsTab } from './SpellsTab';
|
||||
export { StatsTab } from './StatsTab';
|
||||
|
||||
Reference in New Issue
Block a user