{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel-fraction",
  "title": "Fraction carousel",
  "description": "Styled carousel: the Fraction set. 5 decorative carousel variations (CounterCarousel, BarCarousel, RingCarousel, CornerCarousel, CombinedCarousel) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/carousel-fraction.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Fraction carousel family: 5 self-contained sliders (NO embla, NO radix). Each\n   carries a \"1 / 4\" counter and/or a token-colored progress indicator. The number\n   is formatted deterministically from the index + total that come out of the hook\n   (no Date.now or Math.random). Each export is a complete carousel: the slides\n   move horizontally and the arrows change the index. The root carries\n   data-slot=\"styled-carousel\" and role=\"region\". Color comes ONLY from semantic\n   tokens; the progress is primary, via alpha color-mix. Gated on framer's\n   useReducedMotion: under reduced motion the slide changes instantly. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst trackHeight: Record<StyledSize, string> = {\n  sm: \"h-40\",\n  md: \"h-56\",\n  lg: \"h-72\",\n  xl: \"h-96\",\n}\n\nconst focusRing =\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50\"\n\nconst arrowBtn =\n  \"absolute top-1/2 z-20 inline-flex size-9 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-[color-mix(in_oklab,var(--color-background)_72%,transparent)] text-foreground shadow-sm transition-colors supports-[backdrop-filter]:backdrop-blur hover:bg-background [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\n/* Token-surfaced, numbered placeholder panels - for prop-less rendering. */\nconst panelTones = [\"bg-surface-2\", \"bg-surface-3\", \"bg-muted\", \"bg-accent\"]\n\nfunction defaultSlides(count = 4): React.ReactNode[] {\n  return Array.from({ length: count }, (_, i) => (\n    <div\n      key={i}\n      className={cn(\n        \"flex h-full w-full items-center justify-center\",\n        panelTones[i % panelTones.length]\n      )}\n    >\n      <span className=\"text-4xl font-semibold tabular-nums text-foreground\">{i + 1}</span>\n    </div>\n  ))\n}\n\nfunction resolveSlides(slides?: React.ReactNode[]): React.ReactNode[] {\n  return slides && slides.length > 0 ? slides : defaultSlides()\n}\n\n/* Ortak index state: sonsuz sarma (wrap) ile prev/next ve dogrudan jump. */\nfunction useCarousel(count: number) {\n  const [index, setIndex] = React.useState(0)\n  const total = Math.max(count, 1)\n  const wrap = React.useCallback((i: number) => ((i % total) + total) % total, [total])\n  const go = React.useCallback((i: number) => setIndex(wrap(i)), [wrap])\n  const next = React.useCallback(() => setIndex((p) => wrap(p + 1)), [wrap])\n  const prev = React.useCallback(() => setIndex((p) => wrap(p - 1)), [wrap])\n  return { index: Math.min(index, total - 1), setIndex: go, next, prev, total }\n}\n\ninterface CarouselProps {\n  className?: string\n  size?: StyledSize\n  slides?: React.ReactNode[]\n}\n\n/* The main horizontally sliding track: every slide is basis-full, translateX by\n   index. */\nfunction SlideTrack({\n  items,\n  index,\n  next,\n  prev,\n  size,\n  reduce,\n  overlay,\n}: {\n  items: React.ReactNode[]\n  index: number\n  next: () => void\n  prev: () => void\n  size: StyledSize\n  reduce: boolean | null\n  overlay?: React.ReactNode\n}) {\n  return (\n    <div className={cn(\"relative w-full overflow-hidden rounded-xl border border-border\", trackHeight[size])}>\n      <motion.div\n        className=\"flex h-full w-full\"\n        animate={{ x: `-${index * 100}%` }}\n        transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 320, damping: 34 }}\n      >\n        {items.map((slide, i) => (\n          <div\n            key={i}\n            role=\"group\"\n            aria-roledescription=\"slide\"\n            aria-label={`${i + 1} of ${items.length}`}\n            aria-hidden={i !== index}\n            className=\"h-full w-full shrink-0 basis-full overflow-hidden\"\n          >\n            {slide}\n          </div>\n        ))}\n      </motion.div>\n      <button type=\"button\" aria-label=\"Previous slide\" onClick={prev} className={cn(arrowBtn, focusRing, \"left-3\")}>\n        <ChevronLeft />\n      </button>\n      <button type=\"button\" aria-label=\"Next slide\" onClick={next} className={cn(arrowBtn, focusRing, \"right-3\")}>\n        <ChevronRight />\n      </button>\n      {overlay}\n    </div>\n  )\n}\n\n/* Deterministik kesir metni: 1 tabanli. */\nfunction fractionLabel(index: number, total: number) {\n  return `${index + 1} / ${total}`\n}\n\nconst badgeBase =\n  \"inline-flex items-center gap-1 rounded-full border border-border bg-[color-mix(in_oklab,var(--color-background)_72%,transparent)] px-2.5 py-1 text-xs font-medium tabular-nums text-foreground shadow-sm supports-[backdrop-filter]:backdrop-blur\"\n\n/* Counter: belirgin \"1 / 4\" sayaci, ilerleme cizgisiz. */\nexport function CounterCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, next, prev, total } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <SlideTrack items={items} index={index} next={next} prev={prev} size={size} reduce={reduce} />\n      <div className=\"mt-3 flex items-center justify-center\">\n        <span className=\"text-sm font-medium tabular-nums text-muted-foreground\">\n          <span className=\"text-foreground\">{index + 1}</span> / {total}\n        </span>\n      </div>\n    </div>\n  )\n}\n\n/* Bar: alt kenarda token renkli ilerleme cubugu + kucuk sayac. */\nexport function BarCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, next, prev, total } = useCarousel(items.length)\n  const pct = ((index + 1) / total) * 100\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <SlideTrack\n        items={items}\n        index={index}\n        next={next}\n        prev={prev}\n        size={size}\n        reduce={reduce}\n        overlay={\n          <div className=\"absolute inset-x-0 bottom-0 z-10 h-1 bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\">\n            <motion.div\n              className=\"h-full bg-primary\"\n              animate={{ width: `${pct}%` }}\n              transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 320, damping: 34 }}\n            />\n          </div>\n        }\n      />\n      <div className=\"mt-3 flex items-center justify-center\">\n        <span className=\"text-xs font-medium tabular-nums text-muted-foreground\">{fractionLabel(index, total)}</span>\n      </div>\n    </div>\n  )\n}\n\n/* Ring: dairesel token ilerleme halkasi, ortasinda kesir. */\nexport function RingCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, next, prev, total } = useCarousel(items.length)\n  const r = 16\n  const c = 2 * Math.PI * r\n  const offset = c * (1 - (index + 1) / total)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <SlideTrack items={items} index={index} next={next} prev={prev} size={size} reduce={reduce} />\n      <div className=\"mt-3 flex items-center justify-center\">\n        <div className=\"relative inline-flex size-12 items-center justify-center\">\n          <svg viewBox=\"0 0 40 40\" className=\"size-12 -rotate-90\">\n            <circle\n              cx=\"20\"\n              cy=\"20\"\n              r={r}\n              fill=\"none\"\n              strokeWidth=\"3\"\n              className=\"stroke-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\"\n            />\n            <motion.circle\n              cx=\"20\"\n              cy=\"20\"\n              r={r}\n              fill=\"none\"\n              strokeWidth=\"3\"\n              strokeLinecap=\"round\"\n              className=\"stroke-primary\"\n              strokeDasharray={c}\n              animate={{ strokeDashoffset: offset }}\n              transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 260, damping: 30 }}\n            />\n          </svg>\n          <span className=\"absolute text-[11px] font-semibold tabular-nums text-foreground\">{index + 1}</span>\n        </div>\n      </div>\n    </div>\n  )\n}\n\n/* Corner: the fraction badge overlaps the top right corner of the track. */\nexport function CornerCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, next, prev, total } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <SlideTrack\n        items={items}\n        index={index}\n        next={next}\n        prev={prev}\n        size={size}\n        reduce={reduce}\n        overlay={\n          <span className={cn(badgeBase, \"absolute right-3 top-3 z-10\")} aria-hidden=\"true\">\n            {fractionLabel(index, total)}\n          </span>\n        }\n      />\n    </div>\n  )\n}\n\n/* Combined: kose rozeti + alt ilerleme cubugu + sayac birlikte. */\nexport function CombinedCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, next, prev, total } = useCarousel(items.length)\n  const pct = ((index + 1) / total) * 100\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <SlideTrack\n        items={items}\n        index={index}\n        next={next}\n        prev={prev}\n        size={size}\n        reduce={reduce}\n        overlay={\n          <>\n            <span className={cn(badgeBase, \"absolute right-3 top-3 z-10\")} aria-hidden=\"true\">\n              {fractionLabel(index, total)}\n            </span>\n            <div className=\"absolute inset-x-0 bottom-0 z-10 h-1 bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\">\n              <motion.div\n                className=\"h-full bg-primary\"\n                animate={{ width: `${pct}%` }}\n                transition={reduce ? { duration: 0 } : { type: \"spring\" as const, stiffness: 320, damping: 34 }}\n              />\n            </div>\n          </>\n        }\n      />\n      <div className=\"mt-3 flex items-center justify-center\">\n        <span className=\"text-sm font-medium tabular-nums text-muted-foreground\">\n          <span className=\"text-foreground\">{index + 1}</span> / {total}\n        </span>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/carousel-fraction.tsx"
    }
  ],
  "categories": [
    "styled",
    "carousel"
  ],
  "type": "registry:component"
}