// Two interactive WebGL shader backgrounds.
// Each exposes a React component that mounts a <canvas>, sets up GL,
// and runs an rAF loop. Pointer move + click drive uniforms.

const VERT = `
attribute vec2 a_pos;
void main(){ gl_Position = vec4(a_pos, 0.0, 1.0); }
`;

// ---------- SHADER 1: blue sky with white clouds ----------
// Sky gradient base + domain-warped cloud field. Pointer parts/pulls the clouds,
// click brightens a sun-like halo near the cursor.
const FRAG_LIQUID = `
precision highp float;
uniform vec2  u_res;
uniform float u_time;
uniform vec2  u_mouse;   // 0..1
uniform float u_pulse;   // 0..1 decays after click
uniform float u_warp;    // tweak: how strongly cursor pulls clouds
uniform float u_blueMix; // tweak: sky saturation / cloud contrast
uniform vec3  u_cream;   // ~white cloud color
uniform vec3  u_blue;    // sky accent
uniform vec3  u_deep;    // deep underside

float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }
float noise(vec2 p){
  vec2 i=floor(p), f=fract(p);
  float a=hash(i), b=hash(i+vec2(1,0)), c=hash(i+vec2(0,1)), d=hash(i+vec2(1,1));
  vec2 u=f*f*(3.0-2.0*f);
  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
float fbm(vec2 p){
  float v=0.0, a=0.5;
  for(int i=0;i<6;i++){ v+=a*noise(p); p*=2.02; a*=0.5; }
  return v;
}

void main(){
  vec2 uv = gl_FragCoord.xy / u_res.xy;
  float aspect = u_res.x / u_res.y;
  vec2 p = uv; p.x *= aspect;
  vec2 m = u_mouse; m.x *= aspect;

  vec2 d = p - m;
  float r = length(d);
  float pull = exp(-r*2.0) * (0.4 + 0.6*u_pulse);

  // Sky gradient: deeper blue at top, paler near the horizon
  vec3 skyTop = mix(u_blue, u_deep, 0.20);
  vec3 skyMid = u_blue;
  vec3 skyLow = mix(u_blue, u_cream, mix(0.55, 0.25, u_blueMix));
  vec3 sky = mix(skyLow, skyMid, smoothstep(0.0, 0.55, uv.y));
  sky = mix(sky, skyTop, smoothstep(0.55, 1.0, uv.y));

  // Domain-warped cloud field — slow drift, gently bent toward cursor
  float t = u_time*0.04;
  vec2 q = vec2(
    fbm(p*1.1 + vec2(0.0, t)),
    fbm(p*1.1 + vec2(3.1, t+1.7))
  );
  vec2 s = vec2(
    fbm(p*1.5 + 3.0*q + vec2(1.7, 9.2) + t),
    fbm(p*1.5 + 3.0*q + vec2(8.3, 2.8) - t*1.2)
  );
  // cursor parts the clouds — they bend toward where you point
  s += normalize(-d + 1e-4) * pull * u_warp * 0.5;

  float cloud = fbm(p*1.0 + 2.6*s);

  // soft cloud mask + wispy edges
  float fluff = smoothstep(0.46, 0.78, cloud);
  float wisp  = smoothstep(0.34, 0.62, cloud) * 0.45;

  // cloud color: bright top, slight blue underside for depth
  vec3 cloudHi = u_cream;
  vec3 cloudLo = mix(u_cream, u_blue, 0.20);
  vec3 cloudCol = mix(cloudLo, cloudHi, smoothstep(0.5, 0.85, cloud));

  vec3 col = mix(sky, cloudCol, fluff);
  col = mix(col, cloudHi, wisp * 0.25);

  // warm sun halo near cursor — intensifies with click pulse
  float halo = exp(-r*2.8) * (0.25 + 0.9*u_pulse);
  col += vec3(1.0, 0.93, 0.78) * halo * 0.28;
  col = mix(col, u_cream, halo * 0.18);

  // subtle vignette
  vec2 cv = uv - 0.5;
  col *= 1.0 - dot(cv,cv)*0.22;

  gl_FragColor = vec4(col, 1.0);
}
`;

