130 lines
7.0 KiB
Markdown
130 lines
7.0 KiB
Markdown
# 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
|
|
```
|