---
name: film-grain
description: Drop-in film/TV grain overlay for any web UI — canvas-rendered noise scrambled per-frame at 24fps, not a sliding tile. Sub-pixel density on HiDPI displays, `overlay` blend mode so it adds texture without obscuring content. Trigger when the user wants to add film grain, TV static, noise overlay, or analog texture to a website, web app, CEP panel, or HTML page. Also triggers on "make it look filmic", "add grain", "SYSTEM grain", "GIR grain", or when applying the SYSTEM//02 design language.
---


# FILM GRAIN — Drop-in Film Grain for Web UIs

By Gualo Hawes. Ships with SYSTEM//02 but works standalone for any web surface.

## Why this exists

Most web grain implementations tile an SVG noise pattern and translate it on a CSS animation. That produces a visible "sliding box" artifact — you see the tile edges shimmering across the viewport. Real film and TV grain **scrambles the entire frame** each tick; nothing slides.

This skill is a 50-line canvas-based grain engine that:
- Renders fullscreen canvas noise
- Re-randomizes every pixel each refresh (default 24fps = film)
- Runs at sub-pixel density on HiDPI (canvas density × device pixel ratio)
- Uses `overlay` blend mode so text and UI stay readable
- Self-installs — one `<script>` tag and it's running

## Usage

### 1. Drop in

Copy `grain.js` into your project and add to any page:

```html
<script src="grain.js"></script>
```

That's the whole install. It creates a fixed fullscreen canvas at `z-index: 900`, `pointer-events: none`, starts the grain loop automatically.

### 2. Configure (optional)

Set `window.FILM_GRAIN_CONFIG` **before** loading the script:

```html
<script>
  window.FILM_GRAIN_CONFIG = {
    opacity: 0.16,      // 0..1 — default 0.16
    fps: 24,            // refresh rate — 24 = film, 30 = TV, 12 = cinéma vérité
    blend: 'overlay',   // any CSS mix-blend-mode
    zIndex: 900,
    id: 'grain',
    dpr: 2,             // max canvas density (2 = Retina-tight, 1 = chunkier)
  };
</script>
<script src="grain.js"></script>
```

### 3. Runtime control

After load, the public API:

```js
FilmGrain.setOpacity(0.10);   // softer
FilmGrain.setBlend('soft-light');
FilmGrain.setFps(12);          // slower refresh
FilmGrain.destroy();           // remove
```

## Recommended settings

| Surface | opacity | blend | fps | notes |
|---------|---------|-------|-----|-------|
| Light UI (default) | 0.16 | overlay | 24 | SYSTEM silver |
| Dark UI | 0.18 | overlay | 24 | SYSTEM black |
| Over imagery | 0.22 | overlay | 24 | product pages |
| Subtle / readable | 0.10 | soft-light | 24 | long-form content |
| Heavy cinematic | 0.28 | overlay | 24 | hero sections |

## Behavior

- **Zero DOM footprint** except one `<canvas id="grain">`
- **Zero pointer interference** — `pointer-events: none`
- **No input-size bundle** — grain is generated per frame, not a huge noise PNG
- **HiDPI-aware** — canvas scales to device pixel ratio (capped at `dpr` config)
- **Window resize** — automatically resizes canvas on viewport changes
- **Animation frame loop** — pauses when tab is backgrounded (browser default)

## When NOT to use

- Don't stack multiple grain layers — one layer is correct film grain; two looks muddy
- Don't animate the `opacity` via CSS transitions — the canvas is already animating
- Don't set `z-index` below your content if you want grain to overlay (default 900 works for most cases)
- For e-readers or text-heavy docs, drop opacity to 0.08 or skip entirely — grain reduces readability at long lengths

## Performance

- ~1.5ms per frame at 1440p / DPR 2 (fill imageData + putImageData)
- No GPU shader cost
- Runs on mobile (iOS Safari, Chrome Android) without jank

## File contract

```
film-grain/
  SKILL.md    ← this file
  grain.js    ← the engine (self-contained, ~50 lines)
```

## Trigger phrases

- "add film grain to this site"
- "add TV static / noise overlay"
- "make this look filmic / analog"
- "add the SYSTEM grain"
- "use Gualo's grain / GIR grain"
- "add grain to my landing page / web app / CEP panel"

When triggered: copy `grain.js` to the user's project (usually `public/` or `assets/`), add the `<script>` tag to their root layout or index.html, and confirm with the recommended settings table above.

## Version

- **v1.0 — April 2026** (authored by Gualo Hawes)
- Extracted from SYSTEM//02 preview — the exact grain used on demonhub.
