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

- 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:
Refactoring Agent
2026-04-28 11:38:45 +02:00
parent 3c29c1c834
commit 47c71e6f54
61 changed files with 6892 additions and 1842 deletions
+55 -51
View File
@@ -1,7 +1,7 @@
'use client';
import { GameCard, ElementBadge, ActionButton } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ELEMENTS } from '@/lib/game/constants';
interface LabTabProps {
@@ -24,11 +24,13 @@ export function LabTab({ store }: LabTabProps) {
return (
<div
key={id}
className="p-2 rounded border border-gray-700 bg-gray-800/50"
className="p-2 rounded border border-[var(--border-subtle)] bg-[var(--bg-sunken)]"
>
<div className="text-lg text-center">{def?.sym}</div>
<div className="text-xs font-semibold text-center" style={{ color: def?.color }}>{def?.name}</div>
<div className="text-xs text-gray-400 game-mono text-center">{state.current}/{state.max}</div>
<div className="text-lg text-center">
<ElementBadge elementId={id} size="sm" />
</div>
<div className="text-xs font-semibold text-center" style={{ color: `var(--mana-${id})` }}>{def?.name}</div>
<div className="text-xs text-[var(--text-secondary)] font-[var(--font-mono)] text-center">{state.current}/{state.max}</div>
</div>
);
})}
@@ -44,41 +46,43 @@ export function LabTab({ store }: LabTabProps) {
if (compositeElements.length === 0) return null;
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm">Composite Crafting</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{compositeElements.map(([id, def]) => {
const recipe = def.recipe || [];
const canCraft = recipe.every(r => (store.elements[r]?.current || 0) >= 1);
const craftBonus = 1 + (store.skills.elemCrafting || 0) * 0.25;
const output = Math.floor(craftBonus);
return (
<div key={id} className="p-2 rounded border border-gray-700 bg-gray-800/50 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-lg">{def.sym}</span>
<span className="text-sm" style={{ color: def.color }}>{def.name}</span>
<span className="text-xs text-gray-500">
({recipe.map(r => ELEMENTS[r]?.sym).join(' + ')})
</span>
</div>
<Button
size="sm"
variant="outline"
disabled={!canCraft}
onClick={() => store.craftComposite(id)}
>
Craft ({output})
</Button>
<GameCard>
<div className="pb-2">
<h3 className="text-sm font-semibold text-[var(--text-primary)]">Composite Crafting</h3>
</div>
<div className="space-y-2">
{compositeElements.map(([id, def]) => {
const recipe = def.recipe || [];
const canCraft = recipe.every(r => (store.elements[r]?.current || 0) >= 1);
const craftBonus = 1 + (store.skills.elemCrafting || 0) * 0.25;
const output = Math.floor(craftBonus);
return (
<div key={id} className="p-2 rounded border border-[var(--border-subtle)] bg-[var(--bg-sunken)] flex items-center justify-between">
<div className="flex items-center gap-2">
<ElementBadge elementId={id} size="md" />
<span className="text-sm" style={{ color: `var(--mana-${id})` }}>{def.name}</span>
<span className="text-xs text-[var(--text-muted)]">
({recipe.map(r => {
const rDef = ELEMENTS[r];
return rDef?.sym || r;
}).join(' + ')})
</span>
</div>
);
})}
</div>
</CardContent>
</Card>
<Button
size="sm"
variant="outline"
disabled={!canCraft}
onClick={() => store.craftComposite(id)}
className={!canCraft ? 'opacity-50 cursor-not-allowed' : ''}
>
Craft ({output})
</Button>
</div>
);
})}
</div>
</GameCard>
);
};
@@ -87,27 +91,27 @@ export function LabTab({ store }: LabTabProps) {
if (!hasUnlockedElements) {
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardContent className="pt-6">
<div className="text-center text-gray-500">
<GameCard>
<div className="pt-6">
<div className="text-center text-[var(--text-muted)]">
No elemental mana available. Gather or convert mana to see elemental pools.
</div>
</CardContent>
</Card>
</div>
</GameCard>
);
}
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Elemental Mana Display */}
<Card className="bg-gray-900/80 border-gray-700 lg:col-span-2">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 text-sm">Elemental Mana</CardTitle>
</CardHeader>
<CardContent>
<GameCard className="lg:col-span-2">
<div className="pb-2">
<h3 className="text-sm font-semibold text-[var(--text-primary)]">Elemental Mana</h3>
</div>
<div>
{renderElementsGrid()}
</CardContent>
</Card>
</div>
</GameCard>
{/* Composite Crafting */}
{renderCompositeCrafting()}