{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "facescape",
  "type": "registry:ui",
  "title": "Facescape",
  "description": "Interactive, animated user avatars with hover effects and responsive layout.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": ["@scrollxui/avatar", "@scrollxui/tooltip"],
  "dependencies": [
    "@radix-ui/react-avatar",
    "motion",
    "@radix-ui/react-tooltip",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/facescape.tsx",
      "content": "'use client';\nimport React, { useState, useEffect, useRef } from 'react';\nimport { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n  TooltipProvider,\n} from '@/components/ui/tooltip';\nimport { cn } from '@/lib/utils';\n\nexport interface AvatarData {\n  src: string;\n  alt: string;\n  fallback: string;\n  name: string;\n}\n\nexport interface FacescapeProps {\n  avatars: AvatarData[];\n  className?: string;\n  colorDuration?: number;\n  variant?: 'circle' | 'square' | 'squircle';\n}\n\nconst BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536 };\n\nconst useBreakpoint = (breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl') => {\n  const [isBelow, setIsBelow] = useState(false);\n  useEffect(() => {\n    const handleResize = () =>\n      setIsBelow(window.innerWidth < BREAKPOINTS[breakpoint]);\n    handleResize();\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, [breakpoint]);\n  return isBelow;\n};\n\nconst FacescapeItem = React.forwardRef<\n  HTMLDivElement,\n  {\n    className?: string;\n    src: string;\n    alt: string;\n    fallback: string;\n    name: string;\n    colorDuration?: number;\n    autoAnimate?: boolean;\n    variant?: 'circle' | 'square' | 'squircle';\n  }\n>(\n  (\n    {\n      className,\n      src,\n      alt,\n      fallback,\n      name,\n      colorDuration = 3000,\n      autoAnimate = false,\n      variant = 'squircle',\n      ...props\n    },\n    ref,\n  ) => {\n    const [isHovered, setIsHovered] = useState(false);\n    const [isColorful, setIsColorful] = useState(false);\n    const [isLarge, setIsLarge] = useState(false);\n    const [isVisible, setIsVisible] = useState(false);\n    const itemRef = useRef<HTMLDivElement>(null);\n\n    useEffect(() => {\n      if (ref) {\n        if (typeof ref === 'function') {\n          ref(itemRef.current);\n        } else {\n          ref.current = itemRef.current;\n        }\n      }\n    }, [ref]);\n\n    useEffect(() => {\n      if (!autoAnimate || !itemRef.current) return;\n      const observer = new IntersectionObserver(\n        ([entry]) => setIsVisible(entry.isIntersecting),\n        { threshold: 0.3 },\n      );\n      observer.observe(itemRef.current);\n      return () => observer.disconnect();\n    }, [autoAnimate]);\n\n    const active = autoAnimate ? isVisible : isHovered;\n\n    useEffect(() => {\n      let colorTimeout: NodeJS.Timeout | undefined;\n      let sizeTimeout: NodeJS.Timeout | undefined;\n      if (!active) {\n        if (isColorful)\n          colorTimeout = setTimeout(() => setIsColorful(false), colorDuration);\n        if (isLarge)\n          sizeTimeout = setTimeout(() => setIsLarge(false), colorDuration);\n      } else {\n        colorTimeout = setTimeout(() => setIsColorful(true), 0);\n        sizeTimeout = setTimeout(() => setIsLarge(true), 0);\n      }\n      return () => {\n        if (colorTimeout) clearTimeout(colorTimeout);\n        if (sizeTimeout) clearTimeout(sizeTimeout);\n      };\n    }, [active, isColorful, isLarge, colorDuration]);\n\n    const shapeClass = {\n      circle: 'rounded-full',\n      square: 'rounded-none',\n      squircle: 'rounded-md',\n    }[variant];\n\n    return (\n      <TooltipProvider>\n        <Tooltip>\n          <TooltipTrigger asChild>\n            <div\n              ref={itemRef}\n              className={cn(\n                'relative cursor-pointer transition-all duration-500 ease-in-out transform-gpu origin-center',\n                isLarge ? 'scale-150 z-10' : 'scale-100',\n                isColorful\n                  ? 'grayscale-0 contrast-100 brightness-100 opacity-100'\n                  : 'grayscale contrast-50 brightness-75 opacity-60',\n                className,\n              )}\n              onMouseEnter={() => !autoAnimate && setIsHovered(true)}\n              onMouseLeave={() => !autoAnimate && setIsHovered(false)}\n              {...props}\n            >\n              <Avatar className={cn('h-8 w-8', shapeClass)}>\n                <AvatarImage src={src} alt={alt} />\n                <AvatarFallback>{fallback}</AvatarFallback>\n              </Avatar>\n            </div>\n          </TooltipTrigger>\n          <TooltipContent>\n            <p>{name}</p>\n          </TooltipContent>\n        </Tooltip>\n      </TooltipProvider>\n    );\n  },\n);\n\nFacescapeItem.displayName = 'FacescapeItem';\n\nconst Facescape = React.forwardRef<HTMLDivElement, FacescapeProps>(\n  (\n    {\n      avatars,\n      className,\n      colorDuration = 3000,\n      variant = 'squircle',\n      ...props\n    },\n    ref,\n  ) => {\n    const isMobileOrTablet = useBreakpoint('lg');\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          'flex flex-wrap w-full justify-center gap-x-3 gap-y-4',\n          className,\n        )}\n        {...props}\n      >\n        {avatars.map((avatar, index) => (\n          <div\n            key={index}\n            className='flex justify-center items-center w-10 h-10'\n          >\n            <FacescapeItem\n              src={avatar.src}\n              alt={avatar.alt}\n              fallback={avatar.fallback}\n              name={avatar.name}\n              colorDuration={colorDuration}\n              autoAnimate={isMobileOrTablet}\n              variant={variant}\n            />\n          </div>\n        ))}\n      </div>\n    );\n  },\n);\n\nFacescape.displayName = 'Facescape';\n\nexport { Facescape };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/avatar.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport * as AvatarPrimitive from '@radix-ui/react-avatar';\nimport { cn } from '@/lib/utils';\n\ntype AvatarProps = React.ComponentPropsWithoutRef<\n  typeof AvatarPrimitive.Root\n> & {\n  variant?: 'close-friends' | 'normal' | 'none';\n};\n\nconst Avatar = React.forwardRef<\n  React.ComponentRef<typeof AvatarPrimitive.Root>,\n  AvatarProps\n>(({ className, variant = 'none', ...props }, ref) => {\n  const ringClass =\n    variant === 'close-friends'\n      ? 'bg-linear-to-tr from-green-400 to-green-600'\n      : variant === 'normal'\n        ? 'bg-[conic-gradient(at_top_right,#f09433,#e6683c,#dc2743,#cc2366,#bc1888,#f09433)]'\n        : '';\n\n  return variant === 'none' ? (\n    <AvatarPrimitive.Root\n      ref={ref}\n      className={cn(\n        'relative flex h-12 w-12 shrink-0 overflow-hidden rounded-full',\n        className,\n      )}\n      {...props}\n    />\n  ) : (\n    <div\n      className={cn(\n        'h-14 w-14 rounded-full p-0.5',\n        ringClass,\n        'flex items-center justify-center',\n      )}\n    >\n      <div className='h-full w-full rounded-full bg-black flex items-center justify-center overflow-hidden'>\n        <AvatarPrimitive.Root\n          ref={ref}\n          className={cn(\n            'h-full w-full rounded-full overflow-hidden',\n            className,\n          )}\n          {...props}\n        />\n      </div>\n    </div>\n  );\n});\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n  React.ComponentRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn('h-full w-full object-cover not-prose', className)}\n    {...props}\n  />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n  React.ComponentRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      'flex h-full w-full items-center justify-center rounded-full bg-muted text-sm font-medium',\n      className,\n    )}\n    {...props}\n  />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/tooltip.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport { motion } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot='tooltip-provider'\n      delayDuration={delayDuration}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot='tooltip' {...props} />\n    </TooltipProvider>\n  );\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot='tooltip-trigger' {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot='tooltip-content'\n        sideOffset={sideOffset}\n        className='z-50'\n        {...props}\n        asChild\n      >\n        <motion.div\n          initial={{\n            opacity: 0,\n            scale: 0.3,\n            y: 20,\n          }}\n          animate={{\n            opacity: 1,\n            scale: 1,\n            y: 0,\n            x: [0, -4, 4, -3, 3, -2, 2, -1, 1, 0],\n          }}\n          exit={{\n            opacity: 0,\n            scale: 0.8,\n            y: 10,\n            x: [0, -3, 3, -2, 2, -1, 1, 0],\n          }}\n          transition={{\n            opacity: { duration: 0.2 },\n            scale: {\n              duration: 0.6,\n              type: 'spring',\n              damping: 12,\n              stiffness: 400,\n              mass: 0.8,\n            },\n            y: {\n              duration: 0.5,\n              type: 'spring',\n              damping: 10,\n              stiffness: 300,\n              mass: 0.9,\n            },\n            x: {\n              duration: 0.7,\n              delay: 0.2,\n              ease: 'easeOut',\n              times: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1],\n            },\n          }}\n          className={cn(\n            'bg-primary text-primary-foreground w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n            className,\n          )}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className='bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-xs' />\n        </motion.div>\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\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}"
    }
  ]
}
