games: split Call of Duty into one entry per title and Pokémon into one per generation (remakes and spin-offs separate), full names, official logo for each (Steam icons / English logos); data migration moves bo2 status+aliases to black-ops-2; /trackers/games/bo2 and /pokemon redirect; three-column games flyout
All checks were successful
Deploy production / deploy (push) Successful in 1m16s

This commit is contained in:
2026-10-25 18:54:40 +00:00
parent 942c436d03
commit e4162d3a78
34 changed files with 29842 additions and 61 deletions

View File

@@ -0,0 +1,46 @@
-- Hand-written data migration: split the single Call of Duty (bo2) and Pokémon rows into one
-- row per Call of Duty title and one per Pokémon generation (remakes and spin-offs separate).
-- Neither old row had notes or ranks; bo2's status (active) and aliases move to black-ops-2.
-- Deep-tracker data (ref.tracker_games / ref.pokemon_games ids) is untouched.
INSERT INTO games.library (slug, name, short_name, kind, status, platform, tracker_href, aliases, sort_order) VALUES
('black-ops-2', 'Call of Duty: Black Ops 2', 'Black Ops 2', 'video', 'active', NULL, '/projects/game-tracker/call-of-duty/black-ops-2', '{"Call of Duty","Black Ops II","Black Ops 2","BO2"}', 60),
('black-ops', 'Call of Duty: Black Ops', 'Black Ops', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/black-ops', '{"Call of Duty","Black Ops 1"}', 61),
('ghosts', 'Call of Duty: Ghosts', 'Ghosts', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/ghosts', '{"Call of Duty"}', 62),
('advanced-warfare', 'Call of Duty: Advanced Warfare', 'Advanced Warfare', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/advanced-warfare', '{"Call of Duty"}', 63),
('black-ops-3', 'Call of Duty: Black Ops 3', 'Black Ops 3', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/black-ops-3', '{"Call of Duty","Black Ops III","Black Ops 3"}', 64),
('infinite-warfare', 'Call of Duty: Infinite Warfare', 'Infinite Warfare', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/infinite-warfare', '{"Call of Duty"}', 65),
('wwii', 'Call of Duty: WWII', 'WWII', 'video', 'unknown', NULL, '/projects/game-tracker/call-of-duty/wwii', '{"Call of Duty","World War II"}', 66),
('pokemon-red-blue-yellow', 'Pokémon Red, Blue & Yellow', 'Red, Blue & Yellow', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=yellow', '{"Pokémon Red","Pokémon Blue","Pokémon Yellow"}', 200),
('pokemon-gold-silver-crystal', 'Pokémon Gold, Silver & Crystal', 'Gold, Silver & Crystal', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=crystal', '{"Pokémon Gold","Pokémon Silver","Pokémon Crystal"}', 201),
('pokemon-ruby-sapphire-emerald', 'Pokémon Ruby, Sapphire & Emerald', 'Ruby, Sapphire & Emerald', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=emerald', '{"Pokémon Ruby","Pokémon Sapphire","Pokémon Emerald"}', 202),
('pokemon-colosseum', 'Pokémon Colosseum', 'Colosseum', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=colosseum', '{}', 203),
('pokemon-xd', 'Pokémon XD: Gale of Darkness', 'XD: Gale of Darkness', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=xd', '{"Pokémon XD"}', 204),
('pokemon-firered-leafgreen', 'Pokémon FireRed & LeafGreen', 'FireRed & LeafGreen', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=firered', '{"Pokémon FireRed","Pokémon LeafGreen"}', 205),
('pokemon-diamond-pearl-platinum', 'Pokémon Diamond, Pearl & Platinum', 'Diamond, Pearl & Platinum', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=platinum', '{"Pokémon Diamond","Pokémon Pearl","Pokémon Platinum"}', 206),
('pokemon-heartgold-soulsilver', 'Pokémon HeartGold & SoulSilver', 'HeartGold & SoulSilver', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=soulsilver', '{"Pokémon HeartGold","Pokémon SoulSilver"}', 207),
('pokemon-black-white', 'Pokémon Black, White, Black 2 & White 2', 'Black & White', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=black', '{"Pokémon Black","Pokémon White","Pokémon Black 2","Pokémon White 2"}', 208),
('pokemon-x-y', 'Pokémon X & Y', 'X & Y', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=y', '{"Pokémon X","Pokémon Y"}', 209),
('pokemon-omega-ruby-alpha-sapphire', 'Pokémon Omega Ruby & Alpha Sapphire', 'Omega Ruby & Alpha Sapphire', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=omega-ruby', '{"Pokémon Omega Ruby","Pokémon Alpha Sapphire"}', 210),
('pokemon-sun-moon', 'Pokémon Sun, Moon, Ultra Sun & Ultra Moon', 'Sun & Moon', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=moon', '{"Pokémon Sun","Pokémon Moon","Pokémon Ultra Sun","Pokémon Ultra Moon"}', 211),
('pokemon-lets-go', 'Pokémon Let’s Go, Pikachu! & Let’s Go, Eevee!', 'Let’s Go', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=lets-go-eevee', '{"Pokémon Let’s Go, Pikachu!","Pokémon Let’s Go, Eevee!"}', 212),
('pokemon-sword-shield', 'Pokémon Sword & Shield', 'Sword & Shield', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=sword', '{"Pokémon Sword","Pokémon Shield"}', 213),
('pokemon-brilliant-diamond-shining-pearl', 'Pokémon Brilliant Diamond & Shining Pearl', 'Brilliant Diamond & Shining Pearl', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=brilliant-diamond', '{"Pokémon Brilliant Diamond","Pokémon Shining Pearl"}', 214),
('pokemon-legends-arceus', 'Pokémon Legends: Arceus', 'Legends: Arceus', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=legends-arceus', '{}', 215),
('pokemon-scarlet-violet', 'Pokémon Scarlet & Violet', 'Scarlet & Violet', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=scarlet', '{"Pokémon Scarlet","Pokémon Violet"}', 216),
('pokemon-legends-z-a', 'Pokémon Legends: Z-A', 'Legends: Z-A', 'video', 'unknown', NULL, '/projects/game-tracker/pokemon?game=legends-za', '{}', 217)
ON CONFLICT (slug) DO NOTHING;--> statement-breakpoint
UPDATE games.library n SET
status = o.status,
platform = COALESCE(n.platform, o.platform),
current_rank = COALESCE(n.current_rank, o.current_rank),
peak_rank = COALESCE(n.peak_rank, o.peak_rank),
rank_label = COALESCE(n.rank_label, o.rank_label),
notes = COALESCE(n.notes, o.notes),
aliases = ARRAY(SELECT DISTINCT unnest(n.aliases || o.aliases))
FROM games.library o
WHERE o.slug = 'bo2' AND n.slug = 'black-ops-2';--> statement-breakpoint
UPDATE games.library n SET
notes = COALESCE(n.notes, o.notes)
FROM games.library o
WHERE o.slug = 'pokemon' AND n.slug = 'pokemon-diamond-pearl-platinum' AND o.notes IS NOT NULL;--> statement-breakpoint
DELETE FROM games.library WHERE slug IN ('bo2', 'pokemon');

File diff suppressed because it is too large Load Diff

View File

@@ -189,12 +189,15 @@
let flyoutTimer: ReturnType<typeof setTimeout> | undefined;
// Big groups (games) get a two-column flyout; it opens upwards when it would not fit below.
const WIDE_FLYOUT = 6;
// Games (one entry per Call of Duty title and Pokémon generation) get three columns.
const HUGE_FLYOUT = 24;
const flyoutColumns = (count: number) => (count > HUGE_FLYOUT ? 3 : count > WIDE_FLYOUT ? 2 : 1);
function openFlyout(name: string, el: HTMLElement, count: number) {
clearTimeout(flyoutTimer);
if (flyout === name) return;
const wide = count > WIDE_FLYOUT;
const width = wide ? 432 : 240;
const height = (wide ? Math.ceil(count / 2) : count) * 46 + 20;
const columns = flyoutColumns(count);
const width = columns === 3 ? 640 : columns === 2 ? 432 : 240;
const height = Math.min(Math.ceil(count / columns) * 46 + 20, window.innerHeight - 80);
const rect = el.getBoundingClientRect();
const panel = el.closest('[data-menu-panel]')?.getBoundingClientRect() ?? rect;
const leftColumn = rect.left + rect.width / 2 < panel.left + panel.width / 2;
@@ -365,14 +368,20 @@
class={[
'absolute',
flyoutUp ? '-bottom-2' : '-top-2',
project.children.length > WIDE_FLYOUT ? 'w-[27rem]' : 'w-60',
flyoutColumns(project.children.length) === 3
? 'w-[40rem]'
: project.children.length > WIDE_FLYOUT
? 'w-[27rem]'
: 'w-60',
flyoutSide === 'right' ? 'left-full pl-3' : 'right-full pr-3'
]}
>
<div
class={[
'flyout grid gap-1 rounded-md border border-border bg-popover p-1.5 shadow-lg',
project.children.length > WIDE_FLYOUT && 'grid-cols-2'
'flyout grid max-h-[calc(100vh-5rem)] gap-1 overflow-y-auto rounded-md border border-border bg-popover p-1.5 shadow-lg',
flyoutColumns(project.children.length) === 3
? 'grid-cols-3'
: project.children.length > WIDE_FLYOUT && 'grid-cols-2'
]}
class:from-left={flyoutSide === 'left'}
>

View File

@@ -27,7 +27,11 @@
alt=""
draggable="false"
decoding="async"
class={logo.bleed ? 'size-full object-cover' : 'size-[80%] object-contain'}
class={logo.bleed
? 'size-full object-cover'
: logo.wide
? 'size-[92%] object-contain'
: 'size-[80%] object-contain'}
onerror={() => (failed = true)}
/>
</span>

View File

@@ -2,19 +2,24 @@
// static/game-logos/icons/, keyed like `gameTrackers[].icon` (game-<slug>). TrackerIcon uses
// this map everywhere games show up (header flyout, home and /trackers tiles, /start chips,
// the games switcher and each game page); a game without an entry keeps its tracker-icons tile.
// Square app icons/emblems are used instead of wide wordmarks so they stay legible at chip size.
// Square app icons/emblems are used instead of wide wordmarks so they stay legible at chip size;
// Pokémon generations have no square marks, so they use their English logos (`wide`).
export type GameLogo = {
/** Path under static/ (no leading slash), resolved with `asset`/`resolve` at render time. */
src: string;
/** Opaque square app icon: fill the whole tile instead of sitting padded on the dark tile. */
bleed?: boolean;
/** Wide wordmark (Pokémon logos): use more of the tile so it stays readable. */
wide?: boolean;
/** Where the asset came from (shown on /projects/game-tracker/credits). */
credit: string;
source: string;
};
const steamIcon = (appid: number) => `https://store.steampowered.com/app/${appid}/`;
const bulbagarden = (file: string) =>
`https://archives.bulbagarden.net/wiki/File:${encodeURIComponent(file.replaceAll(' ', '_'))}`;
const commons = (file: string) =>
`https://commons.wikimedia.org/wiki/File:${encodeURIComponent(file.replaceAll(' ', '_'))}`;
@@ -47,12 +52,48 @@ export const gameLogos: Record<string, GameLogo> = {
credit: 'Escape from Tarkov app icon, Steam',
source: steamIcon(3932890)
},
'game-bo2': {
src: 'game-logos/icons/bo2.png',
'game-black-ops-2': {
src: 'game-logos/icons/black-ops-2.png',
bleed: true,
credit: 'Call of Duty: Black Ops II app icon, Steam',
source: steamIcon(202970)
},
'game-black-ops': {
src: 'game-logos/icons/black-ops.png',
bleed: true,
credit: 'Call of Duty: Black Ops app icon, Steam',
source: steamIcon(42700)
},
'game-ghosts': {
src: 'game-logos/icons/ghosts.png',
bleed: true,
credit: 'Call of Duty: Ghosts app icon, Steam',
source: steamIcon(209160)
},
'game-advanced-warfare': {
src: 'game-logos/icons/advanced-warfare.png',
bleed: true,
credit: 'Call of Duty: Advanced Warfare app icon, Steam',
source: steamIcon(209650)
},
'game-black-ops-3': {
src: 'game-logos/icons/black-ops-3.png',
bleed: true,
credit: 'Call of Duty: Black Ops III app icon, Steam',
source: steamIcon(311210)
},
'game-infinite-warfare': {
src: 'game-logos/icons/infinite-warfare.png',
bleed: true,
credit: 'Call of Duty: Infinite Warfare app icon, Steam',
source: steamIcon(292730)
},
'game-wwii': {
src: 'game-logos/icons/wwii.png',
bleed: true,
credit: 'Call of Duty: WWII app icon, Steam',
source: steamIcon(476600)
},
'game-valorant': {
src: 'game-logos/icons/valorant.svg',
credit: 'Valorant logo (V mark, pink), Wikimedia Commons',
@@ -93,14 +134,117 @@ export const gameLogos: Record<string, GameLogo> = {
credit: 'World Cube Association logo, Wikimedia Commons',
source: commons('World Cube Association.svg')
},
'game-pokemon': {
src: 'game-logos/icons/pokemon.svg',
credit: 'Poké Ball icon, Wikimedia Commons',
source: commons('Poké Ball icon.svg')
},
'game-sot': {
src: 'game-logos/icons/sot.png',
credit: 'Sea of Thieves app icon (skull), Steam',
source: steamIcon(1172620)
},
'game-pokemon-red-blue-yellow': {
src: 'game-logos/icons/pokemon-red-blue-yellow.png',
wide: true,
credit: 'English logo (Pokémon RB logo 2), Wikimedia Commons',
source: commons('Pokémon RB logo 2.png')
},
'game-pokemon-gold-silver-crystal': {
src: 'game-logos/icons/pokemon-gold-silver-crystal.png',
wide: true,
credit: 'English logo (Pokémon GS logo), Wikimedia Commons',
source: commons('Pokémon GS logo.png')
},
'game-pokemon-ruby-sapphire-emerald': {
src: 'game-logos/icons/pokemon-ruby-sapphire-emerald.png',
wide: true,
credit: 'English logo (Pokemon Emerald Logo EN), Bulbagarden Archives',
source: bulbagarden('Pokemon Emerald Logo EN.png')
},
'game-pokemon-colosseum': {
src: 'game-logos/icons/pokemon-colosseum.png',
wide: true,
credit: 'English logo (Pokémon Colosseum logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Colosseum logo.png')
},
'game-pokemon-xd': {
src: 'game-logos/icons/pokemon-xd.png',
wide: true,
credit: 'English logo (Pokémon XD logo English), Bulbagarden Archives',
source: bulbagarden('Pokémon XD logo English.png')
},
'game-pokemon-firered-leafgreen': {
src: 'game-logos/icons/pokemon-firered-leafgreen.png',
wide: true,
credit: 'English logo (Pokemon FireRed Logo EN), Bulbagarden Archives',
source: bulbagarden('Pokemon FireRed Logo EN.png')
},
'game-pokemon-diamond-pearl-platinum': {
src: 'game-logos/icons/pokemon-diamond-pearl-platinum.png',
wide: true,
credit: 'English logo (Platinum-logo), Bulbagarden Archives',
source: bulbagarden('Platinum-logo.png')
},
'game-pokemon-heartgold-soulsilver': {
src: 'game-logos/icons/pokemon-heartgold-soulsilver.png',
wide: true,
credit: 'English logo (HeartGold SoulSilver Logo), Bulbagarden Archives',
source: bulbagarden('HeartGold SoulSilver Logo.png')
},
'game-pokemon-black-white': {
src: 'game-logos/icons/pokemon-black-white.png',
wide: true,
credit: 'English logo (English BW logo), Bulbagarden Archives',
source: bulbagarden('English BW logo.png')
},
'game-pokemon-x-y': {
src: 'game-logos/icons/pokemon-x-y.png',
wide: true,
credit: 'English logo (Pokémon XY logo), Bulbagarden Archives',
source: bulbagarden('Pokémon XY logo.png')
},
'game-pokemon-omega-ruby-alpha-sapphire': {
src: 'game-logos/icons/pokemon-omega-ruby-alpha-sapphire.png',
wide: true,
credit: 'English logo (Pokémon Omega Ruby EN logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Omega Ruby EN logo.png')
},
'game-pokemon-sun-moon': {
src: 'game-logos/icons/pokemon-sun-moon.png',
wide: true,
credit: 'English logo (Pokémon Sun Moon logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Sun Moon logo.png')
},
'game-pokemon-lets-go': {
src: 'game-logos/icons/pokemon-lets-go.png',
wide: true,
credit: 'English logo (Pokémon Lets Go Eevee Logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Lets Go Eevee Logo.png')
},
'game-pokemon-sword-shield': {
src: 'game-logos/icons/pokemon-sword-shield.png',
wide: true,
credit: 'English logo (Pokémon Sword Shield logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Sword Shield logo.png')
},
'game-pokemon-brilliant-diamond-shining-pearl': {
src: 'game-logos/icons/pokemon-brilliant-diamond-shining-pearl.png',
wide: true,
credit: 'English logo (Pokémon Brilliant Diamond logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Brilliant Diamond logo.png')
},
'game-pokemon-legends-arceus': {
src: 'game-logos/icons/pokemon-legends-arceus.png',
wide: true,
credit: 'English logo (Pokémon Legends Arceus logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Legends Arceus logo.png')
},
'game-pokemon-scarlet-violet': {
src: 'game-logos/icons/pokemon-scarlet-violet.png',
wide: true,
credit: 'English logo (Pokémon Scarlet logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Scarlet logo.png')
},
'game-pokemon-legends-z-a': {
src: 'game-logos/icons/pokemon-legends-z-a.png',
wide: true,
credit: 'English logo (Pokémon Legends Z-A logo), Bulbagarden Archives',
source: bulbagarden('Pokémon Legends Z-A logo.png')
}
};

View File

@@ -82,6 +82,9 @@ export const spottingTrackers: readonly Tracker[] = [
// own page at /trackers/games/<slug>, one click away from the header flyout and the chips.
// `featured` games (my main game and the ones I actively play) show first on tiles; the
// rest sit behind a "more" chip. Data per game lives in games.library (same slugs).
// Call of Duty is one entry per title and Pokémon one per generation (remakes such as
// HeartGold & SoulSilver and spin-offs separate); full names, no abbreviations. The old
// /trackers/games/bo2 and /trackers/games/pokemon URLs redirect (see [slug]/+page.server.ts).
const game = (slug: string, name: string, subtitle: string, featured = false): Tracker => ({
name,
subtitle,
@@ -96,7 +99,7 @@ export const gameTrackers: readonly Tracker[] = [
game('pubg', 'pubg', 'PUBG: Battlegrounds', true),
game('r6', 'r6 siege', 'Tom Clancy’s Rainbow Six Siege', true),
game('tarkov', 'tarkov', 'Escape from Tarkov', true),
game('bo2', 'bo2', 'Call of Duty: Black Ops II', true),
game('black-ops-2', 'black ops 2', 'Call of Duty: Black Ops II (2012)', true),
game('valorant', 'valorant', 'Riot’s tactical shooter', false),
game('lol', 'league', 'League of Legends', false),
game('overwatch', 'overwatch 2', 'Blizzard’s hero shooter', false),
@@ -105,8 +108,81 @@ export const gameTrackers: readonly Tracker[] = [
game('ksp', 'ksp', 'Kerbal Space Program', false),
game('smash', 'smash bros', 'Super Smash Bros.', false),
game('cubing', 'speedcubing', 'Rubik’s cube solving, not a video game', false),
game('pokemon', 'pokémon', 'catching them all across the games', false),
game('sot', 'sea of thieves', 'pirate sandbox', false)
game('sot', 'sea of thieves', 'pirate sandbox', false),
// Call of Duty, one entry per title (the Black Ops 2 one is featured above).
game('black-ops', 'black ops', 'Call of Duty: Black Ops (2010)'),
game('ghosts', 'ghosts', 'Call of Duty: Ghosts (2013)'),
game('advanced-warfare', 'advanced warfare', 'Call of Duty: Advanced Warfare (2014)'),
game('black-ops-3', 'black ops 3', 'Call of Duty: Black Ops III (2015)'),
game('infinite-warfare', 'infinite warfare', 'Call of Duty: Infinite Warfare (2016)'),
game('wwii', 'wwii', 'Call of Duty: WWII (2017)'),
// Pokémon, one entry per generation; remakes and spin-offs get their own.
game('pokemon-red-blue-yellow', 'pokémon red, blue & yellow', 'generation 1, Game Boy'),
game(
'pokemon-gold-silver-crystal',
'pokémon gold, silver & crystal',
'generation 2, Game Boy Color'
),
game(
'pokemon-ruby-sapphire-emerald',
'pokémon ruby, sapphire & emerald',
'generation 3, Game Boy Advance'
),
game('pokemon-colosseum', 'pokémon colosseum', 'generation 3 spin-off, GameCube'),
game('pokemon-xd', 'pokémon xd: gale of darkness', 'generation 3 spin-off, GameCube'),
game(
'pokemon-firered-leafgreen',
'pokémon firered & leafgreen',
'generation 1 remake, Game Boy Advance'
),
game(
'pokemon-diamond-pearl-platinum',
'pokémon diamond, pearl & platinum',
'generation 4, Nintendo DS'
),
game(
'pokemon-heartgold-soulsilver',
'pokémon heartgold & soulsilver',
'generation 2 remake, Nintendo DS'
),
game(
'pokemon-black-white',
'pokémon black & white',
'generation 5 with Black 2 & White 2, Nintendo DS'
),
game('pokemon-x-y', 'pokémon x & y', 'generation 6, Nintendo 3DS'),
game(
'pokemon-omega-ruby-alpha-sapphire',
'pokémon omega ruby & alpha sapphire',
'generation 3 remake, Nintendo 3DS'
),
game(
'pokemon-sun-moon',
'pokémon sun & moon',
'generation 7 with Ultra Sun & Ultra Moon, Nintendo 3DS'
),
game(
'pokemon-lets-go',
'pokémon let’s go, pikachu & eevee',
'generation 1 remake, Nintendo Switch'
),
game(
'pokemon-sword-shield',
'pokémon sword & shield',
'generation 8 with The Isle of Armor & The Crown Tundra, Nintendo Switch'
),
game(
'pokemon-brilliant-diamond-shining-pearl',
'pokémon brilliant diamond & shining pearl',
'generation 4 remake, Nintendo Switch'
),
game('pokemon-legends-arceus', 'pokémon legends: arceus', 'generation 8, Nintendo Switch'),
game(
'pokemon-scarlet-violet',
'pokémon scarlet & violet',
'generation 9 with The Teal Mask & The Indigo Disk, Nintendo Switch'
),
game('pokemon-legends-z-a', 'pokémon legends: z-a', 'generation 9, Nintendo Switch')
];
// Trackers, in display order. Names are short single nouns. Most keep their existing

View File

@@ -19,6 +19,21 @@ export type TrackerIcon = {
const WHITE = '#ffffff';
// Shared glyphs for the per-title Call of Duty (crosshair) and per-generation Pokémon (Poké Ball)
// fallback tiles; each entry only changes the colours.
const codTile: TrackerIcon = {
from: '#fb923c',
to: '#9a3412',
ink: WHITE,
paths: [c(12, 12, 9), 'M21 12h-4M7 12H3M12 7V3M12 21v-4', c(12, 12, 1)]
};
const pokemonTile: TrackerIcon = {
from: '#fb7185',
to: '#c81e4a',
ink: WHITE,
paths: [c(12, 12, 9), 'M3 12h6M15 12h6', c(12, 12, 3)]
};
export const trackerIcons: Record<string, TrackerIcon> = {
tasks: {
from: '#60a5fa',
@@ -228,12 +243,13 @@ export const trackerIcons: Record<string, TrackerIcon> = {
'M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2'
]
},
'game-bo2': {
from: '#fb923c',
to: '#9a3412',
ink: WHITE,
paths: [c(12, 12, 9), 'M21 12h-4M7 12H3M12 7V3M12 21v-4', c(12, 12, 1)]
},
'game-black-ops-2': { ...codTile, from: '#f58147', to: '#863913' },
'game-black-ops': { ...codTile, from: '#f59b47', to: '#864b13' },
'game-ghosts': { ...codTile, from: '#f5b547', to: '#865c13' },
'game-advanced-warfare': { ...codTile, from: '#f5d047', to: '#866d13' },
'game-black-ops-3': { ...codTile, from: '#f5ea47', to: '#867e13' },
'game-infinite-warfare': { ...codTile, from: '#e7f547', to: '#7c8613' },
'game-wwii': { ...codTile, from: '#cdf547', to: '#6b8613' },
'game-valorant': {
from: '#ff6b77',
to: '#9f1239',
@@ -313,12 +329,28 @@ export const trackerIcons: Record<string, TrackerIcon> = {
ink: WHITE,
paths: ['M4 4h16v16H4z', 'M9.33 4v16M14.67 4v16', 'M4 9.33h16M4 14.67h16']
},
'game-pokemon': {
from: '#fb7185',
to: '#c81e4a',
ink: WHITE,
paths: [c(12, 12, 9), 'M3 12h6M15 12h6', c(12, 12, 3)]
'game-pokemon-red-blue-yellow': { ...pokemonTile, from: '#f37868', to: '#9c2b1c' },
'game-pokemon-gold-silver-crystal': { ...pokemonTile, from: '#f3a668', to: '#9c551c' },
'game-pokemon-ruby-sapphire-emerald': { ...pokemonTile, from: '#f3d568', to: '#9c801c' },
'game-pokemon-colosseum': { ...pokemonTile, from: '#e3f368', to: '#8d9c1c' },
'game-pokemon-xd': { ...pokemonTile, from: '#b4f368', to: '#629c1c' },
'game-pokemon-firered-leafgreen': { ...pokemonTile, from: '#86f368', to: '#379c1c' },
'game-pokemon-diamond-pearl-platinum': { ...pokemonTile, from: '#68f378', to: '#1c9c2b' },
'game-pokemon-heartgold-soulsilver': { ...pokemonTile, from: '#68f3a6', to: '#1c9c55' },
'game-pokemon-black-white': { ...pokemonTile, from: '#68f3d5', to: '#1c9c80' },
'game-pokemon-x-y': { ...pokemonTile, from: '#68e3f3', to: '#1c8d9c' },
'game-pokemon-omega-ruby-alpha-sapphire': { ...pokemonTile, from: '#68b4f3', to: '#1c629c' },
'game-pokemon-sun-moon': { ...pokemonTile, from: '#6886f3', to: '#1c379c' },
'game-pokemon-lets-go': { ...pokemonTile, from: '#7868f3', to: '#2b1c9c' },
'game-pokemon-sword-shield': { ...pokemonTile, from: '#a668f3', to: '#551c9c' },
'game-pokemon-brilliant-diamond-shining-pearl': {
...pokemonTile,
from: '#d568f3',
to: '#801c9c'
},
'game-pokemon-legends-arceus': { ...pokemonTile, from: '#f368e3', to: '#9c1c8d' },
'game-pokemon-scarlet-violet': { ...pokemonTile, from: '#f368b4', to: '#9c1c62' },
'game-pokemon-legends-z-a': { ...pokemonTile, from: '#f36886', to: '#9c1c37' },
'game-sot': {
from: '#5eead4',
to: '#0f766e',

View File

@@ -1,4 +1,4 @@
import { error } from '@sveltejs/kit';
import { error, redirect } from '@sveltejs/kit';
import { eq, inArray } from 'drizzle-orm';
import { db } from '#lib/server/db/index.js';
import { gameLibrary, hardwareSensitivities } from '#lib/server/db/schema/index.js';
@@ -9,8 +9,17 @@ import type { PageServerLoad } from './$types';
// One game from games.library, plus its mouse sensitivity (hardware.sensitivities, matched by
// name, short name or alias) when there is one. CS2, PUBG and R6 also get their automatic
// match history (games.matches, filled by the in-app poller).
// Old single entries, split per title/generation: bo2 became black-ops-2, and the Pokémon
// entry (all generations) now points at the Pokémon journal that covers every game.
const MOVED: Record<string, string> = {
bo2: '/trackers/games/black-ops-2',
pokemon: '/projects/game-tracker/pokemon'
};
export const load: PageServerLoad = async (event) => {
const { params } = event;
const moved = MOVED[params.slug];
if (moved) redirect(301, moved);
const [game] = await db.select().from(gameLibrary).where(eq(gameLibrary.slug, params.slug));
if (!game) error(404, 'Not Found');
const names = [game.name, game.shortName, ...game.aliases];

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Pokéball" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 595.3 594.1" style="enable-background:new 0 0 595.3 594.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#DFDFDF;}
.st2{fill:#FF1C1C;}
.st3{fill:#DF1818;}
</style>
<g id="Pokéball_1_">
<g id="Colours">
<path id="Down" class="st0" d="M297.6,380.9c-40.4,0-74.1-28.6-82.1-66.6H81.1c9.5,110.5,102.2,197.2,215.1,197.2
s205.7-86.7,215.1-197.2H379.7C371.7,352.4,338,380.9,297.6,380.9z"/>
<path id="Shadow_Down" class="st1" d="M345.6,505.9c89.6-21,157.7-97.7,165.7-191.6h-53C453,399.5,408.3,471.7,345.6,505.9z"/>
<path id="Center" class="st0" d="M347.1,297L347.1,297C347,297,347,297,347.1,297c-0.1-6.1-1.2-11.9-3.2-17.3
c-7-18.8-25.1-32.1-46.3-32.1s-39.3,13.4-46.3,32.1c-2,5.4-3.1,11.2-3.1,17.3c0,0,0,0,0,0h0.1c0,0,0,0,0,0
c0,6.1,1.1,11.9,3.1,17.3c7,18.8,25.1,32.1,46.3,32.1c21.2,0,39.3-13.4,46.3-32.1C346,309,347.1,303.1,347.1,297
C347.1,297,347.1,297,347.1,297z"/>
<path id="Up" class="st2" d="M297.7,213.2c40.4,0,74.1,28.6,82.1,66.6h134.4C504.7,169.2,412,82.5,299,82.5S93.4,169.2,83.9,279.7
h131.7C223.6,241.7,257.3,213.2,297.7,213.2z"/>
<path id="Shadow_Up" class="st3" d="M458.3,279.7h55.8c-8.2-95.5-78.6-173.3-170.5-192.6C407.4,120.8,452.9,193.7,458.3,279.7z"/>
</g>
<path id="Line" d="M299,82.5c113,0,205.7,86.7,215.1,197.2H379.7c-8-38-41.7-66.6-82.1-66.6c-40.4,0-74.1,28.6-82.1,66.6H83.9
C93.4,169.2,186.1,82.5,299,82.5z M343.9,279.7c2,5.4,3.1,11.2,3.1,17.3c0,0,0,0,0,0h0.1c0,0,0,0,0,0c0,6.1-1.1,11.9-3.1,17.3
c-7,18.8-25.1,32.1-46.3,32.1c-21.2,0-39.3-13.4-46.3-32.1c-2-5.4-3.1-11.2-3.1-17.3c0,0,0,0,0,0h-0.1c0,0,0,0,0,0
c0-6.1,1.1-11.9,3.1-17.3c7-18.8,25.1-32.1,46.3-32.1S336.9,261,343.9,279.7z M296.2,511.6c-113,0-205.7-86.7-215.1-197.2h134.4
c8,38,41.7,66.6,82.1,66.6s74.1-28.6,82.1-66.6h131.7C501.9,424.8,409.2,511.6,296.2,511.6z M297.6,41.3
C156.4,41.3,41.9,155.8,41.9,297s114.5,255.7,255.7,255.7S553.4,438.3,553.4,297S438.9,41.3,297.6,41.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB