{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-avatar",
  "title": "Agent Avatar",
  "description": "A deterministic SVG face for an agent: the same seed always draws the same overlapping blobs, in theme colours.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/agent-avatar.tsx",
      "content": "import type * as React from \"react\"\nimport { useMemo } from \"react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst DEFAULT_SIZE_PX = 40\nconst VIEWBOX = 100\nconst CENTRE = VIEWBOX / 2\nconst BLOB_COUNT = 3\n/** Each blob is drawn through this many points; six is organic without turning spiky. */\nconst BLOB_POINTS = 6\nconst BLOB_RADIUS_MIN = 18\nconst BLOB_RADIUS_RANGE = 16\n/** How far a point may stray from the blob's mean radius: low is a pebble, high is a splat. */\nconst BLOB_WOBBLE = 0.3\n/** Blob centres stay this close to the middle so no face is empty on one side. */\nconst BLOB_OFFSET_MAX = 22\nconst OPACITY_MIN = 0.55\nconst OPACITY_RANGE = 0.4\nconst DECIMALS = 1\nconst TWO_PI = Math.PI * 2\nconst UINT32_RANGE = 4294967296\n/** Ink comes from tokens only; the hash picks and dims, so every avatar sits inside the theme. */\nconst PALETTE = [\"var(--mise)\", \"var(--foreground)\", \"var(--muted-foreground)\"] as const\nconst BACKGROUND = \"var(--muted)\"\nconst CLIP_CIRCLE = \"circle(50% at 50% 50%)\"\n\ninterface Point {\n  x: number\n  y: number\n}\n\ninterface Blob {\n  d: string\n  fill: string\n  opacity: number\n}\n\ninterface AgentAvatarProps extends React.ComponentProps<\"svg\"> {\n  /** Any stable string (agent id, name). The same seed always draws the same face. */\n  seed: string\n  /** Width and height in pixels. */\n  size?: number\n  /** Overrides the accessible name (\"Agent {seed}\"). */\n  label?: string\n}\n\n/** xmur3: folds a string into a 32-bit state and hands out successive hashes of it. */\nfunction xmur3(input: string) {\n  let state = 1779033703 ^ input.length\n  for (let index = 0; index < input.length; index += 1) {\n    state = Math.imul(state ^ input.charCodeAt(index), 3432918353)\n    state = (state << 13) | (state >>> 19)\n  }\n  return () => {\n    state = Math.imul(state ^ (state >>> 16), 2246822507)\n    state = Math.imul(state ^ (state >>> 13), 3266489909)\n    state ^= state >>> 16\n    return state >>> 0\n  }\n}\n\n/** Uniform 0..1 from the hash stream. */\nfunction unit(next: () => number) {\n  return next() / UINT32_RANGE\n}\n\n/** Signed -1..1 from the hash stream. */\nfunction signed(next: () => number) {\n  return unit(next) * 2 - 1\n}\n\nfunction format(point: Point) {\n  return `${point.x.toFixed(DECIMALS)} ${point.y.toFixed(DECIMALS)}`\n}\n\nfunction midpoint(a: Point, b: Point): Point {\n  return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }\n}\n\n/** Quadratic curves through the midpoints: every point is a control, so the outline is always smooth and closed. */\nfunction smoothClosedPath(points: readonly Point[]) {\n  const last = points[points.length - 1]\n  const segments = points.map((point, index) => `Q ${format(point)} ${format(midpoint(point, points[(index + 1) % points.length]))}`)\n  return `M ${format(midpoint(last, points[0]))} ${segments.join(\" \")} Z`\n}\n\nfunction makeBlob(next: () => number, index: number): Blob {\n  const cx = CENTRE + signed(next) * BLOB_OFFSET_MAX\n  const cy = CENTRE + signed(next) * BLOB_OFFSET_MAX\n  const radius = BLOB_RADIUS_MIN + unit(next) * BLOB_RADIUS_RANGE\n  const rotation = unit(next) * TWO_PI\n  const points = Array.from({ length: BLOB_POINTS }, (_, step): Point => {\n    const angle = rotation + (step / BLOB_POINTS) * TWO_PI\n    const reach = radius * (1 + signed(next) * BLOB_WOBBLE)\n    return { x: cx + Math.cos(angle) * reach, y: cy + Math.sin(angle) * reach }\n  })\n  const fill = PALETTE[(next() + index) % PALETTE.length]\n  const opacity = Number((OPACITY_MIN + unit(next) * OPACITY_RANGE).toFixed(2))\n  return { d: smoothClosedPath(points), fill, opacity }\n}\n\nfunction blobsFor(seed: string): readonly Blob[] {\n  const next = xmur3(seed)\n  return Array.from({ length: BLOB_COUNT }, (_, index) => makeBlob(next, index))\n}\n\nfunction AgentAvatar({ seed, size = DEFAULT_SIZE_PX, label, className, style, ...props }: AgentAvatarProps) {\n  const blobs = useMemo(() => blobsFor(seed), [seed])\n\n  return (\n    <svg\n      role=\"img\"\n      aria-label={label ?? `Agent ${seed}`}\n      data-slot=\"agent-avatar\"\n      data-seed={seed}\n      width={size}\n      height={size}\n      viewBox={`0 0 ${VIEWBOX} ${VIEWBOX}`}\n      className={cn(\"shrink-0 overflow-hidden rounded-full\", className)}\n      style={{ ...style, clipPath: CLIP_CIRCLE }}\n      {...props}\n    >\n      <circle cx={CENTRE} cy={CENTRE} r={CENTRE} fill={BACKGROUND} />\n      {blobs.map((blob, index) => (\n        <path key={index} d={blob.d} fill={blob.fill} fillOpacity={blob.opacity} />\n      ))}\n    </svg>\n  )\n}\n\nexport { AgentAvatar, type AgentAvatarProps }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "effects"
  ],
  "type": "registry:ui"
}