import { useState } from 'react' import { useStats } from '../hooks/useStats' import { StatCard } from '../components' import type { PokemonRanking, StatsResponse } from '../types/stats' const typeBarColors: Record = { normal: '#9ca3af', fire: '#ef4444', water: '#3b82f6', electric: '#facc15', grass: '#22c55e', ice: '#67e8f9', fighting: '#b91c1c', poison: '#a855f7', ground: '#d97706', flying: '#a5b4fc', psychic: '#ec4899', bug: '#84cc16', rock: '#b45309', ghost: '#7e22ce', dragon: '#4f46e5', dark: '#374151', steel: '#9ca3af', fairy: '#f9a8d4', } function fmt(value: number | null, suffix = ''): string { if (value === null) return '—' return `${value}${suffix}` } function pct(value: number | null): string { if (value === null) return '—' return `${(value * 100).toFixed(1)}%` } function PokemonList({ title, pokemon }: { title: string; pokemon: PokemonRanking[] }) { const [expanded, setExpanded] = useState(false) const visible = expanded ? pokemon : pokemon.slice(0, 5) return (

{title}

{pokemon.length === 0 ? (

No data

) : ( <>
{visible.map((p, i) => (
{i + 1}. {p.spriteUrl ? ( {p.name} ) : (
)} {p.name} {p.count}
))}
{pokemon.length > 5 && ( )} )}
) } function srgbLuminance(hex: string): number { const toLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4) const r = toLinear(parseInt(hex.slice(1, 3), 16) / 255) const g = toLinear(parseInt(hex.slice(3, 5), 16) / 255) const b = toLinear(parseInt(hex.slice(5, 7), 16) / 255) return 0.2126 * r + 0.7152 * g + 0.0722 * b } function shouldUseDarkText(bgHex: string): boolean { const bgL = srgbLuminance(bgHex) const whiteContrast = 1.05 / (bgL + 0.05) const blackContrast = (bgL + 0.05) / 0.05 return blackContrast > whiteContrast } function HorizontalBar({ label, value, max, colorHex, }: { label: string value: number max: number colorHex: string }) { const width = max > 0 ? (value / max) * 100 : 0 const useDark = shouldUseDarkText(colorHex) return (
{label}
{value}
) } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
) } function StatsContent({ stats }: { stats: StatsResponse }) { const gameMax = Math.max(...stats.runsByGame.map((g) => g.count), 0) const typeMax = Math.max(...stats.typeDistribution.map((t) => t.count), 0) return (
{/* Run Overview */}
Win Rate: {pct(stats.winRate)} Avg Duration:{' '} {fmt(stats.avgDurationDays, ' days')}
{/* Runs by Game */} {stats.runsByGame.length > 0 && (
{stats.runsByGame.map((g) => ( ))}
)} {/* Encounter Stats */}
Catch Rate: {pct(stats.catchRate)} Avg per Run:{' '} {fmt(stats.avgEncountersPerRun)}
{/* Pokemon Rankings */}
{/* Team & Deaths */}
{pct(stats.mortalityRate)}
Mortality Rate
{fmt(stats.avgCatchLevel)}
Avg Catch Lv.
{fmt(stats.avgFaintLevel)}
Avg Faint Lv.
{stats.topDeathCauses.length > 0 && (

Top Death Causes

{stats.topDeathCauses.map((d, i) => (
{i + 1}. {d.cause} {d.count}
))}
)}
{/* Type Distribution */} {stats.typeDistribution.length > 0 && (
{stats.typeDistribution.map((t) => ( ))}
)}
) } export function Stats() { const { data: stats, isLoading, error } = useStats() return (

Stats

{isLoading && (
)} {error && (
Failed to load stats: {error.message}
)} {stats && stats.totalRuns === 0 && (

No data yet

Start a Nuzlocke run to see your stats here.

)} {stats && stats.totalRuns > 0 && }
) }