{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"image-trail","type":"registry:component","title":"Image Trail","description":"A trail of images that follows the cursor.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/image-trail.md\nDocs: https://atelier-ui.com/docs/components/cursor/image-trail","dependencies":["motion"],"registryDependencies":["https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/image-trail/image-trail.tsx","type":"registry:component","target":"components/image-trail/image-trail.tsx","content":"\"use client\"\n\nimport { delay, wrap } from \"motion\"\nimport {\n    AnimatePresence,\n    motion,\n    useMotionValue,\n    useMotionValueEvent,\n    useTransform,\n} from \"motion/react\"\nimport { Children, isValidElement, type ReactNode, useEffect, useRef, useState } from \"react\"\n\ntype TrailItem = {\n    id: number\n    x: number\n    y: number\n    driftX: number\n    driftY: number\n    rotate: number\n    node: ReactNode\n}\n\nexport type ImageTrailProps = {\n    children: ReactNode\n    removeDelay?: number\n    driftAmount?: number\n    spawnDistance?: number\n}\n\nexport function ImageTrail({\n    children,\n    removeDelay = 1.0,\n    driftAmount = 36,\n    spawnDistance = 76,\n}: ImageTrailProps) {\n    const childrenArray = Children.toArray(children).filter(isValidElement)\n    const [items, setItems] = useState<TrailItem[]>([])\n    const sum = useRef(0)\n    const itemIndex = useRef(0)\n    const idCounter = useRef(0)\n    const pointerX = useMotionValue(0)\n    const pointerY = useMotionValue(0)\n\n    const distanceInPixels = useTransform(() => {\n        const mouseX = pointerX.get()\n        const mouseY = pointerY.get()\n        const dx = mouseX - (pointerX.getPrevious() ?? mouseX)\n        const dy = mouseY - (pointerY.getPrevious() ?? mouseY)\n\n        return Math.sqrt(dx * dx + dy * dy)\n    })\n\n    useMotionValueEvent(distanceInPixels, \"change\", (latest) => {\n        sum.current += latest\n        if (sum.current >= spawnDistance) {\n            const mouseX = pointerX.get()\n            const mouseY = pointerY.get()\n\n            const prevMouseX = pointerX.getPrevious() ?? mouseX\n            const prevMouseY = pointerY.getPrevious() ?? mouseY\n\n            const dx = mouseX - prevMouseX\n            const dy = mouseY - prevMouseY\n\n            const dist = Math.sqrt(dx * dx + dy * dy)\n\n            const nx = dx / dist\n            const ny = dy / dist\n            const angle = Math.atan2(ny, nx) * (180 / Math.PI)\n            const item: TrailItem = {\n                id: idCounter.current++,\n                x: mouseX,\n                y: mouseY,\n                driftX: nx * driftAmount + (Math.random() - 0.5) * driftAmount * 0.5,\n                driftY: ny * driftAmount + (Math.random() - 0.5) * driftAmount * 0.5,\n                rotate: angle * 0.15,\n                node: childrenArray[itemIndex.current],\n            }\n\n            setItems((prev) => [...prev, item])\n\n            itemIndex.current = wrap(0, childrenArray.length, itemIndex.current + 1)\n\n            delay(() => {\n                setItems((prev) => prev.filter((i) => i.id !== item.id))\n            }, removeDelay)\n\n            sum.current = 0\n        }\n    })\n\n    useEffect(() => {\n        const handlePointerMove = (event: PointerEvent) => {\n            pointerX.set(event.clientX)\n            pointerY.set(event.clientY)\n        }\n\n        window.addEventListener(\"pointermove\", handlePointerMove)\n        return () => window.removeEventListener(\"pointermove\", handlePointerMove)\n    }, [pointerX, pointerY])\n\n    return (\n        <AnimatePresence>\n            {items.map((item) => (\n                <motion.div\n                    key={item.id}\n                    className=\"pointer-events-none\"\n                    style={{\n                        position: \"fixed\",\n                        left: item.x,\n                        top: item.y,\n                        translate: \"-50% -50%\",\n                    }}\n                    initial={{ scale: 0, x: 0, y: 0, rotate: item.rotate }}\n                    animate={{\n                        scale: 1,\n                        x: item.driftX,\n                        y: item.driftY,\n                        rotate: item.rotate,\n                    }}\n                    exit={{ scale: 0, opacity: 0 }}\n                    transition={{\n                        scale: { type: \"spring\", stiffness: 260, damping: 20, mass: 1 },\n                        x: { type: \"spring\", stiffness: 60, damping: 18, mass: 0.8 },\n                        y: { type: \"spring\", stiffness: 60, damping: 18, mass: 0.8 },\n                        rotate: { type: \"spring\", stiffness: 60, damping: 18, mass: 0.8 },\n                    }}\n                >\n                    {item.node}\n                </motion.div>\n            ))}\n        </AnimatePresence>\n    )\n}\n"}]}