Colophon · How this was built

One shader, asked to behave like honey.

Everything molten on the Maison Ambre page is a single WebGL2 fragment shader on a fullscreen triangle: no textures, no post stack, no photographs. This page shows the actual mathematics, the provenance of every pixel, and the critique log of the three passes it took to make it feel expensive. Built end to end by Claude Fable 5, autonomously.

Le Brief

What the director asked for.

  • ConceptMaison Ambre, a perfume house bottling slow time. Site 08 of the fable-25 set. Luxury without photography; material depth from math; enormous whitespace; nothing cheap.
  • PaletteAmber #c8781e, deep amber #8a4d0f, black lacquer #141210, smoke #3c3733, gilt #e3b04b. Body text is a candle tint of gilt, derived per the constitution.
  • TypeItaliana for display, Montserrat light (tracked out) for UI. Vendored woff2, latin subsets, nothing from a CDN.
  • SignatureA fullscreen molten-amber fragment shader: domain-warped FBM driving color and a fake refraction/caustic term; the liquid parting around the product name; three accord panels that pull a localized swirl of the same shader in on hover; scroll-linked viscosity easing toward stillness at the footer.
  • Must proveThat a brand can feel opulent with zero imagery: the gold is computed, the restraint is typographic, and the one liquid behaves consistently everywhere it appears.

La Technique

Four excerpts, quoted from the shipping source.

I — The gold. Value-noise FBM, warped through itself twice (js/liquid.js, fragment shader)

