# Halftone — WebGL Background for React

> A comic pop-art Ben-Day dot field: three ink screens at classic offset-print angles, mis-registered for printed-comic fringing, their dot radii driven by a slow traveling wave so waves of dot-size ripple across a bold flat ground. Cursor sends a concentric pressure wave. Original OGL fragment shader, DPR-clamped, offscreen-paused, reduced-motion-safe; the ripple idles on its own when untouched.

- Page: https://crucibleui.com/components/backgrounds/halftone
- Category: [backgrounds](https://crucibleui.com/backgrounds)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: ogl
- npm dependencies: ogl

## Install

```bash
npx shadcn@latest add @crucible/halftone
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/halftone`.

Backgrounds render `absolute inset-0 -z-10`. Their parent must be a stacking context — wrap in `relative isolate` and put your content in a `relative z-10` child, or the background disappears behind an opaque ancestor.

## Props (`HalftoneProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors?` | `[string, string, string, string]` | — | Four tones: `[paper, ink0, ink1, ink2]`. `paper` is the bold flat ground the dots print on; `ink0`/`ink1`/`ink2` are the three halftone ink colors (default a CMYK-ish cyan / magenta / near-black key). Each ink is applied as a subtractive multiply tint, so overlapping dots blend the way real printed screens do. Any hex string. |
| `speed?` | `number` | `1` | Wave drift speed multiplier. 1 = default, 2 = twice as fast, 0 = still. |
| `intensity?` | `number` | `1` | Ink density / contrast, 0–2. 1 = default. Higher pushes dark regions to solid ink and thins the highlights; lower flattens toward even mid dots. |
| `density?` | `number` | `1` | Dot-screen density, 0.2–4. 1 = default (a comic-book screen). Higher packs finer dots; lower blows them up into bold Ben-Day discs. |
| `ripple?` | `number` | `1` | Strength of the pointer pressure ripple, 0–1. 0 disables it; 1 sends a full concentric dot-size wave rippling out from the cursor. |
| `paused?` | `boolean` | — | Freeze the animation at its current frame. |
| `idle?` | `boolean` | `true` | Idle autoplay: after ~1.5s with no pointer present, a seeded ghost drifts slowly across the field and sends the pressure ripple, so thumbnails and heroes never look still; a real pointer takes over instantly. Never runs under reduced motion. |

Also accepts: `React.ComponentPropsWithoutRef<"div">`.

## Usage example

`DemoScene` is a site-only preview harness, not part of the component — replace it with your own layout.

```tsx
import { Halftone } from "@/components/crucible/halftone";
import { DemoScene } from "@/components/registry/demo-scene";

export default function HalftoneDemo() {
  return (
    <DemoScene
      background={<Halftone />}
      headline="Straight off the press"
      subhead="A comic-book halftone screen rippling behind your content. Move your cursor to send a wave through the dots."
    />
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through. Pauses offscreen and on hidden tabs, clamps device pixel ratio, and tears down its GL/canvas context on unmount.
