{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card-stack-animated",
  "type": "registry:ui",
  "title": "CardStackAnimated",
  "description": "Optional directional Card Stack handoff with Motion, reduced-motion support, and shared accessible navigation.",
  "dependencies": [
    "motion@^12.43.0"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/card-stack.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/card-stack-animated/card-stack-animated.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/card-stack-animated/card-stack-animated.tsx",
      "content": "import {\n  createContext,\n  forwardRef,\n  useContext,\n  useEffect,\n  useRef,\n} from \"react\";\nimport { useReducedMotion } from \"motion/react\";\nimport { animate } from \"motion/mini\";\nimport { CardStack, type CardStackProps } from \"../card-stack\";\nimport {\n  CardStackRendererContext,\n  type CardStackDeckProps,\n  type CardStackFrameProps,\n} from \"../card-stack/card-stack-renderer\";\n\nexport interface CardStackAnimatedProps extends CardStackProps {\n  /** Opt in to horizontal touch/pen navigation. Buttons remain available. */\n  swipe?: boolean;\n}\n\nconst SwipeContext = createContext(false);\n\nfunction AnimatedDeck({ navigation, style, ...props }: CardStackDeckProps) {\n  const swipe = useContext(SwipeContext);\n  const gesture = useRef<{\n    id: number;\n    x: number;\n    y: number;\n    horizontal: boolean;\n  } | null>(null);\n  const suppressClick = useRef(false);\n  useEffect(() => {\n    gesture.current = null;\n  }, [navigation.activeIndex, swipe]);\n  return (\n    <div\n      {...props}\n      style={{\n        ...style,\n        ...(swipe ? { touchAction: \"pan-y pinch-zoom\" } : {}),\n      }}\n      onPointerDown={(event) => {\n        suppressClick.current = false;\n        gesture.current = null;\n        if (\n          !swipe ||\n          !event.isPrimary ||\n          event.pointerType === \"mouse\" ||\n          event.button !== 0\n        )\n          return;\n        if (\n          (event.target as Element).closest(\n            \"a,button,input,textarea,select,[contenteditable],[role='button'],[data-card-stack-no-swipe]\",\n          )\n        )\n          return;\n        if (window.getSelection()?.isCollapsed === false) return;\n        gesture.current = {\n          id: event.pointerId,\n          x: event.clientX,\n          y: event.clientY,\n          horizontal: false,\n        };\n      }}\n      onPointerMove={(event) => {\n        const start = gesture.current;\n        if (!start || start.id !== event.pointerId) return;\n        const dx = event.clientX - start.x;\n        const dy = event.clientY - start.y;\n        if (\n          window.getSelection()?.isCollapsed === false ||\n          Math.abs(dy) > Math.max(12, Math.abs(dx))\n        ) {\n          gesture.current = null;\n          return;\n        }\n        if (Math.abs(dx) > 12 && Math.abs(dx) > Math.abs(dy) * 1.5)\n          start.horizontal = true;\n      }}\n      onPointerUp={(event) => {\n        const start = gesture.current;\n        gesture.current = null;\n        if (\n          !start ||\n          start.id !== event.pointerId ||\n          !start.horizontal ||\n          window.getSelection()?.isCollapsed === false\n        )\n          return;\n        const dx = event.clientX - start.x;\n        const dy = event.clientY - start.y;\n        const threshold = Math.max(40, event.currentTarget.clientWidth * 0.16);\n        if (Math.abs(dx) < threshold || Math.abs(dx) < Math.abs(dy) * 1.5)\n          return;\n        suppressClick.current = true;\n        const rtl = getComputedStyle(event.currentTarget).direction === \"rtl\";\n        if (dx < 0 !== rtl) navigation.next();\n        else navigation.previous();\n      }}\n      onPointerCancel={() => {\n        gesture.current = null;\n      }}\n      onLostPointerCapture={() => {\n        gesture.current = null;\n      }}\n      onClickCapture={(event) => {\n        if (suppressClick.current) {\n          event.preventDefault();\n          event.stopPropagation();\n          suppressClick.current = false;\n        }\n      }}\n    />\n  );\n}\n\nfunction AnimatedFrame({ frame, style, ...props }: CardStackFrameProps) {\n  const elementRef = useRef<HTMLDivElement>(null);\n  const reducedMotion = useReducedMotion();\n  const wasActive = useRef(frame.active);\n\n  useEffect(() => {\n    const element = elementRef.current;\n    const changed = wasActive.current !== frame.active;\n    const leaving = wasActive.current && !frame.active;\n    wasActive.current = frame.active;\n    if (\n      !element ||\n      !element.animate ||\n      !changed ||\n      reducedMotion ||\n      !frame.visible\n    )\n      return;\n    const direction =\n      frame.direction *\n      (getComputedStyle(element).direction === \"rtl\" ? -1 : 1);\n    let cancelled = false;\n    const resting = \"translateX(0px) rotate(0deg) scale(1)\";\n    const controls = animate(\n      element,\n      {\n        transform: leaving\n          ? [\n              resting,\n              `translateX(${-direction * 42}px) rotate(${-direction * 4}deg) scale(0.97)`,\n              resting,\n            ]\n          : [\n              `translateX(${direction * 24}px) rotate(${direction * 2}deg) scale(0.98)`,\n              resting,\n            ],\n        opacity: leaving ? [1, 0.65, 1] : [0.8, 1],\n      },\n      {\n        duration: 0.28,\n        ease: [0.22, 1, 0.36, 1],\n        ...(leaving ? { times: [0, 0.45, 1] } : {}),\n      },\n    );\n    void controls.then(() => {\n      if (cancelled) return;\n      element.style.transform = \"\";\n      element.style.opacity = \"\";\n    });\n    return () => {\n      cancelled = true;\n      controls.stop();\n      element.style.transform = \"\";\n      element.style.opacity = \"\";\n    };\n  }, [\n    frame.active,\n    frame.visible,\n    frame.direction,\n    frame.count,\n    reducedMotion,\n  ]);\n\n  return <div {...props} ref={elementRef} style={style} />;\n}\n\nconst renderer = { Frame: AnimatedFrame, Deck: AnimatedDeck };\n\n/** Optional Motion-backed presentation; base CardStack retains CSS-only rendering. */\nexport const CardStackAnimated = forwardRef<\n  HTMLDivElement,\n  CardStackAnimatedProps\n>(({ swipe = false, ...props }, ref) => (\n  <SwipeContext.Provider value={swipe}>\n    <CardStackRendererContext.Provider value={renderer}>\n      <CardStack {...props} ref={ref} />\n    </CardStackRendererContext.Provider>\n  </SwipeContext.Provider>\n));\nCardStackAnimated.displayName = \"CardStackAnimated\";\n"
    },
    {
      "path": "packages/components/src/components/card-stack-animated/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/card-stack-animated/index.ts",
      "content": "export {\n  CardStackAnimated,\n  type CardStackAnimatedProps,\n} from \"./card-stack-animated\";\n"
    }
  ]
}
