# Ghost Cursor

> A ghost of glowing smoke that floats after the cursor and curls away as it rises, simulated on raw WebGL2.

A ghost of smoke follows the cursor on a spring, leaves a curling trail that rises and fades, and releases a puff when you click. On first mount it floats across once on its own. It runs a small fluid simulation on raw WebGL2 with no extra dependencies, renders glowing smoke on dark pages and soft ink smoke on light ones, and sleeps when idle or offscreen.

<demo-ghost-cursor>



</demo-ghost-cursor>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/ghost-cursor.json"
```

## Usage

The effect fills its parent (`position: absolute; inset: 0`). If the parent is `static`, it is set to `relative` while mounted. Pointer events pass through to your content.

```vue
<script setup lang="ts">
  import GhostCursor from '~/components/ui/ghost-cursor/GhostCursor.vue';

  const colorMode = useColorMode();
</script>

<template>
  <section class="relative h-125 overflow-hidden">
    <h1>Your content</h1>
    <GhostCursor
      :theme="colorMode.value === 'light' ? 'light' : 'dark'"
      :color="colorMode.value === 'light' ? '#8b6fb3' : '#B497CF'"
      :size="80"
      :trail-length="50"
      :turbulence="0.5"
      :drift="0.3"
    />
  </section>
</template>
```

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#B497CF'
      </code>
    </td>
    
    <td>
      Color of the smoke (hex or rgb). Dense parts glow lighter.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        80
      </code>
    </td>
    
    <td>
      Size of the ghost, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trail-length
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        50
      </code>
    </td>
    
    <td>
      How long the smoke lingers behind the cursor, from 1 to 200.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inertia
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How far the ghost floats behind the cursor and glides on when it leaves, 0 to 0.99.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        turbulence
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How much the smoke swirls and curls as it drifts, from 0 to 2.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drift
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      How fast the smoke rises. Negative values let it sink.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        detail
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How wispy the smoke is, from 0 (soft glow) to 1 (billowing wisps).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        brightness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Brightness of the smoke.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bloom-strength
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      Strength of the soft glow around the smoke.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bloom-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How far the glow spreads, from 0 to 3.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bloom-threshold
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.025
      </code>
    </td>
    
    <td>
      How bright smoke must be before it glows. Higher keeps the glow on the core.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grain-intensity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.05
      </code>
    </td>
    
    <td>
      Film grain over the smoke, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        edge-intensity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Fades the smoke out toward the edges of the area, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        click-puff
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Clicking releases a puff of smoke.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      The ghost floats across once on mount, before the cursor arrives.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Glowing smoke for dark pages, or soft ink smoke for light ones.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mix-blend-mode
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'screen'
      </code>
      
       (dark), <code>
        'normal'
      </code>
      
       (light)
    </td>
    
    <td>
      CSS blend mode for the canvas.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade-delay-ms
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1000
      </code>
      
       (<code>
        500
      </code>
      
       on touch)
    </td>
    
    <td>
      Time after the cursor stops before the ghost starts to fade, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade-duration-ms
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1500
      </code>
      
       (<code>
        1000
      </code>
      
       on touch)
    </td>
    
    <td>
      How long the fade takes, in ms.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        max-device-pixel-ratio
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Highest pixel ratio the canvas renders at.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        target-pixels
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        600000
      </code>
      
       (<code>
        300000
      </code>
      
       on touch)
    </td>
    
    <td>
      Pixel budget for the smoke. Large areas render at a lower resolution to stay fast.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z-index
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Stacking order of the effect over your content.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the container.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The canvas is decorative (`aria-hidden="true"`) and uses `pointer-events: none`, so it never blocks clicks, focus or text selection.
- With `prefers-reduced-motion: reduce` the intro flight is skipped; the ghost only appears when the user moves the pointer.
- The loop stops when the ghost has faded, when the element is offscreen, and when the tab is hidden.
- Ported from [React Bits](https://reactbits.dev/animations/ghost-cursor) by David Haz.
