mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
106 lines
4.5 KiB
TypeScript
106 lines
4.5 KiB
TypeScript
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<string, string[]> = {
|
|
'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<string, GamePalette> = {
|
|
'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 = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 220" role="img" aria-label="${character}">
|
|
<defs>
|
|
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="${startColor}"/>
|
|
<stop offset="100%" stop-color="${endColor}"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="480" height="220" fill="url(#bg)" rx="18"/>
|
|
<circle cx="90" cy="110" r="64" fill="rgba(255,255,255,0.13)"/>
|
|
<text x="90" y="130" text-anchor="middle" fill="#ffffff" font-family="Arial, sans-serif" font-size="56" font-weight="700">${initials}</text>
|
|
<text x="170" y="96" fill="#e2e8f0" font-family="Arial, sans-serif" font-size="20" font-weight="700">${game}</text>
|
|
<text x="170" y="145" fill="#ffffff" font-family="Arial, sans-serif" font-size="38" font-weight="700">${character}</text>
|
|
</svg>`;
|
|
|
|
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<string, string>;
|
|
|
|
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<Record<string, string>>((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<string, FightingCharacterOption[]> = 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] ?? [];
|