Three-blob morph through feTurbulence + displacement refraction.
<!doctype html>
<!-- Example: liquid-blob — 3 blobs · feTurbulence + feDisplacementMap · refraction tint -->
<html data-duration="8" data-aspect="16:9"><head><style>
:root {
--cream:#f6f5f1; --cream-2:#efece4; --ink:#0a0a0a; --mute:#6b6862;
--line:#e3dfd3; --signal:#ff3b1f; --signal-2:#ff6a4a; --frame:#ffb800;
--green:#1f8a5b; --blue:#2b66ff;
}
* { box-sizing: border-box; }
body { margin:0; }
body { background: var(--cream); height: 100vh; overflow: hidden;
display: grid; place-items: center; font-family: ui-sans-serif, system-ui;
position: relative; }
svg.stage { width: 720px; height: 720px; }
.cap { position: absolute; top: 36px; left: 36px;
font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .25em;
color: var(--mute); }
</style></head><body>
<div class="cap">● LIQUID · BLOB · 3-WAY MORPH</div>
<svg class="stage" viewBox="-200 -200 400 400">
<defs>
<filter id="warp" x="-30%" y="-30%" width="160%" height="160%">
<feTurbulence id="tt" type="fractalNoise" baseFrequency="0.012" numOctaves="2" seed="2" result="n"/>
<feDisplacementMap in="SourceGraphic" in2="n" scale="40" xChannelSelector="R" yChannelSelector="G"/>
</filter>
<radialGradient id="b1" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#ff6a4a"/>
<stop offset="100%" stop-color="#ff3b1f"/>
</radialGradient>
<radialGradient id="b2" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#ffb800"/>
<stop offset="100%" stop-color="#ff6a4a"/>
</radialGradient>
<radialGradient id="b3" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#2b66ff"/>
<stop offset="100%" stop-color="#1f4abf"/>
</radialGradient>
</defs>
<g filter="url(#warp)" style="mix-blend-mode: multiply">
<circle id="c1" cx="-50" cy="-30" r="120" fill="url(#b1)" opacity="0.85"/>
<circle id="c2" cx="40" cy="40" r="100" fill="url(#b2)" opacity="0.8"/>
<circle id="c3" cx="-10" cy="80" r="90" fill="url(#b3)" opacity="0.65"/>
</g>
</svg>
<script>
var c1 = document.getElementById('c1');
var c2 = document.getElementById('c2');
var c3 = document.getElementById('c3');
var tt_ = document.getElementById('tt');
var t = 0;
var reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
function render() {
var tt = reduced ? 0 : t;
// Each blob orbits with its own phase + radius.
var P = [
[c1, 60, 1.2, 0, 140, 0.8],
[c2, 80, 0.9, 1.7, 110, 1.1],
[c3, 50, 1.4, 3.4, 100, 0.7]
];
P.forEach(function(p) {
var el = p[0], amp = p[1], freq = p[2], ph = p[3], baseR = p[4], rFreq = p[5];
var x = Math.cos(tt * freq + ph) * amp;
var y = Math.sin(tt * (freq * 1.1) + ph * 0.7) * amp * 0.8;
var r = baseR + Math.sin(tt * rFreq + ph) * 16;
el.setAttribute('cx', x.toFixed(1));
el.setAttribute('cy', y.toFixed(1));
el.setAttribute('r', r.toFixed(1));
});
// Drift the turbulence so the refraction never stops moving.
var bf = 0.008 + 0.006 * (Math.sin(tt * 0.5) + 1) / 2;
tt_.setAttribute('baseFrequency', bf.toFixed(4));
}
addEventListener('hf-seek', function(e) { t = e.detail.time; render(); });
render();
</script>
</body></html>