{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-gradient",
  "title": "Animated Gradient",
  "description": "Animated gradient background with presets and customizable swirl effects using WebGL.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/spell-ui/animated-gradient.tsx",
      "content": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { useRef, useEffect, useMemo, useState, type CSSProperties } from \"react\";\n\ntype PatternShape = \"Checks\" | \"Stripes\" | \"Edge\";\n\nconst PatternShapes: Record<PatternShape, number> = {\n  Checks: 0,\n  Stripes: 1,\n  Edge: 2,\n};\n\ninterface PresetColors {\n  color1: string;\n  color2: string;\n  color3: string;\n}\n\ninterface PresetParams extends PresetColors {\n  rotation: number;\n  proportion: number;\n  scale: number;\n  speed: number;\n  distortion: number;\n  swirl: number;\n  swirlIterations: number;\n  softness: number;\n  offset: number;\n  shape: PatternShape;\n  shapeSize: number;\n  lightColors?: PresetColors;\n}\n\nconst presets: Record<PresetName, PresetParams> = {\n  Prism: {\n    color1: \"#050505\",\n    color2: \"#66B3FF\",\n    color3: \"#FFFFFF\",\n    lightColors: {\n      color1: \"#FAFAFA\",\n      color2: \"#66B3FF\",\n      color3: \"#050505\",\n    },\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    shape: \"Checks\",\n    shapeSize: 45,\n  },\n  Lava: {\n    color1: \"#FF9F21\",\n    color2: \"#FF0303\",\n    color3: \"#000000\",\n    lightColors: {\n      color1: \"#FF9F21\",\n      color2: \"#FF0303\",\n      color3: \"#FAFAFA\",\n    },\n    rotation: 114,\n    proportion: 100,\n    scale: 0.52,\n    speed: 30,\n    distortion: 7,\n    swirl: 18,\n    swirlIterations: 20,\n    softness: 100,\n    offset: 717,\n    shape: \"Edge\",\n    shapeSize: 12,\n  },\n  Plasma: {\n    color1: \"#B566FF\",\n    color2: \"#000000\",\n    color3: \"#000000\",\n    lightColors: {\n      color1: \"#B566FF\",\n      color2: \"#FAFAFA\",\n      color3: \"#FAFAFA\",\n    },\n    rotation: 0,\n    proportion: 63,\n    scale: 0.75,\n    speed: 30,\n    distortion: 5,\n    swirl: 61,\n    swirlIterations: 5,\n    softness: 100,\n    offset: -168,\n    shape: \"Checks\",\n    shapeSize: 28,\n  },\n  Pulse: {\n    color1: \"#66FF85\",\n    color2: \"#000000\",\n    color3: \"#000000\",\n    lightColors: {\n      color1: \"#66FF85\",\n      color2: \"#FAFAFA\",\n      color3: \"#FAFAFA\",\n    },\n    rotation: -167,\n    proportion: 92,\n    scale: 0,\n    speed: 20,\n    distortion: 54,\n    swirl: 75,\n    swirlIterations: 3,\n    softness: 28,\n    offset: -813,\n    shape: \"Checks\",\n    shapeSize: 79,\n  },\n  Vortex: {\n    color1: \"#000000\",\n    color2: \"#FFFFFF\",\n    color3: \"#000000\",\n    lightColors: {\n      color1: \"#FAFAFA\",\n      color2: \"#000000\",\n      color3: \"#FAFAFA\",\n    },\n    rotation: 50,\n    proportion: 41,\n    scale: 0.4,\n    speed: 20,\n    distortion: 0,\n    swirl: 100,\n    swirlIterations: 3,\n    softness: 5,\n    offset: -744,\n    shape: \"Stripes\",\n    shapeSize: 80,\n  },\n  Mist: {\n    color1: \"#050505\",\n    color2: \"#FF66B8\",\n    color3: \"#050505\",\n    lightColors: {\n      color1: \"#FAFAFA\",\n      color2: \"#FF66B8\",\n      color3: \"#FAFAFA\",\n    },\n    rotation: 0,\n    proportion: 33,\n    scale: 0.48,\n    speed: 39,\n    distortion: 4,\n    swirl: 65,\n    swirlIterations: 5,\n    softness: 100,\n    offset: -235,\n    shape: \"Edge\",\n    shapeSize: 48,\n  },\n};\n\ntype PresetName = \"Prism\" | \"Lava\" | \"Plasma\" | \"Pulse\" | \"Vortex\" | \"Mist\";\n\ninterface CustomConfig {\n  preset: \"custom\";\n  color1: string;\n  color2: string;\n  color3: string;\n  rotation?: number;\n  proportion?: number;\n  scale?: number;\n  speed?: number;\n  distortion?: number;\n  swirl?: number;\n  swirlIterations?: number;\n  softness?: number;\n  offset?: number;\n  shape?: PatternShape;\n  shapeSize?: number;\n}\n\ninterface PresetConfig {\n  preset: PresetName;\n  speed?: number;\n}\n\ntype GradientConfig = CustomConfig | PresetConfig;\n\ninterface NoiseConfig {\n  opacity: number;\n  scale?: number;\n}\n\ninterface AnimatedGradientProps {\n  config?: GradientConfig;\n  noise?: NoiseConfig;\n  radius?: string;\n  style?: CSSProperties;\n  className?: string;\n}\n\nexport default function AnimatedGradient({\n  config = { preset: \"Prism\" },\n  noise,\n  radius = \"0px\",\n  style,\n  className,\n}: AnimatedGradientProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const frameIdRef = useRef<number | undefined>(undefined);\n  const startTimeRef = useRef<number>(0);\n\n  const [isMounted, setIsMounted] = useState(false);\n  const { resolvedTheme } = useTheme();\n\n  useEffect(() => {\n    setIsMounted(true);\n    return () => setIsMounted(false);\n  }, []);\n\n  const params = useMemo((): PresetParams => {\n    if (config.preset === \"custom\") {\n      return {\n        color1: config.color1,\n        color2: config.color2,\n        color3: config.color3,\n        rotation: config.rotation ?? 0,\n        proportion: config.proportion ?? 35,\n        scale: config.scale ?? 1,\n        speed: config.speed ?? 25,\n        distortion: config.distortion ?? 12,\n        swirl: config.swirl ?? 80,\n        swirlIterations: config.swirlIterations ?? 10,\n        softness: config.softness ?? 100,\n        offset: config.offset ?? 0,\n        shape: config.shape ?? \"Checks\",\n        shapeSize: config.shapeSize ?? 10,\n      };\n    }\n    const preset = presets[config.preset] || presets.Prism;\n    const useLight = isMounted && resolvedTheme === \"light\" && preset.lightColors;\n    return {\n      ...preset,\n      ...(useLight ? preset.lightColors : null),\n      speed: config.speed ?? preset.speed,\n    };\n  }, [config, isMounted, resolvedTheme]);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container || !isMounted) return;\n\n    const gl = canvas.getContext(\"webgl2\", {\n      premultipliedAlpha: true,\n      alpha: true,\n      antialias: true,\n    });\n    if (!gl) return;\n\n    const vertexShaderSource = `#version 300 es\n    in vec4 a_position;\n    void main() {\n      gl_Position = a_position;\n    }`;\n\n    const vertexShader = gl.createShader(gl.VERTEX_SHADER)!;\n    gl.shaderSource(vertexShader, vertexShaderSource);\n    gl.compileShader(vertexShader);\n\n    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)!;\n    gl.shaderSource(fragmentShader, FRAGMENT_SHADER);\n    gl.compileShader(fragmentShader);\n\n    const program = gl.createProgram()!;\n    gl.attachShader(program, vertexShader);\n    gl.attachShader(program, fragmentShader);\n    gl.linkProgram(program);\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 uniforms = {\n      u_time: gl.getUniformLocation(program, \"u_time\"),\n      u_resolution: gl.getUniformLocation(program, \"u_resolution\"),\n      u_pixelRatio: gl.getUniformLocation(program, \"u_pixelRatio\"),\n      u_scale: gl.getUniformLocation(program, \"u_scale\"),\n      u_rotation: gl.getUniformLocation(program, \"u_rotation\"),\n      u_color1: gl.getUniformLocation(program, \"u_color1\"),\n      u_color2: gl.getUniformLocation(program, \"u_color2\"),\n      u_color3: gl.getUniformLocation(program, \"u_color3\"),\n      u_proportion: gl.getUniformLocation(program, \"u_proportion\"),\n      u_softness: gl.getUniformLocation(program, \"u_softness\"),\n      u_shape: gl.getUniformLocation(program, \"u_shape\"),\n      u_shapeScale: gl.getUniformLocation(program, \"u_shapeScale\"),\n      u_distortion: gl.getUniformLocation(program, \"u_distortion\"),\n      u_swirl: gl.getUniformLocation(program, \"u_swirl\"),\n      u_swirlIterations: gl.getUniformLocation(program, \"u_swirlIterations\"),\n    };\n\n    const resize = () => {\n      const width = container.clientWidth;\n      const height = container.clientHeight;\n      const pixelRatio = window.devicePixelRatio || 1;\n      canvas.width = width * pixelRatio;\n      canvas.height = height * pixelRatio;\n      canvas.style.width = `${width}px`;\n      canvas.style.height = `${height}px`;\n      gl.viewport(0, 0, canvas.width, canvas.height);\n    };\n\n    resize();\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n\n    startTimeRef.current = performance.now();\n\n    const animate = (time: number) => {\n      const elapsed = (time - startTimeRef.current) / 1000;\n      const speed = (params.speed / 100) * 5;\n\n      gl.uniform1f(uniforms.u_time, elapsed * speed + params.offset * 0.01);\n      gl.uniform2f(uniforms.u_resolution, canvas.width, canvas.height);\n      gl.uniform1f(uniforms.u_pixelRatio, window.devicePixelRatio || 1);\n      gl.uniform1f(uniforms.u_scale, params.scale);\n      gl.uniform1f(uniforms.u_rotation, (params.rotation * Math.PI) / 180);\n\n      const c1 = hexToRgba(params.color1);\n      const c2 = hexToRgba(params.color2);\n      const c3 = hexToRgba(params.color3);\n      gl.uniform4f(uniforms.u_color1, c1[0], c1[1], c1[2], c1[3]);\n      gl.uniform4f(uniforms.u_color2, c2[0], c2[1], c2[2], c2[3]);\n      gl.uniform4f(uniforms.u_color3, c3[0], c3[1], c3[2], c3[3]);\n\n      gl.uniform1f(uniforms.u_proportion, params.proportion / 100);\n      gl.uniform1f(uniforms.u_softness, params.softness / 100);\n      gl.uniform1f(uniforms.u_shape, PatternShapes[params.shape]);\n      gl.uniform1f(uniforms.u_shapeScale, params.shapeSize / 100);\n      gl.uniform1f(uniforms.u_distortion, params.distortion / 50);\n      gl.uniform1f(uniforms.u_swirl, params.swirl / 100);\n      gl.uniform1f(\n        uniforms.u_swirlIterations,\n        params.swirl === 0 ? 0 : params.swirlIterations\n      );\n\n      gl.drawArrays(gl.TRIANGLES, 0, 6);\n      frameIdRef.current = requestAnimationFrame(animate);\n    };\n\n    frameIdRef.current = requestAnimationFrame(animate);\n\n    return () => {\n      if (frameIdRef.current !== undefined) {\n        cancelAnimationFrame(frameIdRef.current);\n      }\n      resizeObserver.disconnect();\n      gl.deleteProgram(program);\n      gl.deleteShader(vertexShader);\n      gl.deleteShader(fragmentShader);\n      gl.deleteBuffer(positionBuffer);\n    };\n  }, [isMounted, params]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={className}\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        zIndex: -1,\n        borderRadius: radius,\n        overflow: \"hidden\",\n        ...style,\n      }}\n    >\n      <canvas\n        ref={canvasRef}\n        style={{\n          display: \"block\",\n          width: \"100%\",\n          height: \"100%\",\n        }}\n      />\n      {noise && noise.opacity > 0 && (\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            backgroundImage: `url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEUAAAAAAAAAAAAAAAAAAAAAAADgKxmiAAAABnRSTlMCCgkGBAVJOAVJAAAASklEQVQ4y2NgGAWjYBSMglEwCgY/YGRgZBQUYmJiZGQEkYwMjIyMgoKCjIyMIJKBgRFIMjIyAklGRkYGRkFBYEcwMDIyMjAOUQAA1I4HwVwZAkYAAAAASUVORK5CYII=\")`,\n            backgroundSize: (noise.scale ?? 1) * 200,\n            backgroundRepeat: \"repeat\",\n            opacity: noise.opacity / 2,\n            pointerEvents: \"none\",\n          }}\n        />\n      )}\n    </div>\n  );\n}\n\nfunction hexToRgba(hex: string): [number, number, number, number] {\n  let r = 0,\n    g = 0,\n    b = 0,\n    a = 1;\n\n  if (hex.startsWith(\"rgba(\")) {\n    const parts = hex.slice(5, -1).split(\",\");\n    r = parseInt(parts[0]) / 255;\n    g = parseInt(parts[1]) / 255;\n    b = parseInt(parts[2]) / 255;\n    a = parseFloat(parts[3]);\n  } else if (hex.startsWith(\"rgb(\")) {\n    const parts = hex.slice(4, -1).split(\",\");\n    r = parseInt(parts[0]) / 255;\n    g = parseInt(parts[1]) / 255;\n    b = parseInt(parts[2]) / 255;\n  } else if (hex.startsWith(\"hsla(\") || hex.startsWith(\"hsl(\")) {\n    const isHsla = hex.startsWith(\"hsla(\");\n    const parts = hex.slice(isHsla ? 5 : 4, -1).split(\",\");\n    const h = parseFloat(parts[0]) / 360;\n    const s = parseFloat(parts[1]) / 100;\n    const l = parseFloat(parts[2]) / 100;\n    a = isHsla ? parseFloat(parts[3]) : 1;\n    [r, g, b] = hslToRgb(h, s, l);\n  } else if (hex.startsWith(\"#\")) {\n    const c = hex.slice(1);\n    if (c.length === 3) {\n      r = parseInt(c[0] + c[0], 16) / 255;\n      g = parseInt(c[1] + c[1], 16) / 255;\n      b = parseInt(c[2] + c[2], 16) / 255;\n    } else if (c.length >= 6) {\n      r = parseInt(c.slice(0, 2), 16) / 255;\n      g = parseInt(c.slice(2, 4), 16) / 255;\n      b = parseInt(c.slice(4, 6), 16) / 255;\n      if (c.length === 8) {\n        a = parseInt(c.slice(6, 8), 16) / 255;\n      }\n    }\n  }\n\n  return [r, g, b, a];\n}\n\nfunction hslToRgb(h: number, s: number, l: number): [number, number, number] {\n  let r: number, g: number, b: number;\n\n  if (s === 0) {\n    r = g = b = l;\n  } else {\n    const hue2rgb = (p: number, q: number, t: number) => {\n      if (t < 0) t += 1;\n      if (t > 1) t -= 1;\n      if (t < 1 / 6) return p + (q - p) * 6 * t;\n      if (t < 1 / 2) return q;\n      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n      return p;\n    };\n\n    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n    const p = 2 * l - q;\n    r = hue2rgb(p, q, h + 1 / 3);\n    g = hue2rgb(p, q, h);\n    b = hue2rgb(p, q, h - 1 / 3);\n  }\n\n  return [r, g, b];\n}\n\nconst FRAGMENT_SHADER = `#version 300 es\nprecision highp float;\n\nuniform float u_time;\nuniform float u_pixelRatio;\nuniform vec2 u_resolution;\n\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_shape;\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\n  vec2 u = f * f * (3.0 - 2.0 * f);\n\n  float x1 = mix(a, b, u.x);\n  float x2 = mix(c, d, u.x);\n  return mix(x1, x2, u.y);\n}\n\nvec4 blend_colors(vec4 c1, vec4 c2, vec4 c3, float mixer, float edgesWidth, float edge_blur) {\n    vec3 color1 = c1.rgb * c1.a;\n    vec3 color2 = c2.rgb * c2.a;\n    vec3 color3 = c3.rgb * c3.a;\n\n    float r1 = smoothstep(.0 + .35 * edgesWidth, .7 - .35 * edgesWidth + .5 * edge_blur, mixer);\n    float r2 = smoothstep(.3 + .35 * edgesWidth, 1. - .35 * edgesWidth + edge_blur, mixer);\n\n    vec3 blended_color_2 = mix(color1, color2, r1);\n    float blended_opacity_2 = mix(c1.a, c2.a, r1);\n\n    vec3 c = mix(blended_color_2, color3, r2);\n    float o = mix(blended_opacity_2, c3.a, r2);\n    return vec4(c, o);\n}\n\nvoid main() {\n    vec2 uv = gl_FragCoord.xy / u_resolution.xy;\n\n    float t = .5 * u_time;\n\n    float noise_scale = .0005 + .006 * u_scale;\n\n    uv -= .5;\n    uv *= (noise_scale * u_resolution);\n    uv = rotate(uv, u_rotation * .5 * PI);\n    uv /= u_pixelRatio;\n    uv += .5;\n\n    float n1 = noise(uv * 1. + t);\n    float n2 = noise(uv * 2. - t);\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_number = ceil(clamp(u_swirlIterations, 1., 30.));\n    for (float i = 1.; i <= iterations_number; i++) {\n        uv.x += clamp(u_swirl, 0., 2.) / i * cos(t + i * 1.5 * uv.y);\n        uv.y += clamp(u_swirl, 0., 2.) / i * cos(t + i * 1. * uv.x);\n    }\n\n    float proportion = clamp(u_proportion, 0., 1.);\n\n    float shape = 0.;\n    float mixer = 0.;\n    if (u_shape < .5) {\n      vec2 checks_shape_uv = uv * (.5 + 3.5 * u_shapeScale);\n      shape = .5 + .5 * sin(checks_shape_uv.x) * cos(checks_shape_uv.y);\n      mixer = shape + .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5);\n    } else if (u_shape < 1.5) {\n      vec2 stripes_shape_uv = uv * (.25 + 3. * u_shapeScale);\n      float f = fract(stripes_shape_uv.y);\n      shape = smoothstep(.0, .55, f) * smoothstep(1., .45, f);\n      mixer = shape + .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5);\n    } else {\n      float sh = 1. - uv.y;\n      sh -= .5;\n      sh /= (noise_scale * u_resolution.y);\n      sh += .5;\n      float shape_scaling = .2 * (1. - u_shapeScale);\n      shape = smoothstep(.45 - shape_scaling, .55 + shape_scaling, sh + .3 * (proportion - .5));\n      mixer = shape;\n    }\n\n    vec4 color_mix = blend_colors(u_color1, u_color2, u_color3, mixer, 1. - clamp(u_softness, 0., 1.), .01 + .01 * u_scale);\n\n    fragColor = vec4(color_mix.rgb, color_mix.a);\n}\n`;\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}