"use client"; import { useState, type ReactNode } from "react"; import { motion, useMotionTemplate, useReducedMotion, useSpring } from "motion/react"; type HoverSpotlightProps = { /** Stays clickable: the dark layer lets the pointer through. */ children: ReactNode; /** Size of the light in pixels. */ radius?: number; /** Colour of the dark around the light. Match your background. */ dim?: string; className?: string; }; const FOLLOW = { stiffness: 300, damping: 32 }; /* HoverSpotlight: when the mouse comes in, the block goes dark and only a soft circle around the pointer stays lit. Touch and keyboard see it fully lit. */ export function HoverSpotlight({ children, radius = 140, dim = "rgb(8 7 16 / 0.9)", className = "" }: HoverSpotlightProps) { const reduce = useReducedMotion(); const [on, setOn] = useState(false); const x = useSpring(0, FOLLOW); const y = useSpring(0, FOLLOW); const size = useSpring(radius * 3, { stiffness: 120, damping: 22 }); const background = useMotionTemplate`radial-gradient(circle ${size}px at ${x}px ${y}px, transparent 50%, ${dim} 100%)`; const onMove = (e: React.PointerEvent) => { if (reduce || e.pointerType === "touch") return; const r = e.currentTarget.getBoundingClientRect(); const px = e.clientX - r.left; const py = e.clientY - r.top; if (on) { x.set(px); y.set(py); } else { x.jump(px); y.jump(py); size.set(radius); setOn(true); } }; const onLeave = () => { size.set(radius * 3); setOn(false); }; return (
{children}
); }