Files
Portfolio/docs/redesign/round5.md

18 KiB
Raw Permalink Blame History

Round 5: The Gantry

Brainstormed autonomously (Luca gave "100% leeway" and asked for the brainstorming and frontend-design passes). This file records the understanding, the options weighed and the decisions. Evidence is appended at the end once the work is done.

Understanding

What Luca said (round 4 feedback, desktop first, mobile must not break):

  1. A cinematic outro that mirrors the intro.
  2. Two red lights drift away from the car, faster than it, while scrolling. They must stay locked to the car.
  3. Remove the white rectangles that flash on chapter changes. Nothing may flash more than 3 times a second; no large bright flashes at all.
  4. The top bar feels unfinished. It should feel ready and distinctive.
  5. and 6. The empty band between the bar and the content (kept clear for the 3D signs) is jarring. Keep the signs, make them better, kill the gap.
  6. A lighter car on mobile, a cooler car on desktop (headless Blender only).
  7. The soundtrack and settings buttons get in the way of content.
  8. Card titles sit inside some cards and outside others. One rule.
  9. Much more motion, fluid and professional, reduced motion respected.

Assumptions: the night-drive identity, the content and the copy stay. "Signs" means the chapter exit signs, not the districts. "Professional" rules out gimmicks such as cursor trails, and anything that slows reading.

The big one: where do the signs live? (items 4, 5, 6, 8)

The band exists because the signs dock at the top of the frame, and the content had to stay out of their way.

Option For Against
A. Signs pass at the screen edges Real roadside feel Content spans 1280 of 1440 px. Edges are covered for most chapters. Needs gutters, which is the same waste turned sideways.
B. Camera reframes so signs sit beside the content No new UI Every chapter layout differs (right rail, left timeline, full diagram). No side is free everywhere.
C. Content panels framed by the sign Strong idea for openers The sign is in the canvas, under the DOM. A frame has to be drawn twice and kept in sync on every scroll. Fragile.
D. Sign flies into the HUD (3D to 2D handoff) Clear story, frees the band On its own, the 2D twin is just a label.
E. The top bar becomes an overhead gantry, the sign docks in its window (A + D) The bar and the signs become one object. Content gets the full height. The bar gains a reason to exist. Needs a see-through window in the bar and a clipped reading viewport.

Decision: E, "The Gantry".

  • The top bar is redrawn as a steel overhead gantry spanning the road: a full-bleed beam with rivet rails, a route line and the exits.
  • Its centre holds a sign window: a real cut-out the WebGL canvas shows through. The reading viewport is clipped at the beam's lower edge, so content never shows through the window, and content now starts directly under the beam. The gap is gone.
  • On each chapter approach, the 3D sign rises out of the road on a small overhead truss, grows as it approaches, and locks into the window (the handoff). It holds, then passes overhead, out of the top of the frame, exactly like driving under a gantry.
  • Between signs, the window shows its 2D twin: the current exit, the next exit, route progress and the Bucharest clock (the old bottom-right HUD).
  • The window is interactive: hover tilts the docked sign toward the pointer. Clicking it, or pressing Enter, drives to the next exit.
  • The soundtrack and settings controls move into the gantry's right bay as compact icon buttons. Nothing floats over the content any more.
  • Mobile keeps its compact route bar and the screen-anchored shelf sign. The shelf now docks into the bar too, so mobile also loses its band.

Other decisions

  • Car lights (2), root cause first. See "Car light bug" below. Fixed at the cause, not by hiding symptoms.
  • Flashing (3). The exit gates (nine near-white frames at about 4.5 Hz with a lens flash and a bloom lift) are deleted, along with the flash uniform. The chapter change is now carried by DOM motion: the plate entrance, the gantry plate swap and the palette drift. Every remaining blinking light is audited against the 3 Hz rule.
  • Outro (1). It mirrors the intro. On arrival, the camera cranes up and back to the sun, a telephoto end shot. Letterbox bars close in on every tier (as CSS, so even the low tier gets them). A closing credit line made only of real facts runs in the lower bar. Leaving the destination reverses it. Under reduced motion, the bars appear without a crane.
  • Car (7). Mobile loads a decimated GLB, built by a headless blender -b script. Desktop gets better coachwork: clearcoat paint, a lit tail bar with a soft halo, and a stronger rim from the key light.
  • Cards (9). One rule: chapter titles sit outside, above their plate; every card title sits inside its card, first, under its mono plate label.
  • Motion (10). Chapter plates and cards rise and unmask on entry, with a stagger. Hover lifts cards with an accent rule. The gantry plate swaps on every chapter change, and the exits light in sequence. All of it is transform and opacity only, behind prefers-reduced-motion: no-preference. No cursor effects.

