18 April 2026 · 580 words
Fixing the map: back button, tile seam, and Plan trip
One PR, five fixes. The /map page had been shipping quietly behind the main hero experience. An audit surfaced a cluster of small papercuts — none critical on their own, all visible the first time you land there.
The audit
The map page has been in the product since v1.1.x. It shows every destination in the catalog on a swisstopo base layer, clustered by region, with popups for each marker. Useful but second-class.
A structured audit before the 1.9.x arc flagged four consistent issues plus one obvious gap:
- No visible way back to the app. The logo wordmark was clickable but looked decorative.
- A hard color seam at the Swiss border when panning south or west. OSM's default palette is bright; swisstopo's pixelkarte is muted warm.
- The destination popup linked to SBB only. No Navigate, no tourism link that could stand in for Google Maps.
- No way to jump from "this destination looks good on the map" back to the home page's plan view for it.
Plus the gap: no country filter. Show me just the Dolomites was a pan-and-squint exercise.
Explicit back, centered wordmark
The map header switched from a logo-only centered wordmark to a 3-column grid-cols-[1fr_auto_1fr] with ← Back to app text on the left and the wordmark in the center column. Same pattern as ContentPageHeader since 1.8.2. The wordmark stays mathematically centered because both side columns are equal 1fr. Users who don't recognize a logo-as-link now have a plain text affordance.
Tile seam: two layers, one palette
Inside Switzerland the swisstopo pixelkarte wins. Outside, swisstopo returns 404s (swapped to a 1×1 transparent errorTileUrl in 1.8.3) and OSM shows through from the base layer. The color mismatch was obvious at the CH–DE, CH–IT, CH–FR borders: warm sepia tile, hard line, bright green OSM tile.
Fix: a CSS filter on the OSM layer's <img> tiles — filter: saturate(0.52) brightness(1.04) contrast(0.94) sepia(0.1). Tones OSM's default palette toward swisstopo's muted warmth. Pair that with lowering swisstopo opacity 1.0 → 0.94 so the OSM base softly blends through at the tile edges, and the hard line becomes a gentle style shift. Not invisible, but no longer jarring.
Navigate and Plan trip in the popup
The popup action row picked up two new links.
Navigate uses the destination's curated maps_url where set (each premium anchor has a named-place Google Maps pin picked by hand), falls back to maps.google.com/?q=lat,lon otherwise. Every destination gets a working link no matter what.
Plan trip ↻ is the more interesting one. It's a deep link back to the home page:
/?dest=<id>&origin=<name>&day=<today|tomorrow>
The home page reads those query params on mount, sets origin and dayFocus synchronously, then — in a second effect that waits for the API's first data batch — flips the matching list card and scrolls it into view. That two-effect split was not optional: the DOM node we need to scroll to doesn't exist yet on mount. The list card <article> now carries id={\dest-${id}`}` as the scroll anchor.
The result: tap a marker on the map, tap "Plan trip," land on the home page with the matching card already flipped to its plan view. No manual navigation.
Country filter chip row
Bonus scope. A six-chip row in the top-left toolbar: All · 🇨🇭 · 🇦🎟 · 🇩🇪 · 🇮🇹 · 🇫🇷 · 🇱🇮. Active chip gets an amber ring. When set to a single country, markerRows filters client-side. Show me just the Dolomites is now one tap plus a pan.
Files touched
src/components/MapPageClient.tsx for the back button plus the router deep-link handler, src/components/SunMap.tsx for the popup action row, tile opacity, country filter state and UI, and mapsHref on MapRow, src/components/DestinationCard.tsx for the id={\dest-${id}`} scroll target, src/app/page.tsx for the URL-param handler and sibling data-ready effect, and src/app/globals.css` for the OSM base filter rule.
None of these are big changes individually. Bundled into one release, they close the gap between map-mode and home-mode — the two surfaces now feel like parts of the same product instead of two experiences stitched together.
Shipped 2026-04-18 as v1.9.4. Full release notes in docs/RELEASES.md.