TheDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/full-moon/llms.txt
Use this file to discover all available pages before exploring further.
Cursor component swaps out the browser’s default pointer with a custom three-layer cursor system that matches Full Moon’s witch/dark-fantasy aesthetic. A glowing teal orb trails the pointer with a soft easing lag, a crisp white dot snaps to the exact cursor position for precise targeting, and stochastic spark particles spawn on roughly half of all mouse moves and drift upward before fading out. All three layers are driven by Framer Motion and rendered inside a pointer-events-none container that sits at the very top of the z-stack without ever intercepting clicks.
Visual Elements
The cursor is composed of three independently animated Framer Motion layers, each serving a distinct perceptual role: 1. Glow orb A 16×16 px (w-4 h-4) circle filled with bg-witch-teal-glow (#2dd4bf). The mix-blend-screen blend mode causes it to lighten any pixels beneath it rather than paint over them, preserving the richness of the dark background while adding a luminous halo effect. A blur-[2px] softens the edge into a diffuse glow. The orb follows the real cursor position offset by 8 px (half its width/height) to center it on the pointer, and uses backOut easing over 100 ms — producing a slight overshoot that makes the glow feel elastic and alive.
2. Precision dot
A 4×4 px (w-1 h-1) solid white circle that tracks the cursor with duration: 0 and ease: "linear", meaning it teleports to the new position on every animation frame with zero lag. This gives users a sharp, accurate targeting reference even when the glow orb is still catching up, so interactive elements like buttons and links remain easy to click precisely.
3. Trailing sparks
On approximately 50% of mousemove events, a new particle is pushed into the particles array at the current cursor coordinates. Each spark is a 12×12 px (w-3 h-3) circle using bg-witch-teal-dark (#0e3a3a) with mix-blend-screen and heavy blur (blur-md). Its Framer Motion animation runs for 1 second with easeOut timing: opacity falls from 0.6 to 0, scale grows from 1× to 3×, the particle drifts upward between 20–40 px, and lateral jitter of up to ±10 px gives each spark a unique flight path. The combined effect is a short trail of glowing embers that evaporates as the cursor moves.
State
The component manages two pieces of React state:-
position: { x, y }— Updated on everymousemoveevent withe.clientXande.clientY. Both the glow orb and the precision dot derive theiranimatetarget from this value. -
particles: Array<{ x, y, id }>— A rolling buffer of recent spark-spawn positions. Each entry records the cursor coordinates at the moment of spawn and a monotonically incrementingidused as the Reactkey. The array is capped at 15 entries (see Performance Notes below).
mousemove listener registered in a useEffect with an empty dependency array, so the listener is attached once on mount and cleaned up on unmount.
Performance Notes
Two deliberate constraints keep the component from degrading performance during fast, continuous mouse movement:-
Particle cap at 15. Every time a new spark is spawned, the update uses
prev.slice(-15)to discard any entries beyond the 15 most recent before appending the new one. Without this guard, rapid cursor movement could push thousands of entries into the array, causing React to reconcile and Framer Motion to animate a growing number of DOM nodes simultaneously. -
pointer-events-nonecontainer. The outermost<div>carriespointer-events-none, which instructs the browser’s hit-testing engine to skip the entire subtree when dispatching pointer events. This means none of the cursor elements — orb, dot, or particles — can accidentally absorb clicks, hovers, or drag events intended for the page content beneath them.
Usage
Cursor is rendered once, unconditionally, inside <Layout>. It takes no props:
Cursor mounts at the Layout level it is always present regardless of which route is active, so the cursor effect is consistent across every page without any per-page setup.
The custom cursor is only visible on desktop devices. Touch screens and most mobile browsers do not fire
mousemove events, so the position state never updates from its initial { x: 0, y: 0 } value. The cursor elements render but remain positioned at the top-left corner of the viewport, outside the visible scroll area, and the pointer-events-none container ensures they have no impact on touch interactions.