aurora.js
1 document.documentElement.removeAttribute("style"); 2 3 const open_width = window.innerWidth; 4 const CURRENTS_NUM = 5 open_width < 800 ? 3 : 6 open_width < 1368 ? 7 : 7 open_width < 2000 ? 9 : 8 11; 9 10 const canvas = document.getElementById("aurora"); 11 12 // AI PORT TO WebGL 13 14 // ── Try WebGL first ─────────────────────────────────────────────────────────── 15 16 const gl = (() => { 17 try { 18 return canvas.getContext("webgl") || canvas.getContext("experimental-webgl") || null; 19 } catch (e) { 20 return null; 21 } 22 })(); 23 24 // ═════════════════════════════════════════════════════════════════════════════ 25 // WebGL path 26 // ═════════════════════════════════════════════════════════════════════════════ 27 28 if (gl) { 29 30 const VS = ` 31 attribute vec2 a_pos; 32 void main() { 33 gl_Position = vec4(a_pos, 0.0, 1.0); 34 } 35 `; 36 37 const FS = ` 38 precision highp float; 39 40 uniform vec2 u_resolution; 41 uniform float u_time; 42 43 uniform vec2 u_basePos[${CURRENTS_NUM}]; 44 uniform float u_size[${CURRENTS_NUM}]; 45 uniform float u_speed[${CURRENTS_NUM}]; 46 uniform float u_offset[${CURRENTS_NUM}]; 47 48 // Gradient colour stops (matching Canvas 2D exactly) 49 // stop0: rgba(90,200,255, 0.15) at t=0.0 50 // stop1: rgba(0,120,255, 0.07) at t=0.4 51 // stop2: rgba(0,0,0, 0.0) at t=1.0 52 const vec4 STOP0 = vec4(90.0/255.0, 200.0/255.0, 255.0/255.0, 0.15); 53 const vec4 STOP1 = vec4(0.0, 120.0/255.0, 255.0/255.0, 0.07); 54 const vec4 STOP2 = vec4(0.0, 0.0, 0.0, 0.0); 55 56 vec4 gradientColor(float t) { 57 if (t <= 0.4) { 58 return mix(STOP0, STOP1, t / 0.4); 59 } else { 60 return mix(STOP1, STOP2, (t - 0.4) / 0.6); 61 } 62 } 63 64 void main() { 65 // Flip Y: WebGL origin is bottom-left, Canvas 2D is top-left 66 vec2 fragCoord = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y); 67 68 // Background #0b131b 69 vec3 colour = vec3(11.0/255.0, 19.0/255.0, 27.0/255.0); 70 71 // Accumulate each current with Porter-Duff source-over 72 for (int i = 0; i < ${CURRENTS_NUM}; i++) { 73 float t = u_time * u_speed[i] + u_offset[i]; 74 float cx = u_basePos[i].x + sin(t) * 150.0; 75 float cy = u_basePos[i].y + cos(t * 0.7) * 80.0; 76 77 float dist = distance(fragCoord, vec2(cx, cy)); 78 float radius = u_size[i]; 79 80 if (dist < radius) { 81 vec4 c = gradientColor(dist / radius); 82 colour = c.rgb * c.a + colour * (1.0 - c.a); 83 } 84 } 85 86 gl_FragColor = vec4(colour, 1.0); 87 } 88 `; 89 90 // ── Compile helpers ───────────────────────────────────────────────────────── 91 92 function compile(type, src) { 93 const s = gl.createShader(type); 94 gl.shaderSource(s, src); 95 gl.compileShader(s); 96 if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) 97 throw new Error(gl.getShaderInfoLog(s)); 98 return s; 99 } 100 101 const prog = gl.createProgram(); 102 gl.attachShader(prog, compile(gl.VERTEX_SHADER, VS)); 103 gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FS)); 104 gl.linkProgram(prog); 105 if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) 106 throw new Error(gl.getProgramInfoLog(prog)); 107 gl.useProgram(prog); 108 109 // ── Fullscreen quad ───────────────────────────────────────────────────────── 110 111 const buf = gl.createBuffer(); 112 gl.bindBuffer(gl.ARRAY_BUFFER, buf); 113 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 114 -1, -1, 1, -1, -1, 1, 115 -1, 1, 1, -1, 1, 1, 116 ]), gl.STATIC_DRAW); 117 const aPos = gl.getAttribLocation(prog, "a_pos"); 118 gl.enableVertexAttribArray(aPos); 119 gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0); 120 121 // ── Uniform locations ─────────────────────────────────────────────────────── 122 123 const uRes = gl.getUniformLocation(prog, "u_resolution"); 124 const uTime = gl.getUniformLocation(prog, "u_time"); 125 const uBase = Array.from({length: CURRENTS_NUM}, (_, i) => gl.getUniformLocation(prog, `u_basePos[${i}]`)); 126 const uSize = Array.from({length: CURRENTS_NUM}, (_, i) => gl.getUniformLocation(prog, `u_size[${i}]`)); 127 const uSpeed = Array.from({length: CURRENTS_NUM}, (_, i) => gl.getUniformLocation(prog, `u_speed[${i}]`)); 128 const uOff = Array.from({length: CURRENTS_NUM}, (_, i) => gl.getUniformLocation(prog, `u_offset[${i}]`)); 129 130 // ── Currents ──────────────────────────────────────────────────────────────── 131 132 let currents = null; 133 function createCurrents(w, h) { 134 return Array.from({length: CURRENTS_NUM}, () => ({ 135 baseX: Math.random() * w, 136 baseY: Math.random() * h, 137 size: 300 + Math.random() * 300, 138 speed: 0.0004 + Math.random() * 0.0006, 139 offset: Math.random() * 1000, 140 })); 141 } 142 143 // ── Resize ────────────────────────────────────────────────────────────────── 144 145 let w, h; 146 function resize() { 147 w = canvas.width = window.innerWidth; 148 h = canvas.height = window.innerHeight; 149 gl.viewport(0, 0, w, h); 150 if (!currents) currents = createCurrents(w, h); 151 } 152 window.addEventListener("resize", resize); 153 resize(); 154 155 // ── Render loop ───────────────────────────────────────────────────────────── 156 157 function animate(time) { 158 gl.uniform2f(uRes, w, h); 159 gl.uniform1f(uTime, time); 160 for (let i = 0; i < CURRENTS_NUM; i++) { 161 const c = currents[i]; 162 gl.uniform2f(uBase[i], c.baseX, c.baseY); 163 gl.uniform1f(uSize[i], c.size); 164 gl.uniform1f(uSpeed[i], c.speed); 165 gl.uniform1f(uOff[i], c.offset); 166 } 167 gl.drawArrays(gl.TRIANGLES, 0, 6); 168 requestAnimationFrame(animate); 169 } 170 171 requestAnimationFrame(animate); 172 173 // ═════════════════════════════════════════════════════════════════════════════ 174 // Canvas 2D fallback (original implementation, verbatim) 175 // ═════════════════════════════════════════════════════════════════════════════ 176 177 } else { 178 179 const ctx = canvas.getContext("2d"); 180 let w, h; 181 182 function resize() { 183 w = canvas.width = window.innerWidth; 184 h = canvas.height = window.innerHeight; 185 } 186 window.addEventListener("resize", resize); 187 resize(); 188 189 class Current { 190 constructor() { 191 this.baseX = Math.random() * w; 192 this.baseY = Math.random() * h; 193 this.size = 300 + Math.random() * 300; 194 this.speed = 0.0004 + Math.random() * 0.0006; 195 this.offset = Math.random() * 1000; 196 } 197 draw(time) { 198 const t = (time || 0) * this.speed + this.offset; 199 const x = this.baseX + Math.sin(t) * 150; 200 const y = this.baseY + Math.cos(t * 0.7) * 80; 201 const grad = ctx.createRadialGradient(x, y, 0, x, y, this.size); 202 grad.addColorStop(0, "rgba(90,200,255,0.15)"); 203 grad.addColorStop(0.4, "rgba(0,120,255,0.07)"); 204 grad.addColorStop(1, "rgba(0,0,0,0)"); 205 ctx.fillStyle = grad; 206 ctx.beginPath(); 207 ctx.arc(x, y, this.size, 0, Math.PI * 2); 208 ctx.fill(); 209 } 210 } 211 212 const currents = []; 213 for (let i = 0; i < CURRENTS_NUM; i++) currents.push(new Current()); 214 215 function animate(time = 0) { 216 ctx.fillStyle = "#0b131b"; 217 ctx.fillRect(0, 0, w, h); 218 currents.forEach(c => c.draw(time)); 219 requestAnimationFrame(animate); 220 } 221 222 requestAnimationFrame(animate); 223 224 }