"use client"; /** * Custom cursor — spring-following dot + trailing ring, the page's "alive" tell. * - Ring trails with visible momentum (soft spring); dot leads (tighter spring) * so the two have a believable lead/chase relationship — never a rigid pair. * - On [data-cursor] / links / buttons the ring grows + shows a contextual * label. * - On press (pointerdown anywhere) the ring dips to scale(0.82) for instant * tactile feedback — the same press language as the buttons. * - Hidden on touch / coarse pointers and with prefers-reduced-motion * (globals.css restores the native cursor). */ import { useEffect, useState } from "react"; import { motion, useMotionValue, useSpring } from "motion/react"; import { SPRING } from "./motion"; export default function Cursor() { const [enabled, setEnabled] = useState(false); const [hovering, setHovering] = useState(false); const [label, setLabel] = useState(""); const [pressed, setPressed] = useState(false); const x = useMotionValue(-100); const y = useMotionValue(-100); const ringX = useSpring(x, SPRING.cursorRing); const ringY = useSpring(y, SPRING.cursorRing); const dotX = useSpring(x, SPRING.cursorDot); const dotY = useSpring(y, SPRING.cursorDot); useEffect(() => { const fine = window.matchMedia("(pointer: fine)").matches; const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (!fine || reduce) return; setEnabled(true); document.documentElement.classList.add("has-custom-cursor"); const move = (e: PointerEvent) => { x.set(e.clientX); y.set(e.clientY); const target = (e.target as HTMLElement)?.closest( "[data-cursor], a, button" ); if (target) { setHovering(true); setLabel(target.getAttribute("data-cursor") || ""); } else { setHovering(false); setLabel(""); } }; const down = () => setPressed(true); const up = () => setPressed(false); window.addEventListener("pointermove", move, { passive: true }); window.addEventListener("pointerdown", down, { passive: true }); window.addEventListener("pointerup", up, { passive: true }); return () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerdown", down); window.removeEventListener("pointerup", up); document.documentElement.classList.remove("has-custom-cursor"); }; }, [x, y]); if (!enabled) return null; return ( ); }