{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"deck-cycle","type":"registry:ui","title":"Deck Cycle","description":"A skewed 3D stack of cards that cycles on its own: the front card drops away and springs back in behind the others.","dependencies":["motion"],"registryDependencies":[],"files":[{"path":"registry/ui/deck-cycle.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\nexport interface DeckCycleProps {\n  cards: ReactNode[];\n  /** Card width in px. */\n  width?: number;\n  /** Card height in px. */\n  height?: number;\n  /** Horizontal px between stacked cards. */\n  distanceX?: number;\n  /** Vertical px between stacked cards. */\n  distanceY?: number;\n  /** Skew of the stack in degrees. */\n  skew?: number;\n  /** Ms between swaps. */\n  delay?: number;\n  pauseOnHover?: boolean;\n  /** `elastic` overshoots, `smooth` glides. */\n  easing?: \"elastic\" | \"smooth\";\n  className?: string;\n}\n\nexport function DeckCycle({\n  cards,\n  width = 340,\n  height = 240,\n  distanceX = 56,\n  distanceY = 56,\n  skew = 6,\n  delay = 3500,\n  pauseOnHover = true,\n  easing = \"elastic\",\n  className,\n}: DeckCycleProps) {\n  const n = cards.length;\n  const [order, setOrder] = useState(() => cards.map((_, i) => i));\n  const [dropping, setDropping] = useState(false);\n  const paused = useRef(false);\n  const busy = useRef(false);\n  const timer = useRef<number | undefined>(undefined);\n  const reduce = useReducedMotion();\n  const safeOrder = order.length === n ? order : cards.map((_, i) => i);\n  const dropMs = easing === \"elastic\" ? 650 : 500;\n\n  const swap = useCallback(() => {\n    if (busy.current || n < 2) return;\n    busy.current = true;\n    setDropping(true);\n    timer.current = window.setTimeout(() => {\n      setOrder((o) => (o.length === n ? [...o.slice(1), o[0]] : o));\n      setDropping(false);\n      busy.current = false;\n    }, dropMs);\n  }, [n, dropMs]);\n\n  useEffect(() => {\n    if (reduce) return;\n    const id = window.setInterval(() => !paused.current && swap(), delay);\n    return () => {\n      window.clearInterval(id);\n      window.clearTimeout(timer.current);\n      busy.current = false;\n    };\n  }, [delay, swap, reduce]);\n\n  const spring =\n    easing === \"elastic\"\n      ? { type: \"spring\" as const, stiffness: 170, damping: 13, mass: 1 }\n      : { type: \"spring\" as const, stiffness: 120, damping: 22 };\n\n  return (\n    <div\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Card stack. Press Enter or Right arrow for the next card.\"\n      tabIndex={0}\n      onPointerEnter={() => (paused.current = pauseOnHover)}\n      onPointerLeave={() => (paused.current = false)}\n      onKeyDown={(e) => {\n        if (e.key === \"Enter\" || e.key === \" \" || e.key === \"ArrowRight\") {\n          e.preventDefault();\n          swap();\n        }\n      }}\n      className={`relative rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring ${className ?? \"\"}`}\n      style={{ width: width + distanceX * (n - 1), height: height + distanceY * (n - 1), perspective: 1100 }}\n    >\n      <div className=\"absolute inset-0\" style={{ transformStyle: \"preserve-3d\" }}>\n        {safeOrder.map((card, slot) => {\n          const falling = dropping && slot === 0;\n          return (\n            <motion.div\n              key={card}\n              aria-hidden={slot !== 0 || undefined}\n              className=\"absolute bottom-0 left-0 overflow-hidden rounded-xl border border-border bg-card shadow-[0_30px_60px_-20px_rgba(0,0,0,.7)]\"\n              style={{ width, height, zIndex: n - slot, transformOrigin: \"50% 50%\" }}\n              initial={false}\n              animate={{\n                x: slot * distanceX,\n                y: falling ? height * 2.2 : -slot * distanceY,\n                z: -slot * distanceX * 1.4,\n                skewY: skew,\n                rotateZ: falling ? 4 : 0,\n              }}\n              transition={\n                falling\n                  ? { duration: dropMs / 1000, ease: [0.55, 0, 0.8, 0.3] }\n                  : { ...spring, delay: dropping ? 0 : slot === n - 1 ? 0.25 : slot * 0.08 }\n              }\n            >\n              {cards[card]}\n            </motion.div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n"}]}