Guide · How it was made
MORSE — rhythm keyed into the static
A fictional coast station, VKW, holding the 0200 watch on 500 kc/s. The page has one job: put a real key under the visitor's hand. Hold it and a WebAudio carrier sounds, your marks scroll down a live waterfall among the noise, and a decoder that only understands timing reads your rhythm back as text.
Palette — the radio room at 2 am
Every colour is an object in the room, and the ink/line split is enforced: valve amber is reserved for marks, lamps, and the carrier — never body copy.
Type
Archivo Narrow (500/600/700) is the engraved equipment label: condensed, upright, all-caps with wide tracking for panel titles and the two-line hero. IBM Plex Mono is everything the machine says — the decoder tape, frequency scale, lamps, and WPM readout. The one moment of typographic bravery is the hero's second line of type: the headline re-set in its own Morse, letter by letter, as amber dits and dahs.
Techniques
The signature is the full signal chain: key → carrier → waterfall → decoder, with one shared edge pipeline so the auto-keyer, your hand, and the noise bursts are all decoded by exactly the same code path.
- Honest timing decoder. One state variable — the unit. A mark shorter than 2.0× the unit is a dit, else a dah; 2.6× units of silence commits a letter, 6.0× commits a word space. The unit is re-estimated from every mark (EMA, α = 0.18, clamped 26–240 ms → 5.0–46.2 WPM), so the decoder follows your fist. Gap resolution runs with exact event timestamps at the top of every key-down, so decoding is frame-rate independent.
- Auto-keyer at rest. A fixed 20 WPM (60 ms unit) machine fist works the watch message — 87 characters, 777 units, 46.6 s a pass — through the same pipeline, so the waterfall is alive and half a sentence is already decoded at a 7-second settle. Your first press takes the key over (mode lamp flips AUTO→HAND); after 20 s idle the machine resumes on a fresh tape line.
- Canvas waterfall. A 1×-resolution canvas (image-rendering: pixelated, deliberately phosphor-coarse) scrolled at 90 rows/s by self-drawImage; each new row is a per-pixel ImageData composite of speckle noise, two ghost stations, and the VKW carrier as a Gaussian column (exp(−dx²/4.5)). Rows are timestamp-sampled from the edge history, so marks stay crisp even when frames are slow.
- WebAudio only after a gesture. The rest state is silent-visual; the AudioContext is created on the first trusted key press. Sidetone is a 624 Hz sine gated by setTargetAtTime (τ = 4 ms attack, 5 ms release — no clicks), over a looped band-noise bed (bandpass 900 Hz, gain 0.012 + 0.06·QRN) with a crackle tap for static crashes.
- Honest QRN. The noise slider raises the waterfall floor and injects spurious key edges — on-frequency static bursts of 0.4–2.2 units at a rate of 5·max(0, QRN−0.30)² per second (zero below 0.30, ≈2.45/s at maximum). They enter the same decoder pipeline, so copy genuinely degrades: in QA, six clean dits keyed at maximum noise came back as
EREHEwith 6 spurious edges counted. - Second reads. Ghost GNW at 498.5 actually keys a decodable
VVV DE GNWloop at an 82 ms unit; the band's noise amplitude breathes between 0.64× and 1.12× on a ≈126 s cycle and ghosts fade on a ≈203 s propagation cycle; the ship's clock lights an "ears only" tag during the real UTC silence periods (h+15–18, h+45–48). - Hardening.
window.__morsestate probe for state-gated QA; reduced motion replays the whole message through the real decoder synchronously and paints one settled waterfall frame; rAF pauses on hidden tabs with schedule re-basing; resize repaints from edge history.
Iteration log
- Pass 1 · Craft
Driving the decoder caught two real bugs the load-probe missed: the state probe double-counted the current tape line (decoded read "CQ CQCQ CQ"), and at low frame rates the auto-keyer applied the next key-down before gap resolution ran, merging letters. Fixed by committing gaps with exact timestamps inside key-down. Also: tightened the hero so the waterfall enters the first viewport (padding 78→54 px, headline three lines→two), shortened both figure captions to single lines, hid the spacebar hint under 640 px, and aligned the ZQD ghost to its printed 501.5 frequency.
- Pass 2 · Depth
Upgraded ghost GNW from random blips to a genuine keyed
VVV DE GNWloop; added ionospheric band-breathing (a 0.64–1.12× amplitude swing) and slow propagation fade to the ghosts; made the VKW·500.0 carrier label glow while the carrier is actually up. Re-drove the key after each change — a keyed ·−· still decodes as R. - Pass 3 · Hardening
Chanel rule: removed the third, unlabeled ghost station — two labeled stations carry the story. That exposed a hardcoded ghost-count loop, fixed. Final 19-check headless suite passes: auto-decode reads "CQ CQ CQ DE" from the live pipeline, keyed ·−· prints R, max QRN corrupts clean dits with spurious edges counted, no audio before a trusted gesture, 375 px shows no overflow with nav and wordmark in-viewport, reduced motion serves a fully decoded settled frame, zero console errors.
Concept in three sentences
MORSE gives the visitor a coast station's key on the international calling frequency and refuses to fake the rest. The decoder measures rhythm — two mark lengths, three silences — so what appears on the tape is genuinely what your hand sent, and what the noise floor takes from you is genuinely lost. The audience is anyone who has never felt that code is touch.