/ site / aurora.js
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  }