{ "name": "collection-surfer", "type": "registry:ui", "files": [ { "path": "components/ui/collection-surfer.tsx", "content": "\"use client\";\n\nimport React, { useRef } from \"react\";\nimport { motion, useScroll, useTransform, useSpring, useMotionValue, MotionValue } from \"framer-motion\";\n\nexport interface CollectionItem {\n id: number;\n image: string;\n title: string;\n}\n\nexport type CollectionSurferVariant = \"magnetic\" | \"uplift\" | \"simple\";\n\n// Default items for the component in case none are provided\nconst ITEMS: CollectionItem[] = [\n { id: 1, image: \"https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&q=80\", title: \"HERITAGE 01\" },\n { id: 2, image: \"https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=800&q=80\", title: \"HERITAGE 02\" },\n { id: 3, image: \"https://images.unsplash.com/photo-1483985988355-763728e1935b?w=800&q=80\", title: \"HERITAGE 03\" },\n { id: 4, image: \"https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?w=800&q=80\", title: \"HERITAGE 04\" },\n { id: 5, image: \"https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=800&q=80\", title: \"HERITAGE 05\" },\n { id: 6, image: \"https://images.unsplash.com/photo-1532453288672-3a27e9be9efd?w=800&q=80\", title: \"HERITAGE 06\" },\n { id: 7, image: \"https://images.unsplash.com/photo-1552374196-1ab2a1c593e8?w=800&q=80\", title: \"HERITAGE 07\" },\n { id: 8, image: \"https://images.unsplash.com/photo-1509631179647-0177331693ae?w=800&q=80\", title: \"HERITAGE 08\" },\n { id: 9, image: \"https://images.unsplash.com/photo-1502716119720-b23a93e5fe1b?w=800&q=80\", title: \"HERITAGE 09\" },\n { id: 10, image: \"https://images.unsplash.com/photo-1539008835657-9e8e9680c956?w=800&q=80\", title: \"HERITAGE 10\" },\n { id: 11, image: \"https://images.unsplash.com/photo-1469334031218-e382a71b716b?w=800&q=80\", title: \"HERITAGE 11\" },\n { id: 12, image: \"https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=800&q=80\", title: \"HERITAGE 12\" },\n { id: 13, image: \"https://images.unsplash.com/photo-1581044777550-4cfa60707c03?w=800&q=80\", title: \"HERITAGE 13\" },\n { id: 14, image: \"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=800&q=80\", title: \"HERITAGE 14\" },\n { id: 15, image: \"https://images.unsplash.com/photo-1496217590455-aa63a8350eea?w=800&q=80\", title: \"HERITAGE 15\" },\n { id: 16, image: \"https://images.unsplash.com/photo-1571513722275-4b41940f54b8?w=800&q=80\", title: \"HERITAGE 16\" },\n];\n\ninterface CollectionSurferProps {\n items?: CollectionItem[];\n variant?: CollectionSurferVariant;\n}\n\nexport function CollectionSurfer({ items = ITEMS, variant = \"magnetic\" }: CollectionSurferProps) {\n // 1. Loop Setup: Duplicate items to create a buffer\n // We render the list twice: [Original Set, Duplicate Set]\n // When we scroll past the Original Set, we snap back to the start.\n const duplicatedItems = [...items, ...items];\n\n // Scroll sensitivity\n const scrollPerItem = 600;\n\n // The exact scroll distance to complete one full loop of the ORIGINAL items\n const loopDistance = items.length * scrollPerItem;\n\n const { scrollY } = useScroll();\n\n const smoothScroll = useSpring(scrollY, {\n mass: 0.1,\n stiffness: 100,\n damping: 20\n });\n\n // 2. Modulo Logic:\n // Instead of mapping 0 -> totalScroll, we map to a looped value.\n // loops 0 -> loopDistance -> 0 -> loopDistance...\n const loopedProgress = useTransform(smoothScroll, (value) => value % loopDistance);\n\n // Step vector\n const stepX = 240;\n const stepY = -84;\n const stepZ = -288;\n\n // We only move the scene backwards by the length of ONE set of items\n const x = useTransform(loopedProgress, [0, loopDistance], [0, -items.length * stepX]);\n const y = useTransform(loopedProgress, [0, loopDistance], [0, -items.length * stepY]);\n const z = useTransform(loopedProgress, [0, loopDistance], [0, -items.length * stepZ]);\n\n // Mouse position for magnetic effect\n // Initialize off-screen so no card is scaled by default\n const mouseX = useMotionValue(-10000);\n const mouseY = useMotionValue(-10000);\n\n const handleMouseMove = (e: React.MouseEvent) => {\n if (variant === \"simple\") return;\n mouseX.set(e.clientX);\n mouseY.set(e.clientY);\n };\n\n const handleMouseLeave = () => {\n if (variant === \"simple\") return;\n mouseX.set(-10000);\n mouseY.set(-10000);\n };\n\n return (\n