{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "title": "Carousel",
  "description": "A center-snapping carousel primitive with responsive edge blur and button controls.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/corr/components/carousel.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowLeftIcon, ArrowRightIcon } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\n\ntype CarouselContextValue = {\n  scrollRef: React.RefObject<HTMLDivElement | null>\n  edgeSize: number\n  itemInset: { start: number; end: number }\n  edges: { left: boolean; right: boolean }\n  canScrollPrevious: boolean\n  canScrollNext: boolean\n  scrollToItem: (direction: \"previous\" | \"next\") => void\n}\n\nconst CarouselContext = React.createContext<CarouselContextValue | null>(null)\nconst minimumItemInset = 16\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext)\n\n  if (!context) {\n    throw new Error(\"Carousel components must be used within <Carousel />\")\n  }\n\n  return context\n}\n\nexport function Carousel({\n  children,\n  title,\n  description,\n  controls = true,\n  className,\n  contentClassName,\n}: {\n  children: React.ReactNode\n  title?: string\n  description?: React.ReactNode\n  controls?: boolean\n  className?: string\n  contentClassName?: string\n}) {\n  const scrollRef = React.useRef<HTMLDivElement>(null)\n  const reduceMotion = useReducedMotion()\n  const [edges, setEdges] = React.useState({ left: false, right: false })\n  const [edgeSize, setEdgeSize] = React.useState(48)\n  const [itemInset, setItemInset] = React.useState({ start: 16, end: 16 })\n\n  const updateEdges = React.useCallback(() => {\n    const element = scrollRef.current\n    if (!element) return\n\n    const items = Array.from(\n      element.querySelectorAll<HTMLElement>(\"[data-carousel-item]\")\n    )\n    const firstItem = items[0]\n    const lastItem = items[items.length - 1]\n    const maxScroll = element.scrollWidth - element.clientWidth\n    setEdgeSize(Math.max(20, Math.min(64, element.clientWidth * 0.1)))\n    const shouldCenterFirstItem = firstItem\n      ? firstItem.offsetWidth >= element.clientWidth * 0.5\n      : false\n    const shouldCenterLastItem = lastItem\n      ? lastItem.offsetWidth >= element.clientWidth * 0.5\n      : false\n    const nextInset = {\n      start: firstItem && shouldCenterFirstItem\n        ? Math.max(\n            minimumItemInset,\n            (element.clientWidth - firstItem.offsetWidth) / 2\n          )\n        : minimumItemInset,\n      end: lastItem && shouldCenterLastItem\n        ? Math.max(\n            minimumItemInset,\n            (element.clientWidth - lastItem.offsetWidth) / 2\n          )\n        : minimumItemInset,\n    }\n\n    setItemInset((currentInset) => {\n      if (\n        Math.abs(currentInset.start - nextInset.start) < 1 &&\n        Math.abs(currentInset.end - nextInset.end) < 1\n      ) {\n        return currentInset\n      }\n\n      return nextInset\n    })\n    setEdges({\n      left: element.scrollLeft > 4,\n      right: element.scrollLeft < maxScroll - 4,\n    })\n  }, [])\n\n  React.useEffect(() => {\n    const element = scrollRef.current\n    if (!element) return\n\n    updateEdges()\n    element.addEventListener(\"scroll\", updateEdges, { passive: true })\n    window.addEventListener(\"resize\", updateEdges)\n    const resizeObserver =\n      \"ResizeObserver\" in window\n        ? new ResizeObserver(() => updateEdges())\n        : null\n    resizeObserver?.observe(element)\n\n    return () => {\n      element.removeEventListener(\"scroll\", updateEdges)\n      window.removeEventListener(\"resize\", updateEdges)\n      resizeObserver?.disconnect()\n    }\n  }, [updateEdges])\n\n  React.useEffect(() => {\n    const frame = window.requestAnimationFrame(updateEdges)\n\n    return () => window.cancelAnimationFrame(frame)\n  }, [itemInset, updateEdges])\n\n  const scrollToItem = React.useCallback(\n    (direction: \"previous\" | \"next\") => {\n      const element = scrollRef.current\n      if (!element) return\n\n      const items = Array.from(\n        element.querySelectorAll<HTMLElement>(\"[data-carousel-item]\")\n      )\n      const scrollItems = items.length\n        ? items\n        : Array.from(\n            element.firstElementChild?.children ?? []\n          ).filter((item): item is HTMLElement => item instanceof HTMLElement)\n      if (!scrollItems.length) return\n\n      const viewportCenter = element.scrollLeft + element.clientWidth / 2\n      const currentIndex = scrollItems.reduce((closestIndex, item, index) => {\n        const itemCenter = item.offsetLeft + item.offsetWidth / 2\n        const closest = scrollItems[closestIndex]\n        const closestCenter = closest.offsetLeft + closest.offsetWidth / 2\n\n        return Math.abs(itemCenter - viewportCenter) <\n          Math.abs(closestCenter - viewportCenter)\n          ? index\n          : closestIndex\n      }, 0)\n      const nextIndex =\n        direction === \"next\"\n          ? Math.min(currentIndex + 1, scrollItems.length - 1)\n          : Math.max(currentIndex - 1, 0)\n      const nextItem = scrollItems[nextIndex]\n      const nextLeft =\n        nextItem.offsetLeft + nextItem.offsetWidth / 2 - element.clientWidth / 2\n\n      element.scrollTo({\n        left: nextLeft,\n        behavior: reduceMotion ? \"auto\" : \"smooth\",\n      })\n    },\n    [reduceMotion]\n  )\n\n  const context = React.useMemo<CarouselContextValue>(\n    () => ({\n      scrollRef,\n      edgeSize,\n      itemInset,\n      edges,\n      canScrollPrevious: edges.left,\n      canScrollNext: edges.right,\n      scrollToItem,\n    }),\n    [edgeSize, edges, itemInset, scrollToItem]\n  )\n\n  const hasHeader = title || description || controls\n\n  return (\n    <CarouselContext.Provider value={context}>\n      <div className={cn(\"rounded-md border bg-card\", className)}>\n        {hasHeader ? (\n          <div className=\"flex flex-wrap items-start justify-between gap-3 border-b p-3\">\n            <div className=\"min-w-0\">\n              {title ? <h3 className=\"text-sm font-medium\">{title}</h3> : null}\n              {description ? (\n                <div className=\"mt-1 text-xs leading-5 text-muted-foreground\">\n                  {description}\n                </div>\n              ) : null}\n            </div>\n            {controls ? (\n              <div className=\"flex items-center gap-1\">\n                <CarouselPrevious />\n                <CarouselNext />\n              </div>\n            ) : null}\n          </div>\n        ) : null}\n        <div className={cn(\"relative overflow-hidden\", contentClassName)}>\n          {children}\n        </div>\n      </div>\n    </CarouselContext.Provider>\n  )\n}\n\nexport function CarouselContent({\n  children,\n  className,\n  \"aria-label\": ariaLabel = \"Carousel\",\n}: React.ComponentProps<\"div\">) {\n  const { scrollRef, edges, edgeSize, itemInset } = useCarousel()\n\n  return (\n    <>\n      {edges.left ? <CarouselEdgeBlur side=\"left\" width={edgeSize} /> : null}\n      {edges.right ? <CarouselEdgeBlur side=\"right\" width={edgeSize} /> : null}\n      <div\n        ref={scrollRef}\n        aria-label={ariaLabel}\n        className=\"scrollbar-none overflow-x-auto scroll-smooth py-4\"\n        style={{\n          scrollPaddingInlineStart: itemInset.start,\n          scrollPaddingInlineEnd: itemInset.end,\n        }}\n      >\n        <div\n          className={cn(\n            \"flex snap-x snap-mandatory gap-3 [&>*]:snap-center\",\n            className\n          )}\n        >\n          <div\n            aria-hidden=\"true\"\n            className=\"shrink-0\"\n            style={{ width: itemInset.start }}\n          />\n          {children}\n          <div\n            aria-hidden=\"true\"\n            className=\"shrink-0\"\n            style={{ width: itemInset.end }}\n          />\n        </div>\n      </div>\n    </>\n  )\n}\n\nexport function CarouselItem({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-carousel-item\n      className={cn(\"shrink-0 snap-center\", className)}\n      {...props}\n    />\n  )\n}\n\nexport function CarouselPrevious({\n  className,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { scrollToItem, canScrollPrevious } = useCarousel()\n\n  return (\n    <Button\n      type=\"button\"\n      variant=\"outline\"\n      size=\"icon-sm\"\n      className={cn(\"rounded-full\", className)}\n      onClick={() => scrollToItem(\"previous\")}\n      disabled={!canScrollPrevious}\n      {...props}\n    >\n      <ArrowLeftIcon />\n      <span className=\"sr-only\">Previous</span>\n    </Button>\n  )\n}\n\nexport function CarouselNext({\n  className,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { scrollToItem, canScrollNext } = useCarousel()\n\n  return (\n    <Button\n      type=\"button\"\n      variant=\"outline\"\n      size=\"icon-sm\"\n      className={cn(\"rounded-full\", className)}\n      onClick={() => scrollToItem(\"next\")}\n      disabled={!canScrollNext}\n      {...props}\n    >\n      <ArrowRightIcon />\n      <span className=\"sr-only\">Next</span>\n    </Button>\n  )\n}\n\nexport function CarouselEdgeBlur({\n  side,\n  width,\n}: {\n  side: \"left\" | \"right\"\n  width: number\n}) {\n  const isLeft = side === \"left\"\n\n  return (\n    <motion.div\n      className={cn(\n        \"pointer-events-none absolute inset-y-0 z-10\",\n        isLeft ? \"left-0\" : \"right-0\"\n      )}\n      style={{ width }}\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      exit={{ opacity: 0 }}\n      transition={{ duration: 0.18, ease: \"easeOut\" }}\n    >\n      <div\n        className={cn(\n          \"absolute inset-0\",\n          isLeft ? \"bg-linear-to-r\" : \"bg-linear-to-l\",\n          \"from-card via-card/70 to-transparent\"\n        )}\n      />\n      <div\n        className={cn(\n          \"absolute inset-0 backdrop-blur-[5px]\",\n          isLeft\n            ? \"[mask-image:linear-gradient(to_right,black_0%,black_18%,transparent_100%)]\"\n            : \"[mask-image:linear-gradient(to_left,black_0%,black_18%,transparent_100%)]\"\n        )}\n      />\n      <div\n        className={cn(\n          \"absolute inset-0 backdrop-blur-[10px]\",\n          isLeft\n            ? \"[mask-image:linear-gradient(to_right,black_0%,transparent_76%)]\"\n            : \"[mask-image:linear-gradient(to_left,black_0%,transparent_76%)]\"\n        )}\n      />\n    </motion.div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}