Max Based with grain overlay applied
Intensity 50%
·Playground

Grain.

A frameless WebGL shader that sits over any content like a piece of celluloid: monochrome noise flickering at 24 fps, composited with mix-blend-mode: overlay. Gives anything beneath it the texture of printed paper without touching the content itself.

How it works

01 Stateless per-pixel hash

No texture lookups, no noise libraries: just a two-component hash of each pixel's screen coordinate. hash21(xy) costs one dot, one sin, one fract per fragment. Entirely stateless: any pixel at any time is deterministic from its coordinate alone.

02 Film-speed flicker

Real celluloid grain shifts with every frame of film. To match that, wall-clock time is quantised to 24 fps (floor(u_time × 24)) and added as a large XY offset into the hash. The entire grain field reseeds 24 times per second, film-like, not monitor-locked. Under prefers-reduced-motion the field freezes on a single frame.

// Fragment shader — stateless hash noise, reseeded at ~24fps
float t  = floor(u_time * 24.0);          // film-speed frame index
vec2  co = gl_FragCoord.xy               // pixel coord …
         + vec2(t * 1291.0, t * 1871.0); // … shifted per frame to reseed
float n  = hash21(co);                   // fast stateless hash (no texture)
// centre at neutral grey → overlay brightens AND darkens symmetrically
float g  = 0.5 + (n - 0.5) * u_intensity;
gl_FragColor = vec4(g, g, g, 1.0);

03 Neutral-grey overlay

The shader outputs grey centred on 0.5, scaled by u_intensity. The CSS mix-blend-mode: overlay on the canvas means values above 0.5 brighten the content beneath and values below 0.5 darken it, symmetrically, on both dark and light surfaces. At intensity zero the grain disappears; drag it up and the texture becomes unmistakable. The slider above drives a reactive u_intensity uniform directly: no rebuild, just a uniform update per frame.

A Svelte action, <canvas use:grain={{ intensity }}>. Wrap any element with <Grain /> to fill it. DPR-aware, pauses off-screen, honors reduced-motion. Open to borrow.