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.
Turn on 5-bit, drop amount to 0, then raise it. That's the whole reason the texture layer exists.
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.
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.
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.
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.
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.
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.