// ProcessWebGL.jsx — Three.js + GSAP visuals for How We Work cards.
const ZG_CORAL = 0xe94e3b;
const ZG_CORAL_LIGHT = 0xffb8a8;

function zgProcessReducedMotion() {
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

function zgProcessPixelRatio() {
  const mobile = window.matchMedia("(max-width: 900px)").matches;
  return Math.min(window.devicePixelRatio || 1, mobile ? 1.5 : 2);
}

function zgCreateRenderer(container) {
  const THREE = window.THREE;
  const width = Math.max(container.clientWidth, 1);
  const height = Math.max(container.clientHeight, 1);

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(38, width / height, 0.1, 40);
  camera.position.set(0, 0.15, 4.6);

  const renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true,
    powerPreference: "high-performance",
  });
  renderer.setSize(width, height);
  renderer.setPixelRatio(zgProcessPixelRatio());
  renderer.setClearColor(0x000000, 0);
  if (THREE.SRGBColorSpace) renderer.outputColorSpace = THREE.SRGBColorSpace;
  else if (THREE.sRGBEncoding) renderer.outputEncoding = THREE.sRGBEncoding;
  container.appendChild(renderer.domElement);

  scene.add(new THREE.AmbientLight(0xffffff, 0.28));

  const key = new THREE.PointLight(0xffcdbf, 2.4, 24);
  key.position.set(2.2, 2.8, 3.5);
  scene.add(key);

  const rim = new THREE.PointLight(ZG_CORAL, 1.6, 18);
  rim.position.set(-2.4, -0.6, 2.2);
  scene.add(rim);

  return { THREE, scene, camera, renderer, lights: { key, rim } };
}

function zgBuildOrbScene(THREE, scene) {
  const group = new THREE.Group();

  const core = new THREE.Mesh(
    new THREE.IcosahedronGeometry(0.92, 32),
    new THREE.MeshPhysicalMaterial({
      color: ZG_CORAL,
      emissive: ZG_CORAL,
      emissiveIntensity: 0.35,
      roughness: 0.22,
      metalness: 0.18,
      clearcoat: 1,
      clearcoatRoughness: 0.15,
    })
  );
  group.add(core);

  const halo = new THREE.Mesh(
    new THREE.IcosahedronGeometry(1.18, 24),
    new THREE.MeshBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.14,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    })
  );
  group.add(halo);

  const ring = new THREE.Mesh(
    new THREE.TorusGeometry(1.35, 0.012, 8, 96),
    new THREE.MeshBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.35,
    })
  );
  ring.rotation.x = Math.PI / 2.4;
  group.add(ring);

  const ring2 = new THREE.Mesh(
    new THREE.TorusGeometry(1.55, 0.008, 6, 96),
    new THREE.MeshBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.2,
    })
  );
  ring2.rotation.x = Math.PI / 1.7;
  ring2.rotation.z = Math.PI / 5;
  group.add(ring2);

  scene.add(group);
  return { group, core, halo, ring, ring2 };
}

function zgBuildStackScene(THREE, scene) {
  const group = new THREE.Group();

  const makeSlab = (scale, opacity, emissive) => {
    const shape = new THREE.Shape();
    shape.moveTo(0, 0.72 * scale);
    shape.lineTo(-0.68 * scale, -0.52 * scale);
    shape.lineTo(0.68 * scale, -0.52 * scale);
    shape.closePath();

    const geom = new THREE.ExtrudeGeometry(shape, {
      depth: 0.1,
      bevelEnabled: true,
      bevelSize: 0.03,
      bevelThickness: 0.03,
      bevelSegments: 2,
    });
    if (geom.center) geom.center();

    const mat = new THREE.MeshPhysicalMaterial({
      color: ZG_CORAL,
      emissive: ZG_CORAL,
      emissiveIntensity: emissive,
      roughness: 0.35,
      metalness: 0.08,
      transparent: true,
      opacity,
      side: THREE.DoubleSide,
    });

    const mesh = new THREE.Mesh(geom, mat);
    mesh.rotation.x = -0.55;
    return mesh;
  };

  const back = makeSlab(1.05, 0.22, 0.08);
  back.position.set(0, -0.38, -0.28);
  const mid = makeSlab(0.92, 0.45, 0.18);
  mid.position.set(0, -0.08, 0);
  const front = makeSlab(0.78, 0.95, 0.42);
  front.position.set(0, 0.28, 0.26);

  group.add(back, mid, front);
  scene.add(group);
  return { group, layers: [back, mid, front] };
}

