Series notes · design & build
The Low Room is an intimate listening-room concert series with two monthly formats — Bare Strings (stripped acoustic) and Candlelight Keys (felt piano by candlelight). The page is structured like a record: Side A and Side B, each with its room, its ritual, its liner-note setlist, and ticket-stub date cards. The art direction is candlelit warmth on a rosy midnight ground — kin to a candlelit restaurant, but with its own musical identity.
Midnight
#150D13
Mahogany
#4B2A21
Candle amber
#E8A35A
Dusty rose
#CF8F92
Cream
#F3E6D3
The midnight is deliberately warm and rosy, not neutral charcoal — it reads as a dark room lit by flame, not a dark theme. Rose belongs to Side A, amber to Side B; every accent on the page obeys that split.
Fraunces (display serif, 300–600 with true italics) carries the soul: high contrast, softly wonky, and its italic is a real cursive italic — every song title on the page is set in it, like liner notes on a sleeve. Instrument Sans does the quiet work — labels, ritual steps, meta — and was chosen half for its hushed neutrality and half because a music series set in a face called Instrument was too good to pass up.
A fixed full-viewport canvas draws six radial-gradient glows in lighter composite mode. Each "candle" breathes with three detuned sines so the flicker never loops perceptibly:
const breathe = (t, sp, ph) =>
.72 + .16*Math.sin(t*.9*sp + ph)
+ .08*Math.sin(t*2.3*sp + ph*2.1)
+ .04*Math.sin(t*5.1*sp + ph*.7);
Three of the six candles are pool candles: every section declares where its light should gather (an x fraction and an amber↔rose lean), and each frame the pool candles lerp toward the section nearest the viewport's centerline — so the light literally follows your reading, drifting left for Side A's copy, right for Side B's, warming rose or amber to match. A couple dozen dust motes drift up through the glow, nudged by scroll so they feel like they live in the room, not on the glass.
A second fixed canvas draws a hand-drawn-feeling waveform down the right edge (left edge on mobile). For every y pixel, the code maps back to a document coordinate (scrollY + y) and checks membership in the Side A and Side B sections with a feathered smoothstep band:
function inBand(y, band, feather) {
return smooth(band.top - feather, band.top + feather, y)
* (1 - smooth(band.bottom - feather, band.bottom + feather, y));
}
Membership doesn't just recolor the line — it changes its voice. Side A blends toward a plucked-string waveform (quick, bright, a jagged shimmer term); Side B toward a felt-piano one (slow rounded sines under a long swelling envelope); between them it hums a quiet room tone. A term shaped 4·m·(1−m) peaks exactly where a side hands over to the room, so the ribbon swells at every section transition. It's drawn twice per frame — a wide soft halo, then a bright core — and a glowing bead rides the viewport centerline like a stylus in a groove. With prefers-reduced-motion, the ribbon renders a single static frame and the glow field becomes a CSS gradient.
While a setlist is on screen, one line at a time is playing: the song title glows in its side's color and a hairline underline sweeps across it for a duration derived from the printed track length (4:12 ≈ 5.3 s of glow at 1/48 speed), then the next track takes over. An IntersectionObserver starts and stops the player so nothing runs off screen.
Each side carries a record label that spins slowly — an SVG disc with drawn grooves and a circular textPath ("THE LOW ROOM · SIDE A · BARE STRINGS") rotating at far below 33⅓. The ticket stubs tear on hover: the card is split into an "admit one" tab and a body at a dashed perforation, and the body rotates 2° from a corner origin like a ticket being torn. In the ritual section, a candle in the margin burns down as you scroll — its wax height is a CSS variable driven from the section's scroll progress — and each ritual line inks its own rule in as it enters, its time catching amber light.
The "Hear the room" buttons are user-initiated and synthesize eight seconds of sound from scratch — no audio files. Bare Strings runs a fingerpicked open-D pattern through a Karplus-flavored pluck (detuned triangle pair into a sweeping lowpass); Candlelight Keys stacks soft sine partials with a slow attack and five-second release to fake felt hammers:
// felt piano partials: fundamental + quiet 2nd & 3rd harmonics
[[1, 1], [2, .28], [3, .10]].forEach(([h, a]) => { ... });
The three photographs (candlelit grand piano room, wood-panelled acoustic session, strings-by-candlelight macro) were generated with Nano Banana Pro.
textLength), choreographed the hero load with three drawn flickering candles and candlelight drifting across the word "Low", lit the ritual's burning margin candle and amber times, set the interlude photo breathing with a slow Ken Burns, warmed every hover.