From d7c13d3c9909c8ce783306836afc7c8c86d84565 Mon Sep 17 00:00:00 2001 From: Jonathan Hvid Date: Tue, 12 May 2026 14:56:41 +0200 Subject: [PATCH] feat(route): full-bleed escape + client recompute on real viewport width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled changes so the route actually uses the page width: .rr-wrap.rr-desktop gains the .rr-fullbleed class which uses the canonical calc(50% - 50vw) margin trick to break out of the parent .page max-width. The headline, dispatch banner, route section header, and legend all stay inside the content column at 72rem; only the route itself widens to the viewport. Visually reads as a magazine spread — the section header lands centred, then the path spreads outward beneath. Viewport-aware layout: SSR still uses the 1100 default (we can't know the client viewport server-side), but a new mount script on RoadmapRoute recomputes the layout against window.innerWidth and updates: - .rr-track width (via inline style) - .rr-path-svg width attribute - .rr-path-d d attribute (rebuilt from the same cubic-bezier formula the SSR helper uses, with the live itemX values; itemY comes from per-milestone data-y attributes since amplitude doesn't change with viewport) - .rr-milestone left positions Resize: 120ms debounced handler runs the same recompute + refreshes the arrow/fade nav state. Each milestone keeps its same data-y, so only the horizontal spread changes — the river's vertical shape is preserved on resize. Initial-scroll into shipping rewired to read the .rr-current milestone's live `style.left` after recompute, not the SSR-computed data-initial-x value (which is now stale once the client redoes the math). .rr-scroll horizontal padding 60 → 80 + scroll-padding-{left,right} 60 → 80 so first/last cards have breathing room inside the now- viewport-wide container. Smoke as Lars: rr-fullbleed class on the wrap, data-y attributes on each milestone, rr-path-svg id present. The SVG width and itemX positions land at viewport-derived values after mount. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/RoadmapRoute.astro | 131 +++++++++++++++++++++++------- 1 file changed, 101 insertions(+), 30 deletions(-) diff --git a/src/components/RoadmapRoute.astro b/src/components/RoadmapRoute.astro index 70a073b..3bdb2f6 100644 --- a/src/components/RoadmapRoute.astro +++ b/src/components/RoadmapRoute.astro @@ -69,12 +69,15 @@ const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex - -
+ +
-
-
+ {items.map((item, i) => (
= 0 ? layout.itemX[lastShippingIndex @@ -240,6 +300,15 @@ const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex /* ── Desktop route ──────────────────────────────────────────────── */ .rr-wrap { position: relative; } + + /* Escape the parent .page max-width so the route can use the actual + viewport width. The headline, dispatch banner, section header, and + legend all stay centred at content width — only the route widens. */ + .rr-fullbleed { + width: 100vw; + margin-left: calc(50% - 50vw); + margin-right: calc(50% - 50vw); + } .rr-scroll { /* overflow-x: auto + overflow-y: visible is the only thing that lets hovered cards expand above/below the track without being clipped. @@ -251,11 +320,13 @@ const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex scroll-snap-type: x mandatory; scroll-behavior: smooth; scrollbar-width: none; - /* Top/bottom give cards room to grow above/below the track. The 60px - sides give the first/last cards room when fully scrolled. */ - padding: 60px 60px 80px; - scroll-padding-left: 60px; - scroll-padding-right: 60px; + /* Top/bottom give cards room to grow above/below the track. The 80px + sides give the first/last cards room when fully scrolled inside + the now full-bleed container — small but visible breathing room + between the route and the absolute viewport edges. */ + padding: 60px 80px 80px; + scroll-padding-left: 80px; + scroll-padding-right: 80px; } .rr-scroll::-webkit-scrollbar { display: none; } .rr-scroll-inner { /* structural — keeps the track on its own layer */ }