Build guide — site 15 of 25

How METRONOME
was made

METRONOME is a fictional motion design studio that treats timing as its entire product. The audience is broadcast and brand producers deciding who to trust with an ident. The page has one job: prove the studio's taste in motion by being a piece of motion — the visitor's scroll is the playhead of the showreel.

Palette

#0A0A0A — INK
The screening room. Motion reads best against near-black; true #000 flattens on OLED.
#F5F5F3 — PAPER
Warm off-white for type and the inverted Work section — gives the page a light/dark cutting rhythm.
#2B4CFF — ELECTRIC
The single accent, used like a downbeat: tick dot, playhead, one letter of the logotype. Never for body copy on ink (3.6:1) — paper carries all small text.

Type

Space Grotesk

The voice of the studio: geometric but with cut, slightly eccentric terminals — a grotesk that behaves like a display face at 200px. Used massive and tight (-0.035em) for the hero, showreel words, and section titles; tabular figures drive the timecode HUD.

Inter — small text, labels, descriptions

Deliberately unremarkable at small sizes so the display face keeps all the attention. Uppercase 11px labels at +0.16em tracking do the wayfinding.

Techniques

Scrubbed showreel

The signature element. One GSAP master timeline (14 timeline-seconds, ease "none" at the root) is bound to a single ScrollTrigger with pin: true and scrub: 0.8 over 4.5 viewport-heights of scroll. Five scenes — film leader countdown, beat-stepped square scale-up, clip-path mask reveal of TIMING, split-flap character flips, and the FIN lockup — are absolutely stacked and toggled with autoAlpha set() calls at exact times, so scrubbing backwards replays perfectly with no state pollution. The HUD's timecode (24 s × 24 fps), scene counter, and progress bar are driven from the trigger's onUpdate; the end value is a function of innerHeight with invalidateOnRefresh so resize never desyncs pin spacing.

120 BPM pulse

An SVG pendulum swings ±16° on a 0.5 s sine yoyo — exactly 120 beats per minute. Each extreme fires an onRepeat that flashes the header tick dot, so the whole page keeps time.

Tempo readout

Second-read detail: the HUD's TEMPO field maps scroll velocity onto BPM with lerp smoothing and decays back to 120 when you stop. Scrub too fast and it flags you — "RUSHING".

Procedural everything

No raster assets: SVG leader crosshair and pendulum, CSS clip-path mask, DOM split-flap characters with 3D rotationX and a recessed transform origin, div wipe bars. Fonts and GSAP are the only external resources.

Fallbacks

prefers-reduced-motion or a failed GSAP load adds a static class: the pin is never created, the FIN lockup shows as a poster frame, and all CSS animation is killed via media query. Content order stays intact without JavaScript.

Iteration log

Pass 1 — Craft

  • Caught a real palette violation in headless screenshots: the fixed header used mix-blend-mode difference, which rendered the logo and BPM chip olive-yellow over the blue scenes and the paper Work section. Replaced with an IntersectionObserver that flips the header to ink only while paper is under it.
  • Raised HUD label opacity to .92 and scene tags to .9 — at .62 they fell to roughly 3:1 against the electric blue during the MASK scene.
  • Unified all hover easings on one curve — cubic-bezier(.22,1,.36,1) — where three near-duplicates had crept in.
  • Aligned the HUD top row below the fixed header (64px) so REC and the logo never collide.

Pass 2 — Depth

  • Added the TEMPO velocity readout with the "RUSHING" easter egg — the reel now judges your scrubbing like a conductor.
  • Gave the beat notches a bar structure: every fourth notch is full-height, so the progress bar reads as 4/4 time.
  • Doubled the FIN pulse ring and offset the second by half a beat for a syncopated ending.
  • Leader countdown numbers were hard-swapping; each now lands with a 1.22→1 scale pop so the count feels struck, not toggled.
  • Square scale-up now enters with back.out(2.5) and pulses via steps(2) before the expo.inOut fill — three distinct musical gestures instead of one zoom.

Pass 3 — Hardening

  • 375px: pendulum hidden, BPM counter reduced to its dot, work rows collapse to a two-line grid, showreel words re-clamped so EVERYTHING. never wraps.
  • Verified reduced-motion path never registers ScrollTrigger — no pin spacing is added, page is a plain document.
  • End value made a function with invalidateOnRefresh; anticipatePin:1 removes the one-frame jump on fast scroll into the pin.
  • Chanel rule: the FIN lockup carried both an ® mark and the pulse dot — two competing marks, ugliest in the reduced-motion poster frame. The ® went.
  • Verified in headless Chrome at 1440 and 375: pin start/end exactly 4.5 viewport-heights, zero horizontal overflow, zero console errors, reverse scrub replays clean.

← Back to the site  ·  Collection index