SOMA is one of 25 sites designed and built by Formwork to demonstrate web design, motion, and taste. This one is the calm outlier — no sharp edges, no hurry. Here is exactly how it was made, and how you could build the same.
The brief was a warm, healing breath-work brand — the opposite of a hard tech landing page. So the whole page is built around softness as a system: low-contrast pastels, a rounded humanist sans, generous space, and one hero visual that never sits still but never startles. Colour does the work that photography usually would.
goo filter.IntersectionObserver reveals, CSS transitions, one rAF loop.main branch.Six blush, sage, terracotta and plum circles are stacked in the hero. On their own they are just overlapping discs. The magic is a single SVG filter: blur everything heavily, then re-sharpen the alpha channel so soft edges snap back into one continuous skin. Where two blurred blobs overlap, the combined alpha crosses the threshold and they fuse — the classic "goo" effect.
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="16" result="blur"/>
<feColorMatrix in="blur" result="goo" mode="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 24 -11"/> <!-- steep alpha ramp = crisp gooey edge -->
</filter>
The filter is applied to the container behind an @supports (filter:url("#goo"))
guard; where SVG filters aren't available the blobs simply stay a soft blurred cloud. A slow
requestAnimationFrame loop then drifts each blob on offset sine waves and gently
swells its scale, so the field is always merging, parting and morphing:
const dx = Math.sin(t * b.fx * TAU + b.px) * b.ax + ptr.x * b.par;
const dy = Math.cos(t * b.fy * TAU + b.py) * b.ay + ptr.y * b.par;
const s = 1 + Math.sin(t * b.fs * TAU + b.ps) * b.am; // slow swell/thin
b.el.style.transform =
`translate(-50%,-50%) translate(${dx}px,${dy}px) scale(${s})`;
At the centre sits the live cue. A three-phase state machine sets a data-phase
attribute; CSS maps each phase to an orb scale and eases between them over the phase's own
duration, while the cue word crossfades in sync.
const phases = [
{ phase:'in', label:'Breathe in', dur:4000 },
{ phase:'hold', label:'Hold', dur:4000 },
{ phase:'out', label:'Breathe out', dur:6000 }
];
// each step sets the CSS transition-duration, swaps the word, then schedules the next
.loaded class added on a double requestAnimationFrame with a 400ms failsafe, so a backgrounded tab can't freeze it..js class; if the script never runs, everything is simply visible.prefers-reduced-motion the blobs hold still and the guide becomes a static instruction: “In 4 · hold 4 · out 6.”The site is fully static, so hosting is three commands:
git init -b main && git add -A && git commit -m "ship soma"
gh repo create formwork-soma --public --source=. --push
gh api --method POST /repos/OWNER/formwork-soma/pages \
-f 'source[branch]=main' -f 'source[path]=/'
Use relative paths (./main.js, not /main.js) because project
Pages live under /repo-name/, and add an empty .nojekyll file so every asset
serves verbatim.
That is the whole recipe: one SVG filter, six drifting circles, a small breathing clock, and restraint everywhere else. The other 24 sites each swap the central technique — shaders, particles, kinetic type, isometric 3D — but the discipline is the same.