Car light bug (systematic debugging record)

  • Symptom. While scrolling, two red lights near the car move separately from it and faster, then drift away.
  • Evidence. Frame bursts at 1440, both idle and scrolling, show bodiless red lamp pairs with trails and wet-road pools next to the car's flank, at the height of its rear diffuser. These are world/traffic.ts vehicles: they appear only where traffic lanes are, and their speed matches traffic, not the car.
  • Root cause. Traffic was authored at 1x scale while the hero car renders at 2.78x (a half-width of about 2.6 units).
    • The fast lane at x = -2.6 puts one lamp at x = -1.8, inside the car's footprint. The truck lane at x = 4.9 runs along its flank.
    • Lamp heights (0.95 to 1.95) and lamp spacing match the car's own tail lamps.
    • Traffic moves at its relative speed, up to about 110 units/s while scrolling. So a bodiless pair of red lamps slides out of, or past, the car. It reads as the car's own taillights coming loose.
  • Fix.
    • Lanes are moved clear of the car's envelope.
    • The car's near field (from 18 units ahead of its nose to behind the camera) is a hidden band. Traffic fades out before it and wraps inside it, so no light ever passes the car.
    • A unit test pins both rules.
    • The car's own lamps and the wake stay parented to the car, as before.

Evidence

All captures are 1440x900 unless named otherwise, from the production preview build on a headed GPU Chromium. They are in docs/redesign/round5/.

Item Before After
4, 5, 6, 8 Gantry gantry-before-1440.webp gantry-after-1440-incoming.webp, -docked.webp, -idle.webp, gantry-after-1024.webp, gantry-after-390.webp
1 Outro outro-before-1440.webp outro-after-1440.webp
7 Car left half of car-before-after-desktop.webp right half; car-lite-390.webp
9, 10 Cards and motion cards-before-1440.webp cards-after-1440.webp
  • Gantry docking (headed run, clicking Profile). idle at 300 ms, incoming at 700 ms, docked from 1000 to 2600 ms, passing at 3300 ms, idle at 3700 ms. The face lands within 2 px of the window at 1440x900, 1920x1080, 1024x768, 390x844 and 844x390 (unit tests). Clicking the window, or Enter on it, drives to the next exit (Start to Profile, then Experience) at 1440 and 390.
  • A mask bug found on the way. The window cut-out used --window-x as a mask-position. Percentages there resolve against the box minus the layer, so the hole sat 128 px left of the window and the beam hid half of the sign. The cut-out now has its own --window-mask-x.
  • Car. Phones and the low tier load the lite model: 31,288 to 11,027 faces, 1.19 MB to 0.50 MB raw. The transfer saving is small (149 KB to 143 KB), because the geometry already compresses well. The real wins are parse time and GPU memory.
  • fps (same machine, same script, Intel Arc iGPU, headed).
    start infrastructure builds toolkit contact
    Before 70.1 82.6 83.8 81.8 87.2
    After 68.7 83.5 79.7 83.7 95.1

Design review pass (Fable)

A design consultant reviewed the first pass. Its calls were adopted, except where noted under "Deviations". Evidence is in docs/redesign/round5/.

