{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"infinite-parallax","type":"registry:component","title":"Infinite Parallax","description":"Infinite scrolling columns with parallax.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/infinite-parallax.md\nDocs: https://atelier-ui.com/docs/components/scroll/infinite-parallax","dependencies":["motion"],"registryDependencies":["https://atelier-ui.com/r/smooth-scroll.json","https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/infinite-parallax/infinite-parallax.tsx","type":"registry:component","target":"components/infinite-parallax/infinite-parallax.tsx","content":"\"use client\"\n\nimport { motion, useAnimationFrame, useMotionValue, useScroll, useVelocity } from \"motion/react\"\nimport { type ComponentRef, useEffect, useRef, useState } from \"react\"\n\nconst REVERT_THRESHOLD = 50\nconst PARALLAX_SCALE = 0.0001\n\nexport type InfiniteParallaxProps = {\n    reversed?: boolean\n    autoScrollSpeed?: number\n    parallaxAmount?: number\n    children?: React.ReactNode\n    className?: string\n}\n\nexport function InfiniteParallax({\n    reversed,\n    autoScrollSpeed = 0.02,\n    parallaxAmount = 2,\n    children,\n    className,\n}: InfiniteParallaxProps) {\n    const offsetRef = useRef(0)\n    const contentHRef = useRef(0)\n    const directionRef = useRef<1 | -1>(-1)\n    const containerRef = useRef<ComponentRef<\"div\">>(null)\n    const measureRef = useRef<ComponentRef<\"div\">>(null)\n    const [clones, setClones] = useState(1)\n\n    const y = useMotionValue(0)\n    const { scrollY } = useScroll()\n    const scrollVelocity = useVelocity(scrollY)\n\n    useEffect(() => {\n        const measure = measureRef.current\n        const container = containerRef.current\n        if (!measure || !container) return\n\n        const calcClones = () => {\n            const contentH = measure.getBoundingClientRect().height\n            const containerH = container.getBoundingClientRect().height\n            if (contentH === 0) return\n            contentHRef.current = contentH\n            setClones(Math.ceil(containerH / contentH))\n        }\n\n        calcClones()\n        const ro = new ResizeObserver(calcClones)\n        ro.observe(measure)\n        ro.observe(container)\n\n        return () => ro.disconnect()\n    }, [])\n\n    useAnimationFrame((_, delta) => {\n        if (!contentHRef.current) return\n        const height = contentHRef.current\n        const velocity = scrollVelocity.get()\n\n        if (Math.abs(velocity) > REVERT_THRESHOLD) {\n            directionRef.current = velocity > 0 ? -1 : 1\n        }\n\n        const parallax = Math.abs(velocity) * parallaxAmount * PARALLAX_SCALE\n        const step = delta * (autoScrollSpeed + parallax) * directionRef.current\n        let next = offsetRef.current + step\n\n        if (next <= -height) next = 0\n        else if (next >= 0) next = -height + 1\n\n        offsetRef.current = next\n        y.set(reversed ? next : -next - height)\n    })\n\n    return (\n        <div\n            ref={containerRef}\n            style={{ position: \"relative\", overflow: \"hidden\" }}\n            className={`h-full w-full ${className ?? \"\"}`}\n        >\n            <motion.div style={{ y, position: \"absolute\", top: 0, left: 0, right: 0 }}>\n                <div ref={measureRef}>{children}</div>\n                {Array.from({ length: clones }, (_, i) => (\n                    <div key={i} aria-hidden>\n                        {children}\n                    </div>\n                ))}\n            </motion.div>\n        </div>\n    )\n}\n"}]}