The wax cylinder
The hero is one open-ended CylinderGeometry wearing a custom ShaderMaterial. There is no groove geometry — modelling 210 turns of spiral would drown the GPU. Instead the fragment shader fakes the grooves by perturbing the surface normal along the cylinder's axis, so light rakes across ridges that do not exist.
// helical groove: phase runs along the axis and around the barrel float phase = (vUv.y * 210.0 + vUv.x) * 6.2831853; float dg = cos(phase) * 0.9; // groove slope vec3 N = normalize(vWNormal + vWAxis * dg * 0.55);
A tight specular term (pow(…, 90.)) gives the hairline glints that make it read as wax rather than corduroy; a broad low-exponent term adds the soft sheen. A hash-based grain mottles the colour so no two rings look identical.
Lowering the needle
Nothing makes a sound until you click. On that click a single AudioContext is built and every layer of a phonograph is synthesized live — no audio files ship with this page:
- Surface hiss — looped white-noise buffer through a gentle bandpass.
- Crackle — 130 exponentially-decaying pops baked into a four-second loop.
- Wow & flutter — a slow LFO and a fast LFO both modulating the tone's frequency, the exact defect that makes old wax warble.
- A voice — a two-partial sine "sings" an original lullaby phrase, scheduled a bar ahead, all routed through a 260–3400 Hz band that mimics a brass horn's response.
Meanwhile the shader's uNeedleT uniform advances, drawing a faint bright band that tracks across the wax exactly where the on-screen needle arm is resting.
The catalogue & the paper
The accession cards are plain HTML set in Special Elite, a typewriter face, over a repeating linear-gradient that draws the ruled lines. A punched hole, a rotation of half a degree, and a red rubber-stamp "played once" on hover do the rest. Every recording described is invented; every place name is fictional.
The archive photograph
The one image on the site — the vault anteroom behind the preservation section — was generated with a nano-banana still (an Edwardian shelf of labelled cylinder boxes under a single brass lamp), then compressed to WebP and dropped to 42% opacity behind a warm vignette so the type stays legible.
Motion with manners
The render loop follows a settle pattern: draw one frame, wait 1.5 s, then start requestAnimationFrame — which keeps screenshot tools from hanging on the live WebGL. The loop pauses on document.hidden, reveals honour IntersectionObserver, and prefers-reduced-motion freezes the cylinder into a still portrait and silences all drift.