{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flashy-card",
  "type": "registry:ui",
  "title": "Flashy Card",
  "description": "Interactive card with smooth animated activation and visual effects.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/flashy-card.tsx",
      "content": "'use client';\nimport React, { useState, useRef, useEffect } from 'react';\nimport { cn } from '@/lib/utils';\n\ninterface FlashyCardProps extends React.HTMLAttributes<HTMLDivElement> {\n  children?: React.ReactNode;\n  onStateChange?: (isActive: boolean) => void;\n  disabled?: boolean;\n  iconClassName?: string;\n  rippleClassName?: string;\n  glareClassName?: string;\n  defaultSrc?: string;\n  activeSrc?: string;\n  activeType?: 'image' | 'video';\n}\n\ninterface FlashyCardContentProps {\n  children: React.ReactNode;\n}\n\nconst FlashyCardDefault: React.FC<FlashyCardContentProps> = ({ children }) => {\n  return <>{children}</>;\n};\n\nconst FlashyCardActive: React.FC<FlashyCardContentProps> = ({ children }) => {\n  return <>{children}</>;\n};\n\nconst FlashyCard = React.forwardRef<HTMLDivElement, FlashyCardProps>(\n  (\n    {\n      children,\n      className,\n      onStateChange,\n      disabled = false,\n      iconClassName,\n      rippleClassName,\n      glareClassName,\n      defaultSrc,\n      activeSrc,\n      activeType,\n      ...props\n    },\n    ref,\n  ) => {\n    const [isSelected, setIsSelected] = useState(false);\n    const [isButtonHovered, setIsButtonHovered] = useState(false);\n    const [isAnimating, setIsAnimating] = useState(false);\n    const [videoReady, setVideoReady] = useState(false);\n    const glareRef = useRef<HTMLDivElement>(null);\n    const cardRef = useRef<HTMLDivElement>(null);\n    const videoRef = useRef<HTMLVideoElement>(null);\n\n    React.useImperativeHandle(ref, () => cardRef.current as HTMLDivElement);\n\n    const childArray = React.Children.toArray(children);\n    const defaultContentChild = childArray.find(\n      (child) =>\n        React.isValidElement(child) && child.type === FlashyCardDefault,\n    );\n    const activeContentChild = childArray.find(\n      (child) => React.isValidElement(child) && child.type === FlashyCardActive,\n    );\n\n    const defaultContent =\n      defaultContentChild ||\n      (defaultSrc && (\n        <div className='aspect-4/3 relative'>\n          <img\n            src={defaultSrc}\n            alt='Default content'\n            className='w-full h-full object-cover'\n          />\n        </div>\n      ));\n\n    const isVideo = (src: string) => {\n      const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov'];\n      return videoExtensions.some((ext) => src.toLowerCase().endsWith(ext));\n    };\n\n    const shouldShowVideo =\n      activeType === 'video' ||\n      (!activeType && activeSrc && isVideo(activeSrc));\n\n    useEffect(() => {\n      if (shouldShowVideo && videoRef.current) {\n        const video = videoRef.current;\n        video.load();\n        const handleCanPlay = () => setVideoReady(true);\n        video.addEventListener('canplay', handleCanPlay);\n        return () => video.removeEventListener('canplay', handleCanPlay);\n      }\n    }, [shouldShowVideo]);\n\n    const activeContent =\n      activeContentChild ||\n      (activeSrc && (\n        <div className='aspect-4/3 relative'>\n          {shouldShowVideo ? (\n            <video\n              key='active-video'\n              ref={videoRef}\n              src={activeSrc}\n              autoPlay\n              loop\n              muted\n              playsInline\n              preload='auto'\n              className='w-full h-full object-cover'\n            />\n          ) : (\n            <img\n              src={activeSrc}\n              alt='Active content'\n              className='w-full h-full object-cover'\n            />\n          )}\n        </div>\n      ));\n\n    const handleClick = () => {\n      if (isAnimating || disabled) return;\n\n      setIsAnimating(true);\n\n      const card = cardRef.current;\n\n      if (!isSelected) {\n        if (videoRef.current) {\n          videoRef.current.currentTime = 0;\n          videoRef.current.play();\n        }\n\n        if (card) {\n          card.style.transition =\n            'border-color 300ms ease, box-shadow 300ms ease';\n          card.style.borderColor = 'hsl(var(--primary))';\n          card.style.boxShadow = '0 25px 50px -12px hsl(var(--primary) / 0.3)';\n        }\n\n        setTimeout(() => {\n          const el = glareRef.current;\n          if (el) {\n            el.style.transition = 'none';\n            el.style.backgroundPosition = '-100% -100%, 0 0';\n\n            requestAnimationFrame(() => {\n              el.style.transition = '650ms ease';\n              el.style.backgroundPosition = '100% 100%, 0 0';\n            });\n          }\n        }, 300);\n\n        setTimeout(() => {\n          setIsSelected(true);\n          setIsAnimating(false);\n          onStateChange?.(true);\n        }, 950);\n      } else {\n        if (card) {\n          card.style.transition =\n            'border-color 300ms ease, box-shadow 300ms ease';\n          card.style.borderColor = 'hsl(var(--border))';\n          card.style.boxShadow = 'none';\n        }\n\n        setTimeout(() => {\n          const el = glareRef.current;\n          if (el) {\n            el.style.transition = 'none';\n            el.style.backgroundPosition = '-100% -100%, 0 0';\n\n            requestAnimationFrame(() => {\n              el.style.transition = '650ms ease';\n              el.style.backgroundPosition = '100% 100%, 0 0';\n            });\n          }\n        }, 300);\n\n        setTimeout(() => {\n          setIsSelected(false);\n          setIsAnimating(false);\n          onStateChange?.(false);\n        }, 950);\n      }\n    };\n\n    return (\n      <div\n        ref={cardRef}\n        className={cn(\n          'relative rounded-xl bg-card border-2 transition-all duration-300',\n          isSelected ? 'border-primary shadow-2xl' : 'border-border',\n          className,\n        )}\n        {...props}\n      >\n        <div\n          ref={glareRef}\n          className={cn(\n            'absolute inset-0 pointer-events-none z-20 rounded-xl',\n            glareClassName,\n          )}\n          style={{\n            background:\n              'linear-gradient(-45deg, hsla(0,0%,0%,0) 60%, rgba(255,255,255,0.5) 70%, hsla(0,0%,0%,0) 100%)',\n            backgroundSize: '250% 250%, 100% 100%',\n            backgroundRepeat: 'no-repeat',\n            backgroundPosition: '-100% -100%, 0 0',\n          }}\n        />\n\n        <div className='relative rounded-xl overflow-hidden bg-card'>\n          <div\n            className={cn(\n              'transition-opacity duration-300',\n              isSelected ? 'opacity-100' : 'opacity-0 absolute inset-0',\n            )}\n          >\n            {activeContent}\n          </div>\n          <div\n            className={cn(\n              'transition-opacity duration-300',\n              !isSelected ? 'opacity-100' : 'opacity-0 absolute inset-0',\n            )}\n          >\n            {defaultContent}\n          </div>\n        </div>\n\n        {shouldShowVideo && !isSelected && (\n          <div className='absolute inset-0 pointer-events-none opacity-0 -z-10'>\n            <video\n              src={activeSrc}\n              loop\n              muted\n              playsInline\n              preload='auto'\n              className='w-full h-full object-cover'\n            />\n          </div>\n        )}\n\n        <div className='relative h-0 z-30'>\n          <div className='absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 z-30'>\n            {!isButtonHovered && !isSelected && !disabled && (\n              <div\n                className={cn(\n                  'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-24 h-24 z-30 pointer-events-none',\n                  rippleClassName,\n                )}\n              >\n                <div\n                  className='absolute inset-0 border-2 border-primary/30 rounded-full'\n                  style={{\n                    animation:\n                      'ripple 2.5s cubic-bezier(0, 0.2, 0.8, 1) infinite',\n                  }}\n                />\n                <div\n                  className='absolute inset-0 border-2 border-primary/40 rounded-full'\n                  style={{\n                    animation:\n                      'ripple 2.5s cubic-bezier(0, 0.2, 0.8, 1) infinite 0.5s',\n                  }}\n                />\n                <div\n                  className='absolute inset-0 border-2 border-primary/50 rounded-full'\n                  style={{\n                    animation: 'ripple 2.5s ease-out infinite 1s',\n                  }}\n                />\n              </div>\n            )}\n\n            <button\n              onClick={handleClick}\n              onMouseEnter={() => setIsButtonHovered(true)}\n              onMouseLeave={() => setIsButtonHovered(false)}\n              disabled={isAnimating || disabled}\n              aria-label={isSelected ? 'Deactivate card' : 'Activate card'}\n              className={cn(\n                'relative z-40 w-14 h-14 rounded-full flex items-center justify-center transition-all duration-300',\n                isAnimating || disabled ? 'opacity-50 cursor-not-allowed' : '',\n                isButtonHovered || isSelected\n                  ? 'bg-primary shadow-lg shadow-primary/50 hover:scale-110 active:scale-95'\n                  : 'bg-transparent border-2 border-primary/50',\n                iconClassName,\n              )}\n            >\n              <svg\n                className={cn(\n                  'w-7 h-7 transition-colors duration-300',\n                  isButtonHovered || isSelected\n                    ? 'text-primary-foreground'\n                    : 'text-primary',\n                )}\n                fill='none'\n                viewBox='0 0 24 24'\n                stroke='currentColor'\n                strokeWidth={2.5}\n              >\n                {isSelected ? (\n                  <path\n                    strokeLinecap='round'\n                    strokeLinejoin='round'\n                    d='M6 18L18 6M6 6l12 12'\n                  />\n                ) : (\n                  <path\n                    strokeLinecap='round'\n                    strokeLinejoin='round'\n                    d='M12 4v16m8-8H4'\n                  />\n                )}\n              </svg>\n            </button>\n          </div>\n        </div>\n\n        <style jsx>{`\n          @keyframes ripple {\n            0% {\n              transform: scale(0.5);\n              opacity: 1;\n            }\n            100% {\n              transform: scale(3);\n              opacity: 0;\n            }\n          }\n        `}</style>\n      </div>\n    );\n  },\n);\n\nFlashyCard.displayName = 'FlashyCard';\nFlashyCardDefault.displayName = 'FlashyCardDefault';\nFlashyCardActive.displayName = 'FlashyCardActive';\n\nexport { FlashyCard, FlashyCardDefault, FlashyCardActive };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
