// components/ShaderBackground.tsx /** biome-ignore-all lint/style/noNonNullAssertion: why */ "use client"; import { useEffect, useRef, useState } from "react"; export default function ShaderBackground() { const canvasRef = useRef(null); const [loaded, setLoaded] = useState(false); useEffect(() => { if (!canvasRef.current) return; const canvas = canvasRef.current; const gl = canvas.getContext("webgl"); if (!gl) return; const vertexShaderSrc = ` attribute vec2 aPosition; void main() { gl_Position = vec4(aPosition, 0.0, 1.0); } `; const fragmentShaderSrc = ` precision mediump float; uniform vec2 iResolution; uniform float iTime; float distLine(vec2 p, vec2 a, vec2 b) { vec2 ap = p - a; vec2 ab = b - a; float aDotB = clamp(dot(ap, ab) / dot(ab, ab), 0.0, 1.0); return length(ap - ab * aDotB); } float drawLine(vec2 uv, vec2 a, vec2 b) { float line = smoothstep(0.014, 0.01, distLine(uv, a, b)); float dist = length(b-a); return line * (smoothstep(1.3, 0.8, dist) * 0.5 + smoothstep(0.04, 0.03, abs(dist - 0.75))); } float n21(vec2 i) { i += fract(i * vec2(223.64, 823.12)); i += dot(i, i + 23.14); return fract(i.x * i.y); } vec2 n22(vec2 i) { float x = n21(i); return vec2(x, n21(i+x)); } vec2 getPoint (vec2 id, vec2 offset) { return offset + sin(n22(id + offset) * iTime * 1.0) * 0.4; } float layer (vec2 uv) { float m = 0.0; float t = iTime * 2.0; vec2 gv = fract(uv) - 0.5; vec2 id = floor(uv) - 0.5; vec2 p[9]; // Hardcoded population to ensure compatibility with all WebGL 1.0 implementations p[0] = getPoint(id, vec2(-1.0, -1.0)); p[1] = getPoint(id, vec2(0.0, -1.0)); p[2] = getPoint(id, vec2(1.0, -1.0)); p[3] = getPoint(id, vec2(-1.0, 0.0)); p[4] = getPoint(id, vec2(0.0, 0.0)); p[5] = getPoint(id, vec2(1.0, 0.0)); p[6] = getPoint(id, vec2(-1.0, 1.0)); p[7] = getPoint(id, vec2(0.0, 1.0)); p[8] = getPoint(id, vec2(1.0, 1.0)); for (int i = 0; i < 9; i++) { m += drawLine(gv, p[4], p[i]); float sparkle = 1.0 / pow(length(gv - p[i]), 1.5) * 0.005; m += sparkle * (sin(t + fract(p[i].x) * 12.23) * 0.4 + 0.6); } m += drawLine(gv, p[1], p[3]); m += drawLine(gv, p[1], p[5]); m += drawLine(gv, p[7], p[3]); m += drawLine(gv, p[7], p[5]); return m; } void main() { vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y; vec3 c = sin(iTime * 2.0 * vec3(.234, .324, .768)) * 0.4 + 0.6; vec3 col = vec3(0.0); // FFT mocked as a constant since we don't have audio input float fft = 0.7; c.x += (uv.x + 0.5); col += pow(-uv.y + 0.5, 5.0) * fft * c; float m = 0.0; float x = sin(iTime * 0.1); float y = cos(iTime * 0.2); mat2 rotMat = mat2(x, y, -y, x); uv *= rotMat; for (float i = 0.0; i <= 1.0; i += 0.25) { float z = fract(i + iTime * 0.05); float size = mix(15.0, 0.1, z) * 1.50; float fade = smoothstep(0.0, 1.0, z) * smoothstep(1.0, 0.9, z); m += layer((size * uv) + i * 10.0) * fade; } col += m * c; gl_FragColor = vec4(col, 1.0); } `; function compileShader(type: number, source: string) { // for gods sake typescript if (!gl) throw "GL not found"; const shader = gl.createShader(type)!; gl!.shaderSource(shader, source); gl!.compileShader(shader); if (!gl!.getShaderParameter(shader, gl!.COMPILE_STATUS)) { console.error(gl!.getShaderInfoLog(shader)); } return shader; } const vs = compileShader(gl.VERTEX_SHADER, vertexShaderSrc); const fs = compileShader(gl.FRAGMENT_SHADER, fragmentShaderSrc); const program = gl.createProgram()!; gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); // biome-ignore lint/correctness/useHookAtTopLevel: false, not a hook gl.useProgram(program); const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW, ); const positionLoc = gl.getAttribLocation(program, "aPosition"); gl.enableVertexAttribArray(positionLoc); gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); const iResolution = gl.getUniformLocation(program, "iResolution"); const iTime = gl.getUniformLocation(program, "iTime"); function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; gl!.viewport(0, 0, canvas.width, canvas.height); } window.addEventListener("resize", resizeCanvas); resizeCanvas(); const start = performance.now(); let animationFrameId: number; function render() { const time = (performance.now() - start) * 0.001; gl!.uniform2f(iResolution, canvas.width, canvas.height); gl!.uniform1f(iTime, time); gl!.drawArrays(gl!.TRIANGLES, 0, 6); animationFrameId = requestAnimationFrame(render); } render(); setLoaded(true); return () => { window.removeEventListener("resize", resizeCanvas); if (animationFrameId) cancelAnimationFrame(animationFrameId); }; }, []); return ( ); }