{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feature-carousel",
  "type": "registry:ui",
  "description": "Feature carousel component with smooth transitions and customizable layouts",
  "dependencies": [
    "motion",
    "clsx",
    "react-wrap-balancer"
  ],
  "files": [
    {
      "path": "registry/default/ui/feature-carousel.tsx",
      "content": "/*\n ! Add the following to your .globals.css\n\n       .animated-cards::before {\n         @apply pointer-events-none absolute select-none rounded-3xl opacity-0 transition-opacity duration-300 hover:opacity-100;\n         background: radial-gradient(\n           1000px circle at var(--x) var(--y),\n           #c9ee80 0,\n           #eebbe2 10%,\n           #adc0ec 25%,\n           #c9ee80 35%,\n           rgba(255, 255, 255, 0) 50%,\n           transparent 80%\n         );\n         z-index: -1;\n         content: \"\";\n         inset: -1px;\n       }\n*/\n\"use client\"\n\nimport {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n  type MouseEvent,\n} from \"react\"\nimport Image, { type StaticImageData } from \"next/image\"\nimport clsx from \"clsx\"\nimport {\n  AnimatePresence,\n  motion,\n  useMotionTemplate,\n  useMotionValue,\n  type MotionStyle,\n  type MotionValue,\n  type Variants,\n} from \"motion/react\"\nimport Balancer from \"react-wrap-balancer\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Types\ntype WrapperStyle = MotionStyle & {\n  \"--x\": MotionValue<string>\n  \"--y\": MotionValue<string>\n}\n\ninterface CardProps {\n  title: string\n  description: string\n  bgClass?: string\n}\n\ninterface ImageSet {\n  step1dark1?: StaticImageData | string\n  step1dark2?: StaticImageData | string\n  step1light1: StaticImageData | string\n  step1light2: StaticImageData | string\n  step2dark1?: StaticImageData | string\n  step2dark2?: StaticImageData | string\n  step2light1: StaticImageData | string\n  step2light2: StaticImageData | string\n  step3dark?: StaticImageData | string\n  step3light: StaticImageData | string\n  step4light: StaticImageData | string\n  alt: string\n}\n\ninterface FeatureCarouselProps extends CardProps {\n  step1img1Class?: string\n  step1img2Class?: string\n  step2img1Class?: string\n  step2img2Class?: string\n  step3imgClass?: string\n  step4imgClass?: string\n  image: ImageSet\n}\n\ninterface StepImageProps {\n  src: StaticImageData | string\n  alt: string\n  className?: string\n  style?: React.CSSProperties\n  width?: number\n  height?: number\n}\n\ninterface Step {\n  id: string\n  name: string\n  title: string\n  description: string\n}\n\n// Constants\nconst TOTAL_STEPS = 4\n\nconst steps = [\n  {\n    id: \"1\",\n    name: \"Step 1\",\n    title: \"Feature 1\",\n    description: \"Feature 1 description  \",\n  },\n  {\n    id: \"2\",\n    name: \"Step 2\",\n    title: \"Feature 2\",\n    description: \"Feature 2 description\",\n  },\n  {\n    id: \"3\",\n    name: \"Step 3\",\n    title: \"Feature 3\",\n    description: \"Feature 3 description\",\n  },\n  {\n    id: \"4\",\n    name: \"Step 4\",\n    title: \"Feature 4\",\n    description: \"Feature 4 description\",\n  },\n] as const\n\n/**\n * Animation presets for reusable motion configurations.\n * Each preset defines the initial, animate, and exit states,\n * along with spring physics parameters for smooth transitions.\n */\nconst ANIMATION_PRESETS = {\n  fadeInScale: {\n    initial: { opacity: 0, scale: 0.95 },\n    animate: { opacity: 1, scale: 1 },\n    exit: { opacity: 0, scale: 0.95 },\n    transition: {\n      type: \"spring\",\n      stiffness: 300, // Higher value = more rigid spring\n      damping: 25, // Higher value = less oscillation\n      mass: 0.5, // Lower value = faster movement\n    },\n  },\n  slideInRight: {\n    initial: { opacity: 0, x: 20 },\n    animate: { opacity: 1, x: 0 },\n    exit: { opacity: 0, x: -20 },\n    transition: {\n      type: \"spring\",\n      stiffness: 300,\n      damping: 25,\n      mass: 0.5,\n    },\n  },\n  slideInLeft: {\n    initial: { opacity: 0, x: -20 },\n    animate: { opacity: 1, x: 0 },\n    exit: { opacity: 0, x: 20 },\n    transition: {\n      type: \"spring\",\n      stiffness: 300,\n      damping: 25,\n      mass: 0.5,\n    },\n  },\n} as const\n\ntype AnimationPreset = keyof typeof ANIMATION_PRESETS\n\ninterface AnimatedStepImageProps extends StepImageProps {\n  preset?: AnimationPreset\n  delay?: number\n  onAnimationComplete?: () => void\n}\n\n/**\n * Custom hook for managing cyclic transitions with auto-play functionality.\n * Handles both automatic cycling and manual transitions between steps.\n */\nfunction useNumberCycler(\n  totalSteps: number = TOTAL_STEPS,\n  interval: number = 3000\n) {\n  const [currentNumber, setCurrentNumber] = useState(0)\n  const [isManualInteraction, setIsManualInteraction] = useState(false)\n  const timerRef = useRef<NodeJS.Timeout | null>(null)\n\n  // Setup timer function\n  const setupTimer = useCallback(() => {\n    console.log(\"Setting up timer\")\n    // Clear any existing timer\n    if (timerRef.current) {\n      clearTimeout(timerRef.current)\n    }\n\n    timerRef.current = setTimeout(() => {\n      console.log(\"Timer triggered, advancing to next step\")\n      setCurrentNumber((prev) => (prev + 1) % totalSteps)\n      setIsManualInteraction(false)\n      // Recursively setup next timer\n      setupTimer()\n    }, interval)\n  }, [interval, totalSteps])\n\n  // Handle manual increment\n  const increment = useCallback(() => {\n    console.log(\"Manual increment triggered\")\n    setIsManualInteraction(true)\n    setCurrentNumber((prev) => (prev + 1) % totalSteps)\n\n    // Reset timer on manual interaction\n    setupTimer()\n  }, [totalSteps, setupTimer])\n\n  // Initial timer setup and cleanup\n  useEffect(() => {\n    console.log(\"Initial timer setup\")\n    setupTimer()\n\n    return () => {\n      console.log(\"Cleaning up timer\")\n      if (timerRef.current) {\n        clearTimeout(timerRef.current)\n      }\n    }\n  }, [setupTimer])\n\n  // Debug logging\n  useEffect(() => {\n    console.log(\"Current state:\", {\n      currentNumber,\n      isManualInteraction,\n      hasTimer: !!timerRef.current,\n    })\n  }, [currentNumber, isManualInteraction])\n\n  return {\n    currentNumber,\n    increment,\n    isManualInteraction,\n  }\n}\n\nfunction useIsMobile() {\n  const [isMobile, setIsMobile] = useState(false)\n\n  useEffect(() => {\n    const userAgent = navigator.userAgent\n    const isSmall = window.matchMedia(\"(max-width: 768px)\").matches\n    const isMobile = Boolean(\n      /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.exec(\n        userAgent\n      )\n    )\n\n    const isDev = process.env.NODE_ENV !== \"production\"\n    if (isDev) setIsMobile(isSmall || isMobile)\n\n    setIsMobile(isSmall && isMobile)\n  }, [])\n\n  return isMobile\n}\n\n// Components\nfunction IconCheck({ className, ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 256 256\"\n      fill=\"currentColor\"\n      className={cn(\"h-4 w-4\", className)}\n      {...props}\n    >\n      <path d=\"m229.66 77.66-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69 218.34 66.34a8 8 0 0 1 11.32 11.32Z\" />\n    </svg>\n  )\n}\n\nconst stepVariants: Variants = {\n  inactive: {\n    scale: 0.8,\n    opacity: 0.5,\n  },\n  active: {\n    scale: 1,\n    opacity: 1,\n  },\n}\n\nconst StepImage = forwardRef<\n  HTMLImageElement,\n  StepImageProps & { [key: string]: any }\n>(\n  (\n    { src, alt, className, style, width = 1200, height = 630, ...props },\n    ref\n  ) => {\n    return (\n      <Image\n        ref={ref}\n        alt={alt}\n        className={className}\n        src={src}\n        width={width}\n        height={height}\n        style={{\n          position: \"absolute\",\n          userSelect: \"none\",\n          maxWidth: \"unset\",\n          ...style,\n        }}\n        {...props}\n      />\n    )\n  }\n)\nStepImage.displayName = \"StepImage\"\n\nconst MotionStepImage = motion(StepImage)\n\n/**\n * Wrapper component for StepImage that applies animation presets.\n * Simplifies the application of complex animations through preset configurations.\n */\nconst AnimatedStepImage = ({\n  preset = \"fadeInScale\",\n  delay = 0,\n  onAnimationComplete,\n  ...props\n}: AnimatedStepImageProps) => {\n  const presetConfig = ANIMATION_PRESETS[preset]\n  return (\n    <MotionStepImage\n      {...props}\n      {...presetConfig}\n      transition={{\n        ...presetConfig.transition,\n        delay,\n      }}\n      onAnimationComplete={onAnimationComplete}\n    />\n  )\n}\n\n/**\n * Main card component that handles mouse tracking for gradient effect.\n * Uses motion values to create an interactive gradient that follows the cursor.\n */\nfunction FeatureCard({\n  bgClass,\n  children,\n  step,\n}: CardProps & {\n  children: React.ReactNode\n  step: number\n}) {\n  const [mounted, setMounted] = useState(false)\n  const mouseX = useMotionValue(0)\n  const mouseY = useMotionValue(0)\n  const isMobile = useIsMobile()\n\n  function handleMouseMove({ currentTarget, clientX, clientY }: MouseEvent) {\n    if (isMobile) return\n    const { left, top } = currentTarget.getBoundingClientRect()\n    mouseX.set(clientX - left)\n    mouseY.set(clientY - top)\n  }\n\n  useEffect(() => {\n    setMounted(true)\n  }, [])\n\n  return (\n    <motion.div\n      className=\"animated-cards relative w-full rounded-[16px]\"\n      onMouseMove={handleMouseMove}\n      style={\n        {\n          \"--x\": useMotionTemplate`${mouseX}px`,\n          \"--y\": useMotionTemplate`${mouseY}px`,\n        } as WrapperStyle\n      }\n    >\n      <div\n        className={clsx(\n          \"group relative w-full overflow-hidden rounded-3xl border border-black/10 bg-gradient-to-b from-neutral-900/90 to-stone-800 transition duration-300 dark:from-neutral-950/90 dark:to-neutral-800/90\",\n          \"md:hover:border-transparent\",\n          bgClass\n        )}\n      >\n        <div className=\"m-10 min-h-[450px] w-full\">\n          <AnimatePresence mode=\"wait\">\n            <motion.div\n              key={step}\n              className=\"flex w-4/6 flex-col gap-3\"\n              initial={{ opacity: 0, y: 20 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0, y: -20 }}\n              transition={{\n                duration: 0.3,\n                ease: [0.23, 1, 0.32, 1],\n              }}\n            >\n              <motion.h2\n                className=\"text-xl font-bold tracking-tight text-white md:text-2xl\"\n                initial={{ opacity: 0, x: -20 }}\n                animate={{ opacity: 1, x: 0 }}\n                transition={{\n                  delay: 0.1,\n                  duration: 0.3,\n                  ease: [0.23, 1, 0.32, 1],\n                }}\n              >\n                {steps[step].title}\n              </motion.h2>\n              <motion.div\n                initial={{ opacity: 0, x: -20 }}\n                animate={{ opacity: 1, x: 0 }}\n                transition={{\n                  delay: 0.2,\n                  duration: 0.3,\n                  ease: [0.23, 1, 0.32, 1],\n                }}\n              >\n                <p className=\"text-sm leading-5 text-neutral-300 sm:text-base sm:leading-5 dark:text-zinc-400\">\n                  <Balancer>{steps[step].description}</Balancer>\n                </p>\n              </motion.div>\n            </motion.div>\n          </AnimatePresence>\n          {mounted ? children : null}\n        </div>\n      </div>\n    </motion.div>\n  )\n}\n\n/**\n * Progress indicator component that shows current step and completion status.\n * Handles complex state transitions and animations for step indicators.\n */\nfunction Steps({\n  steps,\n  current,\n  onChange,\n}: {\n  steps: readonly Step[]\n  current: number\n  onChange: (index: number) => void\n}) {\n  return (\n    <nav aria-label=\"Progress\" className=\"flex justify-center px-4\">\n      <ol\n        className=\"flex w-full flex-wrap items-start justify-start gap-2 sm:justify-center md:w-10/12 md:divide-y-0\"\n        role=\"list\"\n      >\n        {steps.map((step, stepIdx) => {\n          // Calculate step states for styling and animations\n          const isCompleted = current > stepIdx\n          const isCurrent = current === stepIdx\n          const isFuture = !isCompleted && !isCurrent\n\n          return (\n            <motion.li\n              key={`${step.name}-${stepIdx}`}\n              initial=\"inactive\"\n              animate={isCurrent ? \"active\" : \"inactive\"}\n              variants={stepVariants}\n              transition={{ duration: 0.3 }}\n              className={cn(\n                \"relative z-50 rounded-full px-3 py-1 transition-all duration-300 ease-in-out md:flex\",\n                isCompleted ? \"bg-neutral-500/20\" : \"bg-neutral-500/10\"\n              )}\n            >\n              <div\n                className={cn(\n                  \"group flex w-full cursor-pointer items-center focus:outline-none focus-visible:ring-2\",\n                  (isFuture || isCurrent) && \"pointer-events-none\"\n                )}\n                onClick={() => onChange(stepIdx)}\n              >\n                <span className=\"flex items-center gap-2 text-sm font-medium\">\n                  <motion.span\n                    initial={false}\n                    animate={{\n                      scale: isCurrent ? 1.2 : 1,\n                    }}\n                    className={cn(\n                      \"flex h-4 w-4 shrink-0 items-center justify-center rounded-full duration-300\",\n                      isCompleted &&\n                        \"bg-brand-400 dark:bg-brand-400 text-white\",\n                      isCurrent &&\n                        \"bg-brand-300/80 text-neutral-400 dark:bg-neutral-500/50\",\n                      isFuture && \"bg-brand-300/10 dark:bg-neutral-500/20\"\n                    )}\n                  >\n                    {isCompleted ? (\n                      <motion.div\n                        initial={{ scale: 0 }}\n                        animate={{ scale: 1 }}\n                        transition={{\n                          type: \"spring\",\n                          stiffness: 300,\n                          damping: 20,\n                        }}\n                      >\n                        <IconCheck className=\"h-3 w-3 stroke-white stroke-[3] text-white dark:stroke-black\" />\n                      </motion.div>\n                    ) : (\n                      <span\n                        className={cn(\n                          \"text-xs\",\n                          !isCurrent && \"text-[#C6EA7E]\"\n                        )}\n                      >\n                        {stepIdx + 1}\n                      </span>\n                    )}\n                  </motion.span>\n                  <motion.span\n                    initial={{ opacity: 0, x: -10 }}\n                    animate={{ opacity: 1, x: 0 }}\n                    className={clsx(\n                      \"text-sm font-medium duration-300\",\n                      isCompleted && \"text-muted-foreground\",\n                      isCurrent && \"text-lime-300 dark:text-lime-500\",\n                      isFuture && \"text-neutral-500\"\n                    )}\n                  >\n                    {step.name}\n                  </motion.span>\n                </span>\n              </div>\n            </motion.li>\n          )\n        })}\n      </ol>\n    </nav>\n  )\n}\n\nconst defaultClasses = {\n  step1img1:\n    \"pointer-events-none w-[50%] border border-border-100/10 transition-all duration-500 dark:border-border-700/50 rounded-2xl\",\n  step1img2:\n    \"pointer-events-none w-[60%] border border-border-100/10 dark:border-border-700/50 transition-all duration-500 overflow-hidden rounded-2xl\",\n  step2img1:\n    \"pointer-events-none w-[50%] border border-border-100/10 transition-all duration-500 dark:border-border-700 rounded-2xl overflow-hidden\",\n  step2img2:\n    \"pointer-events-none w-[40%] border border-border-100/10 dark:border-border-700 transition-all duration-500 rounded-2xl overflow-hidden\",\n  step3img:\n    \"pointer-events-none w-[90%] border border-border-100/10 dark:border-border-700 rounded-2xl transition-all duration-500 overflow-hidden\",\n  step4img:\n    \"pointer-events-none w-[90%] border border-border-100/10 dark:border-border-700 rounded-2xl transition-all duration-500 overflow-hidden\",\n} as const\n\n/**\n * Main component that orchestrates the multi-step animation sequence.\n * Manages state transitions, handles animation timing, and prevents\n * animation conflicts through the isAnimating flag.\n */\nexport function FeatureCarousel({\n  image,\n  step1img1Class = defaultClasses.step1img1,\n  step1img2Class = defaultClasses.step1img2,\n  step2img1Class = defaultClasses.step2img1,\n  step2img2Class = defaultClasses.step2img2,\n  step3imgClass = defaultClasses.step3img,\n  step4imgClass = defaultClasses.step4img,\n  ...props\n}: FeatureCarouselProps) {\n  const { currentNumber: step, increment } = useNumberCycler()\n  const [isAnimating, setIsAnimating] = useState(false)\n\n  const handleIncrement = () => {\n    if (isAnimating) return\n    setIsAnimating(true)\n    increment()\n  }\n\n  const handleAnimationComplete = () => {\n    setIsAnimating(false)\n  }\n\n  const renderStepContent = () => {\n    const content = () => {\n      switch (step) {\n        case 0:\n          /**\n           * Layout: Two images side by side\n           * - Left image (step1img1): 50% width, positioned left\n           * - Right image (step1img2): 60% width, positioned right\n           * Animation:\n           * - Left image slides in from left\n           * - Right image slides in from right with 0.1s delay\n           * - Both use spring animation for smooth motion\n           */\n          return (\n            <motion.div\n              className=\"relative h-full w-full\"\n              onAnimationComplete={handleAnimationComplete}\n            >\n              <AnimatedStepImage\n                alt={image.alt}\n                className={clsx(step1img1Class)}\n                src={image.step1light1}\n                preset=\"slideInLeft\"\n              />\n              <AnimatedStepImage\n                alt={image.alt}\n                className={clsx(step1img2Class)}\n                src={image.step1light2}\n                preset=\"slideInRight\"\n                delay={0.1}\n              />\n            </motion.div>\n          )\n        case 1:\n          /**\n           * Layout: Two images with overlapping composition\n           * - First image (step2img1): 50% width, positioned left\n           * - Second image (step2img2): 40% width, overlaps first image\n           * Animation:\n           * - Both images fade in and scale up from 95%\n           * - Second image has 0.1s delay for staggered effect\n           * - Uses spring physics for natural motion\n           */\n          return (\n            <motion.div\n              className=\"relative h-full w-full\"\n              onAnimationComplete={handleAnimationComplete}\n            >\n              <AnimatedStepImage\n                alt={image.alt}\n                className={clsx(step2img1Class, \"rounded-2xl\")}\n                src={image.step2light1}\n                preset=\"fadeInScale\"\n              />\n              <AnimatedStepImage\n                alt={image.alt}\n                className={clsx(step2img2Class, \"rounded-2xl\")}\n                src={image.step2light2}\n                preset=\"fadeInScale\"\n                delay={0.1}\n              />\n            </motion.div>\n          )\n        case 2:\n          /**\n           * Layout: Single centered image\n           * - Full width image (step3img): 90% width, centered\n           * Animation:\n           * - Fades in and scales up from 95%\n           * - Uses spring animation for smooth scaling\n           * - Triggers animation complete callback\n           */\n          return (\n            <AnimatedStepImage\n              alt={image.alt}\n              className={clsx(step3imgClass, \"rounded-2xl\")}\n              src={image.step3light}\n              preset=\"fadeInScale\"\n              onAnimationComplete={handleAnimationComplete}\n            />\n          )\n        case 3:\n          /**\n           * Layout: Final showcase layout\n           * - Container: Centered, 60% width on desktop\n           * - Image (cult): 90% width, positioned slightly up\n           * Animation:\n           * - Container fades in and scales up\n           * - Image follows with 0.1s delay\n           * - Both use spring physics for natural motion\n           */\n          return (\n            <motion.div\n              className={clsx(\n                \"absolute top-1/3 left-2/4 flex w-[100%] -translate-x-1/2 -translate-y-[33%] flex-col gap-12 text-center text-2xl font-bold md:w-[60%]\"\n              )}\n              {...ANIMATION_PRESETS.fadeInScale}\n              onAnimationComplete={handleAnimationComplete}\n            >\n              <AnimatedStepImage\n                alt={image.alt}\n                className=\"pointer-events-none top-[50%] w-[90%] overflow-hidden rounded-2xl border border-neutral-100/10 md:top-[30%] md:left-[35px] md:w-full dark:border-zinc-700\"\n                src=\"/cults.png\"\n                preset=\"fadeInScale\"\n                delay={0.1}\n              />\n            </motion.div>\n          )\n        default:\n          return null\n      }\n    }\n\n    return (\n      <AnimatePresence mode=\"wait\">\n        <motion.div\n          key={step}\n          {...ANIMATION_PRESETS.fadeInScale}\n          className=\"absolute h-full w-full\"\n        >\n          {content()}\n        </motion.div>\n      </AnimatePresence>\n    )\n  }\n\n  return (\n    <FeatureCard {...props} step={step}>\n      {renderStepContent()}\n      <motion.div\n        initial={{ opacity: 0 }}\n        animate={{ opacity: 1 }}\n        transition={{ delay: 0.2 }}\n        className=\"absolute top-5 left-[12rem] z-50 h-full w-full cursor-pointer md:left-0\"\n      >\n        <Steps current={step} onChange={() => {}} steps={steps} />\n      </motion.div>\n      <motion.div\n        className=\"absolute top-0 right-0 z-50 h-full w-full cursor-pointer md:left-0\"\n        onClick={handleIncrement}\n        whileHover={{ scale: 1.02 }}\n        whileTap={{ scale: 0.98 }}\n      />\n    </FeatureCard>\n  )\n}\n\nexport default FeatureCarousel\n",
      "type": "registry:ui"
    }
  ]
}