The sign interlude (items 5, 6)

  • Sequence.
    • The reading plates step aside: 260 ms in-quart, rising 24 px. Headings fade only.
    • The road and the sign then have the frame to themselves. The sign hangs from a 3D gantry truss, about 600 px wide in the upper third.
    • It lifts off the truss and arcs into the window, landing within 2 px (unit tested). The truss passes overhead as it docks.
    • The plates rise back with a 90 ms stagger (720 ms out-expo).
    • The whole sequence is driven by html[data-interlude] (on, then rise), which the scene loop owns.
  • Window.
    • At 1280 px and wider it is 320x72, with an 8 px bulge in the beam.
    • The rails sit at 40%.
    • An amber cone of light falls below the window.
  • Docked.
    • The 3D face hands off in 200 ms to a flat 2D plate. The plate mirrors the face: tab, label fitted like the canvas, and lane rule. So the handoff lands in place (window-handoff-1440.webp).
    • Hover brings back the 3D twin, which leans at most about 4 degrees, with a "DRIVE TO EXIT NN →" label (window-hover-1440.webp).
  • Skips.
    • Fast scrolling: brisk latches skip the interlude.
    • A hash load or back/forward lands docked without replaying.
    • Reduced motion: no scene and no interlude. The plates simply change.
  • Measured at 1440 (nav click).
    • Plates hidden from 340 to 1246 ms, rise until 2355 ms, idle at 3388 ms.
    • About 1.9 s with the rise, against the brief's ~1.4 s. This was kept so the arc stays readable.
    • A wheel crossing triggers the interlude.
    • A 2500 px jump shows no sign.

Top bar and controls (items 4, 8)

  • Layout. Brand | exits 01–04 | window | exits 05–08 | bay.
  • Window plate.
    • It shows the current exit tab in the sign colour, the chapter, a route rule, and a chevron breathing at 0.5 Hz.
    • At the destination the chevron reads "AGAIN ↺", and the window drives back to the start.
  • Exit lamps.
    • The current exit lamp carries an 18 px ghost swatch in the sign colour.
    • The NEXT EXIT plaques and the RESUME chip are retired. Resume now lives in the window ("RESUME EXIT NN →").
  • Bay.
    • Two lilac tiles: the soundtrack, with a three-bar equaliser that moves at most 1 Hz while playing, and settings.
    • Settings opens a popover: 240 ms out-expo, Esc closes it, focus is trapped, and it becomes a bottom sheet on phones. Volume and Pause motion moved into it.
    • The active state is amber.
    • Tooltips hang from the button's right edge, are hidden while a popover is open or on touch, and are kept out of the accessible name.
  • Title stack. The chapter title is smaller (5.6vw, up to 100 px), with a tighter gap, so the body starts about 50 px higher at 1440.

Cards (items 9, 10)

  • One rule. A single .plate with panel, record and grid variants.
  • Motion.
    • Arrivals ease out-expo, departures in-quart.
    • Durations are 160, 320 and 720 ms.
  • Entrance. A 28 px rise plus a top-down clip-path unmask over view() entry 0–55%. Firefox and other browsers without animation-timeline get an IntersectionObserver fallback (useCardEntrances).
  • Why translate. Entrances animate translate, not transform. A filled animation overrides transform, which had silently disabled the hover lift.
  • Focus. Focus-visible gets the hover accent edge plus an outline.
  • chapter-settle removed. It is dropped because the interlude now owns the chapter change ("one timeline owns the chapter change").

Outro: Engine off (item 1)

  • Car and scene. On arrival the drive winds down to a stop: travel, traffic and wheel spin. The tail and running lamps drop to parking level, the sky darkens 10%, stars rise about 80%, and the grid fades.
  • Sound. The engine and road noise take a 1.2 s low-pass sweep to idle when the final exit is reached. Driving away restores them.
  • Letterbox.
    • Each bar stays within 8vh.
    • The credit, built from real identity facts, types into the lower bar in mono. It cannot go in the upper bar, which the contact avatar shares.
    • The Email CTA and the END OF ROUTE plaque stay clear at 1440 and 390.
  • Reduced motion. Bars and poster dimming only; the credit simply appears.

Found on the way

  • The mouse wheel did not scroll at all.
    • Cause: overscroll-behavior: none on the script-scrolled, overflow-hidden reading viewport, introduced in 71959f7, swallowed wheel events.
    • Removed. The wheel now scrolls and wheel crossings raise signs.
  • The docked plate's root was a fragment. A template comment made it one, which dropped aria-hidden from the HUD in tests. The comment was moved into the script.
  • Grey slabs on the road. These are the existing Blender district billboards and gantries, not the new truss. They were left as authored.

