{
  "name": "compare",
  "type": "registry:ui",
  "dependencies": [
    "@tabler/icons-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/compare.tsx",
      "content": "\"use client\";\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { SparklesCore } from \"@/components/ui/sparkles\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\nimport { IconDotsVertical } from \"@tabler/icons-react\";\n\ninterface CompareProps {\n  firstImage?: string;\n  secondImage?: string;\n  className?: string;\n  firstImageClassName?: string;\n  secondImageClassname?: string;\n  initialSliderPercentage?: number;\n  slideMode?: \"hover\" | \"drag\";\n  showHandlebar?: boolean;\n  autoplay?: boolean;\n  autoplayDuration?: number;\n}\nexport const Compare = ({\n  firstImage = \"\",\n  secondImage = \"\",\n  className,\n  firstImageClassName,\n  secondImageClassname,\n  initialSliderPercentage = 50,\n  slideMode = \"hover\",\n  showHandlebar = true,\n  autoplay = false,\n  autoplayDuration = 5000,\n}: CompareProps) => {\n  const [sliderXPercent, setSliderXPercent] = useState(initialSliderPercentage);\n  const [isDragging, setIsDragging] = useState(false);\n\n  const sliderRef = useRef<HTMLDivElement>(null);\n\n  const [isMouseOver, setIsMouseOver] = useState(false);\n\n  const autoplayRef = useRef<NodeJS.Timeout | null>(null);\n\n  const startAutoplay = useCallback(() => {\n    if (!autoplay) return;\n\n    const startTime = Date.now();\n    const animate = () => {\n      const elapsedTime = Date.now() - startTime;\n      const progress =\n        (elapsedTime % (autoplayDuration * 2)) / autoplayDuration;\n      const percentage = progress <= 1 ? progress * 100 : (2 - progress) * 100;\n\n      setSliderXPercent(percentage);\n      autoplayRef.current = setTimeout(animate, 16); // ~60fps\n    };\n\n    animate();\n  }, [autoplay, autoplayDuration]);\n\n  const stopAutoplay = useCallback(() => {\n    if (autoplayRef.current) {\n      clearTimeout(autoplayRef.current);\n      autoplayRef.current = null;\n    }\n  }, []);\n\n  useEffect(() => {\n    startAutoplay();\n    return () => stopAutoplay();\n  }, [startAutoplay, stopAutoplay]);\n\n  function mouseEnterHandler() {\n    setIsMouseOver(true);\n    stopAutoplay();\n  }\n\n  function mouseLeaveHandler() {\n    setIsMouseOver(false);\n    if (slideMode === \"hover\") {\n      setSliderXPercent(initialSliderPercentage);\n    }\n    if (slideMode === \"drag\") {\n      setIsDragging(false);\n    }\n    startAutoplay();\n  }\n\n  const handleStart = useCallback(\n    (clientX: number) => {\n      if (slideMode === \"drag\") {\n        setIsDragging(true);\n      }\n    },\n    [slideMode]\n  );\n\n  const handleEnd = useCallback(() => {\n    if (slideMode === \"drag\") {\n      setIsDragging(false);\n    }\n  }, [slideMode]);\n\n  const handleMove = useCallback(\n    (clientX: number) => {\n      if (!sliderRef.current) return;\n      if (slideMode === \"hover\" || (slideMode === \"drag\" && isDragging)) {\n        const rect = sliderRef.current.getBoundingClientRect();\n        const x = clientX - rect.left;\n        const percent = (x / rect.width) * 100;\n        requestAnimationFrame(() => {\n          setSliderXPercent(Math.max(0, Math.min(100, percent)));\n        });\n      }\n    },\n    [slideMode, isDragging]\n  );\n\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent) => handleStart(e.clientX),\n    [handleStart]\n  );\n  const handleMouseUp = useCallback(() => handleEnd(), [handleEnd]);\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent) => handleMove(e.clientX),\n    [handleMove]\n  );\n\n  const handleTouchStart = useCallback(\n    (e: React.TouchEvent) => {\n      if (!autoplay) {\n        handleStart(e.touches[0].clientX);\n      }\n    },\n    [handleStart, autoplay]\n  );\n\n  const handleTouchEnd = useCallback(() => {\n    if (!autoplay) {\n      handleEnd();\n    }\n  }, [handleEnd, autoplay]);\n\n  const handleTouchMove = useCallback(\n    (e: React.TouchEvent) => {\n      if (!autoplay) {\n        handleMove(e.touches[0].clientX);\n      }\n    },\n    [handleMove, autoplay]\n  );\n\n  return (\n    <div\n      ref={sliderRef}\n      className={cn(\"w-[400px] h-[400px] overflow-hidden\", className)}\n      style={{\n        position: \"relative\",\n        cursor: slideMode === \"drag\" ? \"grab\" : \"col-resize\",\n      }}\n      onMouseMove={handleMouseMove}\n      onMouseLeave={mouseLeaveHandler}\n      onMouseEnter={mouseEnterHandler}\n      onMouseDown={handleMouseDown}\n      onMouseUp={handleMouseUp}\n      onTouchStart={handleTouchStart}\n      onTouchEnd={handleTouchEnd}\n      onTouchMove={handleTouchMove}\n    >\n      <AnimatePresence initial={false}>\n        <motion.div\n          className=\"h-full w-px absolute top-0 m-auto z-30 bg-gradient-to-b from-transparent from-[5%] to-[95%] via-indigo-500 to-transparent\"\n          style={{\n            left: `${sliderXPercent}%`,\n            top: \"0\",\n            zIndex: 40,\n          }}\n          transition={{ duration: 0 }}\n        >\n          <div className=\"w-36 h-full [mask-image:radial-gradient(100px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-indigo-400 via-transparent to-transparent z-20 opacity-50\" />\n          <div className=\"w-10 h-1/2 [mask-image:radial-gradient(50px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-cyan-400 via-transparent to-transparent z-10 opacity-100\" />\n          <div className=\"w-10 h-3/4 top-1/2 -translate-y-1/2 absolute -right-10 [mask-image:radial-gradient(100px_at_left,white,transparent)]\">\n            <MemoizedSparklesCore\n              background=\"transparent\"\n              minSize={0.4}\n              maxSize={1}\n              particleDensity={1200}\n              className=\"w-full h-full\"\n              particleColor=\"#FFFFFF\"\n            />\n          </div>\n          {showHandlebar && (\n            <div className=\"h-5 w-5 rounded-md top-1/2 -translate-y-1/2 bg-white z-30 -right-2.5 absolute   flex items-center justify-center shadow-[0px_-1px_0px_0px_#FFFFFF40]\">\n              <IconDotsVertical className=\"h-4 w-4 text-black\" />\n            </div>\n          )}\n        </motion.div>\n      </AnimatePresence>\n      <div className=\"overflow-hidden w-full h-full relative z-20 pointer-events-none\">\n        <AnimatePresence initial={false}>\n          {firstImage ? (\n            <motion.div\n              className={cn(\n                \"absolute inset-0 z-20 rounded-2xl shrink-0 w-full h-full select-none overflow-hidden\",\n                firstImageClassName\n              )}\n              style={{\n                clipPath: `inset(0 ${100 - sliderXPercent}% 0 0)`,\n              }}\n              transition={{ duration: 0 }}\n            >\n              <img\n                alt=\"first image\"\n                src={firstImage}\n                className={cn(\n                  \"absolute inset-0  z-20 rounded-2xl shrink-0 w-full h-full select-none\",\n                  firstImageClassName\n                )}\n                draggable={false}\n              />\n            </motion.div>\n          ) : null}\n        </AnimatePresence>\n      </div>\n\n      <AnimatePresence initial={false}>\n        {secondImage ? (\n          <motion.img\n            className={cn(\n              \"absolute top-0 left-0 z-[19]  rounded-2xl w-full h-full select-none\",\n              secondImageClassname\n            )}\n            alt=\"second image\"\n            src={secondImage}\n            draggable={false}\n          />\n        ) : null}\n      </AnimatePresence>\n    </div>\n  );\n};\n\nconst MemoizedSparklesCore = React.memo(SparklesCore);\n",
      "type": "registry:ui",
      "target": "components/ui/compare.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Compare"
}