ittybitty

ittybitty

WebGL2, GLSL, Astro, Playwright

Role
Solo, Launching soon
Timeline
Autumn 2026, 60 effects

Nice effects, no strings attached

Most of the nice effects on the web come with a framework or a subscription attached. I wanted the effect and nothing else. So ittybitty rewrites them as plain GLSL you copy and own: 60 so far, 4 to 6 KB each, every one written from scratch. Here are a few in turn, with their GPU times on an M1 Max:

They stack, too. That’s kind of the whole gimmick, really. Tons of versatility. Here’s one running live under a dither. Swap either layer and it stays a single program:

Five shaders, one call

That’s two effects sharing one shader. To get there, a small compiler rewrites each effect so it can sit alongside the others. One step at a time:

#version 300 es
precision highp float;
uniform float uTime;
out vec4 fragColor;
vec3 palette(float t) { ... }
void main() {
  if (uTime < 0.0) return;
  fragColor = vec4(palette(uTime), 1.0);
}
#version 300 es
precision highp float;
uniform float uTime;
out vec4 fragColor;
vec3 palette(float t) { ... }
void main() {
  if (uTime < 0.0) return;
  fragColor = vec4(palette(uTime), 1.0);
}
uniform float L0_uTime;
vec3 L0_palette(float t) { ... }
void main() {
  if (L0_uTime < 0.0) return;
  fragColor = vec4(L0_palette(L0_uTime), 1.0);
}
void main() {
vec4 L0_main(vec2 fragCoord) {
  vec4 fragColor = vec4(0.0);
  if (L0_uTime < 0.0) return; return fragColor;
  fragColor = vec4(L0_palette(L0_uTime), 1.0);
  return fragColor;
}
void main() {
  vec4 c = L0_main(gl_FragCoord.xy);
  c = composite(c, L1_main(gl_FragCoord.xy), uBlend1, uOpacity1);
  c = composite(c, L2_main(gl_FragCoord.xy), uBlend2, uOpacity2);
  fragColor = c;
}

Filters were the hard part. A dither has to see everything underneath it, and the naive way re-runs the whole stack for every pixel it samples. Brutal. Instead, the stack draws into a texture once and the filter reads from that. One extra pass per filter, and that’s it.

Good boy manners by default

Every effect also does all of this without being asked:

  • Pauses offscreen
  • Pauses in background tabs
  • A still frame for reduced motion
  • Recovers a lost WebGL context
  • No forced layout
  • Allocates nothing per frame

Each effect gets four to nine controls, and every one has to earn its spot. Honestly, picking them was harder than writing the shaders.