feat(ui): complete Task 4 UI redesign — all sub-tasks 1-10
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 8m47s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 8m47s
- Implemented complete design system with 40+ CSS custom properties - Created 9 UI primitives (GameCard, SectionHeader, StatRow, ManaBar, ElementBadge, ValueDisplay, ActionButton, SkillRow, TooltipInfo) - Redesigned all tabs: Spire, Skills, Stats, Equipment, Crafting, Attunements, Golemancy, Spells, Loot, Achievements, Lab, Debug - Added toast notification system (GameToast) with success/warning/error/info types - Added confirmation dialogs for destructive actions - Removed all dev artifacts and component name labels - Added empty states to all tabs - Replaced emoji icons with Lucide React icons - Added enchantPower placeholder to StatsTab and EquipmentTab - Mobile audit passed at 375px viewport - Build passes with 0 errors, lint passes with 0 errors Sub-tasks completed: - ST1: Design System Implementation - ST2: Global Layout & Header - ST3: Left Panel (Mana Display & Action Area) - ST4: Skills Tab - ST5: Spire Tab & Spire Mode UI - ST6: Stats Tab - ST7: Equipment & Crafting Tabs - ST8: Attunements Tab - ST9: Remaining Tabs - ST10: Toast System & Confirmation Dialogs Documentation: 15+ files in docs/task4/
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { GameCard, ElementBadge } from '@/components/ui';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ELEMENTS, SPELLS_DEF } from '@/lib/game/constants';
|
||||
import { ENCHANTMENT_EFFECTS } from '@/lib/game/data/enchantment-effects';
|
||||
@@ -53,12 +52,16 @@ export function SpellsTab({ store }: SpellsTabProps) {
|
||||
return canAffordSpellCost(spell.cost, store.rawMana, store.elements);
|
||||
};
|
||||
|
||||
const hasPactSpells = store.signedPacts.length > 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Equipment-Granted Spells */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-3 text-cyan-400">✨ Known Spells</h3>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
<h3 className="text-lg font-[var(--font-heading)] font-semibold mb-3 text-[var(--mana-crystal)]">
|
||||
Known Spells
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||
Spells are obtained by enchanting equipment with spell effects.
|
||||
Visit the Crafting tab to design and apply enchantments.
|
||||
</p>
|
||||
@@ -75,61 +78,90 @@ export function SpellsTab({ store }: SpellsTabProps) {
|
||||
const sources = spellSources[id] || [];
|
||||
|
||||
return (
|
||||
<Card
|
||||
<GameCard
|
||||
key={id}
|
||||
className={`bg-gray-900/80 border-cyan-600/50 ${canCast ? 'ring-1 ring-green-500/30' : ''}`}
|
||||
className={canCast ? 'ring-1 ring-[var(--color-success)]/30' : ''}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm" style={{ color: def.elem === 'raw' ? '#60A5FA' : elemDef?.color }}>
|
||||
<h4
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: def.elem === 'raw' ? 'var(--mana-transfer)' : `var(--mana-${def.elem})` }}
|
||||
>
|
||||
{def.name}
|
||||
</CardTitle>
|
||||
<Badge className="bg-cyan-900/50 text-cyan-300 text-xs">Equipment</Badge>
|
||||
</h4>
|
||||
<Badge className="bg-[var(--bg-elevated)] text-[var(--mana-crystal)] text-xs border border-[var(--mana-crystal)]/30">
|
||||
Equipment
|
||||
</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>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs text-[var(--text-secondary)]">
|
||||
{def.elem !== 'raw' && (
|
||||
<span className="mr-2">
|
||||
<ElementBadge elementId={def.elem} size="sm" /> {elemDef?.name}
|
||||
</span>
|
||||
)}
|
||||
<span>⚔️ {def.dmg} dmg</span>
|
||||
</div>
|
||||
<div className="text-xs game-mono" style={{ color: getSpellCostColor(def.cost) }}>
|
||||
<div className="text-xs font-[var(--font-mono)]" style={{ color: getSpellCostColor(def.cost) }}>
|
||||
Cost: {formatSpellCost(def.cost)}
|
||||
</div>
|
||||
<div className="text-xs text-cyan-400/70">From: {sources.join(', ')}</div>
|
||||
<div className="text-xs text-[var(--mana-crystal)]/70">From: {sources.join(', ')}</div>
|
||||
<div className="flex gap-2">
|
||||
{isActive ? (
|
||||
<Badge className="bg-amber-900/50 text-amber-300">Active</Badge>
|
||||
<Badge className="bg-[var(--bg-elevated)] text-[var(--color-warning)] border border-[var(--color-warning)]/30">
|
||||
Active
|
||||
</Badge>
|
||||
) : (
|
||||
<Button size="sm" variant="outline" onClick={() => store.setSpell(id)}>
|
||||
<button
|
||||
className="px-3 py-1 text-xs border border-[var(--border-default)] rounded hover:border-[var(--border-focus)] transition-colors"
|
||||
onClick={() => store.setSpell(id)}
|
||||
>
|
||||
Set Active
|
||||
</Button>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</GameCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-8 bg-gray-800/30 rounded border border-gray-700">
|
||||
<div className="text-gray-500 mb-2">No spells known yet</div>
|
||||
<div className="text-sm text-gray-600">Enchant a staff with a spell effect to gain spells</div>
|
||||
<div className="text-center p-8 bg-[var(--bg-sunken)] rounded border border-[var(--border-subtle)]">
|
||||
<div className="text-[var(--text-muted)] mb-2">No spells known yet</div>
|
||||
<div className="text-sm text-[var(--text-muted)]">Enchant a staff with a spell effect to gain spells</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pact Spells (from guardian defeats) */}
|
||||
{store.signedPacts.length > 0 && (
|
||||
{/* Pact Spells (from guardian defeats) - Empty State */}
|
||||
{!hasPactSpells && (
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-3 text-amber-400">🏆 Pact Spells</h3>
|
||||
<p className="text-sm text-gray-400 mb-3">Spells earned through guardian pacts appear here.</p>
|
||||
<h3 className="text-lg font-[var(--font-heading)] font-semibold mb-3 text-[var(--color-warning)]">
|
||||
Pact Spells
|
||||
</h3>
|
||||
<div className="text-center p-6 bg-[var(--bg-sunken)] rounded border border-[var(--border-subtle)]">
|
||||
<p className="text-sm text-[var(--text-muted)]">Defeat guardians and sign pacts to unlock powerful spells</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasPactSpells && (
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-[var(--font-heading)] font-semibold mb-3 text-[var(--color-warning)]">
|
||||
Pact Spells
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-3">Spells earned through guardian pacts appear here.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spell Reference - show all available spells for enchanting */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold mb-3 text-purple-400">📚 Spell Reference</h3>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
<h3 className="text-lg font-[var(--font-heading)] font-semibold mb-3 text-[var(--mana-death)]">
|
||||
Spell Reference
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||
These spells can be applied to equipment through the enchanting system.
|
||||
Research enchantment effects in the Skills tab to unlock them for designing.
|
||||
</p>
|
||||
@@ -140,37 +172,53 @@ export function SpellsTab({ store }: SpellsTabProps) {
|
||||
const isUnlocked = store.unlockedEffects?.includes(`spell_${id}`);
|
||||
|
||||
return (
|
||||
<Card
|
||||
<GameCard
|
||||
key={id}
|
||||
className={`bg-gray-900/80 border-gray-700 ${isUnlocked ? 'border-purple-500/50' : 'opacity-60'}`}
|
||||
variant={isUnlocked ? "default" : "sunken"}
|
||||
className={isUnlocked ? 'border-[var(--mana-death)]/50' : 'opacity-60'}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-sm" style={{ color: def.elem === 'raw' ? '#60A5FA' : elemDef?.color }}>
|
||||
<h4
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: def.elem === 'raw' ? 'var(--mana-transfer)' : `var(--mana-${def.elem})` }}
|
||||
>
|
||||
{def.name}
|
||||
</CardTitle>
|
||||
</h4>
|
||||
<div className="flex gap-1">
|
||||
{def.tier > 0 && <Badge variant="outline" className="text-xs">T{def.tier}</Badge>}
|
||||
{isUnlocked && <Badge className="bg-purple-900/50 text-purple-300 text-xs">Unlocked</Badge>}
|
||||
{def.tier > 0 && (
|
||||
<span className="text-xs px-1.5 py-0.5 border border-[var(--border-default)] rounded">
|
||||
T{def.tier}
|
||||
</span>
|
||||
)}
|
||||
{isUnlocked && (
|
||||
<Badge className="bg-[var(--bg-elevated)] text-[var(--mana-death)] text-xs border border-[var(--mana-death)]/30">
|
||||
Unlocked
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</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>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs text-[var(--text-secondary)]">
|
||||
{def.elem !== 'raw' && (
|
||||
<span className="mr-2">
|
||||
<ElementBadge elementId={def.elem} size="sm" /> {elemDef?.name}
|
||||
</span>
|
||||
)}
|
||||
<span>⚔️ {def.dmg} dmg</span>
|
||||
</div>
|
||||
<div className="text-xs game-mono" style={{ color: getSpellCostColor(def.cost) }}>
|
||||
<div className="text-xs font-[var(--font-mono)]" style={{ color: getSpellCostColor(def.cost) }}>
|
||||
Cost: {formatSpellCost(def.cost)}
|
||||
</div>
|
||||
{def.desc && (
|
||||
<div className="text-xs text-gray-500 italic">{def.desc}</div>
|
||||
<div className="text-xs text-[var(--text-muted)] italic">{def.desc}</div>
|
||||
)}
|
||||
{!isUnlocked && (
|
||||
<div className="text-xs text-amber-400/70">Research to unlock for enchanting</div>
|
||||
<div className="text-xs text-[var(--color-warning)]/70">Research to unlock for enchanting</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</GameCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user