/* =====================================================================
   Shader Wallpapers — 8 interactive canvas/shader backgrounds
   All accept { className, style, children } and render an absolutely-
   positioned canvas that reacts to mouse position (normalised 0..1)
   and click. Used as page backgrounds AND as giant button fills.
   ===================================================================== */

const { useEffect, useRef, useState } = React;

// ---- shared hook: track pointer pos + click pulses over a DOM el -----
function usePointer(ref) {
  const state = useRef({ x: 0.5, y: 0.5, tx: 0.5, ty: 0.5, click: 0, down: false });
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const cx = (e.touches ? e.touches[0].clientX : e.clientX);
      const cy = (e.touches ? e.touches[0].clientY : e.clientY);
      state.current.tx = Math.max(0, Math.min(1, (cx - r.left) / r.width));
      state.current.ty = Math.max(0, Math.min(1, (cy - r.top) / r.height));
    };
    const onDown = () => { state.current.down = true; state.current.click = 1; };
    const onUp   = () => { state.current.down = false; };
    const onLeave = () => { state.current.tx = 0.5; state.current.ty = 0.5; };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('touchmove', onMove, { passive: true });
    el.addEventListener('mousedown', onDown);
    window.addEventListener('mouseup', onUp);
    el.addEventListener('mouseleave', onLeave);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('touchmove', onMove);
      el.removeEventListener('mousedown', onDown);
      window.removeEventListener('mouseup', onUp);
      el.removeEventListener('mouseleave', onLeave);
    };
  }, [ref]);
  return state;
}

// ---- GLSL helpers ----------------------------------------------------
function makeGL(canvas, fragSrc) {
  const gl = canvas.getContext('webgl', { antialias: false, premultipliedAlpha: false });
  if (!gl) return null;
  const vs = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vs, 'attribute vec2 p; void main(){gl_Position=vec4(p,0.,1.);}');
  gl.compileShader(vs);
  const fs = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fs, fragSrc);
  gl.compileShader(fs);
  if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
    console.warn(gl.getShaderInfoLog(fs));
    return null;
  }
  const prog = gl.createProgram();
  gl.attachShader(prog, vs);
  gl.attachShader(prog, fs);
  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, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
  const loc = gl.getAttribLocation(prog, 'p');
  gl.enableVertexAttribArray(loc);
  gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
  const u = (n) => gl.getUniformLocation(prog, n);
  return { gl, prog, u };
}

function useGLShader(canvasRef, containerRef, fragSrc, opts = {}) {
  const ptr = usePointer(containerRef);
  useEffect(() => {
    const canvas = canvasRef.current;
    const container = containerRef.current;
    if (!canvas || !container) return;
    const ctx = makeGL(canvas, fragSrc);
    if (!ctx) return;
    const { gl, u } = ctx;
    const uRes = u('uRes'), uTime = u('uTime'), uMouse = u('uMouse'), uClick = u('uClick');
    let raf, running = true, t0 = performance.now();
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = container.getBoundingClientRect();
      canvas.width = Math.max(1, Math.floor(r.width * dpr));
      canvas.height = Math.max(1, Math.floor(r.height * dpr));
      canvas.style.width = r.width + 'px';
      canvas.style.height = r.height + 'px';
      gl.viewport(0, 0, canvas.width, canvas.height);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(container);
    const frame = () => {
      if (!running) return;
      const s = ptr.current;
      // ease pointer
      s.x += (s.tx - s.x) * 0.12;
      s.y += (s.ty - s.y) * 0.12;
      s.click *= 0.93;
      gl.uniform2f(uRes, canvas.width, canvas.height);
      gl.uniform1f(uTime, (performance.now() - t0) * 0.001);
      gl.uniform2f(uMouse, s.x, 1 - s.y);
      gl.uniform1f(uClick, s.click);
      gl.drawArrays(gl.TRIANGLES, 0, 6);
      raf = requestAnimationFrame(frame);
    };
    frame();
    return () => { running = false; cancelAnimationFrame(raf); ro.disconnect(); };
  }, [fragSrc]);
}

