{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "b-avatar",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "b-avatar.tsx",
      "content": "\"use client\";\n\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport { motion } from \"motion/react\";\nimport { forwardRef, type HTMLAttributes, useEffect, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst AVATAR_SIZE = 44;\nconst WHITESPACE_REGEX = /\\s+/g;\nconst FALLBACK_SPLIT_REGEX = /[\\s_-]+/;\nconst NON_ALPHANUMERIC_REGEX = /[^\\p{L}\\p{N}]+/gu;\nconst enterEase = [0.16, 1, 0.3, 1] as const;\n\ntype DivHTMLAttributesForMotion = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  | \"children\"\n  | \"onAnimationEnd\"\n  | \"onAnimationIteration\"\n  | \"onAnimationStart\"\n  | \"onDrag\"\n  | \"onDragEnd\"\n  | \"onDragEnter\"\n  | \"onDragExit\"\n  | \"onDragLeave\"\n  | \"onDragOver\"\n  | \"onDragStart\"\n  | \"onDrop\"\n>;\n\nexport interface AvatarProps extends DivHTMLAttributesForMotion {\n  alt?: string;\n  className?: string;\n  fallback?: string;\n  loading?: \"eager\" | \"lazy\";\n  name?: string;\n  src?: string;\n}\n\ntype ImageStatus = \"idle\" | \"loaded\" | \"error\";\n\nfunction normalizeText(value?: string) {\n  return value?.trim().replace(WHITESPACE_REGEX, \" \") ?? \"\";\n}\n\nfunction firstCharacter(value: string) {\n  return Array.from(value)[0] ?? \"\";\n}\n\nfunction getFallbackLabel(fallback?: string, name?: string, alt?: string) {\n  const normalized = [fallback, name, alt].map(normalizeText).find(Boolean);\n\n  if (!normalized) {\n    return \"?\";\n  }\n\n  const words = normalized\n    .split(FALLBACK_SPLIT_REGEX)\n    .map((word) => word.replace(NON_ALPHANUMERIC_REGEX, \"\"))\n    .filter(Boolean);\n\n  if (words.length >= 2) {\n    return `${firstCharacter(words[0])}${firstCharacter(words[1])}`.toUpperCase();\n  }\n\n  const compact = Array.from(\n    words[0] ?? normalized.replace(WHITESPACE_REGEX, \"\")\n  )\n    .slice(0, 2)\n    .join(\"\");\n\n  return compact.toUpperCase() || \"?\";\n}\n\nfunction getAltText(alt?: string, name?: string) {\n  if (alt !== undefined) {\n    return normalizeText(alt);\n  }\n\n  const normalizedName = normalizeText(name);\n  return normalizedName || \"Avatar\";\n}\n\nfunction resolveImageStatus(status: string): ImageStatus {\n  if (status === \"loaded\") {\n    return \"loaded\";\n  }\n\n  if (status === \"error\") {\n    return \"error\";\n  }\n\n  return \"idle\";\n}\n\nconst rootTransition = {\n  type: \"spring\" as const,\n  stiffness: 340,\n  damping: 26,\n  mass: 0.88,\n};\n\nconst imageTransition = {\n  type: \"spring\" as const,\n  stiffness: 300,\n  damping: 24,\n  mass: 0.82,\n};\n\nconst accentTransition = { duration: 0.3, ease: enterEase };\n\nconst hoverLift = { scale: 1.02, y: -1, transition: rootTransition };\n\nconst pressMotion = { scale: 0.995, y: 0, transition: { duration: 0.1 } };\n\nfunction getAccentAnimate(imageStatus: ImageStatus) {\n  return {\n    opacity: imageStatus === \"loaded\" ? 0.18 : 0.34,\n    scale: imageStatus === \"loaded\" ? 1.02 : 1,\n    x: imageStatus === \"loaded\" ? 2 : 0,\n    y: imageStatus === \"loaded\" ? -2 : 0,\n  };\n}\n\nconst accentHover = { opacity: 0.28, scale: 1.03, x: 1, y: -1 };\n\nfunction getFallbackAnimate(imageStatus: ImageStatus) {\n  if (imageStatus !== \"loaded\") {\n    return { opacity: 1, scale: 1 };\n  }\n\n  return { opacity: 0, scale: 0.94 };\n}\n\nfunction getFallbackHover(imageStatus: ImageStatus) {\n  if (imageStatus === \"loaded\") {\n    return undefined;\n  }\n\n  return { scale: 1.02 };\n}\n\nfunction getImageAnimate(imageStatus: ImageStatus) {\n  if (imageStatus === \"loaded\") {\n    return { opacity: 1, scale: 1 };\n  }\n\n  return { opacity: 0, scale: 1.04 };\n}\n\nfunction getImageHover(imageStatus: ImageStatus) {\n  return imageStatus === \"loaded\" ? { scale: 1.03 } : { scale: 1.015 };\n}\n\nfunction getRingAnimate(imageStatus: ImageStatus) {\n  return {\n    opacity: imageStatus === \"loaded\" ? 1 : 0.85,\n    scale: imageStatus === \"loaded\" ? 1 : 0.985,\n  };\n}\n\nconst ringHover = { opacity: 0.92, scale: 1.015 };\n\nconst Avatar = forwardRef<HTMLDivElement, AvatarProps>(\n  (\n    { src, fallback = \"?\", alt, name, loading = \"eager\", className, ...props },\n    ref\n  ) => {\n    const fallbackLabel = getFallbackLabel(fallback, name, alt);\n    const altText = getAltText(alt, name);\n    const [imageStatus, setImageStatus] = useState<ImageStatus>(\n      src ? \"idle\" : \"error\"\n    );\n\n    useEffect(() => {\n      if (!src) {\n        setImageStatus(\"error\");\n        return;\n      }\n\n      let active = true;\n      const image = new window.Image();\n\n      const commitStatus = (status: ImageStatus) => {\n        if (active) {\n          setImageStatus(status);\n        }\n      };\n\n      setImageStatus(\"idle\");\n      image.onload = () => commitStatus(\"loaded\");\n      image.onerror = () => commitStatus(\"error\");\n      image.src = src;\n\n      if (image.complete) {\n        commitStatus(image.naturalWidth > 0 ? \"loaded\" : \"error\");\n      }\n\n      return () => {\n        active = false;\n        image.onload = null;\n        image.onerror = null;\n      };\n    }, [src]);\n\n    return (\n      <motion.div\n        animate={{ opacity: 1, scale: 1, y: 0 }}\n        className={cn(\n          componentThemeClassName,\n          \"relative inline-flex h-11 w-11 shrink-0 items-center justify-center\",\n          className\n        )}\n        initial={{ opacity: 0, scale: 0.96, y: 3 }}\n        ref={ref}\n        transition={rootTransition}\n        whileHover={hoverLift}\n        whileTap={pressMotion}\n        {...props}\n      >\n        <AvatarPrimitive.Root className=\"relative flex h-full w-full items-center justify-center overflow-hidden rounded-full bg-linear-to-br from-neutral-950 via-neutral-800 to-neutral-700 text-white shadow-[0_18px_40px_rgba(15,23,42,0.16)] dark:from-neutral-100 dark:via-neutral-200 dark:to-neutral-300 dark:text-neutral-900\">\n          <motion.div\n            animate={getAccentAnimate(imageStatus)}\n            className=\"pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_30%_25%,rgba(255,255,255,0.38),transparent_55%)] dark:bg-[radial-gradient(circle_at_30%_25%,rgba(255,255,255,0.6),transparent_58%)]\"\n            initial={false}\n            transition={accentTransition}\n            whileHover={accentHover}\n          />\n\n          <motion.span\n            animate={getFallbackAnimate(imageStatus)}\n            className=\"absolute inset-0 flex select-none items-center justify-center font-semibold text-sm uppercase tracking-[0.08em]\"\n            initial={false}\n            transition={accentTransition}\n            whileHover={getFallbackHover(imageStatus)}\n          >\n            {fallbackLabel}\n          </motion.span>\n\n          {src ? (\n            <motion.div\n              animate={getImageAnimate(imageStatus)}\n              className=\"absolute inset-0 overflow-hidden rounded-full\"\n              initial={false}\n              transition={imageTransition}\n              whileHover={getImageHover(imageStatus)}\n            >\n              <AvatarPrimitive.Image\n                alt={altText}\n                className=\"h-full w-full object-cover\"\n                height={AVATAR_SIZE}\n                loading={loading}\n                onLoadingStatusChange={(status) => {\n                  setImageStatus(resolveImageStatus(status));\n                }}\n                src={src}\n                width={AVATAR_SIZE}\n              />\n            </motion.div>\n          ) : null}\n\n          <motion.div\n            animate={getRingAnimate(imageStatus)}\n            className=\"pointer-events-none absolute inset-0 rounded-full ring-1 ring-white/16 ring-inset dark:ring-black/10\"\n            initial={false}\n            transition={accentTransition}\n            whileHover={ringHover}\n          />\n        </AvatarPrimitive.Root>\n      </motion.div>\n    );\n  }\n);\n\nAvatar.displayName = \"Avatar\";\n\nexport { Avatar as avatar };\nexport { Avatar };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Avatar (Base UI)",
  "description": "Avatar with the same single Iconiq API layered over Base UI primitives, keeping immediate fallback initials and a quiet image crossfade."
}
