# Round 3: The Route ## Concept Round 2 gave the page one sign language. It still read as one page: eight identical glass cards laid over a world that never changed. Round 3 makes the drive a route, with places along it. - **The world changes per chapter.** A Blender-authored kit of roadside districts rises out of the ground for each chapter and sinks again behind you. - **The page changes its form.** Each chapter opens with its exit tab and a large heading standing over the road, then a reading panel, then a "next exit" sign. Open road separates chapters, so the districts can be seen. - **Navigation is a map of the route.** You can see where you are, which exits you have passed, and where you stopped last time. ## Changes ### World - **Roadside districts** (`assets/blender/districts/`, `src/scene/world/districts.ts`). - Eight original pieces, 5,424 triangles in total. The GLB is 84 KB raw, or 13 KB with brotli. - The kit is authored by a headless script, `author-districts.py`. It writes `nightdrive-districts.blend` with its own "Roadside Districts" scene. - `export-districts.py` writes the GLB and the provenance file. - Which pieces rise in which chapter: | Pieces | Chapters | |---|---| | Gantries | Profile to Toolkit | | Billboards | Profile, Career | | Server rows and radio masts | Infra | | Workshop and cranes | Builds | | Container yard | More work | | Destination arch | Contact | - Pieces are instanced and wrapped by the same distance shader as the expressway. Flat normals are derived per fragment, so the GLB carries positions only. - Lights take the chapter accent, catch with a neon stutter, and dim close to the camera. - Pieces at rest are hidden, so they cost no draw calls. - The low tier halves the instance counts. - Shaders compile at the on-ramp, not mid-drive. ### Page - **Reading stage** (fixed in `71959f7`, before this round's UI work). - Sticky failed because the viewport used `overflow: clip`. - The viewport is now a script-scrolled `overflow: hidden` container. - Browser-initiated inner scrolls (find-in-page, focus) are forwarded to the document. - Navigation re-lands before focus if the stage switches on or off mid-journey. - **Chapter openers** (`PortfolioSection.vue`, `route.css`). - Each chapter starts with a `clamp(54px, 7.2vw, 124px)` heading over the road, with a dark halo instead of a plate and an accent bar. - The panel chrome moves to a new `.chapter-body`. - Each chapter ends with a `NEXT EXIT 04 / Infrastructure →` sign. - Phones get the same opener, scaled to the column. - **Case studies** (≥1100 px). - Evidence sits beside a sticky media column. This was impossible before the stage fix. - Evidence reads as spec rows: a lettered stop, then the text. - **Destination.** - A 168 px heading and larger copy and CTA. - The avatar stands beside the heading. - The plate reads `END OF ROUTE · 7 OF 8 EXITS · 3 MIN ON THE ROAD`. This comes from the real visit and is rendered as CSS generated content. - **Route-map navigation.** - A road line under the exits fills up to the current one. It changes on chapter change only, never per scroll. - Nodes are hollow ahead, filled once passed, and shown as a car marker where you are. - On phones the closed bar reads `EXIT 05 · Builds`, and the open list is a vertical route. - **Persisted progress** (`useRouteMemory.ts`). - Exits passed and the last stop are kept in localStorage. - Storage is read only after mount, and failures are silent. - A returning visitor gets a "Resume · Exit 05 · Builds →" chip in the hero. The chip has an aria-label and a CSS label, so the hydrated text is identical to the server document. - **Keyboard.** Alt+↓ and Alt+↑ go to the next and previous exit. ## Decisions | Brief item | Decision | Why | |---|---|---| | Per-chapter Blender props and districts | Adopted | The biggest missing piece: the world now tells you which chapter you are in. | | Gantries | Adopted | They are part of the district kit, and their height clears the chase camera. | | Better sign geometry | Adapted | Round 2's sign faces read well at 1280, 1440 and 1920. The budget went to the districts instead. | | Atmosphere and time progression | Kept | The chapter palettes already run from deep night to pre-dawn. District accents now follow them. | | Rain or wet road | Rejected | A new full-screen pass plus reflections costs a GPU tier, and it fights the synthwave identity. | | Better hero car lighting | Deferred | The car GLB was not touched this round (see the Blender note below). | | Draco or meshopt | Rejected | The districts are 13 KB brotli, and the decoder would cost more than it saves. | | Reading panels and typography | Adopted | Chapter openers, `.chapter-body` panels and next-exit signs. | | Case studies | Adapted | Sticky two-column at ≥1100 px. Long galleries stay in normal flow when taller than the window. | | Route map or minimap | Adopted | The navigation itself is the map. No extra widget, and no per-scroll work. | | Scroll choreography | Adapted | Rising districts and the exit-tab strike carry it. The view-timeline reveals stay off inside the live stage to avoid fighting the reading mask. | | Mobile first-class | Adapted | Openers, the route bar and the vertical route list on phones. No horizontal scroll at 320 or 390 px. | | Contact as a destination and a strong ending | Adopted | The arch district, the giant heading and the trip log. | | Sticky layout | Fixed | See "Reading stage" above. | | Keyboard journey | Adapted | Alt+arrows, not digits or `[` `]`: single-character shortcuts fail WCAG 2.1.4 without a way to turn them off. | | Deep links | Kept | Hash links already land correctly and are now more robust. | | Persisted progress | Adopted | localStorage, client-only. | | Honest loading states | Kept | Districts stream in silently. A failed fetch leaves the old road. | | Sound coupling | Deferred | Round 2's music pulse already couples the world to the soundtrack. A pass-by on exit arrival needed audio design time this round did not have. | | Stylesheet consolidation | Deferred | `route.css` is one new file. Merging the eleven existing files safely needs a cascade-order audit, and its risk is out of proportion to its value. | ## Blender note An early attempt drove the user's live Blender session through MCP. Calling `bpy.data.libraries.write` with a scene crashed that Blender. The open file reported no unsaved changes beforehand. The crash log is at `/tmp/nightdrive-grand-tourer.crash.txt`. All later work ran headless (`blender -b --factory-startup`). The author script refuses to run anywhere else. ## Run ```sh source ~/.nvm/nvm.sh && nvm use 22.22.1 npm run build:preview # budgets: "violations": [] python3 -m http.server 5175 -d dist blender -b --factory-startup --python assets/blender/districts/author-districts.py blender -b assets/blender/districts/nightdrive-districts.blend --python assets/blender/districts/export-districts.py ```