// ---------------------------------------------------------------------
// 1. GRAVITY GRID  (Canvas 2D — crisp dots that pull toward cursor)
// ---------------------------------------------------------------------
function GravityGrid({ color = '#14b8a6', bg = '#0a0a0b', density = 28, pull = 80, children, className, style }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  const ptr = usePointer(containerRef);
  useEffect(() => {
    const c = canvasRef.current, el = containerRef.current;
    const ctx = c.getContext('2d');
    let raf, running = true;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = el.getBoundingClientRect();
      c.width = r.width * dpr; c.height = r.height * dpr;
      c.style.width = r.width + 'px'; c.style.height = r.height + 'px';
      ctx.scale(dpr, dpr);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(el);
    const draw = () => {
      if (!running) return;
      const r = el.getBoundingClientRect();
      const w = r.width, h = r.height;
      const s = ptr.current;
      s.x += (s.tx - s.x) * 0.15; s.y += (s.ty - s.y) * 0.15;
      s.click *= 0.92;
      ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
      const cols = Math.ceil(w / density), rows = Math.ceil(h / density);
      const mx = s.x * w, my = s.y * h;
      for (let i = 0; i <= cols; i++) {
        for (let j = 0; j <= rows; j++) {
          const x = i * density, y = j * density;
          const dx = x - mx, dy = y - my;
          const d = Math.sqrt(dx*dx + dy*dy) + 0.01;
          const force = pull / (d * 0.6 + 40) * (1 + s.click * 2);
          const tx = x - dx * force * 0.3;
          const ty = y - dy * force * 0.3;
          const a = Math.max(0.12, Math.min(1, 1.4 - d / (Math.max(w, h) * 0.6)));
          ctx.fillStyle = color;
          ctx.globalAlpha = a;
          const rad = 1.2 + (1 - d / 400) * 1.6;
          ctx.beginPath();
          ctx.arc(tx, ty, Math.max(0.5, rad), 0, Math.PI * 2);
          ctx.fill();
        }
      }
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { running = false; cancelAnimationFrame(raf); ro.disconnect(); };
  }, [color, bg, density, pull]);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 2. PLASMA / FLOW-FIELD (GLSL)
// ---------------------------------------------------------------------
const PLASMA_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse; uniform float uClick;
float hash(vec2 p){return fract(sin(dot(p,vec2(23.1,47.7)))*43758.5);}
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<5;i++){v+=a*noise(p);p*=2.03;a*=.5;}return v;}
void main(){
  vec2 uv=(gl_FragCoord.xy*2.-uRes)/min(uRes.x,uRes.y);
  vec2 m=(uMouse*2.-1.);
  float t=uTime*0.08;
  vec2 p=uv*1.4;
  // warped flow field pulled toward cursor
  vec2 w=vec2(fbm(p+t), fbm(p-t+3.1))*2.-1.;
  p+=w*0.7;
  p+=normalize(m-uv+0.001)*exp(-distance(uv,m)*1.6)*0.6*(1.+uClick*2.);
  float v=fbm(p*1.6+t*2.);
  v=pow(v,1.5);
  vec3 ink=vec3(0.039,0.039,0.043);
  vec3 deep=vec3(0.024,0.180,0.165);
  vec3 teal=vec3(0.078,0.722,0.651);
  vec3 peak=vec3(0.372,0.831,0.765);
  vec3 col=mix(ink, deep, smoothstep(0.30,0.55,v));
  col=mix(col, teal, smoothstep(0.55,0.80,v));
  col=mix(col, peak, smoothstep(0.82,0.97,v));
  col*=1.-0.28*dot(uv,uv);
  col+=(hash(gl_FragCoord.xy+uTime)-0.5)*0.02;
  gl_FragColor=vec4(col,1.);
}`;

function PlasmaFlow({ className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  useGLShader(canvasRef, containerRef, PLASMA_FRAG);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: '#0a0a0b', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 3. VORONOI (GLSL)
// ---------------------------------------------------------------------
const VORONOI_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse; uniform float uClick;
vec2 hash2(vec2 p){p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3)));return fract(sin(p)*43758.5453);}
void main(){
  vec2 uv=gl_FragCoord.xy/uRes.y;
  float s=5.5;
  vec2 g=uv*s;
  vec2 id=floor(g); vec2 f=fract(g);
  vec2 m=uMouse*uRes.xy/uRes.y;
  float d1=1e9, d2=1e9;
  vec2 mp;
  for(int j=-1;j<=1;j++)for(int i=-1;i<=1;i++){
    vec2 off=vec2(i,j);
    vec2 h=hash2(id+off);
    vec2 p=off+0.5+0.5*sin(uTime*0.5+6.283*h);
    float pd=distance(id+p,m);
    p+=normalize((m-(id+p))+0.001)*(0.4/(pd+0.5))*(1.+uClick);
    float d=distance(f,p);
    if(d<d1){d2=d1;d1=d;mp=h;}else if(d<d2){d2=d;}
  }
  float edge=1.-smoothstep(0.0,0.04,d2-d1);
  vec3 ink=vec3(0.039,0.039,0.043);
  vec3 deep=vec3(0.024,0.180,0.165);
  vec3 teal=vec3(0.078,0.722,0.651);
  float prox=exp(-distance(uv,m)*1.6);
  vec3 fill=mix(ink, deep, prox*0.4);
  fill=mix(fill, teal, prox*0.25*(0.3+0.7*mp.x));
  vec3 col=mix(fill, teal*1.3, edge);
  col+=0.04*vec3(0.372,0.831,0.765)*prox;
  gl_FragColor=vec4(col,1.);
}`;

