// Neurasea for Business — WebGL hero background
// A subtle domain-warped flow field reading as "data currents on the grid".
// On-brand: tinted with the live (green) + draw (blue) accents over the cream win bg.
// Exports to window: HeroFlowField

const NS_FLOW_FRAG = `
precision highp float;
uniform vec2  u_res;
uniform float u_time;
uniform vec2  u_mouse;
uniform vec3  u_green;
uniform vec3  u_blue;

// --- hash / value noise ---
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);
  vec2 u = f * f * (3.0 - 2.0 * f);
  float a = hash(i);
  float b = hash(i + vec2(1.0, 0.0));
  float c = hash(i + vec2(0.0, 1.0));
  float d = hash(i + vec2(1.0, 1.0));
  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;
  mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
  for(int i = 0; i < 5; i++){
    v += a * noise(p);
    p = m * p;
    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;

  float t = u_time * 0.045;

  // domain warp — two passes of fbm feeding the next, gives soft flowing currents
  vec2 q = vec2(fbm(p * 1.6 + vec2(0.0, t)),
                fbm(p * 1.6 + vec2(4.3, 1.7 - t)));
  vec2 r = vec2(fbm(p * 1.6 + 2.0 * q + vec2(1.2, 9.1) + t * 0.8),
                fbm(p * 1.6 + 2.0 * q + vec2(8.3, 2.4) - t * 0.6));
  float f = fbm(p * 1.6 + 2.6 * r);

  // mouse adds a gentle local swell
  vec2 mp = u_mouse; mp.x *= aspect;
  float md = 1.0 - smoothstep(0.0, 0.55, length(p - mp));
  f += md * 0.18;

  // contour banding -> faint flowing isolines (the "grid" reading)
  float bands = abs(sin((f * 9.0) - t * 6.0));
  float line = smoothstep(0.55, 0.97, bands);

  // color: lerp green<->blue across the field
  vec3 col = mix(u_green, u_blue, smoothstep(0.25, 0.85, f + r.x * 0.3));

  // intensity: soft fill from the warp + brighter isolines
  float fill = smoothstep(0.35, 0.95, f);
  float a = fill * 0.40 + line * 0.42 + md * 0.14;

  // radial vignette so edges fade into the page
  float vig = smoothstep(1.15, 0.25, length(uv - 0.5) * 1.4);
  a *= vig;

  gl_FragColor = vec4(col, clamp(a, 0.0, 0.85));
}
`;

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

function HeroFlowField({ green = [0.09, 0.66, 0.45], blue = [0.18, 0.44, 0.92], style }) {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false, antialias: true });
    if (!gl) return;

    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    const compile = (type, src) => {
      const s = gl.createShader(type);
      gl.shaderSource(s, src); gl.compileShader(s);
      if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { console.warn(gl.getShaderInfoLog(s)); }
      return s;
    };
    const prog = gl.createProgram();
    gl.attachShader(prog, compile(gl.VERTEX_SHADER, NS_FLOW_VERT));
    gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, NS_FLOW_FRAG));
    gl.linkProgram(prog);
    gl.useProgram(prog);

    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
    const loc = gl.getAttribLocation(prog, 'a_pos');
    gl.enableVertexAttribArray(loc);
    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);

    const uRes = gl.getUniformLocation(prog, 'u_res');
    const uTime = gl.getUniformLocation(prog, 'u_time');
    const uMouse = gl.getUniformLocation(prog, 'u_mouse');
    const uGreen = gl.getUniformLocation(prog, 'u_green');
    const uBlue = gl.getUniformLocation(prog, 'u_blue');
    gl.uniform3fv(uGreen, green);
    gl.uniform3fv(uBlue, blue);

    let mouse = [0.5, 0.5], target = [0.5, 0.5];

    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const w = canvas.clientWidth, h = canvas.clientHeight;
      canvas.width = Math.max(1, Math.floor(w * dpr));
      canvas.height = Math.max(1, Math.floor(h * dpr));
      gl.viewport(0, 0, canvas.width, canvas.height);
      gl.uniform2f(uRes, canvas.width, canvas.height);
    };
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);
    resize();

    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      target = [(e.clientX - rect.left) / rect.width, 1 - (e.clientY - rect.top) / rect.height];
    };
    canvas.parentElement?.addEventListener('mousemove', onMove);

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    let raf, start = performance.now();
    const draw = (now) => {
      const t = (now - start) / 1000;
      mouse[0] += (target[0] - mouse[0]) * 0.06;
      mouse[1] += (target[1] - mouse[1]) * 0.06;
      gl.uniform1f(uTime, reduce ? 8.0 : t);
      gl.uniform2f(uMouse, mouse[0], mouse[1]);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      if (!reduce) raf = requestAnimationFrame(draw);
    };
    draw(performance.now());
    if (reduce) requestAnimationFrame(draw); // one settle frame after resize

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      canvas.parentElement?.removeEventListener('mousemove', onMove);
      gl.getExtension('WEBGL_lose_context')?.loseContext();
    };
  }, []);

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

Object.assign(window, { HeroFlowField });
