← Effects

Lava blobs

Blobs · WebGL shader with a CSS fallback

Soft, glossy blobs drift across the page like wax in a lava lamp. Six round shapes float on slow, overlapping orbits; where two meet they stretch into one body and later pull apart. Edges are smooth with a faint glow around them, and each blob shades gently from one token colour to the other. Playful but calm.

✓ (2×) · claude-haiku-5-5 → claude-opus-5-5
# Build the "Lava blobs" background effect (`lava-blobs`)

Family: Blobs · WebGL fragment shader over a CSS fallback. Six glossy blobs drift on slow orbits and merge like a lava lamp.

## Look
Soft, glossy blobs drift across the page like wax in a lava lamp. Six round shapes float on slow, overlapping orbits; where two meet they stretch into one body and later pull apart. Edges are smooth with a faint glow around them, and each blob shades gently from one token colour to the other. Playful but calm.

## Parameters (current values)
- Speed `--fx-speed`: 0.5 (range 0.1–2)
- Blob size `--fx-size`: 1 (range 0.5–1.5)
- Edge softness `--fx-softness`: 0.2 (range 0.05–0.5)
- Colour A `--fx-color-a`: the `primary` colour token, `var(--c-primary)`
- Colour B `--fx-color-b`: the `secondary` colour token, `var(--c-secondary)`
- Background `--fx-color-bg`: the `bg` colour token, `var(--c-bg)`

## Recipe
- Coordinates: `p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y` (centred, aspect-correct); `aspect = u_resolution.x / u_resolution.y`; `t = u_time`.
- Six blobs, i = 0..5 as a float: centre `c = vec2(0.42 * aspect * sin(t * (0.21 + 0.05 * i) + i * 1.7), 0.33 * cos(t * (0.17 + 0.04 * i) + i * 2.3))`; radius `r = (0.13 + 0.03 * mod(i, 3.0)) * u_size`.
- Metaball field: `F += r * r / (dot(p - c, p - c) + 1e-4)` summed over the six.
- Body: `m = smoothstep(1.0 - u_softness, 1.0 + u_softness, F)`.
- Tint: `tint = mix(u_color_a, u_color_b, clamp(0.5 + 0.5 * (p.y * 1.4 + 0.3 * sin(t * 0.3)), 0.0, 1.0))`; gloss on the thick cores: `tint = mix(tint, min(tint * 1.15 + 0.08, 1.0), smoothstep(1.4, 3.5, F) * 0.6)`.
- Halo just outside the edge: `halo = smoothstep(0.35, 1.0, F) * (1.0 - m) * 0.25`.
- Output `vec4(mix(u_color_bg, tint, m) + tint * halo, 1.0)`, clamped to 0–1.

## CSS fallback
The element's background `--fx-color-bg` with four soft discs, each an elliptical `radial-gradient(W H at X Y, colour 86%, transparent 100%)` whose width and height percentages make it round on a 16:10 box: `--fx-color-a` at 30% 42% sized 20% 32%; `--fx-color-b` at 45% 52% sized 15% 24% (it overlaps the first, so the two read as one body merging); `--fx-color-a` at 74% 30% sized 13% 21%; `--fx-color-b` at 62% 76% sized 16% 26%. List them in that order in one `background` with the background colour last. Static.

## Runtime rules
- Mount it in one element that fills a fixed, `pointer-events: none`, `aria-hidden` layer behind the page content; every parameter above is a CSS custom property on that element.
- Colours come only from the colour-token variables above (mix them with `color-mix()` or alpha), never hard-coded hex, so the effect follows any palette and light or dark mode.
- Two layers. A CSS background on the element is the fallback and must look complete alone (described above): it is all that shows without WebGL or after a lost context. A full-size `<canvas>` on top draws the shader.
- Plain WebGL 1: one fragment shader over a full-screen triangle; no libraries, CDN, textures or images; script under ~8 KB.
- Uniforms: `u_resolution` (canvas size in pixels), `u_time` (seconds, already multiplied by the speed parameter) and one per parameter except speed: `u_size` (float), `u_softness` (float), `u_color_a` (vec3 RGB 0–1, read from the CSS variable), `u_color_b` (vec3 RGB 0–1, read from the CSS variable), `u_color_bg` (vec3 RGB 0–1, read from the CSS variable). Output opaque colour.
- Cap the device pixel ratio at 2 (1.5 on phones) and the frame rate at 30 fps on phones; pause when the tab is hidden or the canvas is off screen; release the context when it leaves the screen.
- With prefers-reduced-motion, or a `?shot` URL flag (screenshots), draw one frame at `u_time = 2` and stop. No WebGL, or a lost context: remove the canvas and leave the CSS fallback.

## Avoid
- More than eight blobs, or blobs leaving the viewport entirely
- Aliased, hard edges
- Neon rainbow colours: only colour A, colour B and the background
- Bouncing or fast motion: a blob crosses the page in no less than 10 seconds at speed 1
Copied!

↑ ↓ to move · Enter to open · Esc to close