{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "prism-gradient",
  "type": "registry:ui",
  "title": "Prism Gradient",
  "dependencies": [
    "next-themes",
    "clsx",
    "tailwind-merge"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "A theme-aware WebGL prism field with liquid checkered motion, electric-blue refraction, and tactile grain.",
  "files": [
    {
      "path": "components/ui/prism-gradient.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useTheme } from \"next-themes\";\nimport {\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  type CSSProperties,\n} from \"react\";\n\nexport interface PrismGradientNoise {\n  /** Opacity of the grain overlay. */\n  opacity: number;\n  /** Size multiplier for the grain texture. */\n  scale?: number;\n}\n\nexport interface PrismGradientProps {\n  /** Animation speed multiplier. Prism's original speed is 1. */\n  speed?: number;\n  /** Optional grain overlay. */\n  noise?: PrismGradientNoise;\n  /** Border radius applied to the gradient. */\n  radius?: string;\n  /** Additional inline styles. */\n  style?: CSSProperties;\n  /** Additional CSS classes. */\n  className?: string;\n}\n\nconst PRISM = {\n  dark: [\"#050505\", \"#66B3FF\", \"#FFFFFF\"],\n  light: [\"#FAFAFA\", \"#66B3FF\", \"#050505\"],\n  rotation: -50,\n  proportion: 1,\n  scale: 0.01,\n  speed: 30,\n  distortion: 0,\n  swirl: 50,\n  swirlIterations: 16,\n  softness: 47,\n  offset: -299,\n  shapeSize: 45,\n} as const;\n\nconst NOISE_TEXTURE =\n  \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEUAAAAAAAAAAAAAAAAAAAAAAADgKxmiAAAABnRSTlMCCgkGBAVJOAVJAAAASklEQVQ4y2NgGAWjYBSMglEwCgY/YGRgZBQUYmJiZGQEkYwMjIyMgoKCjIyMIJKBgRFIMjIyAklGRkYGRkFBYEcwMDIyMjAOUQAA1I4HwVwZAkYAAAAASUVORK5CYII=\";\n\nexport function PrismGradient({\n  speed = 1,\n  noise,\n  radius = \"0px\",\n  style,\n  className,\n}: PrismGradientProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const frameIdRef = useRef<number | undefined>(undefined);\n  const [mounted, setMounted] = useState(false);\n  const [webglFailed, setWebglFailed] = useState(false);\n  const { resolvedTheme } = useTheme();\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const colors = useMemo(\n    () => (mounted && resolvedTheme === \"light\" ? PRISM.light : PRISM.dark),\n    [mounted, resolvedTheme],\n  );\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container || !mounted || webglFailed) return;\n\n    const gl = canvas.getContext(\"webgl2\", {\n      premultipliedAlpha: true,\n      alpha: true,\n      antialias: true,\n    });\n    if (!gl) {\n      setWebglFailed(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      if (vertexShader) gl.deleteShader(vertexShader);\n      if (fragmentShader) gl.deleteShader(fragmentShader);\n      setWebglFailed(true);\n      return;\n    }\n\n    const program = gl.createProgram();\n    if (!program) {\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n      setWebglFailed(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      setWebglFailed(true);\n      return;\n    }\n    gl.useProgram(program);\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, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),\n      gl.STATIC_DRAW,\n    );\n\n    const positionLocation = gl.getAttribLocation(program, \"a_position\");\n    gl.enableVertexAttribArray(positionLocation);\n    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n\n    const uniform = (name: string) => gl.getUniformLocation(program, name);\n    const uniforms = {\n      time: uniform(\"u_time\"),\n      resolution: uniform(\"u_resolution\"),\n      pixelRatio: uniform(\"u_pixelRatio\"),\n      scale: uniform(\"u_scale\"),\n      rotation: uniform(\"u_rotation\"),\n      color1: uniform(\"u_color1\"),\n      color2: uniform(\"u_color2\"),\n      color3: uniform(\"u_color3\"),\n      proportion: uniform(\"u_proportion\"),\n      softness: uniform(\"u_softness\"),\n      shapeScale: uniform(\"u_shapeScale\"),\n      distortion: uniform(\"u_distortion\"),\n      swirl: uniform(\"u_swirl\"),\n      swirlIterations: uniform(\"u_swirlIterations\"),\n    };\n\n    const resize = () => {\n      const pixelRatio = window.devicePixelRatio || 1;\n      canvas.width = Math.max(\n        1,\n        Math.round(container.clientWidth * pixelRatio),\n      );\n      canvas.height = Math.max(\n        1,\n        Math.round(container.clientHeight * pixelRatio),\n      );\n      gl.viewport(0, 0, canvas.width, canvas.height);\n    };\n\n    resize();\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    const startedAt = performance.now();\n    const reduceMotion = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\",\n    ).matches;\n\n    const draw = (time: number) => {\n      const elapsed = (time - startedAt) / 1000;\n      const prismSpeed = (PRISM.speed / 100) * 5 * Math.max(0, speed);\n      const color1 = hexToRgba(colors[0]);\n      const color2 = hexToRgba(colors[1]);\n      const color3 = hexToRgba(colors[2]);\n\n      gl.uniform1f(uniforms.time, elapsed * prismSpeed + PRISM.offset * 0.01);\n      gl.uniform2f(uniforms.resolution, canvas.width, canvas.height);\n      gl.uniform1f(uniforms.pixelRatio, window.devicePixelRatio || 1);\n      gl.uniform1f(uniforms.scale, PRISM.scale);\n      gl.uniform1f(uniforms.rotation, (PRISM.rotation * Math.PI) / 180);\n      gl.uniform4fv(uniforms.color1, color1);\n      gl.uniform4fv(uniforms.color2, color2);\n      gl.uniform4fv(uniforms.color3, color3);\n      gl.uniform1f(uniforms.proportion, PRISM.proportion / 100);\n      gl.uniform1f(uniforms.softness, PRISM.softness / 100);\n      gl.uniform1f(uniforms.shapeScale, PRISM.shapeSize / 100);\n      gl.uniform1f(uniforms.distortion, PRISM.distortion / 50);\n      gl.uniform1f(uniforms.swirl, PRISM.swirl / 100);\n      gl.uniform1f(uniforms.swirlIterations, PRISM.swirlIterations);\n      gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n      if (!reduceMotion && speed > 0) {\n        frameIdRef.current = requestAnimationFrame(draw);\n      }\n    };\n\n    frameIdRef.current = requestAnimationFrame(draw);\n\n    return () => {\n      if (frameIdRef.current !== undefined)\n        cancelAnimationFrame(frameIdRef.current);\n      resizeObserver.disconnect();\n      gl.deleteBuffer(positionBuffer);\n      gl.deleteProgram(program);\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n    };\n  }, [colors, mounted, speed, webglFailed]);\n\n  return (\n    <div\n      ref={containerRef}\n      aria-hidden=\"true\"\n      className={cn(\"absolute inset-0 z-0 overflow-hidden\", className)}\n      style={{ borderRadius: radius, ...style }}\n    >\n      {webglFailed ? (\n        <div className=\"absolute inset-0 bg-[radial-gradient(circle_at_55%_45%,#66b3ff_0%,#050505_48%,#fff_100%)] dark:bg-[radial-gradient(circle_at_55%_45%,#66b3ff_0%,#050505_55%,#fff_100%)]\" />\n      ) : (\n        <canvas ref={canvasRef} className=\"block size-full\" />\n      )}\n      {noise && noise.opacity > 0 && (\n        <div\n          className=\"pointer-events-none absolute inset-0 bg-repeat\"\n          style={{\n            backgroundImage: `url(\"${NOISE_TEXTURE}\")`,\n            backgroundSize: (noise.scale ?? 1) * 200,\n            opacity: noise.opacity / 2,\n          }}\n        />\n      )}\n    </div>\n  );\n}\n\nfunction hexToRgba(hex: string): [number, number, number, number] {\n  const value = hex.replace(\"#\", \"\");\n  const expanded =\n    value.length === 3\n      ? value\n          .split(\"\")\n          .map((character) => character + character)\n          .join(\"\")\n      : value;\n\n  return [\n    Number.parseInt(expanded.slice(0, 2), 16) / 255,\n    Number.parseInt(expanded.slice(2, 4), 16) / 255,\n    Number.parseInt(expanded.slice(4, 6), 16) / 255,\n    expanded.length === 8 ? Number.parseInt(expanded.slice(6, 8), 16) / 255 : 1,\n  ];\n}\n\nconst VERTEX_SHADER = `#version 300 es\nin vec4 a_position;\nvoid main() {\n  gl_Position = a_position;\n}`;\n\nconst FRAGMENT_SHADER = `#version 300 es\nprecision highp float;\n\nuniform float u_time;\nuniform float u_pixelRatio;\nuniform vec2 u_resolution;\nuniform float u_scale;\nuniform float u_rotation;\nuniform vec4 u_color1;\nuniform vec4 u_color2;\nuniform vec4 u_color3;\nuniform float u_proportion;\nuniform float u_softness;\nuniform float u_shapeScale;\nuniform float u_distortion;\nuniform float u_swirl;\nuniform float u_swirlIterations;\n\nout vec4 fragColor;\n\n#define TWO_PI 6.28318530718\n#define PI 3.14159265358979323846\n\nvec2 rotate(vec2 uv, float th) {\n  return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;\n}\n\nfloat random(vec2 st) {\n  return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);\n}\n\nfloat noise(vec2 st) {\n  vec2 i = floor(st);\n  vec2 f = fract(st);\n  float a = random(i);\n  float b = random(i + vec2(1.0, 0.0));\n  float c = random(i + vec2(0.0, 1.0));\n  float d = random(i + vec2(1.0, 1.0));\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nvec4 blendColors(vec4 c1, vec4 c2, vec4 c3, float mixer, float edgesWidth, float edgeBlur) {\n  vec3 color1 = c1.rgb * c1.a;\n  vec3 color2 = c2.rgb * c2.a;\n  vec3 color3 = c3.rgb * c3.a;\n  float r1 = smoothstep(.0 + .35 * edgesWidth, .7 - .35 * edgesWidth + .5 * edgeBlur, mixer);\n  float r2 = smoothstep(.3 + .35 * edgesWidth, 1. - .35 * edgesWidth + edgeBlur, mixer);\n  vec3 blendedColor2 = mix(color1, color2, r1);\n  float blendedOpacity2 = mix(c1.a, c2.a, r1);\n  vec3 color = mix(blendedColor2, color3, r2);\n  float opacity = mix(blendedOpacity2, c3.a, r2);\n  return vec4(color, opacity);\n}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / u_resolution.xy;\n  float time = .5 * u_time;\n  float noiseScale = .0005 + .006 * u_scale;\n\n  uv -= .5;\n  uv *= noiseScale * u_resolution;\n  uv = rotate(uv, u_rotation * .5 * PI);\n  uv /= u_pixelRatio;\n  uv += .5;\n\n  float n1 = noise(uv + time);\n  float n2 = noise(uv * 2. - time);\n  float angle = n1 * TWO_PI;\n  uv.x += 4. * u_distortion * n2 * cos(angle);\n  uv.y += 4. * u_distortion * n2 * sin(angle);\n\n  float iterations = ceil(clamp(u_swirlIterations, 1., 30.));\n  for (float i = 1.; i <= iterations; i++) {\n    uv.x += clamp(u_swirl, 0., 2.) / i * cos(time + i * 1.5 * uv.y);\n    uv.y += clamp(u_swirl, 0., 2.) / i * cos(time + i * uv.x);\n  }\n\n  float proportion = clamp(u_proportion, 0., 1.);\n  vec2 checksUv = uv * (.5 + 3.5 * u_shapeScale);\n  float shape = .5 + .5 * sin(checksUv.x) * cos(checksUv.y);\n  float mixer = shape + .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5);\n  vec4 colorMix = blendColors(\n    u_color1,\n    u_color2,\n    u_color3,\n    mixer,\n    1. - clamp(u_softness, 0., 1.),\n    .01 + .01 * u_scale\n  );\n  fragColor = colorMix;\n}\n`;\n\nexport default PrismGradient;\n",
      "type": "registry:ui"
    }
  ]
}