Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/spooky/llms.txt
Use this file to discover all available pages before exploring further.
CandleCursor hides the native cursor site-wide and renders a layered flame effect in its place. The flame is built from three concentric layers — a white-yellow inner flame, a pumpkin-orange outer flame, and a wide diffuse glow — and follows the mouse pointer with spring physics so it trails the cursor with a natural, organic lag. When the user hovers over a link or button, the entire flame scales up to 1.5× its normal size, providing tactile hover feedback without any additional CSS.
How it works
The component manages three pieces of React state: the current(x, y) mouse position, a boolean isHovering flag for interactive elements, and an isVisible flag that starts false until the mouse has moved at least once (preventing the flame from flashing at the origin 0, 0 on page load).
Three window event listeners are registered in a useEffect:
mousemove— updates the(x, y)position state and setsisVisibletotrueon the first movementmouseover— inspects the event target: if it is an<a>,<button>, or any element with[role="button"](checked viatagNameandclosest()),isHoveringis set totrue; otherwise it is reset tofalsedocument.mouseleave— setsisVisibletofalsewhen the pointer leaves the browser window entirely
framer-motion motion.div fixed at z-[9999] with pointer-events-none and mix-blend-screen. Its animate prop drives the x and y position, offset by −10px on each axis to centre the flame on the hotspot. The transition uses spring physics with type: 'spring', stiffness: 500, damping: 28, and mass: 0.5 for smooth, responsive lag.
Inside the container a second motion.div animates its scale between 1 (idle) and 1.5 (hovering) to produce the hover bloom. The three flame layers are absolutely positioned div elements:
| Layer | Classes | Effect |
|---|---|---|
| White-yellow inner flame | w-2 h-3, bg-yellow-200, blur-[1px], animate-flicker | Sharp bright core |
| Orange mid flame | w-4 h-6, bg-[#ff7518], blur-[3px], opacity-80, animate-flicker | Main flame body |
| Orange diffuse glow | w-8 h-12, bg-[#ff7518], blur-[10px], opacity-30, animate-flicker | Soft ambient halo |
animate-flicker class (with staggered animationDelay values of 0s, 0.1s, and 0.2s) so the layers pulse slightly out of phase, creating a more organic flicker.
When isHovering is true, an AnimatePresence smoke puff is mounted: a small gray motion.div that starts at opacity: 0.5, scale: 0.5, y: 0 and animates to opacity: 0, scale: 1.5, y: -20 over 500 ms, giving the impression of a flame-blown smoke wisp.
The component returns null when isVisible is false, so it is completely absent from the DOM until the mouse moves.
CSS integration
The native cursor is suppressed globally for all interactive element types inmain.css:
Cursor visibility on touch devices
Touch devices do not firemousemove events, so the isVisible state remains false and the component returns null for the entire session — it is effectively invisible on mobile without any additional logic. If you need to explicitly restore the native cursor on touch devices (for example after progressive enhancement), you can layer a media query on top of the cursor: none rule:
Customizing the flame
All flame appearance values are straightforward to tweak in the source before rebuilding:- Color — both the mid-flame and diffuse-glow layers use
bg-[#ff7518](pumpkin orange). Replace#ff7518with any CSS color to change the flame hue. - Size — the three layers use Tailwind width/height utility classes (
w-2 h-3,w-4 h-6,w-8 h-12). Increase or decrease these to make the flame larger or smaller. - Spring physics — the
transitionobject{ type: 'spring', stiffness: 500, damping: 28, mass: 0.5 }controls how tightly the flame tracks the cursor. Increasestiffnessfor a snappier feel, decrease it for more float. Increasedampingto reduce oscillation.