{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "compare-slider",
  "title": "Compare Slider",
  "description": "Before/after comparison slider for images, themes and redesigns.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/compare-slider.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronsLeftRightIcon } from \"lucide-react\";\n\nimport { gsap, motionScale, useGSAP } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\ntype CompareSliderProps = React.ComponentProps<\"div\"> & {\n  /** The \"before\" layer (shown on the left). Any element: an <img>, a <video>, a component. */\n  before: React.ReactNode;\n  /** The \"after\" layer (shown on the right). */\n  after: React.ReactNode;\n  beforeLabel?: string;\n  afterLabel?: string;\n  /** Starting position, 0–100. */\n  defaultPosition?: number;\n  /** Follow the pointer on hover instead of requiring a drag. */\n  hover?: boolean;\n};\n\n/**\n * Before/after comparison: drag the handle (or use arrow keys) to reveal one layer over the other with a\n * hardware-accelerated clip-path. On first view the handle sweeps once to show it can move.\n */\nfunction CompareSlider({ before, after, beforeLabel = \"Before\", afterLabel = \"After\", defaultPosition = 50, hover = false, className, ...props }: CompareSliderProps) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [pos, setPos] = React.useState(defaultPosition);\n  const dragging = React.useRef(false);\n\n  const fromEvent = (clientX: number) => {\n    const r = ref.current!.getBoundingClientRect();\n    setPos(Math.min(100, Math.max(0, ((clientX - r.left) / r.width) * 100)));\n  };\n\n  useGSAP(\n    () => {\n      const el = ref.current;\n      if (!el || !motionScale(el)) return;\n      const proxy = { p: defaultPosition };\n      gsap\n        .timeline({ delay: 0.4, scrollTrigger: undefined })\n        .to(proxy, { p: defaultPosition - 18, duration: 0.5, ease: \"power2.inOut\", onUpdate: () => setPos(proxy.p) })\n        .to(proxy, { p: defaultPosition + 12, duration: 0.6, ease: \"power2.inOut\", onUpdate: () => setPos(proxy.p) })\n        .to(proxy, { p: defaultPosition, duration: 0.5, ease: \"cue\", onUpdate: () => setPos(proxy.p) });\n    },\n    { scope: ref },\n  );\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"compare-slider\"\n      className={cn(\"relative isolate overflow-hidden rounded-2xl border border-border bg-muted select-none touch-pan-y\", className)}\n      onPointerDown={(e) => {\n        dragging.current = true;\n        e.currentTarget.setPointerCapture(e.pointerId);\n        fromEvent(e.clientX);\n      }}\n      onPointerMove={(e) => (dragging.current || hover) && fromEvent(e.clientX)}\n      onPointerUp={() => (dragging.current = false)}\n      {...props}\n    >\n      <div className=\"pointer-events-none [&>*]:block [&>*]:size-full [&>*]:object-cover\">{after}</div>\n      <div className=\"pointer-events-none absolute inset-0 [&>*]:block [&>*]:size-full [&>*]:object-cover\" style={{ clipPath: `inset(0 ${100 - pos}% 0 0)` }}>\n        {before}\n      </div>\n      <span className=\"cue-label pointer-events-none absolute top-3 left-3 rounded-full bg-background/80 px-2 py-1 backdrop-blur\">{beforeLabel}</span>\n      <span className=\"cue-label pointer-events-none absolute top-3 right-3 rounded-full bg-background/80 px-2 py-1 backdrop-blur\">{afterLabel}</span>\n      <div className=\"pointer-events-none absolute inset-y-0 w-px bg-background shadow-[0_0_0_0.5px_rgb(0_0_0/0.15)]\" style={{ left: `${pos}%` }} />\n      <button\n        type=\"button\"\n        role=\"slider\"\n        aria-label=\"Comparison position\"\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuenow={Math.round(pos)}\n        onKeyDown={(e) => {\n          const step = e.shiftKey ? 10 : 2;\n          if (e.key === \"ArrowLeft\") setPos((p) => Math.max(0, p - step));\n          if (e.key === \"ArrowRight\") setPos((p) => Math.min(100, p + step));\n        }}\n        className=\"absolute top-1/2 grid size-9 -translate-x-1/2 -translate-y-1/2 cursor-ew-resize place-items-center rounded-full border border-border bg-background shadow-raised outline-none transition-[scale] duration-150 ease-cue focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-95\"\n        style={{ left: `${pos}%` }}\n      >\n        <ChevronsLeftRightIcon className=\"size-4\" />\n      </button>\n    </div>\n  );\n}\n\nexport { CompareSlider };\nexport type { CompareSliderProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/compare-slider",
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}