// WebGL background effect for Nixtaml website // Particle system with subtle animations fitting "crash-over-burn" theme (function() { // Check for reduced motion preference const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; // Check if mobile (simplify or disable for performance) const isMobile = window.innerWidth < 768; // If reduced motion or mobile, skip WebGL if (prefersReducedMotion || isMobile) { console.log('WebGL background disabled: reduced motion or mobile'); return; } // Check WebGL support const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (!gl) { console.log('WebGL not supported, using fallback'); return; } // Append canvas to body canvas.id = 'webgl-bg'; document.body.insertBefore(canvas, document.body.firstChild); // Set canvas size function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; gl.viewport(0, 0, canvas.width, canvas.height); } resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Load shader function loadShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Shader compile error:', gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } // Create program const vertexShaderSource = ` attribute vec2 a_position; attribute float a_size; attribute vec3 a_color; uniform mat4 u_matrix; uniform float u_time; varying vec3 v_color; void main() { vec4 position = vec4(a_position, 0.0, 1.0); position.x += sin(u_time * 0.001 + a_position.y * 0.01) * 0.05; position.y += cos(u_time * 0.0005 + a_position.x * 0.01) * 0.02; gl_Position = u_matrix * position; gl_PointSize = a_size; v_color = a_color; } `; const fragmentShaderSource = ` precision mediump float; varying vec3 v_color; void main() { vec2 center = gl_PointCoord - vec2(0.5); float dist = length(center); if (dist > 0.5) { discard; } float alpha = 1.0 - smoothstep(0.0, 0.5, dist); gl_FragColor = vec4(v_color, alpha * 0.2); } `; const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource); const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Program link error:', gl.getProgramInfoLog(program)); return; } gl.useProgram(program); // Get attribute locations const positionLocation = gl.getAttribLocation(program, 'a_position'); const sizeLocation = gl.getAttribLocation(program, 'a_size'); const colorLocation = gl.getAttribLocation(program, 'a_color'); // Get uniform locations const matrixLocation = gl.getUniformLocation(program, 'u_matrix'); const timeLocation = gl.getUniformLocation(program, 'u_time'); // Create particles const numParticles = 200; const positions = new Float32Array(numParticles * 2); const sizes = new Float32Array(numParticles); const colors = new Float32Array(numParticles * 3); for (let i = 0; i < numParticles; i++) { positions[i * 2] = (Math.random() - 0.5) * 4; // Spread across screen positions[i * 2 + 1] = (Math.random() - 0.5) * 4; sizes[i] = Math.random() * 5 + 2; // Size 2-7 // Orange/red colors colors[i * 3] = Math.random() * 0.5 + 0.5; // R colors[i * 3 + 1] = Math.random() * 0.3 + 0.2; // G colors[i * 3 + 2] = Math.random() * 0.1; // B } // Create buffers const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); const sizeBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer); gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW); const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); // Enable blending gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // Render loop let startTime = Date.now(); function render() { const time = Date.now() - startTime; // Clear gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); // Set uniforms const matrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); gl.uniformMatrix4fv(matrixLocation, false, matrix); gl.uniform1f(timeLocation, time); // Bind position buffer gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // Bind size buffer gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer); gl.enableVertexAttribArray(sizeLocation); gl.vertexAttribPointer(sizeLocation, 1, gl.FLOAT, false, 0, 0); // Bind color buffer gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.enableVertexAttribArray(colorLocation); gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false, 0, 0); // Draw gl.drawArrays(gl.POINTS, 0, numParticles); requestAnimationFrame(render); } render(); })();