{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"infinite-zoom","type":"registry:component","title":"Infinite Zoom","description":"An infinite zoom through images.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/infinite-zoom.md\nDocs: https://atelier-ui.com/docs/components/scroll/infinite-zoom","dependencies":["motion"],"registryDependencies":["https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/infinite-zoom/infinite-zoom.tsx","type":"registry:component","target":"components/infinite-zoom/infinite-zoom.tsx","content":"\"use client\"\n\nimport { wrap } from \"motion\"\nimport {\n    type MotionValue,\n    motion,\n    useAnimationFrame,\n    useMotionValue,\n    useTransform,\n} from \"motion/react\"\n\nimport {\n    Children,\n    type ComponentRef,\n    isValidElement,\n    type ReactElement,\n    type ReactNode,\n    useEffect,\n    useRef,\n} from \"react\"\n\nfunction lerp(current: number, target: number, factor: number) {\n    return current + (target - current) * factor\n}\n\nexport type InfiniteZoomProps = {\n    children: ReactNode\n    zoomAmount?: number\n    lerpValue?: number\n    className?: string\n    backgroundSpeed?: number\n}\n\ntype ItemsProps = {\n    children: ReactElement\n    index: number\n    itemsCount: number\n    progress: MotionValue<number>\n    isClone: boolean\n} & Required<Pick<InfiniteZoomProps, \"zoomAmount\" | \"backgroundSpeed\">>\n\nfunction Items({\n    children,\n    index,\n    itemsCount,\n    zoomAmount,\n    progress,\n    isClone,\n    backgroundSpeed,\n}: ItemsProps) {\n    const offset = index / itemsCount\n    const minScale = zoomAmount ** -(itemsCount - 1)\n    const totalScale = zoomAmount ** itemsCount\n    const wrapperRef = useRef<ComponentRef<\"div\">>(null)\n\n    const scale = useTransform(() => {\n        const position = wrap(0, 1, progress.get() + offset)\n        const scaleValue = minScale * totalScale ** position\n\n        /*\n         * once the image fills reach the outer container,\n         * decelerate the scaling.\n         */\n        const overflow = Math.max(0, scaleValue - 1)\n        return scaleValue - overflow * (1 - backgroundSpeed)\n    })\n\n    const clipPath = useTransform(() => {\n        const _scale = scale.get()\n        const wrapper = wrapperRef.current\n        const item = wrapper?.firstElementChild as HTMLElement | null\n        if (!wrapper || !item || _scale <= 1) return \"none\"\n        const x = Math.max(0, (wrapper.offsetWidth - item.offsetWidth / _scale) / 2)\n        const y = Math.max(0, (wrapper.offsetHeight - item.offsetHeight / _scale) / 2)\n        return `inset(${y}px ${x}px)`\n    })\n\n    const zIndex = useTransform(() => {\n        const pos = wrap(0, 1, progress.get() + offset)\n        return Math.floor((1 - pos) * 1000)\n    })\n\n    return (\n        <motion.div\n            ref={wrapperRef}\n            aria-hidden={isClone || undefined}\n            style={{ scale, zIndex, clipPath }}\n            className=\"absolute inset-0 flex items-center justify-center\"\n        >\n            {children}\n        </motion.div>\n    )\n}\n\nexport default function InfiniteZoom({\n    children,\n    zoomAmount = 5,\n    lerpValue = 0.08,\n    className,\n    backgroundSpeed = 0.2,\n}: InfiniteZoomProps) {\n    const items = Children.toArray(children).filter(isValidElement) as ReactElement[]\n    const itemsCount = items.length * 2\n    const progress = useMotionValue(0)\n    const smoothProgress = useMotionValue(0)\n    const containerRef = useRef<ComponentRef<\"div\">>(null)\n\n    useAnimationFrame(() => {\n        smoothProgress.set(lerp(smoothProgress.get(), progress.get(), lerpValue))\n    })\n\n    useEffect(() => {\n        const el = containerRef.current\n        if (!el) return\n\n        const handleWheel = (event: WheelEvent) => {\n            event.preventDefault()\n            progress.set(progress.get() + event.deltaY * 0.0001)\n        }\n\n        el.addEventListener(\"wheel\", handleWheel, { passive: false })\n        return () => el.removeEventListener(\"wheel\", handleWheel)\n    }, [progress])\n\n    return (\n        <motion.div\n            ref={containerRef}\n            className={className}\n            onPan={(event, info) => {\n                event.preventDefault()\n                progress.set(progress.get() + info.delta.y * 0.001)\n            }}\n        >\n            {Array.from({ length: itemsCount }, (_, index) => (\n                <Items\n                    key={index}\n                    index={index}\n                    itemsCount={itemsCount}\n                    zoomAmount={zoomAmount}\n                    progress={smoothProgress}\n                    isClone={index >= items.length}\n                    backgroundSpeed={backgroundSpeed}\n                >\n                    {items[index % items.length]}\n                </Items>\n            ))}\n        </motion.div>\n    )\n}\n"}]}