le guide de fabrication — how this site was made
Oil, ivory & one shader
i — the concept
Scent as color
Maison Vétiver is a fictional perfume house built around one ingredient, so the site is built around one image: perfume oil moving in light. There is not a single photograph — the full-screen WebGL “oil” is the only imagery, and each of the three scents owns a liquid palette that floods the entire page when you select it.
Everything else stays disciplined so the liquid can be loud: content sits on ivory “blotter sheets” laid over the oil, labels are set like apothecary type, and the structure encodes real information — the pyramid is a genuine top/heart/base diagram, the method ledger’s big numerals are actual durations, not decorative 01/02/03.
ii — palette & typography
Four oils and a sheet of blotter paper
Each scent is a four-stop palette fed to the shader as uniforms. The page accent colors are derived from the same families, so when the oil changes, rules, chips and pyramid strata follow.
№ 1 racine — the earth
№ 2 fumé — the fire
№ 3 froid — the frost
the paper & the ink
The faces
- Italiana — the display voice. A high-contrast engraved French serif; it has the flourish of a flacon label without tipping into Playfair cliché. Used enormous in the hero and for the ledger numerals.
- Cormorant Garamond — the reading voice. A true Garamond revival for body copy and italic mottos; its oldstyle figures suit prices like 185 €.
- Cormorant SC — real small caps, not faked with uppercase. Letterspaced 0.34em, it does every apothecary label, chip, button and nav item on the site.
iii — the signature: liquid oil in webgl
Domain-warped noise, colored like perfume
The oil is one fragment shader on a fixed full-screen canvas, rendered at half resolution (blur is what oil looks like anyway). It is the classic fbm domain-warp: warp the plane with noise, warp it again with the result, then read a final field from the doubly-warped coordinates.
vec2 q = vec2(fbm(p + t), fbm(p + vec2(5.2, 1.3) - t * .8));
vec2 r = vec2(fbm(p + 3.4*q + vec2(1.7, 9.2) + .12*t),
fbm(p + 3.4*q + vec2(8.3, 2.8) - .09*t + mouse*stir));
float f = fbm(p + 3.1 * r);
vec3 col = mix(u_c1, u_c2, clamp(f*f*2.6, 0., 1.));
col = mix(col, u_c3, clamp(length(q)*.95, 0., 1.));
col = mix(col, u_c4, clamp(pow(abs(r.y), 1.5), 0., 1.));
The four colors arrive as uniforms. Switching scents never snaps — JavaScript eases the current palette toward the target every frame, so the new scent floods through the old one like a drop of dye:
// per frame, per channel
cur[i][j] += (target[i][j] - cur[i][j]) * 0.035;
Pointer movement feeds a stir uniform that decays over time, so the surface swirls where you stir it and settles when you stop. With prefers-reduced-motion, the loop stops: the shader renders frames only until a palette change settles, then holds a still image.
The rest of the imagery is also code
- The flacons are inline SVG — the “liquid” inside each bottle is a gradient whose stops are retinted from the same palette, transitioned with plain CSS (
stop { transition: stop-color 1.1s }). - The notes pyramid is three
<button>strata cut withclip-pathtrapezoids; hover, focus or tap opens that layer’s notes, and its color rides the scent accent via a registered@propertycustom property so even the pyramid crossfades. - The film-grain overlay is a data-URI SVG
feTurbulence— no image files anywhere on the site.
iv — the three passes
Screenshot, critique, correct
- Correctness & composition. Rebuilt the notes pyramid so all three strata share one continuous silhouette (the clip-path angles now belong to a single triangle), simplified the mobile scent tabs to names only, raised inactive-tab contrast over the oil, and removed a dead column of whitespace under short note lists.
- Elevate. Gave the shader a
surge()— switching scents now visibly stirs the oil while the new palette floods through — and set an enormous translucent vintage numeral behind the flacon that crossfades with each scent, plus a small press-down on the tabs. - Taste. Chanel rule: deleted the flacon’s liquid “slosh” animation so the page has exactly one living surface — the oil. Calmed the film grain, nudged the numeral until it just peeks past the glass, and re-checked 390 px and reduced-motion end to end.
v — do this yourself
A recipe for a one-ingredient site
- Pick one image-idea and refuse all others. Ours was “perfume oil in light.” Ask Claude for a site where a single generative visual carries everything, and ban photography up front.
- Write the palettes before the code. Four hex stops per “mood,” plus one paper color and one ink color. Make Claude show you swatches first; argue about them.
- Ask for a domain-warped fbm shader with the palette as uniforms — the exact phrase “domain warping with fbm, palette as four vec3 uniforms, eased in JS” gets you this class of visual in one file with no libraries.
- Make state flood everything. One switch (our scent tabs) should change the shader, the CSS accent variables, the SVG gradient stops and the copy. If a state change only touches one element, it will feel like a widget, not a world.
- Choose three faces that argue for the subject — a display, a text and a genuine small-caps face — and give the small caps all the labels. Never let the display face write a paragraph.
- Put real numbers in the structure. Durations, temperatures, harvest years. Invented-but-plausible beats decorative every time.
- Run three screenshot passes. Desktop fold, full page, and 390 px mobile. Critique like a stranger, fix, repeat. The third pass is only for removing things.
- Respect
prefers-reduced-motionby designing the still: a stopped frame of a beautiful shader should still be a beautiful poster.