V

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

#141507
#3A3D1C
#6E6A33
#A89B5B

№ 2 fumé — the fire

#190F08
#45250F
#8A5222
#C98E44

№ 3 froid — the frost

#0B1A16
#24463A
#5D8A6B
#C4D3A2

the paper & the ink

#F3EEE1
#211D0F
#5A5A28
#C9BC7E

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 with clip-path trapezoids; hover, focus or tap opens that layer’s notes, and its color rides the scent accent via a registered @property custom 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

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. Put real numbers in the structure. Durations, temperatures, harvest years. Invented-but-plausible beats decorative every time.
  7. 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.
  8. Respect prefers-reduced-motion by designing the still: a stopped frame of a beautiful shader should still be a beautiful poster.