Complete frontend rewrite from vanilla HTML/CSS/JS to React 19 + TypeScript + Tailwind CSS v4. Frontend (frontend/commander-ai-lab-ui/): - Vite + React 19 + TypeScript + Tailwind CSS v4 + React Router - 7 pages: BatchSim, Collection, Decks, AutoGen, Simulator, Coach, Training - Reusable components: Spinner, StatusBadge, CardImage, ManaSymbols, EmptyState - Full typed API client layer (8 modules matching all backend endpoints) - Recharts for mana curves, win rate charts, progress bars - Lucide icons throughout - Custom dark theme with MTG mana color tokens - Code splitting (recharts + react in separate chunks) Backend (lab_api.py): - SPA-aware static file serving with catch-all for client-side routing - Fallback to legacy ui/ folder if React dist not built - Updated startup log with SPA route list Build system: - .gitignore added (was missing - removes pyc, db, node_modules, etc.) - build-full-installer-v2.py: v23 with Node.js check + React build step - Vite build config with manual chunks for optimal loading Also includes: V3 deck generator fixes, coach improvements, DeepSeek sim modules
70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
// Parse MTG mana cost strings like "{2}{W}{U}" into colored symbols
|
|
|
|
const MANA_COLORS: Record<string, string> = {
|
|
W: 'bg-mana-white text-black',
|
|
U: 'bg-mana-blue text-white',
|
|
B: 'bg-mana-black text-white',
|
|
R: 'bg-mana-red text-white',
|
|
G: 'bg-mana-green text-white',
|
|
C: 'bg-mana-colorless text-white',
|
|
}
|
|
|
|
interface ManaSymbolsProps {
|
|
cost: string
|
|
size?: 'sm' | 'md'
|
|
}
|
|
|
|
export function ManaSymbols({ cost, size = 'sm' }: ManaSymbolsProps) {
|
|
if (!cost) return null
|
|
|
|
const symbols = cost.match(/\{([^}]+)\}/g)
|
|
if (!symbols) return <span className="text-text-tertiary">{cost}</span>
|
|
|
|
const sz = size === 'sm' ? 'w-5 h-5 text-[10px]' : 'w-6 h-6 text-xs'
|
|
|
|
return (
|
|
<span className="inline-flex items-center gap-0.5">
|
|
{symbols.map((s, i) => {
|
|
const val = s.replace(/[{}]/g, '')
|
|
const colorClass = MANA_COLORS[val] || 'bg-mana-colorless text-white'
|
|
return (
|
|
<span
|
|
key={i}
|
|
className={`${sz} inline-flex items-center justify-center rounded-full font-bold ${colorClass}`}
|
|
>
|
|
{val}
|
|
</span>
|
|
)
|
|
})}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
// Color identity dots
|
|
interface ColorDotsProps {
|
|
colors: string[]
|
|
size?: 'sm' | 'md'
|
|
}
|
|
|
|
export function ColorDots({ colors, size = 'sm' }: ColorDotsProps) {
|
|
if (!colors || colors.length === 0) return <span className="text-text-tertiary text-xs">Colorless</span>
|
|
|
|
const sz = size === 'sm' ? 'w-3 h-3' : 'w-4 h-4'
|
|
|
|
const DOT_COLORS: Record<string, string> = {
|
|
W: 'bg-mana-white',
|
|
U: 'bg-mana-blue',
|
|
B: 'bg-mana-black border border-border-secondary',
|
|
R: 'bg-mana-red',
|
|
G: 'bg-mana-green',
|
|
}
|
|
|
|
return (
|
|
<span className="inline-flex items-center gap-0.5">
|
|
{colors.map(c => (
|
|
<span key={c} className={`${sz} rounded-full ${DOT_COLORS[c] || 'bg-mana-colorless'}`} />
|
|
))}
|
|
</span>
|
|
)
|
|
}
|