// ---------- SHADER 2: dot grid with ripples ----------
const FRAG_GRID = `
precision highp float;
uniform vec2  u_res;
uniform float u_time;
uniform vec2  u_mouse;
uniform vec4  u_ripples[6]; // xy=pos(0..1), z=startTime, w=strength
uniform float u_density;    // tweak: grid cells per shortest side
uniform float u_dotSize;    // tweak
uniform vec3  u_cream;
uniform vec3  u_blue;
uniform vec3  u_deep;

void main(){
  vec2 uv = gl_FragCoord.xy / u_res.xy;
  float aspect = u_res.x / u_res.y;
  vec2 p = uv; p.x *= aspect;
  vec2 m = u_mouse; m.x *= aspect;

  // grid coords
  float cells = u_density;
  vec2 g = p * cells;
  vec2 gi = floor(g);
  vec2 gf = fract(g) - 0.5;
  vec2 cellCenter = (gi + 0.5) / cells;
  vec2 cellPos = cellCenter; cellPos.x *= 1.0; // already aspect-scaled

  // base displacement: subtle breathing
  float breath = sin(u_time*0.6 + (gi.x+gi.y)*0.35)*0.05;

  // cursor influence: dots near cursor shift slightly toward it
  vec2 toMouse = m - cellPos;
  float md = length(toMouse);
  float follow = exp(-md*3.5) * 0.18;
  vec2 disp = normalize(toMouse + 1e-4) * follow;

  // ripples
  float rippleAmp = 0.0;
  vec2 rippleDisp = vec2(0.0);
  for(int i=0;i<6;i++){
    vec4 rp = u_ripples[i];
    if(rp.w <= 0.0) continue;
    vec2 rpos = rp.xy; rpos.x *= aspect;
    float age = u_time - rp.z;
    if(age < 0.0 || age > 3.5) continue;
    vec2 toR = cellPos - rpos;
    float dR = length(toR);
    float wave = sin(dR*18.0 - age*7.0);
    float env  = exp(-pow(dR - age*0.55, 2.0)*9.0) * exp(-age*0.7) * rp.w;
    rippleAmp += wave * env;
    rippleDisp += normalize(toR + 1e-4) * wave * env * 0.05;
  }

  // displaced dot center
  vec2 dotCenter = vec2(0.0) + disp*cells*0.6 + rippleDisp*cells*0.6;
  float r = length(gf - dotCenter);

  // dot radius scales with influence
  float baseR = u_dotSize;
  float boost = follow*1.5 + abs(rippleAmp)*0.6 + breath;
  float radius = baseR * (1.0 + boost*1.8);

  float dot = 1.0 - smoothstep(radius - 0.06, radius + 0.02, r);

  // background cream with faint horizontal banding
  vec3 bg = mix(u_cream*0.965, u_cream*1.01, 0.5 + 0.5*sin(uv.y*40.0)*0.05);

  // dot color: blue near cursor / ripple energy, otherwise deep ink
  float energy = clamp(follow*4.0 + abs(rippleAmp)*2.0, 0.0, 1.0);
  vec3 dotCol = mix(u_deep, u_blue, energy);

  vec3 col = mix(bg, dotCol, dot);

  // subtle ring from active ripples (additive)
  col += u_blue * abs(rippleAmp) * 0.08;

  // vignette
  vec2 cv = uv - 0.5;
  col *= 1.0 - dot*0.0 - dot*0.0 + 0.0; // no-op kept for clarity
  col *= 1.0 - dot2(cv);

  gl_FragColor = vec4(col, 1.0);
}

float dot2(vec2 v){ return dot(v,v)*0.35; }
`;
// Note: GLSL doesn't hoist; move dot2 above main. We'll inject a fixed version below.

const FRAG_GRID_FIXED = `
precision highp float;
uniform vec2  u_res;
uniform float u_time;
uniform vec2  u_mouse;
uniform vec4  u_ripples[6];
uniform float u_density;
uniform float u_dotSize;
uniform vec3  u_cream;
uniform vec3  u_blue;
uniform vec3  u_deep;

void main(){
  vec2 uv = gl_FragCoord.xy / u_res.xy;
  float aspect = u_res.x / u_res.y;
  vec2 p = uv; p.x *= aspect;
  vec2 m = u_mouse; m.x *= aspect;

  float cells = u_density;
  vec2 g = p * cells;
  vec2 gi = floor(g);
  vec2 gf = fract(g) - 0.5;
  vec2 cellCenter = (gi + 0.5) / cells;

  float breath = sin(u_time*0.6 + (gi.x+gi.y)*0.35)*0.05;

  vec2 toMouse = m - cellCenter;
  float md = length(toMouse);
  float follow = exp(-md*3.5) * 0.18;
  vec2 disp = normalize(toMouse + 1e-4) * follow;

  float rippleAmp = 0.0;
  vec2 rippleDisp = vec2(0.0);
  for(int i=0;i<6;i++){
    vec4 rp = u_ripples[i];
    if(rp.w <= 0.0) continue;
    vec2 rpos = rp.xy; rpos.x *= aspect;
    float age = u_time - rp.z;
    if(age < 0.0 || age > 3.5) continue;
    vec2 toR = cellCenter - rpos;
    float dR = length(toR);
    float wave = sin(dR*18.0 - age*7.0);
    float env  = exp(-pow(dR - age*0.55, 2.0)*9.0) * exp(-age*0.7) * rp.w;
    rippleAmp += wave * env;
    rippleDisp += normalize(toR + 1e-4) * wave * env * 0.05;
  }

  vec2 dotCenter = disp*cells*0.6 + rippleDisp*cells*0.6;
  float r = length(gf - dotCenter);

  float baseR = u_dotSize;
  float boost = follow*1.5 + abs(rippleAmp)*0.6 + breath;
  float radius = baseR * (1.0 + boost*1.8);

  float dotMask = 1.0 - smoothstep(radius - 0.06, radius + 0.02, r);

  vec3 bg = mix(u_cream*0.965, u_cream*1.01, 0.5 + 0.5*sin(uv.y*40.0)*0.05);

  float energy = clamp(follow*4.0 + abs(rippleAmp)*2.0, 0.0, 1.0);
  vec3 dotCol = mix(u_deep, u_blue, energy);

  vec3 col = mix(bg, dotCol, dotMask);
  col += u_blue * abs(rippleAmp) * 0.08;

  vec2 cv = uv - 0.5;
  col *= 1.0 - dot(cv,cv)*0.35;

  gl_FragColor = vec4(col, 1.0);
}
`;

