All checks were successful
Deploy production / deploy (push) Successful in 1m7s
253 lines
7.5 KiB
TypeScript
253 lines
7.5 KiB
TypeScript
// Real projects (tools and experiments). Personal data trackers live in `trackers`.
|
||
export const projects = [
|
||
{
|
||
name: 'vash esports',
|
||
subtitle: 'the best way to play osu!',
|
||
href: 'https://esports.vash.software',
|
||
external: true
|
||
},
|
||
{
|
||
name: 'osu! map finder',
|
||
subtitle: 'find osu! maps for Spotify songs/albums/playlists',
|
||
href: '/projects/spotify-beatmap-finder',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'derby lens (warning: AI slop)',
|
||
subtitle: 'estimate odds for horse betting',
|
||
href: '/projects/derby',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'world opinions (warning: AI slop)',
|
||
subtitle: 'ideologies, values and biases across countries',
|
||
href: '/projects/world-opinions',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'language learner (warning: AI slop)',
|
||
subtitle: 'Dutch, Korean and English, from zero to fluent',
|
||
href: '/projects/language-learner',
|
||
external: false
|
||
}
|
||
] as const;
|
||
|
||
type Tracker = {
|
||
name: string;
|
||
subtitle: string;
|
||
href: string;
|
||
external: boolean;
|
||
/** Sub-trackers linked directly from this item (header flyout, tile chips). */
|
||
children?: readonly Tracker[];
|
||
/** Icon key in #lib/tracker-icons when it differs from `name` (games). */
|
||
icon?: string;
|
||
/** Shown on group tiles before the "more" chip (main and actively played games). */
|
||
featured?: boolean;
|
||
/** Owner-only (needs login): every listing shows a small lock after the name. */
|
||
locked?: boolean;
|
||
};
|
||
|
||
// The spotting trackers: pokedexes of things I can spot in the wild. Grouped under one
|
||
// "spotting" item in the header, home page and /trackers; each keeps its own URL.
|
||
export const spottingTrackers: readonly Tracker[] = [
|
||
{
|
||
name: 'cars',
|
||
subtitle: 'a car pokedex: every brand and model, with Dutch and US data',
|
||
href: '/trackers/cars',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'trains',
|
||
subtitle:
|
||
'a train pokedex: steam, diesel and electric locos, high-speed trains, multiple units, metros and trams',
|
||
href: '/trackers/trains',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'planes',
|
||
subtitle: 'a plane pokedex: airliners, helicopters, bizjets, light aircraft and warbirds',
|
||
href: '/trackers/planes',
|
||
external: false
|
||
},
|
||
{
|
||
name: 'wildlife',
|
||
subtitle:
|
||
'a wild animal pokedex: everything you can spot in the Netherlands, plus famous animals worldwide',
|
||
href: '/trackers/wildlife',
|
||
external: false
|
||
}
|
||
];
|
||
|
||
// Games I play (plus speedcubing), grouped under "games" like spotting: every game has its
|
||
// 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).
|
||
const game = (slug: string, name: string, subtitle: string, featured = false): Tracker => ({
|
||
name,
|
||
subtitle,
|
||
href: `/trackers/games/${slug}`,
|
||
external: false,
|
||
icon: `game-${slug}`,
|
||
...(featured ? { featured } : {})
|
||
});
|
||
export const gameTrackers: readonly Tracker[] = [
|
||
game('osu', 'osu!', 'rhythm game, my main focus right now', true),
|
||
game('cs2', 'cs2', 'Counter-Strike 2 (CS:GO)', true),
|
||
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('valorant', 'valorant', 'Riot’s tactical shooter', false),
|
||
game('lol', 'league', 'League of Legends', false),
|
||
game('overwatch', 'overwatch 2', 'Blizzard’s hero shooter', false),
|
||
game('minecraft', 'minecraft', 'blocks, mining and building', false),
|
||
game('factorio', 'factorio', 'factory building and automation', false),
|
||
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)
|
||
];
|
||
|
||
// Trackers, in display order. Names are short single nouns. Most keep their existing
|
||
// /projects/... URLs (no moves, so no redirects needed). `private` trackers are shown in the
|
||
// header, home page and /trackers only to the logged-in owner (layout data `isOwner`), never
|
||
// in the sitemap; their routes 404 for everyone else. Private trackers, and listed ones that
|
||
// still need a login (`locked`, e.g. money), get a lock icon next to their name everywhere.
|
||
const allTrackers: readonly (Tracker & { private: boolean })[] = [
|
||
{
|
||
name: 'tasks',
|
||
subtitle: 'to-dos, goals and my TickTick history',
|
||
href: '/trackers/tasks',
|
||
external: false,
|
||
private: true
|
||
},
|
||
{
|
||
name: 'money',
|
||
subtitle: 'a manual ledger for accounts, bills and utilities',
|
||
href: '/trackers/money',
|
||
external: false,
|
||
locked: true,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'reading',
|
||
subtitle: 'books, scientific papers and articles I am reading, have read and want to read',
|
||
href: '/trackers/books',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'spotting',
|
||
subtitle: "cars, trains, planes & wildlife I've spotted",
|
||
href: '/trackers/spotting',
|
||
external: false,
|
||
children: spottingTrackers,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'music',
|
||
subtitle: 'everything I listen to: top artists, tracks, albums and streaks',
|
||
href: '/trackers/music',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'pc',
|
||
subtitle: 'my PC setup: every part, my devices, the next build and mouse sensitivities',
|
||
href: '/trackers/pc',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'health',
|
||
subtitle: 'body, workouts, sleep, meds and vaccinations',
|
||
href: '/trackers/health',
|
||
external: false,
|
||
private: true
|
||
},
|
||
{
|
||
name: 'groceries',
|
||
subtitle: 'supermarket products, favorites and prices per store',
|
||
href: '/trackers/groceries',
|
||
external: false,
|
||
private: true
|
||
},
|
||
{
|
||
name: 'houses',
|
||
subtitle: 'rentals around Gouda, applications and alerts',
|
||
href: '/trackers/houses',
|
||
external: false,
|
||
private: true
|
||
},
|
||
{
|
||
name: 'holders',
|
||
subtitle: 'who has my address, email, phone and bank details, and a moving checklist',
|
||
href: '/trackers/data-holders',
|
||
external: false,
|
||
private: true
|
||
},
|
||
{
|
||
name: 'energy',
|
||
subtitle: 'Dutch electricity mix, utilization and gas storage',
|
||
href: '/projects/energy',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'games',
|
||
subtitle: 'every pokemon in every game, and call of duty camo mastery',
|
||
href: '/projects/game-tracker',
|
||
external: false,
|
||
children: gameTrackers,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'skins',
|
||
subtitle: 'Counter-Strike skin market prices across time',
|
||
href: '/projects/cs-prices',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'pets',
|
||
subtitle: 'animaldex: add neighborhood pets to the world map',
|
||
href: '/projects/animaldex',
|
||
external: false,
|
||
private: false
|
||
},
|
||
{
|
||
name: 'lists',
|
||
subtitle: 'books, games, places and other things worth keeping',
|
||
href: '/lists',
|
||
external: false,
|
||
private: false
|
||
}
|
||
];
|
||
|
||
const strip = ({
|
||
name,
|
||
subtitle,
|
||
href,
|
||
external,
|
||
children,
|
||
locked,
|
||
private: hidden
|
||
}: Tracker & { private: boolean }): Tracker => ({
|
||
name,
|
||
subtitle,
|
||
href,
|
||
external,
|
||
...(children ? { children } : {}),
|
||
...(locked || hidden ? { locked: true } : {})
|
||
});
|
||
/** Public trackers (sitemap, logged-out navigation). */
|
||
export const trackers: readonly Tracker[] = allTrackers.filter((t) => !t.private).map(strip);
|
||
/** Owner-only trackers. */
|
||
export const privateTrackers: readonly Tracker[] = allTrackers.filter((t) => t.private).map(strip);
|
||
const ownerTrackers: readonly Tracker[] = allTrackers.map(strip);
|
||
|
||
export function visibleTrackers(isOwner: boolean): readonly Tracker[] {
|
||
return isOwner ? ownerTrackers : trackers;
|
||
}
|