{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "smooth-cursor",
  "type": "registry:ui",
  "description": "A customizable, physics-based smooth cursor animation component with spring animations and rotation effects",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "registry/magicui/smooth-cursor.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState, type FC } from \"react\"\nimport { motion, useSpring } from \"motion/react\"\n\ninterface Position {\n  x: number\n  y: number\n}\n\nexport interface SmoothCursorProps {\n  cursor?: React.ReactNode\n  springConfig?: {\n    damping: number\n    stiffness: number\n    mass: number\n    restDelta: number\n  }\n}\n\nconst DESKTOP_POINTER_QUERY = \"(any-hover: hover) and (any-pointer: fine)\"\n\nfunction isTrackablePointer(pointerType: string) {\n  return pointerType !== \"touch\"\n}\n\nconst DefaultCursorSVG: FC = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={50}\n      height={54}\n      viewBox=\"0 0 50 54\"\n      fill=\"none\"\n      style={{ scale: 0.5 }}\n    >\n      <g filter=\"url(#filter0_d_91_7928)\">\n        <path\n          d=\"M42.6817 41.1495L27.5103 6.79925C26.7269 5.02557 24.2082 5.02558 23.3927 6.79925L7.59814 41.1495C6.75833 42.9759 8.52712 44.8902 10.4125 44.1954L24.3757 39.0496C24.8829 38.8627 25.4385 38.8627 25.9422 39.0496L39.8121 44.1954C41.6849 44.8902 43.4884 42.9759 42.6817 41.1495Z\"\n          fill=\"black\"\n        />\n        <path\n          d=\"M43.7146 40.6933L28.5431 6.34306C27.3556 3.65428 23.5772 3.69516 22.3668 6.32755L6.57226 40.6778C5.3134 43.4156 7.97238 46.298 10.803 45.2549L24.7662 40.109C25.0221 40.0147 25.2999 40.0156 25.5494 40.1082L39.4193 45.254C42.2261 46.2953 44.9254 43.4347 43.7146 40.6933Z\"\n          stroke=\"white\"\n          strokeWidth={2.25825}\n        />\n      </g>\n      <defs>\n        <filter\n          id=\"filter0_d_91_7928\"\n          x={0.602397}\n          y={0.952444}\n          width={49.0584}\n          height={52.428}\n          filterUnits=\"userSpaceOnUse\"\n          colorInterpolationFilters=\"sRGB\"\n        >\n          <feFlood floodOpacity={0} result=\"BackgroundImageFix\" />\n          <feColorMatrix\n            in=\"SourceAlpha\"\n            type=\"matrix\"\n            values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\"\n            result=\"hardAlpha\"\n          />\n          <feOffset dy={2.25825} />\n          <feGaussianBlur stdDeviation={2.25825} />\n          <feComposite in2=\"hardAlpha\" operator=\"out\" />\n          <feColorMatrix\n            type=\"matrix\"\n            values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0\"\n          />\n          <feBlend\n            mode=\"normal\"\n            in2=\"BackgroundImageFix\"\n            result=\"effect1_dropShadow_91_7928\"\n          />\n          <feBlend\n            mode=\"normal\"\n            in=\"SourceGraphic\"\n            in2=\"effect1_dropShadow_91_7928\"\n            result=\"shape\"\n          />\n        </filter>\n      </defs>\n    </svg>\n  )\n}\n\nexport function SmoothCursor({\n  cursor = <DefaultCursorSVG />,\n  springConfig = {\n    damping: 45,\n    stiffness: 400,\n    mass: 1,\n    restDelta: 0.001,\n  },\n}: SmoothCursorProps) {\n  const lastMousePos = useRef<Position>({ x: 0, y: 0 })\n  const velocity = useRef<Position>({ x: 0, y: 0 })\n  const lastUpdateTime = useRef(Date.now())\n  const previousAngle = useRef(0)\n  const accumulatedRotation = useRef(0)\n  const [isEnabled, setIsEnabled] = useState(false)\n  const [isVisible, setIsVisible] = useState(false)\n\n  const cursorX = useSpring(0, springConfig)\n  const cursorY = useSpring(0, springConfig)\n  const rotation = useSpring(0, {\n    ...springConfig,\n    damping: 60,\n    stiffness: 300,\n  })\n  const scale = useSpring(1, {\n    ...springConfig,\n    stiffness: 500,\n    damping: 35,\n  })\n\n  useEffect(() => {\n    const mediaQuery = window.matchMedia(DESKTOP_POINTER_QUERY)\n\n    const updateEnabled = () => {\n      const nextIsEnabled = mediaQuery.matches\n      setIsEnabled(nextIsEnabled)\n\n      if (!nextIsEnabled) {\n        setIsVisible(false)\n      }\n    }\n\n    updateEnabled()\n    mediaQuery.addEventListener(\"change\", updateEnabled)\n\n    return () => {\n      mediaQuery.removeEventListener(\"change\", updateEnabled)\n    }\n  }, [])\n\n  useEffect(() => {\n    if (!isEnabled) {\n      return\n    }\n\n    let timeout: ReturnType<typeof setTimeout> | null = null\n\n    const updateVelocity = (currentPos: Position) => {\n      const currentTime = Date.now()\n      const deltaTime = currentTime - lastUpdateTime.current\n\n      if (deltaTime > 0) {\n        velocity.current = {\n          x: (currentPos.x - lastMousePos.current.x) / deltaTime,\n          y: (currentPos.y - lastMousePos.current.y) / deltaTime,\n        }\n      }\n\n      lastUpdateTime.current = currentTime\n      lastMousePos.current = currentPos\n    }\n\n    const smoothPointerMove = (e: PointerEvent) => {\n      if (!isTrackablePointer(e.pointerType)) {\n        return\n      }\n\n      setIsVisible(true)\n\n      const currentPos = { x: e.clientX, y: e.clientY }\n      updateVelocity(currentPos)\n\n      const speed = Math.sqrt(\n        Math.pow(velocity.current.x, 2) + Math.pow(velocity.current.y, 2)\n      )\n\n      cursorX.set(currentPos.x)\n      cursorY.set(currentPos.y)\n\n      if (speed > 0.1) {\n        const currentAngle =\n          Math.atan2(velocity.current.y, velocity.current.x) * (180 / Math.PI) +\n          90\n\n        let angleDiff = currentAngle - previousAngle.current\n        if (angleDiff > 180) angleDiff -= 360\n        if (angleDiff < -180) angleDiff += 360\n        accumulatedRotation.current += angleDiff\n        rotation.set(accumulatedRotation.current)\n        previousAngle.current = currentAngle\n\n        scale.set(0.95)\n\n        if (timeout !== null) {\n          clearTimeout(timeout)\n        }\n\n        timeout = setTimeout(() => {\n          scale.set(1)\n        }, 150)\n      }\n    }\n\n    let rafId = 0\n    const throttledPointerMove = (e: PointerEvent) => {\n      if (!isTrackablePointer(e.pointerType)) {\n        return\n      }\n\n      if (rafId) return\n\n      rafId = requestAnimationFrame(() => {\n        smoothPointerMove(e)\n        rafId = 0\n      })\n    }\n\n    document.body.style.cursor = \"none\"\n    window.addEventListener(\"pointermove\", throttledPointerMove, {\n      passive: true,\n    })\n\n    return () => {\n      window.removeEventListener(\"pointermove\", throttledPointerMove)\n      document.body.style.cursor = \"auto\"\n      if (rafId) cancelAnimationFrame(rafId)\n      if (timeout !== null) {\n        clearTimeout(timeout)\n      }\n    }\n  }, [cursorX, cursorY, rotation, scale, isEnabled])\n\n  if (!isEnabled) {\n    return null\n  }\n\n  return (\n    <motion.div\n      style={{\n        position: \"fixed\",\n        left: cursorX,\n        top: cursorY,\n        translateX: \"-50%\",\n        translateY: \"-50%\",\n        rotate: rotation,\n        scale: scale,\n        zIndex: 100,\n        pointerEvents: \"none\",\n        willChange: \"transform\",\n        opacity: isVisible ? 1 : 0,\n      }}\n      initial={false}\n      animate={{ opacity: isVisible ? 1 : 0 }}\n      transition={{\n        duration: 0.15,\n      }}\n    >\n      {cursor}\n    </motion.div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}