{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-flow",
  "type": "registry:ui",
  "title": "Aurora Flow",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "A procedural atmospheric hero background with layered flowing color, diffused aurora light, and subtle interaction.",
  "files": [
    {
      "path": "components/ui/aurora-flow.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { WebGLErrorBoundary } from \"./webgl-error-boundary\";\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\n\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAGMENT_SHADER = `\nprecision highp float;\n\nuniform vec2 u_res;\nuniform vec2 u_pointer;\nuniform vec2 u_flowDirection;\nuniform float u_time;\nuniform float u_speed;\nuniform float u_animationSpeed;\nuniform float u_intensity;\nuniform float u_opacity;\nuniform float u_blur;\nuniform float u_contrast;\nuniform float u_brightness;\nuniform float u_layers;\nuniform float u_flowScale;\nuniform float u_flowStrength;\nuniform float u_pointerStrength;\nuniform float u_scroll;\nuniform float u_parallaxStrength;\nuniform float u_grainOpacity;\nuniform float u_noiseOpacity;\nuniform float u_noiseScale;\nuniform float u_lighting;\nuniform float u_lightingIntensity;\nuniform float u_lightingRadius;\nuniform float u_lightingSpeed;\nuniform float u_ambientGlow;\nuniform float u_ambientOpacity;\nuniform float u_vignette;\nuniform vec3 u_color0;\nuniform vec3 u_color1;\nuniform vec3 u_color2;\nuniform vec3 u_color3;\nuniform vec3 u_color4;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nfloat valueNoise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n\n  float a = hash(i);\n  float b = hash(i + vec2(1.0, 0.0));\n  float c = hash(i + vec2(0.0, 1.0));\n  float d = hash(i + vec2(1.0, 1.0));\n\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nmat2 rotate2d(float angle) {\n  float s = sin(angle);\n  float c = cos(angle);\n  return mat2(c, -s, s, c);\n}\n\nfloat fbm(vec2 p) {\n  float value = 0.0;\n  float amplitude = 0.52;\n  mat2 turn = mat2(0.84, 0.54, -0.54, 0.84);\n\n  for (int i = 0; i < 7; i++) {\n    float enabled = step(float(i) + 0.5, u_layers);\n    value += amplitude * valueNoise(p) * enabled;\n    p = turn * p * 2.03 + 0.17;\n    amplitude *= 0.5;\n  }\n\n  return value;\n}\n\nvec3 grade(vec3 color, float contrast, float brightness) {\n  color = (color - 0.5) * contrast + 0.5;\n  return color * brightness;\n}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / u_res;\n  float aspect = u_res.x / max(u_res.y, 1.0);\n  vec2 p = (uv - 0.5) * vec2(aspect, 1.0);\n\n  float t = u_time * 0.075 * u_speed * u_animationSpeed;\n  vec2 direction = normalize(u_flowDirection + vec2(0.0001));\n  vec2 crossDirection = vec2(-direction.y, direction.x);\n\n  vec2 pointer = (u_pointer - 0.5) * vec2(aspect, 1.0);\n  vec2 pointerDelta = pointer - p;\n  float pointerFalloff = smoothstep(0.68, 0.0, length(pointerDelta));\n  p += crossDirection * pointerDelta.x * pointerFalloff * 0.035 * u_pointerStrength;\n  p += direction * pointerDelta.y * pointerFalloff * 0.022 * u_pointerStrength;\n\n  p.y += u_scroll * 0.045 * u_parallaxStrength;\n  p.x -= u_scroll * 0.018 * u_parallaxStrength;\n\n  float scale = max(0.2, u_flowScale);\n  vec2 broadP = p * scale;\n  float warpA = fbm(broadP * 0.82 + direction * t * 0.71 + vec2(2.7, -1.3));\n  float warpB = fbm(rotate2d(0.73) * broadP * 1.08 - crossDirection * t * 0.53 + vec2(-4.1, 3.6));\n  float warpC = fbm(rotate2d(-0.46) * broadP * 1.42 + vec2(-t * 0.31, t * 0.43) + 8.2);\n\n  vec2 silk = broadP;\n  silk += vec2(warpA - 0.5, warpB - 0.5) * 0.72 * u_flowStrength;\n  silk += direction * (warpC - 0.5) * 0.28 * u_flowStrength;\n\n  float fieldA = fbm(rotate2d(0.28) * silk * 0.74 + direction * t * 0.83 + 1.9);\n  float fieldB = fbm(rotate2d(-0.81) * silk * 0.91 - crossDirection * t * 0.61 + 5.4);\n  float fieldC = fbm(rotate2d(1.17) * silk * 1.16 + vec2(t * 0.37, -t * 0.29) + 9.7);\n\n  float softness = clamp(u_blur * 0.08, 0.025, 0.2);\n  float veilA = smoothstep(0.28 - softness, 0.76 + softness, fieldA + (warpB - 0.5) * 0.28);\n  float veilB = smoothstep(0.34 - softness, 0.82 + softness, fieldB + (warpC - 0.5) * 0.24);\n  float veilC = smoothstep(0.42 - softness, 0.86 + softness, fieldC + (fieldA - 0.5) * 0.18);\n\n  float folded = 0.5 + 0.5 * sin(\n    silk.y * 2.7 + silk.x * 0.72 + warpA * 3.1 - warpB * 1.9 + t * 0.47\n  );\n  float silkFold = smoothstep(0.18, 0.92, folded) * smoothstep(0.12, 0.94, fieldB);\n\n  vec3 color = mix(u_color0, u_color1, clamp(0.22 + veilA * 0.62, 0.0, 1.0));\n  color = mix(color, u_color2, veilB * 0.58 * u_intensity);\n  color = mix(color, u_color3, veilC * 0.42 * u_intensity);\n  color += u_color4 * silkFold * 0.13 * u_intensity;\n\n  vec2 lightCenter = vec2(\n    0.5 + sin(t * 0.37 * u_lightingSpeed + 1.4) * 0.24,\n    0.48 + cos(t * 0.29 * u_lightingSpeed - 0.8) * 0.18\n  );\n  vec2 lightDelta = uv - lightCenter;\n  lightDelta = rotate2d(0.42 + warpA * 0.22) * lightDelta;\n  lightDelta *= vec2(0.74, 1.32);\n  float lightRadius = max(0.08, u_lightingRadius * 0.34);\n  float bloom = exp(-dot(lightDelta, lightDelta) / (lightRadius * lightRadius));\n  bloom *= 0.76 + fieldC * 0.24;\n  color += mix(u_color3, u_color4, 0.58) * bloom * 0.22 * u_lightingIntensity * u_lighting;\n\n  vec2 ambientDelta = uv - vec2(0.52, 0.47);\n  ambientDelta *= vec2(0.72, 1.0);\n  float ambient = exp(-dot(ambientDelta, ambientDelta) / 0.34);\n  color += mix(u_color2, u_color4, 0.5) * ambient * 0.12 * u_ambientOpacity * u_ambientGlow;\n\n  float haze = fbm(p * max(0.5, u_noiseScale) * 2.6 + vec2(-t * 0.18, t * 0.14));\n  color += (haze - 0.5) * u_noiseOpacity * 0.16;\n\n  float vignetteMask = smoothstep(0.18, 1.08, length((uv - 0.5) * vec2(1.0, 0.86)));\n  color *= 1.0 - vignetteMask * 0.52 * u_vignette;\n\n  float grain = hash(gl_FragCoord.xy + floor(u_time * 12.0) * 17.0) - 0.5;\n  color += grain * u_grainOpacity * 0.16;\n  color = grade(color, u_contrast, u_brightness);\n\n  gl_FragColor = vec4(clamp(color, 0.0, 1.0), clamp(u_opacity, 0.0, 1.0));\n}\n`;\n\nconst HEX_COLOR_REGEX = /^#?[0-9a-fA-F]{6}$/;\n\nexport const AURORA_FLOW_PRESETS = {\n  ocean: [\"#020817\", \"#062a55\", \"#0b62b4\", \"#39bfe6\", \"#d9f6ff\"],\n  aurora: [\"#07051a\", \"#25105e\", \"#4d43c7\", \"#20a7a0\", \"#c3f5eb\"],\n  sky: [\"#071425\", \"#16456e\", \"#4d9acb\", \"#9edbf0\", \"#eefaff\"],\n  cloud: [\"#20242c\", \"#4b5360\", \"#8c98a7\", \"#ced5dc\", \"#f7f8fa\"],\n  sunset: [\"#1d0812\", \"#6d1e3e\", \"#d95b48\", \"#f2a35b\", \"#ffe2a6\"],\n  midnight: [\"#010206\", \"#080d1c\", \"#17264d\", \"#304d7f\", \"#a8c6ef\"],\n  ice: [\"#07141b\", \"#123b49\", \"#3e8796\", \"#a6e4e7\", \"#f1ffff\"],\n  dream: [\"#10091c\", \"#3b1d58\", \"#8d4ca0\", \"#df91bd\", \"#ffe7f1\"],\n  nebula: [\"#090512\", \"#32104a\", \"#813b8f\", \"#315fa8\", \"#e2b7ff\"],\n  emerald: [\"#03120e\", \"#073c2c\", \"#0e7358\", \"#53c89f\", \"#d9fff0\"],\n  lavender: [\"#100b20\", \"#30205d\", \"#6252a6\", \"#b58bc6\", \"#f4dff4\"],\n  mono: [\"#020202\", \"#171717\", \"#414141\", \"#8a8a8a\", \"#f2f2f2\"],\n} as const;\n\nexport type AuroraFlowPreset = keyof typeof AURORA_FLOW_PRESETS;\n\nexport interface AuroraFlowProps extends React.HTMLAttributes<HTMLDivElement> {\n  colors?: readonly string[];\n  preset?: AuroraFlowPreset;\n  speed?: number;\n  intensity?: number;\n  opacity?: number;\n  blur?: number;\n  contrast?: number;\n  brightness?: number;\n  grain?: boolean;\n  grainOpacity?: number;\n  layers?: number;\n  flowScale?: number;\n  flowStrength?: number;\n  flowDirection?: number;\n  animationSpeed?: number;\n  pointerInteraction?: boolean;\n  pointerStrength?: number;\n  scrollInteraction?: boolean;\n  parallaxStrength?: number;\n  lighting?: boolean;\n  lightingIntensity?: number;\n  lightingRadius?: number;\n  lightingSpeed?: number;\n  ambientGlow?: boolean;\n  ambientOpacity?: number;\n  noise?: boolean;\n  noiseOpacity?: number;\n  noiseScale?: number;\n  vignette?: boolean;\n  vignetteStrength?: number;\n  borderRadius?: React.CSSProperties[\"borderRadius\"];\n  children?: React.ReactNode;\n}\n\nfunction clamp(value: number, minimum: number, maximum: number) {\n  return Math.min(maximum, Math.max(minimum, value));\n}\n\nfunction sanitizeHexColor(value: string | undefined, fallback: string) {\n  const trimmed = value?.trim() ?? \"\";\n  if (!HEX_COLOR_REGEX.test(trimmed)) return fallback;\n  return trimmed.startsWith(\"#\") ? trimmed : `#${trimmed}`;\n}\n\nfunction hexToRgb01(hex: string): [number, number, number] {\n  const normalized = hex.slice(1);\n  return [\n    parseInt(normalized.slice(0, 2), 16) / 255,\n    parseInt(normalized.slice(2, 4), 16) / 255,\n    parseInt(normalized.slice(4, 6), 16) / 255,\n  ];\n}\n\nfunction useReducedMotion() {\n  const [reduced, setReduced] = React.useState(false);\n\n  React.useEffect(() => {\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const update = () => setReduced(query.matches);\n    update();\n    query.addEventListener(\"change\", update);\n    return () => query.removeEventListener(\"change\", update);\n  }, []);\n\n  return reduced;\n}\n\nexport function AuroraFlow({\n  colors,\n  preset = \"ocean\",\n  speed = 1,\n  intensity = 1,\n  opacity = 1,\n  blur = 1,\n  contrast = 1.04,\n  brightness = 1,\n  grain = true,\n  grainOpacity = 0.22,\n  layers = 6,\n  flowScale = 1,\n  flowStrength = 1,\n  flowDirection = -18,\n  animationSpeed = 1,\n  pointerInteraction = true,\n  pointerStrength = 0.7,\n  scrollInteraction = false,\n  parallaxStrength = 0.5,\n  lighting = true,\n  lightingIntensity = 0.8,\n  lightingRadius = 1,\n  lightingSpeed = 0.8,\n  ambientGlow = true,\n  ambientOpacity = 0.7,\n  noise = true,\n  noiseOpacity = 0.16,\n  noiseScale = 1,\n  vignette = true,\n  vignetteStrength = 0.55,\n  borderRadius = 0,\n  className,\n  style,\n  children,\n  ...props\n}: AuroraFlowProps) {\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n  const drawStaticRef = React.useRef<(() => void) | null>(null);\n  const pointerRef = React.useRef({ x: 0.5, y: 0.5 });\n  const targetPointerRef = React.useRef({ x: 0.5, y: 0.5 });\n  const scrollRef = React.useRef(0);\n  const targetScrollRef = React.useRef(0);\n  const [hasWebGLError, setHasWebGLError] = React.useState(false);\n  const reducedMotion = useReducedMotion();\n\n  const palette = React.useMemo(() => {\n    const fallback = AURORA_FLOW_PRESETS[preset];\n    return fallback.map((color, index) =>\n      sanitizeHexColor(colors?.[index], color),\n    ) as [string, string, string, string, string];\n  }, [colors, preset]);\n\n  const settings = React.useMemo(\n    () => ({\n      palette: palette.map(hexToRgb01),\n      speed: clamp(speed, 0, 3),\n      intensity: clamp(intensity, 0, 2),\n      opacity: clamp(opacity, 0, 1),\n      blur: clamp(blur, 0.25, 2),\n      contrast: clamp(contrast, 0.6, 1.6),\n      brightness: clamp(brightness, 0.5, 1.5),\n      grainOpacity: grain ? clamp(grainOpacity, 0, 1) : 0,\n      layers: clamp(Math.round(layers), 3, 7),\n      flowScale: clamp(flowScale, 0.35, 2.5),\n      flowStrength: clamp(flowStrength, 0, 2),\n      flowDirection,\n      animationSpeed: clamp(animationSpeed, 0, 3),\n      pointerStrength: pointerInteraction ? clamp(pointerStrength, 0, 1.5) : 0,\n      scrollInteraction,\n      parallaxStrength: clamp(parallaxStrength, 0, 1.5),\n      lighting: lighting ? 1 : 0,\n      lightingIntensity: clamp(lightingIntensity, 0, 2),\n      lightingRadius: clamp(lightingRadius, 0.3, 2),\n      lightingSpeed: clamp(lightingSpeed, 0, 2),\n      ambientGlow: ambientGlow ? 1 : 0,\n      ambientOpacity: clamp(ambientOpacity, 0, 1.5),\n      noiseOpacity: noise ? clamp(noiseOpacity, 0, 1) : 0,\n      noiseScale: clamp(noiseScale, 0.4, 3),\n      vignette: vignette ? clamp(vignetteStrength, 0, 1.5) : 0,\n      pointerInteraction,\n    }),\n    [\n      ambientGlow,\n      ambientOpacity,\n      animationSpeed,\n      blur,\n      brightness,\n      contrast,\n      flowDirection,\n      flowScale,\n      flowStrength,\n      grain,\n      grainOpacity,\n      intensity,\n      layers,\n      lighting,\n      lightingIntensity,\n      lightingRadius,\n      lightingSpeed,\n      noise,\n      noiseOpacity,\n      noiseScale,\n      opacity,\n      palette,\n      parallaxStrength,\n      pointerInteraction,\n      pointerStrength,\n      scrollInteraction,\n      speed,\n      vignette,\n      vignetteStrength,\n    ],\n  );\n  const settingsRef = React.useRef(settings);\n  settingsRef.current = settings;\n\n  React.useEffect(() => {\n    if (hasWebGLError) return;\n\n    const container = containerRef.current;\n    const canvas = canvasRef.current;\n    if (!container || !canvas) return;\n\n    const gl = canvas.getContext(\"webgl\", {\n      antialias: false,\n      alpha: true,\n      premultipliedAlpha: true,\n      powerPreference: \"high-performance\",\n    });\n    if (!gl) {\n      setHasWebGLError(true);\n      return;\n    }\n\n    const compileShader = (type: number, source: string) => {\n      const shader = gl.createShader(type);\n      if (!shader) return null;\n      gl.shaderSource(shader, source);\n      gl.compileShader(shader);\n      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n        gl.deleteShader(shader);\n        return null;\n      }\n      return shader;\n    };\n\n    const vertexShader = compileShader(gl.VERTEX_SHADER, VERTEX_SHADER);\n    const fragmentShader = compileShader(gl.FRAGMENT_SHADER, FRAGMENT_SHADER);\n    if (!vertexShader || !fragmentShader) {\n      setHasWebGLError(true);\n      return;\n    }\n\n    const program = gl.createProgram();\n    if (!program) {\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n      setHasWebGLError(true);\n      return;\n    }\n\n    gl.attachShader(program, vertexShader);\n    gl.attachShader(program, fragmentShader);\n    gl.linkProgram(program);\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n      gl.deleteProgram(program);\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n      setHasWebGLError(true);\n      return;\n    }\n\n    gl.useProgram(program);\n    const position = gl.getAttribLocation(program, \"position\");\n    const buffer = gl.createBuffer();\n    if (position < 0 || !buffer) {\n      setHasWebGLError(true);\n      return;\n    }\n\n    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),\n      gl.STATIC_DRAW,\n    );\n    gl.enableVertexAttribArray(position);\n    gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n    const uniformNames = [\n      \"u_res\",\n      \"u_pointer\",\n      \"u_flowDirection\",\n      \"u_time\",\n      \"u_speed\",\n      \"u_animationSpeed\",\n      \"u_intensity\",\n      \"u_opacity\",\n      \"u_blur\",\n      \"u_contrast\",\n      \"u_brightness\",\n      \"u_layers\",\n      \"u_flowScale\",\n      \"u_flowStrength\",\n      \"u_pointerStrength\",\n      \"u_scroll\",\n      \"u_parallaxStrength\",\n      \"u_grainOpacity\",\n      \"u_noiseOpacity\",\n      \"u_noiseScale\",\n      \"u_lighting\",\n      \"u_lightingIntensity\",\n      \"u_lightingRadius\",\n      \"u_lightingSpeed\",\n      \"u_ambientGlow\",\n      \"u_ambientOpacity\",\n      \"u_vignette\",\n      \"u_color0\",\n      \"u_color1\",\n      \"u_color2\",\n      \"u_color3\",\n      \"u_color4\",\n    ] as const;\n    const uniforms = Object.fromEntries(\n      uniformNames.map((name) => [name, gl.getUniformLocation(program, name)]),\n    ) as Record<(typeof uniformNames)[number], WebGLUniformLocation | null>;\n\n    if (uniformNames.some((name) => uniforms[name] === null)) {\n      gl.deleteBuffer(buffer);\n      gl.deleteProgram(program);\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n      setHasWebGLError(true);\n      return;\n    }\n\n    const handlePointerMove = (event: PointerEvent) => {\n      if (!settingsRef.current.pointerInteraction || reducedMotion) return;\n      const rect = container.getBoundingClientRect();\n      targetPointerRef.current = {\n        x: clamp((event.clientX - rect.left) / rect.width, 0, 1),\n        y: clamp(1 - (event.clientY - rect.top) / rect.height, 0, 1),\n      };\n    };\n    const handlePointerLeave = () => {\n      targetPointerRef.current = { x: 0.5, y: 0.5 };\n    };\n    const handleScroll = () => {\n      if (!settingsRef.current.scrollInteraction || reducedMotion) return;\n      targetScrollRef.current =\n        window.scrollY / Math.max(window.innerHeight, 1);\n    };\n\n    container.addEventListener(\"pointermove\", handlePointerMove, {\n      passive: true,\n    });\n    container.addEventListener(\"pointerleave\", handlePointerLeave);\n    window.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n    let elapsed = reducedMotion ? 7.25 : 0;\n    let lastNow = performance.now();\n    let rafId = 0;\n    let running = false;\n\n    const draw = (now: number, scheduleNext = true) => {\n      const current = settingsRef.current;\n      if (!reducedMotion) {\n        elapsed += Math.min((now - lastNow) / 1000, 0.05);\n      }\n      lastNow = now;\n\n      pointerRef.current.x +=\n        (targetPointerRef.current.x - pointerRef.current.x) * 0.035;\n      pointerRef.current.y +=\n        (targetPointerRef.current.y - pointerRef.current.y) * 0.035;\n      scrollRef.current +=\n        (targetScrollRef.current - scrollRef.current) * 0.025;\n\n      const radians = (current.flowDirection * Math.PI) / 180;\n      gl.uniform2f(\n        uniforms.u_pointer!,\n        pointerRef.current.x,\n        pointerRef.current.y,\n      );\n      gl.uniform2f(\n        uniforms.u_flowDirection!,\n        Math.cos(radians),\n        Math.sin(radians),\n      );\n      gl.uniform1f(uniforms.u_time!, elapsed);\n      gl.uniform1f(uniforms.u_speed!, current.speed);\n      gl.uniform1f(uniforms.u_animationSpeed!, current.animationSpeed);\n      gl.uniform1f(uniforms.u_intensity!, current.intensity);\n      gl.uniform1f(uniforms.u_opacity!, current.opacity);\n      gl.uniform1f(uniforms.u_blur!, current.blur);\n      gl.uniform1f(uniforms.u_contrast!, current.contrast);\n      gl.uniform1f(uniforms.u_brightness!, current.brightness);\n      gl.uniform1f(uniforms.u_layers!, current.layers);\n      gl.uniform1f(uniforms.u_flowScale!, current.flowScale);\n      gl.uniform1f(uniforms.u_flowStrength!, current.flowStrength);\n      gl.uniform1f(\n        uniforms.u_pointerStrength!,\n        reducedMotion ? 0 : current.pointerStrength,\n      );\n      gl.uniform1f(\n        uniforms.u_scroll!,\n        reducedMotion || !current.scrollInteraction ? 0 : scrollRef.current,\n      );\n      gl.uniform1f(uniforms.u_parallaxStrength!, current.parallaxStrength);\n      gl.uniform1f(uniforms.u_grainOpacity!, current.grainOpacity);\n      gl.uniform1f(uniforms.u_noiseOpacity!, current.noiseOpacity);\n      gl.uniform1f(uniforms.u_noiseScale!, current.noiseScale);\n      gl.uniform1f(uniforms.u_lighting!, current.lighting);\n      gl.uniform1f(uniforms.u_lightingIntensity!, current.lightingIntensity);\n      gl.uniform1f(uniforms.u_lightingRadius!, current.lightingRadius);\n      gl.uniform1f(uniforms.u_lightingSpeed!, current.lightingSpeed);\n      gl.uniform1f(uniforms.u_ambientGlow!, current.ambientGlow);\n      gl.uniform1f(uniforms.u_ambientOpacity!, current.ambientOpacity);\n      gl.uniform1f(uniforms.u_vignette!, current.vignette);\n      current.palette.forEach((color, index) => {\n        const location = uniforms[`u_color${index}` as \"u_color0\"]!;\n        gl.uniform3f(location, color[0], color[1], color[2]);\n      });\n      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n      if (running && scheduleNext) rafId = window.requestAnimationFrame(draw);\n    };\n\n    const resize = () => {\n      const dpr = Math.min(window.devicePixelRatio || 1, 1.75);\n      const rect = container.getBoundingClientRect();\n      canvas.width = Math.max(1, Math.floor(rect.width * dpr));\n      canvas.height = Math.max(1, Math.floor(rect.height * dpr));\n      gl.viewport(0, 0, canvas.width, canvas.height);\n      gl.uniform2f(uniforms.u_res!, canvas.width, canvas.height);\n      draw(performance.now(), false);\n    };\n\n    const start = () => {\n      if (running || reducedMotion || document.hidden) return;\n      running = true;\n      lastNow = performance.now();\n      rafId = window.requestAnimationFrame(draw);\n    };\n    const stop = () => {\n      running = false;\n      window.cancelAnimationFrame(rafId);\n    };\n\n    resize();\n    drawStaticRef.current = () => draw(performance.now(), false);\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    const intersectionObserver = new IntersectionObserver(([entry]) => {\n      if (entry?.isIntersecting) start();\n      else stop();\n    });\n    intersectionObserver.observe(container);\n    const handleVisibility = () => {\n      if (document.hidden) stop();\n      else start();\n    };\n    document.addEventListener(\"visibilitychange\", handleVisibility);\n\n    if (reducedMotion) draw(performance.now(), false);\n\n    return () => {\n      stop();\n      drawStaticRef.current = null;\n      resizeObserver.disconnect();\n      intersectionObserver.disconnect();\n      document.removeEventListener(\"visibilitychange\", handleVisibility);\n      container.removeEventListener(\"pointermove\", handlePointerMove);\n      container.removeEventListener(\"pointerleave\", handlePointerLeave);\n      window.removeEventListener(\"scroll\", handleScroll);\n      gl.deleteBuffer(buffer);\n      gl.deleteProgram(program);\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n    };\n  }, [hasWebGLError, reducedMotion]);\n\n  React.useEffect(() => {\n    if (reducedMotion) drawStaticRef.current?.();\n  }, [reducedMotion, settings]);\n\n  const staticBackground = {\n    background: [\n      `radial-gradient(ellipse at 18% 28%, ${palette[2]}99, transparent 52%)`,\n      `radial-gradient(ellipse at 78% 34%, ${palette[3]}66, transparent 48%)`,\n      `radial-gradient(ellipse at 52% 82%, ${palette[1]}aa, transparent 58%)`,\n      palette[0],\n    ].join(\", \"),\n  } satisfies React.CSSProperties;\n\n  const fallback = (\n    <div\n      className={cn(\"relative min-h-[420px] w-full overflow-hidden\", className)}\n      style={{ ...style, borderRadius }}\n      {...props}\n    >\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0\"\n        style={staticBackground}\n      />\n      {children && <div className=\"relative z-10 size-full\">{children}</div>}\n    </div>\n  );\n\n  return (\n    <WebGLErrorBoundary fallback={fallback}>\n      <div\n        ref={containerRef}\n        className={cn(\n          \"relative min-h-[420px] w-full overflow-hidden\",\n          className,\n        )}\n        style={{ ...style, borderRadius }}\n        {...props}\n      >\n        <div\n          aria-hidden=\"true\"\n          className=\"absolute inset-0\"\n          style={staticBackground}\n        />\n        {!hasWebGLError && (\n          <canvas\n            ref={canvasRef}\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 size-full\"\n          />\n        )}\n        {children && <div className=\"relative z-10 size-full\">{children}</div>}\n      </div>\n    </WebGLErrorBoundary>\n  );\n}\n\nexport default AuroraFlow;\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/webgl-error-boundary.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\ninterface WebGLErrorBoundaryProps {\n  children: React.ReactNode;\n  fallback?: React.ReactNode;\n  onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n}\n\ninterface WebGLErrorBoundaryState {\n  hasError: boolean;\n}\n\nexport class WebGLErrorBoundary extends React.Component<\n  WebGLErrorBoundaryProps,\n  WebGLErrorBoundaryState\n> {\n  public state: WebGLErrorBoundaryState = { hasError: false };\n\n  static getDerivedStateFromError(): WebGLErrorBoundaryState {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    this.props.onError?.(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return this.props.fallback ?? <WebGLFallback />;\n    }\n    return this.props.children;\n  }\n}\n\ninterface WebGLFallbackProps {\n  className?: string;\n  message?: string;\n}\n\nexport function WebGLFallback({\n  className,\n  message = \"Interactive WebGL content is unavailable on this device/browser.\",\n}: WebGLFallbackProps) {\n  return (\n    <div\n      className={cn(\n        \"flex h-full w-full items-center justify-center bg-gradient-to-br from-zinc-950 via-slate-900 to-zinc-900 px-4 text-center text-sm text-white/75\",\n        className,\n      )}\n      role=\"status\"\n      aria-live=\"polite\"\n    >\n      <p>{message}</p>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ]
}
