Expand website and game trackers
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s

This commit is contained in:
2026-09-25 22:43:04 +00:00
parent f6b34150d4
commit c2ba8febb4
4076 changed files with 97237 additions and 1453 deletions

View File

@@ -0,0 +1,91 @@
<script lang="ts">
import { pokemonImage } from './game-art';
let {
groups,
caughtNumbers,
onselect
}: {
groups: [
number,
{
name: string;
type1: string;
type2: string | null;
minLevel: number | null;
maxLevel: number | null;
}[]
][];
caughtNumbers: Set<number>;
onselect: (number: number) => void;
} = $props();
let width = $state(600);
let height = $state(400);
// Fit every species, including broad locations with hundreds of records, into one overview.
const columns = $derived(
Math.max(
1,
Math.min(
groups.length,
Math.ceil(Math.sqrt(((groups.length * width) / Math.max(1, height)) * 0.7))
)
)
);
const rows = $derived(Math.max(1, Math.ceil(groups.length / columns)));
const cellWidth = $derived(width / columns);
const cellHeight = $derived(height / rows);
const showName = $derived(cellWidth >= 58 && cellHeight >= 42);
const showLevel = $derived(cellWidth >= 65 && cellHeight >= 64);
const spriteSize = $derived(
Math.max(
12,
Math.min(48, cellWidth - 8, cellHeight - (showName ? 14 : 0) - (showLevel ? 12 : 0) - 8)
)
);
</script>
<div class="h-full min-h-0 w-full" bind:clientWidth={width} bind:clientHeight={height}>
<div
class="encounter-grid grid gap-1"
role="group"
aria-label="All Pokémon in this area"
style:grid-template-columns={`repeat(${columns}, minmax(0,1fr))`}
style:grid-template-rows={`repeat(${rows}, minmax(0,1fr))`}
style:height={`min(100%, ${rows * 108}px)`}
>
{#each groups as [number, encounters] (number)}
{@const p = encounters[0]}
{@const levels = encounters
.flatMap((e) => [e.minLevel, e.maxLevel])
.filter((n): n is number => n !== null)}
{@const min = levels.length ? Math.min(...levels) : null}
{@const max = levels.length ? Math.max(...levels) : null}
<button
class="relative flex min-h-0 min-w-0 flex-col items-center justify-center overflow-hidden rounded-lg border bg-background p-0.5 hover:bg-muted focus-visible:z-10 focus-visible:outline-2"
class:border-emerald-500={caughtNumbers.has(number)}
aria-label={`${p.name} ${p.type1}${p.type2 ? ` / ${p.type2}` : ''} encounters${caughtNumbers.has(number) ? ', caught' : ''}`}
title={`${p.name} · Lv. ${min ?? '?'}${max !== min ? `–${max}` : ''}${caughtNumbers.has(number) ? ' · caught' : ''}`}
onclick={() => onselect(number)}
>
{#if caughtNumbers.has(number)}<span
class="absolute top-0 right-1 text-[10px] text-emerald-600"
aria-hidden="true">✓</span
>{/if}
<img
src={pokemonImage(number)}
alt=""
width="48"
height="48"
style:width={`${spriteSize}px`}
style:height={`${spriteSize}px`}
class="shrink-0 object-contain"
/>
{#if showName}<span class="block w-full truncate text-center text-[10px] font-medium"
>{p.name}</span
>{/if}
{#if showLevel}<span class="text-[10px] text-muted-foreground"
>Lv. {min ?? '?'}{max !== min ? `–${max}` : ''}</span
>{/if}
</button>
{/each}
</div>
</div>