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:
91
src/lib/game-tracker/EncounterGrid.svelte
Normal file
91
src/lib/game-tracker/EncounterGrid.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user