function Voronoi({ className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  useGLShader(canvasRef, containerRef, VORONOI_FRAG);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: '#0a0a0b', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 4. RAY-MARCHED FRACTAL  (GLSL — kaleidoscopic kif / mandelbox-ish)
// ---------------------------------------------------------------------
const FRACTAL_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse; uniform float uClick;
float map(vec3 p){
  p=abs(p)-1.2;
  for(int i=0;i<5;i++){
    p=abs(p)-0.3;
    p=p.yzx*1.15;
  }
  return length(p)-0.9;
}
void main(){
  vec2 uv=(gl_FragCoord.xy*2.-uRes)/min(uRes.x,uRes.y);
  vec3 ro=vec3(0.,0.,-4.);
  vec3 rd=normalize(vec3(uv, 1.6));
  float a=(uMouse.x-0.5)*3.14;
  float b=(uMouse.y-0.5)*2.;
  mat2 R=mat2(cos(a),-sin(a),sin(a),cos(a));
  rd.xz*=R; ro.xz*=R;
  ro.y+=b;
  float t=uTime*0.15;
  float dtot=0.; float h=0.;
  for(int i=0;i<60;i++){
    vec3 p=ro+rd*dtot;
    p.xy*=mat2(cos(t),-sin(t),sin(t),cos(t));
    h=map(p);
    if(h<0.001)break;
    dtot+=h*0.7;
    if(dtot>12.)break;
  }
  float f=1.-dtot/12.;
  vec3 col=mix(vec3(0.04,0.04,0.045), vec3(0.95,0.74,0.3), f*f);
  col+=vec3(0.4,0.2,0.1)*pow(f,8.)*uClick*2.;
  gl_FragColor=vec4(col,1.);
}`;

function Fractal({ className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  useGLShader(canvasRef, containerRef, FRACTAL_FRAG);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: '#0a0a0b', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 5. PARTICLE SWARM (Canvas 2D)
// ---------------------------------------------------------------------
function ParticleSwarm({ count = 180, color = '#14b8a6', bg = '#0a0a0b', className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  const ptr = usePointer(containerRef);
  useEffect(() => {
    const c = canvasRef.current, el = containerRef.current;
    const ctx = c.getContext('2d');
    let raf, running = true;
    let parts = [];
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = el.getBoundingClientRect();
      c.width = r.width * dpr; c.height = r.height * dpr;
      c.style.width = r.width + 'px'; c.style.height = r.height + 'px';
      ctx.setTransform(dpr,0,0,dpr,0,0);
      parts = Array.from({length: count}, () => ({
        x: Math.random() * r.width, y: Math.random() * r.height,
        vx: (Math.random()-0.5)*0.4, vy: (Math.random()-0.5)*0.4,
        s: 0.6 + Math.random()*2.2,
      }));
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(el);
    const tick = () => {
      if (!running) return;
      const r = el.getBoundingClientRect();
      const s = ptr.current;
      s.x += (s.tx - s.x) * 0.12; s.y += (s.ty - s.y) * 0.12;
      s.click *= 0.9;
      ctx.fillStyle = bg + 'cc'; // trail
      ctx.fillRect(0, 0, r.width, r.height);
      const mx = s.x * r.width, my = s.y * r.height;
      for (const p of parts) {
        const dx = mx - p.x, dy = my - p.y;
        const d = Math.sqrt(dx*dx + dy*dy) + 0.01;
        const f = (40 / d) * (s.click > 0.1 ? -3 : 1);
        p.vx += (dx/d) * f * 0.04;
        p.vy += (dy/d) * f * 0.04;
        p.vx *= 0.96; p.vy *= 0.96;
        p.x += p.vx; p.y += p.vy;
        if (p.x < 0) p.x = r.width; if (p.x > r.width) p.x = 0;
        if (p.y < 0) p.y = r.height; if (p.y > r.height) p.y = 0;
        const speed = Math.min(1, Math.hypot(p.vx,p.vy)*2);
        ctx.fillStyle = color;
        ctx.globalAlpha = 0.5 + speed*0.5;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.s, 0, Math.PI*2);
        ctx.fill();
      }
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => { running = false; cancelAnimationFrame(raf); ro.disconnect(); };
  }, [count, color, bg]);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: bg, ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 6. DISPLACEMENT NOISE FIELD (GLSL)
// ---------------------------------------------------------------------
const NOISE_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse; uniform float uClick;
float hash(vec2 p){return fract(sin(dot(p,vec2(23.1,47.7)))*43758.5);}
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.;float a=.5;for(int i=0;i<5;i++){v+=a*noise(p);p*=2.02;a*=.5;}return v;}
void main(){
  vec2 uv=gl_FragCoord.xy/uRes.y;
  vec2 m=uMouse*uRes.xy/uRes.y;
  vec2 q=uv+vec2(fbm(uv*3.+uTime*0.1), fbm(uv*3.-uTime*0.1))*0.4;
  q+= normalize((m-uv)+0.001) * exp(-distance(uv,m)*3.) * 0.3 * (1.+uClick*2.);
  float v=fbm(q*4.+uTime*0.05);
  vec3 base=vec3(0.04,0.04,0.045);
  vec3 warm=vec3(0.93,0.72,0.29);
  vec3 col=mix(base, warm, smoothstep(0.4,0.85,v));
  // contour lines
  float band=smoothstep(0.02,0.0,abs(fract(v*12.)-0.5));
  col+=band*0.15;
  gl_FragColor=vec4(col,1.);
}`;

function DisplacementField({ className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  useGLShader(canvasRef, containerRef, NOISE_FRAG);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: '#0a0a0b', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 7. ASCII / DOT-MATRIX FIELD  (Canvas 2D)
// ---------------------------------------------------------------------
function AsciiField({ className, style, children, color = '#14b8a6', bg = '#0a0a0b', chars = '·:+*xXO#@' }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  const ptr = usePointer(containerRef);
  useEffect(() => {
    const c = canvasRef.current, el = containerRef.current;
    const ctx = c.getContext('2d');
    let raf, running = true, t0 = performance.now();
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      const r = el.getBoundingClientRect();
      c.width = r.width * dpr; c.height = r.height * dpr;
      c.style.width = r.width + 'px'; c.style.height = r.height + 'px';
      ctx.setTransform(dpr,0,0,dpr,0,0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(el);
    const cell = 12;
    const tick = () => {
      if (!running) return;
      const r = el.getBoundingClientRect();
      const s = ptr.current;
      s.x += (s.tx - s.x) * 0.15; s.y += (s.ty - s.y) * 0.15;
      s.click *= 0.92;
      const t = (performance.now() - t0) * 0.001;
      ctx.fillStyle = bg; ctx.fillRect(0,0,r.width,r.height);
      ctx.font = `11px 'JetBrains Mono', monospace`;
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      const cols = Math.floor(r.width / cell), rows = Math.floor(r.height / cell);
      const mx = s.x * cols, my = s.y * rows;
      for (let i = 0; i < cols; i++) {
        for (let j = 0; j < rows; j++) {
          const dx = i - mx, dy = j - my;
          const d = Math.sqrt(dx*dx + dy*dy);
          const wave = Math.sin(d * 0.3 - t * 4) * 0.5 + 0.5;
          const prox = Math.max(0, 1 - d / 18) + s.click * 0.6;
          const v = Math.max(0, Math.min(chars.length-1, Math.floor((wave * 0.6 + prox * 0.8) * (chars.length-1))));
          ctx.globalAlpha = 0.15 + v / chars.length * 0.85;
          ctx.fillStyle = color;
          ctx.fillText(chars[v], i*cell + cell/2, j*cell + cell/2);
        }
      }
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(tick);
    };
    tick();
    return () => { running = false; cancelAnimationFrame(raf); ro.disconnect(); };
  }, [color, bg, chars]);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: bg, ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// ---------------------------------------------------------------------
// 8. LIQUID METAL / CHROMATIC BLOB (GLSL — metaballs with chroma)
// ---------------------------------------------------------------------
const METAL_FRAG = `
precision highp float;
uniform vec2 uRes; uniform float uTime; uniform vec2 uMouse; uniform float uClick;
float mb(vec2 p, vec2 c, float r){return r*r/dot(p-c,p-c);}
void main(){
  vec2 uv=(gl_FragCoord.xy*2.-uRes)/min(uRes.x,uRes.y);
  vec2 m=(uMouse*2.-1.);
  float t=uTime*0.6;
  float f=0.;
  f+=mb(uv, m*0.9, 0.35);
  f+=mb(uv, vec2(sin(t)*0.7, cos(t*1.3)*0.5), 0.25);
  f+=mb(uv, vec2(cos(t*0.7)*0.8, sin(t*1.1)*0.6), 0.22);
  f+=mb(uv, vec2(sin(t*1.4+m.x)*0.6, cos(t*0.9+m.y)*0.7), 0.18);
  float iso=smoothstep(0.9,1.2, f+uClick*0.5);
  // chroma splits along iso edge
  float edge=1.-abs(f-1.)*4.;
  edge=max(0., edge);
  vec3 base=vec3(0.04,0.04,0.045);
  vec3 hot=vec3(0.96,0.76,0.32);
  vec3 chrom=vec3(0.4,0.85,0.9)*edge*0.5 + vec3(0.95,0.55,0.3)*edge*0.5;
  vec3 col=mix(base, hot, iso);
  col+=chrom*0.35;
  col*=1.-0.25*length(uv)*0.5;
  gl_FragColor=vec4(col,1.);
}`;

function LiquidMetal({ className, style, children }) {
  const containerRef = useRef(null);
  const canvasRef = useRef(null);
  useGLShader(canvasRef, containerRef, METAL_FRAG);
  return (
    <div ref={containerRef} className={className} style={{ position: 'relative', overflow: 'hidden', background: '#0a0a0b', ...style }}>
      <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
      <div style={{ position: 'relative', zIndex: 1, height: '100%' }}>{children}</div>
    </div>
  );
}

// expose to window so other <script type="text/babel"> files see them
Object.assign(window, {
  GravityGrid, PlasmaFlow, Voronoi, Fractal, ParticleSwarm,
  DisplacementField, AsciiField, LiquidMetal,
  usePointer
});