function zgBuildGridScene(THREE, scene) {
  const group = new THREE.Group();
  const tiles = [];
  const cols = 3;
  const rows = 3;
  const gap = 0.1;
  const tileSize = 0.44;
  const path = [0, 1, 2, 5, 8, 7, 6, 3, 4];

  for (let r = 0; r < rows; r += 1) {
    for (let c = 0; c < cols; c += 1) {
      const idx = r * cols + c;
      const onPath = path.includes(idx);

      const mesh = new THREE.Mesh(
        new THREE.BoxGeometry(tileSize, 0.07, tileSize),
        new THREE.MeshPhysicalMaterial({
          color: ZG_CORAL,
          emissive: ZG_CORAL,
          emissiveIntensity: onPath ? 0.14 : 0.03,
          roughness: 0.38,
          metalness: 0.1,
          transparent: true,
          opacity: onPath ? 0.78 : 0.28,
        })
      );

      mesh.position.set(
        (c - (cols - 1) / 2) * (tileSize + gap),
        0,
        (r - (rows - 1) / 2) * (tileSize + gap)
      );
      mesh.userData.idx = idx;
      group.add(mesh);
      tiles.push(mesh);
    }
  }

  const pathPoints = path.map((idx) => {
    const r = Math.floor(idx / cols);
    const c = idx % cols;
    return new THREE.Vector3(
      (c - (cols - 1) / 2) * (tileSize + gap),
      0.06,
      (r - (rows - 1) / 2) * (tileSize + gap)
    );
  });

  const line = new THREE.Line(
    new THREE.BufferGeometry().setFromPoints(pathPoints),
    new THREE.LineBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.42,
    })
  );
  group.add(line);

  const traveler = new THREE.Mesh(
    new THREE.SphereGeometry(0.07, 18, 18),
    new THREE.MeshBasicMaterial({
      color: 0xffe4dc,
      transparent: true,
      opacity: 0.95,
    })
  );
  traveler.position.copy(pathPoints[0]);
  group.add(traveler);

  const travelerHalo = new THREE.Mesh(
    new THREE.SphereGeometry(0.16, 18, 18),
    new THREE.MeshBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.32,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    })
  );
  travelerHalo.position.copy(pathPoints[0]);
  group.add(travelerHalo);

  group.rotation.x = -0.58;
  group.rotation.y = 0.32;
  group.position.y = 0.08;
  scene.add(group);

  return { group, tiles, line, path, pathPoints, traveler, travelerHalo };
}

