How this site was built.
A service-manual for the OSC Recordings site: the concept, the phosphor palette, the type pairing, and how the playable sequencer in the hero actually schedules sound. Everything here is hand-written HTML, CSS and one vanilla JS file — no frameworks, no build step, no images.
Concept & art direction
THE MODULE IS THE HERO
OSC is a small-run label for modular and hardware synth music, so the site behaves like hardware: the hero is a real, playable 8-step sequencer with three Web Audio voices, a tempo knob and an oscilloscope on the master bus. Everything else — catalog spines you pull off a shelf, liner notes set like small print, silkscreen labels — stays disciplined so the instrument carries the boldness.
The look is CRT phosphor green on near-black: one glowing ink, panel greys mixed from the same green, and patch-cable primaries (red, yellow, blue, orange, pink) used only as tiny voice and release markers. Intimate and hardware-shaped, not festival-loud.
Palette
ONE PHOSPHOR, MANY PANELS
Every grey is green-tinted so the whole page reads as one CRT. The glow itself is a shared CSS token — --glow — so LEDs, lit steps, the BPM readout and the logo all bloom identically.
Type pairing
SILKSCREEN MONO + RUBBERY DISPLAY
The rule: if it would be printed on the hardware, it's mono. Unbounded appears only where a record sleeve would shout.
How the sequencer works
SIGNATURE ELEMENT — WEB AUDIO, NO SAMPLES
Scheduling. setInterval alone drifts, so the module uses the classic lookahead clock: a 25 ms timer that schedules every note falling inside the next 120 ms on the audio clock, which is sample-accurate. UI events go into a queue and are drawn only when their scheduled time arrives — the playhead you see is the playhead you hear.
// 25ms timer, 0.12s horizon — timing lives on ctx.currentTime
function schedule() {
while (nextTime < ctx.currentTime + AHEAD) {
if (pattern.kck[current]) playKick(nextTime);
if (pattern.hat[current]) playHat(nextTime);
if (pattern.osc[current]) playLead(nextTime, current);
drawQueue.push({ step: current, time: nextTime });
nextTime += 30 / bpm; // 8th notes
current = (current + 1) % STEPS;
}
}
Voices. Three synthesized instruments, zero samples: a sine kick with an exponential pitch drop (150→46 Hz), a highpassed white-noise hat, and a two-oscillator detuned saw lead through a resonant lowpass and a tape-style feedback delay. Which steps you light chooses notes from a fixed A-minor-pentatonic walk, so every pattern is playable by accident.
The scope. An AnalyserNode feeds a canvas that never fully clears — each frame paints a 28%-alpha black rectangle over the last, so the trace decays like real phosphor.
// decay instead of clear = phosphor persistence
sctx.fillStyle = "rgba(4, 6, 4, 0.28)";
sctx.fillRect(0, 0, W, H);
analyser.getFloatTimeDomainData(wave);
The logo listens. Each scheduled hit adds energy to a --pulse custom property (kick 1.0, lead 0.6, hat 0.35) that decays by 12% per frame; the mark's scale and glow are pure CSS functions of it. Under prefers-reduced-motion the pulse, playhead flashes and scope animation all stand down to calm static states.
Three passes
SCREENSHOT · CRITIQUE · FIX · REPEAT
- Correctness & composition. Built the module, shelf and liner notes; killed a mobile horizontal overflow (the header nav and PWR badge fought for 390px — the nav now re-racks onto its own bordered row); balanced the hero's two-column grid so the H1 and copy share a baseline.
- Elevate. Gave the scope phosphor persistence instead of a hard clear; made SEED musically biased (kicks favour downbeats, hats favour offbeats) so random patterns groove; aligned the step-number rail to the actual grid columns and let it track the playhead — the footer now tells the truth about time.
- Taste. Chanel rule: scanlines survive only on the CRT glass, nowhere else; patch-cable colours demoted to 6px dots and 14px chips; pinned each spine's hidden body to a stable width so closed spines stopped silently tripling the shelf's height; the shelf becomes a proper accordion below 980px; verified reduced-motion serves a still scope and an unlit logo instead of a broken page.
Do this yourself
A RECIPE FOR A SITE LIKE THIS, WITH CLAUDE
- 01Pick the one object your subject's audience reveres — for synth heads it's the instrument — and ask Claude to make it real and operable in the hero, not a picture of it.
- 02Fix a one-ink palette: a single glowing accent plus greys mixed from that accent's hue. Put every colour and the glow in CSS custom properties before styling anything.
- 03Choose two faces with a rule you can say out loud ("if the hardware would print it, it's mono"). Reject anything you'd use on a different subject.
- 04Have Claude build the interactive core first — here, the lookahead scheduler and voices — and test it by ear before any decoration exists.
- 05Make structure carry information: catalog numbers that count something real, spec tables with true units, a step rail that shows actual time.
- 06Screenshot at 1440 and 390, and make Claude read the screenshots and criticise them — spacing rhythm, contrast, overflow — then fix. That's one pass; do three.
- 07Wire
prefers-reduced-motionto a designed calm state (static trace, steady LED), never a dead one. - 08Finish by removing one accessory. If a glow, gradient or scanline doesn't serve the instrument, unplug it.