trains: pokedex pages (builders, operators, countries, detail, random) and registrations
All checks were successful
Deploy production / deploy (push) Successful in 1m9s

This commit is contained in:
2026-10-25 00:11:50 +00:00
parent 8c0fc74283
commit 5f940e8196
28 changed files with 2630 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { flag, fmtNumber, monogram, brandHue, thumbAt } from '#lib/trains/shared.js';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<div class="mx-auto max-w-6xl px-4 py-12 sm:px-6 sm:py-16">
<a href="/trackers/trains" class="back"><ArrowLeft size={15} /> all trains</a>
<header class="mt-6 mb-8">
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">countries</h1>
<p class="mt-3 max-w-2xl text-[var(--site-muted)]">
Train types from {fmtNumber(data.countries.length)} countries, counted where they were built and
where they ran. Tap one for its trains, oldest first.
</p>
</header>
<ul class="grid">
{#each data.countries as c, i (c.slug)}
<li>
<a href="/trackers/trains/countries/{c.slug}" class="tile">
<span class="media" style:--hue={brandHue(c.name)}>
{#if c.photo}
<img
src={thumbAt(c.photo, 330)}
alt=""
loading={i < 8 ? 'eager' : 'lazy'}
decoding="async"
width="330"
height="186"
/>
{:else}
<span class="placeholder" aria-hidden="true">{monogram(c.name)}</span>
{/if}
</span>
<span class="name">{flag(c.name)} {c.name}</span>
<span class="meta">{fmtNumber(c.types)} {c.types === 1 ? 'type' : 'types'}</span>
</a>
</li>
{/each}
</ul>
</div>
<style>
.back {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.85rem;
color: var(--site-muted);
}
.back:hover {
color: var(--site-text);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: 0.75rem;
}
.tile {
display: flex;
flex-direction: column;
gap: 0.3rem;
height: 100%;
padding: 0 0 0.65rem;
overflow: hidden;
border: 1px solid var(--site-border);
border-radius: 0.9rem;
background: var(--site-surface);
transition:
transform 0.15s,
border-color 0.15s,
background 0.15s;
}
.tile:hover,
.tile:focus-visible {
transform: translateY(-2px);
border-color: var(--site-muted);
background: var(--site-hover);
}
.media {
position: relative;
display: block;
aspect-ratio: 16 / 9;
margin-bottom: 0.3rem;
background: radial-gradient(circle at 30% 20%, hsl(var(--hue) 35% 22%), hsl(var(--hue) 30% 9%));
}
.media img {
width: 100%;
height: 100%;
object-fit: cover;
}
.placeholder {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-size: 2rem;
font-weight: 800;
color: hsl(var(--hue) 50% 60% / 0.25);
}
.name,
.meta {
padding: 0 0.75rem;
}
.name {
font-size: 0.9rem;
font-weight: 600;
line-height: 1.2;
}
.meta {
font-size: 0.72rem;
color: var(--site-muted);
font-variant-numeric: tabular-nums;
}
</style>