{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-background",
  "title": "Aurora Background",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Soft, slowly drifting aurora light made of blurred token-colored glows with fine vertical curtains and an optional radial fade. Pure CSS layers, paused off-screen, still under reduced motion.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/aurora-background.tsx",
      "content": "// Ballmac UI: Aurora Background. https://ui.ballmac.com/components/aurora-background\n\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype AuroraBackgroundProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Colors of the light, from left to right. Any CSS colors or var(). */\n  colors?: string[]\n  /** Seconds for one slow drift of the light. Higher is calmer. */\n  duration?: number\n  /** Overall strength of the light (0–1). */\n  intensity?: number\n  /** Blur radius of the light in pixels. */\n  blur?: number\n  /** Draw fine vertical curtains through the light, like a real aurora. */\n  curtains?: boolean\n  /** Fade the aurora out toward the bottom and sides with an elliptical mask. */\n  radialMask?: boolean\n}\n\n// Where each light sits and how it drifts: [left %, top %, width %, drift x %, drift y %, scale].\nconst lights = [\n  [-10, -14, 62, 14, 8, 1.15],\n  [20, -22, 58, -10, 12, 1.2],\n  [46, -16, 60, 12, 6, 1.1],\n  [68, -24, 52, -14, 10, 1.25],\n] as const\n\nfunction AuroraBackground({\n  colors = [\"var(--chart-1)\", \"var(--chart-2)\", \"var(--chart-4)\", \"var(--chart-1)\"],\n  duration = 22,\n  intensity = 0.75,\n  blur = 64,\n  curtains = true,\n  radialMask = true,\n  className,\n  style,\n  ...props\n}: AuroraBackgroundProps) {\n  const rootRef = React.useRef<HTMLDivElement>(null)\n\n  React.useEffect(() => {\n    const root = rootRef.current\n    if (!root || typeof root.animate !== \"function\") return\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return\n    const animations: Animation[] = []\n    root.querySelectorAll<HTMLElement>(\"[data-slot=aurora-light]\").forEach((node, i) => {\n      const [, , , dx, dy, scale] = lights[i % lights.length]!\n      animations.push(\n        node.animate(\n          [\n            { transform: \"translate3d(0, 0, 0) scale(1) rotate(0deg)\" },\n            { transform: `translate3d(${dx}%, ${dy}%, 0) scale(${scale}) rotate(${i % 2 ? -8 : 8}deg)` },\n          ],\n          {\n            duration: duration * 1000 * (0.8 + i * 0.15),\n            delay: -i * 3000,\n            direction: \"alternate\",\n            iterations: Infinity,\n            easing: \"cubic-bezier(0.45, 0, 0.55, 1)\",\n          }\n        )\n      )\n    })\n    const rays = root.querySelector<HTMLElement>(\"[data-slot=aurora-curtains]\")\n    if (rays) {\n      animations.push(\n        rays.animate([{ backgroundPosition: \"0% 0%\" }, { backgroundPosition: \"200% 0%\" }], {\n          duration: duration * 2500,\n          iterations: Infinity,\n          easing: \"linear\",\n        })\n      )\n    }\n    let visible = true\n    const sync = () => {\n      for (const a of animations) {\n        if (visible && !document.hidden) a.play()\n        else a.pause()\n      }\n    }\n    const io = new IntersectionObserver(([entry]) => {\n      visible = entry?.isIntersecting ?? true\n      sync()\n    })\n    io.observe(root)\n    document.addEventListener(\"visibilitychange\", sync)\n    return () => {\n      io.disconnect()\n      document.removeEventListener(\"visibilitychange\", sync)\n      for (const a of animations) a.cancel()\n    }\n  }, [duration, colors.length, curtains])\n\n  const mask = radialMask ? \"radial-gradient(ellipse 100% 80% at 50% 0%, black 35%, transparent 85%)\" : undefined\n\n  return (\n    <div\n      ref={rootRef}\n      aria-hidden=\"true\"\n      data-slot=\"aurora-background\"\n      className={cn(\"pointer-events-none absolute inset-0 overflow-hidden\", className)}\n      style={{ maskImage: mask, WebkitMaskImage: mask, ...style }}\n      {...props}\n    >\n      <div\n        className=\"absolute -inset-[10%] will-change-transform\"\n        style={{ filter: `blur(${blur}px) saturate(1.2)`, opacity: intensity }}\n      >\n        {colors.map((color, i) => {\n          const [left, top, width] = lights[i % lights.length]!\n          return (\n            <span\n              key={i}\n              data-slot=\"aurora-light\"\n              className=\"absolute block aspect-[1.6/1] rounded-full\"\n              style={{\n                left: `${left + (i >= lights.length ? 10 : 0)}%`,\n                top: `${top}%`,\n                width: `${width}%`,\n                background: `radial-gradient(closest-side, ${color}, color-mix(in oklch, ${color} 40%, transparent) 55%, transparent)`,\n              }}\n            />\n          )\n        })}\n      </div>\n      {curtains && (\n        <div\n          data-slot=\"aurora-curtains\"\n          className=\"absolute -inset-[5%] opacity-60 blur-[6px]\"\n          style={{\n            // Stripes of the page color cut soft vertical curtains through the light.\n            backgroundImage:\n              \"repeating-linear-gradient(98deg, var(--background) 0%, var(--background) 4%, transparent 7%, transparent 9%, var(--background) 13%)\",\n            backgroundSize: \"200% 100%\",\n            maskImage: \"linear-gradient(to bottom, black, transparent 85%)\",\n            WebkitMaskImage: \"linear-gradient(to bottom, black, transparent 85%)\",\n          }}\n        />\n      )}\n    </div>\n  )\n}\n\nexport { AuroraBackground, type AuroraBackgroundProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/aurora-background.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "aurora",
      "gradient",
      "glow",
      "background",
      "hero",
      "decorative"
    ],
    "version": "1.0.0",
    "updated": "2026-09-29",
    "ai": {
      "summary": "Place <AuroraBackground /> as the first child of a relative, overflow-hidden hero; it is absolute inset-0, aria-hidden and pointer-events-none. The light sits along the top edge and fades down, so headline text reads on the page background.",
      "whenToUse": [
        "Behind a hero headline or a launch announcement",
        "Sign-in or waitlist pages that need atmosphere without imagery"
      ],
      "whenNotToUse": [
        "Behind dense content or data",
        "Several stacked sections (use once per page, at the top)"
      ],
      "composesWith": [
        "text-reveal",
        "shimmer-text",
        "magnetic-button"
      ],
      "a11y": [],
      "customization": [
        "colors: array of CSS colors, default chart-1, chart-2, chart-4, chart-1",
        "duration (s, default 22), intensity (0–1, default 0.75), blur (px, default 64)",
        "curtains toggles the vertical ray texture; radialMask toggles the elliptical fade"
      ]
    },
    "examples": [
      "aurora-background-demo",
      "aurora-background-warm"
    ],
    "url": "https://ui.ballmac.com/components/aurora-background"
  },
  "categories": [
    "backgrounds"
  ],
  "type": "registry:ui"
}