# Fluid Field (Moonarc)

Draws smoke that follows the cursor. It is a real fluid simulation on the GPU (advection, vorticity confinement and a pressure solve in WebGL2), drawn in two tones: the ground and one colour. The dye is one channel coloured at display time, so a theme change never resets the smoke; `dots` lights a halftone grid instead of a wash. A slow emitter wanders around the focus while the pointer is still and on touch. Every constant is a custom property re-read when the element's style changes. Without WebGL2 or JavaScript a still glow stands in. Fills its positioned parent.

- Pro block. Install: `npx shadcn@latest add @moonarc-pro/fluid-field` (license key required)
- Tier C · category background · trigger pointer, always
- Readout: `<FluidField curl={22} fade={0.9}>`
- Browser support: widely (Chrome 56 · Firefox 51 · Safari 15); elsewhere: without WebGL2 or a colour-renderable float texture (EXT_color_buffer_float), a still radial glow in the colour at the focus
- Measured cost: 1.9 kB raw JS · 1.1 kB gzip · + runtime + canvas + gl + fluid, 12.8 kB raw in all (with dependencies 12.8 kB raw; CSS 5.2 kB raw)
- Page: https://moonarc.dev/components/fluid-field/

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `color` | `string` | `'oklch(0.66 0.2 292)'` | Smoke colour; any CSS colour, theme tokens included. Re-read when the theme changes. |
| `core` | `string` | `color lifted 45 % toward white` | The second tone: the colour where the smoke is densest. |
| `curl` | `number` | `22` | Vorticity confinement: how much the smoke curls. 0 is laminar, 40 is turbulent. |
| `fade` | `number` | `0.9` | How fast the smoke fades, per second. 0.3 lingers; 2 is a quick wisp. |
| `radius` | `number` | `0.22` | Splat radius as a share of the field. |
| `force` | `number` | `1` | Pointer force multiplier. |
| `dots` | `number` | `0` | Halftone cell in CSS px; the dye sizes a dot per cell. 0 draws a smooth wash. |
| `ambient` | `boolean` | `true` | A slow emitter wanders around the focus after two seconds without pointer movement, and on touch. |
| `focus` | `string` | `'0.7 0.55'` | Where the ambient emitter wanders and the fallback glow sits, as fractions of the box (x y, from the top-left). |

## Usage

```astro
<section class="relative min-h-[70vh]">
  <FluidField color="var(--primary)" />
  <h1 class="relative">Move your cursor. The smoke follows.</h1>
</section>
```

## Reduced motion

No loop and no emitter: a few splats around the focus are settled by a second of simulation and drawn once, as a still. The pointer does not stir it.

## With ClientRouter

Bound through the shared runtime: the frame loop, observers and the pointer listener are released before the swap and the WebGL context is lost on purpose after it, so the GPU memory goes with the page; five round trips leave one context. Under transition:persist the canvas keeps its context and starts again on the next page.

## Craft

- Stable fluids (Stam) on two grids: velocity and pressure at 128 cells on the short side, the dye at 512. The expensive solve runs on the small grid; the eye reads detail from the dye.
- The dye is one half-float channel, not RGB: colour is applied in the display pass, so the two tones follow the theme instantly and the textures cost a third.
- Vorticity confinement puts back the curls the coarse grid smooths away. Without it the smoke reads as a blurry smear.
- The pressure solve starts from last frame's pressure damped by 0.8, so 18 Jacobi iterations look like 40.
- Pointer events only store the latest move: one rect read and one splat per frame however many events arrive, and the listener is idle while the field is off screen.
- Premultiplied alpha on a transparent canvas: the ground is the page, so the field sits on any background in either theme.
- The halftone samples the dye at each cell's centre and sizes the dot with a one-pixel analytic edge. It stays crisp at any DPR and needs no second texture.
- Colours are resolved by painting them into a 1×1 2D canvas: oklch, color-mix and tokens all come back as bytes WebGL can take.

## Replaces

- SplashCursor (React Bits)
- Smoke / fluid hero (React Bits Pro)
- WebGL-Fluid-Simulation (PavelDoGreat)

## Source

Pro block. The source is served by the license-gated registry: `npx shadcn@latest add @moonarc-pro/fluid-field` with a key in components.json (https://moonarc.dev/account/). Related free primitives: glyph-field, aurora.
