{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cursor-image-trail",
  "title": "Cursor Image Trail",
  "description": "A trail of images that follows the cursor with spring-physics fade and rotation.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/components/unlumen/cursor-image-trail/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface CursorImageTrailProps {\n  items: React.ReactNode[];\n  /** Size of each trail item in px. @default 120 */\n  itemSize?: number;\n  /** Max simultaneous items in the trail. @default 8 */\n  trailLength?: number;\n  /** Minimum cursor travel (px) before spawning a new item. @default 80 */\n  spawnDistance?: number;\n  /** Max random rotation applied to each item in degrees. @default 20 */\n  rotationRange?: number;\n  /** Render target — defaults to the whole window. */\n  containerRef?: React.RefObject<HTMLElement>;\n  className?: string;\n  children?: React.ReactNode;\n}\n\ninterface TrailItem {\n  id: number;\n  x: number;\n  y: number;\n  rotation: number;\n  itemIndex: number;\n}\n\nlet _id = 0;\nconst nextId = () => ++_id;\n\nexport function CursorImageTrail({\n  items,\n  itemSize = 120,\n  trailLength = 8,\n  spawnDistance = 80,\n  rotationRange = 20,\n  containerRef,\n  className,\n  children,\n}: CursorImageTrailProps) {\n  const [trail, setTrail] = React.useState<TrailItem[]>([]);\n  const lastPos = React.useRef<{ x: number; y: number } | null>(null);\n  const itemCounter = React.useRef(0);\n  const containerElRef = React.useRef<HTMLDivElement>(null);\n\n  React.useEffect(() => {\n    const el = containerRef?.current ?? containerElRef.current ?? window;\n\n    const onLeave = () => setTrail([]);\n\n    const onMove = (e: Event) => {\n      const mouseEvent = e as MouseEvent;\n      const rect =\n        containerRef?.current?.getBoundingClientRect() ??\n        containerElRef.current?.getBoundingClientRect();\n\n      const x = rect ? mouseEvent.clientX - rect.left : mouseEvent.clientX;\n      const y = rect ? mouseEvent.clientY - rect.top : mouseEvent.clientY;\n\n      if (lastPos.current) {\n        const dx = x - lastPos.current.x;\n        const dy = y - lastPos.current.y;\n        const dist = Math.sqrt(dx * dx + dy * dy);\n        if (dist < spawnDistance) return;\n      }\n\n      lastPos.current = { x, y };\n\n      const rotation = (Math.random() * 2 - 1) * rotationRange;\n      const itemIndex = itemCounter.current % items.length;\n      itemCounter.current += 1;\n\n      setTrail((prev) => {\n        const next = [...prev, { id: nextId(), x, y, rotation, itemIndex }];\n        return next.slice(-trailLength);\n      });\n    };\n\n    el.addEventListener(\"mousemove\", onMove);\n    el.addEventListener(\"mouseleave\", onLeave);\n    return () => {\n      el.removeEventListener(\"mousemove\", onMove);\n      el.removeEventListener(\"mouseleave\", onLeave);\n    };\n  }, [items, spawnDistance, rotationRange, trailLength, containerRef]);\n\n  const total = trail.length;\n\n  return (\n    <div\n      ref={containerElRef}\n      className={cn(\"relative overflow-hidden\", className)}\n    >\n      {children}\n\n      <AnimatePresence>\n        {trail.map((item, i) => {\n          const age = total - 1 - i;\n          const scale = 0.6 + 0.4 * (1 - age / trailLength);\n\n          return (\n            <motion.div\n              key={item.id}\n              className=\"pointer-events-none absolute select-none\"\n              style={{\n                left: item.x,\n                top: item.y,\n                width: itemSize,\n                x: \"-50%\",\n                y: \"-50%\",\n                zIndex: i,\n              }}\n              initial={{\n                opacity: 0,\n                scale: 0.5,\n                rotate: item.rotation * 1.5,\n              }}\n              animate={{\n                opacity: 1,\n                scale,\n                rotate: item.rotation,\n              }}\n              exit={{\n                opacity: 0,\n                scale: 0.3,\n                rotate: item.rotation * 0.5,\n                filter: \"blur(4px)\",\n              }}\n              transition={{\n                duration: 0.4,\n                ease: [0.23, 1, 0.32, 1],\n              }}\n            >\n              <div className=\"w-full [&>svg]:h-auto [&>svg]:w-full [&>img]:h-auto [&>img]:w-full\">\n                {items[item.itemIndex]}\n              </div>\n            </motion.div>\n          );\n        })}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/cursor-image-trail.tsx"
    }
  ],
  "type": "registry:ui"
}