{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-split-card",
  "type": "registry:block",
  "title": "Scroll split card",
  "description": "Scroll split card",
  "files": [
    {
      "path": "components/usages/scrollsplitcardusage.tsx",
      "content": "import { useRef } from \"react\"\nimport { ScrollSplitCard } from \"@/registry/open-source/scroll-split-card\"\n\nexport default function Example() {\n    const containerRef = useRef<HTMLDivElement>(null)\n\n    return (\n        <div ref={containerRef} className=\"h-full w-full overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\">\n            <ScrollSplitCard\n                containerRef={containerRef}\n                imageSrc=\"https://images.unsplash.com/photo-1773058373644-74e4120bfc77?q=80&w=2832&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n                cards={[\n                    {\n                        title: \"Going Zero to One\",\n                        description: \"If you've navigating a new business... breaking into a new market.\",\n                        bgColor: \"#e2e2e2\",\n                        textColor: \"#111111\"\n                    },\n                    {\n                        title: \"Scaling from One to N\",\n                        description: \"If you've achieved Product/Market Fit...\",\n                        bgColor: \"#1a5bcf\",\n                        textColor: \"#ffffff\"\n                    },\n                    {\n                        title: \"Need Quick Solutions\",\n                        description: \"If you know exactly what you want and need...\",\n                        bgColor: \"#1c1c1c\",\n                        textColor: \"#ffffff\"\n                    }\n                ]}\n            />\n        </div>\n    )\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/scrollsplitcardusage.tsx",
      "content": "import { useRef } from \"react\"\nimport { ScrollSplitCard } from \"@/registry/open-source/scroll-split-card\"\n\nexport default function Example() {\n    const containerRef = useRef<HTMLDivElement>(null)\n\n    return (\n        <div ref={containerRef} className=\"h-full w-full overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\">\n            <ScrollSplitCard\n                containerRef={containerRef}\n                imageSrc=\"https://images.unsplash.com/photo-1773058373644-74e4120bfc77?q=80&w=2832&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n                cards={[\n                    {\n                        title: \"Going Zero to One\",\n                        description: \"If you've navigating a new business... breaking into a new market.\",\n                        bgColor: \"#e2e2e2\",\n                        textColor: \"#111111\"\n                    },\n                    {\n                        title: \"Scaling from One to N\",\n                        description: \"If you've achieved Product/Market Fit...\",\n                        bgColor: \"#1a5bcf\",\n                        textColor: \"#ffffff\"\n                    },\n                    {\n                        title: \"Need Quick Solutions\",\n                        description: \"If you know exactly what you want and need...\",\n                        bgColor: \"#1c1c1c\",\n                        textColor: \"#ffffff\"\n                    }\n                ]}\n            />\n        </div>\n    )\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/scroll-split-card.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/registry/utilities/cn\";\nimport { motion, useScroll, useTransform, useMotionTemplate } from \"motion/react\";\nimport { useRef, useState, useEffect } from \"react\";\n\n// Credit:\n// https://www.componentry.fun/docs/components/scroll-split-card\n\ninterface ScrollSplitCardItem {\n    title: string;\n    description: string;\n    bgColor: string;\n    textColor: string;\n    icon?: React.ReactNode;\n}\n\ninterface ScrollSplitCardProps {\n    className?: string;\n    imageSrc: string;\n    cards: ScrollSplitCardItem[];\n    containerRef?: React.RefObject<HTMLElement | null>;\n}\n\nexport function ScrollSplitCard({\n    className,\n    imageSrc,\n    cards,\n    containerRef: externalContainerRef,\n}: ScrollSplitCardProps) {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const [scrollContainer, setScrollContainer] = useState<React.RefObject<HTMLElement | null> | undefined>();\n\n    useEffect(() => {\n        if (externalContainerRef?.current) {\n            setScrollContainer(externalContainerRef);\n        }\n    }, [externalContainerRef]);\n\n    const { scrollYProgress } = useScroll({\n        target: containerRef,\n        ...(scrollContainer ? { container: scrollContainer } : {}),\n        offset: [\"start start\", \"end end\"],\n    });\n\n    // Stage 1 to 2: Separation (0 to 0.4), then Stage 2 to 3: Overlap closer (0.4 to 0.8)\n    const leftX = useTransform(scrollYProgress, [0, 0.4, 0.8], [0, -48, -24]);\n    const rightX = useTransform(scrollYProgress, [0, 0.4, 0.8], [0, 48, 24]);\n    const scale = useTransform(scrollYProgress, [0, 0.4], [1, 0.9]);\n\n    // Stage 2 to 3: Flip (0.4 to 0.8)\n    const rotateY = useTransform(scrollYProgress, [0.4, 0.8], [0, 180]);\n    // Due to 180deg Y flip, positive Z becomes visual counter-clockwise, negative Z becomes visual clockwise\n    const rotateZLeft = useTransform(scrollYProgress, [0.4, 0.8], [0, 6]);\n    const rotateZRight = useTransform(scrollYProgress, [0.4, 0.8], [0, -6]);\n\n    // Dynamic borders/radii so it looks like ONE flat image initially\n    const borderRadiusLeft = useTransform(scrollYProgress, [0, 0.2], [\"16px 0px 0px 16px\", \"16px 16px 16px 16px\"]);\n    const borderRadiusMiddle = useTransform(scrollYProgress, [0, 0.2], [\"0px 0px 0px 0px\", \"16px 16px 16px 16px\"]);\n    const borderRadiusRight = useTransform(scrollYProgress, [0, 0.2], [\"0px 16px 16px 0px\", \"16px 16px 16px 16px\"]);\n    const borderOpacity = useTransform(scrollYProgress, [0, 0.2], [0, 0.2]);\n    const shadowOpacity = useTransform(scrollYProgress, [0, 0.2], [0, 0.4]);\n    const boxShadow = useMotionTemplate`inset 0 1px 1px rgba(255, 255, 255, ${borderOpacity}), inset 0 -24px 48px rgba(0, 0, 0, ${shadowOpacity}), 0 25px 50px -12px rgba(0, 0, 0, ${shadowOpacity})`;\n\n    // Cards move up in the last viewport\n    const cardsY = useTransform(scrollYProgress, [0.8, 1], [0, -200]);\n\n    // Text appearance at the end in the sticky viewport\n    const textOpacity = useTransform(scrollYProgress, [0.8, 1], [0, 1]);\n    const textY = useTransform(scrollYProgress, [0.8, 1], [40, 0]);\n\n    // Indicator text appearance at the start\n    const startTextOpacity = useTransform(scrollYProgress, [0, 0.1], [1, 0]);\n    const startTextY = useTransform(scrollYProgress, [0, 0.1], [0, 20]);\n\n    return (\n        <div\n            ref={containerRef}\n            className={cn(\"relative h-[500vh] w-full\", className)}\n        >\n            <div className=\"sticky top-0 flex h-screen w-full items-center justify-center overflow-hidden [perspective:1200px]\">\n                {/* Starting Text indicator */}\n                <motion.div\n                    className=\"absolute top-[20%] left-0 right-0 text-center\"\n                    style={{\n                        opacity: startTextOpacity,\n                        y: startTextY,\n                    }}\n                >\n                    <p className=\"text-sm font-medium tracking-widest text-foreground/50 uppercase\">\n                        Scroll down\n                    </p>\n                </motion.div>\n\n                <motion.div\n                    style={{ scale, y: cardsY, transformStyle: \"preserve-3d\" }}\n                    className=\"flex h-[400px] w-full max-w-4xl px-4 relative\"\n                >\n                    {cards.slice(0, 3).map((card, i) => (\n                        <motion.div\n                            key={i}\n                            className=\"relative h-full flex-1\"\n                            style={{\n                                x: i === 0 ? leftX : i === 2 ? rightX : 0,\n                                rotateY,\n                                rotateZ: i === 0 ? rotateZLeft : i === 2 ? rotateZRight : 0,\n                                zIndex: i, // Ensures Left is under Middle, and Right is above Middle\n                                transformStyle: \"preserve-3d\",\n                            }}\n                        >\n                            {/* Front Side: Original Image Split */}\n                            <motion.div\n                                className=\"absolute inset-0 overflow-hidden [backface-visibility:hidden]\"\n                                style={{\n                                    zIndex: 2, // Ensure front stays above initially\n                                    borderRadius: i === 0 ? borderRadiusLeft : i === 2 ? borderRadiusRight : borderRadiusMiddle,\n                                    boxShadow,\n                                }}\n                            >\n                                <div\n                                    className=\"absolute inset-0 h-full w-[300%]\"\n                                    style={{\n                                        left: `${-100 * i}%`,\n                                        backgroundImage: `url(${imageSrc})`,\n                                        backgroundSize: \"100% 100%\",\n                                        backgroundPosition: \"center\",\n                                    }}\n                                />\n                            </motion.div>\n\n                            {/* Back Side: New Content Card */}\n                            <motion.div\n                                className={cn(\n                                    \"absolute inset-0 overflow-hidden flex flex-col justify-end p-8 [backface-visibility:hidden] will-change-transform\",\n                                    \"border border-white/5 bg-gradient-to-br from-white/10 to-transparent\",\n                                    \"shadow-[inset_0_1px_1px_rgba(255,255,255,0.1),inset_0_-24px_48px_rgba(0,0,0,0.2)]\"\n                                )}\n                                style={{\n                                    backgroundColor: card.bgColor,\n                                    color: card.textColor,\n                                    transform: \"rotateY(180deg)\",\n                                    zIndex: 1, // Ensure back is behind before flip\n                                    borderRadius: i === 0 ? borderRadiusLeft : i === 2 ? borderRadiusRight : borderRadiusMiddle,\n                                    boxShadow,\n                                }}\n                            >\n                                {/* Grainy Noise Overlay */}\n                                <div\n                                    className=\"pointer-events-none absolute inset-0 opacity-20 mix-blend-overlay\"\n                                    style={{\n                                        backgroundImage: `url(\"https://framerusercontent.com/images/6mcf62RlDfRfU61Yg5vb2pefpi4.png?width=256&height=256\")`,\n                                        backgroundRepeat: \"repeat\",\n                                    }}\n                                />\n\n                                <div className=\"relative z-10 mb-auto\">{card.icon}</div>\n                                <h3 className=\"relative z-10 mb-4 text-2xl font-medium leading-tight\">\n                                    {card.title}\n                                </h3>\n                                <p className=\"relative z-10 text-sm opacity-80\">{card.description}</p>\n                            </motion.div>\n                        </motion.div>\n                    ))}\n                </motion.div>\n\n                {/* Ending Text fixed in the sticky viewport */}\n                <motion.div\n                    className=\"absolute bottom-[20%] left-0 right-0 text-center\"\n                    style={{\n                        opacity: textOpacity,\n                        y: textY,\n                    }}\n                >\n                    <p className=\"text-3xl font-medium tracking-tight text-foreground/80 font-serif italic\">\n                        So cool, right?\n                    </p>\n                </motion.div>\n            </div>\n        </div>\n    );\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}