{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "image-compare",
  "title": "Image Compare",
  "author": "AlgorizeTech",
  "description": "Ask the user to pick one of 10 image compare variants: https://avaraui.com/view/image-compare. This item holds 10 of them as looks of one component (classic-slider, split-handle, glowing-divider, minimal-line, badge-overlay, glass-handle, hover-reveal, vertical-slide, circle-magnifier, diagonal-split). Skip the question if the user already named one. A before/after image slider with five handle styles and drag or click positioning. Use it for retouching, redesign and product comparison sections.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/ImageCompareBlock.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useRef, useCallback, useEffect } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  ChevronsLeftRight,\n  Sparkles,\n  Sliders,\n  Layers,\n  Split,\n  Scan,\n} from \"lucide-react\";\n\nexport type ImageCompareVariant =\n  | \"classic-slider\"\n  | \"split-handle\"\n  | \"glowing-divider\"\n  | \"badge-overlay\"\n  | \"minimal-line\"\n  | \"hover-reveal\"\n  | \"vertical-slide\"\n  | \"circle-magnifier\"\n  | \"glass-handle\"\n  | \"diagonal-split\";\n\nexport type ImageCompareColor = \"indigo\" | \"rose\" | \"emerald\" | \"zinc\" | \"primary\" | (string & {});\n\nexport interface ImageCompareProps extends React.HTMLAttributes<HTMLDivElement> {\n  variant?: ImageCompareVariant;\n  color?: ImageCompareColor | string;\n  beforeImage?: string;\n  afterImage?: string;\n  beforeLabel?: string;\n  afterLabel?: string;\n  initialPosition?: number;\n  className?: string;\n}\n\nconst colorThemes: Record<string, { accent: string; rgb: string }> = {\n  primary: {\n    accent: \"#6366f1\",\n    rgb: \"99, 102, 241\",\n  },\n  indigo: {\n    accent: \"#6366f1\",\n    rgb: \"99, 102, 241\",\n  },\n  rose: {\n    accent: \"#f43f5e\",\n    rgb: \"244, 63, 94\",\n  },\n  emerald: {\n    accent: \"#10b981\",\n    rgb: \"16, 185, 129\",\n  },\n  zinc: {\n    accent: \"#a1a1aa\",\n    rgb: \"161, 161, 170\",\n  },\n};\n\nconst hexToRgb = (hex: string) => {\n  let cleanHex = hex.replace(/^#/, \"\");\n  if (cleanHex.length === 3)\n    cleanHex = cleanHex\n      .split(\"\")\n      .map((x) => x + x)\n      .join(\"\");\n  const result = /^([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(cleanHex);\n  return result\n    ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`\n    : \"99, 102, 241\";\n};\n\nconst DEFAULT_BEFORE =\n  \"https://res.cloudinary.com/dodndg7ai/image/upload/f_webp,q_auto,w_1200,c_limit/v1790146211/photo-1518709268805-4e9042af9f23-1790146212209.jpg\";\nconst DEFAULT_AFTER =\n  \"https://res.cloudinary.com/dodndg7ai/image/upload/f_webp,q_auto,w_1200,c_limit/v1790146209/photo-1470071459604-3b5ec3a7fe05-1790146212205.jpg\";\n\nexport function ImageCompare({\n  variant = \"classic-slider\",\n  color = \"primary\",\n  beforeImage = DEFAULT_BEFORE,\n  afterImage = DEFAULT_AFTER,\n  beforeLabel = \"Original\",\n  afterLabel = \"Enhanced\",\n  initialPosition = 50,\n  className,\n  ...props\n}: ImageCompareProps) {\n  const isPrimary = color === \"primary\" || color === \"default\" || !color;\n  const activeColor = isPrimary\n    ? \"var(--primary)\"\n    : color?.startsWith(\"#\")\n      ? color\n      : colorThemes[color]?.accent || \"var(--primary)\";\n\n  const activeRgb = isPrimary\n    ? \"var(--primary)\"\n    : color?.startsWith(\"#\")\n      ? hexToRgb(color)\n      : colorThemes[color]?.rgb || \"99, 102, 241\";\n\n  const [sliderPos, setSliderPos] = useState(initialPosition);\n  const [sliderPosY, setSliderPosY] = useState(50);\n  const [isDragging, setIsDragging] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const handleMove = useCallback((clientX: number, clientY: number) => {\n    if (!containerRef.current) return;\n    const rect = containerRef.current.getBoundingClientRect();\n    if (variant === \"vertical-slide\") {\n      const y = Math.max(0, Math.min(clientY - rect.top, rect.height));\n      const percentage = (y / rect.height) * 100;\n      setSliderPos(percentage);\n    } else if (variant === \"circle-magnifier\") {\n      const x = Math.max(0, Math.min(clientX - rect.left, rect.width));\n      const y = Math.max(0, Math.min(clientY - rect.top, rect.height));\n      setSliderPos((x / rect.width) * 100);\n      setSliderPosY((y / rect.height) * 100);\n    } else {\n      const x = Math.max(0, Math.min(clientX - rect.left, rect.width));\n      const percentage = (x / rect.width) * 100;\n      setSliderPos(percentage);\n    }\n  }, [variant]);\n\n  const handleTouchMove = useCallback(\n    (e: TouchEvent) => {\n      if (!isDragging) return;\n      handleMove(e.touches[0].clientX, e.touches[0].clientY);\n    },\n    [isDragging, handleMove]\n  );\n\n  const handleMouseMove = useCallback(\n    (e: MouseEvent) => {\n      if (!isDragging && variant !== \"hover-reveal\") return;\n      handleMove(e.clientX, e.clientY);\n    },\n    [isDragging, variant, handleMove]\n  );\n\n  const handleMouseUp = useCallback(() => {\n    setIsDragging(false);\n  }, []);\n\n  useEffect(() => {\n    if (isDragging || variant === \"hover-reveal\") {\n      window.addEventListener(\"mousemove\", handleMouseMove);\n      window.addEventListener(\"mouseup\", handleMouseUp);\n      window.addEventListener(\"touchmove\", handleTouchMove);\n      window.addEventListener(\"touchend\", handleMouseUp);\n    }\n    return () => {\n      window.removeEventListener(\"mousemove\", handleMouseMove);\n      window.removeEventListener(\"mouseup\", handleMouseUp);\n      window.removeEventListener(\"touchmove\", handleTouchMove);\n      window.removeEventListener(\"touchend\", handleMouseUp);\n    };\n  }, [isDragging, variant, handleMouseMove, handleMouseUp, handleTouchMove]);\n\n  const handleMouseDown = (e: React.MouseEvent | React.TouchEvent) => {\n    setIsDragging(true);\n    const clientX = \"touches\" in e ? e.touches[0].clientX : e.clientX;\n    const clientY = \"touches\" in e ? e.touches[0].clientY : e.clientY;\n    handleMove(clientX, clientY);\n  };\n\n  return (\n    <div\n      ref={containerRef}\n      onMouseDown={handleMouseDown}\n      onTouchStart={handleMouseDown}\n      className={cn(\n        \"relative w-full h-[480px] sm:h-[560px] rounded-2xl overflow-hidden select-none border transition-all duration-300 cursor-ew-resize group\",\n        \"bg-muted border-border\",\n        variant === \"vertical-slide\" && \"cursor-ns-resize\",\n        variant === \"circle-magnifier\" && \"cursor-crosshair\",\n        className\n      )}\n      {...props}\n    >\n      {/* BEFORE IMAGE (Full Background) */}\n      <img\n        src={beforeImage}\n        alt={beforeLabel}\n        className=\"absolute inset-0 w-full h-full object-cover pointer-events-none\"\n      />\n\n      {/* AFTER IMAGE (Clipped Container) */}\n      <div\n        className=\"absolute inset-0 overflow-hidden pointer-events-none transition-all duration-75\"\n        style={{\n          clipPath:\n            variant === \"vertical-slide\"\n              ? `inset(0 0 ${100 - sliderPos}% 0)`\n              : variant === \"circle-magnifier\"\n                ? `circle(120px at ${sliderPos}% ${sliderPosY}%)`\n                : `inset(0 ${100 - sliderPos}% 0 0)`,\n        }}\n      >\n        <img\n          src={afterImage}\n          alt={afterLabel}\n          className=\"absolute inset-0 w-full h-full object-cover pointer-events-none\"\n        />\n      </div>\n\n      {/* BEFORE & AFTER LABELS */}\n      <div className=\"absolute top-4 left-4 z-20 pointer-events-none\">\n        <span className=\"px-3 py-1.5 rounded-full text-xs font-extrabold bg-black/60 text-white backdrop-blur-md border border-white/20 shadow-md\">\n          {beforeLabel}\n        </span>\n      </div>\n      <div className=\"absolute top-4 right-4 z-20 pointer-events-none\">\n        <span\n          className=\"px-3 py-1.5 rounded-full text-xs font-extrabold text-white backdrop-blur-md border shadow-md\"\n          style={{\n            backgroundColor: isPrimary ? \"color-mix(in oklab, var(--primary) 80%, transparent)\" : `rgba(${activeRgb}, 0.8)`,\n            borderColor: isPrimary ? \"color-mix(in oklab, var(--primary) 40%, transparent)\" : `rgba(${activeRgb}, 0.4)`,\n          }}\n        >\n          {afterLabel}\n        </span>\n      </div>\n\n      {/* VARIANT 1: CLASSIC SLIDER */}\n      {variant === \"classic-slider\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-1 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 w-9 h-9 rounded-full bg-card border-2 shadow-2xl flex items-center justify-center pointer-events-auto\"\n            style={{ borderColor: activeColor, color: activeColor }}\n          >\n            <ChevronsLeftRight className=\"w-5 h-5\" />\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 2: SPLIT HANDLE */}\n      {variant === \"split-handle\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-0.5 transition-all duration-75 pointer-events-none bg-white/80\"\n          style={{ left: `${sliderPos}%` }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 px-3 py-1.5 rounded-xl border text-xs font-bold tracking-wider uppercase shadow-xl flex items-center gap-1.5 pointer-events-auto text-primary-foreground\"\n            style={{\n              backgroundColor: activeColor,\n              borderColor: \"rgba(255,255,255,0.4)\",\n            }}\n          >\n            <Split className=\"w-4 h-4\" />\n            Drag\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 3: GLOWING DIVIDER */}\n      {variant === \"glowing-divider\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-1 shadow-[0_0_20px_4px] transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor, color: activeColor }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 rounded-full flex items-center justify-center shadow-2xl text-primary-foreground\"\n            style={{ backgroundColor: activeColor }}\n          >\n            <Sparkles className=\"w-5 h-5\" />\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 4: BADGE OVERLAY */}\n      {variant === \"badge-overlay\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-1 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute bottom-6 -translate-x-1/2 px-4 py-2 rounded-2xl backdrop-blur-md border shadow-2xl text-xs font-extrabold flex items-center gap-2 pointer-events-auto text-primary-foreground\"\n            style={{\n              backgroundColor: isPrimary ? \"color-mix(in oklab, var(--primary) 85%, transparent)\" : `rgba(${activeRgb}, 0.85)`,\n              borderColor: \"rgba(255,255,255,0.3)\",\n            }}\n          >\n            <Sliders className=\"w-4 h-4\" />\n            {Math.round(sliderPos)}% Compare\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 5: MINIMAL LINE */}\n      {variant === \"minimal-line\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-0.5 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 w-4 h-4 rounded-full shadow-lg border border-black/20\"\n            style={{ backgroundColor: activeColor }}\n          />\n        </div>\n      )}\n\n      {/* VARIANT 6: HOVER REVEAL */}\n      {variant === \"hover-reveal\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-0.5 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-6 -translate-x-1/2 px-3 py-1 rounded-full text-[10px] font-bold uppercase shadow-lg text-primary-foreground\"\n            style={{ backgroundColor: activeColor }}\n          >\n            Hover Reveal\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 7: VERTICAL SLIDE */}\n      {variant === \"vertical-slide\" && (\n        <div\n          className=\"absolute left-0 right-0 z-30 h-1 transition-all duration-75 pointer-events-none\"\n          style={{ top: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute left-1/2 -translate-x-1/2 -translate-y-1/2 w-9 h-9 rounded-full bg-card border-2 shadow-2xl flex items-center justify-center pointer-events-auto\"\n            style={{ borderColor: activeColor, color: activeColor }}\n          >\n            <ChevronsLeftRight className=\"w-5 h-5 rotate-90\" />\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 8: CIRCLE MAGNIFIER */}\n      {variant === \"circle-magnifier\" && (\n        <div\n          className=\"absolute -translate-x-1/2 -translate-y-1/2 z-30 w-[240px] h-[240px] rounded-full border-4 shadow-2xl pointer-events-none transition-all duration-75\"\n          style={{ left: `${sliderPos}%`, top: `${sliderPosY}%`, borderColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-2 left-1/2 -translate-x-1/2 px-2.5 py-0.5 rounded-full text-[10px] font-bold shadow-md text-primary-foreground\"\n            style={{ backgroundColor: activeColor }}\n          >\n            Lens Scope\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 9: GLASS HANDLE */}\n      {variant === \"glass-handle\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-1 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 w-12 h-12 rounded-2xl bg-white/20 dark:bg-black/30 backdrop-blur-xl border-2 shadow-2xl flex items-center justify-center pointer-events-auto text-white\"\n            style={{ borderColor: activeColor }}\n          >\n            <Layers className=\"w-5 h-5\" />\n          </div>\n        </div>\n      )}\n\n      {/* VARIANT 10: DIAGONAL SPLIT */}\n      {variant === \"diagonal-split\" && (\n        <div\n          className=\"absolute top-0 bottom-0 z-30 w-1 transition-all duration-75 pointer-events-none\"\n          style={{ left: `${sliderPos}%`, backgroundColor: activeColor }}\n        >\n          <div\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 rounded-xl rotate-45 shadow-2xl flex items-center justify-center pointer-events-auto text-primary-foreground\"\n            style={{ backgroundColor: activeColor }}\n          >\n            <Scan className=\"w-5 h-5 -rotate-45\" />\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/ImageCompareBlock.tsx"
    }
  ],
  "docs": "Named export: ImageCompare. `beforeImage` and `afterImage` take URLs, with `beforeLabel` / `afterLabel` for the badges and `initialPosition` as a 0-100 percentage. Variants: classic-slider | split-handle | glowing-divider | badge-overlay | minimal. Both images should share an aspect ratio or the reveal will misalign.",
  "categories": [
    "media"
  ],
  "type": "registry:component"
}
