MERIDIAN is one of 27 sites in the Formwork collection. Its signature is a readout you can
watch work: a live analog gauge on a real <canvas>, stats that roll into place like an
odometer, and a copper line that fills as you scroll. No chart library, no animation framework — just a
couple hundred lines of vanilla JS.
Espresso is a measured thing: nine bars, ninety-three and a half degrees, eighteen grams in, thirty-six out. The brand promise is precision, so the interface had to feel like an instrument. Everything that carries a number is treated as a readout — it moves the way a dial or a counter moves, never the way a marketing headline fades in.
translateY.requestAnimationFrame; observers pause offscreen work.The gauge runs one continuous extraction cycle: pressure ramps to nine bars on an ease-out, holds while
the shot timer and yield count up, drops, rests, and loops. State is a pure function of a single clock
t, so the drawing code never has to remember anything:
function stateAt(t) {
let bar;
if (t < RAMP) bar = easeOut(t / RAMP) * MAXBAR;
else if (t < RAMP + HOLD) bar = MAXBAR;
else if (t < SHOT_END) bar = MAXBAR * (1 - easeIn((t - RAMP - HOLD) / DROP));
else bar = 0;
// timer and yield are read off the same clock…
return { bar, timer, yld };
}
The animation loop is delta-timed, so the cycle takes the same wall-clock time on a 60 Hz or 120 Hz
display, and devicePixelRatio is capped at 2 so a 3× phone doesn't render a nine-megapixel
dial. An IntersectionObserver stops the loop entirely once the hero scrolls away:
const dt = Math.min(0.05, (now - last) / 1000); last = now;
if (visible) t = (t + dt) % CYCLE; // advance only while on screen
draw(stateAt(t).bar);
Every stat is a row of clipped columns, each holding a 0–9 reel. To show a digit you slide
its reel up by that many ems. rollTo only rebuilds the DOM when the shape of the value
changes (say 27 s → 3:15); otherwise the existing reels animate straight from the
old value to the new one, which is what makes the brew-card numbers spin when you switch methods:
function rollTo(el, str, animate) {
const mask = str.replace(/[0-9]/g, '#');
if (el._mask !== mask) buildOdometer(el, str); // shape changed → rebuild reels
let ci = 0;
for (const ch of str)
if (ch >= '0' && ch <= '9')
cols[ci++].firstChild.style.transform = `translateY(-${+ch}em)`;
}
Four components carry the page. Each is a real, keyboard-accessible control — not a picture of one — and each is grounded in a pattern that ships in production software.
A genuine role="tablist" with a sliding thumb positioned by offsetLeft/
offsetWidth, roving tabindex, and arrow / Home / End keys. Grounded in the inline
pricing pills of Webflow and the tabs-wired-to-a-panel pattern from 1Password
— a filled active segment that swaps the content below it immediately.
Six <button> rows with aria-current, a sticky image panel, and a thin
auto-advance bar that pauses on hover, focus, reduced-motion, or when scrolled offscreen. Grounded in the
numbered walkthrough steppers of Sequence and the list-left / art-right layout of
Mural.
Three flat machine cards separated by hairlines rather than boxes, one CTA each, with a single copper "MOST BUILT" flag on the middle card. Grounded in the dark, flat, hairline-divided product cards of Grok and the single-CTA tier ladder of Claude's pricing.
.js class the script adds. Without it, the
first method panel and the first process step are already the visible defaults, and every stat shows its
final value as plain text.:focus-visible ring
throughout, one <h1>, landmark regions, and a real tablist that answers arrow keys.A dial, a reel of digits, and a line down the edge of the page — the whole site is one instrument reading itself out.