// bg.jsx — the page backdrop. One WebGL canvas that shows either a flat
// "office wall" colour (professional side, perfectly still) or live northern
// lights (childhood side). `mix` crossfades between the two.

const BG_VERT = `attribute vec2 p; void main(){ gl_Position = vec4(p, 0., 1.); }`;
const BG_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform float uMix;
uniform vec3 uC1; uniform vec3 uC2; uniform vec3 uC3; uniform vec3 uSolid;
float hash(vec2 p){ p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); }
float noise(vec2 p){
  vec2 i = floor(p), f = fract(p); f = f * f * (3. - 2. * f);
  return mix(mix(hash(i), hash(i + vec2(1., 0.)), f.x), mix(hash(i + vec2(0., 1.)), hash(i + vec2(1., 1.)), f.x), f.y);
}
float fbm(vec2 p){ float v = 0., a = .5; for (int i = 0; i < 4; i++){ v += a * noise(p); p = p * 2.03 + vec2(17.1, 9.2); a *= .5; } return v; }
vec3 aurora(vec2 uv, float asp){
  float t = uTime;
  vec3 col = mix(vec3(.010, .018, .045), vec3(.022, .014, .060), uv.y);
  // a few slow-twinkling stars high in the sky
  vec2 sp = uv * vec2(asp, 1.) * 80.; float sr = hash(floor(sp));
  float star = step(.988, sr) * smoothstep(.32, 0., length(fract(sp) - .5)) * (.55 + .45 * sin(t * 1.3 + sr * 40.));
  col += star * .7 * smoothstep(.25, .95, uv.y);
  // three curtains of light, each drifting and rippling at its own pace
  for (int i = 0; i < 3; i++){
    float fi = float(i);
    float x = uv.x * asp * (.9 + fi * .35) + fi * 5.7;
    float w = fbm(vec2(x * .7 + t * .028 * (1. + fi * .4), t * .045 + fi * 3.1));
    float base = .16 + fi * .15 + (w - .5) * .6;
    float d = uv.y - base;
    float rays = fbm(vec2(x * 7. + w * 4., t * .11 + fi * 2.));
    float fine = noise(vec2(x * 24. + w * 9., t * .28));
    float len = .26 + .32 * rays;
    float body = smoothstep(-.035, .015, d) * exp(-max(d, 0.) / len);
    float inten = body * (.22 + 1.15 * rays) * (.72 + .4 * fine);
    vec3 lo = i == 0 ? uC1 : (i == 1 ? uC2 : uC3);
    vec3 hi = i == 0 ? uC2 : (i == 1 ? uC3 : uC1);
    col += mix(lo, hi, smoothstep(0., .45, d)) * inten * (.6 - .09 * fi);
  }
  return 1. - exp(-col * 1.55);
}
void main(){
  vec2 uv = gl_FragCoord.xy / uRes;
  vec3 col = uSolid;
  if (uMix > .001) col = mix(uSolid, aurora(uv, uRes.x / uRes.y), uMix);
  col += (hash(gl_FragCoord.xy + fract(uTime)) - .5) / 255. * 1.5 * uMix;
  gl_FragColor = vec4(col, 1.);
}`;

function Backdrop({ solid, colors, showAurora }) {
  const canvasRef = React.useRef(null);
  const targetRef = React.useRef({ solid, colors, mix: showAurora ? 1 : 0 });
  const kickRef = React.useRef(null);

  // Keep the latest targets where the render loop can read them, and wake it.
  targetRef.current = { solid, colors, mix: showAurora ? 1 : 0 };
  React.useEffect(() => { kickRef.current && kickRef.current(); }, [solid, colors.join(), showAurora]);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const gl = canvas.getContext('webgl', { antialias: false, alpha: false, preserveDrawingBuffer: true });
    const toVec = (hex) => hexToRgb(hex).map(v => v / 255);
    if (!gl) {
      // No WebGL: fall back to a flat colour so the page is still usable.
      kickRef.current = () => {
        const t = targetRef.current;
        canvas.style.background = t.mix ? `linear-gradient(0deg, #060a18, ${t.colors[0]}55, ${t.colors[1]}44, #0a0820)` : t.solid;
      };
      kickRef.current();
      return;
    }
    const compile = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; };
    const prog = gl.createProgram();
    gl.attachShader(prog, compile(gl.VERTEX_SHADER, BG_VERT));
    gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, BG_FRAG));
    gl.linkProgram(prog); gl.useProgram(prog);
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
    const loc = gl.getAttribLocation(prog, 'p');
    gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
    const U = {}; ['uRes', 'uTime', 'uMix', 'uC1', 'uC2', 'uC3', 'uSolid'].forEach(n => U[n] = gl.getUniformLocation(prog, n));

    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const t0 = targetRef.current;
    const cur = { solid: toVec(t0.solid), c: t0.colors.map(toVec), mix: t0.mix };

    const resize = () => {
      // The lights are soft, so half resolution is plenty and keeps phones cool.
      const scale = 0.5 * Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.max(2, Math.round(window.innerWidth * scale));
      canvas.height = Math.max(2, Math.round(window.innerHeight * scale));
      gl.viewport(0, 0, canvas.width, canvas.height);
      kick();
    };

    let raf = null, last = 0, override = null;
    const start = performance.now();
    const frame = (now) => {
      raf = null;
      const dt = Math.min(0.5, (now - (last || now)) / 1000); last = now;
      const t = targetRef.current;
      const ease = reduced ? 1 : 1 - Math.exp(-dt * 3.2);
      const tv = { solid: toVec(t.solid), c: t.colors.map(toVec) };
      let moving = false;
      const approach = (a, b) => a.map((v, i) => { const d = b[i] - v; if (Math.abs(d) > 0.002) moving = true; return v + d * ease; });
      cur.solid = approach(cur.solid, tv.solid);
      cur.c = cur.c.map((c, i) => approach(c, tv.c[i]));
      const dm = t.mix - cur.mix; if (Math.abs(dm) > 0.002) moving = true;
      cur.mix += dm * (reduced ? 1 : 1 - Math.exp(-dt * 2.4));
      if (!moving) { cur.mix = t.mix; }

      gl.uniform2f(U.uRes, canvas.width, canvas.height);
      gl.uniform1f(U.uTime, reduced ? 12 : (now - start) / 1000 + 12);
      gl.uniform1f(U.uMix, override == null ? cur.mix : override);
      gl.uniform3fv(U.uC1, cur.c[0]); gl.uniform3fv(U.uC2, cur.c[1]); gl.uniform3fv(U.uC3, cur.c[2]);
      gl.uniform3fv(U.uSolid, cur.solid);
      gl.drawArrays(gl.TRIANGLES, 0, 3);

      // The office wall is still: only keep animating while lights are showing.
      if (override == null && (moving || (cur.mix > 0.001 && !reduced))) raf = requestAnimationFrame(frame);
    };
    // Video export drives the backdrop itself: draw one frame at a given mix
    // (0 = office wall, 1 = northern lights) right now, then hand back control.
    window.__backdrop = {
      canvas,
      renderAt: (mix) => { override = mix; if (raf != null) { cancelAnimationFrame(raf); raf = null; } frame(performance.now()); },
      release: () => { override = null; kick(); },
    };
    const kick = () => { if (raf == null) { last = 0; raf = requestAnimationFrame(frame); } };
    kickRef.current = kick;

    window.addEventListener('resize', resize);
    resize();
    return () => { window.removeEventListener('resize', resize); if (raf != null) cancelAnimationFrame(raf); kickRef.current = null; };
  }, []);

  return <canvas ref={canvasRef} className="backdrop" aria-hidden="true"/>;
}

Object.assign(window, { Backdrop });
