{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card-deal",
  "type": "registry:component",
  "title": "Card Deal",
  "description": "A grid that deals itself out from one point like a hand of cards: each card flies to its slot tilted and settles flat, and a shuffle sweeps them back into the deck and deals a fresh order, even mid-flight.",
  "categories": [
    "motion",
    "Surfaces"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/card-deal.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { motion, type TargetAndTransition, type Transition, useInView, useReducedMotion } from \"motion/react\";\nimport { type ReactNode, type RefObject, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Offset = { x: number; y: number };\ntype Phase = \"deck\" | \"dealt\";\n\nexport interface CardDealProps<T> {\n  items: T[];\n  getKey: (item: T) => string;\n  renderItem: (item: T, index: number) => ReactNode;\n  origin?: RefObject<HTMLElement | null>;\n  stagger?: number;\n  className?: string;\n  slotClassName?: string;\n  label?: string;\n}\n\nfunction useCalmMotion() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!prefers;\n}\n\nfunction tilt(index: number, round: number) {\n  const n = Math.sin(index * 12.9898 + round * 78.233) * 43758.5453;\n  const unit = n - Math.floor(n);\n  return (index % 2 ? 1 : -1) * (5 + unit * 9);\n}\n\nconst fly = [0.55, 0, 0.25, 1] as const;\n\nexport function CardDeal<T>({\n  items,\n  getKey,\n  renderItem,\n  origin,\n  stagger = 0.05,\n  className,\n  slotClassName,\n  label = \"Cards\",\n}: CardDealProps<T>) {\n  const root = useRef<HTMLDivElement>(null);\n  const slots = useRef<(HTMLDivElement | null)[]>([]);\n  const reduce = useCalmMotion();\n  const inView = useInView(root, { once: true, amount: 0.4 });\n  const [shown, setShown] = useState(items);\n  const [phase, setPhase] = useState<Phase>(\"deck\");\n  const [round, setRound] = useState(0);\n  const [offsets, setOffsets] = useState<Offset[] | null>(null);\n  const [instant, setInstant] = useState(true);\n\n  const signature = items.map(getKey).join(\"|\");\n  const shownSignature = shown.map(getKey).join(\"|\");\n\n  useLayoutEffect(() => {\n    const container = root.current;\n    if (!container) return;\n    const measure = () => {\n      const box = container.getBoundingClientRect();\n      const scale = box.width / (container.offsetWidth || 1) || 1;\n      const anchor = origin?.current?.getBoundingClientRect();\n      const ox = anchor ? (anchor.left + anchor.width / 2 - box.left) / scale : container.offsetWidth / 2;\n      const oy = anchor ? (anchor.top + anchor.height / 2 - box.top) / scale : container.offsetHeight + 24;\n      setOffsets(\n        slots.current.map((slot) =>\n          slot\n            ? { x: ox - (slot.offsetLeft + slot.offsetWidth / 2), y: oy - (slot.offsetTop + slot.offsetHeight / 2) }\n            : { x: 0, y: 0 },\n        ),\n      );\n    };\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(container);\n    return () => observer.disconnect();\n  }, [origin, shown.length]);\n\n  useEffect(() => {\n    if (!inView || !offsets) return;\n    if (signature === shownSignature) {\n      const frame = requestAnimationFrame(() => {\n        setInstant(false);\n        setPhase(\"dealt\");\n      });\n      return () => cancelAnimationFrame(frame);\n    }\n    setPhase(\"deck\");\n    const wait = reduce ? 160 : 320;\n    const timer = setTimeout(() => {\n      setShown(items);\n      setRound((r) => r + 1);\n      setPhase(\"dealt\");\n    }, wait);\n    return () => clearTimeout(timer);\n  }, [inView, offsets === null, signature, shownSignature, reduce]);\n\n  const count = shown.length;\n\n  return (\n    <div aria-busy={phase === \"deck\"} aria-label={label} className={cn(\"relative grid grid-cols-3 gap-3\", className)} ref={root} role=\"list\">\n      {shown.map((item, i) => {\n        const offset = offsets?.[i] ?? { x: 0, y: 0 };\n        const gathered: TargetAndTransition = reduce\n          ? { x: 0, y: 0, rotate: 0, scale: 1, opacity: 0 }\n          : { x: offset.x, y: offset.y, rotate: tilt(i, round + 1), scale: 0.5, opacity: 0 };\n        const dealt: TargetAndTransition = { x: 0, y: 0, rotate: 0, scale: 1, opacity: 1 };\n        const out = i * stagger;\n        const transition: Transition = instant\n          ? { duration: 0 }\n          : reduce\n            ? { duration: 0.18, delay: phase === \"dealt\" ? i * 0.03 : 0 }\n            : phase === \"dealt\"\n              ? {\n                  x: { type: \"spring\", stiffness: 210, damping: 26, delay: out },\n                  y: { type: \"spring\", stiffness: 210, damping: 26, delay: out },\n                  rotate: { type: \"spring\", stiffness: 170, damping: 17, delay: out },\n                  scale: { type: \"spring\", stiffness: 260, damping: 24, delay: out },\n                  opacity: { duration: 0.12, ease: \"easeOut\", delay: out },\n                }\n              : {\n                  x: { duration: 0.3, ease: fly },\n                  y: { duration: 0.3, ease: fly },\n                  rotate: { duration: 0.3, ease: fly },\n                  scale: { duration: 0.3, ease: fly },\n                  opacity: { duration: 0.1, ease: \"linear\", delay: 0.2 },\n                };\n        return (\n          <div\n            className={cn(\"relative\", slotClassName)}\n            key={i}\n            ref={(el) => {\n              slots.current[i] = el;\n            }}\n            role=\"listitem\"\n            style={{ zIndex: phase === \"dealt\" ? i + 1 : count - i }}\n          >\n            <motion.div\n              animate={phase === \"dealt\" && offsets ? dealt : gathered}\n              className=\"h-full origin-center will-change-transform\"\n              initial={{ opacity: 0 }}\n              transition={transition}\n            >\n              {renderItem(item, i)}\n            </motion.div>\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
    }
  ]
}