{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "neon-aurora",
  "title": "NeonAurora",
  "description": "The neon.com hero as a live WebGL surface: clusters of thin vertical bars twinkling in green, teal, and blue.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/neon-aurora/neon-aurora.tsx",
      "content": "\"use client\";\n\n/*\n * NeonAurora: the neon.com hero as a live WebGL surface — clusters of\n * thin vertical bars in green/teal/blue twinkling on black. A brand\n * background for heroes, empty workspaces, and marketing moments.\n * Static single frame under reduced motion. GLSL lives in\n * neon-aurora-shader.ts.\n */\n\nimport { useEffect, useRef } from \"react\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport { AURORA_FRAGMENT, AURORA_VERTEX } from \"./neon-aurora-shader\";\n\nexport type NeonAuroraProps = Omit<ComponentProps<\"canvas\">, \"children\"> & {\n  /** Animation speed multiplier; 0 freezes the field. */\n  speed?: number;\n  /** 0-1 share of bar clusters alive at once. */\n  density?: number;\n  /** Overall brightness multiplier. */\n  intensity?: number;\n  /** 0-1 depth-of-field blur on the near layer as it approaches. */\n  blur?: number;\n  /** 0-1 ambient bloom fog hanging around the lit regions. */\n  glare?: number;\n  /** 0-1 how often the warm flare ignites; 0 disables it. */\n  flare?: number;\n  /** 0-1 how much of its column each bar fills. */\n  thickness?: number;\n  /** 0-1 how often the hot white flare ignites; 0 disables it. */\n  whiteFlare?: number;\n  /** Bar palette, mixed per bar: [deep, primary, accent]. */\n  colors?: [string, string, string];\n};\n\n/**\n * Seconds for a dial change to close ~63% of its gap — uniforms ease\n * toward their targets each frame, so a dragged slider glides\n * instead of snapping.\n */\nconst SMOOTH_TAU = 0.12;\n\nconst DEFAULT_COLORS: [string, string, string] = [\n  \"#0e5f45\",\n  \"#00e599\",\n  \"#3b82f6\",\n];\n\nconst parseColor = (css: string): [number, number, number] => {\n  const probe = document.createElement(\"canvas\");\n  probe.width = 1;\n  probe.height = 1;\n  const context = probe.getContext(\"2d\");\n\n  if (!context) {\n    return [0, 0.9, 0.6];\n  }\n\n  context.fillStyle = css;\n  context.fillRect(0, 0, 1, 1);\n  const [r, g, b] = context.getImageData(0, 0, 1, 1).data;\n  return [(r ?? 0) / 255, (g ?? 0) / 255, (b ?? 0) / 255];\n};\n\nconst warnDev = (message: string) => {\n  if (typeof process !== \"undefined\" && process.env.NODE_ENV !== \"production\") {\n    console.warn(message);\n  }\n};\n\nconst compile = (\n  gl: WebGLRenderingContext,\n  type: number,\n  source: string\n): WebGLShader | null => {\n  const shader = gl.createShader(type);\n\n  if (!shader) {\n    return null;\n  }\n\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    warnDev(\n      `neon-ui: shader compile failed: ${gl.getShaderInfoLog(shader) ?? \"unknown\"}`\n    );\n    gl.deleteShader(shader);\n    return null;\n  }\n\n  return shader;\n};\n\nexport const NeonAurora = ({\n  blur = 1,\n  className,\n  colors = DEFAULT_COLORS,\n  density = 0.2,\n  flare = 0.75,\n  glare = 0.2,\n  intensity = 2,\n  speed = 0.7,\n  thickness = 0,\n  whiteFlare = 0.6,\n  ...props\n}: NeonAuroraProps) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  // Shader time, accumulated frame by frame so a speed change scales\n  // the flow from here instead of teleporting the whole field.\n  const phaseRef = useRef(0);\n  const lastFrameRef = useRef<number | null>(null);\n  // Smoothed dial values, persisting across prop-driven re-inits.\n  const smoothedRef = useRef<Record<string, number> | null>(null);\n  const [deep, primary, accent] = colors;\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const gl = canvas?.getContext(\"webgl\", { alpha: false });\n\n    if (!(canvas && gl)) {\n      return;\n    }\n\n    const vertex = compile(gl, gl.VERTEX_SHADER, AURORA_VERTEX);\n    const fragment = compile(gl, gl.FRAGMENT_SHADER, AURORA_FRAGMENT);\n    const program = gl.createProgram();\n\n    if (!(vertex && fragment && program)) {\n      if (vertex) {\n        gl.deleteShader(vertex);\n      }\n      if (fragment) {\n        gl.deleteShader(fragment);\n      }\n      return;\n    }\n\n    gl.attachShader(program, vertex);\n    gl.attachShader(program, fragment);\n    gl.linkProgram(program);\n\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n      warnDev(\n        `neon-ui: program link failed: ${gl.getProgramInfoLog(program) ?? \"unknown\"}`\n      );\n      gl.deleteProgram(program);\n      gl.deleteShader(vertex);\n      gl.deleteShader(fragment);\n      return;\n    }\n\n    // oxlint-disable-next-line react/react-compiler -- WebGL method, not a React hook\n    gl.useProgram(program);\n\n    const quad = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, quad);\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 3, -1, -1, 3]),\n      gl.STATIC_DRAW\n    );\n    const position = gl.getAttribLocation(program, \"a_position\");\n    gl.enableVertexAttribArray(position);\n    gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n    const uResolution = gl.getUniformLocation(program, \"u_resolution\");\n    const uTime = gl.getUniformLocation(program, \"u_time\");\n    const uDensity = gl.getUniformLocation(program, \"u_density\");\n    const uIntensity = gl.getUniformLocation(program, \"u_intensity\");\n    const uBlur = gl.getUniformLocation(program, \"u_blur\");\n    const uGlare = gl.getUniformLocation(program, \"u_glare\");\n    const uFlare = gl.getUniformLocation(program, \"u_flare\");\n    const uThickness = gl.getUniformLocation(program, \"u_thickness\");\n    const uWhiteFlare = gl.getUniformLocation(program, \"u_white_flare\");\n\n    const targets: Record<string, number> = {\n      blur,\n      density,\n      flare,\n      glare,\n      intensity,\n      speed,\n      thickness,\n      whiteFlare,\n    };\n\n    smoothedRef.current ??= { ...targets };\n    const smoothed = smoothedRef.current;\n\n    /** Ease every dial toward its target and upload; k=1 snaps. */\n    const applyUniforms = (k: number) => {\n      for (const key of Object.keys(targets)) {\n        const current = smoothed[key] ?? targets[key] ?? 0;\n        smoothed[key] = current + ((targets[key] ?? 0) - current) * k;\n      }\n\n      gl.uniform1f(uWhiteFlare, smoothed.whiteFlare ?? whiteFlare);\n      gl.uniform1f(uThickness, smoothed.thickness ?? thickness);\n      gl.uniform1f(uFlare, smoothed.flare ?? flare);\n      gl.uniform1f(uDensity, smoothed.density ?? density);\n      gl.uniform1f(uIntensity, smoothed.intensity ?? intensity);\n      gl.uniform1f(uBlur, smoothed.blur ?? blur);\n      gl.uniform1f(uGlare, smoothed.glare ?? glare);\n    };\n\n    for (const [name, css] of [\n      [\"u_color1\", deep],\n      [\"u_color2\", primary],\n      [\"u_color3\", accent],\n    ] as const) {\n      const [r, g, b] = parseColor(css);\n      gl.uniform3f(gl.getUniformLocation(program, name), r, g, b);\n    }\n\n    const dpr = Math.min(window.devicePixelRatio || 1, 2);\n    let frame = 0;\n    let staticFrame = false;\n\n    const renderStatic = () => {\n      applyUniforms(1);\n      gl.uniform1f(uTime, 7);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n    };\n\n    const resize = () => {\n      const width = Math.max(1, Math.round(canvas.clientWidth * dpr));\n      const height = Math.max(1, Math.round(canvas.clientHeight * dpr));\n\n      if (canvas.width !== width || canvas.height !== height) {\n        canvas.width = width;\n        canvas.height = height;\n        gl.viewport(0, 0, width, height);\n      }\n\n      gl.uniform2f(uResolution, canvas.width, canvas.height);\n    };\n\n    const observer = new ResizeObserver(() => {\n      resize();\n\n      if (staticFrame) {\n        renderStatic();\n      }\n    });\n    observer.observe(canvas);\n    resize();\n\n    const dispose = () => {\n      observer.disconnect();\n      gl.deleteBuffer(quad);\n      gl.deleteProgram(program);\n      gl.deleteShader(vertex);\n      gl.deleteShader(fragment);\n    };\n\n    const draw = (now: number) => {\n      const last = lastFrameRef.current ?? now;\n      lastFrameRef.current = now;\n      // Skip GL work while hidden (e.g. behind a docs Code overlay's\n      // visibility:hidden panel) — keep the loop alive, drop the cost.\n      if (!(canvas.checkVisibility?.() ?? true)) {\n        frame = requestAnimationFrame(draw);\n        return;\n      }\n\n      const dt = (now - last) / 1000;\n      applyUniforms(1 - Math.exp(-dt / SMOOTH_TAU));\n      phaseRef.current += dt * (smoothed.speed ?? speed);\n      gl.uniform1f(uTime, phaseRef.current);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n      frame = requestAnimationFrame(draw);\n    };\n\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\n    if (reduced.matches || speed === 0) {\n      staticFrame = true;\n      resize();\n      renderStatic();\n      return dispose;\n    }\n\n    frame = requestAnimationFrame(draw);\n\n    return () => {\n      cancelAnimationFrame(frame);\n      dispose();\n    };\n  }, [\n    accent,\n    blur,\n    deep,\n    density,\n    flare,\n    glare,\n    intensity,\n    primary,\n    speed,\n    thickness,\n    whiteFlare,\n  ]);\n\n  return (\n    <canvas\n      aria-hidden=\"true\"\n      className={cn(\"size-full\", className)}\n      data-slot=\"neon-aurora\"\n      ref={canvasRef}\n      {...props}\n    />\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/neon-aurora/neon-aurora-shader.ts",
      "content": "/*\n * The visual shader for NeonAurora, kept separate from the React mount.\n *\n * A clean-room recreation of the neon.com hero video background, built\n * from frame-by-frame study of the source footage:\n *\n * - ~110 wide, soft-edged bar columns across the frame width\n * - rectangular blocks of contiguous lit bars that live for seconds,\n *   plus sparse solo bars between them\n * - near-static geometry: a creep of a pixel or two per second, no zoom\n * - a fast subtle shimmer on every lit bar, every frame\n * - one warm flare at a time, a short block that swells and dies\n * - a fine halftone screen over everything, and a blurred foreground\n *   wash as a second depth layer\n *\n * Fragment shader uniforms:\n * - u_resolution (vec2): canvas resolution in pixels\n * - u_time (float): animation time in seconds (pre-multiplied by speed)\n * - u_density (float): 0-1 share of blocks alive at once\n * - u_intensity (float): overall brightness multiplier\n * - u_blur (float): 0-1 blur on the foreground wash layer\n * - u_glare (float): 0-1 ambient bloom fog around lit regions\n * - u_flare (float): 0-1 how often the warm flare ignites\n * - u_color1/2/3 (vec3): the bar palette, mixed per bar\n */\n\nexport const AURORA_VERTEX = `\nattribute vec2 a_position;\nvoid main() {\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n`;\n\nexport const AURORA_FRAGMENT = `\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_density;\nuniform float u_intensity;\nuniform float u_blur;\nuniform float u_glare;\nuniform float u_flare;\nuniform float u_thickness;\nuniform float u_white_flare;\nuniform vec3 u_color1;\nuniform vec3 u_color2;\nuniform vec3 u_color3;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);\n}\n\nfloat vnoise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = smoothstep(0.0, 1.0, fract(p));\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  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\n// A value that wanders smoothly per cell on its own desynced clock:\n// random phase and rate, so no two cells change in unison.\nfloat roll(vec2 cell, float t) {\n  float rate = 0.6 + 1.2 * hash(cell * 1.7 + 9.3);\n  float tt = t * rate + hash(cell * 3.3 + 1.1) * 7.0;\n  float tick = floor(tt);\n  float f = smoothstep(0.0, 1.0, fract(tt));\n  float a = hash(cell + tick * vec2(0.37, 0.11));\n  float b = hash(cell + (tick + 1.0) * vec2(0.37, 0.11));\n  return mix(a, b, f);\n}\n\n// One flare event stream: a block of bars that ignites, swells, and\n// dies bar by bar. freq drives how often; ofs desyncs streams so warm\n// and white flares never share a clock or a position.\nfloat flare(float col, vec2 uv, float period, float freq, float ofs, float seed) {\n  float gt = u_time * (0.06 + 0.26 * freq) + seed * 0.37 + ofs;\n  float gTick = floor(gt);\n  float gF = fract(gt);\n  float has = step(1.0 - 0.95 * freq, hash(vec2(gTick, seed + 51.3 + ofs))) *\n    step(0.001, freq);\n  float heroCol = floor(hash(vec2(gTick, 5.5 + seed + ofs)) * (u_resolution.x / period)) +\n    seed * 97.0;\n  float heroY = 0.3 + hash(vec2(gTick, 8.2 + seed + ofs)) * 0.4;\n  float gx = exp(-abs(col - heroCol) * 0.55) * has;\n  // Per-bar onset and span stagger the ignition and the decay.\n  float onset = hash(vec2(col, gTick + 17.0 + ofs)) * 0.25;\n  float span = 0.45 + hash(vec2(col, gTick + 29.0 + ofs)) * 0.5;\n  float phase = clamp((gF - onset) / span, 0.0, 1.0);\n  float swell = sin(phase * 3.14159);\n  float gdy = (uv.y - heroY) / (0.1 + span * 0.14);\n  return gx * swell * exp(-gdy * gdy);\n}\n\n// One depth layer of the bar field. Returns premultiplied color in\n// rgb and coverage in a.\nvec4 field(vec2 px, float seed, float blur) {\n  vec2 uv = px / u_resolution;\n\n  // ~110 wide soft columns across the width, like the source.\n  float period = max(u_resolution.x / 110.0, 6.0);\n  float col = floor(px.x / period) + seed * 97.0;\n  float duty = fract(px.x / period);\n\n  // A glowing-tube profile: soft shoulders, bright core. Thickness\n  // sets how much of the period the tube fills; blur melts the edges.\n  float w = 0.3 + 0.5 * u_thickness;\n  float start = (1.0 - w) * 0.5;\n  float end = (1.0 + w) * 0.5;\n  float shoulder = 0.1 + blur * 0.2;\n  float profile = smoothstep(start - shoulder * 0.3, start + shoulder, duty) *\n    (1.0 - smoothstep(end - shoulder, end + shoulder * 0.3, duty));\n\n  // Irregular rows: five bands whose boundaries shift per column stretch.\n  float coarse = floor(px.x / (period * 10.0));\n  float row = floor(uv.y * 5.0 + (hash(vec2(coarse, 7.7)) - 0.5) * 0.7);\n\n  // Blocks: runs of bars with wobbled boundaries, any width from a few\n  // bars to dozens. Each block lives for seconds, then yields.\n  float bx = px.x / (period * 14.0);\n  bx += (vnoise(vec2(bx * 0.6, row * 3.7 + seed * 11.0)) - 0.5) * 1.6;\n  vec2 block = vec2(floor(bx) + seed * 31.0, row);\n  float life = roll(block * 3.7, u_time * 0.14 + seed);\n  float blockOn = smoothstep(1.0 - u_density - 0.05, 1.0 - u_density + 0.05, life);\n\n  // The block rectangle: hard-ish top and bottom, height varying\n  // widely between blocks, bottoms slightly ragged per bar.\n  float blockCenter = (row + 0.5) / 5.0 + (hash(block * 1.9) - 0.5) * 0.12;\n  float blockHalf = (0.03 + 0.11 * pow(hash(block * 2.3 + 5.1), 1.4)) *\n    (0.85 + 0.3 * hash(vec2(col, 3.3)));\n  float dy = abs(uv.y - blockCenter);\n  float rect = 1.0 - smoothstep(blockHalf * 0.8, blockHalf, dy);\n\n  // Solo bars: sparse loners between the blocks with their own clocks,\n  // shorter, some just dashed stubs.\n  float soloPick = step(0.86, hash(vec2(col, 91.3 + row)));\n  float soloLife = roll(vec2(col, row + 61.0), u_time * 0.2 + seed);\n  float soloOn = smoothstep(0.62, 0.75, soloLife) * soloPick;\n  float soloCenter = (row + 0.5) / 5.0 + (hash(vec2(col, 27.9)) - 0.5) * 0.16;\n  float soloHalf = 0.012 + 0.05 * pow(hash(vec2(col, 14.2)), 2.0);\n  float sdy = (uv.y - soloCenter) / soloHalf;\n  float soloEnv = exp(-sdy * sdy);\n\n  float coverage = max(blockOn * rect, soloOn * soloEnv);\n\n  // Per-bar brightness: a slow breath plus the fast shimmer the source\n  // has on every lit bar, every frame.\n  float breath = 0.55 + 0.45 * roll(vec2(col, row), u_time * 0.5 + seed);\n  float ft = u_time * 9.0 + hash(vec2(col, 41.7)) * 4.0;\n  float flick = mix(\n    hash(vec2(col, floor(ft))),\n    hash(vec2(col, floor(ft) + 1.0)),\n    smoothstep(0.0, 1.0, fract(ft))\n  );\n  float glow = breath * (0.78 + 0.35 * flick);\n\n  // Halftone screen in device pixels; blur washes it out.\n  float checker = mod(\n    floor(gl_FragCoord.x / 1.5) + floor(gl_FragCoord.y / 1.5),\n    2.0\n  );\n  float dither = mix(0.6 + 0.4 * checker, 1.0, blur * 0.8);\n\n  // Palette per bar, biased toward the luminous middle of the ramp.\n  float hue = 0.12 + 0.6 * hash(vec2(col * 1.3, row * 7.1));\n  vec3 color = hue < 0.5\n    ? mix(u_color1, u_color2, hue * 2.0)\n    : mix(u_color2, u_color3, hue * 2.0 - 1.0);\n\n  // TWO FLARE STREAMS on independent clocks: the warm amber block\n  // (u_flare) and the rarer hot white one (u_white_flare).\n  float warmBody = flare(col, uv, period, u_flare, 0.0, seed) * profile * dither;\n  float whiteRaw = flare(col, uv, period, u_white_flare, 43.7, seed);\n  float whiteBody = whiteRaw * profile * dither;\n  // The white flare blooms: a soft halo that spills past the bar mask\n  // and fills the gaps between tubes with haze.\n  float whiteGlow = pow(whiteRaw, 0.6) * 0.35;\n\n  vec3 warm = mix(vec3(1.0, 0.93, 0.78), u_color2, 0.25);\n  // Warm white, leaning yellow — hot filament, not fluorescent.\n  vec3 white = vec3(1.0, 0.96, 0.72);\n\n  // Ambient bloom fog hugging the lit mass, dithered against banding.\n  vec2 fogCoord = vec2(px.x / (period * 30.0), uv.y * 2.6);\n  float fogField = vnoise(fogCoord + vec2(u_time * 0.03 + seed, seed * 3.1));\n  float fogJitter = (hash(gl_FragCoord.xy) - 0.5) * 0.06;\n  float fog = smoothstep(0.5 + fogJitter, 0.95, fogField) * u_glare * 0.12;\n  vec3 fogTint = mix(u_color1, u_color2, 0.45) * 0.8;\n\n  float crisp = profile * coverage * glow * dither;\n  float alpha = crisp + warmBody + whiteBody + whiteGlow + fog;\n\n  return vec4(\n    color * crisp * 1.6 + warm * warmBody * 1.5 +\n      white * (whiteBody * 1.7 + whiteGlow * 0.8) + fogTint * fog,\n    alpha\n  );\n}\n\nvoid main() {\n  // A slow, constant dolly toward the viewer: two copies of the field\n  // an octave apart scale up forever, crossfading as they trade places,\n  // so the approach never resets. A gentle sine wander rides along.\n  vec2 origin = u_resolution * 0.5;\n  float total = u_time * 0.025;\n  vec4 acc = vec4(0.0);\n  float weightSum = 0.0;\n\n  for (int k = 0; k < 2; k++) {\n    float fk = float(k);\n    float phase = fract(total + fk * 0.5);\n    float life = floor(total + fk * 0.5);\n    float scale = pow(2.0, phase);\n    float weight = sin(phase * 3.14159);\n    float drift = u_time * (0.8 + fk * 1.2) +\n      sin(u_time * (0.05 + fk * 0.04) + fk * 2.7) * 24.0;\n    vec2 px = origin + (gl_FragCoord.xy - origin) / scale;\n    px.x += drift;\n    // Depth of field: the near (large) end of a layer's life blurs.\n    float layerBlur = u_blur * smoothstep(0.45, 1.0, phase);\n    acc += field(px, life * 2.0 + fk, layerBlur) * weight;\n    weightSum += weight;\n  }\n\n  vec4 bars = acc / max(weightSum, 0.001);\n\n  // Opaque over black: the field is its own backdrop, so screenshots,\n  // recordings, and the page all see the same image.\n  vec3 outColor = min(bars.rgb, vec3(1.0)) * u_intensity;\n  gl_FragColor = vec4(outColor, 1.0);\n}\n`;\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}