float vnoise(vec2 p){
  vec2 i = floor(p), f = fract(p);
  vec2 u = f*f*f*(f*(f*6.-15.)+10.);
  float a = hash(i), b = hash(i+vec2(1,0)), c = hash(i+vec2(0,1)), d = hash(i+vec2(1,1));
  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
const mat2 ROT = mat2(.80,.60,-.60,.80);
float fbm(vec2 p){
  float v = 0., a = .53;
  for(int i=0;i<5;i++){ v += a*vnoise(p); p = ROT*p*2.03 + 7.3; a *= .52; }
  return v;
}

/* in main() — two rounds of domain warp, then the field */
float t1 = uT*.11, t2 = uT*.085;
vec2 q = vec2(fbm(p + vec2(0., t1)),               fbm(p + vec2(4.7,1.3) - t1*.7));
vec2 r = vec2(fbm(p + 2.6*q + vec2(1.7,9.2) + t2), fbm(p + 2.6*q + vec2(8.3,2.8) - t2));
float f0 = fbm(p + 2.4*r);

The field f0 is pushed through a five-stop ramp (lacquer → deep → amber → gilt → candle). The marbling is the warp: q bends the space r is read in, and r bends the space the color is read in.

II — The parting. A rounded-box SDF around the H1, edge roughened by the same noise

vec2 tp = (css - uTitle.xy)/S;
float dT = sdRBox(tp, uTitle.zw/S, min(uTitle.w/S, .09));
dT += (fbm(tp*2.7 + vec2(3.1,7.7) + uT*.02) - .5)*.17;   /* organic, breathing edge */
float part = (uTitle.z > 1.) ? (1. - smoothstep(-.02, .27, dT)) : 0.;
part *= heroP;

/* flow around the stone, not away from it: tangential advection,
   confined to the boundary band so the void center has no pinch */
vec2 away = tp / max(length(tp), 1e-3);
vec2 tang = vec2(away.y, -away.x);
float band = part*part*(1.-part)*3.4;
p += (tang*.8 + away*.25) * band * .82;

float f = f0 * mix(1., .30, part);   /* the liquid recedes under the name */
float rim = exp(-abs(dT - .045)*60.) * part;
col += GILT * rim * (.06 + .55*f0) * .5;   /* a faint, broken meniscus */

The same code serves twice: uTitle is the hero plate, uFoot is the footer’s “Where time sets.” block. One brand behavior, two appearances.

III — Caustics & refraction. Fold ridges of the warp, and a gradient-shifted resample of the ramp

float fold = 1. - abs(2.*fbm(p*1.8 + 2.9*r) - 1.);
float caust = pow(clamp(fold, 0., 1.), 10.);
col += vec3(1., .82, .45) * caust * (.22 + .22*f) * (1.-.85*part) * (1.-.7*setF);

/* refraction faked by re-reading the color ramp offset by the warp
   gradient — stays on the amber ramp, so the palette cannot leak */
float dsh = (r.x - q.y)*.16;
col = mix(col, ramp(clamp(f + dsh, 0., 1.)), .30);

IV — Viscosity. The clock itself thickens as you descend (JavaScript, per frame)

const maxScroll = Math.max(1, doc.scrollHeight - innerHeight);
const sc = Math.min(1, scrollY / maxScroll);
const vT = 1 - Math.pow(sc, 1.35);          // target viscosity from scroll depth
visc += (vT - visc) * .06;                  // lerped, never raw
T += dt * (.05 + .95 * visc);               // at the footer the clock nearly stops

The accord panels reuse the field through per-panel rect uniforms: hover eases a presence value up, a Gaussian around the cursor gathers the gold, and a differential rotation of the sample domain produces the swirl. Each accord tints the field to its note family — Lumière brighter, Résine deeper.

Provenance

Every pixel is computed.

  • ImageryNone. No photography, no stock, no AI-generated media. The gold is a fragment shader; everything else is typography, CSS, and one inline SVG favicon.
  • FontsItaliana 400 and Montserrat 300/400 (+ 400 italic), from the Google Fonts library via fontsource, vendored locally as latin-subset woff2. No CDN requests at runtime.
  • LibrariesNone. Raw WebGL2 on a fullscreen triangle; vanilla JS and CSS. The site makes zero external requests.
  • DataThe maison, its people and its dates are fiction, written for this brief. The mathematics is real.
  • Motion careprefers-reduced-motion collapses entrances, stops the render loop, and redraws single frames on scroll so the page stays complete and legible. The canvas pauses when the tab hides, and survives WebGL context loss.

Journal

Three passes, kept honestly.

Passe I

What the shots showed

  • The meniscus rim rendered as a continuous glowing rounded rectangle: a lit hotel sign, not a liquid.
  • Radial push around the title SDF smeared the noise into sunburst rays.
  • The channel-split “refraction” leaked olive-green and slate-blue out of the palette.
  • The field was loud edge to edge; nowhere for the eye to rest.
  • Mobile: the descend cue collided with the hero data rows, and the third row never revealed (the observer’s rootMargin excluded the bottom band at load).
  • Accord numerals floated weakly, vertically centered by default.

What changed

  • SDF distance roughened with FBM so the void edge breathes; rim cut to a faint, field-modulated seam.
  • Refraction rewritten as a full-ramp resample: the palette cannot leak by construction.
  • Exposure rebalanced: darker bias, stronger vignette, caustics thinned.
  • The void widened to the whole hero plate so eyebrow and sub-copy sit on calm glass.
  • Mobile descend cue removed; observer margin fixed; accord rows top-aligned to a shared line.

Upgrade: surface tension — the cursor dents the gold in the hero, gently, radius-limited and absent inside the void.

Passe II

What the shots showed

  • The first correction overshot: the vat went muddy brown, the molten-ness gone.
  • The radial displacement still streaked; the liquid did not read as flowing around the name.
  • The footer failed: gilt display type on live bright gold, barely legible, and the “set” state looked as lively as the pour.
  • The hover pool bled past the accord’s top rule into the neighbouring row, and washed out the panel’s dim window label.

What changed

  • Displacement made tangential — flow around the stone — and the exposure re-lifted to molten.
  • The footer text block became a second void: the set gold parts around “Where time sets.” exactly as the hero parts around the name.
  • A set-factor flattens the field, kills caustics and mattes the color as viscosity reaches zero.
  • Panel mask tightened inside the rules; window labels lift to candle on hover.

Upgrade: per-accord temperature — each panel’s swirl reads its note family: Lumière pulls toward gilt, Cire stays waxen, Résine sinks toward the deep.

Passe III

What the shots showed

  • A pinch singularity at the center of the footer void: the away vector flips direction where the SDF crosses zero, and the streamlines converged into a visible star.
  • Final sweep: full-page desktop and mobile shots re-read for rhythm, widows, and contrast; console and request logs checked to zero.

What changed

  • Displacement confined to a boundary band, part²·(1−part), so it acts only at the meniscus and the void interior stays quiet.
  • Reduced-motion verified by hand-audit of the CSS and the render path: entrances collapse, the loop yields to on-demand single frames.

Upgrade: crystallisation — as the footer gold sets, sparse candle-bright glints seed in it, like sugar in old honey. They do not move; that is the point.