EMBER & OAK ← Back to site
Build guide · Site 23 of 25

A roastery you can almost smell.

Ember & Oak is one of 25 sites designed and built by Formwork to show off web craft, motion, and taste. Here's exactly how the warmth was made — and how you can rebuild it.

The idea

Coffee is a tactile, sensory thing, so the brief was to make a page feel warm and physical rather than slick. Kraft-brown paper, cream ink, a single burnt-amber accent, and a characterful serif do most of the emotional work. The one moving thing that sells it is steam — real, drifting steam rising off a cup at the top of the page.

The stack

The signature technique: rising steam

Each puff is a particle that rises, cools and spreads. It fades in and out over its life with a sine envelope, and its horizontal drift widens with height — that's what reads as wispy turbulence rather than a straight column. Puffs are drawn as soft radial gradients with globalCompositeOperation = 'lighter' so they glow warmly against the dark kraft.

const t = p.life / p.max;                 // 0 → 1 over the puff's life
p.y -= p.vy * dt * (1.15 - t * 0.5);      // rise, decelerating as it cools
const x = p.x + Math.sin(p.life*0.001*p.swaySpeed + p.phase) * p.sway * t;
const r = p.r0 + p.grow * t;              // grow as it spreads
const a = Math.sin(Math.PI * t) * p.alpha;// fade in, then out
const g = ctx.createRadialGradient(x, p.y, 0, x, p.y, r);
g.addColorStop(0, `rgba(244,235,214,${a})`);

The emitter isn't a fixed point — it's measured from the live position of the cup SVG, so the steam always leaves the rim even as the parallax nudges everything around. The device-pixel-ratio is capped at 1.5 so it stays smooth on retina screens, and a hidden-tab check parks the loop when you're not looking.

Details that matter

Ship it on GitHub Pages

Every site here is static, so hosting is three commands:

git init -b main && git add -A && git commit -m "ship"
gh repo create formwork-roastery --public --source=. --push
gh api --method POST /repos/OWNER/formwork-roastery/pages \
  -f 'source[branch]=main' -f 'source[path]=/'

Use relative paths (./main.js, not /main.js) because project Pages live under /repo-name/. Add an empty .nojekyll file so every asset serves verbatim.

That's the whole recipe: one particle system, a little parallax, honest type, and static hosting. The other 24 sites each swap the central technique — shaders, kinetic type, 3D scenes — but the discipline is the same.