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
46
drizzle/20261025190000_games_split_cod_pokemon/migration.sql
Normal 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');
|
||||
29492
drizzle/20261025190000_games_split_cod_pokemon/snapshot.json
Normal 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'}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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];
|
||||
|
||||
BIN
static/game-logos/icons/advanced-warfare.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
static/game-logos/icons/black-ops-3.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/game-logos/icons/black-ops.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
static/game-logos/icons/ghosts.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
static/game-logos/icons/infinite-warfare.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
static/game-logos/icons/pokemon-black-white.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
static/game-logos/icons/pokemon-colosseum.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
static/game-logos/icons/pokemon-diamond-pearl-platinum.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
static/game-logos/icons/pokemon-firered-leafgreen.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/game-logos/icons/pokemon-gold-silver-crystal.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/game-logos/icons/pokemon-heartgold-soulsilver.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/game-logos/icons/pokemon-legends-arceus.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
static/game-logos/icons/pokemon-legends-z-a.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
static/game-logos/icons/pokemon-lets-go.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
static/game-logos/icons/pokemon-omega-ruby-alpha-sapphire.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/game-logos/icons/pokemon-red-blue-yellow.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
static/game-logos/icons/pokemon-ruby-sapphire-emerald.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/game-logos/icons/pokemon-scarlet-violet.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/game-logos/icons/pokemon-sun-moon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/game-logos/icons/pokemon-sword-shield.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
static/game-logos/icons/pokemon-x-y.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
static/game-logos/icons/pokemon-xd.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
@@ -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 |
BIN
static/game-logos/icons/wwii.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |