124 lines
4.5 KiB
TypeScript
Executable File
124 lines
4.5 KiB
TypeScript
Executable File
'use client';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Zap, ChevronDown, ChevronUp } from 'lucide-react';
|
|
import { fmt, fmtDec } from '@/lib/game/store';
|
|
import { ELEMENTS } from '@/lib/game/constants';
|
|
import { useState } from 'react';
|
|
|
|
interface ManaDisplayProps {
|
|
rawMana: number;
|
|
maxMana: number;
|
|
effectiveRegen: number;
|
|
meditationMultiplier: number;
|
|
clickMana: number;
|
|
isGathering: boolean;
|
|
onGatherStart: () => void;
|
|
onGatherEnd: () => void;
|
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>;
|
|
}
|
|
|
|
export function ManaDisplay({
|
|
rawMana,
|
|
maxMana,
|
|
effectiveRegen,
|
|
meditationMultiplier,
|
|
clickMana,
|
|
isGathering,
|
|
onGatherStart,
|
|
onGatherEnd,
|
|
elements,
|
|
}: ManaDisplayProps) {
|
|
const [expanded, setExpanded] = useState(true);
|
|
|
|
// Get unlocked elements with current > 0, sorted by current amount
|
|
const unlockedElements = Object.entries(elements)
|
|
.filter(([, state]) => state.unlocked && state.current > 0)
|
|
.sort((a, b) => b[1].current - a[1].current);
|
|
|
|
return (
|
|
<Card className="bg-gray-900/80 border-gray-700">
|
|
<CardContent className="pt-4 space-y-3">
|
|
{/* Raw Mana - Main Display */}
|
|
<div>
|
|
<div className="flex items-baseline gap-1">
|
|
<span className="text-3xl font-bold game-mono text-blue-400">{fmt(rawMana)}</span>
|
|
<span className="text-sm text-gray-400">/ {fmt(maxMana)}</span>
|
|
</div>
|
|
<div className="text-xs text-gray-400">
|
|
+{fmtDec(effectiveRegen)} mana/hr {meditationMultiplier > 1.01 && <span className="text-purple-400">({fmtDec(meditationMultiplier, 1)}x med)</span>}
|
|
</div>
|
|
</div>
|
|
|
|
<Progress
|
|
value={(rawMana / maxMana) * 100}
|
|
className="h-2 bg-gray-800"
|
|
/>
|
|
|
|
<Button
|
|
className={`w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 ${isGathering ? 'animate-pulse' : ''}`}
|
|
onMouseDown={onGatherStart}
|
|
onMouseUp={onGatherEnd}
|
|
onMouseLeave={onGatherEnd}
|
|
onTouchStart={onGatherStart}
|
|
onTouchEnd={onGatherEnd}
|
|
>
|
|
<Zap className="w-4 h-4 mr-2" />
|
|
Gather +{clickMana} Mana
|
|
{isGathering && <span className="ml-2 text-xs">(Holding...)</span>}
|
|
</Button>
|
|
|
|
{/* Elemental Mana Pools */}
|
|
{unlockedElements.length > 0 && (
|
|
<div className="border-t border-gray-700 pt-3 mt-3">
|
|
<button
|
|
onClick={() => setExpanded(!expanded)}
|
|
className="flex items-center justify-between w-full text-xs text-gray-400 hover:text-gray-300 mb-2"
|
|
>
|
|
<span>Elemental Mana ({unlockedElements.length})</span>
|
|
{expanded ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
|
</button>
|
|
|
|
{expanded && (
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{unlockedElements.map(([id, state]) => {
|
|
const elem = ELEMENTS[id];
|
|
if (!elem) return null;
|
|
|
|
return (
|
|
<div
|
|
key={id}
|
|
className="p-2 rounded bg-gray-800/50 border border-gray-700"
|
|
>
|
|
<div className="flex items-center gap-1 mb-1">
|
|
<span style={{ color: elem.color }}>{elem.sym}</span>
|
|
<span className="text-xs font-medium" style={{ color: elem.color }}>
|
|
{elem.name}
|
|
</span>
|
|
</div>
|
|
<div className="h-1.5 bg-gray-700 rounded-full overflow-hidden mb-1">
|
|
<div
|
|
className="h-full rounded-full transition-all"
|
|
style={{
|
|
width: `${Math.min(100, (state.current / state.max) * 100)}%`,
|
|
backgroundColor: elem.color
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-gray-400 game-mono">
|
|
{fmt(state.current)}/{fmt(state.max)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|