{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scrollable-card-stack",
  "type": "registry:block",
  "title": "Scrollable card stack",
  "description": "Scrollable card stack",
  "files": [
    {
      "path": "components/usages/scrollablecardstackusage.tsx",
      "content": "\"use client\";\r\n\r\nimport ScrollableCardStack from \"@/registry/open-source/scrollable-card-stack\";\r\n\r\nexport default function ScrollableCardStackDemo() {\r\n\tconst cardData = [\r\n\t\t{\r\n\t\t\tid: \"siriorb\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"richpopover\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"sparkbites\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"svgl\",\r\n\t\t\tname: \"Pheralb\",\r\n\t\t\thandle: \"@pheralb\",\r\n\t\t\tavatar: \"https://github.com/pheralb.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/pheralb\",\r\n\t\t},\r\n\t];\r\n\r\n\treturn (\r\n\t\t<div className=\"mx-auto w-full max-w-md\">\r\n\t\t\t<ScrollableCardStack\r\n\t\t\t\titems={cardData}\r\n\t\t\t\tcardHeight={384}\r\n\t\t\t\tperspective={1000}\r\n\t\t\t\ttransitionDuration={180}\r\n\t\t\t\tclassName=\"mx-auto\"\r\n\t\t\t/>\r\n\t\t</div>\r\n\t);\r\n}\r\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/scrollablecardstackusage.tsx",
      "content": "\"use client\";\r\n\r\nimport ScrollableCardStack from \"@/registry/open-source/scrollable-card-stack\";\r\n\r\nexport default function ScrollableCardStackDemo() {\r\n\tconst cardData = [\r\n\t\t{\r\n\t\t\tid: \"siriorb\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"richpopover\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"sparkbites\",\r\n\t\t\tname: \"Edu Calvo\",\r\n\t\t\thandle: \"@educalvolpz\",\r\n\t\t\tavatar: \"https://github.com/educlopez.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/educalvolpz\",\r\n\t\t},\r\n\t\t{\r\n\t\t\tid: \"svgl\",\r\n\t\t\tname: \"Pheralb\",\r\n\t\t\thandle: \"@pheralb\",\r\n\t\t\tavatar: \"https://github.com/pheralb.png\",\r\n\t\t\tvideo: \"/placeholder.mp4\",\r\n\t\t\thref: \"https://x.com/pheralb\",\r\n\t\t},\r\n\t];\r\n\r\n\treturn (\r\n\t\t<div className=\"mx-auto w-full max-w-md\">\r\n\t\t\t<ScrollableCardStack\r\n\t\t\t\titems={cardData}\r\n\t\t\t\tcardHeight={384}\r\n\t\t\t\tperspective={1000}\r\n\t\t\t\ttransitionDuration={180}\r\n\t\t\t\tclassName=\"mx-auto\"\r\n\t\t\t/>\r\n\t\t</div>\r\n\t);\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/scrollable-card-stack.tsx",
      "content": "\"use client\";\r\n\r\nimport { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport { motion, useMotionValue, useSpring } from \"motion/react\";\r\n\r\ninterface CardItem {\r\n\tid: string;\r\n\tname: string;\r\n\thandle: string;\r\n\tavatar: string;\r\n\tvideo: string;\r\n\thref: string;\r\n}\r\n\r\ninterface ScrollableCardStackProps {\r\n\titems: CardItem[];\r\n\tcardHeight?: number;\r\n\tperspective?: number;\r\n\ttransitionDuration?: number;\r\n\tclassName?: string;\r\n}\r\n\r\nconst ScrollableCardStack: React.FC<ScrollableCardStackProps> = ({\r\n\titems,\r\n\tcardHeight = 384,\r\n\tperspective = 1000,\r\n\ttransitionDuration = 180, // Reduced from 300ms for snappier feel\r\n\tclassName,\r\n}) => {\r\n\tconst [currentIndex, setCurrentIndex] = useState(0);\r\n\tconst [isDragging, setIsDragging] = useState(false);\r\n\tconst [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\r\n\tconst [isScrolling, setIsScrolling] = useState(false);\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\tconst scrollY = useMotionValue(0);\r\n\tconst lastScrollTime = useRef(0);\r\n\r\n\t// Optimized spring config for snappy animations\r\n\tconst springConfig = { damping: 30, stiffness: 500 }; // Increased stiffness for more responsive feel\r\n\r\n\t// Calculate the total number of items\r\n\tconst totalItems = items.length;\r\n\tconst maxIndex = totalItems - 1;\r\n\r\n\t// Spring-based scroll for smooth animations\r\n\tconst springScrollY = useSpring(scrollY, springConfig);\r\n\r\n\t// Controlled scroll function to move exactly one card\r\n\tconst scrollToCard = useCallback(\r\n\t\t(direction: 1 | -1) => {\r\n\t\t\tif (isScrolling) return; // Prevent multiple scrolls while one is in progress\r\n\r\n\t\t\tconst now = Date.now();\r\n\t\t\tconst timeSinceLastScroll = now - lastScrollTime.current;\r\n\t\t\tconst minScrollInterval = 300; // Minimum 300ms between scrolls\r\n\r\n\t\t\tif (timeSinceLastScroll < minScrollInterval) {\r\n\t\t\t\treturn; // Throttle rapid scrolls\r\n\t\t\t}\r\n\r\n\t\t\tconst newIndex = Math.max(\r\n\t\t\t\t0,\r\n\t\t\t\tMath.min(maxIndex, currentIndex + direction)\r\n\t\t\t);\r\n\r\n\t\t\tif (newIndex !== currentIndex) {\r\n\t\t\t\tlastScrollTime.current = now;\r\n\t\t\t\tsetIsScrolling(true);\r\n\t\t\t\tsetCurrentIndex(newIndex);\r\n\t\t\t\tscrollY.set(newIndex * 18);\r\n\r\n\t\t\t\t// Reset scrolling state after animation completes\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tsetIsScrolling(false);\r\n\t\t\t\t}, transitionDuration + 100); // Slightly longer timeout for better reliability\r\n\t\t\t}\r\n\t\t},\r\n\t\t[currentIndex, maxIndex, scrollY, isScrolling, transitionDuration]\r\n\t);\r\n\r\n\t// Handle scroll events with improved responsiveness\r\n\tconst handleScroll = useCallback(\r\n\t\t(deltaY: number) => {\r\n\t\t\tif (isDragging || isScrolling) return;\r\n\r\n\t\t\t// Add minimum threshold to prevent accidental scrolls from tiny movements\r\n\t\t\tconst minScrollThreshold = 20;\r\n\t\t\tif (Math.abs(deltaY) < minScrollThreshold) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst scrollDirection = deltaY > 0 ? 1 : -1;\r\n\t\t\tscrollToCard(scrollDirection);\r\n\t\t},\r\n\t\t[isDragging, isScrolling, scrollToCard]\r\n\t);\r\n\r\n\t// Handle wheel events - simplified without debouncing\r\n\tconst handleWheel = useCallback(\r\n\t\t(e: WheelEvent) => {\r\n\t\t\te.preventDefault();\r\n\t\t\thandleScroll(e.deltaY);\r\n\t\t},\r\n\t\t[handleScroll]\r\n\t);\r\n\r\n\t// Handle keyboard navigation\r\n\tconst handleKeyDown = useCallback(\r\n\t\t(e: React.KeyboardEvent) => {\r\n\t\t\tif (isScrolling) return;\r\n\r\n\t\t\tswitch (e.key) {\r\n\t\t\t\tcase \"ArrowUp\":\r\n\t\t\t\t\te.preventDefault();\r\n\t\t\t\t\tif (currentIndex > 0) {\r\n\t\t\t\t\t\tscrollToCard(-1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"ArrowDown\":\r\n\t\t\t\t\te.preventDefault();\r\n\t\t\t\t\tif (currentIndex < maxIndex) {\r\n\t\t\t\t\t\tscrollToCard(1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"Home\":\r\n\t\t\t\t\te.preventDefault();\r\n\t\t\t\t\tif (currentIndex !== 0) {\r\n\t\t\t\t\t\tsetIsScrolling(true);\r\n\t\t\t\t\t\tsetCurrentIndex(0);\r\n\t\t\t\t\t\tscrollY.set(0);\r\n\t\t\t\t\t\tsetTimeout(\r\n\t\t\t\t\t\t\t() => setIsScrolling(false),\r\n\t\t\t\t\t\t\ttransitionDuration + 100\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"End\":\r\n\t\t\t\t\te.preventDefault();\r\n\t\t\t\t\tif (currentIndex !== maxIndex) {\r\n\t\t\t\t\t\tsetIsScrolling(true);\r\n\t\t\t\t\t\tsetCurrentIndex(maxIndex);\r\n\t\t\t\t\t\tscrollY.set(maxIndex * 18);\r\n\t\t\t\t\t\tsetTimeout(\r\n\t\t\t\t\t\t\t() => setIsScrolling(false),\r\n\t\t\t\t\t\t\ttransitionDuration + 100\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t},\r\n\t\t[\r\n\t\t\tcurrentIndex,\r\n\t\t\tmaxIndex,\r\n\t\t\tscrollY,\r\n\t\t\tisScrolling,\r\n\t\t\tscrollToCard,\r\n\t\t\ttransitionDuration,\r\n\t\t]\r\n\t);\r\n\r\n\t// Handle touch events for mobile with improved gesture detection\r\n\tconst touchStartY = useRef(0);\r\n\tconst touchStartIndex = useRef(0);\r\n\tconst touchStartTime = useRef(0);\r\n\tconst touchMoved = useRef(false);\r\n\r\n\tconst handleTouchStart = useCallback(\r\n\t\t(e: React.TouchEvent) => {\r\n\t\t\ttouchStartY.current = e.touches[0].clientY;\r\n\t\t\ttouchStartIndex.current = currentIndex;\r\n\t\t\ttouchStartTime.current = Date.now();\r\n\t\t\ttouchMoved.current = false;\r\n\t\t\tsetIsDragging(true);\r\n\t\t},\r\n\t\t[currentIndex]\r\n\t);\r\n\r\n\tconst handleTouchMove = useCallback(\r\n\t\t(e: React.TouchEvent) => {\r\n\t\t\tif (!isDragging || isScrolling) return;\r\n\r\n\t\t\tconst touchY = e.touches[0].clientY;\r\n\t\t\tconst deltaY = touchStartY.current - touchY;\r\n\t\t\tconst scrollThreshold = 100; // Increased threshold for more intentional swipes\r\n\r\n\t\t\tif (Math.abs(deltaY) > scrollThreshold && !touchMoved.current) {\r\n\t\t\t\tconst scrollDirection = deltaY > 0 ? 1 : -1;\r\n\t\t\t\tscrollToCard(scrollDirection);\r\n\t\t\t\ttouchMoved.current = true; // Prevent multiple movements in one gesture\r\n\t\t\t}\r\n\t\t},\r\n\t\t[isDragging, isScrolling, scrollToCard]\r\n\t);\r\n\r\n\tconst handleTouchEnd = useCallback(() => {\r\n\t\tsetIsDragging(false);\r\n\t\ttouchMoved.current = false;\r\n\t}, []);\r\n\r\n\t// Set up event listeners\r\n\tuseEffect(() => {\r\n\t\tconst container = containerRef.current;\r\n\t\tif (!container) return;\r\n\r\n\t\tcontainer.addEventListener(\"wheel\", handleWheel, { passive: false });\r\n\r\n\t\treturn () => {\r\n\t\t\tcontainer.removeEventListener(\"wheel\", handleWheel);\r\n\t\t};\r\n\t}, [handleWheel]);\r\n\r\n\t// Snap to current index when not dragging\r\n\tuseEffect(() => {\r\n\t\tif (!isDragging) {\r\n\t\t\tscrollY.set(currentIndex * 18);\r\n\t\t}\r\n\t}, [currentIndex, isDragging, scrollY]);\r\n\r\n\t// Calculate which cards should be visible based on current index and total items\r\n\tconst getVisibleCards = useCallback(() => {\r\n\t\t// Always show all cards, but manage opacity and visibility through transforms\r\n\t\t// This prevents the weird behavior of cards appearing from the right\r\n\t\treturn items;\r\n\t}, [items]);\r\n\r\n\t// Get the adjusted current index for visible cards\r\n\tconst getAdjustedCurrentIndex = useCallback(() => {\r\n\t\t// Since we always show all cards, the current index is simply the currentIndex\r\n\t\treturn currentIndex;\r\n\t}, [currentIndex]);\r\n\r\n\t// Calculate transform for each card based on the example\r\n\tconst getCardTransform = useCallback(\r\n\t\t(index: number, visibleItems: CardItem[]) => {\r\n\t\t\tconst adjustedCurrentIndex = getAdjustedCurrentIndex();\r\n\t\t\tconst distance = index - adjustedCurrentIndex;\r\n\t\t\tconst absDistance = Math.abs(distance);\r\n\r\n\t\t\t// Scale values from the example: 1, 0.94, 0.88, 0.82, 0.76, 0.7, 0.64\r\n\t\t\tconst scaleValues = [1, 0.94, 0.88, 0.82, 0.76, 0.7, 0.64];\r\n\t\t\tconst scale = scaleValues[absDistance] || 0.64;\r\n\r\n\t\t\t// Vertical offset: 0, -18px, -36px, -54px, -72px, -90px, -108px\r\n\t\t\tconst translateY = -18 * absDistance;\r\n\r\n\t\t\t// Simplified opacity logic - only hide cards that are too far away\r\n\t\t\tlet opacity = 1;\r\n\t\t\tif (absDistance >= 6) {\r\n\t\t\t\topacity = 0;\r\n\t\t\t} else if (absDistance >= 4) {\r\n\t\t\t\topacity = 0.3; // Very faint for distant cards\r\n\t\t\t} else if (absDistance >= 2) {\r\n\t\t\t\topacity = 0.6; // Semi-transparent for cards further away\r\n\t\t\t}\r\n\r\n\t\t\t// Z-index: 10, 9, 8, 7, 6, 5, 4\r\n\t\t\tconst zIndex = 10 - absDistance;\r\n\r\n\t\t\treturn {\r\n\t\t\t\ttranslateY,\r\n\t\t\t\tscale,\r\n\t\t\t\topacity,\r\n\t\t\t\tzIndex,\r\n\t\t\t};\r\n\t\t},\r\n\t\t[getAdjustedCurrentIndex]\r\n\t);\r\n\r\n\tconst visibleCards = getVisibleCards();\r\n\r\n\treturn (\r\n\t\t<section\r\n\t\t\tref={containerRef}\r\n\t\t\tclassName={cn(\r\n\t\t\t\t\"relative mx-auto h-fit min-h-[200px] w-fit min-w-[300px]\",\r\n\t\t\t\tclassName\r\n\t\t\t)}\r\n\t\t\tstyle={{\r\n\t\t\t\tperspective: `${perspective}px`,\r\n\t\t\t\tperspectiveOrigin: \"center 60%\",\r\n\t\t\t\ttouchAction: \"none\",\r\n\t\t\t}}\r\n\t\t\tonTouchStart={handleTouchStart}\r\n\t\t\tonTouchMove={handleTouchMove}\r\n\t\t\tonTouchEnd={handleTouchEnd}\r\n\t\t\tonKeyDown={handleKeyDown}\r\n\t\t\taria-live=\"polite\"\r\n\t\t\taria-atomic=\"true\"\r\n\t\t\taria-label=\"Scrollable card stack\"\r\n\t\t>\r\n\t\t\t{visibleCards.map((item, i) => {\r\n\t\t\t\tconst adjustedCurrentIndex = getAdjustedCurrentIndex();\r\n\t\t\t\tconst transform = getCardTransform(i, visibleCards);\r\n\t\t\t\tconst isActive = i === adjustedCurrentIndex;\r\n\t\t\t\tconst isHovered = hoveredIndex === i;\r\n\r\n\t\t\t\treturn (\r\n\t\t\t\t\t<motion.div\r\n\t\t\t\t\t\tkey={`scrollable-card-${item.id}`}\r\n\t\t\t\t\t\tclassName=\"bg-background absolute top-1/2 left-1/2 h-max w-max max-w-[100vw] overflow-hidden rounded-2xl border shadow-lg transition-shadow duration-200\"\r\n\t\t\t\t\t\tdata-active={isActive}\r\n\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\tzIndex: transform.zIndex,\r\n\t\t\t\t\t\t\tpointerEvents: isActive ? \"auto\" : \"none\",\r\n\t\t\t\t\t\t\ttransformOrigin: \"center center\",\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t\tanimate={{\r\n\t\t\t\t\t\t\topacity: transform.opacity,\r\n\t\t\t\t\t\t\tx: \"-50%\",\r\n\t\t\t\t\t\t\ty: `calc(-50% + ${transform.translateY}px)`,\r\n\t\t\t\t\t\t\tscale: transform.scale,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t\twhileHover={\r\n\t\t\t\t\t\t\tisActive\r\n\t\t\t\t\t\t\t\t? {\r\n\t\t\t\t\t\t\t\t\t\tscale: transform.scale * 1.02,\r\n\t\t\t\t\t\t\t\t\t\ttransition: {\r\n\t\t\t\t\t\t\t\t\t\t\tduration: 0.15,\r\n\t\t\t\t\t\t\t\t\t\t\tease: [0.22, 1, 0.36, 1],\r\n\t\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t: {}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\ttransition={{\r\n\t\t\t\t\t\t\tduration: transitionDuration / 1000,\r\n\t\t\t\t\t\t\tease: [0.22, 1, 0.36, 1], // ease-out-quint\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t\taria-hidden={!isActive}\r\n\t\t\t\t\t\ttabIndex={isActive ? 0 : -1}\r\n\t\t\t\t\t\tonMouseEnter={() => isActive && setHoveredIndex(i)}\r\n\t\t\t\t\t\tonMouseLeave={() => setHoveredIndex(null)}\r\n\t\t\t\t\t\tonFocus={() => isActive && setHoveredIndex(i)}\r\n\t\t\t\t\t\tonBlur={() => setHoveredIndex(null)}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t{/* Card Content */}\r\n\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\tclassName={cn(\r\n\t\t\t\t\t\t\t\t\"bg-background flex h-fit w-96 flex-col items-center rounded-xl transition-all duration-200\",\r\n\t\t\t\t\t\t\t\tisHovered && \"shadow-xl\",\r\n\t\t\t\t\t\t\t\tisScrolling &&\r\n\t\t\t\t\t\t\t\t\tisActive &&\r\n\t\t\t\t\t\t\t\t\t\"ring-opacity-50 ring-brand ring-2\"\r\n\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t{/* Scroll indicator - shows when throttling is active */}\r\n\t\t\t\t\t\t\t{isScrolling && isActive && (\r\n\t\t\t\t\t\t\t\t<div className=\"absolute -top-1 left-1/2 h-1 w-8 -translate-x-1/2 rounded-full bg-blue-200 opacity-75\" />\r\n\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t{/* Video Container */}\r\n\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\tclassName=\"relative w-full overflow-hidden\"\r\n\t\t\t\t\t\t\t\tstyle={{ aspectRatio: \"1.77778 / 1\" }}\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t{/* Background blur image */}\r\n\t\t\t\t\t\t\t\t<img\r\n\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\r\n\t\t\t\t\t\t\t\t\talt=\"\"\r\n\t\t\t\t\t\t\t\t\tdecoding=\"async\"\r\n\t\t\t\t\t\t\t\t\tsrc=\"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwIiBoZWlnaHQ9IjEwIiBmaWxsPSIjZjNmNGY2Ii8+PC9zdmc+\"\r\n\t\t\t\t\t\t\t\t\tclassName=\"absolute inset-0 h-full w-full object-cover text-transparent\"\r\n\t\t\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\t\t\tfilter: \"blur(32px)\",\r\n\t\t\t\t\t\t\t\t\t\tscale: \"1.2\",\r\n\t\t\t\t\t\t\t\t\t\tzIndex: 1,\r\n\t\t\t\t\t\t\t\t\t\tpointerEvents: \"none\",\r\n\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t{/* Video */}\r\n\t\t\t\t\t\t\t\t<video\r\n\t\t\t\t\t\t\t\t\tautoPlay\r\n\t\t\t\t\t\t\t\t\tloop\r\n\t\t\t\t\t\t\t\t\tplaysInline\r\n\t\t\t\t\t\t\t\t\tmuted\r\n\t\t\t\t\t\t\t\t\tsrc={item.video}\r\n\t\t\t\t\t\t\t\t\tclassName=\"absolute inset-0 h-full w-full object-cover\"\r\n\t\t\t\t\t\t\t\t\tstyle={{ zIndex: 2 }}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t\t\t{/* Header */}\r\n\t\t\t\t\t\t\t<a\r\n\t\t\t\t\t\t\t\tclassName={cn(\r\n\t\t\t\t\t\t\t\t\t\"text-decoration-none flex items-center gap-1 p-3 text-inherit transition-colors duration-200\"\r\n\t\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\t\thref={item.href}\r\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\r\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\r\n\t\t\t\t\t\t\t\taria-label={`View ${item.name}'s profile`}\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t<img\r\n\t\t\t\t\t\t\t\t\tclassName=\"mr-1 h-5 w-5 overflow-hidden rounded-full\"\r\n\t\t\t\t\t\t\t\t\talt={`${item.name}'s avatar`}\r\n\t\t\t\t\t\t\t\t\twidth={20}\r\n\t\t\t\t\t\t\t\t\theight={20}\r\n\t\t\t\t\t\t\t\t\tsrc={item.avatar}\r\n\t\t\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\t\t\tboxShadow:\r\n\t\t\t\t\t\t\t\t\t\t\t\"0 0 0 1px var(--border-secondary, #e0e0e0)\",\r\n\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t<span className=\"text-foreground text-sm leading-none font-medium\">\r\n\t\t\t\t\t\t\t\t\t{item.name}\r\n\t\t\t\t\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<span className=\"text-foreground/70 text-sm font-normal\">\r\n\t\t\t\t\t\t\t\t\t{item.handle}\r\n\t\t\t\t\t\t\t\t</span>\r\n\t\t\t\t\t\t\t</a>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</motion.div>\r\n\t\t\t\t);\r\n\t\t\t})}\r\n\r\n\t\t\t{/* Navigation indicators */}\r\n\t\t\t<div\r\n\t\t\t\tclassName=\"absolute bottom-4 left-1/2 flex -translate-x-1/2 transform space-x-2\"\r\n\t\t\t\trole=\"tablist\"\r\n\t\t\t\taria-label=\"Card navigation\"\r\n\t\t\t>\r\n\t\t\t\t{Array.from({ length: items.length }, (_, i) => {\r\n\t\t\t\t\tconst visibleItems = getVisibleCards();\r\n\t\t\t\t\tconst isVisible = i < visibleItems.length;\r\n\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t<motion.button\r\n\t\t\t\t\t\t\tkey={`scrollable-indicator-${items[i]?.id || i}`}\r\n\t\t\t\t\t\t\ttype=\"button\"\r\n\t\t\t\t\t\t\tonClick={() => {\r\n\t\t\t\t\t\t\t\tif (i !== currentIndex && !isScrolling) {\r\n\t\t\t\t\t\t\t\t\tsetIsScrolling(true);\r\n\t\t\t\t\t\t\t\t\tsetCurrentIndex(i);\r\n\t\t\t\t\t\t\t\t\tscrollY.set(i * 18);\r\n\t\t\t\t\t\t\t\t\tsetTimeout(\r\n\t\t\t\t\t\t\t\t\t\t() => setIsScrolling(false),\r\n\t\t\t\t\t\t\t\t\t\ttransitionDuration + 100\r\n\t\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\tclassName={cn(\r\n\t\t\t\t\t\t\t\t\"h-2 w-2 rounded-full transition-all duration-200 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-hidden\",\r\n\t\t\t\t\t\t\t\ti === currentIndex\r\n\t\t\t\t\t\t\t\t\t? \"scale-125 bg-blue-500\"\r\n\t\t\t\t\t\t\t\t\t: isVisible\r\n\t\t\t\t\t\t\t\t\t\t? \"bg-background hover:bg-background\"\r\n\t\t\t\t\t\t\t\t\t\t: \"bg-background opacity-50\"\r\n\t\t\t\t\t\t\t)}\r\n\t\t\t\t\t\t\twhileHover={{ scale: 1.2 }}\r\n\t\t\t\t\t\t\twhileTap={{ scale: 0.9 }}\r\n\t\t\t\t\t\t\ttransition={{ duration: 0.1, ease: [0.22, 1, 0.36, 1] }}\r\n\t\t\t\t\t\t\trole=\"tab\"\r\n\t\t\t\t\t\t\taria-selected={i === currentIndex}\r\n\t\t\t\t\t\t\taria-label={`Go to card ${i + 1} of ${items.length}`}\r\n\t\t\t\t\t\t\tdisabled={!isVisible}\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t);\r\n\t\t\t\t})}\r\n\t\t\t</div>\r\n\r\n\t\t\t{/* Instructions for screen readers */}\r\n\t\t\t<div className=\"sr-only\" aria-live=\"polite\">\r\n\t\t\t\t{`Card ${currentIndex + 1} of ${items.length} selected. Use arrow keys to navigate one card at a time, or click the dots below.`}\r\n\t\t\t</div>\r\n\t\t</section>\r\n\t);\r\n};\r\n\r\nexport default ScrollableCardStack;\r\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"
    }
  ]
}