{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "nebula",
  "title": "Nebula",
  "description": "An animated nebula cloud background using WebGL shaders.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/nebula.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\ninterface NebulaProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Primary nebula color */\n  colorA?: string\n  /** Secondary nebula color */\n  colorB?: string\n  /** Tertiary accent color */\n  colorC?: string\n  /** Show grain/star dust overlay */\n  grain?: boolean\n  /** Animation speed multiplier (1 = default) */\n  speed?: number\n  /** Number of nebula blobs */\n  blobCount?: number\n  /** Visual mode - \"dark\" for space theme, \"light\" for bright/pastel */\n  mode?: \"dark\" | \"light\"\n}\n\nfunction hexToRgba(hex: string, alpha: number): string {\n  const r = parseInt(hex.slice(1, 3), 16)\n  const g = parseInt(hex.slice(3, 5), 16)\n  const b = parseInt(hex.slice(5, 7), 16)\n  return `rgba(${r},${g},${b},${alpha})`\n}\n\nconst Nebula = React.forwardRef<HTMLDivElement, NebulaProps>(\n  (\n    {\n      colorA = \"#6b1d9e\",\n      colorB = \"#0d3b7a\",\n      colorC = \"#c2185b\",\n      grain = true,\n      speed = 1,\n      blobCount = 18,\n      mode = \"dark\",\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const isDark = mode === \"dark\"\n    const bgColor = isDark ? \"#080012\" : \"#f4f0f8\"\n    const canvasRef = React.useRef<HTMLCanvasElement>(null)\n    const containerRef = React.useRef<HTMLDivElement>(null)\n    const uniqueId = React.useId()\n    const filterId = `nebula-grain-${uniqueId}`\n\n    React.useImperativeHandle(ref, () => containerRef.current!)\n\n    React.useEffect(() => {\n      const canvas = canvasRef.current\n      const container = containerRef.current\n      if (!canvas || !container) return\n\n      const ctx = canvas.getContext(\"2d\")\n      if (!ctx) return\n\n      let w = 0\n      let h = 0\n      let rafId = 0\n\n      const colors = [colorA, colorB, colorC]\n\n      interface Blob {\n        x: number\n        y: number\n        r: number\n        color: string\n        phaseX: number\n        phaseY: number\n        freqX: number\n        freqY: number\n        ampX: number\n        ampY: number\n        rotPhase: number\n        rotSpeed: number\n      }\n\n      let blobs: Blob[] = []\n\n      function initBlobs() {\n        blobs = Array.from({ length: blobCount }, (_, i) => {\n          const color = colors[i % colors.length]\n          return {\n            x: Math.random() * w,\n            y: Math.random() * h,\n            r: Math.max(w, h) * (0.15 + Math.random() * 0.25),\n            color,\n            phaseX: Math.random() * Math.PI * 2,\n            phaseY: Math.random() * Math.PI * 2,\n            freqX: 0.15 + Math.random() * 0.35,\n            freqY: 0.12 + Math.random() * 0.3,\n            ampX: w * (0.1 + Math.random() * 0.2),\n            ampY: h * (0.1 + Math.random() * 0.2),\n            rotPhase: Math.random() * Math.PI * 2,\n            rotSpeed: 0.05 + Math.random() * 0.15,\n          }\n        })\n      }\n\n      function resize() {\n        const rect = container!.getBoundingClientRect()\n        const dpr = Math.min(window.devicePixelRatio, 2)\n        w = rect.width\n        h = rect.height\n        canvas!.width = w * dpr\n        canvas!.height = h * dpr\n        canvas!.style.width = `${w}px`\n        canvas!.style.height = `${h}px`\n        ctx!.setTransform(dpr, 0, 0, dpr, 0, 0)\n        initBlobs()\n      }\n\n      function animate(time: number) {\n        const t = time * 0.001 * speed\n\n        ctx!.globalCompositeOperation = \"source-over\"\n        ctx!.fillStyle = mode === \"dark\" ? \"#080012\" : \"#f4f0f8\"\n        ctx!.fillRect(0, 0, w, h)\n\n        // Screen for dark (additive glow), multiply for light (subtractive tint)\n        ctx!.globalCompositeOperation = mode === \"dark\" ? \"screen\" : \"multiply\"\n\n        for (const b of blobs) {\n          // Smooth orbital movement\n          const dx = Math.sin(t * b.freqX + b.phaseX) * b.ampX\n            + Math.sin(t * b.freqX * 0.4 + b.phaseX * 2.1) * b.ampX * 0.4\n          const dy = Math.cos(t * b.freqY + b.phaseY) * b.ampY\n            + Math.cos(t * b.freqY * 0.3 + b.phaseY * 1.7) * b.ampY * 0.3\n\n          const drawX = b.x + dx\n          const drawY = b.y + dy\n\n          // Pulsating radius\n          const pulse = 1 + Math.sin(t * b.rotSpeed + b.rotPhase) * 0.15\n          const r = b.r * pulse\n\n          // Draw soft color blob\n          const grad = ctx!.createRadialGradient(\n            drawX, drawY, 0,\n            drawX, drawY, r\n          )\n          grad.addColorStop(0, hexToRgba(b.color, 0.7))\n          grad.addColorStop(0.3, hexToRgba(b.color, 0.4))\n          grad.addColorStop(0.6, hexToRgba(b.color, 0.12))\n          grad.addColorStop(1, \"rgba(0,0,0,0)\")\n\n          ctx!.fillStyle = grad\n          ctx!.beginPath()\n          ctx!.arc(drawX, drawY, r, 0, Math.PI * 2)\n          ctx!.fill()\n        }\n\n        ctx!.globalCompositeOperation = mode === \"dark\" ? \"screen\" : \"multiply\"\n        const emissionCount = 3\n        for (let i = 0; i < emissionCount; i++) {\n          const ex = w * (0.3 + 0.4 * Math.sin(t * 0.2 * (i + 1) + i * 2.1))\n          const ey = h * (0.3 + 0.4 * Math.cos(t * 0.17 * (i + 1) + i * 1.3))\n          const er = Math.max(w, h) * 0.04\n          const brightness = 0.15 + Math.sin(t * 0.5 + i * 1.5) * 0.1\n\n          const eg = ctx!.createRadialGradient(ex, ey, 0, ex, ey, er)\n          eg.addColorStop(0, `rgba(255,255,255,${brightness})`)\n          eg.addColorStop(0.5, `rgba(200,210,255,${brightness * 0.4})`)\n          eg.addColorStop(1, \"rgba(0,0,0,0)\")\n\n          ctx!.fillStyle = eg\n          ctx!.beginPath()\n          ctx!.arc(ex, ey, er, 0, Math.PI * 2)\n          ctx!.fill()\n        }\n\n        rafId = requestAnimationFrame(animate)\n      }\n\n      resize()\n      rafId = requestAnimationFrame(animate)\n\n      const ro = new ResizeObserver(resize)\n      ro.observe(container)\n\n      return () => {\n        cancelAnimationFrame(rafId)\n        ro.disconnect()\n      }\n    }, [colorA, colorB, colorC, blobCount, speed, mode])\n\n    return (\n      <div\n        ref={containerRef}\n        className={cn(\"relative overflow-hidden\", className)}\n        style={{ background: bgColor }}\n        {...props}\n      >\n        {/* SVG filter for grain */}\n        {grain && (\n          <svg className=\"absolute\" width=\"0\" height=\"0\" aria-hidden=\"true\">\n            <defs>\n              <filter id={filterId}>\n                <feTurbulence\n                  type=\"fractalNoise\"\n                  baseFrequency=\"0.65\"\n                  numOctaves={3}\n                  stitchTiles=\"stitch\"\n                  result=\"grain\"\n                />\n                <feColorMatrix\n                  type=\"saturate\"\n                  values=\"0\"\n                  in=\"grain\"\n                  result=\"bwGrain\"\n                />\n                <feComponentTransfer in=\"bwGrain\" result=\"thresholded\">\n                  <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 0 0 0 0.04 0.12\" />\n                </feComponentTransfer>\n              </filter>\n            </defs>\n          </svg>\n        )}\n\n        {/* Nebula canvas with soft blur for extra fluidity */}\n        <div className=\"absolute inset-0\" style={{ filter: \"blur(30px)\" }}>\n          <canvas ref={canvasRef} className=\"absolute inset-0\" />\n        </div>\n\n        {/* Grain / star dust overlay */}\n        {grain && (\n          <div\n            className={`absolute inset-0 ${isDark ? \"mix-blend-screen\" : \"mix-blend-multiply\"}`}\n            style={{\n              filter: `url(#${filterId})`,\n              background: isDark ? \"white\" : \"black\",\n            }}\n          />\n        )}\n\n        {/* Subtle vignette */}\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            background: isDark\n              ? \"radial-gradient(ellipse at 50% 50%, transparent 30%, rgba(0,0,0,0.5) 100%)\"\n              : \"radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(0,0,0,0.12) 100%)\",\n          }}\n        />\n\n        {/* Children */}\n        {children && <div className=\"relative z-10\">{children}</div>}\n      </div>\n    )\n  }\n)\nNebula.displayName = \"Nebula\"\n\nexport { Nebula }\nexport type { NebulaProps }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}