// ---------- shared GL boilerplate ----------
function createGL(canvas, fragSrc){
  const gl = canvas.getContext('webgl', { antialias: true, premultipliedAlpha: false });
  if(!gl) return null;
  const compile = (type, src) => {
    const s = gl.createShader(type);
    gl.shaderSource(s, src); gl.compileShader(s);
    if(!gl.getShaderParameter(s, gl.COMPILE_STATUS)){
      console.error(gl.getShaderInfoLog(s), src);
      return null;
    }
    return s;
  };
  const vs = compile(gl.VERTEX_SHADER, VERT);
  const fs = compile(gl.FRAGMENT_SHADER, fragSrc);
  if(!vs || !fs) return null;
  const prog = gl.createProgram();
  gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog);
  if(!gl.getProgramParameter(prog, gl.LINK_STATUS)){
    console.error(gl.getProgramInfoLog(prog));
    return null;
  }
  gl.useProgram(prog);
  const buf = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
  const loc = gl.getAttribLocation(prog, 'a_pos');
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
  return { gl, prog };
}

// Parse "#rrggbb" → [r,g,b] floats 0..1
function hexToRGB(h){
  const v = h.replace('#','');
  return [
    parseInt(v.slice(0,2),16)/255,
    parseInt(v.slice(2,4),16)/255,
    parseInt(v.slice(4,6),16)/255,
  ];
}

// ---------- Component: LiquidShader ----------
function LiquidShader({ cream='#EFE5CC', blue='#2944BA', deep='#0F1538', warp=1.0, blueMix=0.55, paused=false, className, style }){
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef({ mouse:[0.5,0.5], target:[0.5,0.5], pulse:0, lastT:0 });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const setup = createGL(canvas, FRAG_LIQUID);
    if(!setup) return;
    const { gl, prog } = setup;
    const u = {
      res: gl.getUniformLocation(prog, 'u_res'),
      time: gl.getUniformLocation(prog, 'u_time'),
      mouse: gl.getUniformLocation(prog, 'u_mouse'),
      pulse: gl.getUniformLocation(prog, 'u_pulse'),
      warp: gl.getUniformLocation(prog, 'u_warp'),
      blueMix: gl.getUniformLocation(prog, 'u_blueMix'),
      cream: gl.getUniformLocation(prog, 'u_cream'),
      blue: gl.getUniformLocation(prog, 'u_blue'),
      deep: gl.getUniformLocation(prog, 'u_deep'),
    };

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio||1, 2);
      const r = canvas.getBoundingClientRect();
      canvas.width = Math.max(1, Math.round(r.width*dpr));
      canvas.height = Math.max(1, Math.round(r.height*dpr));
      gl.viewport(0,0,canvas.width, canvas.height);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      stateRef.current.target = [
        (e.clientX - r.left) / r.width,
        1.0 - (e.clientY - r.top) / r.height,
      ];
    };
    const onLeave = () => { stateRef.current.target = [0.5, 0.5]; };
    const onDown = () => { stateRef.current.pulse = 1.0; };
    window.addEventListener('pointermove', onMove);
    canvas.addEventListener('pointerleave', onLeave);
    window.addEventListener('pointerdown', onDown);

    let raf, t0 = performance.now();
    const tick = () => {
      const t = (performance.now() - t0) / 1000;
      const dt = Math.min(0.05, t - stateRef.current.lastT);
      stateRef.current.lastT = t;
      // ease mouse
      const s = stateRef.current;
      s.mouse[0] += (s.target[0] - s.mouse[0]) * 0.08;
      s.mouse[1] += (s.target[1] - s.mouse[1]) * 0.08;
      s.pulse *= Math.exp(-dt*2.2);

      gl.uniform2f(u.res, canvas.width, canvas.height);
      gl.uniform1f(u.time, t);
      gl.uniform2f(u.mouse, s.mouse[0], s.mouse[1]);
      gl.uniform1f(u.pulse, s.pulse);
      gl.uniform1f(u.warp, warp);
      gl.uniform1f(u.blueMix, blueMix);
      gl.uniform3fv(u.cream, hexToRGB(cream));
      gl.uniform3fv(u.blue, hexToRGB(blue));
      gl.uniform3fv(u.deep, hexToRGB(deep));
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      if(!paused) raf = requestAnimationFrame(tick);
    };
    tick();
    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      window.removeEventListener('pointermove', onMove);
      canvas.removeEventListener('pointerleave', onLeave);
      window.removeEventListener('pointerdown', onDown);
    };
  }, [cream, blue, deep, warp, blueMix, paused]);

  return <canvas ref={canvasRef} className={className} style={{display:'block', width:'100%', height:'100%', ...style}} />;
}

