export interface FightingCharacterOption { label: string; value: string; image: string; } type GamePalette = readonly [startColor: string, endColor: string]; const DEFAULT_PLACEHOLDER_PALETTE: GamePalette = ['#334155', '#0f172a']; const MAX_INITIALS = 2; const characterNamesByGame: Record = { 'Street Fighter 6': ['A.K.I.', 'Akuma', 'Alex', 'Bison', 'Blanka', 'Cammy', 'Chun-Li', 'Dee Jay', 'Dhalsim', 'E. Honda', 'Ed', 'Elena', 'Guile', 'Jamie', 'JP', 'Juri', 'Ken', 'Kimberly', 'Lily', 'Luke', 'Mai', 'Manon', 'Marisa', 'Rashid', 'Ryu', 'Sagat', 'Terry', 'Viper', 'Zangief'], 'TEKKEN 8': ['Alisa', 'Anna', 'Armor King', 'Asuka', 'Azucena', 'Bob', 'Bryan', 'Claudio', 'Clive', 'Devil Jin', 'Dragunov', 'Eddy', 'Fahkumram', 'Feng', 'Heihachi', 'Hwoarang', 'Jack-8', 'Jin', 'Jun', 'Kazuya', 'King', 'Kuma', 'Kunimitsu', 'Lars', 'Law', 'Lee', 'Leo', 'Leroy', 'Lidia', 'Lili', 'Miary Zo', 'Nina', 'Panda', 'Paul', 'Raven', 'Reina', 'Roger Jr', 'Shaheen', 'Steve', 'Victor', 'Xiaoyu', 'Yoshimitsu', 'Zafina'], 'Guilty Gear -Strive-': ['A.B.A','Anji Mito','Asuka R. Kreutz','Axl Low','Baiken','Bedman?','Bridget','Chipp Zanuff','Elphelt Valentine','Faust','Giovanna','Goldlewis Dickinson','Happy Chaos','I-No','Jack-O\'','Johnny','Ky Kiske','Leo Whitefang','Lucy Kushinada','May','Millia Rage','Nagoriyuki','Potemkin','Queen Dizzy','Ramlethal Valentine','Sin Kiske','Slayer','Sol Badguy','Testament','Unika','Venom','Zato-1'], }; const paletteByGame: Record = { 'Street Fighter 6': ['#f97316', '#b91c1c'], 'TEKKEN 8': ['#2563eb', '#111827'], 'Guilty Gear -Strive-': ['#a855f7', '#312e81'], }; const toSlug = (value: string) => value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); const toDataUrl = (svg: string) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; const buildCharacterPlaceholder = (game: string, character: string) => { const [startColor, endColor] = paletteByGame[game] ?? DEFAULT_PLACEHOLDER_PALETTE; const initials = character .split(/\s+/) .map((part) => part[0]) .join('') .slice(0, MAX_INITIALS) .toUpperCase(); const svg = ` ${initials} ${game} ${character} `; return toDataUrl(svg.trim()); }; const characterImageModules = import.meta.glob('/src/shared/character-images/**/*.{png,jpg,jpeg,webp,avif,svg}', { eager: true, import: 'default', query: '?url', }) as Record; const resolveImageKey = (path: string): string | null => { const segments = path.split('/'); const gameFolder = segments.at(-2); const filename = segments.at(-1); if (!gameFolder || !filename) { return null; } const characterSlug = filename.replace(/\.[^.]+$/, ''); return `${gameFolder}/${characterSlug}`; }; const characterImageByKey = Object.entries(characterImageModules).reduce>((acc, [path, url]) => { const key = resolveImageKey(path); if (!key) { return acc; } acc[key] = url; return acc; }, {}); const getCharacterImage = (game: string, character: string, characterValue: string) => { const gameSlug = toSlug(game); const key = `${gameSlug}/${characterValue}`; return characterImageByKey[key] ?? buildCharacterPlaceholder(game, character); }; export const fightingCharactersByGame: Record = Object.fromEntries( Object.entries(characterNamesByGame).map(([game, characterNames]) => [ game, characterNames.map((character) => { const value = toSlug(character); // Prefer packaged artwork and gracefully fallback to a generated image. return { label: character, value, image: getCharacterImage(game, character, value), }; }), ]), ); export const getCharactersByGame = (game: string) => fightingCharactersByGame[game] ?? [];