Skip to main content

Documentation Index

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

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

CursorWisp replaces the browser’s default pointer with a two-layer animated cursor that follows the mouse with physics-based spring lag. A small sharp dot tracks closely; a larger blurred orb trails behind. When the cursor moves over an <a> or <button> element, the dot scales up and the orb brightens. The component is rendered once at the application root level so it persists across all route changes. The component takes no props.

Rendering

CursorWisp is imported in main.js at the top of the component tree (outside any page component). It returns a Fragment containing two absolutely positioned motion.div elements fixed to the viewport at z-[9999] and z-[9998] respectively.
// From main.js root render
import '../components/CursorWisp.js';

// The component mounts globally via a side-effect import — or render it explicitly:
<CursorWisp />
CursorWisp requires the native cursor to be hidden globally. The Digital Alchemy site adds a hide-cursor class to <body> which applies cursor: none via a Tailwind utility. Without this, both the native cursor and the wisp will be visible simultaneously.

The Two Layers

Layer 1 — Sharp dot (z-[9999])

w-2 h-2
bg-turquoise
rounded-full
pointer-events-none
mix-blend-screen
Tracks the mouse with tight spring physics:
// Spring config for the dot
{ damping: 25, stiffness: 150, mass: 0.5 }
Animated states:
animate:    { scale: isHoveringInteractive ? 2.5 : 1,
              opacity: isHoveringInteractive ? 0.8 : 1 }
transition: { duration: 0.2 }
When over an interactive element, the dot grows to 2.5× its size and slightly reduces opacity — indicating a clickable target.

Layer 2 — Blurred orb (z-[9998])

w-8 h-8
rounded-full
pointer-events-none
mix-blend-screen
blur-md
bg-teal/40
Trails behind the mouse with slower, heavier spring physics:
// Spring config for the orb
{ damping: 15, stiffness: 80, mass: 1 }
The higher mass and lower stiffness cause the orb to lag noticeably behind the dot, giving a “wisp-following-its-master” feel. Animated states:
animate: {
  scale:   isHoveringInteractive ? 1.5   : [1, 1.2, 1],     // expands on hover
  opacity: isHoveringInteractive ? 0.4   : [0.3, 0.6, 0.3], // pulses normally
}
transition: {
  scale:   { duration: 2, repeat: Infinity },
  opacity: { duration: 2, repeat: Infinity },
}
In its resting state the orb continuously pulses in scale and opacity. Over interactive elements it locks to a stable 1.5× scale.

Position Tracking

Mouse coordinates are tracked with window.addEventListener("mousemove"). Each layer has its own pair of spring-smoothed MotionValue instances via a custom useSpring-like hook (g in the minified source):
// Dot — tight tracking
const dotX   = useSpring(0, { damping: 25, stiffness: 150, mass: 0.5 });
const dotY   = useSpring(0, { damping: 25, stiffness: 150, mass: 0.5 });

// Orb — slow, heavy trailing
const orbX   = useSpring(0, { damping: 15, stiffness: 80,  mass: 1   });
const orbY   = useSpring(0, { damping: 15, stiffness: 80,  mass: 1   });
On every mousemove, all four values are updated simultaneously. Because they have different spring configs, they settle at different rates — creating the visual separation between dot and orb. Both layers use translateX: "-50%", translateY: "-50%" style props to centre the element on the cursor hotspot.

Interactive Element Detection

A mouseover listener checks event.target on every DOM element the pointer enters:
const isInteractive = (target) =>
  target.tagName.toLowerCase() === "a"   ||
  target.tagName.toLowerCase() === "button" ||
  target.closest("a")    !== null ||
  target.closest("button") !== null;
setIsHovering(true/false) drives the scale/opacity variants on both layers. The closest() checks ensure that text or icons inside a button or link also trigger the expanded state.

mix-blend-screen

Both layers use mix-blend-screen. This blend mode means the cursor layers add their color to the underlying content rather than covering it — the turquoise dot and teal orb brighten whatever is beneath them, creating a luminous glow effect rather than an opaque cursor overlaid on the page.
CursorWisp only works on devices with a pointer (mouse or trackpad). On touch devices there is no mousemove event, so the wisp never appears. This is intentional — touch devices use the native tap interaction model.

Build docs developers (and LLMs) love