Mesh App

Gradient bench · two renderers · one control field

WEBGL —

The Gemini/Claude gradient is not a linear-gradient. It's a handful of soft color nodes composited with distance falloff, then dithered so the ramp doesn't band. Drag the nodes. Switch renderers — the same field drives both. The code below is live.


      

Renderer

Field

NODES5
BLUR64px
SPREAD34%

Palette

INTERPOLATION

Texture

AMOUNT42
DOT PITCH4px

Turn on 5-bit, drop amount to 0, then raise it. That's the whole reason the texture layer exists.

Motion & ground

DRIFT
GROUND

Six things that separate this from a linear-gradient

Overscan the blur

A background paints only inside its box, so blur() samples transparent past the edge and leaves a washed rim. inset: -20% on the gradient layer pushes that artifact outside the clip.

Match the lightness

Every node here sits at the same OKLCH lightness. Mixed-lightness blobs read as stains; equal-lightness blobs read as light. Vary hue and chroma, hold L.

Interpolate outside sRGB

Blue→orange in sRGB passes through dishwater grey. Oklab holds perceptual lightness across the whole ramp. Toggle the space above and watch the midpoints, not the ends.

Texture before you quantize

8 bits per channel across a wide soft ramp bands badly on OLED. Break it up before the quantize step — random noise dissolves the edge invisibly, an ordered dot screen does the same job but stays legible as a material. Both subtract their own mean so the field doesn't get lighter.

Animate transforms, nothing else

Never animate filter or gradient stops — both re-rasterize per frame. Drift here moves the blob elements with translate3d under a single static blur pass. 20–40s durations, offset per node.

Render once when you can

Static mesh? Draw it to an offscreen canvas on resize only, or ship a compressed WebP — a blurry gradient is a few KB and costs zero GPU. Reserve the live shader for gradients that actually react to something.

Native equivalents of the WebGL tab: SwiftUI MeshGradient (iOS 18+) and Android RuntimeShader/AGSL (API 33+). Both tabs above are generated from the current field.