{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "scroll-card", "title": "Scroll Card", "description": "A scroll-linked card layout with synchronized image transitions and text reveals.", "dependencies": [ "motion" ], "files": [ { "path": "registry/new-york/components/scroll-card/scroll-card.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport Image from \"next/image\";\nimport { motion, MotionValue, useMotionValueEvent, useScroll, useSpring, useTransform } from \"motion/react\";\nimport { useRef, useState, type RefObject } from \"react\";\n\nexport interface ScrollCardFeature {\n title: string;\n description: string;\n image: string;\n}\n\nexport interface ScrollCardProps {\n features?: ScrollCardFeature[];\n className?: string;\n /** Height of the scroll container (default: \"500vh\") */\n scrollHeight?: string;\n /** Optional scrollable parent for embedded previews. */\n scrollContainer?: RefObject;\n}\n\nconst defaultFeatures: ScrollCardFeature[] = [\n {\n title: \"Secure Wallet Infrastructure\",\n description: \"Embedded wallets with MPC-based key management and recovery.\",\n image: \"/assets/dummy/Card1.png\",\n },\n {\n title: \"Passwordless Authentication\",\n description: \"Sign in with email or social accounts in just one click.\",\n image: \"/assets/dummy/Card2.png\",\n },\n {\n title: \"Developer-Friendly APIs\",\n description: \"Simple SDKs for wallet creation, signing, and user onboarding.\",\n image: \"/assets/dummy/Card3.png\",\n },\n {\n title: \"Enterprise-Grade Security\",\n description: \"Policy-based access controls with secure distributed custody.\",\n image: \"/assets/dummy/Card4.png\",\n },\n];\n\nexport default function ScrollCard({\n features = defaultFeatures,\n className,\n scrollHeight = \"500vh\",\n scrollContainer,\n}: ScrollCardProps) {\n const containerRef = useRef(null);\n\n const { scrollYProgress } = useScroll({\n container: scrollContainer,\n target: containerRef,\n offset: [\"start start\", \"end end\"],\n });\n\n const smoothProgress = useSpring(scrollYProgress, {\n stiffness: 320,\n damping: 42,\n mass: 0.7,\n restDelta: 0.0005,\n });\n\n return (\n \n
\n
\n
\n {features.map((feature, index) => (\n \n ))}\n
\n\n
\n {features.map((feature, index) => (\n \n ))}\n
\n
\n
\n \n );\n}\n\ninterface ScrollItemProps {\n feature: ScrollCardFeature;\n index: number;\n total: number;\n progress: MotionValue;\n}\n\nfunction useActiveValue(progress: MotionValue, index: number, total: number) {\n const sectionLength = 1 / total;\n const start = index * sectionLength;\n const end = (index + 1) * sectionLength;\n const slideStart = Math.max(0, start - sectionLength);\n const isFirst = index === 0;\n const isLast = index === total - 1;\n\n const inputs = isFirst\n ? [0, start, end]\n : isLast\n ? [slideStart, start, 1]\n : [slideStart, start, end];\n const outputs = isFirst\n ? [1, 1, 0]\n : isLast\n ? [0, 1, 1]\n : [0, 1, 0];\n\n const active = useTransform(progress, inputs, outputs);\n return { active, start, end, slideStart, isFirst, isLast };\n}\n\nfunction ScrollImage({ feature, index, total, progress }: ScrollItemProps) {\n const { start, end, slideStart, isFirst, isLast } = useActiveValue(progress, index, total);\n\n const y = useTransform(\n progress,\n isFirst ? [0, 1] : [slideStart, start],\n isFirst ? [\"0%\", \"0%\"] : [\"100%\", \"0%\"],\n );\n const scale = useTransform(progress, isLast ? [0, 1] : [start, end], isLast ? [1, 1] : [1, 0.7]);\n const hidePoint = Math.min(end + 0.001, 1);\n const opacity = useTransform(\n progress,\n isLast ? [0, 1] : [start, end, hidePoint, 1],\n isLast ? [1, 1] : [1, 0.2, 0, 0],\n );\n\n return (\n \n \n \n );\n}\n\nfunction TextItem({ feature, index, total, progress }: ScrollItemProps) {\n const { active } = useActiveValue(progress, index, total);\n const [isActive, setIsActive] = useState(() => active.get() > 0.5);\n\n useMotionValueEvent(active, \"change\", (v) => {\n setIsActive(v > 0.5);\n });\n\n const letterSpacing = useTransform(active, [0, 1], [\"-0.01em\", \"-0.02em\"]);\n const descHeight = useTransform(active, [0, 1], [0, 56]);\n const descOpacity = useTransform(active, [0, 0.6, 1], [0, 0, 1]);\n const descY = useTransform(active, [0, 1], [-6, 0]);\n\n return (\n
\n \n {feature.title}\n \n \n \n {feature.description}\n \n \n
\n );\n}\n\nexport { ScrollCard };\n" } ], "type": "registry:component" }