# Solvent — WebGL Background for React

> Interactive fluid ink — a real GPU stable-fluids solver (semi-Lagrangian advection, vorticity confinement, Jacobi pressure projection) driving a persistent two-pigment dye field: cursor strokes push force and pigment into the fluid, so magenta and cyan inks swirl, fold through violet where they meet, and keep marbling long after the cursor stops. A seeded virtual cursor keeps it alive when idle and hands over to the real pointer with an exponential tween; two pigment blooms are pre-seeded so it is never blank on load. Half-res HalfFloat sim FBOs, adaptive Jacobi budget, DPR-clamped, offscreen-paused, reduced-motion-safe (a frozen ink marbling).

- Page: https://crucibleui.com/components/backgrounds/solvent
- 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/solvent
```

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

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

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 (`SolventProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors?` | `[string, string, string]` | `iridescent magenta / cyan meeting through violet` | Three ink tones: `[pigmentA, pigmentB, midpoint]`. Splats alternate slowly between the two pigments, and wherever the inks fold into each other the blend passes through the midpoint — real marbling reads. |
| `force?` | `number` | — | Stir strength — how hard cursor strokes push the fluid, 0–3. 1 = default. |
| `splatRadius?` | `number` | `0.11` | Splat brush radius as a fraction of the container height. |
| `dissipation?` | `number` | `0.9` | Fraction of ink kept after one second, 0.5–0.995. Higher holds marbling longer after the cursor stops; lower clears faster. |
| `swirl?` | `number` | — | Vorticity-confinement boost, 0–3 — how much the flow curls back on itself into eddies instead of smearing straight. 1 = default. |
| `iterations?` | `number` | `20` | Jacobi pressure-solve iterations, 4–40. Higher is a crisper, more incompressible flow. Automatically capped at 12 on high-DPR displays and small viewports. |
| `autoDemo?` | `{ speed?: number; intensity?: number; resumeDelay?: number } \| false` | ``{ speed: 1, intensity: 0.85, resumeDelay: 1.5 }`` | Idle auto-demo: a seeded virtual cursor wanders the canvas and keeps the ink alive when nobody is interacting. `speed` scales its wander rate, `intensity` its ink output (both around 1), `resumeDelay` is the seconds of pointer stillness before it fades back in. While it drives it also drops an occasional blot, like a click. Pass `false` to disable. |
| `idle?` | `boolean` | `true` | Idle autoplay master switch: `false` turns the virtual cursor off (same as `autoDemo={false}`), leaving the ink to the real pointer alone. Never runs under reduced motion beyond the frozen poster pre-roll. |
| `interactive?` | `boolean` | `true` | Respond to the real pointer. When false only the auto-demo stirs. |
| `speed?` | `number` | — | Simulation speed multiplier. 1 = default. |
| `paused?` | `boolean` | — | Freeze the simulation, holding the current frame. |
| `ref?` | `React.Ref<SolventHandle>` | — | React 19 ref prop → {@link SolventHandle}. |

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 { Solvent } from "@/components/crucible/solvent";
import { DemoScene } from "@/components/registry/demo-scene";

export default function SolventDemo() {
  return (
    <DemoScene
      background={<Solvent />}
      headline="Ink that answers back"
      subhead="Drag through the fluid to stir the pigments — click to drop a blot. Leave it alone and it keeps painting itself."
    />
  );
}
```

## 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.
