Engineering

Scroll-Scrubbed Video Hero — Complete

The HUD video hero is verified in production: frame-accurate scrub at 59.94fps, spec counters that snap-lock at scroll checkpoints, a variable-font model name that morphs weight and width as it settles, and a zero-console-error Playwright confirmation.

AXIS HUD hero — scrub rail and instrument cluster in progress
Engineering002-video-hero-complete

The crown jewel, shipped

The scroll-scrubbed video hero is verified on the production build. Playwright confirms zero console errors, fonts loaded, the video scrubs frame-accurately across the 9.14s clip, and all four spec counters snap-lock with their acid-lime highlight at the correct scroll checkpoints.

This is the signature element the entire template is built around.

How the scrub works

The hero video was re-encoded with ffmpeg using -g 1 -x264-params scenecut=0: every frame is an I-frame. At 59.94fps over 9.14s that is 548 keyframes — every single one a seek target. This is what makes video.currentTime scrubbing feel cinematic rather than choppy.

The GSAP ScrollTrigger pins the hero section and drives a scrub: 1 tween. The tween’s onUpdate callback writes video.currentTime directly via requestVideoFrameCallback confirmation — not requestAnimationFrame, which can outrun the decoder. The scrub-progress rail, counter values, and font-variation-settings all write to the DOM directly on that same tick, never through React state (which would cause frame drops).

scroll progress 0 → 1
  video.currentTime   0 → 9.14
  counter[0] RANGE    0 → 612 km  (locks at progress 0.3)
  counter[1] 0–100    0 → 2.9 s   (locks at progress 0.5)
  counter[2] POWER    0 → 680 kW  (locks at progress 0.7)
  counter[3] SPEED    0 → 261km/h (locks at progress 0.9)
  scrub rail fill     0% → 100%
  Archivo wdth        80 → 102
  Archivo wght        220 → 530

Mobile path

On coarse-pointer devices and prefers-reduced-motion: reduce, the scrub is completely bypassed. The hero falls back to hero-mobile.mp4 — a 290KB autoplay loop at 800×450 — with counters displaying their final values as static readouts from the start. The real poster (hero-poster.jpg, frame 0 of the 1440×810 clip) eliminates CLS during load.

What locked as a design decision

The acid-lime “locked” state on each counter was originally intended as a brief flash. After testing, it stays permanently locked once reached — the counter does not unlock if you scroll back. This reads more like an instrument cluster initializing, which is the right metaphor. You only get one boot sequence.

The model name morph — wdth 80 → 102, wght 220 → 530 — was tuned to feel settled, not performative. The full morph completes at 60% scroll progress, so the final 40% is just the video completing with a stable, confident wordmark above it.

Verified build state

  • astro check: 0 errors / 0 warnings / 0 hints
  • bun run build: exits 0
  • Playwright production-build verification: video scrubs, counters lock, grain mounts, cursor active
  • No any in TypeScript, no <style> in TSX, BASE_URL-safe paths throughout