function zgBuildArcScene(THREE, scene) {
  const group = new THREE.Group();

  const curve = new THREE.QuadraticBezierCurve3(
    new THREE.Vector3(-1.55, -0.95, 0),
    new THREE.Vector3(0, 1.35, 0.35),
    new THREE.Vector3(1.55, -0.95, 0)
  );

  const track = new THREE.Mesh(
    new THREE.TubeGeometry(curve, 64, 0.028, 10, false),
    new THREE.MeshBasicMaterial({
      color: 0xffffff,
      transparent: true,
      opacity: 0.08,
    })
  );
  group.add(track);

  const beamMat = new THREE.MeshPhysicalMaterial({
    color: ZG_CORAL_LIGHT,
    emissive: ZG_CORAL,
    emissiveIntensity: 1.2,
    roughness: 0.2,
    metalness: 0.1,
    transparent: true,
    opacity: 0.95,
  });
  const beam = new THREE.Mesh(new THREE.TubeGeometry(curve, 64, 0.05, 12, false), beamMat);
  group.add(beam);

  const node = new THREE.Mesh(
    new THREE.SphereGeometry(0.11, 24, 24),
    new THREE.MeshPhysicalMaterial({
      color: 0xffffff,
      emissive: ZG_CORAL,
      emissiveIntensity: 1.4,
      roughness: 0.15,
      metalness: 0.05,
    })
  );
  group.add(node);

  const pulse = new THREE.Mesh(
    new THREE.RingGeometry(0.14, 0.22, 32),
    new THREE.MeshBasicMaterial({
      color: ZG_CORAL_LIGHT,
      transparent: true,
      opacity: 0.45,
      side: THREE.DoubleSide,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    })
  );
  group.add(pulse);

  const trailCount = 6;
  const trail = [];
  for (let i = 0; i < trailCount; i += 1) {
    const baseOpacity = 0.42 * (1 - i / trailCount);
    const ghost = new THREE.Mesh(
      new THREE.SphereGeometry(0.09 - i * 0.008, 16, 16),
      new THREE.MeshBasicMaterial({
        color: ZG_CORAL_LIGHT,
        transparent: true,
        opacity: baseOpacity,
        blending: THREE.AdditiveBlending,
        depthWrite: false,
      })
    );
    ghost.userData.baseOpacity = baseOpacity;
    group.add(ghost);
    trail.push(ghost);
  }

  scene.add(group);
  return { group, curve, beamMat, node, pulse, trail };
}

function zgScaleTweens(tweens, scale) {
  tweens.forEach((tween) => {
    if (tween && typeof tween.timeScale === "function") {
      tween.timeScale(scale);
    }
  });
}

function zgAnimateOrb(gsap, refs, reduced) {
  const { group, core, halo, ring, ring2 } = refs;
  if (reduced) {
    return { tweens: [], setBoost: () => {} };
  }

  const tweens = [
    gsap.to(group.position, { y: 0.22, duration: 3.2, ease: "sine.inOut", yoyo: true, repeat: -1 }),
    gsap.to(group.rotation, { y: Math.PI * 2, duration: 18, ease: "none", repeat: -1 }),
    gsap.to(halo.scale, { x: 1.08, y: 1.08, z: 1.08, duration: 2.8, ease: "sine.inOut", yoyo: true, repeat: -1 }),
    gsap.to(ring.rotation, { z: Math.PI * 2, duration: 22, ease: "none", repeat: -1 }),
    gsap.to(ring2.rotation, { z: -Math.PI * 2, duration: 16, ease: "none", repeat: -1 }),
    gsap.to(core.material, { emissiveIntensity: 0.55, duration: 2.4, ease: "sine.inOut", yoyo: true, repeat: -1 }),
  ];

  const setBoost = (on) => {
    zgScaleTweens(tweens, on ? 2.1 : 1);
    gsap.to(core.material, { emissiveIntensity: on ? 1.05 : 0.4, duration: 0.4, overwrite: "auto" });
    gsap.to(halo.material, { opacity: on ? 0.32 : 0.14, duration: 0.4, overwrite: "auto" });
    gsap.to(ring.material, { opacity: on ? 0.75 : 0.35, duration: 0.4, overwrite: "auto" });
    gsap.to(ring2.material, { opacity: on ? 0.55 : 0.2, duration: 0.4, overwrite: "auto" });
  };

  return { tweens, setBoost };
}

