diff --git a/schemas/scoreboard.json b/schemas/scoreboard.json
index 03e66ce..dfaccaf 100644
--- a/schemas/scoreboard.json
+++ b/schemas/scoreboard.json
@@ -35,6 +35,14 @@
"type": "string",
"default": ""
},
+ "leftCharacter": {
+ "type": "string",
+ "default": ""
+ },
+ "rightCharacter": {
+ "type": "string",
+ "default": ""
+ },
"leftScore": {
"type": "integer",
"default": 0,
@@ -63,6 +71,8 @@
"rightTeamOverride",
"leftCountryOverride",
"rightCountryOverride",
+ "leftCharacter",
+ "rightCharacter",
"leftScore",
"rightScore",
"round",
@@ -77,6 +87,8 @@
"rightTeamOverride": "",
"leftCountryOverride": "",
"rightCountryOverride": "",
+ "leftCharacter": "",
+ "rightCharacter": "",
"leftScore": 0,
"rightScore": 0,
"round": "",
diff --git a/src/dashboard/example/components/ScoreboardPanel.vue b/src/dashboard/example/components/ScoreboardPanel.vue
index c99bba6..ebf0e56 100644
--- a/src/dashboard/example/components/ScoreboardPanel.vue
+++ b/src/dashboard/example/components/ScoreboardPanel.vue
@@ -1,6 +1,7 @@
@@ -619,6 +673,34 @@ watchEffect(() => {
/>
+
+
+
![Left character preview]()
+
{
/>
+
+
+
![Right character preview]()
+
{
min-width: 0;
}
+.character-preview {
+ width: 100%;
+ border-radius: 8px;
+ overflow: hidden;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ background: rgba(0, 0, 0, 0.25);
+}
+
+.character-preview img {
+ display: block;
+ width: 100%;
+ height: 88px;
+ object-fit: cover;
+}
+
@media (min-width: 1024px) {
.scoreboard-grid {
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
diff --git a/src/dashboard/example/stores/scoreboard.ts b/src/dashboard/example/stores/scoreboard.ts
index 8620322..031f5fd 100644
--- a/src/dashboard/example/stores/scoreboard.ts
+++ b/src/dashboard/example/stores/scoreboard.ts
@@ -16,6 +16,8 @@ const defaultScoreboard: Scoreboard = {
rightTeamOverride: '',
leftCountryOverride: '',
rightCountryOverride: '',
+ leftCharacter: '',
+ rightCharacter: '',
leftScore: 0,
rightScore: 0,
round: '',
@@ -33,6 +35,8 @@ const normalizeScoreboard = (input: unknown): Scoreboard => {
rightTeamOverride: typeof candidate.rightTeamOverride === 'string' ? candidate.rightTeamOverride : '',
leftCountryOverride: typeof candidate.leftCountryOverride === 'string' ? candidate.leftCountryOverride : '',
rightCountryOverride: typeof candidate.rightCountryOverride === 'string' ? candidate.rightCountryOverride : '',
+ leftCharacter: typeof candidate.leftCharacter === 'string' ? candidate.leftCharacter : '',
+ rightCharacter: typeof candidate.rightCharacter === 'string' ? candidate.rightCharacter : '',
leftScore: typeof candidate.leftScore === 'number' ? Math.max(0, Math.floor(candidate.leftScore)) : 0,
rightScore: typeof candidate.rightScore === 'number' ? Math.max(0, Math.floor(candidate.rightScore)) : 0,
round: typeof candidate.round === 'string' ? candidate.round : '',
@@ -119,6 +123,8 @@ export const useScoreboardStore = defineStore('scoreboard', () => {
rightTeamOverride: scoreboard.value.leftTeamOverride,
leftCountryOverride: scoreboard.value.rightCountryOverride,
rightCountryOverride: scoreboard.value.leftCountryOverride,
+ leftCharacter: scoreboard.value.rightCharacter,
+ rightCharacter: scoreboard.value.leftCharacter,
leftScore: scoreboard.value.rightScore,
rightScore: scoreboard.value.leftScore,
};
diff --git a/src/graphics/scoreboard/main.vue b/src/graphics/scoreboard/main.vue
index 26cf39d..a17d9f4 100644
--- a/src/graphics/scoreboard/main.vue
+++ b/src/graphics/scoreboard/main.vue
@@ -16,6 +16,8 @@ const defaultScoreboard: Schemas.Scoreboard = {
rightTeamOverride: '',
leftCountryOverride: '',
rightCountryOverride: '',
+ leftCharacter: '',
+ rightCharacter: '',
leftScore: 0,
rightScore: 0,
round: '',
diff --git a/src/shared/fighting-characters.ts b/src/shared/fighting-characters.ts
new file mode 100644
index 0000000..e6666f6
--- /dev/null
+++ b/src/shared/fighting-characters.ts
@@ -0,0 +1,69 @@
+export interface FightingCharacterOption {
+ label: string;
+ value: string;
+ image: string;
+}
+
+const characterNamesByGame: Record = {
+ 'Street Fighter 6': ['Ryu', 'Ken', 'Chun-Li', 'Luke', 'Juri', 'Cammy'],
+ 'TEKKEN 8': ['Jin', 'Kazuya', 'Nina', 'King', 'Asuka', 'Reina'],
+ 'Guilty Gear -Strive-': ['Sol Badguy', 'Ky Kiske', 'May', 'Zato-1', 'I-No', 'Baiken'],
+ 'Mortal Kombat 1': ['Scorpion', 'Sub-Zero', 'Raiden', 'Liu Kang', 'Kitana', 'Mileena'],
+ 'The King of Fighters XV': ['Kyo Kusanagi', 'Iori Yagami', 'Terry Bogard', 'Mai Shiranui', 'Leona', 'Athena'],
+ 'Granblue Fantasy Versus: Rising': ['Gran', 'Djeeta', 'Lancelot', 'Narmaya', 'Vira', 'Belial'],
+ '2XKO': ['Ahri', 'Darius', 'Ekko', 'Yasuo', 'Illaoi', 'Jinx'],
+};
+
+const paletteByGame: Record = {
+ 'Street Fighter 6': ['#f97316', '#b91c1c'],
+ 'TEKKEN 8': ['#2563eb', '#111827'],
+ 'Guilty Gear -Strive-': ['#facc15', '#9333ea'],
+ 'Mortal Kombat 1': ['#84cc16', '#1f2937'],
+ 'The King of Fighters XV': ['#38bdf8', '#1d4ed8'],
+ 'Granblue Fantasy Versus: Rising': ['#60a5fa', '#7c3aed'],
+ '2XKO': ['#22d3ee', '#0f766e'],
+};
+
+const toCharacterValue = (character: string) => character.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
+
+const toDataUrl = (svg: string) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
+
+const buildCharacterImage = (game: string, character: string) => {
+ const [startColor, endColor] = paletteByGame[game] ?? ['#334155', '#0f172a'];
+ const initials = character
+ .split(/\s+/)
+ .map((part) => part[0])
+ .join('')
+ .slice(0, 2)
+ .toUpperCase();
+
+ const svg = `
+`;
+
+ return toDataUrl(svg.trim());
+};
+
+export const fightingCharactersByGame: Record = Object.fromEntries(
+ Object.entries(characterNamesByGame).map(([game, characterNames]) => [
+ game,
+ characterNames.map((character) => ({
+ label: character,
+ value: toCharacterValue(character),
+ image: buildCharacterImage(game, character),
+ })),
+ ]),
+);
+
+export const getCharactersByGame = (game: string) => fightingCharactersByGame[game] ?? [];
diff --git a/src/types/schemas/scoreboard.d.ts b/src/types/schemas/scoreboard.d.ts
index db38b47..7c43f23 100644
--- a/src/types/schemas/scoreboard.d.ts
+++ b/src/types/schemas/scoreboard.d.ts
@@ -15,6 +15,8 @@ export interface Scoreboard {
rightTeamOverride: string;
leftCountryOverride: string;
rightCountryOverride: string;
+ leftCharacter: string;
+ rightCharacter: string;
leftScore: number;
rightScore: number;
round: string;