[CASE STUDY] // NOV 2026

How MONO
was built.

MONO is a fictional product. The point of the site was to see what happens when interaction is treated as the story, not the decoration — the exploded view teaches the anatomy, the knob shows the filter response, the pads let you hear the voice.

▊ STACK

Every layer earns its place.

Framework
Next.js 16 App Router
Static-first, zero-config, one Vercel route.
Type
TypeScript strict
Catches drag-math and Web Audio node-graph typos before runtime.
Styling
Tailwind v4
Design tokens live in globals.css, no config file.
Smooth scroll
Lenis
Buttery scroll; disabled under prefers-reduced-motion.
Scroll anims
GSAP + ScrollTrigger
Pinning + scrubbed timelines beat CSS scroll-timeline for cross-browser reliability today.
UI anims
Framer Motion
Declarative enter/exit, whileInView, focused pointer gestures.
Audio
Web Audio API (raw)
No samples, no plugins. Every hit synthesized live.

▊ ANATOMY

Pinned scroll, five parts, one component.

The section pins for about three viewport-heights. As you scroll, four parts fly outward from the chassis with staggered easing, and their labels fade in a beat behind them.

  • ▸gsap.context() scoped to the section so everything cleans up automatically on unmount.
  • ▸Timeline uses scrub: 0.6 — scroll drives the animation with a slight lag, which reads more physical than scrub: true.
  • ▸All parts start centered via xPercent/yPercent: -50 and tween to their explosion offsets. No SVG assets — every part is inline JSX so first paint is instant.

▊ SOUND ENGINE

The knob is not a Framer drag.

Framer's drag gives you two axes; I wanted linear vertical. So the knob uses raw pointer events with setPointerCapture, computes dy against the start position, and maps to a normalized 0..1. Cutoff frequency is exponential from 120 Hz to 8 kHz — a linear map would spend half the sweep in the top octave, useless.

The waveform preview is a live-computed SVG path. It sums 24 harmonics of a sawtooth with a per-harmonic rolloff: 1 / (1 + (n / cutoffScale)^4). As cutoff climbs, higher n pass through and the wave gets sharper. When you audition, a Web Audio drone (C3 saw + C2 sub sine) routes through a BiquadFilterNode whose frequency is retargeted on every knob change via setTargetAtTime — click-free filter sweeps.

▊ PLAYABLE PADS

Real synthesis, not samples.

Eight pads mapped to a C major pentatonic across two octaves. Each press spawns a fresh voice: main oscillator (saw / square / triangle, user-selectable) + sub sine one octave down, routed into a low-pass filter that sweeps 2400 → 400 Hz over 500ms and a gain envelope with 8ms attack and 550ms decay.

Keys 1–8 play too. If a coder's synth can't be played from the keyboard, the joke doesn't land. AudioContext is lazily created on first press to respect browser autoplay policy.

▊ DESIGN SYSTEM

Nine tokens, one file.

The whole palette lives in app/globals.css as CSS custom properties, wired into Tailwind via @theme inline. Terminal green is an accent, not a background — pure Matrix green everywhere would read as costume. Warm amber (#FFB800) is reserved for price and primary CTAs, so those hits have contrast against the green.

▊ ACCESSIBILITY

Every animation has an off switch.

  • ▸Every animation collapses under prefers-reduced-motion: reduce. Lenis skips init, GSAP durations are near-zero via CSS, Framer transitions pass through unmodified.
  • ▸The knob has role="slider" with aria-valuemin/max/now.
  • ▸The reservation modal is a real dialog: role="dialog", aria-modal, focus lands on the first input on open, Escape closes, backdrop click closes.

▊ TRADEOFFS

What I chose not to do.

  • ▸No 3D anatomy. A Three.js exploded view would look great, but SVG was faster and works on lower-end phones. Real hardware companies often ship SVG anatomy too — Teenage Engineering, Elektron.
  • ▸No preset gallery. The product story is "no presets you have to page through." Building a preset picker would contradict the pitch.
  • ▸No custom fonts loaded from disk. JetBrains Mono + IBM Plex Mono are Google-hosted with display: swap. Faster first paint, no font FOUT.