# SplashCursor

> A WebGL fluid simulation that trails the pointer in coloured dye behind the page content.

## Facts

- **Product**: Max
- **Family**: Effects
- **Global**: `window.DigitalCrew.SplashCursor`
- **Source**: `max-agent: src/components/ui/splash-cursor.tsx`
- **Import in the app**: `import { SplashCursor } from "@/components/ui/splash-cursor";`
- **Live preview**: /design/components/splash-cursor/preview.html
- **Page**: /design/components/splash-cursor

## Guidelines

**Use it for** ambient decoration only. Max mounts it app-wide with `quality="low"` when Settings › Appearance › Splash cursor is on, and behind the public booking page.

**What you provide**:
- `quality`:
  - `"high"` (default): dye 768, simulation 128, 20 pressure iterations, pixel ratio up to 1.5.
  - `"low"`: dye 512, simulation 128, 16 iterations, pixel ratio 1.
- Optional overrides, with their defaults: `CAPTURE_RESOLUTION` 512, `DENSITY_DISSIPATION` 2.2, `VELOCITY_DISSIPATION` 2, `PRESSURE` 0.1, `CURL` 3, `SPLAT_RADIUS` 0.32, `SPLAT_FORCE` 6000, `SHADING` true, `COLOR_UPDATE_SPEED` 10, `BACK_COLOR` `{ r: 0.5, g: 0, b: 0 }`, `TRANSPARENT` true. `SIM_RESOLUTION`, `DYE_RESOLUTION` and `PRESSURE_ITERATIONS` override the quality preset.

**Anatomy**: a `pointer-events-none fixed inset-0 z-0` canvas. Sections with their own background cover it, and nothing underneath loses clicks.

**Behaviour**:
- It never starts under `prefers-reduced-motion` (checked live), on coarse pointers, or on devices reporting 4 CPU cores or fewer, or 4 GB of memory or less.
- The render loop stops 2s after the last input, once the dye has faded. It also pauses while the tab is hidden, and releases every GPU resource on unmount.
- If WebGL setup fails, it silently switches itself off.

**In this preview** the canvas is contained by a `transform` on the 320px box, and a short scripted sweep runs on load. The headless renderer reports 4 cores, so there the effect switches itself off and the box stays blank.

**Don't** mount more than one.

## API

```ts
export type SplashQuality = "high" | "low";
type SplashCursorProps = {
    quality?: SplashQuality;
    SIM_RESOLUTION?: number;
    DYE_RESOLUTION?: number;
    CAPTURE_RESOLUTION?: number;
    DENSITY_DISSIPATION?: number;
    VELOCITY_DISSIPATION?: number;
    PRESSURE?: number;
    PRESSURE_ITERATIONS?: number;
    CURL?: number;
    SPLAT_RADIUS?: number;
    SPLAT_FORCE?: number;
    SHADING?: boolean;
    COLOR_UPDATE_SPEED?: number;
    BACK_COLOR?: {
        r: number;
        g: number;
        b: number;
    };
    TRANSPARENT?: boolean;
};
declare function SplashCursor(props: SplashCursorProps): JSX.Element;
export { SplashCursor };
```

## Example

```html
<div id="root" class="p-6"></div>
<script>
(function () {
  var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
  function App() {
    var ref = React.useRef(null);
    React.useEffect(function () {
      // One short scripted sweep so the panel is not blank before the viewer moves the pointer.
      var box = ref.current, step = 0, timer;
      function tick() {
        if (!box || step > 40) return;
        var r = box.getBoundingClientRect(), t = step / 40;
        window.dispatchEvent(new MouseEvent("mousemove", {
          clientX: r.left + r.width * (0.1 + 0.8 * t),
          clientY: r.top + r.height * (0.5 + 0.28 * Math.sin(t * Math.PI * 2))
        }));
        step += 1;
        timer = setTimeout(tick, 16);
      }
      timer = setTimeout(tick, 400);
      return function () { clearTimeout(timer); };
    }, []);
    // The transform makes this box the containing block for SplashCursor's fixed, inset-0 canvas.
    return h("div", { ref: ref, className: "relative h-[320px] overflow-hidden rounded-xl border bg-background [transform:translateZ(0)]" },
      h(DC.SplashCursor, { quality: "low" }),
      h("div", { className: "pointer-events-none relative z-10 flex h-full flex-col items-center justify-center gap-1 text-center" },
        h("p", { className: "text-sm font-medium" }, "Move your pointer across this panel"),
        h("p", { className: "max-w-xs text-xs text-muted-foreground" }, "Skipped under reduced motion, on touch screens, and on devices with 4 cores or 4 GB of memory or less.")));
  }
  ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>
```

## More in Effects

- [CosmicSynapseCanvas](/design/components/cosmic-synapse-canvas.md): A canvas animation of a slowly rotating sphere of neurons that fire and send pulses along their links, like a brain lighting up.
- [CpuArchitecture](/design/components/cpu-architecture.md): An SVG circuit diagram: eight traces run into a central chip labelled "DIGITAL CREW", with coloured light pulses travelling along them.
- [FlickeringGrid](/design/components/flickering-grid.md): A canvas of small squares whose opacity flickers at random, with optional text knocked into the grid as brighter squares.
- [Glitchy404](/design/components/glitchy-404.md): A "404" made of shaking fragments, rasterised to a canvas where every pixel row is jittered sideways each frame, like a bad signal.
- [Globe](/design/components/globe.md): An auto-rotating 3D dot-matrix globe (cobe, WebGL) with orange city markers, which the visitor can drag to spin.
- [GlowingEffect](/design/components/glowing-effect.md): A multicolour glow that runs along a card's border and follows the pointer around it.
- [PixelCard](/design/components/pixel-card.md): A card whose surface fills with shimmering pixels, spreading out from the centre, while it is hovered or focused.
- [ProgressiveBlur](/design/components/progressive-blur.md): A stack of backdrop-blur layers, each masked to a band, so content behind it blurs progressively toward one edge.
- [SecondBentoAnimation](/design/components/second-bento-animation.md): A security emblem for the bento grid: a solid shield sits on a flickering dot grid, and the whole tile is clipped to a shield silhouette until hovered.