function zgAnimateStack(gsap, refs, reduced) {
  const [back, mid, front] = refs.layers;
  if (reduced) {
    return { tweens: [], setBoost: () => {} };
  }

  const tweens = [
    gsap.to(back.position, { y: -0.48, duration: 2.6, ease: "sine.inOut", yoyo: true, repeat: -1 }),
    gsap.to(mid.position, { y: -0.18, duration: 2.6, ease: "sine.inOut", yoyo: true, repeat: -1, delay: 0.15 }),
    gsap.to(front.position, { y: 0.38, duration: 2.6, ease: "sine.inOut", yoyo: true, repeat: -1, delay: 0.3 }),
    gsap.to(front.rotation, { z: 0.08, duration: 2.6, ease: "sine.inOut", yoyo: true, repeat: -1 }),
  ];

  const setBoost = (on) => {
    zgScaleTweens(tweens, on ? 1.9 : 1);
    gsap.to(back.position, { z: on ? -0.55 : -0.28, duration: 0.5, overwrite: "auto" });
    gsap.to(front.position, { z: on ? 0.55 : 0.26, duration: 0.5, overwrite: "auto" });
    gsap.to(front.material, { emissiveIntensity: on ? 0.85 : 0.42, duration: 0.5, overwrite: "auto" });
    gsap.to(mid.material, { emissiveIntensity: on ? 0.42 : 0.18, duration: 0.5, overwrite: "auto" });
  };

  return { tweens, setBoost };
}

function zgAnimateGrid(gsap, refs, reduced) {
  const { tiles, path, group, pathPoints, traveler, travelerHalo } = refs;
  const pathTiles = path.map((idx) => tiles[idx]).filter(Boolean);
  const state = { step: 0 };

  const paint = () => {
    const exact = state.step % pathTiles.length;
    const activeIndex = Math.floor(exact);
    const frac = exact - activeIndex;

    pathTiles.forEach((tile, index) => {
      const active = index === activeIndex;
      tile.material.emissiveIntensity = active ? 0.72 : 0.14;
      tile.material.opacity = active ? 1 : 0.62;
      tile.position.y = active ? 0.12 : 0;
      tile.scale.set(active ? 1.06 : 1, active ? 1.12 : 1, active ? 1.06 : 1);
    });

    tiles.forEach((tile) => {
      if (!path.includes(tile.userData.idx)) {
        tile.material.emissiveIntensity = 0.03;
        tile.material.opacity = 0.24;
        tile.position.y = 0;
        tile.scale.set(1, 1, 1);
      }
    });

    if (pathPoints && traveler) {
      const from = pathPoints[activeIndex % pathPoints.length];
      const to = pathPoints[(activeIndex + 1) % pathPoints.length];
      traveler.position.lerpVectors(from, to, frac);
      traveler.position.y = 0.18 + Math.sin(frac * Math.PI) * 0.06;
      if (travelerHalo) {
        travelerHalo.position.copy(traveler.position);
      }
    }
  };

  paint();
  if (reduced) {
    return { tweens: [], setBoost: () => {} };
  }

  const tweens = [
    gsap.to(state, {
      step: pathTiles.length,
      duration: pathTiles.length * 0.52,
      ease: "none",
      repeat: -1,
      onUpdate: paint,
    }),
    gsap.to(group.rotation, {
      y: 0.42,
      duration: 4.8,
      ease: "sine.inOut",
      yoyo: true,
      repeat: -1,
    }),
    gsap.to(group.position, {
      y: 0.14,
      duration: 3.4,
      ease: "sine.inOut",
      yoyo: true,
      repeat: -1,
    }),
  ];

  const setBoost = (on) => {
    zgScaleTweens(tweens, on ? 2.4 : 1);
    if (traveler) {
      gsap.to(traveler.material, { opacity: on ? 1 : 0.95, duration: 0.4, overwrite: "auto" });
      gsap.to(traveler.scale, { x: on ? 1.4 : 1, y: on ? 1.4 : 1, z: on ? 1.4 : 1, duration: 0.4, overwrite: "auto" });
    }
    if (travelerHalo) {
      gsap.to(travelerHalo.material, { opacity: on ? 0.65 : 0.32, duration: 0.4, overwrite: "auto" });
      gsap.to(travelerHalo.scale, { x: on ? 1.5 : 1, y: on ? 1.5 : 1, z: on ? 1.5 : 1, duration: 0.4, overwrite: "auto" });
    }
  };

  return { tweens, setBoost };
}

