feat(hikes): medium hero variant + Switzerland-framed overview, drop static→live wobble

Three related improvements to the pre-rendered hero map system:

* New medium viewport variant (561–899 CSS px) for the per-hike detail
  hero and the /hikes overview. Tablet/split-pane viewports were
  getting the wide pose (chosen for ~1920 CSS px), which landed too
  zoomed in. Each variant is rendered at a pose matching its
  container, so the static→Leaflet handover aligns at every band.
  Manifest fields are optional — pages fall back to the wide variant
  on tablets until build-hikes regenerates the images.

* Overview frames on Switzerland (fixed center [46.82, 8.23]) with
  explicit per-variant zooms (wide=8, medium=8, narrow=7) rather than
  auto-fitting the union of hike bboxes. The previous behavior zoomed
  in on whichever corner the catalogue clustered in; this reads as
  "hikes across CH". Bumps OVERVIEW_RENDER_VERSION so cached overview
  images get invalidated on the next build.

* Removed the post-tile-load flyToBounds in both HikeMap.svelte and
  HikesOverviewMap.svelte. The map already opens at the static pose
  via setView; the second auto-fit was adding a visible wobble on
  routes whose bbox sits at an integer-zoom boundary (e.g. the
  Einsiedeln–Unteriberg detail), where the build-time fit and
  Leaflet's runtime fit disagree by one zoom step at the user's
  actual container size.
This commit is contained in:
2026-05-26 11:51:48 +02:00
parent b49a299371
commit 8a67f5fba8
8 changed files with 237 additions and 173 deletions
@@ -151,35 +151,17 @@
// union bounds.
let initialBounds: ReturnType<typeof L.latLngBounds> | null = null;
// First-paint handover: when the schematic tile layer finishes
// loading its initial batch, fire `onReady` (so the static hero
// can fade out) and — if we opened with `setView` to match a
// pre-rendered hero — animate to Leaflet's natural `fitBounds`
// of the union polyline bounds. The fade overlaps with the zoom
// animation so the user sees the map ease into its final
// framing as the static dissolves. Mirrors the same pattern in
// First-paint handover: fire `onReady` once the schematic tile
// layer's initial batch loads so the static hero can fade out.
// The map already opened at the static pose via setView (see
// the initialCenter branch below), so no extra animation is
// needed — and `flyToBounds(union)` here used to cause a
// visible wobble on hikes whose union bbox sits at an integer-
// zoom boundary, where the static's fit and Leaflet's runtime
// fit disagree by one zoom step. Mirrors the same fix in
// `HikeMap.svelte`.
tileLayers.schematic.once('load', () => {
if (!initialCenter || typeof initialZoom !== 'number' || !initialBounds) {
onReady?.();
return;
}
map.flyToBounds(initialBounds, {
padding: [32, 32],
maxZoom: 13,
duration: 0.9,
easeLinearity: 0.3
});
map.once('moveend', () => {
let fired = false;
const fire = () => {
if (fired) return;
fired = true;
onReady?.();
};
tileLayers.schematic.once('load', fire);
setTimeout(fire, 350);
});
onReady?.();
});
// One polyline per hike, sourced from the manifest's already-