Skip to main content

Documentation Index

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

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

CursorTrail is a purely decorative React component that makes the mouse cursor feel like it’s trailing magical sparks across the grimoire. As the user moves their pointer, small teal glowing orbs materialize at the cursor’s position, drift downward with a gentle float, and fade out over about 600ms. The effect is subtle enough to stay out of the way of content while being vivid enough to reinforce the enchanted atmosphere of The Craft on every page.

How It Works

CursorTrail manages an array of particle objects in React state. Each particle carries an id, a position (x, y), and a randomly chosen size between 2px and 6px.
1

Listening for mouse movement

On mount, CursorTrail attaches a mousemove listener to window. The handler is throttled so a new particle is only added when at least 30ms have elapsed since the last one — preventing hundreds of particles on fast mouse sweeps. Each valid event pushes a new particle object onto state, keeping only the most recent 15 particles in the array at any time (slice(-15)).
2

Auto-expiring particles

A second useEffect watches the particles array. Whenever particles exist, it schedules a setTimeout for 400ms that removes the oldest particle from the front of the array via slice(1). This creates a rolling FIFO queue: new sparkles appear at the cursor while old ones are continuously pruned from the tail.
3

Animating with Framer Motion

Each particle is rendered as a motion.div inside a Framer Motion AnimatePresence block. Particles enter with opacity: 0.8, scale: 1 at their spawn coordinates (x and y set via the initial prop), then animate to opacity: 0, scale: 0 while drifting 40px downward and up to ±20px horizontally — simulating the natural scatter of magical sparks. The animation runs for 0.6s with an easeOut curve.

Visual Appearance

Each spark is a rounded-full motion.div with:
  • Color: bg-spell — the teal accent color from the Tailwind theme.
  • Glow: shadow-[0_0_8px_2px_rgba(45,212,191,0.6)] — a soft teal radial glow that halos each orb.
  • Size: Random, between 2px and 6px wide and tall, for a natural variation in sparkle intensity.
  • Position: Absolute, offset by half its own size (left: -size/2, top: -size/2) so the spark’s center aligns with the cursor’s hotspot.

Placement in the Component Tree

CursorTrail is rendered directly inside Layout.js, after the ambient blur blobs and before the MoonPhaseNav and <main> elements. Its root container is:
<div className="pointer-events-none fixed inset-0 z-[9999] overflow-hidden">
  {/* AnimatePresence + motion.div particles */}
</div>
The z-[9999] stacking context places the trail above all page content, navigation, and modals, so sparks always appear visually on top. The pointer-events-none class on the container ensures that every click, hover, and drag passes through to the interactive elements below — the trail is purely cosmetic.

Usage

Because CursorTrail self-attaches its own event listeners, no props or configuration are required. Simply render it once in your layout:
import { C as CursorTrail } from './components/CursorTrail';

export function Layout() {
  return (
    <div>
      <CursorTrail />
      {/* rest of layout */}
    </div>
  );
}
CursorTrail should be rendered once at the layout level, not inside individual pages. Rendering it multiple times would register duplicate mousemove listeners and spawn overlapping particles.

Performance

CursorTrail is designed to stay lightweight:
  • The 30ms throttle on the mousemove handler caps particle creation at roughly 33 particles per second, regardless of how fast the mouse moves.
  • The 15-particle cap (slice(-15)) puts a hard ceiling on the number of DOM nodes in the animation layer at any given time.
  • Particles are automatically removed after 400ms, so the DOM never accumulates stale nodes during long idle periods.
  • Framer Motion’s AnimatePresence handles exit animations without keeping invisible elements in the DOM.

Accessibility & Disabling

The cursor trail has no semantic meaning and does not affect focus, tab order, keyboard navigation, or screen readers. Its pointer-events-none container means it never intercepts ARIA interactions.
To disable the cursor trail entirely — for performance, reduced-motion preferences, or touch-only devices — simply remove <CursorTrail /> from Layout.js. No other files need to change. Consider conditionally rendering it based on window.matchMedia('(prefers-reduced-motion: reduce)') for a fully accessible implementation.

Build docs developers (and LLMs) love