{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card-stack",
  "type": "registry:ui",
  "title": "CardStack",
  "description": "Responsive Card deck with stack and open fan modes, keyboard focus continuity, position announcements, inert inactive cards, and reduced-motion-aware CSS transitions.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json",
    "https://components.dethink.co.uk/r/card.json",
    "https://components.dethink.co.uk/r/icon-button.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/card-stack/card-stack-renderer.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/card-stack/card-stack-renderer.tsx",
      "content": "import { createContext, type ComponentType, type HTMLAttributes } from \"react\";\n\n/** Internal rendering seam shared by the CSS and optional animated variants. */\nexport interface CardStackFrameProps extends HTMLAttributes<HTMLDivElement> {\n  inert?: boolean;\n  frame: {\n    active: boolean;\n    visible: boolean;\n    direction: number;\n    count: number;\n  };\n}\n\nexport interface CardStackDeckProps extends HTMLAttributes<HTMLDivElement> {\n  navigation: { previous: () => void; next: () => void; activeIndex: number };\n}\n\nexport const CardStackRendererContext = createContext<{\n  Frame: ComponentType<CardStackFrameProps>;\n  Deck: ComponentType<CardStackDeckProps>;\n} | null>(null);\n\nexport function StaticCardStackFrame({\n  frame: _frame,\n  ...props\n}: CardStackFrameProps) {\n  void _frame;\n  return <div {...props} />;\n}\n\nexport function StaticCardStackDeck({\n  navigation: _navigation,\n  ...props\n}: CardStackDeckProps) {\n  void _navigation;\n  return <div {...props} />;\n}\n"
    },
    {
      "path": "packages/components/src/components/card-stack/card-stack.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/card-stack/card-stack.tsx",
      "content": "import {\n  Children,\n  cloneElement,\n  forwardRef,\n  isValidElement,\n  useCallback,\n  useContext,\n  useEffect,\n  useImperativeHandle,\n  useRef,\n  useMemo,\n  useState,\n  version as reactVersion,\n  type CSSProperties,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type MouseEvent,\n  type ReactElement,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { Card, type CardProps } from \"../card\";\nimport { IconButton } from \"../icon-button\";\nimport {\n  CardStackRendererContext,\n  StaticCardStackDeck,\n  StaticCardStackFrame,\n} from \"./card-stack-renderer\";\n\nexport type CardStackMode = \"stack\" | \"open\";\nexport type CardStackCardElement = ReactElement<CardProps>;\n\nexport interface CardStackProps extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> {\n  activeIndex?: number;\n  angle?: number;\n  children?: CardStackCardElement | CardStackCardElement[];\n  defaultActiveIndex?: number;\n  /** Explicit labels for selectors and the active-card announcement. */\n  getCardLabel?: (index: number) => string;\n  /** Maximum visible cards, including the active card (1–9). Others stay mounted. */\n  visibleCount?: number;\n  loop?: boolean;\n  mode?: CardStackMode;\n  nextLabel?: string;\n  onActiveIndexChange?: (index: number) => void;\n  previousLabel?: string;\n  showControls?: boolean;\n  showNextControl?: boolean;\n  showPreviousControl?: boolean;\n  stackOffset?: number;\n}\n\ntype CardStackItemStyle = CSSProperties & {\n  \"--card-stack-rotate\"?: string;\n  \"--card-stack-scale\"?: string;\n  \"--card-stack-translate\"?: string;\n};\n\ntype InjectedCardProps = CardProps & {\n  \"aria-hidden\"?: boolean;\n  \"data-card-stack-active\"?: \"true\" | \"false\";\n  \"data-card-stack-depth\"?: number;\n  \"data-card-stack-index\"?: number;\n  \"data-card-stack-position\"?: \"active\" | \"before\" | \"after\" | \"behind\";\n  inert?: boolean;\n};\n\nconst cardStackRootClasses =\n  \"relative isolate min-w-0 text-foreground [--card-stack-direction:1] rtl:[--card-stack-direction:-1] [--card-stack-max-width:24rem] data-[mode=open]:[--card-stack-max-width:32rem] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\";\n\nconst cardStackDeckClasses =\n  \"relative mx-auto grid w-full max-w-[min(100%,var(--card-stack-max-width))] place-items-center overflow-visible px-[6%] py-[var(--dt-space-8)] data-[mode=open]:px-[10%] data-[mode=open]:py-[var(--dt-space-12)]\";\n\nconst cardStackItemClasses =\n  \"group/card-stack-item col-start-1 row-start-1 w-full min-w-0 rounded-lg [rotate:calc(var(--card-stack-rotate)*var(--card-stack-direction))] [scale:var(--card-stack-scale)] [translate:var(--card-stack-translate)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-safe:transition-[translate,rotate,scale,opacity] motion-safe:duration-[var(--dt-motion-standard)] motion-safe:ease-control motion-reduce:transition-none\";\n\nconst cardStackCardClasses =\n  \"relative w-full motion-safe:transition-[translate,box-shadow,border-color,background-color] motion-safe:duration-[var(--dt-motion-fast)] motion-reduce:transition-none\";\n\nconst cardStackControlsClasses =\n  \"relative z-50 mx-auto flex w-fit items-center justify-center gap-3 pb-2\";\n\n// React 18 serializes unknown DOM attributes; React 19 understands boolean inert.\nconst inertAttribute = (\n  Number.parseInt(reactVersion, 10) < 19 ? \"\" : true\n) as true;\n\nfunction clampNumber(value: number, min: number, max: number) {\n  if (!Number.isFinite(value)) {\n    return min;\n  }\n\n  return Math.min(Math.max(value, min), max);\n}\n\nfunction positiveModulo(value: number, divisor: number) {\n  return ((value % divisor) + divisor) % divisor;\n}\n\nfunction normalizeIndex(\n  index: number | undefined,\n  count: number,\n  loop: boolean,\n) {\n  if (count <= 0) {\n    return -1;\n  }\n\n  const safeIndex = Number.isFinite(index) ? Number(index) : 0;\n\n  if (loop) {\n    return positiveModulo(Math.trunc(safeIndex), count);\n  }\n\n  return clampNumber(Math.trunc(safeIndex), 0, count - 1);\n}\n\nfunction getForwardDistance(index: number, activeIndex: number, count: number) {\n  if (count <= 0 || index === activeIndex) {\n    return 0;\n  }\n\n  return positiveModulo(index - activeIndex, count);\n}\n\nfunction getOpenRelativePosition(\n  index: number,\n  activeIndex: number,\n  count: number,\n  loop = true,\n) {\n  let relativePosition = index - activeIndex;\n\n  if (loop && count > 2) {\n    const halfCount = count / 2;\n\n    if (relativePosition > halfCount) {\n      relativePosition -= count;\n    }\n\n    if (relativePosition < -halfCount) {\n      relativePosition += count;\n    }\n  }\n\n  return relativePosition;\n}\n\nfunction getCardStackPosition(\n  mode: CardStackMode,\n  relativePosition: number,\n  isActive: boolean,\n) {\n  if (isActive) {\n    return \"active\";\n  }\n\n  if (mode === \"stack\") {\n    return \"behind\";\n  }\n\n  return relativePosition < 0 ? \"before\" : \"after\";\n}\n\nfunction getItemStyle({\n  activeIndex,\n  angle,\n  count,\n  index,\n  mode,\n  stackOffset,\n  loop,\n}: {\n  activeIndex: number;\n  angle: number;\n  count: number;\n  index: number;\n  mode: CardStackMode;\n  stackOffset: number;\n  loop: boolean;\n}): { depth: number; relativePosition: number; style: CardStackItemStyle } {\n  const isActive = index === activeIndex;\n  const safeAngle = clampNumber(angle, 0, 30);\n  const safeStackOffset = clampNumber(stackOffset, 0, 32);\n\n  if (mode === \"open\") {\n    const relativePosition = getOpenRelativePosition(\n      index,\n      activeIndex,\n      count,\n      loop,\n    );\n    const depth = Math.abs(relativePosition);\n    const x = relativePosition * 12;\n    const y = depth * 10;\n    const rotate = clampNumber(relativePosition * safeAngle, -60, 60);\n    const scale = Math.max(0.88, 1 - depth * 0.035);\n\n    return {\n      depth,\n      relativePosition,\n      style: {\n        \"--card-stack-rotate\": `${rotate}deg`,\n        \"--card-stack-scale\": String(isActive ? 1 : scale),\n        \"--card-stack-translate\": `calc(${x}% * var(--card-stack-direction)) ${y}px`,\n        zIndex: isActive ? count + 20 : count - depth,\n      },\n    };\n  }\n\n  const depth = isActive\n    ? 0\n    : Math.min(getForwardDistance(index, activeIndex, count), 4);\n  const rotate = isActive ? 0 : depth * 0.75;\n  const scale = Math.max(0.9, 1 - depth * 0.02);\n  const offset = depth * safeStackOffset;\n\n  return {\n    depth,\n    relativePosition: isActive ? 0 : depth,\n    style: {\n      \"--card-stack-rotate\": `${rotate}deg`,\n      \"--card-stack-scale\": String(isActive ? 1 : scale),\n      \"--card-stack-translate\": `calc(${offset}px * var(--card-stack-direction)) ${offset}px`,\n      zIndex: isActive ? count + 20 : count - depth,\n    },\n  };\n}\n\nfunction getCardChildren(children: CardStackProps[\"children\"]) {\n  const childArray = Children.toArray(children);\n\n  return childArray.map((child) => {\n    if (!isValidElement<CardProps>(child) || child.type !== Card) {\n      throw new Error(\"CardStack expects direct Card children.\");\n    }\n\n    return child as CardStackCardElement;\n  });\n}\n\nfunction ChevronLeftIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"rtl:rotate-180\"\n      fill=\"none\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentColor\"\n    >\n      <path\n        d=\"M10 3.5 5.5 8l4.5 4.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className=\"rtl:rotate-180\"\n      fill=\"none\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentColor\"\n    >\n      <path\n        d=\"m6 3.5 4.5 4.5L6 12.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nexport function cardStackClassNames({\n  className,\n}: Pick<CardStackProps, \"className\"> = {}) {\n  return cn(cardStackRootClasses, className);\n}\n\nexport const CardStack = forwardRef<HTMLDivElement, CardStackProps>(\n  (\n    {\n      \"aria-label\": ariaLabel,\n      \"aria-labelledby\": ariaLabelledBy,\n      activeIndex,\n      angle = 15,\n      children,\n      className,\n      defaultActiveIndex = 0,\n      getCardLabel,\n      visibleCount = 5,\n      onFocusCapture,\n      onBlurCapture,\n      loop = true,\n      mode = \"stack\",\n      nextLabel = \"Show next card\",\n      onActiveIndexChange,\n      onKeyDown,\n      previousLabel = \"Show previous card\",\n      role = \"group\",\n      showControls,\n      showNextControl,\n      showPreviousControl,\n      stackOffset = 8,\n      tabIndex,\n      ...props\n    },\n    ref,\n  ) => {\n    const renderer = useContext(CardStackRendererContext);\n    const Frame = renderer?.Frame ?? StaticCardStackFrame;\n    const Deck = renderer?.Deck ?? StaticCardStackDeck;\n    const cards = useMemo(() => getCardChildren(children), [children]);\n    const cardCount = cards.length;\n    const isControlled = activeIndex !== undefined;\n    const rootRef = useRef<HTMLDivElement>(null);\n    useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);\n    const focusedCardKey = useRef<string | null>(null);\n    const [selection, setSelection] = useState(() => {\n      const index = normalizeIndex(defaultActiveIndex, cardCount, loop);\n      return { index, key: cards[index]?.key ?? null };\n    });\n    const anchoredIndex =\n      selection.key === null\n        ? -1\n        : cards.findIndex((card) => card.key === selection.key);\n    const resolvedActiveIndex = isControlled\n      ? normalizeIndex(activeIndex, cardCount, loop)\n      : anchoredIndex >= 0\n        ? anchoredIndex\n        : normalizeIndex(\n            selection.index === -1 ? defaultActiveIndex : selection.index,\n            cardCount,\n            false,\n          );\n    const activeKey = cards[resolvedActiveIndex]?.key ?? null;\n    const [navigation, setNavigation] = useState({\n      index: resolvedActiveIndex,\n      direction: 1,\n    });\n    if (navigation.index !== resolvedActiveIndex) {\n      setNavigation({\n        index: resolvedActiveIndex,\n        direction: resolvedActiveIndex >= navigation.index ? 1 : -1,\n      });\n    }\n    if (\n      !isControlled &&\n      (selection.index !== resolvedActiveIndex || selection.key !== activeKey)\n    ) {\n      setSelection({ index: resolvedActiveIndex, key: activeKey });\n    }\n    useEffect(() => {\n      if (\n        focusedCardKey.current !== null &&\n        focusedCardKey.current !== activeKey\n      ) {\n        const root = rootRef.current;\n        const focused = root?.ownerDocument.activeElement;\n        if (\n          root &&\n          (focused === root.ownerDocument.body ||\n            (focused && root.contains(focused)))\n        ) {\n          root.focus({ preventScroll: true });\n        }\n        focusedCardKey.current = null;\n      }\n    }, [activeKey]);\n    const limit = Math.trunc(clampNumber(visibleCount, 1, 9));\n    const visibleIndices = new Set<number>();\n    for (\n      let step = 0;\n      step < cardCount && visibleIndices.size < limit;\n      step++\n    ) {\n      const offset =\n        mode === \"open\" ? (step % 2 ? (step + 1) / 2 : -step / 2) : step;\n      const candidate = resolvedActiveIndex + offset;\n      if (loop) visibleIndices.add(positiveModulo(candidate, cardCount));\n      else if (candidate >= 0 && candidate < cardCount)\n        visibleIndices.add(candidate);\n    }\n    // At a non-looping edge, fill unused fan positions from the remaining side.\n    if (mode === \"open\" && !loop) {\n      for (\n        let distance = 1;\n        distance < cardCount && visibleIndices.size < limit;\n        distance++\n      ) {\n        for (const candidate of [\n          resolvedActiveIndex + distance,\n          resolvedActiveIndex - distance,\n        ]) {\n          if (\n            candidate >= 0 &&\n            candidate < cardCount &&\n            visibleIndices.size < limit\n          )\n            visibleIndices.add(candidate);\n        }\n      }\n    }\n    const activeLabel =\n      resolvedActiveIndex >= 0\n        ? getCardLabel?.(resolvedActiveIndex)?.trim()\n        : undefined;\n    const canMovePrevious = cardCount > 1 && (loop || resolvedActiveIndex > 0);\n    const canMoveNext =\n      cardCount > 1 && (loop || resolvedActiveIndex < cardCount - 1);\n    const controlsVisible = showControls ?? (mode === \"stack\" && cardCount > 1);\n    const previousControlVisible = showPreviousControl ?? controlsVisible;\n    const nextControlVisible = showNextControl ?? controlsVisible;\n    const controlsRendered = previousControlVisible || nextControlVisible;\n    const resolvedTabIndex = tabIndex ?? (cardCount > 1 ? 0 : -1);\n\n    const setActiveIndex = useCallback(\n      (nextIndex: number) => {\n        const normalizedIndex = normalizeIndex(nextIndex, cardCount, loop);\n\n        if (normalizedIndex === -1) {\n          return;\n        }\n\n        if (!isControlled) {\n          setSelection({\n            index: normalizedIndex,\n            key: cards[normalizedIndex]?.key ?? null,\n          });\n        }\n\n        if (normalizedIndex !== resolvedActiveIndex) {\n          setNavigation({\n            index: normalizedIndex,\n            direction: nextIndex >= resolvedActiveIndex ? 1 : -1,\n          });\n          onActiveIndexChange?.(normalizedIndex);\n        }\n      },\n      [\n        cardCount,\n        cards,\n        isControlled,\n        loop,\n        onActiveIndexChange,\n        resolvedActiveIndex,\n        setNavigation,\n      ],\n    );\n\n    const movePrevious = useCallback(() => {\n      if (!canMovePrevious) {\n        return;\n      }\n\n      setActiveIndex(resolvedActiveIndex - 1);\n    }, [canMovePrevious, resolvedActiveIndex, setActiveIndex]);\n\n    const moveNext = useCallback(() => {\n      if (!canMoveNext) {\n        return;\n      }\n\n      setActiveIndex(resolvedActiveIndex + 1);\n    }, [canMoveNext, resolvedActiveIndex, setActiveIndex]);\n\n    const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n      onKeyDown?.(event);\n\n      if (\n        event.defaultPrevented ||\n        event.target !== event.currentTarget ||\n        cardCount <= 1\n      ) {\n        return;\n      }\n\n      const rtl = getComputedStyle(event.currentTarget).direction === \"rtl\";\n      if (\n        event.key === (rtl ? \"ArrowRight\" : \"ArrowLeft\") ||\n        event.key === \"ArrowUp\"\n      ) {\n        event.preventDefault();\n        movePrevious();\n        return;\n      }\n\n      if (\n        event.key === (rtl ? \"ArrowLeft\" : \"ArrowRight\") ||\n        event.key === \"ArrowDown\"\n      ) {\n        event.preventDefault();\n        moveNext();\n        return;\n      }\n\n      if (event.key === \"Home\") {\n        event.preventDefault();\n        setActiveIndex(0);\n        return;\n      }\n\n      if (event.key === \"End\") {\n        event.preventDefault();\n        setActiveIndex(cardCount - 1);\n      }\n    };\n\n    const accessibleNameProps = ariaLabelledBy\n      ? { \"aria-labelledby\": ariaLabelledBy }\n      : { \"aria-label\": ariaLabel ?? \"Card stack\" };\n\n    return (\n      <div\n        {...props}\n        {...accessibleNameProps}\n        ref={rootRef}\n        role={role}\n        tabIndex={resolvedTabIndex}\n        data-slot=\"card-stack\"\n        data-mode={mode}\n        data-loop={loop ? \"true\" : \"false\"}\n        data-count={cardCount}\n        data-active-index={\n          resolvedActiveIndex === -1 ? undefined : resolvedActiveIndex\n        }\n        className={cardStackClassNames({ className })}\n        onKeyDown={handleKeyDown}\n        onFocusCapture={(event) => {\n          onFocusCapture?.(event);\n          const item = (event.target as HTMLElement).closest<HTMLElement>(\n            '[data-slot=\"card-stack-item\"]',\n          );\n          focusedCardKey.current = item\n            ? String(cards[Number(item.dataset.cardStackIndex)]?.key ?? \"\")\n            : null;\n        }}\n        onBlurCapture={(event) => {\n          onBlurCapture?.(event);\n          if (\n            event.relatedTarget &&\n            !event.currentTarget.contains(event.relatedTarget as Node)\n          )\n            focusedCardKey.current = null;\n        }}\n      >\n        <Deck\n          data-slot=\"card-stack-deck\"\n          navigation={{\n            previous: movePrevious,\n            next: moveNext,\n            activeIndex: resolvedActiveIndex,\n          }}\n          data-mode={mode}\n          className={cardStackDeckClasses}\n        >\n          {cards.map((card, index) => {\n            const isActive = index === resolvedActiveIndex;\n            const visible = visibleIndices.has(index);\n            const label = getCardLabel?.(index)?.trim();\n            const { depth, relativePosition, style } = getItemStyle({\n              activeIndex: resolvedActiveIndex,\n              angle,\n              count: cardCount,\n              index,\n              mode,\n              stackOffset,\n              loop,\n            });\n            const position = getCardStackPosition(\n              mode,\n              relativePosition,\n              isActive,\n            );\n            const inactiveOpenCard = mode === \"open\" && !isActive && visible;\n            const handleInactiveClick = (event: MouseEvent<HTMLDivElement>) => {\n              if (!inactiveOpenCard) {\n                return;\n              }\n\n              event.preventDefault();\n              event.stopPropagation();\n              setActiveIndex(index);\n            };\n            const clonedCard = cloneElement(card, {\n              \"aria-hidden\": isActive ? card.props[\"aria-hidden\"] : true,\n              \"data-card-stack-active\": isActive ? \"true\" : \"false\",\n              \"data-card-stack-depth\": depth,\n              \"data-card-stack-index\": index,\n              \"data-card-stack-position\": position,\n              inert: isActive ? undefined : inertAttribute,\n              onClick: isActive ? card.props.onClick : undefined,\n              tabIndex: isActive ? card.props.tabIndex : -1,\n              className: cn(\n                card.props.className,\n                cardStackCardClasses,\n                !isActive && \"pointer-events-none select-none\",\n                inactiveOpenCard &&\n                  \"motion-safe:group-hover/card-stack-item:-translate-y-1 motion-safe:group-focus-visible/card-stack-item:-translate-y-1\",\n              ),\n            } as Partial<InjectedCardProps>);\n\n            return (\n              <Frame\n                key={card.key ?? index}\n                frame={{\n                  active: isActive,\n                  visible,\n                  direction: navigation.direction,\n                  count: cardCount,\n                }}\n                data-slot=\"card-stack-item\"\n                data-card-stack-visible={visible ? \"true\" : \"false\"}\n                aria-hidden={!visible || undefined}\n                inert={!visible ? inertAttribute : undefined}\n                data-card-stack-active={isActive ? \"true\" : \"false\"}\n                data-card-stack-index={index}\n                data-card-stack-position={position}\n                className={cn(\n                  cardStackItemClasses,\n                  inactiveOpenCard && \"cursor-pointer\",\n                )}\n                style={{\n                  ...style,\n                  visibility: visible ? undefined : \"hidden\",\n                  pointerEvents: visible ? undefined : \"none\",\n                }}\n                role={inactiveOpenCard ? \"button\" : undefined}\n                tabIndex={inactiveOpenCard ? 0 : undefined}\n                aria-label={\n                  inactiveOpenCard\n                    ? label\n                      ? `Show ${label}`\n                      : `Show card ${index + 1}`\n                    : undefined\n                }\n                onKeyDown={(event) => {\n                  if (\n                    inactiveOpenCard &&\n                    (event.key === \"Enter\" || event.key === \" \")\n                  ) {\n                    event.preventDefault();\n                    event.stopPropagation();\n                    if (event.key === \"Enter\") {\n                      event.currentTarget\n                        .closest<HTMLElement>('[data-slot=\"card-stack\"]')\n                        ?.focus({ preventScroll: true });\n                      setActiveIndex(index);\n                    }\n                  }\n                }}\n                onKeyUp={(event) => {\n                  if (inactiveOpenCard && event.key === \" \") {\n                    event.preventDefault();\n                    event.stopPropagation();\n                    event.currentTarget\n                      .closest<HTMLElement>('[data-slot=\"card-stack\"]')\n                      ?.focus({ preventScroll: true });\n                    setActiveIndex(index);\n                  }\n                }}\n                onClick={handleInactiveClick}\n              >\n                {clonedCard}\n              </Frame>\n            );\n          })}\n        </Deck>\n        <span className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n          {cardCount > 1\n            ? `Card ${resolvedActiveIndex + 1} of ${cardCount}${activeLabel ? `: ${activeLabel}` : \"\"}`\n            : \"\"}\n        </span>\n        {controlsRendered ? (\n          <div\n            data-slot=\"card-stack-controls\"\n            className={cardStackControlsClasses}\n          >\n            {previousControlVisible ? (\n              <IconButton\n                aria-label={previousLabel}\n                className=\"pointer-events-auto\"\n                disabled={!canMovePrevious}\n                shape=\"circle\"\n                size=\"sm\"\n                variant=\"outline\"\n                onClick={movePrevious}\n              >\n                <ChevronLeftIcon />\n              </IconButton>\n            ) : (\n              <span aria-hidden=\"true\" className=\"pointer-events-none\" />\n            )}\n            {nextControlVisible ? (\n              <IconButton\n                aria-label={nextLabel}\n                className=\"pointer-events-auto\"\n                disabled={!canMoveNext}\n                shape=\"circle\"\n                size=\"sm\"\n                variant=\"outline\"\n                onClick={moveNext}\n              >\n                <ChevronRightIcon />\n              </IconButton>\n            ) : (\n              <span aria-hidden=\"true\" className=\"pointer-events-none\" />\n            )}\n          </div>\n        ) : null}\n      </div>\n    );\n  },\n);\n\nCardStack.displayName = \"CardStack\";\n"
    },
    {
      "path": "packages/components/src/components/card-stack/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/card-stack/index.ts",
      "content": "export {\n  CardStack,\n  cardStackClassNames,\n  type CardStackCardElement,\n  type CardStackMode,\n  type CardStackProps,\n} from \"./card-stack\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
