/* global React */ /* Spring mouse-follow — adapted from the "mouse-follow-animations" (framer-motion useSpring) component. A soft moss blob trails the pointer with spring physics, scaling/fading in on enter and out on leave. Re-implemented with a stable discrete spring (no framer-motion) and the native cursor kept (content-heavy site). Mouse-only; off for touch / reduced-motion. */ const { useRef, useEffect } = React; function SpringMouseFollow() { const ref = useRef(null); useEffect(() => { const mq = window.matchMedia; if (mq && !mq("(pointer: fine)").matches) return; if (mq && mq("(prefers-reduced-motion: reduce)").matches) return; const el = ref.current; const target = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; const pos = { x: target.x, y: target.y }; const vel = { x: 0, y: 0 }; let appear = 0, appearVel = 0, appearTarget = 0; // discrete semi-implicit spring (stable at 60fps); springy with light overshoot const K = 0.18, D = 0.74; const onMove = (e) => { target.x = e.clientX; target.y = e.clientY; appearTarget = 1; }; const onOut = (e) => { if (!e.relatedTarget) appearTarget = 0; }; window.addEventListener("mousemove", onMove, { passive: true }); window.addEventListener("mouseout", onOut); let raf = 0; const tick = () => { vel.x += (target.x - pos.x) * K; vel.x *= D; pos.x += vel.x; vel.y += (target.y - pos.y) * K; vel.y *= D; pos.y += vel.y; appearVel += (appearTarget - appear) * 0.2; appearVel *= 0.7; appear += appearVel; const a = Math.max(0, Math.min(1, appear)); el.style.transform = "translate(" + pos.x.toFixed(1) + "px," + pos.y.toFixed(1) + "px) translate(-50%,-50%) scale(" + a.toFixed(3) + ")"; el.style.opacity = a.toFixed(3); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { cancelAnimationFrame(raf); window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseout", onOut); }; }, []); return
; } Object.assign(window, { SpringMouseFollow });