SOMA ← Back to site
Build guide · Site 08 of 25

A breathing practice, rendered as soft, merging light.

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 idea

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.

The stack

The signature technique: gooey metaballs

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})`;

The breathing guide

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

Details that matter

Ship it on GitHub Pages

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.