Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s
92 lines
3.0 KiB
Svelte
92 lines
3.0 KiB
Svelte
<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>
|