feat(hikes): inline cantonal Wappen next to region label

26 public-domain coats of arms fetched once from Wikimedia Commons
via scripts/download-cantons.ts and committed under static/cantons/.
$lib/data/cantons.ts maps Swisstopo's free-form name (German default,
French/Italian alternates for Romandie / Ticino) to the ISO code +
emblem URL.

Card shows an 18×22 emblem, detail page a 24×30 one — both with a
drop-shadow so they read against the dark hero gradient. Unknown
canton names fall back to plain text without the emblem.

The downloaded SVGs are written verbatim — earlier draft prepended a
provenance HTML comment but that breaks the leading `<?xml … ?>` and
browsers refuse to render the image. Provenance lives in the script's
CANTONS table instead.
This commit is contained in:
2026-05-19 08:44:30 +02:00
parent 2c3886296c
commit cfdd58fb18
31 changed files with 2371 additions and 3 deletions
+35 -1
View File
@@ -16,6 +16,7 @@
import CalendarRange from '@lucide/svelte/icons/calendar-range';
import Download from '@lucide/svelte/icons/download';
import { buildGpx, type GpxWritePoint } from '$lib/gpx';
import { resolveCanton } from '$lib/data/cantons';
import type { HikeTrackPoint } from '$types/hikes';
import type { PageProps } from './$types';
@@ -49,6 +50,8 @@
return () => mq.removeEventListener('change', onChange);
});
const canton = $derived(resolveCanton(hike.canton));
const heroPose = $derived.by(() => {
if (
narrowViewport &&
@@ -334,7 +337,21 @@
<h1>{hike.title}</h1>
{#if hike.region}
<p class="region">
{hike.region}{hike.canton && hike.canton !== hike.region ? `, ${hike.canton}` : ''}
{#if canton}
<img
class="canton-emblem"
src={canton.emblemUrl}
alt=""
aria-hidden="true"
loading="eager"
decoding="async"
/>
{/if}
<span class="region-text">
{hike.region}{hike.canton && hike.canton !== hike.region
? `, ${hike.canton}`
: ''}
</span>
</p>
{/if}
</div>
@@ -571,11 +588,28 @@
}
.region {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.2rem 0 0;
opacity: 0.9;
text-shadow: 0 1px 4px rgb(0 0 0 / 0.45);
}
.canton-emblem {
flex: 0 0 auto;
width: 24px;
height: 30px;
object-fit: contain;
/* Drop shadow keeps the emblem readable on the gradient overlay
* (which only goes dark from ~50 % down). */
filter: drop-shadow(0 1px 3px rgb(0 0 0 / 0.5));
}
.region-text {
min-width: 0;
}
.metrics {
display: flex;
flex-wrap: wrap;