function zgAnimateArc(gsap, refs, reduced) {
  const { curve, beamMat, node, pulse, trail = [] } = refs;
  const progress = { t: reduced ? 0.52 : 0 };

  const place = () => {
    const p = curve.getPoint(progress.t);
    const tangent = curve.getTangent(progress.t).normalize();
    node.position.copy(p);
    pulse.position.copy(p);
    pulse.lookAt(p.clone().add(tangent));

    trail.forEach((ghost, i) => {
      const lagT = Math.max(0, progress.t - (i + 1) * 0.045);
      ghost.position.copy(curve.getPoint(lagT));
    });
  };

  place();
  if (reduced) {
    return { tweens: [], setBoost: () => {} };
  }

  const tweens = [
    gsap.to(progress, {
      t: 1,
      duration: 3.6,
      ease: "sine.inOut",
      yoyo: true,
      repeat: -1,
      onUpdate: place,
    }),
    gsap.to(beamMat, { emissiveIntensity: 2, duration: 1.8, ease: "sine.inOut", yoyo: true, repeat: -1 }),
    gsap.to(pulse.scale, { x: 1.35, y: 1.35, z: 1.35, duration: 1.8, ease: "sine.inOut", yoyo: true, repeat: -1 }),
    gsap.to(pulse.material, { opacity: 0.12, duration: 1.8, ease: "sine.inOut", yoyo: true, repeat: -1 }),
  ];

  const setBoost = (on) => {
    zgScaleTweens(tweens, on ? 2 : 1);
    gsap.to(beamMat, { emissiveIntensity: on ? 2.8 : 1.4, duration: 0.4, overwrite: "auto" });
    gsap.to(node.material, { emissiveIntensity: on ? 2.2 : 1.4, duration: 0.4, overwrite: "auto" });
    trail.forEach((ghost) => {
      const base = ghost.userData.baseOpacity ?? ghost.material.opacity;
      gsap.to(ghost.material, {
        opacity: on ? Math.min(0.85, base * 2.4) : base,
        duration: 0.4,
        overwrite: "auto",
      });
    });
  };

  return { tweens, setBoost };
}

