{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "animated-gradient", "title": "Animated Gradient", "description": "WebGL-powered animated mist, lava, and plasma backgrounds with smooth silk-like flowing streams.", "dependencies": [], "registryDependencies": [], "files": [ { "path": "registry/spark-ui/animated-gradient.tsx", "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useEffect, useRef } from \"react\";\n\ninterface AnimatedGradientProps {\n className?: string;\n variant?: \"mist\" | \"lava\" | \"vortex\";\n speed?: number;\n opacity?: number;\n children?: React.ReactNode;\n}\n\n\nconst VERTEX_SHADER = `\n attribute vec2 a_position;\n varying vec2 v_uv;\n \n void main() {\n v_uv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n }\n`;\n\nconst MIST_SHADER = `\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n varying vec2 v_uv;\n uniform float u_time;\n uniform vec2 u_resolution;\n\n #define TWO_PI 6.28318530718\n #define PI 3.14159265358979323846\n\n float random(vec2 st) {\n return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);\n }\n\n float 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\n void main() {\n vec2 uv = v_uv;\n\n // Mist preset timing parameters: speed = 39, offset = -235\n float t = u_time * 0.975 - 1.175;\n\n // Mist preset scale parameter: scale = 0.48\n float noise_scale = 0.00338; // .0005 + .006 * 0.48\n\n uv -= 0.5;\n uv *= (noise_scale * u_resolution);\n uv /= 1.5; // pixel ratio normalization\n uv += 0.5;\n\n // 70% Same: Distortion & Swirl loops preserved exactly for perfect motion fidelity\n // Distortion: distortion = 4 (u_distortion = 0.08)\n float n1 = noise(uv * 1.0 + t);\n float n2 = noise(uv * 2.0 - t);\n float angle = n1 * TWO_PI;\n uv.x += 0.32 * n2 * cos(angle);\n uv.y += 0.32 * n2 * sin(angle);\n\n // Swirl: swirl = 65, swirlIterations = 5\n for (int i = 1; i <= 5; i++) {\n float fi = float(i);\n uv.x += 0.65 / fi * cos(t + fi * 1.5 * uv.y);\n uv.y += 0.65 / fi * cos(t + fi * 1.0 * uv.x);\n }\n\n // Edge shape shape-mixer calculation (shapeSize = 48, proportion = 33)\n float sh = 1.0 - uv.y;\n sh -= 0.5;\n sh /= (noise_scale * u_resolution.y);\n sh += 0.5;\n\n float shape_scaling = 0.104; // 0.2 * (1.0 - 0.48)\n float shape = smoothstep(0.45 - shape_scaling, 0.55 + shape_scaling, sh + 0.3 * (0.33 - 0.5));\n float mixer = shape;\n\n // 30% Premium Upgrade: Highly classy Parallax Fog & Cinematic Sweeping God Rays with Focused Ribbon\n vec3 bg = vec3(0.0196, 0.0196, 0.0196); // Obsidian background (#050505)\n vec3 pink = vec3(1.0, 0.4, 0.7215); // Pure elegant magenta core (#FF66B8)\n\n // Exponent-sine bell curve to isolate mist into a beautiful center ribbon splaying dynamically\n float mistFocus = pow(sin(mixer * PI), 4.2);\n\n // Double-layer parallax mist density (blending broad background flow and fine parallax foreground)\n float fineMist = noise(uv * 3.5 - t * 1.3) * 0.4 + noise(uv * 1.5 + t * 0.85) * 0.6;\n float combinedDensity = mix(mixer, fineMist, 0.28) * mistFocus;\n\n // Smooth constant-color gradient mapping\n vec3 col = mix(bg, pink, smoothstep(0.0, 0.85, combinedDensity));\n\n // Foreground parallax highlight glow, strictly bounded by the mist focus ribbon\n float highlight = smoothstep(0.42, 0.88, fineMist) * smoothstep(0.12, 0.9, mixer) * mistFocus;\n col = mix(col, pink * 1.15, highlight * 0.35);\n\n // Sweeping Crepuscular God Rays (light shafts filtering through the moving fog ribbon)\n vec2 raySource = vec2(0.2, 1.25); // light source positioned above the upper-left viewport\n vec2 rayDir = normalize(v_uv - raySource);\n float rayAngle = atan(rayDir.y, rayDir.x);\n \n // Sweep modulation using multiple low-frequency time-shifted sines\n float rays = sin(rayAngle * 6.5 + t * 0.35) * 0.35 +\n sin(rayAngle * 12.0 - t * 0.22) * 0.25 +\n sin(rayAngle * 24.0 + t * 0.15) * 0.15;\n rays = smoothstep(0.15, 0.82, rays * 0.5 + 0.5);\n\n // Volumetric crepuscular light soft overlay, masked by fog density and center focus ribbon\n float rayGlow = rays * smoothstep(0.15, 0.9, combinedDensity) * (1.1 - v_uv.y) * mistFocus * 0.7;\n col += pink * rayGlow * 0.38;\n\n // Cinematic Haze: micro-fine glowing particulate dust in illuminated center areas\n float grain = random(gl_FragCoord.xy * 0.15 + t * 0.05);\n float particles = step(0.988, grain) * smoothstep(0.2, 0.9, combinedDensity);\n col += pink * particles * 0.32;\n\n // Final contrast optimization\n col = pow(col, vec3(0.92));\n\n gl_FragColor = vec4(col, 1.0);\n }\n`;\n\nconst LAVA_SHADER = `\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n varying vec2 v_uv;\n uniform float u_time;\n uniform vec2 u_resolution;\n\n #define PI 3.14159265359\n\n float hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n }\n\n float noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),\n mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);\n }\n\n // Limited to 2 octaves to eliminate jagged details and ensure broad, glassy waves\n float fbm(vec2 p) {\n float v = 0.0;\n float a = 0.5;\n vec2 shift = vec2(100.0);\n mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for (int i = 0; i < 2; ++i) {\n v += a * noise(p);\n p = rot * p * 2.0 + shift;\n a *= 0.5;\n }\n return v;\n }\n\n void main() {\n vec2 uv = v_uv;\n float aspect = u_resolution.x / u_resolution.y;\n uv.x *= aspect;\n \n // Smooth upward animation speed\n float t = u_time * 0.22;\n \n // Slow gentle sideways sway of the flame body\n float sway = sin(uv.x * 2.5 + u_time * 0.45) * 0.07;\n \n // Domain warping noise layers (wider waves for blurry glass look)\n vec2 q = vec2(0.0);\n q.x = fbm(uv * 2.0 + vec2(0.0, -t));\n q.y = fbm(uv * 2.0 + vec2(t * 0.35, -t * 0.85));\n \n vec2 r = vec2(0.0);\n r.x = fbm(uv * 2.2 + 2.8 * q + vec2(1.7, -t * 1.5));\n r.y = fbm(uv * 2.2 + 2.8 * q + vec2(8.3, -t * 1.25));\n \n // Soft low-frequency noise (completely smoothed out)\n float f = fbm(uv * 1.05 + 1.65 * r);\n \n // Vertical flame base offset with swaying peaks (lowered base multiplier from 1.35 to 1.15)\n float fire = (1.0 - (uv.y + sway)) * 1.15; \n \n // Volumetric flame thresholds with extremely wide smoothstep windows for a blurry look\n // Scale noise by (1.1 - uv.y) to suppress height and create gorgeous dark spots/channels at the top\n float noiseGlow = f * 1.65 * (1.1 - uv.y);\n float flameIntensity = fire + noiseGlow - 0.78;\n \n float flame = smoothstep(-0.25, 0.95, flameIntensity);\n float orangeGlow = smoothstep(0.12, 0.98, flameIntensity);\n float goldCore = smoothstep(0.38, 1.0, fire + f * 0.75 * (1.1 - uv.y) - 0.42);\n \n // Soft glowing volumetric smoke tips\n float smoke = smoothstep(-0.3, 0.45, flameIntensity) * (1.0 - smoothstep(0.45, 0.95, flameIntensity));\n \n // Warm fire color definitions (no harsh bright yellows or whites!)\n vec3 black = vec3(0.0, 0.0, 0.0);\n vec3 deepRed = vec3(0.55, 0.015, 0.0);\n vec3 brightOrange = vec3(0.92, 0.25, 0.0);\n vec3 goldenOrange = vec3(0.96, 0.42, 0.02);\n \n // Blending volumetric color layers for a blurry glass bonfire effect\n vec3 col = mix(black, deepRed, flame);\n col = mix(col, brightOrange, orangeGlow);\n col = mix(col, goldenOrange, goldCore);\n \n // Volumetric smoke tip glow for glowing depth\n col += deepRed * smoke * 0.35;\n \n // Edge hot glow edge highlighting (wider transition for blurry glow)\n float edge = smoothstep(0.25, 0.55, f) * (1.0 - smoothstep(0.55, 0.85, f));\n col += edge * brightOrange * 0.18 * (1.0 - uv.y);\n \n // Contrast boost\n col = pow(col, vec3(0.85));\n \n gl_FragColor = vec4(col, 1.0);\n }\n`;\n\nconst VORTEX_SHADER = `\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n varying vec2 v_uv;\n uniform float u_time;\n uniform vec2 u_resolution;\n uniform float u_dark;\n\n #define PI 3.14159265358979323846\n\n float hash(vec2 p) {\n return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n }\n\n float noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),\n mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);\n }\n\n // Limited to 2 octaves to eliminate jagged details and ensure broad, unbroken, glassy silk waves\n float fbm(vec2 p) {\n float v = 0.0;\n float a = 0.5;\n vec2 shift = vec2(100.0);\n mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n for (int i = 0; i < 2; ++i) {\n v += a * noise(p);\n p = rot * p * 2.0 + shift;\n a *= 0.5;\n }\n return v;\n }\n\n void main() {\n vec2 uv = v_uv;\n float aspect = u_resolution.x / u_resolution.y;\n \n // Smooth fluid motion speed\n float t = u_time * 0.55;\n \n // Center of the vortex\n vec2 center = vec2(0.5);\n vec2 st = uv - center;\n st.x *= aspect;\n \n float dist = length(st);\n float angle = atan(st.y, st.x);\n \n // Natural fluid vortex swirl physics: decays smoothly away from center, damped at the absolute core to prevent breaking\n float swirl = 3.2 / (dist + 0.3);\n angle += swirl * 0.6 * smoothstep(0.04, 0.25, dist) + t * 0.3;\n \n // Smooth fluid radial ripples: damped near the center to prevent high-frequency coordinate tearing\n float rippleMask = smoothstep(0.08, 0.35, dist);\n dist += sin(angle * 2.0 - t * 1.2) * 0.015 * rippleMask * (1.0 - smoothstep(0.0, 0.9, dist));\n \n // Reconstruct twisted coordinates\n vec2 twisted = vec2(cos(angle), sin(angle)) * dist;\n twisted.x /= aspect;\n twisted += center;\n \n // Scale coordinates for large, beautiful topography waves\n vec2 flowCoord = twisted * 1.5;\n \n // Domain warping for natural marble fluid veins\n vec2 q = vec2(\n fbm(flowCoord - t * 0.04),\n fbm(flowCoord + vec2(5.2, 1.3) + t * 0.02)\n );\n \n vec2 r = flowCoord + q * 0.35;\n float f = fbm(r);\n \n // Generate continuous, unbroken contour lines with beautiful dense spacing to avoid empty areas\n float contour = sin(f * 18.0 - t * 1.2);\n \n // Pure monochrome black & white color scheme (no greys, vignettes, or halos)\n // u_dark = 1.0 -> White lines on Solid Black background\n // u_dark = 0.0 -> Black lines on Solid White background\n vec3 bgColor = mix(vec3(1.0), vec3(0.0), u_dark);\n vec3 lineColor = mix(vec3(0.0), vec3(1.0), u_dark);\n \n // Draw highly anti-aliased thin elegant lines (making them significantly thinner and sharper)\n float line = smoothstep(0.965, 0.985, abs(contour));\n \n // Blend strictly between background and line color\n vec3 col = mix(bgColor, lineColor, line);\n \n gl_FragColor = vec4(col, 1.0);\n }\n`;\n\nconst FRAGMENT_SHADERS = {\n mist: MIST_SHADER,\n lava: LAVA_SHADER,\n vortex: VORTEX_SHADER,\n} as const;\n\nexport function AnimatedGradient({\n className,\n variant = \"mist\",\n speed = 1,\n opacity = 1,\n children,\n}: AnimatedGradientProps) {\n const canvasRef = useRef(null);\n const animationRef = useRef(0);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const gl = canvas.getContext(\"webgl\", {\n antialias: false,\n alpha: false,\n preserveDrawingBuffer: false,\n });\n if (!gl) return;\n\n const resize = () => {\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n canvas.width = canvas.clientWidth * dpr;\n canvas.height = canvas.clientHeight * dpr;\n gl.viewport(0, 0, canvas.width, canvas.height);\n };\n\n resize();\n window.addEventListener(\"resize\", resize);\n\n const createShader = (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 console.error(gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n return null;\n }\n return shader;\n };\n\n const vertexShader = createShader(gl.VERTEX_SHADER, VERTEX_SHADER);\n const fragmentShaderSource =\n FRAGMENT_SHADERS[variant] || FRAGMENT_SHADERS.mist;\n const fragmentShader = createShader(\n gl.FRAGMENT_SHADER,\n fragmentShaderSource,\n );\n\n if (!vertexShader || !fragmentShader) {\n if (vertexShader) gl.deleteShader(vertexShader);\n if (fragmentShader) gl.deleteShader(fragmentShader);\n return;\n }\n\n const program = gl.createProgram();\n if (!program) {\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error(gl.getProgramInfoLog(program));\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n gl.deleteProgram(program);\n return;\n }\n\n gl.useProgram(program);\n\n const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);\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 timeLocation = gl.getUniformLocation(program, \"u_time\");\n const resolutionLocation = gl.getUniformLocation(program, \"u_resolution\");\n const darkLocation = gl.getUniformLocation(program, \"u_dark\");\n\n const startTime = Date.now();\n\n const render = () => {\n const elapsed = (Date.now() - startTime) / 1000;\n gl.uniform1f(timeLocation, elapsed * speed);\n gl.uniform2f(resolutionLocation, canvas.width, canvas.height);\n\n if (darkLocation) {\n const isDark =\n document.documentElement.classList.contains(\"dark\") ||\n document.documentElement.getAttribute(\"data-theme\") === \"dark\" ||\n (!document.documentElement.classList.contains(\"light\") &&\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches);\n gl.uniform1f(darkLocation, isDark ? 1.0 : 0.0);\n }\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n animationRef.current = requestAnimationFrame(render);\n };\n\n render();\n\n return () => {\n window.removeEventListener(\"resize\", resize);\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current);\n }\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n gl.deleteProgram(program);\n gl.deleteBuffer(buffer);\n };\n }, [variant, speed]);\n\n return (\n
\n \n {children &&
{children}
}\n
\n );\n}\n", "type": "registry:component" } ], "type": "registry:component" }