CHOIR is a mute vaulted nave that only sings when you move. There are no recordings: a formant-synthesis voice is built live in WebAudio, its vowel set by your cursor's horizontal position, its pitch by the vertical, wrapped in a synthesised stone reverb — and it dies to breath the instant you hold still.

01Palette

Four pigments from a scriptorium: the near-black of oak-gall ink, the warm off-white of vellum, the fired red of a rubricated initial, and the dull gold of gilt. On the dark nave the vellum carries all body text, while the rubric and gilt are held back for the marks, the rules and the illuminated capitals. Because deep red and dark gold both fail on near-black at small sizes, every accent has a lit twin — a brighter rubric and a gold-leaf tone — reserved for the few places accent text sits on the dark.

Sumi
#221E1B
Oak-gall black, warmed. The nave itself, and the ground of every page below the fold.
Vellum
#EAE0CC
Prepared calfskin. Carries all body copy and headings at a clean 11:1 over the sumi.
Rubric
#8E2B24
The red of a rubricated initial. Used for marks, the drop-caps and the rose window — lifted to #CF5A48 for small text.
Gilt
#7A5A16
Dull leaf gold for rules and the resonance curve — lifted to gold-leaf #C8A24B for labels on the dark.

02Type

Cardo · display & capitals
Choir
a mute nave

Cardo is a scholarly, liturgical serif drawn for classicists — the right voice for a cathedral. It sets the wordmark, every heading and the rubricated drop-caps. In the wordmark the two vowels O and I are pulled out in gold and red, so the name itself spells the site's subject.

EB Garamond · text & interface
Formant

EB Garamond, a warm old-style face, carries the reading columns, the inscribed small-caps labels and the tabular formant tables. Its even colour keeps long text calm against the dark, and its numerals sit cleanly in the vowel reference.

03Technique

The signature: the page is mute until you move

No AudioContext exists until your first gesture. The moment the cursor stirs, the graph is created and a voice appears from silence — horizontal position chooses the vowel by interpolating between the five cardinal formant patterns (ah, eh, ee, oh, oo), vertical position sets the pitch across two octaves, and the loudness rises with how fast you move. Hold still and the voiced tone dissolves within a breath, leaving only aspiration in the last vowel's shape. The rose window at the vanishing point brightens with your loudness. Nothing here has a voice but the one your hand makes — and it exists only while you are moving.

04Build log

Pass 1 · Craft

Spacing, scale, contrast, the throat

  • Contrast discipline from the start. Applied the wave's ink/line split: vellum for every body and label text, rubric and gilt held back for marks and rules, with brighter #CF5A48 and #C8A24B twins for the handful of accent labels that sit on the dark. No small text sits on the raw rubric or gilt.
  • Type scale and bravery. Set a deliberate ramp from 10.5px inscribed labels to a clamped 74–220px wordmark, and pulled the vowels O and I out of the wordmark in gold and red — the one typographic moment, and a direct pointer at the subject.
  • Caught a dead spectrogram. Re-reading the render loop found the signature was silently broken: the frequency-to-pixel map was built once at load — before the lazy AudioContext exists — so it stayed empty and the scrolling throat never drew a single column, even though the voice was plainly audible. This is the exact failure a "no console errors, hero visible" check sails straight past. Rebuilt the map the instant audio starts and corrected the self-copy scroll; a pixel-count assertion in the headless driver now guards it (it went from 0% to 90% lit).
  • Envelope safety. Wrapped every value going to the audio graph in a finite-guard (fin()) and used setTargetAtTime throughout so no interpolation can ever pass a NaN or a zero to an exponential ramp — the failure mode that silently killed earlier audio sites.
Pass 2 · Depth

Second read, motion, micro-interaction

  • The nave answers. Tied the rose window's brightness to the live loudness of your voice and made it glow warmer on the close vowels (a brightness driven by how low F1 sits) — a detail noticed only on a second visit, and called out in the reading ("watch it while you hold an ee").
  • Two views of one throat. Added the live resonance curve beside the scrolling spectrogram and a small mouth icon that morphs open-to-slit-to-round with the vowel, so the same sound is read three ways at once.
  • Breath, not a switch. Made stillness fall to aspiration noise shaped by the last vowel rather than to hard silence, and let the vibrato depth swell with movement — so the voice feels alive rather than gated on and off.
Pass 3 · Hardening

375px, reduced motion, resilience

  • Reduced motion. With prefers-reduced-motion the spectrogram stops scrolling and shows a calm, low-rate instantaneous spectrum, the rose stops pulsing and the throat redraws only a few times a second — never blank, and the voice still answers the cursor because that is the whole point. Verified by driving the pointer under the reduced-motion media feature and asserting the voice still sounds and the throat stays lit.
  • Mobile and touch. Confirmed at 375px — via a probe that ignores clipped decorative SVG and checks each element against the viewport box, not just scrollWidth — that the wordmark, nav and voice rail sit inside the viewport, and bound pointer events with touch-action:none so a dragged finger sings on a phone exactly as a cursor does on a desktop.
  • Resilience and the no-audio path. DPR capped at 2, canvases re-size to their containers, the rAF loop and audio context both pause on tab-hide and resume cleanly. If WebAudio is unavailable the throat's resonance curve is still driven directly from the cursor's formant target, so the instrument keeps responding with a quiet note in place of the voice.