Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/digital-coven/llms.txt
Use this file to discover all available pages before exploring further.
CustomCursor replaces the browser’s native arrow with a bespoke crosshair-style cursor that reacts to both position and hover context. It consists of two visual layers that move together: a circular ring with crosshair lines that snaps to the pointer with a short tween, and a brief trail of star-shaped sparkle particles that scatter behind fast movements. The component is mounted at the very top of the AppShell tree so it renders above every other element, including the site header.
Hiding the Native Cursor
BeforeCustomCursor can render convincingly, the browser’s built-in cursor must be suppressed. This is done globally in main.css using two rules:
body rule hides the cursor everywhere by default. The second rule uses !important to override the browser’s own UA stylesheet, which forces cursor: pointer on interactive elements like <a> and <button>. Without it, the native hand cursor would reappear whenever the user hovers over a link.
If you add a new interactive element that isn’t covered by the selector list — for example a
<label> styled as a button — append its selector to the second rule to keep the custom cursor consistent.How CustomCursor Works
The component is a function component that uses three pieces of state and a singlemousemove listener to drive its animation:
.slice(-15)) to keep the DOM small. The sparkle emission probability of ~40% (Math.random() > 0.6) ensures particles appear during fast movement but not every frame.
Cursor Anatomy
The rendered output is a singlepointer-events-none fixed inset-0 z-[9999] overflow-hidden container with two children: the animated particle trail and the main cursor body.
Particle Trail
Each trail particle is a tiny SVG 8-pointed star (M12 2L15 9L22 12...) rendered in text-neon-lime with a neon lime drop shadow. It enters at opacity: 0.8, scale: 1 and animates out to opacity: 0, scale: 0, rotate: 90 over 0.8 seconds with an easeOut curve. The particles are managed by AnimatePresence so each one plays its exit animation before being removed from the DOM.
Main Cursor Body
The main cursor is a 32×32motion.div centred on the pointer via -ml-4 -mt-4 negative margins. It moves with a tween animation using backOut easing at 0.15s duration — fast enough to feel immediate but with a tiny elastic overshoot that gives it a physical quality. The animate object drives both position and hover-state transforms:
| Element | Default | On Hover |
|---|---|---|
| Circular ring (border) | border-electric-purple + shadow-[0_0_15px_var(--electric-purple)] | border-cyan + shadow-[0_0_15px_var(--cyan)] |
| Centre dot | bg-magenta + shadow-[0_0_8px_var(--magenta)] | bg-neon-lime + shadow-[0_0_8px_var(--neon-lime)] |
| Vertical line | bg-electric-purple/50 | bg-cyan/50 |
| Horizontal line | bg-electric-purple/50 | bg-cyan/50 |
isHover is true, the cursor also scales to 1.5× and rotates 45° — turning the crosshair into a diagonal × shape that visually signals “this is clickable.”
AnimatePresence Mode
TheAnimatePresence wrapping the particle trail does not use a mode prop explicitly in CustomCursor itself — it uses the default "sync" mode, meaning multiple particles can enter and exit simultaneously. This is correct behaviour for a trail: particles should fade out independently without waiting for each other. The mode="wait" used for page transitions belongs to the AnimatePresence in AppShell, not here.
Placement in the Component Tree
CustomCursor is the first child rendered by AppShell, before BackgroundAtmosphere, the header, and the page content:
z-[9999] stacking ensures it floats above the header (z-50), page content (z-10), and any modals or overlays that might appear within routes.