# Wisp — Cursor Effect for React

> A ribbon-trail cursor: a smooth, tapering comet ribbon chases the pointer with eased catch-up, fattening on fast strokes and thinning to nothing at the tail beneath a soft glowing head. Collapses gracefully and pauses when the pointer stops; idles on its own when untouched. Scoped to its container by default, auto-disabled on coarse pointers, and reduced to a quiet motion-free dot under reduced motion.

- Page: https://crucibleui.com/components/cursors/wisp
- Category: [cursors](https://crucibleui.com/components/cursors)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: canvas

## Install

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

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

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

## Props (`WispProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className?` | `string` | — | — |
| `width?` | `number` | `22` | Maximum ribbon thickness in px (reached at peak pointer velocity). The ribbon tapers from here at the head down to zero at the tail. |
| `length?` | `number` | `32` | Number of samples retained in the trail — longer means a lengthier comet tail that takes more frames to collapse when the pointer stops. |
| `speed?` | `number` | `1` | Catch-up responsiveness of the glowing head, 0–2. Higher snaps the head to the pointer faster (a shorter, tighter ribbon); lower lets it lag into a long, languid sweep. |
| `intensity?` | `number` | `1` | 0–1+ master energy: how strongly pointer velocity fattens the ribbon and how bright the head glow burns. |
| `colors?` | `[string, string]` | `a soft-white head over a cool cyan body` | `[head, body]` colors. The head glows toward the first color; the ribbon body is the second and fades to transparent at the tail. Hex only. |
| `global?` | `boolean` | — | Attach to the whole viewport instead of the nearest containing element. Off by default — Wisp is scoped to its parent (place it inside a `relative` container) so it never draws a trail page-wide unless you explicitly opt in. |
| `hideNativeCursor?` | `boolean` | `false ("augment")` | Hide the native system cursor while active. |
| `paused?` | `boolean` | — | Freeze the ribbon in place. |
| `idle?` | `boolean` | `true` | Idle autoplay (scoped mode only): after 1.5s with no pointer in the container, a ghost sweeps the ribbon along a seeded Lissajous path — surging and easing so it fattens and thins — fading in at slightly reduced presence. A real pointer takes over instantly. Also runs on touch-only devices. Never under reduced motion; sleeps offscreen. |

## Usage example

```tsx
import { Wisp } from "@/components/crucible/wisp";

export default function WispDemo() {
  return (
    <div className="relative isolate flex h-full min-h-[360px] w-full items-center justify-center overflow-hidden rounded-xl bg-neutral-950">
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(255,255,255,0.04),transparent_70%)]"
      />
      {/* Scoped to this stage — the ribbon only draws inside the panel. */}
      <Wisp />
      <div className="relative z-10 px-6 text-center">
        <p className="text-3xl font-semibold tracking-tight text-white">Move your cursor</p>
        <p className="mx-auto mt-2 max-w-sm text-sm text-neutral-500">
          Sweep across the panel — the ribbon fattens on fast strokes and
          collapses when you pause.
        </p>
      </div>
    </div>
  );
}
```

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