function ProcessVisualSvg({ variant, uid }) {
  if (variant === "orb") {
    return (
      <div className="zg-process-svg-wrap">
        <svg className="zg-process-orb" viewBox="0 0 240 240" aria-hidden="true">
          <defs>
            <radialGradient id={`${uid}-orb`} cx="38%" cy="32%" r="68%">
              <stop offset="0%" stopColor="#ffb8a8" />
              <stop offset="35%" stopColor="#e94e3b" />
              <stop offset="72%" stopColor="#6b1f14" />
              <stop offset="100%" stopColor="#120806" />
            </radialGradient>
          </defs>
          <circle cx="120" cy="118" r="72" fill={`url(#${uid}-orb)`} className="zg-process-svg-orb" />
        </svg>
      </div>
    );
  }

  if (variant === "stack") {
    return (
      <div className="zg-process-svg-wrap zg-process-stack" aria-hidden="true">
        <svg viewBox="0 0 200 180" className="layer-3">
          <polygon points="100,24 176,148 24,148" fill="rgba(233,78,59,0.08)" stroke="rgba(233,78,59,0.45)" strokeWidth="1.5" />
        </svg>
        <svg viewBox="0 0 200 180" className="layer-2">
          <polygon points="100,34 166,142 34,142" fill="rgba(233,78,59,0.14)" stroke="rgba(255,255,255,0.22)" strokeWidth="1.5" />
        </svg>
        <svg viewBox="0 0 200 180" className="layer-1">
          <polygon points="100,44 156,136 44,136" fill="rgba(233,78,59,0.85)" />
        </svg>
      </div>
    );
  }

  if (variant === "grid") {
    const path = [0, 1, 2, 5, 8, 7, 6, 3, 4];
    const cellSize = 52;
    const gap = 16;
    const origin = 36;

    const cellCenter = (idx) => {
      const row = Math.floor(idx / 3);
      const col = idx % 3;
      return {
        x: origin + col * (cellSize + gap) + cellSize / 2,
        y: origin + row * (cellSize + gap) + cellSize / 2,
      };
    };

    const linePoints = path.map((idx) => {
      const { x, y } = cellCenter(idx);
      return `${x},${y}`;
    });

    return (
      <div className="zg-process-svg-wrap zg-process-grid" aria-hidden="true">
        <svg viewBox="0 0 240 240" aria-hidden="true">
          <polyline
            className="zg-process-grid-line"
            points={linePoints.join(" ")}
            fill="none"
            stroke="rgba(255,184,168,0.45)"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          />
          {Array.from({ length: 9 }, (_, idx) => {
            const row = Math.floor(idx / 3);
            const col = idx % 3;
            const pathIndex = path.indexOf(idx);
            const onPath = pathIndex >= 0;
            return (
              <rect
                key={idx}
                className={`zg-process-grid-cell${onPath ? " zg-process-grid-cell--path" : ""}`}
                x={origin + col * (cellSize + gap)}
                y={origin + row * (cellSize + gap)}
                width={cellSize}
                height={cellSize}
                rx="8"
                style={onPath ? { animationDelay: `${pathIndex * 0.48}s` } : undefined}
              />
            );
          })}
        </svg>
      </div>
    );
  }

  return (
    <svg className="zg-process-arc" viewBox="0 0 240 200" aria-hidden="true">
      <path d="M 36 148 Q 120 28 204 148" fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="2" />
      <path className="zg-process-arc-beam" d="M 36 148 Q 120 28 204 148" fill="none" stroke="#e94e3b" strokeWidth="3" strokeLinecap="round" />
      <circle className="zg-process-arc-node" cx="120" cy="52" r="10" fill="#e94e3b" />
    </svg>
  );
}

