Expand website and game trackers
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
This commit is contained in:
@@ -1,107 +1,211 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import GameLogo from '$lib/game-tracker/GameLogo.svelte';
|
||||
import ProgressMeter from '$lib/game-tracker/ProgressMeter.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
function percent(value: number, total: number) {
|
||||
return total > 0 ? Math.round((value / total) * 100) : 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">game tracker</h1>
|
||||
<p class="mt-2 text-[var(--site-muted)]">
|
||||
completionist progress: every Pokémon in every game, and every achievement, weapon and camo in
|
||||
Call of Duty.
|
||||
</p>
|
||||
{#if !data.user}<p class="mt-6">
|
||||
<div class="tracker-library mx-auto flex w-full max-w-[1600px] flex-col px-4 py-5 sm:px-6">
|
||||
<div class="mb-5 flex flex-wrap items-end justify-between gap-2">
|
||||
<div>
|
||||
<p class="text-xs tracking-[.18em] text-muted-foreground uppercase">Your next session</p>
|
||||
<h1 class="mt-1 text-3xl font-semibold tracking-tight">Game tracker</h1>
|
||||
</div>
|
||||
{#if !data.user}<a
|
||||
class="text-sm text-muted-foreground underline"
|
||||
href={resolve('/login?returnTo=/projects/game-tracker')}>Sign in to save progress</a
|
||||
>{:else}<a
|
||||
class="text-xs text-muted-foreground underline"
|
||||
href={resolve('/projects/game-tracker/credits')}>Artwork credits</a
|
||||
>{/if}
|
||||
</div>
|
||||
<div
|
||||
class="mb-4 grid grid-cols-2 gap-3 rounded-xl border bg-card p-3 lg:grid-cols-4"
|
||||
aria-label="Your completion dashboard"
|
||||
>
|
||||
<ProgressMeter
|
||||
label="National collection"
|
||||
progress={data.pokemon.caught}
|
||||
detail={`${data.pokemon.caught.value} unique species caught`}
|
||||
hint="Unique species caught across the Pokémon games in your rules, out of the full species catalog."
|
||||
/>
|
||||
<ProgressMeter
|
||||
label="Species seen at least once"
|
||||
progress={data.pokemon.seen}
|
||||
color="sky"
|
||||
detail={`${data.pokemon.seen.value} species discovered`}
|
||||
/>
|
||||
<ProgressMeter
|
||||
label="Pokémon games completed"
|
||||
progress={data.pokemon.completed}
|
||||
color="amber"
|
||||
hint="Games marked story complete, or with all default story milestones checked."
|
||||
/>
|
||||
<ProgressMeter
|
||||
label="Call of Duty overall"
|
||||
progress={data.codOverall}
|
||||
color="violet"
|
||||
detail="Mastery · achievements · challenges"
|
||||
/>
|
||||
</div>
|
||||
<div class="series-grid grid min-h-0 flex-1 gap-4">
|
||||
<section
|
||||
class="series-column flex min-h-0 min-w-0 flex-col rounded-2xl border bg-card p-3"
|
||||
aria-labelledby="pokemon-series"
|
||||
>
|
||||
<a
|
||||
href={resolve('/login?returnTo=/projects/game-tracker')}
|
||||
class="underline underline-offset-4">sign in</a
|
||||
> to save your progress.
|
||||
</p>{/if}
|
||||
|
||||
<section class="mt-10">
|
||||
<h2 class="text-2xl font-semibold">Pokémon</h2>
|
||||
<Card.Root class="mt-4 py-0 transition-colors hover:bg-muted/50"
|
||||
><Card.Content class="p-0"
|
||||
><a href={resolve('/projects/game-tracker/pokemon')} class="block p-5">
|
||||
<div class="grid grid-cols-[minmax(0,1fr)_8rem_auto] items-center gap-3">
|
||||
<p class="text-xl font-semibold">catchemall</p>
|
||||
<div class="w-32">
|
||||
<ProgressBar
|
||||
value={data.pokemon.caught.entries}
|
||||
max={data.pokemon.totals.entries}
|
||||
label="Pokémon caught"
|
||||
small
|
||||
href={resolve('/projects/game-tracker/pokemon')}
|
||||
class="series-heading mb-3 flex items-center gap-3 rounded-xl bg-amber-500/10 p-3 transition-colors hover:bg-amber-500/20"
|
||||
>
|
||||
<GameLogo slug="pokemon" name="Pokémon" class="h-12 w-24" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 id="pokemon-series" class="font-semibold">Pokémon</h2>
|
||||
<p class="text-xs text-muted-foreground">Open your adventure journal →</p>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
{data.pokemon.caught.value} / {data.pokemon.caught.total} species
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<div class="mb-3 space-y-2 rounded-lg bg-muted/20 p-2">
|
||||
<ProgressMeter
|
||||
label="Across every adventure"
|
||||
progress={data.pokemon.overall}
|
||||
color="amber"
|
||||
compact
|
||||
hint="Equal weight per game; collection and story stages each contribute."
|
||||
/>
|
||||
<ProgressMeter
|
||||
label="Every game’s collection"
|
||||
progress={data.pokemon.collection}
|
||||
compact
|
||||
hint="Each confirmed species/game pairing counts once, including trades and events."
|
||||
/>
|
||||
</div>
|
||||
{#if data.pokemon.games.length}
|
||||
<div class="collection-grid grid grid-cols-2 gap-2">
|
||||
{#each data.pokemon.games as game (game.id)}
|
||||
<a
|
||||
class="game-row flex min-h-0 min-w-0 items-center gap-2 rounded-lg border px-2 py-1.5 transition-colors hover:border-amber-500/50 hover:bg-amber-500/5"
|
||||
href={resolve(`/projects/game-tracker/pokemon?game=${game.slug ?? game.id}`)}
|
||||
title={`${game.name} · ${game.releaseYear}`}
|
||||
>
|
||||
<GameLogo
|
||||
slug={`pokemon/${game.slug}`}
|
||||
name={game.name}
|
||||
class="game-cover h-10 w-10"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="game-name text-xs leading-tight font-medium">{game.name}</p>
|
||||
<p class="game-year mt-0.5 text-[10px] text-muted-foreground">
|
||||
{game.releaseYear}{game.completed ? ' · ✓ story' : ''}
|
||||
</p>
|
||||
<ProgressMeter
|
||||
label="Game progress"
|
||||
progress={game.overall}
|
||||
color="amber"
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}<a
|
||||
class="rounded-xl border border-dashed p-5 text-sm text-muted-foreground hover:text-foreground"
|
||||
href={resolve('/projects/game-tracker/pokemon')}
|
||||
>Choose your games in “set rules” to build your collection here.</a
|
||||
>{/if}
|
||||
</section>
|
||||
<section
|
||||
class="series-column flex min-h-0 min-w-0 flex-col rounded-2xl border bg-card p-3"
|
||||
aria-labelledby="cod-series"
|
||||
>
|
||||
<div class="series-heading mb-3 flex items-center gap-3 rounded-xl bg-zinc-500/10 p-3">
|
||||
<GameLogo slug="call-of-duty" name="Call of Duty" class="h-12 w-24" />
|
||||
<div>
|
||||
<h2 id="cod-series" class="font-semibold">Call of Duty</h2>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{data.callOfDuty.length} campaigns of progress
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 px-1">
|
||||
<ProgressMeter label="COD completion" progress={data.codOverall} color="violet" compact />
|
||||
</div>
|
||||
<div class="cod-rows grid min-h-0 gap-1.5">
|
||||
{#each data.callOfDuty as game (game.slug)}
|
||||
<a
|
||||
href={resolve(`/projects/game-tracker/call-of-duty/${game.slug}`)}
|
||||
class="game-row flex min-h-0 items-center gap-3 rounded-lg border p-2 transition-colors hover:border-primary/40 hover:bg-muted/50"
|
||||
>
|
||||
<GameLogo slug={`cod/${game.slug}`} name={game.name} class="h-11 w-24" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm leading-tight font-medium">{game.name}</p>
|
||||
<p class="mt-1 text-[11px] text-muted-foreground">
|
||||
{game.releaseYear}<span class="ml-2"
|
||||
>{game.achievements.value}/{game.achievements.total} achievements</span
|
||||
>
|
||||
</p>
|
||||
<ProgressMeter
|
||||
label={`${game.name} progress`}
|
||||
progress={game.overall}
|
||||
color="violet"
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xl font-semibold tabular-nums">
|
||||
{percent(data.pokemon.caught.entries, data.pokemon.totals.entries)}%
|
||||
</p>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
class="series-column flex min-w-0 flex-col rounded-2xl border bg-card p-3"
|
||||
aria-labelledby="osu-series"
|
||||
>
|
||||
<div class="series-heading mb-3 flex items-center gap-3 rounded-xl bg-pink-500/10 p-3">
|
||||
<GameLogo slug="osu" name="osu!" class="h-12 w-12" />
|
||||
<div>
|
||||
<h2 id="osu-series" class="font-semibold">osu!</h2>
|
||||
<p class="text-xs text-muted-foreground">One more attempt</p>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={resolve('/projects/game-tracker/osu')}
|
||||
class="rounded-xl border p-4 transition-colors hover:border-pink-500/50 hover:bg-pink-500/5"
|
||||
><div class="flex items-center gap-3">
|
||||
<GameLogo slug="osu" name="osu!" class="h-14 w-14" />
|
||||
<div>
|
||||
<p class="font-semibold">osu!</p>
|
||||
<p class="text-xs text-muted-foreground">2007</p>
|
||||
</div>
|
||||
<p class="mt-2 text-[var(--site-muted)]">
|
||||
{data.pokemon.caught.entries} of {data.pokemon.totals.entries} obtainable entries ·
|
||||
{data.pokemon.caught.species} of {data.pokemon.totals.species} species · {data.pokemon
|
||||
.totals.games} games
|
||||
</p>
|
||||
</a></Card.Content
|
||||
></Card.Root
|
||||
>
|
||||
<Button variant="link" href={resolve('/projects/game-tracker/pokemon/locations')} class="mt-3"
|
||||
>browse Pokémon locations</Button
|
||||
>
|
||||
</section>
|
||||
|
||||
<section class="mt-12">
|
||||
<h2 class="text-2xl font-semibold">Call of Duty</h2>
|
||||
<ul class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
{#each data.callOfDuty as game (game.slug)}
|
||||
<li>
|
||||
<Card.Root class="h-full py-0 transition-colors hover:bg-muted/50"
|
||||
><Card.Content class="p-0"
|
||||
><a
|
||||
href={resolve('/projects/game-tracker/call-of-duty/[slug]', { slug: game.slug })}
|
||||
class="block h-full p-5"
|
||||
>
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-3">
|
||||
<p class="text-xl font-semibold">{game.name}</p>
|
||||
<p class="text-[var(--site-muted)]">{game.releaseYear}</p>
|
||||
</div>
|
||||
<dl class="mt-4 grid gap-3">
|
||||
{#each [{ key: 'achievements', label: 'achievements', ...game.achievements }, ...game.milestones] as row (row.key)}
|
||||
<div
|
||||
class="grid grid-cols-[minmax(0,1fr)_8rem_auto] items-center gap-3 text-sm text-[var(--site-muted)]"
|
||||
>
|
||||
<dt class="truncate">{row.label}</dt>
|
||||
<div class="w-32">
|
||||
<ProgressBar value={row.value} max={row.total} label={row.label} small />
|
||||
</div>
|
||||
<dd class="min-w-12 text-right tabular-nums">{row.value}/{row.total}</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
{#if game.tiers.length}
|
||||
<p class="mt-4 text-[var(--site-muted)]">
|
||||
{game.tiers
|
||||
.map((tier) =>
|
||||
tier.scope === 'class'
|
||||
? `${tier.label} ${tier.value}/${tier.total} classes`
|
||||
: `${tier.label} ${tier.achieved ? '✓' : `${percent(tier.value, tier.total)}%`}`
|
||||
)
|
||||
.join(' · ')}
|
||||
</p>
|
||||
{/if}
|
||||
</a></Card.Content
|
||||
></Card.Root
|
||||
>
|
||||
</li>
|
||||
{:else}
|
||||
<li class="text-[var(--site-muted)]">the Call of Duty catalog has not been loaded yet.</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<p class="mt-4 text-sm">Your training room →</p>
|
||||
<p class="mt-1 text-xs leading-relaxed text-muted-foreground">
|
||||
Scores, dream FCs, stamina and speed. Build your next personal best.
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<ProgressMeter label="osu! goals achieved" progress={data.osuGoals} color="pink" />
|
||||
</div></a
|
||||
>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.series-grid {
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.2fr) minmax(0, 0.8fr);
|
||||
align-items: start;
|
||||
}
|
||||
.game-row {
|
||||
min-height: 64px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.series-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 639px) {
|
||||
.series-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user