Alexander fd2d8a58d9 feat(hikes): pre-rendered static hero map with smooth Leaflet handover
Each hike now ships two SSR-friendly hero images (light + dark theme),
composited at build time from Swisstopo tiles plus an SVG overlay of the
trail polyline, start/end markers, and per-photo camera badges. The
detail page renders the right variant immediately at first paint, then
hands over to live Leaflet without visible jumps.

Renderer (scripts/staticHikeMap.ts):
- Parallel tile fetcher with on-disk cache (scripts/.cache/swisstopo-
  tiles/) for re-build idempotency.
- `computeStaticMapPose` picks the zoom + centre Leaflet's fitBounds
  would land on at a reference 1920x640 viewport, so the static frames
  the full route on every typical desktop hero.
- Canvas rendered at 3840x2400 — large enough to fully cover ultrawide
  / 4K displays at native pixel size, so `object-fit: none` keeps the
  trail pixel-aligned with Leaflet's tile pane.
- SVG overlay: trail in Nord red, start dot Nord green, end dot Nord
  red, Lucide `camera` icon inside each photo badge. Photo badge
  fill / border / icon-stroke colours are passed per theme so light and
  dark variants match the live `.hike-photo-marker .badge` styling
  exactly (Nord10/Nord8 fill, Nord6/Nord1 border, white/Nord0 icon
  stroke). Map tiles themselves are identical across themes — no naive
  invert (it mangles the Pixelkarte palette).
- Public photo markers only — private positions are filtered out so
  they don't leak in the SSR image.

Build wiring (scripts/build-hikes.ts):
- `processHero` renders both variants in parallel, hashes inputs per
  theme, skips on cache hit. Output filenames carry the content hash so
  changes invalidate cleanly via the existing orphan sweep.
- `HikeManifestEntry` gains `heroMapUrlLight`, `heroMapUrlDark`,
  `heroMapZoom`, `heroMapCenter`.

Detail page (src/routes/hikes/[slug]/+page.svelte):
- Reserves the hero box height up front (kills CLS).
- Renders both `<img>` tags; CSS picks the right one via `data-theme`
  with `prefers-color-scheme` as the fallback.
- `object-fit: none; object-position: center` so the image displays at
  native pixel size, perfectly aligned with Leaflet's tile rendering.
- `isolation: isolate` on the hero gives Leaflet's z-index:200+ panes
  a stacking context so they can't bleed over the sticky nav.

HikeMap (src/lib/components/hikes/HikeMap.svelte):
- New `initialCenter` / `initialZoom` props — when set, the map opens
  with `setView` at the static hero's pose instead of `fitBounds`.
- New `onReady` callback — fires after the post-fly-to-bounds tile
  batch finishes loading (or a 350 ms safety timeout), letting the
  detail page fade the static out onto fully-painted tiles instead of
  onto a brief grey gap.
- Sequence: render static -> Leaflet `setView` to match -> first tile
  load -> `flyToBounds(track)` to the natural fit -> wait for new
  tiles -> fade static out.
2026-05-18 23:38:24 +02:00
2023-06-24 15:33:41 +02:00
2023-06-24 15:35:36 +02:00

Personal Homepage

My own homepage, bocken.org, built with SvelteKit and Svelte 5.

Features

Recipes (/rezepte · /recipes)

Bilingual recipe collection with search, category filtering, and seasonal recommendations. Authenticated users can add recipes and mark favorites. Recipes are browsable offline via service worker caching.

Faith (/glaube · /faith)

Catholic prayer collection in German, English, and Latin. Includes an interactive Rosary with scroll-synced SVG bead visualization, mystery images (sticky column on desktop, draggable PiP on mobile), decade progress tracking, and a daily streak counter. Adapts prayers for liturgical seasons like Eastertide.

Fitness (/fitness)

Workout tracker with template-based training plans, set logging with RPE, rest timers synced across devices via SSE, workout history with statistics, and body measurement tracking. Cardio exercises support native GPS tracking via the Android app with background location recording.

Android app: Download APK — Tauri v2 shell with native GPS foreground service for screen-off tracking, live notification with elapsed time, distance, and pace.

Expense Sharing (/cospend)

Shared expense tracker with balance dashboards, debt breakdowns, monthly bar charts with category filtering, and payment management.

Self-Hosted Services

Landing pages and themed integrations for Gitea, Jellyfin, SearxNG, Photoprism, Jitsi, Webtrees, and more — all behind Authentik SSO.

Technical Highlights

  • PWA with offline support — service worker with network-first caching, offline recipe browsing, and intelligent prefetching
  • Bilingual routing — language derived from URL (/rezepte vs /recipes, /glaube vs /faith) with seamless switching
  • Nord theme — consistent color palette with light/dark mode support
  • Auth — Auth.js with OIDC/LDAP via Authentik, role-based access control
  • Progressive enhancement — core functionality works without JavaScript

TODO

General

Rezepte

Glaube

E-Mail

  • emailwiz setup
  • fail2ban
  • LDAP?

Dendrite

  • setup dendrite
  • Connect to LDAP/OIDC (waiting on upstream)
  • Serve some web-frontend -> Just element?

Webtrees

  • setup Oauth2proxy -> not necessary, authentik has proxy integrated
  • connect to OIDC using Oauth2proxy (using authentik)
  • consistent theming
  • auto-login if not logged in

Jitsi

  • consistent theming
  • move away from docker
  • find a way to improve max video quality without jitsi becoming unreliable

Searx

  • investigate SearxNG as more reliable alternative
  • consistent theming

Photoprism

  • consistent theming
  • OIDC integration
S
Description
My personal website built to learn SvelteKit. The largest part is the Recipes subsection.
https://bocken.org
Readme AGPL-3.0 160 MiB
Languages
Svelte 52%
TypeScript 43.1%
CSS 1.3%
Kotlin 1%
JavaScript 0.9%
Other 1.7%