function ProcessCanvas({ variant, step, floorClass = "" }) {
  const mountRef = React.useRef(null);
  const mobile = useMobileLayout(900);
  const preferSvg = mobile || zgProcessReducedMotion();
  const [mode, setMode] = React.useState(() => (preferSvg ? "svg" : "pending"));

  React.useEffect(() => {
    if (preferSvg) {
      setMode("svg");
      return undefined;
    }

    const container = mountRef.current;
    if (!container) return undefined;

    let active = true;
    let teardown = () => {};

    const bootWebGL = async () => {
      try {
        if (!window.THREE && window.zgLoadThree) {
          await window.zgLoadThree();
        }
        if (!active || !window.THREE) {
          setMode("svg");
          return;
        }

        let sceneBundle = null;
        let meshRefs = null;
        let tweens = [];
        let setBoost = () => {};
        let rendering = true;
        let raf = 0;

        sceneBundle = zgCreateRenderer(container);
        const { THREE, scene, camera, renderer, lights } = sceneBundle;
        const gsap = window.gsap;
        const reduced = zgProcessReducedMotion();

        if (variant === "orb") meshRefs = zgBuildOrbScene(THREE, scene);
        else if (variant === "stack") meshRefs = zgBuildStackScene(THREE, scene);
        else if (variant === "grid") meshRefs = zgBuildGridScene(THREE, scene);
        else meshRefs = zgBuildArcScene(THREE, scene);

        if (gsap) {
          let bundle = null;
          if (variant === "orb") bundle = zgAnimateOrb(gsap, meshRefs, reduced);
          else if (variant === "stack") bundle = zgAnimateStack(gsap, meshRefs, reduced);
          else if (variant === "grid") bundle = zgAnimateGrid(gsap, meshRefs, reduced);
          else bundle = zgAnimateArc(gsap, meshRefs, reduced);
          tweens = bundle.tweens || [];
          setBoost = bundle.setBoost || (() => {});
        }

        const renderFrame = () => {
          if (!active || !rendering) return;
          raf = requestAnimationFrame(renderFrame);
          if (meshRefs.group && !gsap && !reduced) meshRefs.group.rotation.y += 0.004;
          if (lights.key) lights.key.position.x = 2 + Math.sin(Date.now() * 0.00035) * 0.35;
          renderer.render(scene, camera);
        };

        const resize = () => {
          const w = Math.max(container.clientWidth, 1);
          const h = Math.max(container.clientHeight, 1);
          camera.aspect = w / h;
          camera.updateProjectionMatrix();
          renderer.setPixelRatio(zgProcessPixelRatio());
          renderer.setSize(w, h);
        };

        const visibilityIo = new IntersectionObserver(
          ([entry]) => {
            rendering = entry.isIntersecting;
            if (rendering) renderFrame();
            else if (raf) {
              cancelAnimationFrame(raf);
              raf = 0;
            }
          },
          { threshold: 0.08 }
        );

        const ro = new ResizeObserver(resize);
        ro.observe(container);
        visibilityIo.observe(container);
        resize();
        renderFrame();
        setMode("webgl");

        const card = container.closest(".zg-process-card");
        const hoverSupported = window.matchMedia("(hover: hover)").matches;
        const onEnter = () => setBoost(true);
        const onLeave = () => setBoost(false);
        if (card && hoverSupported && !reduced) {
          card.addEventListener("pointerenter", onEnter);
          card.addEventListener("pointerleave", onLeave);
          card.addEventListener("focusin", onEnter);
          card.addEventListener("focusout", onLeave);
        }

        teardown = () => {
          active = false;
          rendering = false;
          cancelAnimationFrame(raf);
          visibilityIo.disconnect();
          ro.disconnect();
          if (card && hoverSupported && !reduced) {
            card.removeEventListener("pointerenter", onEnter);
            card.removeEventListener("pointerleave", onLeave);
            card.removeEventListener("focusin", onEnter);
            card.removeEventListener("focusout", onLeave);
          }
          tweens.forEach((t) => t.kill());
          renderer.dispose();
          container.querySelectorAll("canvas").forEach((c) => c.remove());
          scene.traverse((obj) => {
            if (obj.geometry) obj.geometry.dispose();
            if (obj.material) {
              if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
              else obj.material.dispose();
            }
          });
        };
      } catch (err) {
        console.warn("[ProcessWebGL] falling back to SVG:", err);
        if (active) setMode("svg");
      }
    };

    const io = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return;
        io.disconnect();
        bootWebGL();
      },
      { threshold: 0.05, rootMargin: "160px 0px" }
    );

    io.observe(container);

    return () => {
      active = false;
      io.disconnect();
      teardown();
    };
  }, [variant, preferSvg]);

  const uid = `zg-process-${step}`;

  return (
    <div className="zg-process-stage">
      <div className={`zg-process-floor ${floorClass}`.trim()} aria-hidden="true" />
      <div
        ref={mountRef}
        className={`zg-process-canvas-wrap${
          mode === "webgl" ? " zg-process-canvas-wrap--live" : " zg-process-canvas-wrap--idle"
        }`}
        aria-hidden="true"
      />
      {(mode === "svg" || mode === "pending") && (
        <ProcessVisualSvg variant={variant} uid={uid} />
      )}
    </div>
  );
}

function ProcessVisual({ variant, step }) {
  const floorClass =
    variant === "stack"
      ? "zg-process-floor--stack"
      : variant === "grid"
        ? "zg-process-floor--grid"
        : variant === "arc"
          ? "zg-process-floor--arc"
          : "";
  return <ProcessCanvas variant={variant} step={step} floorClass={floorClass} />;
}

window.ProcessCanvas = ProcessCanvas;
window.ProcessVisual = ProcessVisual;
