G SITE GUIDE · THE PROMOTION
The same building, with a spine.
The brief
Track 2, the evaluation artifact: promote the accepted track-1 Nightwork site from a static folder to Astro without redesigning it. The renders stay faithful to the director-accepted finals. Two things change, and both are the point: the content becomes real, editable structure, and the movement adopts the sample-3 motion primitives the operator preferred. This build is the proof of concept for the Brightinfinite phase-3 direction; the operator drives it personally before ruling.
- Register chamber
- Lit from within. Darkness is the room; warmth is the work happening inside it.
- Field
- Night, #0b0f11, tone tiers separating every surface. Unchanged.
- Stroke
- Window amber, #f2a93b. Unchanged.
- Surround
- Deep green, #1f3a2d, the fixed frame. Unchanged, and newly working: it is the curtain material.
- Type
- Space Grotesk + IBM Plex Mono. Unchanged.
- Stack
- Astro, static output, content collections. One client script: the motion layer.
- Growth path
- Promote to Next. Astro is the evaluation stage.
The promotion delta
What changed from the static site, and why:
- Content collections
- Five typed collections now feed the page: projects (the lit windows: client, room, what runs there, status, facade position), the night log ticker entries, services, method steps, and proof cases. Every repeating content type is a schema-checked markdown or data entry. Edit a file, rebuild, and the building rearranges itself; nothing about the design is welded to the copy.
- The facade went pure CSS
- The track-1 building was built and animated by 250 lines of runtime JS. The promotion renders all 80 cells at build time from the projects collection, and every living behavior is CSS: the power-on choreography carries per-cell delays assigned by a seeded shuffle at build, the shift scheduler became offset long-cycle keyframes, the hover readout runs on :has(), the reserved window answers the CTA through :has(), and dawn mode is a checkbox the stylesheet reads. The building no longer needs JavaScript to be alive.
- Dawn mode, zero JS
- The DAWN toggle is a checkbox and label; the theme is a CSS XOR between the ?dawn deep link class and the checkbox state, so the toggle still flips a ?dawn visitor back to night. The track-1 mobile toggle fix ships preserved: same classes, same layout. The morning inversion holds its AA-checked tones.
- Componentized floors
- Each floor of the descent is a component fed by its collection. The layout owns the tokens; scoped styles stay with their sections.
- What did not change
- Palette, type, copy, the floor-by-floor structure, the frame, the grain, the credit line. The renders are the accepted renders.
The motion mandate
The operator's ruling was explicit: he likes this building, and he likes sample-3's movement better. So the promotion carries sample-3's motion primitives, realized in the house register, as the only client JS on the site:
- Lenis bed
- Smooth scroll driven by GSAP's ticker, so ScrollTrigger and the scroll position never disagree. Wheel and touch are virtualized; keyboard scrolling stays native and Lenis resyncs from it. Anchor links ride the bed.
- Line-split reveals
- The headline, the manifesto lead, and the ground-floor title split into masked lines that rise on sample-3's reveal ease, cubic-bezier(1, 0, 0.25, 0.995). Splitting waits for the fonts so the line breaks hold.
- Ink-curtain wipes
- Sample-3 slides a solid ink panel off its media blocks. Here the panel is the surround green, and the blocks are the proof cases and the reserved-window facade: the surround sweeps off the room and the work is lit inside. Gradient material stayed banned; the curtain is flat house green.
- Count-ups
- The proof stats count to their real values on arrival. The HTML ships the finished numbers; the script only animates toward them.
- The eases
- Both sample-3 curves verbatim: cubic-bezier(0.19, 1, 0.22, 1) for everything that settles, cubic-bezier(1, 0, 0.25, 0.995) for everything that reveals.
- Reduced motion
- A pre-paint script arms the motion layer only when the visitor has not asked for reduced motion. Reduced motion, or no JS at all, gets the dignified still: the finished building, every line of copy standing, native scrolling. Nothing is blank, nothing is hidden.
The signature: a building compiled, not scripted
The facade is now a build-time render of the projects collection. The nine rooms take their place on the grid from their schema entries, and the power-on choreography is decided at compile time: a seeded shuffle hands every lit cell its arrival delay as a CSS custom property, so the shift starts in the same order on every visit, with no runtime code involved.
// power-on: shuffle the lit cells deterministically (seeded LCG) and
// hand each its arrival delay, the same 350ms + 170ms cadence.
const lit = cells.filter((c) =>
c.kind === 'room' || c.kind === 'ember' || c.kind === 'flicker');
let seed = 20260718;
const rnd = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
for (let i = lit.length - 1; i > 0; i--) {
const j = Math.floor(rnd() * (i + 1));
[lit[i], lit[j]] = [lit[j], lit[i]];
}
lit.forEach((cell, order) =>
(cell.delay = (0.35 + order * 0.17).toFixed(2))); In the stylesheet, one keyframe block plays every arrival: animation: lamp-on 1.5s backwards; animation-delay: var(--d). Under reduced motion the animation never runs and the finished composition is simply there, because the lit state is the markup's resting truth, not something a script switches on.
The critique passes
Fidelity against the accepted finals
Found: the line splitter silently destroyed the non-breaking spaces the track-1 build used to control the headline rag. Splitting text into word units turns an nbsp join into a breakable gap, so "built while" and "name on it." came apart and the hero wrapped off the accepted five-line break. Everything else rendered faithful: facade, floors, services grid, ground, footer.
Changed: the glued pairs became explicit nowrap spans, which survive splitting because the wrapper element carries the constraint, not the whitespace. Measured the rendered line breaks against the track-1 site word for word: they now match exactly.
Upgrade: the night log rides the bed. Scroll velocity speeds the ticker; settle and the night breathes at its own pace.
Function, keyboard, and the still page
Found: an interaction harness (run with reduced motion explicitly off, since headless defaults are untrustworthy here) reported the two :has() systems cold. Direct probes proved both work; the harness had measured element positions before Lenis finished settling. The same run verified the things that matter: real wheel events move the page under Lenis, space and arrow keys scroll natively, count-ups land on their exact values, the dawn XOR works in both directions including the ?dawn deep link, and the reduced page stands complete with zero hidden elements and zero curtains.
Changed: nothing needed fixing; the finding and the harness stay in the QA folder as the promotion's regression net.
Upgrade: the descent cue. Sample-3's scroll cue in house mono, with a bobbing amber arrow that stands still under reduced motion.
Dawn, proof, and the wipe that wasn't
Found: the three proof curtains shared one trigger moment and read as a single green slab row, not a wipe. Dawn mode held everything: the morning clock, the inverted tones, the lamps still on against the bone sky. The mobile ground floor and footer held their recomposition.
Changed: curtains that share a row now wipe left to right on a short stagger, so the surround sweeps across the proof instead of blinking off it.
Upgrade: the ghost drifts. NIGHT moves slowly against the scroll behind the manifesto, a long-exposure parallax scrubbed to the studio floor.
Colophon
Astro static output, five content collections, one shared layout, one client script. GSAP, ScrollTrigger, SplitText, and Lenis bundled from npm under the constitution's motion allowance; Google Fonts is the only external request. Zero console errors and a passing release gate on both routes.