[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.
▊ 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 thanscrub: true. - ▸All parts start centered via
xPercent/yPercent: -50and 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"witharia-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.