02 / GLSL / 2026-04-29
Paint Mix
Four fragment shaders. Signal blue, violet, hot pink. Drop one on your hero.
// why this exists
shader fluency under brand discipline
How it works
// 01 DPI and pixel density
Each tile is a single React component that mounts a WebGL canvas, compiles a vertex and fragment shader, binds a fullscreen quad, and ticks a draw loop on requestAnimationFrame. The vertex shader is identical across every tile and just emits the corner positions of the quad. All the visual variation lives in the fragment shader, which receives uniforms for resolution, time, and (where applicable) cursor position.
// 02 Bleed and cut tolerance
Brand colors are baked in at the top of every fragment shader as srgb-decoded vec3 constants. Signal blue is vec3(0.267, 0.0, 1.0), violet is vec3(0.478, 0.078, 0.831), pink is vec3(0.992, 0.063, 0.604), and the dark base is vec3(0.024, 0.024, 0.047). No tile is allowed to introduce a color outside this set. That constraint is what keeps the gallery from drifting into rainbow demo territory.
// 03 Safe zone and trim accuracy
Mobile gets a degraded budget. Device pixel ratio is capped at 1.0 below 768 viewport width, frame rate ticks at 24fps instead of 60fps, and the shader resolves to fewer noise octaves where it matters. prefers-reduced-motion freezes each shader at a representative still frame instead of disabling them, so the gallery still reads visually for users who do not want movement.
Most personal sites that try to do "ambient motion" pull a free three.js demo off the shelf, swap a hex code, and call it a day. The result reads as borrowed. This widget is a working argument that ambient motion can be brand-locked, hand-shipped, and still hold its own next to the work that paid agencies put on award sites.
Ten fragment shaders, all in the same paint-mix family. Each one sits in its own tile inside a gallery, runs at native frame rate on a real WebGL canvas, and uses only the colors that already live in the design system. The palette is deliberately narrow. Signal blue at the deep end, violet in the middle, hot pink at the bright end, and the dark stack as the floor. No rainbow. No purple gradient on a white background. No teal. The brand is the constraint and the constraint is what makes the gallery cohere.
The ten variations split into six families. Ambient pieces breathe without input. Reactive pieces respond to cursor, click, or scroll. Material pieces explore physical surfaces, oil on water, ferrofluid, marbled paper. Editorial pieces lean into rest, with ink that bleeds slowly or smoke that drifts up the frame. Structural pieces use brand color as line, not blob, with contour and topography. The operator piece bridges back into the rest of the site by interrupting the paint with the same scanline glitch the chat terminal uses when it errors.
Each shader is one file. Open the COPY button on any tile and you get the full source as plain text, ready to paste into your own React app. No npm package, no build step, no dependency. Just a single component that reads device pixel ratio, caps frame rate on mobile, respects prefers-reduced-motion, and pauses when offscreen. The point is not the gallery. The point is that the gallery is also a parts shelf.
Frequently asked questions
Why ten shaders and not one big hero?
A gallery does double duty. It shows breadth, which is the point of /lab, and each tile is also a self-contained drop-in for a future hero. One big hero would prove fluency once. Ten proves fluency ten times.
Why no GSAP or three.js?
Both are heavy and neither is necessary for fragment shaders running on a fullscreen quad. Raw WebGL plus the brand color constraint produces a tighter result with a smaller bundle. The site already has motion (Framer Motion) for orchestration where it earns its weight.
Can I use these on my own site?
Yes. Click COPY on any tile, paste the file into your project, swap my comment header for yours. They have no dependencies. The colors are constants in the shader, so a palette swap is six numbers.
How do they perform on phones?
Each shader caps device pixel ratio at 1.0 below 768 viewport width, ticks at 24fps instead of 60fps, and pauses on a single rendered frame when the user prefers reduced motion. The whole gallery should hold a steady frame rate on any iPhone newer than the SE.
Why a paint-mix theme specifically?
Because the brand gradient is already a paint mix. Signal blue at one end, hot pink at the other, violet in the middle. A shader gallery that lives inside that palette feels like a natural extension of the site, not a borrowed effect.
Are these the same as the Animated Backgrounds widget?
Different family. Animated Backgrounds is mostly SVG and canvas2d, exploring ambient wallpaper concepts. Paint Mix is pure GLSL fragment shaders with a tighter color constraint. The two galleries are siblings, not duplicates.
How long did this take to build?
About twelve hours total across the ten shaders. Most of the time was spent narrowing the palette and tuning the noise frequencies, not writing the GLSL.
What does 'flagship effort' mean here?
Each shader was treated as if it might end up as the home page hero. Brand-locked palette, mobile guardrails, accessibility respected, source code shippable as-is. Not a demo gallery. A parts shelf.