Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/y2k-web/llms.txt

Use this file to discover all available pages before exploring further.

CursorTrail is a purely decorative overlay that sprinkles hot-pink sparkle particles wherever the mouse moves. Each sparkle springs into existence at full opacity and scale, then fades out while drifting slightly downward and sideways over 0.8 seconds — the kind of whimsical cursor magic you’d find on a mid-2000s fan shrine or DeviantArt profile page. The effect is powered by Framer Motion’s AnimatePresence so entries and exits are smoothly orchestrated without any manual cleanup logic.

Behavior

  • Event listener — the component attaches a mousemove listener to window on mount and removes it on unmount, keeping side-effects contained.
  • Spawn rate — a sparkle is only created on roughly every other mousemove event (50% random chance), which prevents flooding the DOM during fast mouse sweeps while still keeping the trail feeling continuous.
  • Rolling buffer — the component maintains a state array capped at the last 15 sparkles. Once the cap is reached, the oldest entry is dropped each time a new one is added.
  • Exit animation — each sparkle fades its opacity to 0, shrinks its scale to 0, and drifts +20 px downward plus a small random horizontal offset over 0.8 s with an easeOut curve, giving the impression that the glitter is briefly floating before vanishing.

Props

CursorTrail accepts no props. All state (the sparkle buffer, the event listener lifecycle) is managed entirely inside the component.

Import and Usage

The component is exported as S from Primitives.js. Import it under an alias and drop it once inside App — outside the router outlet or main content area so it sits as a global overlay:
import { S as CursorTrail } from '../components/Primitives.js';

// Place once in App, outside the main content:
<CursorTrail />
Because the component is fixed and pointer-events-none it has no impact on layout or interaction — you can safely place it anywhere in the tree.

Framer Motion Animation Config

Each sparkle is a motion.div wrapped inside AnimatePresence. The animation is configured directly on the element via initial, animate, exit, and transition props:
<motion.div
  key={sparkle.id}
  initial={{ opacity: 1, scale: 1, x: sparkle.x, y: sparkle.y }}
  animate={{
    opacity: 0,
    scale: 0,
    y: sparkle.y + 20 + Math.random() * 20,
    x: sparkle.x + (Math.random() - 0.5) * 20,
  }}
  exit={{ opacity: 0 }}
  transition={{ duration: 0.8, ease: 'easeOut' }}
  className="absolute w-2 h-2 text-retro-pink font-pixel text-[10px]"
  style={{ left: -4, top: -4 }}
>

</motion.div>
The left: -4, top: -4 offset centres the 8 px glyph on the exact cursor coordinates stored in sparkle.x / sparkle.y.

Accessibility: prefers-reduced-motion

Inside the useEffect that wires up the mousemove listener, the component checks the user’s motion preference before doing anything:
useEffect(() => {
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
    return; // bail out — no listener attached, no sparkles spawned
  }
  // ... attach mousemove listener
}, []);
If the OS or browser has reduced-motion enabled, the useEffect returns early without attaching any listener. The fixed overlay div is still mounted (it’s pointer-events-none and contains no children), but no sparkle particles are ever created and no animation overhead is incurred. This ensures the site remains comfortable for users who are sensitive to motion or who have explicitly requested a calmer experience.
The 15-particle cap combined with the 50% spawn rate means the component adds at most 15 live motion.div nodes at any instant, even during the fastest possible mouse movement. Framer Motion handles the RAF-based animation loop efficiently for this scale, so the trail should not cause measurable jank on modern hardware. If you do notice frame drops on lower-end devices, reduce the cap (e.g. to 8) or lower the spawn probability from 0.5 to 0.3.
The CursorTrail wrapper element is position: fixed and z-[9999] with pointer-events: none. This means it floats above every other element on the page but is completely transparent to all mouse and touch events — clicks, hovers, text selection, and drag interactions all pass straight through to the content beneath it.

Build docs developers (and LLMs) love