import React from 'react' import { Box, Text, type Color } from '@anthropic/ink' import type { SpeciesId, StatName } from '../types' import { STAT_NAMES, STAT_LABELS } from '../types' import { getSpeciesData } from '../data/species' import { SPECIES_PERSONALITY } from '../data/names' import { getNextEvolution } from '../data/evolution' import { StatBar } from './StatBar' import { getStatColor } from './shared' const CYAN: Color = 'ansi:cyan' const GRAY: Color = 'ansi:white' const WHITE: Color = 'ansi:whiteBright' const YELLOW: Color = 'ansi:yellow' const GREEN: Color = 'ansi:green' const RED: Color = 'ansi:red' const BLUE: Color = 'ansi:blue' /** Type → color */ const TYPE_COLORS: Record = { grass: 'ansi:green', poison: 'ansi:magenta', fire: 'ansi:red', flying: 'ansi:cyan', water: 'ansi:blue', electric: 'ansi:yellow', normal: 'ansi:white', } interface SpeciesDetailProps { speciesId: SpeciesId caughtLevel?: number spriteLines?: string[] } /** * Detailed species info page — base stats, evolution chain, flavor text. */ export function SpeciesDetail({ speciesId, caughtLevel, spriteLines }: SpeciesDetailProps) { const species = getSpeciesData(speciesId) const nextEvo = getNextEvolution(speciesId) // Type badges const typeBadges = species.types.filter((t): t is string => Boolean(t)).map((t, i) => ( {i > 0 ? ' / ' : ''}{t.toUpperCase()} )) // Gender info const genderInfo = species.genderRate === -1 ? 'Genderless' : species.genderRate === 0 ? '♂ 100%' : species.genderRate === 8 ? '♀ 100%' : `♀ ${(species.genderRate / 8 * 100).toFixed(1)}%` // Max base stat for bar scaling const maxBase = 130 return ( {/* Header */} #{String(species.dexNumber).padStart(3, '0')} {species.names.zh ?? species.name} {caughtLevel && Best: Lv.{caughtLevel}} {/* Type + gender */} {typeBadges} {genderInfo} {/* Sprite */} {spriteLines && ( {spriteLines.map((line, i) => {line})} )} {/* Flavor text */} {species.flavorText && ( {species.flavorText} )} {/* Base Stats */} ─── Base Stats ─── {STAT_NAMES.map((stat) => ( {STAT_LABELS[stat].padEnd(3)} {'█'.repeat(Math.round((species.baseStats[stat] / maxBase) * 15))} {'░'.repeat(15 - Math.round((species.baseStats[stat] / maxBase) * 15))} {String(species.baseStats[stat]).padStart(3)} ))} {/* Total */} {'Total'.padEnd(3)} {'─'.repeat(15)} {Object.values(species.baseStats).reduce((a, b) => a + b, 0)} {/* Evolution chain */} {(nextEvo || species.dexNumber > 1) && ( ─── Evolution ─── )} {/* Info */} ─── Info ─── Growth: {species.growthRate} Capture: {species.captureRate} Happiness: {species.baseHappiness} ) } /** Render evolution chain arrow */ function EvolutionChain({ speciesId }: { speciesId: SpeciesId }) { // Find the chain head const chainHeads: SpeciesId[] = ['bulbasaur', 'charmander', 'squirtle', 'pikachu'] let head: SpeciesId = speciesId for (const starter of chainHeads) { if (isInChain(speciesId, starter)) { head = starter break } } const chain: SpeciesId[] = [head] let current: SpeciesId | undefined = head while (current) { const next = getNextEvolution(current) if (next) { chain.push(next.to) current = next.to } else { current = undefined } } return ( {chain.map((sid, i) => ( {i > 0 && } {getSpeciesData(sid).names.zh ?? getSpeciesData(sid).name} {i < chain.length - 1 && getNextEvolution(sid) && ( Lv.{getNextEvolution(sid)!.minLevel} )} ))} ) } function isInChain(target: SpeciesId, head: SpeciesId): boolean { let current: SpeciesId | undefined = head while (current) { if (current === target) return true const next = getNextEvolution(current) current = next ? next.to : undefined } return false }