Bénard
Colophon · how it was made

Bénard

A live GLSL simulation of Rayleigh–Bénard convection — a fluid heated from below tiling itself into breathing hexagonal cells. This page documents the palette, the type, the shader, and the three iteration passes. Everything is procedural: no video, no images, one <canvas>.

01The concept

An invisible thermal gradient, given a body.

Bénard is the fictional subject-site for the physics of self-organising heat. Its single job: let a visitor watch an invisible field become pattern. Warm a shallow fluid layer from below and, past a critical Rayleigh number (~1708), conduction gives way to convection — buoyant plumes rise, cool sheets sink, and the layer packs itself into hexagons roughly as wide as it is deep. Audience: anyone who has stared into a cooling bowl of soup and wondered why the skin cracks into cells. The page is cinematic and top-down — heat seen from directly above the plate.

02Palette

Cold plate, molten cells.

Every colour is argued from the physics: a cold blue-black plate for the sinking sheets, warming through ember to a gold plume core. The bright accent is reserved for lines and marks; body copy uses a derived warm-ash ink that clears 4.5:1 on the dark ground.

Cold-plate
#0B1622 · ground
The unheated base and the deep sinking sheets between cells. Blue-black reads as cold against the warm plumes.
Ember
#E86A2C · line / mark
The mid-column heat and the site's accent — kicker, rules, the é. Too dark for small body text, so it stays on lines.
Plume-hot
#F4C049 · rising core
The brightest rising plume at each cell centre, and the live readout numerals.
Warm-ash ink
#D6CFC2 · body (derived)
Ash carried up on the heat. ~10:1 on the plate — the legible body ink the brief asked us to derive, not the ember accent.
cold · sinkingember · mid-columnplume core · white-hot
03Type

Bricolage Grotesque & Chivo Mono.

Bricolage Grotesque carries the display: its slightly irregular, humanist grotesque shapes feel poured and settled rather than engineered — the right voice for a pattern the fluid chooses for itself. It runs at weight 800 for the wordmark and headline, 500–600 for sub-heads. Chivo Mono handles all readouts and body copy; a monospace reads as instrumentation, and its tabular figures keep the live Rayleigh number and ΔT gradient from jittering as they tick.

04Technique — the signature

A hex field built from a jittered lattice.

The convection field is a single full-screen fragment shader (raw WebGL, no library). Hexagonal cells come from a Voronoi diagram of a triangular lattice — the Voronoi of a triangular point set is a tiling of regular hexagons. Each pixel is transformed into lattice coordinates by the inverse basis Binv, and the nine surrounding lattice nodes are searched for the nearest jittered feature point (the plume centre).

A second 5×5 pass computes distance to the nearest Voronoi edge with the classic border metric dot(0.5·(a+b), normalise(b−a)) — that distance is the sinking sheet, drawn dark and cool. The temperature field is high at each cell centre, pulses per-cell on an independent phase, flickers with fbm to read as molten fluid, and occasionally flares into a brighter super-plume. The whole domain is advected by a slow curl-like fbm warp, so cells churn, merge and re-knit — the field never finishes reorganising. Temperature maps through the ramp above; a warm shoulder-ridge sharpens each hexagon wall.

Everything is guarded: DPR capped at 2 (rendered at ×0.9), sized to its container, resize-aware, the rAF loop pauses on document.hidden and out of view, WebGL context loss falls back to a static molten-gradient scene, and prefers-reduced-motion paints one settled frame with no loop.

05Iteration log

Three passes, driven not just loaded.

Pass 1 — Craft
  • Headless probe caught a 5px overflow at 375px: the decorative hero-scrim bled ±10vw past the viewport. Clipped it with overflow:hidden on the hero — scrollWidth back to 375.
  • Screenshotting the resting frame at desktop and 375px exposed the real defect: body copy over bright plumes fell below 4.5:1. Rebuilt the scrim into a left-column + bottom gradient (a mobile-only vertical variant) so the text zone darkens while the right-hand cells stay vibrant.
  • Verified the live readout actually ticks (Ra / cells / ΔT / regime) and that the delta node updates in place.
Pass 2 — Depth
  • Enriched the signature: added a warm shoulder-ridge term keyed to the edge distance, sharpening the hexagon walls so the tiling reads instantly rather than as a soft blur.
  • Second-read detail: each cell now runs an independent slow super-plume flare — return after a minute and a different scatter of cells is blazing brighter than their neighbours.
  • Kept the pointer heat-injection micro-interaction: moving the cursor warms the fluid beneath it, brightening nearby plumes.
Pass 3 — Hardening
  • Re-verified the full matrix at 375 / 768 / 1440: zero console errors (incl. WebGL), one visible <h1>, no fallback, two-frame passive hash diff confirms the cells evolve on their own.
  • Chanel rule — removed the global brightness "breathing" oscillation; it competed with the per-plume pulse and flattened the resting frame. The plume pulse and flares carry the life alone.
  • Confirmed reduced-motion renders a settled, fully-formed frame (not blank) and that context-loss swaps to the CSS molten fallback.