{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-trail",
  "title": "Image Trail",
  "description": "A very smooth and interactive trail of images that follows your mouse cursor with discrete step interpolation.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/radix-nova/image-trail/image-trail.tsx",
      "content": "\"use client\"\r\n\r\nimport { useRef, useEffect, type ReactNode } from \"react\"\r\nimport { animate } from \"motion/react\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\ninterface ImageMouseTrailProps {\r\n  items: string[]\r\n  children?: ReactNode\r\n  className?: string\r\n  imgClass?: string\r\n  distance?: number\r\n  maxNumberOfImages?: number\r\n}\r\n\r\nexport default function ImageCursorTrail({\r\n  items = [],\r\n  children,\r\n  className,\r\n  imgClass = \"w-40 h-48\",\r\n  distance = 40,\r\n  maxNumberOfImages = 8,\r\n}: ImageMouseTrailProps) {\r\n  const containerRef = useRef<HTMLDivElement>(null)\r\n  const lastPositionRef = useRef({ x: 0, y: 0 })\r\n  const globalIndexRef = useRef(0)\r\n  const zIndexRef = useRef(1)\r\n  const timeoutsRef = useRef<Set<ReturnType<typeof setTimeout>>>(new Set())\r\n\r\n  useEffect(() => {\r\n    const handleMove = (e: MouseEvent | TouchEvent) => {\r\n      const clientX = \"touches\" in e ? e.touches[0]?.clientX : e.clientX\r\n      const clientY = \"touches\" in e ? e.touches[0]?.clientY : e.clientY\r\n\r\n      if (clientX === undefined || clientY === undefined) return\r\n\r\n      const lastX = lastPositionRef.current.x\r\n      const lastY = lastPositionRef.current.y\r\n      \r\n      if (lastX === 0 && lastY === 0) {\r\n        lastPositionRef.current = { x: clientX, y: clientY }\r\n        return\r\n      }\r\n\r\n      const dist = Math.hypot(clientX - lastX, clientY - lastY)\r\n\r\n      if (dist > distance) {\r\n        const count = Math.floor(dist / distance)\r\n        \r\n        for (let i = 1; i <= count; i++) {\r\n          const t = i / count\r\n          const x = lastX + (clientX - lastX) * t\r\n          const y = lastY + (clientY - lastY) * t\r\n          activateImage(x, y)\r\n        }\r\n        \r\n        lastPositionRef.current = { x: clientX, y: clientY }\r\n      }\r\n    }\r\n\r\n    window.addEventListener(\"mousemove\", handleMove)\r\n    window.addEventListener(\"touchmove\", handleMove)\r\n    \r\n    return () => {\r\n      window.removeEventListener(\"mousemove\", handleMove)\r\n      window.removeEventListener(\"touchmove\", handleMove)\r\n      timeoutsRef.current.forEach((t) => clearTimeout(t))\r\n      timeoutsRef.current.clear()\r\n    }\r\n  }, [distance, items, maxNumberOfImages])\r\n\r\n  const activateImage = (clientX: number, clientY: number) => {\r\n    if (!containerRef.current || !items || items.length === 0) return\r\n\r\n    const containerRect = containerRef.current.getBoundingClientRect()\r\n    const relativeX = clientX - containerRect.left\r\n    const relativeY = clientY - containerRect.top\r\n    \r\n    const img = document.createElement(\"img\")\r\n    img.src = items[globalIndexRef.current % items.length]!\r\n    img.alt = \"\"\r\n    img.setAttribute(\"aria-hidden\", \"true\")\r\n    img.className = cn(\r\n      \"pointer-events-none absolute rounded-3xl object-cover shadow-xl\",\r\n      imgClass\r\n    )\r\n    \r\n    Object.assign(img.style, {\r\n      left: `${relativeX}px`,\r\n      top: `${relativeY}px`,\r\n      zIndex: String(zIndexRef.current),\r\n      position: \"absolute\",\r\n      transform: \"translate(-50%, -50%) scale(0)\",\r\n      opacity: \"0\",\r\n    })\r\n    \r\n    containerRef.current.appendChild(img)\r\n    \r\n    const activeImages = containerRef.current.querySelectorAll(\"img\")\r\n    if (activeImages.length > maxNumberOfImages) {\r\n      activeImages[0]?.remove()\r\n    }\r\n    \r\n    const rotation = Math.random() * 20 - 10\r\n\r\n    animate(img, \r\n      { \r\n        scale: [0, 1],\r\n        opacity: [0, 1],\r\n        rotate: [rotation - 10, rotation]\r\n      }, \r\n      { \r\n        type: \"spring\",\r\n        stiffness: 400,\r\n        damping: 20,\r\n        mass: 0.8\r\n      }\r\n    )\r\n\r\n    const timer = setTimeout(() => {\r\n      const controls = animate(img, \r\n        { \r\n          scale: 0,\r\n          opacity: 0,\r\n          rotate: rotation + 10\r\n        }, \r\n        { \r\n          duration: 0.4, \r\n          ease: \"backIn\" \r\n        }\r\n      )\r\n      \r\n      controls.then(() => {\r\n        img.remove()\r\n        timeoutsRef.current.delete(timer)\r\n      })\r\n    }, 1000)\r\n\r\n    timeoutsRef.current.add(timer)\r\n    \r\n    globalIndexRef.current++\r\n    zIndexRef.current = (zIndexRef.current % 10000) + 1\r\n  }\r\n\r\n  return (\r\n    <div\r\n      ref={containerRef}\r\n      className={cn(\r\n        \"relative grid w-full isolate place-content-center bg-transparent\",\r\n        className\r\n      )}\r\n    >\r\n      <div className=\"relative z-10001\">{children}</div>\r\n    </div>\r\n  )\r\n}\r\n",
      "type": "registry:component",
      "target": "@components/grootstudio/image-trail.tsx"
    }
  ],
  "type": "registry:block"
}