// ---------- Component: GridShader ----------
function GridShader({ cream='#EFE5CC', blue='#2944BA', deep='#0F1538', density=42, dotSize=0.14, className, style }){
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef({
    mouse:[0.5,0.5], target:[0.5,0.5],
    ripples: Array.from({length:6}, () => [0,0,-999,0]), // x,y,startTime,strength
    nextRipple: 0,
  });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const setup = createGL(canvas, FRAG_GRID_FIXED);
    if(!setup) return;
    const { gl, prog } = setup;
    const u = {
      res: gl.getUniformLocation(prog,'u_res'),
      time: gl.getUniformLocation(prog,'u_time'),
      mouse: gl.getUniformLocation(prog,'u_mouse'),
      ripples: gl.getUniformLocation(prog,'u_ripples'),
      density: gl.getUniformLocation(prog,'u_density'),
      dotSize: gl.getUniformLocation(prog,'u_dotSize'),
      cream: gl.getUniformLocation(prog,'u_cream'),
      blue: gl.getUniformLocation(prog,'u_blue'),
      deep: gl.getUniformLocation(prog,'u_deep'),
    };

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio||1, 2);
      const r = canvas.getBoundingClientRect();
      canvas.width = Math.max(1, Math.round(r.width*dpr));
      canvas.height = Math.max(1, Math.round(r.height*dpr));
      gl.viewport(0,0,canvas.width, canvas.height);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);

    let tNow = 0;
    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      stateRef.current.target = [
        (e.clientX - r.left) / r.width,
        1.0 - (e.clientY - r.top) / r.height,
      ];
    };
    const onDown = (e) => {
      const r = canvas.getBoundingClientRect();
      const x = (e.clientX - r.left) / r.width;
      const y = 1.0 - (e.clientY - r.top) / r.height;
      if(x<0||x>1||y<0||y>1) return;
      const s = stateRef.current;
      s.ripples[s.nextRipple] = [x, y, tNow, 1.0];
      s.nextRipple = (s.nextRipple + 1) % 6;
    };
    window.addEventListener('pointermove', onMove);
    canvas.addEventListener('pointerdown', onDown);

    let raf, t0 = performance.now();
    const ripBuf = new Float32Array(24);
    const tick = () => {
      const t = (performance.now() - t0) / 1000;
      tNow = t;
      const s = stateRef.current;
      s.mouse[0] += (s.target[0] - s.mouse[0]) * 0.1;
      s.mouse[1] += (s.target[1] - s.mouse[1]) * 0.1;

      for(let i=0;i<6;i++){
        const rp = s.ripples[i];
        ripBuf[i*4]   = rp[0];
        ripBuf[i*4+1] = rp[1];
        ripBuf[i*4+2] = rp[2];
        ripBuf[i*4+3] = rp[3];
      }

      gl.uniform2f(u.res, canvas.width, canvas.height);
      gl.uniform1f(u.time, t);
      gl.uniform2f(u.mouse, s.mouse[0], s.mouse[1]);
      gl.uniform4fv(u.ripples, ripBuf);
      gl.uniform1f(u.density, density);
      gl.uniform1f(u.dotSize, dotSize);
      gl.uniform3fv(u.cream, hexToRGB(cream));
      gl.uniform3fv(u.blue, hexToRGB(blue));
      gl.uniform3fv(u.deep, hexToRGB(deep));
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      window.removeEventListener('pointermove', onMove);
      canvas.removeEventListener('pointerdown', onDown);
    };
  }, [cream, blue, deep, density, dotSize]);

  return <canvas ref={canvasRef} className={className} style={{display:'block', width:'100%', height:'100%', ...style}} />;
}

Object.assign(window, { LiquidShader, GridShader });
