{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-glass",
  "title": "Aurora Glass",
  "description": "A shimmering glass tile background with ripple layers, bevel shading, and chromatic spread.",
  "dependencies": [
    "react"
  ],
  "files": [
    {
      "path": "registry/gammaui/aurora-glass.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport type { ReactNode } from \"react\"\n\n/**\n * AuroraGlass\n * A shimmering background of colorful glass tiles, rendered with raw WebGL2.\n *\n * A handful of broad directional ripple layers sweep across the whole tile\n * lattice in one continuous field (sampled per-fragment in grid-space), so\n * the highlight band flows smoothly from tile to tile rather than each tile\n * animating independently. Each tile's rounded-rect bevel normal warps the\n * sample point near its edges, giving a refraction-like bend as the band\n * crosses every tile.\n *\n * Props\n * ----------------------------------------------------------------------\n * width            string | number   \"100%\"     Container width\n * height           string | number   \"100%\"     Container height\n * className        string            \"\"         Additional CSS classes\n * children         ReactNode         undefined  Content rendered above the effect\n * speed            number            1          Animation speed multiplier (0-3)\n * tileDensity      number            4          Tiles across the surface (1-16)\n * rippleLayers     number            6          Stacked ripple layers (1-8)\n * warpStrength     number            0.33       Per-tile inverse-distance warp (0-0.6)\n * bandSharpness    number            3          Highlight peak sharpness (0.5-10)\n * chromaticSpread  number            0          Per-channel separation (0-1)\n * colorA           string            \"#1E00FF\"  Gradient stop (deep color)\n * colorB           string            \"#D765E6\"  Gradient stop (bright color)\n * backgroundColor  string            \"#FFFFFF\"  Fill where the field is dark\n * opacity          number            1          Master alpha (0-1)\n * dpr              number            1.5        Max device pixel ratio (1-3)\n *\n * Usage\n *   <AuroraGlass colorA=\"#1E00FF\" colorB=\"#D765E6\" tileDensity={6}>\n *     <h1>Your content, rendered above the effect</h1>\n *   </AuroraGlass>\n */\n\nconst MAX_RIPPLE_LAYERS = 8\n\nconst VERTEX_SRC = `#version 300 es\nin vec2 a_position;\nvoid main() {\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n`\n\nconst FRAGMENT_SRC = `#version 300 es\nprecision highp float;\n\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_speed;\nuniform float u_tileDensity;\nuniform int u_rippleLayers;\nuniform float u_warpStrength;\nuniform float u_bandSharpness;\nuniform float u_chromaticSpread;\nuniform vec3 u_colorA;\nuniform vec3 u_colorB;\nuniform vec3 u_backgroundColor;\nuniform float u_opacity;\n\nout vec4 fragColor;\n\nfloat hash21(vec2 p) {\n  p = fract(p * vec2(123.34, 456.21));\n  p += dot(p, p + 45.32);\n  return fract(p.x * p.y);\n}\n\nvec2 hash22(vec2 p) {\n  float n = hash21(p);\n  float n2 = hash21(p + 17.13);\n  return vec2(n, n2);\n}\n\nfloat sdRoundRect(vec2 p, vec2 halfSize, float r) {\n  vec2 q = abs(p) - halfSize + r;\n  return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;\n}\n\n// Height field for the tile surface: flat in the middle, with a wide\n// rounded bevel that rises toward the border -- like a slightly domed pane\n// of glass set in a frame. d is the rounded-rect SDF (negative inside, 0 at\n// the border). Near the border height is highest; deep inside it flattens.\nfloat tileHeight(float d, float r) {\n  float bevelWidth = r * 4.0;\n  float distFromEdge = clamp(-d / bevelWidth, 0.0, 1.0);\n  float bevel = 1.0 - distFromEdge;\n  return bevel * bevel * (3.0 - 2.0 * bevel);\n}\n\n// One directional ripple band. Layer 0-2 are broad, slow, coherent sweeps\n// (the main visible \"ribbons\"); layers beyond that add progressively finer,\n// dimmer secondary detail so increasing rippleLayers enriches the texture\n// without diluting the primary bands.\nfloat rippleLayer(vec2 guv, float t, int i) {\n  float fi = float(i);\n  float dirAngle = 0.55 + fi * 0.7;\n  vec2 dir = vec2(cos(dirAngle), sin(dirAngle));\n  vec2 perp = vec2(-dir.y, dir.x);\n\n  float freqBase = 0.16 + fract(fi * 0.31) * 0.1;\n  float freq = freqBase * (1.0 + fi * 0.22);\n  float speedVar = 0.35 + fract(fi * 0.53) * 0.35;\n  float phase = fi * 2.7;\n  float bow = 1.6 + fract(fi * 0.19) * 1.4;\n\n  float along = dot(guv, dir);\n  float across = dot(guv, perp);\n\n  float bend = sin(along * freq + t * speedVar + phase) * bow\n             + cos(t * speedVar * 0.5 + phase * 1.3) * 1.2;\n  float d = abs(across - bend);\n\n  float sharpness = 6.0 + fi * 1.5;\n  float core = exp(-d * d * sharpness);\n  float halo = exp(-d * d * 1.1) * 0.15;\n\n  float weight = 1.0 / (1.0 + fi * 0.55);\n\n  return (core + halo) * weight;\n}\n\n// Sum of N ripple layers sampled at this grid-space position. The field is\n// continuous across the whole grid (not per-tile), so highlights flow\n// smoothly from one tile into its neighbor.\nfloat lightField(vec2 guv, float t) {\n  float field = 0.0;\n  for (int i = 0; i < ${MAX_RIPPLE_LAYERS}; i++) {\n    if (i >= u_rippleLayers) break;\n    field += rippleLayer(guv, t, i);\n  }\n  return field;\n}\n\nvoid main() {\n  vec2 fragCoord = gl_FragCoord.xy;\n  vec2 res = u_resolution;\n\n  // tileDensity = how many tile cells fit across the SHORTER side of the\n  // surface, so density reads consistently regardless of aspect ratio.\n  float shortSide = min(res.x, res.y);\n  float cell = shortSide / max(u_tileDensity, 1.0);\n  float gap = cell * 0.06;\n\n  vec2 gridPos = fragCoord / cell;\n  vec2 cellId = floor(gridPos);\n  vec2 localUv = fract(gridPos) - 0.5;\n  vec2 localPx = localUv * cell;\n\n  float tileHalf = (cell - gap) * 0.5;\n  float radius = tileHalf * 0.32;\n\n  float d = sdRoundRect(localPx, vec2(tileHalf), radius);\n\n  if (d > 0.0) {\n    fragColor = vec4(u_backgroundColor, u_opacity);\n    return;\n  }\n\n  vec2 rnd = hash22(cellId);\n\n  // --- Always-on ambient bevel shading ---\n  // A true 3D surface normal derived from a domed height field, lit by a\n  // fixed upper-left studio light. This runs on EVERY tile regardless of\n  // the colorful streak, so dark tiles still read as curved glass rather\n  // than flat black squares.\n  float epsH = 1.2;\n  float dCx1 = sdRoundRect(localPx + vec2(epsH, 0.0), vec2(tileHalf), radius);\n  float dCx0 = sdRoundRect(localPx - vec2(epsH, 0.0), vec2(tileHalf), radius);\n  float dCy1 = sdRoundRect(localPx + vec2(0.0, epsH), vec2(tileHalf), radius);\n  float dCy0 = sdRoundRect(localPx - vec2(0.0, epsH), vec2(tileHalf), radius);\n  float hR = tileHeight(dCx1, radius);\n  float hL = tileHeight(dCx0, radius);\n  float hU = tileHeight(dCy1, radius);\n  float hD = tileHeight(dCy0, radius);\n  vec2 heightGrad = vec2(hR - hL, hU - hD) / (2.0 * epsH);\n  vec3 surfaceNormal = normalize(vec3(-heightGrad * 4.0, 1.0));\n\n  vec3 lightDir = normalize(vec3(-0.45, 0.55, 0.7));\n  vec3 viewDir = vec3(0.0, 0.0, 1.0);\n  vec3 halfDir = normalize(lightDir + viewDir);\n\n  float diffuse = max(dot(surfaceNormal, lightDir), 0.0);\n  float specular = pow(max(dot(surfaceNormal, halfDir), 0.0), 14.0);\n\n  // Tinted by the background color rather than neutral gray, so it reads as\n  // a hint of glass curvature rather than a separate plastic/keycap layer.\n  vec3 ambientBevel = u_backgroundColor * diffuse * 0.16\n                     + mix(u_backgroundColor, vec3(0.7, 0.65, 0.78), 0.5) * specular * 0.22;\n\n  // Bevel normal from the rounded-rect SDF gradient, used only to bend\n  // (warp) the light sample point near tile edges -- a refraction cue.\n  float eps = 1.5;\n  float dx = sdRoundRect(localPx + vec2(eps, 0.0), vec2(tileHalf), radius)\n           - sdRoundRect(localPx - vec2(eps, 0.0), vec2(tileHalf), radius);\n  float dy = sdRoundRect(localPx + vec2(0.0, eps), vec2(tileHalf), radius)\n           - sdRoundRect(localPx - vec2(0.0, eps), vec2(tileHalf), radius);\n  vec2 gradDir = vec2(dx, dy) / (2.0 * eps);\n  float rim = smoothstep(-radius * 1.6, 0.0, d);\n\n  // Per-tile inverse-distance warp: the closer to the tile edge, the more\n  // the sample point bends, like light refracting through curved glass.\n  float distFromCenter = length(localPx) / max(tileHalf, 0.001);\n  float invDist = 1.0 / max(1.0 - distFromCenter * 0.85, 0.15);\n  vec2 warpOffset = gradDir * rim * u_warpStrength * invDist * 0.4;\n\n  float t = u_time * u_speed;\n  vec2 sampleUv = gridPos + warpOffset;\n\n  float fieldR = lightField(sampleUv + vec2(u_chromaticSpread * 0.6, 0.0), t);\n  float fieldG = lightField(sampleUv, t);\n  float fieldB = lightField(sampleUv - vec2(u_chromaticSpread * 0.6, 0.0), t);\n\n  float shaped = pow(clamp(fieldG, 0.0, 1.6), u_bandSharpness);\n  float shapedR = pow(clamp(fieldR, 0.0, 1.6), u_bandSharpness);\n  float shapedB = pow(clamp(fieldB, 0.0, 1.6), u_bandSharpness);\n\n  float variance = mix(0.75, 1.15, rnd.y);\n  shaped *= variance;\n  shapedR *= variance;\n  shapedB *= variance;\n\n  vec3 gradColor = mix(u_colorA, u_colorB, clamp(shaped, 0.0, 1.0));\n\n  vec3 lit = gradColor * shaped;\n  float chromaMix = clamp(u_chromaticSpread * 3.0, 0.0, 1.0);\n  lit.r = mix(lit.r, gradColor.r * shapedR, chromaMix);\n  lit.b = mix(lit.b, gradColor.b * shapedB, chromaMix);\n\n  vec3 color = u_backgroundColor * 0.03 + ambientBevel + lit;\n\n  // Soft recessed contact shadow right at the rounded border\n  float innerEdge = smoothstep(-2.5, 0.0, d);\n  color *= (1.0 - innerEdge * 0.55);\n\n  fragColor = vec4(color, u_opacity);\n}\n`\n\nfunction compileShader(\n  gl: WebGL2RenderingContext,\n  type: number,\n  source: string\n): WebGLShader {\n  const shader = gl.createShader(type)!\n  gl.shaderSource(shader, source)\n  gl.compileShader(shader)\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    const info = gl.getShaderInfoLog(shader)\n    gl.deleteShader(shader)\n    throw new Error(\"Shader compile error: \" + info)\n  }\n  return shader\n}\n\nfunction createProgram(\n  gl: WebGL2RenderingContext,\n  vertexSrc: string,\n  fragmentSrc: string\n): WebGLProgram {\n  const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc)\n  const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc)\n  const program = gl.createProgram()!\n  gl.attachShader(program, vs)\n  gl.attachShader(program, fs)\n  gl.linkProgram(program)\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n    const info = gl.getProgramInfoLog(program)\n    gl.deleteProgram(program)\n    throw new Error(\"Program link error: \" + info)\n  }\n  gl.deleteShader(vs)\n  gl.deleteShader(fs)\n  return program\n}\n\nfunction hexToRgb(hex: string) {\n  const clean = (hex || \"#000000\").replace(\"#\", \"\")\n  const full =\n    clean.length === 3\n      ? clean\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : clean\n  const bigint = parseInt(full, 16) || 0\n  const r = ((bigint >> 16) & 255) / 255\n  const g = ((bigint >> 8) & 255) / 255\n  const b = (bigint & 255) / 255\n  return [r, g, b]\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n  Math.min(max, Math.max(min, value))\n\ninterface AuroraGlassProps {\n  width?: string | number\n  height?: string | number\n  className?: string\n  children?: ReactNode\n  speed?: number\n  tileDensity?: number\n  rippleLayers?: number\n  warpStrength?: number\n  bandSharpness?: number\n  chromaticSpread?: number\n  colorA?: string\n  colorB?: string\n  backgroundColor?: string\n  opacity?: number\n  dpr?: number\n}\n\nexport default function AuroraGlass({\n  width = \"100%\",\n  height = \"100%\",\n  className = \"\",\n  children,\n  speed = 1,\n  tileDensity = 4,\n  rippleLayers = 6,\n  warpStrength = 0.33,\n  bandSharpness = 3,\n  chromaticSpread = 0,\n  colorA = \"#1E00FF\",\n  colorB = \"#D765E6\",\n  backgroundColor = \"#FFFFFF\",\n  opacity = 1,\n  dpr = 1.5,\n}: AuroraGlassProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const rafRef = useRef(0)\n\n  // Keep the latest prop values in a ref so the render loop (started once)\n  // always reads current values without needing to be torn down and\n  // rebuilt on every prop change.\n  interface PropsSnapshot {\n    speed: number\n    tileDensity: number\n    rippleLayers: number\n    warpStrength: number\n    bandSharpness: number\n    chromaticSpread: number\n    colorA: string\n    colorB: string\n    backgroundColor: string\n    opacity: number\n  }\n  const propsRef = useRef<PropsSnapshot>({} as PropsSnapshot)\n  propsRef.current = {\n    speed,\n    tileDensity: clamp(tileDensity, 1, 16),\n    rippleLayers: Math.round(clamp(rippleLayers, 1, MAX_RIPPLE_LAYERS)),\n    warpStrength: clamp(warpStrength, 0, 0.6),\n    bandSharpness: clamp(bandSharpness, 0.5, 10),\n    chromaticSpread: clamp(chromaticSpread, 0, 1),\n    colorA,\n    colorB,\n    backgroundColor,\n    opacity: clamp(opacity, 0, 1),\n  }\n\n  const dprRef = useRef(clamp(dpr, 1, 3))\n  dprRef.current = clamp(dpr, 1, 3)\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    if (!canvas) return\n\n    const gl = canvas.getContext(\"webgl2\", { antialias: true, alpha: true })\n    if (!gl) {\n      console.warn(\n        \"WebGL2 is not supported in this browser; AuroraGlass cannot render.\"\n      )\n      return\n    }\n\n    let program: WebGLProgram\n    try {\n      program = createProgram(gl, VERTEX_SRC, FRAGMENT_SRC)\n    } catch (err) {\n      console.error(err)\n      return\n    }\n    gl.useProgram(program)\n    gl.enable(gl.BLEND)\n    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)\n\n    const positionBuffer = gl.createBuffer()\n    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 3, -1, -1, 3]),\n      gl.STATIC_DRAW\n    )\n    const positionLoc = gl.getAttribLocation(program, \"a_position\")\n    gl.enableVertexAttribArray(positionLoc)\n    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n    const u: Record<string, WebGLUniformLocation | null> = {}\n    ;[\n      \"u_resolution\",\n      \"u_time\",\n      \"u_speed\",\n      \"u_tileDensity\",\n      \"u_rippleLayers\",\n      \"u_warpStrength\",\n      \"u_bandSharpness\",\n      \"u_chromaticSpread\",\n      \"u_colorA\",\n      \"u_colorB\",\n      \"u_backgroundColor\",\n      \"u_opacity\",\n    ].forEach((name) => {\n      u[name] = gl.getUniformLocation(program, name)\n    })\n\n    let w = 0\n    let h = 0\n\n    function resize() {\n      const cv = canvas!\n      const parent = cv.parentElement\n      const rect = parent\n        ? parent.getBoundingClientRect()\n        : cv.getBoundingClientRect()\n      const ratio = dprRef.current\n      w = Math.max(1, Math.floor(rect.width * ratio))\n      h = Math.max(1, Math.floor(rect.height * ratio))\n      if (cv.width !== w || cv.height !== h) {\n        cv.width = w\n        cv.height = h\n      }\n      gl!.viewport(0, 0, w, h)\n    }\n\n    const resizeObserver = new ResizeObserver(resize)\n    if (canvas.parentElement) resizeObserver.observe(canvas.parentElement)\n    resize()\n\n    const start = performance.now()\n\n    function frame(now: number) {\n      const t = (now - start) / 1000\n      const p = propsRef.current\n      const g = gl!\n\n      g.useProgram(program)\n      g.clearColor(0, 0, 0, 0)\n      g.clear(g.COLOR_BUFFER_BIT)\n\n      g.uniform2f(u.u_resolution, w, h)\n      g.uniform1f(u.u_time, t)\n      g.uniform1f(u.u_speed, p.speed)\n      g.uniform1f(u.u_tileDensity, p.tileDensity)\n      g.uniform1i(u.u_rippleLayers, p.rippleLayers)\n      g.uniform1f(u.u_warpStrength, p.warpStrength)\n      g.uniform1f(u.u_bandSharpness, p.bandSharpness)\n      g.uniform1f(u.u_chromaticSpread, p.chromaticSpread)\n      g.uniform3fv(u.u_colorA, hexToRgb(p.colorA))\n      g.uniform3fv(u.u_colorB, hexToRgb(p.colorB))\n      g.uniform3fv(u.u_backgroundColor, hexToRgb(p.backgroundColor))\n      g.uniform1f(u.u_opacity, p.opacity)\n\n      g.drawArrays(g.TRIANGLES, 0, 3)\n      rafRef.current = requestAnimationFrame(frame)\n    }\n\n    rafRef.current = requestAnimationFrame(frame)\n\n    return () => {\n      cancelAnimationFrame(rafRef.current)\n      resizeObserver.disconnect()\n      gl.deleteProgram(program)\n      gl.deleteBuffer(positionBuffer)\n    }\n    // Render loop is started once; per-frame values are read from propsRef\n    // and dprRef so prop changes don't require tearing down the GL context.\n  }, [])\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"relative\",\n        width,\n        height,\n        overflow: \"hidden\",\n      }}\n    >\n      <canvas\n        ref={canvasRef}\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          display: \"block\",\n          width: \"100%\",\n          height: \"100%\",\n        }}\n      />\n      {children != null && (\n        <div style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}>\n          {children}\n        </div>\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}