{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-backdrop",
  "type": "registry:ui",
  "title": "Aurora Backdrop",
  "description": "A wash of blurred colour that drifts behind content on cycles that never line up.",
  "registryDependencies": [
    "utils",
    "use-in-viewport"
  ],
  "files": [
    {
      "path": "registry/loomui/aurora-backdrop.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useInViewport } from \"@/registry/lib/use-in-viewport\"\n\nexport interface AuroraBackdropProps extends React.ComponentProps<\"div\"> {\n  /** Colours of the drifting blobs. One blob is drawn per colour. */\n  colors?: string[]\n  /** Blur applied to the whole wash, as a CSS length. */\n  blur?: string\n  /** Seconds for one full drift of a single blob. */\n  duration?: number\n  /** Opacity of the layer, from `0` to `1`. */\n  intensity?: number\n  /** Changes where the blobs sit. Same seed, same layout, every render. */\n  seed?: number\n  /** Render the wash static. */\n  disabled?: boolean\n}\n\nconst DEFAULT_COLORS = [\"#2dd4bf\", \"#38bdf8\", \"#3b82f6\", \"#a78bfa\"]\n\n/** Mulberry32. Deterministic so the server and the client agree. */\nfunction createRandom(seed: number) {\n  let state = seed >>> 0\n  return () => {\n    state = (state + 0x6d2b79f5) >>> 0\n    let t = state\n    t = Math.imul(t ^ (t >>> 15), t | 1)\n    t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n  }\n}\n\nexport function AuroraBackdrop({\n  colors = DEFAULT_COLORS,\n  blur = \"72px\",\n  duration = 18,\n  intensity = 0.5,\n  seed = 1,\n  disabled = false,\n  className,\n  style,\n  ...props\n}: AuroraBackdropProps) {\n  const blobs = React.useMemo(() => {\n    const palette = colors.length > 0 ? colors : DEFAULT_COLORS\n    const random = createRandom(seed)\n\n    return palette.map((color, index) => ({\n      key: index,\n      color,\n      // Well under the container, so the drift is a blob crossing the frame\n      // rather than a full-bleed wash shifting a few percent.\n      size: 38 + random() * 32,\n      x: random() * 100,\n      y: random() * 100,\n      // Every blob runs the same keyframes at a different length and phase.\n      // Cycles that never line up are what stops the wash reading as a loop.\n      duration: duration * (0.75 + random() * 0.6),\n      delay: -random() * duration,\n    }))\n  }, [colors, seed, duration])\n\n  const root = React.useRef<HTMLDivElement>(null)\n  // Large blurred gradients are the most expensive thing here to keep drifting\n  // for a viewport nobody is looking at.\n  const onScreen = useInViewport(root)\n\n  return (\n    <div\n      ref={root}\n      data-slot=\"aurora-backdrop\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"pointer-events-none absolute inset-0 overflow-hidden\",\n        className\n      )}\n      style={{ opacity: intensity, ...style }}\n      {...props}\n    >\n      {blobs.map((blob) => (\n        <span\n          key={blob.key}\n          className={cn(\n            \"absolute rounded-full\",\n            !disabled && \"animate-aurora-drift motion-reduce:animate-none\",\n            !onScreen && \"[animation-play-state:paused]\"\n          )}\n          style={{\n            width: `${blob.size}%`,\n            height: `${blob.size}%`,\n            left: `${blob.x}%`,\n            top: `${blob.y}%`,\n            // The blobs are placed by their centres, so one sitting at the edge\n            // still bleeds off it instead of stopping short.\n            translate: \"-50% -50%\",\n            // An ellipse, not a circle: a circle sized to the closest side\n            // leaves the corners of a wide blob empty.\n            background: `radial-gradient(closest-side, ${blob.color}, transparent)`,\n            filter: `blur(${blur})`,\n            animationDuration: `${blob.duration}s`,\n            animationDelay: `${blob.delay}s`,\n          }}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "cssVars": {
    "theme": {
      "ease-in-out-cubic": "cubic-bezier(0.645, 0.045, 0.355, 1)",
      "animate-aurora-drift": "aurora-drift 18s var(--ease-in-out-cubic) infinite"
    }
  },
  "css": {
    "@keyframes aurora-drift": {
      "0%, 100%": {
        "transform": "translate(0%, 0%) scale(1)"
      },
      "33%": {
        "transform": "translate(26%, -22%) scale(1.35)"
      },
      "66%": {
        "transform": "translate(-24%, 18%) scale(0.75)"
      }
    }
  }
}