Deviations from the review

  • Bay targets. The buttons stay 44 px targets with a 36 px drawn tile. The keyboard and pointer-target tests require 44x44.
  • A third recovery button. It stays in the bay while motion is paused or static ("Enable motion"). Reduced-motion users must reach it without opening settings.
  • Interlude length. It runs about 1.9 s rather than 1.4 s; see above.
  • The 2D crossfade under reduced motion. It is a plain swap: with reduced motion the scene never starts, so there is no 3D sign to crossfade from.

Risks checked

  • mask-composite with backdrop-filter: no backdrop-filter is used anywhere.
  • No animation-timeline (Firefox): covered by the IntersectionObserver fallback.
  • will-change: none added.
  • Scene cost: the engine-off and interlude scene work is uniform writes only. It is free on every tier.
  • Adaptive tiers: the low tier keeps the CSS bars and plates.

Verification after the review pass

  • Title stack (1440x900, Experience). Below the 72 px beam, the section starts at 100 px, the h2 at 148–245 px, and the chapter body at 268 px. Evidence: round5/title-stack-1440.webp.
  • Interlude sequencing. A CDP screencast of a Start → Experience click showed the Profile chapter flashing past during the smooth scroll, before the sign's approach hid the plates. A long navigator jump (more than 0.6 viewport) now pre-arms data-interlude="on" at click time, while the scene runs. The loop takes the attribute over when the interlude starts, and a 1.4 s timeout lets the plates rise if no interlude follows. Evidence: round5/interlude-prearm-1440-sequence.webp.
  • Found by the browser suite.
    • The volume <output> was an implicit second live region. It is now an aria-hidden readout, and the slider carries aria-valuetext.
    • The bay tiles lost their outline in forced colours, because the gradients are dropped. They now get a ButtonText border.

Playwright against the round-4 baseline

The baseline is 63 failures. The final full run gave 147 passed, 88 failed and 55 skipped. Of the 27 failures new against the baseline, 25 were coupled or fixed and re-run:

  • Coupled to Volume and Pause moving into settings: audio-controls ×3, bfcache, scene-controls and visual-regression audio. These tests were updated.
  • Coupled to the gantry: title-navigation ×2, and journey-integration ×4, where the window pre-draws the resting "Start" twin. These tests were updated.
  • Real regressions, fixed: forced colours (bay borders) and the scene-controls status (the <output> live region).
  • Flaky under full-suite load: contact-refinements reading mask. It passes on a re-run.

Two remain on both projects and were left as is:

  • journey-integration "carries a departed sign past the car"
  • journey-integration "keeps the readable Profile face outside its destination panel"

Both assert the retired roadside choreography: the sign now hands off to the window plate. Seven siblings already fail in the baseline. Rewriting the geometry oracle is out of scope.

Fixed against the baseline: accessibility-keyboard "explicit keyboard navigation" (desktop) and audio-controls compact panels (mobile).

Test edits (intentional behaviour changes)

  • Unit, tests/unit/sign-rendering.test.ts.
    • The fade test now uses relative texture counts, and the docked face is hidden after the handoff.
    • "closes in over the road in the upper third, then arcs up into the window".
    • "leaves the window to the flat plate as it departs".
    • The tilt applies only on hover.
  • Unit, tests/unit/SceneCanvas.test.ts. Volume and Pause motion are checked inside the open settings panel.
  • Browser, settings.
    • audio-controls.spec.ts: opens Drive settings for volume and pause. The volume stays visible beside Quality after Static. The interruption test lifts its synthetic state before Resume, because Resume and Mute now share one slot.
    • bfcache-lifecycle.spec.ts, scene-controls.spec.ts and visual-regression.spec.ts: open Drive settings before Pause motion or volume.
    • tests/performance/drive-trace.spec.ts: Drive settings instead of Sound volume. Not run.
  • Browser, gantry.
    • title-navigation.spec.ts: the gantry is docked and unscaled from the first frame. The current exit is its lane lamp or a rounded inset.
    • journey-integration.spec.ts: roadsideLabels() leaves out the resting "Start" twin texture from the roadside label checks.