{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gradient-orb",
  "title": "Gradient Orb",
  "description": "A mesh gradient orb generated from any string. The same string always gives the same orb.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/registry/kinematic/ui/gradient-orb.tsx",
      "content": "import type * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst BLOB_COUNT = 4\nconst HUE_OFFSETS = [0, 34, -42, 72]\nconst HUE_START = 150\nconst HUE_SPAN = 290\n\ntype Blob = { x: number; y: number; size: number; color: string }\n\nexport type OrbPalette = { base: string; blobs: Blob[] }\n\nfunction hashString(value: string) {\n  let hash = 2166136261\n  for (let index = 0; index < value.length; index += 1) {\n    hash ^= value.charCodeAt(index)\n    hash = Math.imul(hash, 16777619)\n  }\n  return hash >>> 0\n}\n\nfunction seededRandom(seed: number) {\n  let state = seed\n  return () => {\n    state = (state + 0x6d2b79f5) | 0\n    let mixed = Math.imul(state ^ (state >>> 15), 1 | state)\n    mixed = (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed)) ^ mixed\n    return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296\n  }\n}\n\nfunction between(random: () => number, min: number, max: number) {\n  return min + random() * (max - min)\n}\n\n/** The colours `GradientOrb` would use for a seed, to reuse elsewhere. */\nexport function orbPalette(seed: string): OrbPalette {\n  const random = seededRandom(hashString(seed))\n  const hue = (HUE_START + Math.floor(random() * HUE_SPAN)) % 360\n  const blobs = Array.from({ length: BLOB_COUNT }, (_, index) => {\n    const blobHue = (hue + HUE_OFFSETS[index] + between(random, -12, 12) + 360) % 360\n    const lightness = between(random, 0.7, 0.9).toFixed(3)\n    const chroma = between(random, 0.13, 0.2).toFixed(3)\n    return {\n      x: Math.round(between(random, 5, 95)),\n      y: Math.round(between(random, 5, 95)),\n      size: Math.round(between(random, 45, 80)),\n      color: `oklch(${lightness} ${chroma} ${blobHue.toFixed(1)})`,\n    }\n  })\n  return { base: `oklch(0.68 0.16 ${hue})`, blobs }\n}\n\nfunction meshBackground({ base, blobs }: OrbPalette) {\n  const layers = blobs.map(\n    ({ x, y, size, color }) => `radial-gradient(circle at ${x}% ${y}%, ${color} 0%, transparent ${size}%)`\n  )\n  return [...layers, base].join(\", \")\n}\n\nconst LIGHTING = [\n  \"radial-gradient(circle at 32% 26%, rgb(255 255 255 / 0.45) 0%, transparent 42%)\",\n  \"radial-gradient(circle at 70% 82%, rgb(0 0 0 / 0.12) 0%, transparent 55%)\",\n].join(\", \")\n\nexport type GradientOrbProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** Any string. The same seed always gives the same orb. Also used as its accessible label. */\n  seed: string\n  /** Slowly drifts the gradient. Paused for reduced motion. @default false */\n  animated?: boolean\n}\n\n/** A mesh gradient avatar generated from a string. Size it with `className`, e.g. `size-10`. */\nexport function GradientOrb({ seed, animated = false, className, style, ...props }: GradientOrbProps) {\n  return (\n    <span\n      role=\"img\"\n      aria-label={seed}\n      data-slot=\"gradient-orb\"\n      className={cn(\"relative inline-block shrink-0 rounded-full\", className)}\n      style={style}\n      {...props}\n    >\n      {/* clip-path, not just overflow + radius: WebKit stops clipping the blurred layer to the radius once an\n          ancestor is transformed (as shared elements are mid-morph), and the orb renders as a square. */}\n      <span aria-hidden className=\"absolute inset-0 isolate overflow-hidden rounded-full [clip-path:circle(50%)]\">\n        <span\n          className={cn(\n            \"absolute inset-[-25%] blur-[6px]\",\n            animated && \"motion-safe:animate-[spin_9s_linear_infinite]\"\n          )}\n          style={{ background: meshBackground(orbPalette(seed)) }}\n        />\n        <span className=\"absolute inset-0\" style={{ background: LIGHTING }} />\n      </span>\n      <span aria-hidden className=\"absolute inset-0 rounded-full ring-1 ring-black/10 ring-inset dark:ring-white/10\" />\n    </span>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}