{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collection-surfer",
  "type": "registry:block",
  "title": "Collection surfer",
  "description": "Collection surfer",
  "files": [
    {
      "path": "components/usages/collectionsurferusage.tsx",
      "content": "import { CollectionSurfer } from \"@/registry/open-source/collection-surfer\"\n\nexport default function Page() {\n    return <CollectionSurfer variant=\"uplift\" />\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/collectionsurferusage.tsx",
      "content": "import { CollectionSurfer } from \"@/registry/open-source/collection-surfer\"\n\nexport default function Page() {\n    return <CollectionSurfer variant=\"uplift\" />\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/collection-surfer.tsx",
      "content": "\"use client\";\n\nimport React, { useRef } from \"react\";\nimport { motion, useScroll, useTransform, useSpring, useMotionValue, MotionValue } from \"framer-motion\";\n\n//Credit:\n// https://www.componentry.fun/docs/components/collection-surfer\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        <div className=\"relative bg-black min-h-screen text-white w-full\">\n            {/* 3. Infinite Spacer: \n                We make this huge so the user can scroll for a very long time.\n                (e.g., 50,000px) */}\n            <div style={{ height: \"50000px\" }} className=\"w-full\" />\n\n            {/* Fixed viewport */}\n            <div\n                className=\"fixed inset-0 w-full h-screen overflow-hidden flex items-center justify-center perspective-container\"\n                onMouseMove={handleMouseMove}\n                onMouseLeave={handleMouseLeave}\n            >\n\n                {/* UI Overlays */}\n                <div className=\"absolute top-[3vw] left-[3vw] z-50 pointer-events-none mix-blend-difference\">\n                    <h1 className=\"font-heading font-bold text-[clamp(2rem,6vw,5rem)] leading-[0.9] tracking-tighter ml-[4vw]\">\n                        HERITAGE FW25/26\n                    </h1>\n                    <h1 className=\"font-heading font-bold text-[clamp(2rem,6vw,5rem)] leading-[0.9] tracking-tighter\">\n                        COLLECTION\n                        <span className=\"text-[0.4em] align-top relative top-[0.6em] ml-2 font-mono tabular-nums\">\n                            ({items.length})\n                        </span>\n                    </h1>\n                </div>\n\n                <div className=\"absolute bottom-[3vw] right-[3vw] z-50 font-mono text-xs tracking-wider uppercase opacity-70\">\n                    scroll to surf\n                </div>\n\n                {/* 3D Scene */}\n                <div\n                    className=\"absolute inset-0 flex items-center justify-center\"\n                    style={{\n                        perspective: \"2000px\",\n                        perspectiveOrigin: \"10% 10%\",\n                    }}\n                >\n                    {/* Animated Track */}\n                    <motion.div\n                        className=\"relative w-0 h-0\"\n                        style={{\n                            x,\n                            y,\n                            z,\n                            transformStyle: \"preserve-3d\",\n                        }}\n                    >\n                        {duplicatedItems.map((item, i) => (\n                            <Card\n                                key={`${item.id}-${i}`}\n                                item={item}\n                                i={i}\n                                stepX={stepX}\n                                stepY={stepY}\n                                stepZ={stepZ}\n                                mouseX={mouseX}\n                                mouseY={mouseY}\n                                scrollSpring={smoothScroll}\n                                variant={variant}\n                            />\n                        ))}\n                    </motion.div>\n                </div>\n            </div>\n        </div>\n    );\n}\n\nfunction Card({\n    item,\n    i,\n    stepX,\n    stepY,\n    stepZ,\n    mouseX,\n    mouseY,\n    scrollSpring,\n    variant\n}: {\n    item: CollectionItem,\n    i: number,\n    stepX: number,\n    stepY: number,\n    stepZ: number,\n    mouseX: MotionValue<number>,\n    mouseY: MotionValue<number>,\n    scrollSpring: MotionValue<number>,\n    variant: CollectionSurferVariant\n}) {\n    const ref = useRef<HTMLDivElement>(null);\n\n    // Calculate distance from mouse to center of card\n    const distance = useTransform([mouseX, mouseY, scrollSpring], ([x, y]) => {\n        if (!ref.current || variant === \"simple\") return 200; // Default large distance\n        const rect = ref.current.getBoundingClientRect();\n        const centerX = rect.left + rect.width / 2;\n        const centerY = rect.top + rect.height / 2;\n        const dist = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));\n        return dist;\n    });\n\n    // --- Magnetic Variant ---\n    // Map distance to scale: Closer = larger\n    const targetScale = useTransform(distance, [0, 400], [1.5, 1]);\n    const springScale = useSpring(targetScale, {\n        mass: 0.5,\n        stiffness: 300,\n        damping: 20\n    });\n\n    // --- Uplift Variant ---\n    // Map distance to Y uplift: Closer = move up (negative Y)\n    const targetUplift = useTransform(distance, [0, 400], [-100, 0]);\n    const springUplift = useSpring(targetUplift, {\n        mass: 0.5,\n        stiffness: 300,\n        damping: 20\n    });\n\n    // Combine transforms based on variant\n    const transform = useTransform(\n        [springScale, springUplift],\n        ([s, u]) => {\n            let scaleValue = 1;\n            let upliftValue = 0;\n\n            if (variant === \"magnetic\") {\n                scaleValue = Number(s);\n            } else if (variant === \"uplift\") {\n                upliftValue = Number(u);\n            }\n\n            const baseX = i * stepX;\n            const baseY = i * stepY;\n            const baseZ = i * stepZ;\n\n            return `translate3d(${baseX}px, ${baseY + upliftValue}px, ${baseZ}px) rotateY(-50deg) scale(${scaleValue})`;\n        }\n    );\n\n    return (\n        <motion.div\n            ref={ref}\n            className=\"absolute w-[300px] h-[400px] bg-neutral-900 overflow-hidden shadow-2xl transition-colors duration-500 ease-out group\"\n            style={{\n                transform,\n                transformStyle: \"preserve-3d\",\n            }}\n        >\n            {/* Index number: Using i % 16 + 1 so the duplicate cards show correct numbers (01-16) */}\n            <div className=\"absolute -top-6 -left-4 text-white font-mono text-xs opacity-50 transition-opacity group-hover:opacity-100\">\n                {String((i % 16) + 1).padStart(2, '0')}\n            </div>\n\n            {/* Image */}\n            <div className=\"relative w-full h-full brightness-75 group-hover:brightness-100 transition-all duration-300\">\n                <img\n                    src={item.image}\n                    alt={item.title}\n                    className=\"w-full h-full object-cover\"\n                />\n            </div>\n\n            <div className=\"absolute inset-0 bg-linear-to-tr from-black/20 to-transparent pointer-events-none\" />\n        </motion.div>\n    );\n}\n",
      "type": "registry:ui"
    }
  ]
}