{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "compare-slider",
  "title": "Compare Slider",
  "description": "A before-and-after stage with drag, hover and keyboard interaction.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@rivelle/utils"
  ],
  "files": [
    {
      "path": "registry/nova/effects/compare-slider.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype CompareSliderProps = Omit<React.ComponentProps<\"div\">, \"onChange\"> & {\n  after: React.ReactNode;\n  before: React.ReactNode;\n  defaultValue?: number;\n  mode?: \"drag\" | \"hover\";\n  onValueChange?: (value: number) => void;\n  value?: number;\n};\n\nfunction CompareSlider({\n  after,\n  before,\n  className,\n  defaultValue = 50,\n  mode = \"drag\",\n  onPointerLeave,\n  onPointerMove,\n  onValueChange,\n  value,\n  ...props\n}: CompareSliderProps) {\n  const [internalValue, setInternalValue] = React.useState(defaultValue);\n  const [dragging, setDragging] = React.useState(false);\n  const currentValue = value ?? internalValue;\n\n  const update = React.useCallback(\n    (nextValue: number) => {\n      const clamped = Math.min(100, Math.max(0, nextValue));\n      if (value === undefined) setInternalValue(clamped);\n      onValueChange?.(clamped);\n    },\n    [onValueChange, value],\n  );\n\n  const updateFromPointer = (event: React.PointerEvent<HTMLDivElement>) => {\n    const bounds = event.currentTarget.getBoundingClientRect();\n    update(((event.clientX - bounds.left) / bounds.width) * 100);\n  };\n\n  return (\n    <div\n      className={cn(\n        \"relative isolate touch-none select-none overflow-hidden rounded-[2rem] border border-foreground/10 bg-card outline-none\",\n        className,\n      )}\n      data-slot=\"compare-slider\"\n      onPointerDown={(event) => {\n        if (mode !== \"drag\") return;\n        setDragging(true);\n        event.currentTarget.setPointerCapture(event.pointerId);\n        updateFromPointer(event);\n      }}\n      onPointerLeave={(event) => {\n        if (mode === \"hover\") update(50);\n        onPointerLeave?.(event);\n      }}\n      onPointerMove={(event) => {\n        if (mode === \"hover\" || dragging) updateFromPointer(event);\n        onPointerMove?.(event);\n      }}\n      onPointerUp={(event) => {\n        setDragging(false);\n        if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n          event.currentTarget.releasePointerCapture(event.pointerId);\n        }\n      }}\n      {...props}\n    >\n      <div className=\"absolute inset-0\">{after}</div>\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 overflow-hidden\"\n        style={{ clipPath: `inset(0 ${100 - currentValue}% 0 0)` }}\n      >\n        {before}\n      </div>\n      <div\n        aria-label=\"Comparison position\"\n        aria-orientation=\"horizontal\"\n        aria-valuemax={100}\n        aria-valuemin={0}\n        aria-valuenow={Math.round(currentValue)}\n        className=\"absolute inset-y-0 z-20 w-px -translate-x-1/2 bg-background/90 shadow-[0_0_18px_rgba(0,0,0,.28)] outline-none\"\n        onKeyDown={(event) => {\n          if (event.key === \"ArrowLeft\") update(currentValue - 2);\n          if (event.key === \"ArrowRight\") update(currentValue + 2);\n          if (event.key === \"Home\") update(0);\n          if (event.key === \"End\") update(100);\n        }}\n        role=\"slider\"\n        style={{ bottom: 0, left: `${currentValue}%`, top: 0 }}\n        tabIndex={0}\n      >\n        <span\n          className=\"absolute left-1/2 flex h-11 w-8 items-center justify-center rounded-full border border-background/40 bg-foreground text-background shadow-xl\"\n          style={{ top: \"50%\", transform: \"translate(-50%, -50%)\" }}\n        >\n          <ChevronLeft className=\"size-3.5\" />\n          <ChevronRight className=\"size-3.5\" />\n        </span>\n      </div>\n    </div>\n  );\n}\n\nexport { CompareSlider };\nexport type { CompareSliderProps };\n",
      "type": "registry:ui",
      "target": "@effects/compare-slider.tsx"
    }
  ],
  "type": "registry:ui"
}