/* ===========================================================
   Motion graphics layer — premium tech-medical motion.
   All components export to window for use across babel scripts.
   Respects prefers-reduced-motion. Canvases pause off-screen.
   =========================================================== */
const { useState: useStateM, useEffect: useEffectM, useRef: useRefM } = React;

const REDUCE = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

/* ---------------------------------------------------------
   NeuralCanvas — drifting node/synapse field on <canvas>.
   props: base/glow (rgb strings), density, link, grid,
          speed, maxLink, parallax (0..1)
   --------------------------------------------------------- */
function NeuralCanvas({
  className = "", base = "20,120,135", glow = "52,226,216",
  density = 1, link = true, dots = true, grid = false,
  speed = 0.35, maxLink = 124, parallax = 0
}) {
  const ref = useRefM(null);
  useEffectM(() => {
    const canvas = ref.current;
    const parent = canvas.parentElement;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0, nodes = [], raf = 0, visible = true;
    let mx = 0, my = 0, tx = 0, ty = 0;

    function build() {
      const r = parent.getBoundingClientRect();
      W = Math.max(1, r.width); H = Math.max(1, r.height);
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + "px"; canvas.style.height = H + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const count = Math.max(8, Math.min(70, Math.round((W * H) / 15500 * density)));
      nodes = [];
      for (let i = 0; i < count; i++) {
        nodes.push({
          x: Math.random() * W, y: Math.random() * H,
          vx: (Math.random() - .5) * speed, vy: (Math.random() - .5) * speed,
          r: Math.random() * 1.5 + 1, c: Math.random() < 0.2
        });
      }
    }

    function draw(animate) {
      ctx.clearRect(0, 0, W, H);
      if (parallax) { tx += (mx - tx) * 0.06; ty += (my - ty) * 0.06; }
      ctx.save();
      if (parallax) ctx.translate(tx * parallax, ty * parallax);

      if (grid) {
        ctx.strokeStyle = `rgba(${base},0.06)`; ctx.lineWidth = 1;
        const g = 48;
        for (let x = (W % g) / 2; x < W; x += g) { ctx.beginPath(); ctx.moveTo(x, -40); ctx.lineTo(x, H + 40); ctx.stroke(); }
        for (let y = (H % g) / 2; y < H; y += g) { ctx.beginPath(); ctx.moveTo(-40, y); ctx.lineTo(W + 40, y); ctx.stroke(); }
      }

      if (animate) for (const n of nodes) {
        n.x += n.vx; n.y += n.vy;
        if (n.x < -24) n.x = W + 24; if (n.x > W + 24) n.x = -24;
        if (n.y < -24) n.y = H + 24; if (n.y > H + 24) n.y = -24;
      }

      if (link) for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
          const a = nodes[i], b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const d = Math.hypot(dx, dy);
          if (d < maxLink) {
            const al = (1 - d / maxLink) * 0.5;
            ctx.strokeStyle = (a.c && b.c) ? `rgba(${glow},${al})` : `rgba(${base},${al})`;
            ctx.lineWidth = 1;
            ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
          }
        }
      }

      if (dots) for (const n of nodes) {
        const col = n.c ? glow : base;
        ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, 6.2832);
        ctx.fillStyle = `rgba(${col},${n.c ? 0.95 : 0.55})`;
        if (n.c) { ctx.shadowColor = `rgba(${glow},0.9)`; ctx.shadowBlur = 9; }
        ctx.fill(); ctx.shadowBlur = 0;
      }
      ctx.restore();
    }

    function loop() { draw(true); raf = requestAnimationFrame(loop); }

    build();
    if (REDUCE) { draw(false); }
    else { raf = requestAnimationFrame(loop); }

    const ro = new ResizeObserver(() => { build(); if (REDUCE) draw(false); });
    ro.observe(parent);

    const io = new IntersectionObserver((es) => es.forEach((e) => {
      visible = e.isIntersecting;
      if (REDUCE) return;
      if (visible && !raf) raf = requestAnimationFrame(loop);
      else if (!visible && raf) { cancelAnimationFrame(raf); raf = 0; }
    }), { threshold: 0 });
    io.observe(parent);

    function onMove(e) {
      const r = parent.getBoundingClientRect();
      mx = (e.clientX - r.left) - W / 2;
      my = (e.clientY - r.top) - H / 2;
    }
    if (parallax && !REDUCE) window.addEventListener("pointermove", onMove);

    return () => {
      if (raf) cancelAnimationFrame(raf);
      ro.disconnect(); io.disconnect();
      if (parallax && !REDUCE) window.removeEventListener("pointermove", onMove);
    };
  }, []);
  return <canvas ref={ref} className={"neural-canvas " + className} aria-hidden="true"></canvas>;
}

/* ---------------------------------------------------------
   EKG — heartbeat line with a glowing pulse travelling across.
   --------------------------------------------------------- */
const EKG_PATH =
  "M0 40 H150 l12 0 l6 -4 l5 8 l7 -34 l8 56 l7 -30 l6 4 l10 0 " +
  "H450 l12 0 l6 -4 l5 8 l7 -34 l8 56 l7 -30 l6 4 l10 0 " +
  "H750 l12 0 l6 -4 l5 8 l7 -34 l8 56 l7 -30 l6 4 l10 0 " +
  "H1050 l12 0 l6 -4 l5 8 l7 -34 l8 56 l7 -30 l6 4 l10 0 H1200";

function EKG({ className = "" }) {
  const ref = useRefM(null);
  useEffectM(() => {
    if (REDUCE) return;
    const path = ref.current;
    let len = 2600;
    try { len = path.getTotalLength(); } catch (e) {}
    path.style.strokeDasharray = `170 ${len}`;
    const anim = path.animate(
      [{ strokeDashoffset: len + 170 }, { strokeDashoffset: -10 }],
      { duration: 3800, iterations: Infinity, easing: "linear" }
    );
    return () => anim.cancel();
  }, []);
  return (
    <svg className={"ekg " + className} viewBox="0 0 1200 80" preserveAspectRatio="none" aria-hidden="true">
      <path className="base" d={EKG_PATH} />
      <path className="pulse" ref={ref} d={EKG_PATH} />
    </svg>
  );
}

/* ---------------------------------------------------------
   Counter — counts up to a numeric value when scrolled into view.
   Keeps any suffix (+, ★, M+, etc.) and comma / decimal format.
   --------------------------------------------------------- */
function Counter({ value, className = "" }) {
  const ref = useRefM(null);
  useEffectM(() => {
    const el = ref.current;
    const m = String(value).match(/^([\d.,]+)(.*)$/);
    if (!m) { el.textContent = value; return; }
    const raw = m[1], suffix = m[2];
    const hasComma = raw.includes(",");
    const decimals = (raw.split(".")[1] || "").length;
    const target = parseFloat(raw.replace(/,/g, ""));
    const fmt = (v) => {
      let out = decimals ? v.toFixed(decimals) : Math.round(v).toString();
      if (hasComma) out = Number(out).toLocaleString("en-US");
      return out + suffix;
    };
    if (REDUCE) { el.textContent = fmt(target); return; }
    el.textContent = fmt(0);
    let done = false;
    const io = new IntersectionObserver((es) => es.forEach((e) => {
      if (!e.isIntersecting || done) return;
      done = true; io.unobserve(e.target);
      el.classList.add("counting");
      const dur = 1600, t0 = performance.now();
      const tick = (now) => {
        const p = Math.min(1, (now - t0) / dur);
        const ease = 1 - Math.pow(1 - p, 3);
        el.textContent = fmt(target * ease);
        if (p < 1) requestAnimationFrame(tick);
        else el.classList.remove("counting");
      };
      requestAnimationFrame(tick);
    }), { threshold: 0.5 });
    io.observe(el);
    return () => io.disconnect();
  }, [value]);
  return <b ref={ref} className={className}>{value}</b>;
}

/* ---------------------------------------------------------
   SpineGraphic — stylised spinal column, geometric primitives,
   with a glowing pulse running down the rail.
   --------------------------------------------------------- */
function SpineGraphic({ className = "" }) {
  const discs = Array.from({ length: 8 });
  return (
    <svg className={"spine-gfx " + className} viewBox="0 0 120 360" fill="none" aria-hidden="true">
      <line className="spine-rail" x1="60" y1="14" x2="60" y2="346" />
      {discs.map((_, i) => {
        const y = 30 + i * 42;
        return <ellipse key={i} className="spine-disc" cx="60" cy={y} rx="26" ry="9"
          style={{ animationDelay: (i * 0.2) + "s" }} />;
      })}
      <circle className={"spine-dot" + (REDUCE ? "" : " run")} cx="60" cy="30" r="5" />
    </svg>
  );
}

/* ---------------------------------------------------------
   Global FX init (runs once on load, survives re-renders):
   - card spotlight: tracks pointer -> --mx/--my on hovered card
   - hero parallax: translates [data-parallax] on scroll
   --------------------------------------------------------- */
(function initMotionFX() {
  if (window.__motionFX) return;
  window.__motionFX = true;
  const SEL = ".card,.cond-card,.treat-card,.why-card,.quote,.res-card,.btn";
  document.addEventListener("pointermove", (e) => {
    const el = e.target.closest && e.target.closest(SEL);
    if (!el) return;
    const r = el.getBoundingClientRect();
    el.style.setProperty("--mx", ((e.clientX - r.left) / r.width * 100) + "%");
    el.style.setProperty("--my", ((e.clientY - r.top) / r.height * 100) + "%");
  }, { passive: true });

  if (!REDUCE) {
    let ticking = false;
    const update = () => {
      const y = window.scrollY || 0;
      document.querySelectorAll("[data-parallax]").forEach((el) => {
        const f = parseFloat(el.dataset.parallax) || 0;
        el.style.transform = `translate3d(0, ${y * f}px, 0)`;
      });
      ticking = false;
    };
    window.addEventListener("scroll", () => {
      if (!ticking) { requestAnimationFrame(update); ticking = true; }
    }, { passive: true });
  }
})();

/* ---------------------------------------------------------
   Magnetic button wrapper — subtle pull toward the cursor.
   --------------------------------------------------------- */
function useMagnetic(ref, strength = 0.28) {
  useEffectM(() => {
    if (REDUCE) return;
    const el = ref.current; if (!el) return;
    const move = (e) => {
      const r = el.getBoundingClientRect();
      const dx = e.clientX - (r.left + r.width / 2);
      const dy = e.clientY - (r.top + r.height / 2);
      el.style.transform = `translate(${dx * strength}px, ${dy * strength}px)`;
    };
    const reset = () => { el.style.transform = ""; };
    el.addEventListener("pointermove", move);
    el.addEventListener("pointerleave", reset);
    return () => { el.removeEventListener("pointermove", move); el.removeEventListener("pointerleave", reset); };
  }, []);
}

Object.assign(window, { NeuralCanvas, EKG, Counter, SpineGraphic, useMagnetic, REDUCE });

/* ---------------------------------------------------------
   BrainSignals — neural signal field. Nodes are weighted to the
   top (the "brain"); faint wiring connects them; electrical
   pulses FIRE and cascade along edges. Periodic "courier" pulses
   travel downward across the field — signals dispatched from the
   brain to the rest of the body. Reads as firing neurons, not
   constellations.
   --------------------------------------------------------- */
function BrainSignals({ className = "", base = "20,120,135", glow = "52,226,216", density = 1, parallax = 0, line = 0.2, dotA = 0.5 }) {
  const ref = useRefM(null);
  useEffectM(() => {
    const canvas = ref.current;
    const parent = canvas.parentElement;
    const ctx = canvas.getContext("2d");
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0, nodes = [], edges = [], adj = [], pulses = [];
    let raf = 0, lastSeed = 0, lastCourier = 0, prev = 0, t0 = performance.now();
    let mx = 0, my = 0, tx = 0, ty = 0;

    function build() {
      const r = parent.getBoundingClientRect();
      W = Math.max(1, r.width); H = Math.max(1, r.height);
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + "px"; canvas.style.height = H + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const count = Math.max(24, Math.min(170, Math.round(W * H / 7000 * density)));
      nodes = [];
      for (let i = 0; i < count; i++) {
        const sy = 0.06 + Math.random() * 0.88;     // fill the visible field; firing still biased to top via .bias
        const x = Math.random() * W, y = sy * H;
        nodes.push({ hx: x, hy: y, x, y, ph: Math.random() * 6.28, amp: 5 + Math.random() * 9,
          sp: 0.25 + Math.random() * 0.4, r: Math.random() * 1.4 + 1.4, lit: 0, bias: 1 - sy });
      }
      edges = []; const seen = new Set();
      for (let i = 0; i < nodes.length; i++) {
        const ds = [];
        for (let j = 0; j < nodes.length; j++) if (j !== i) {
          const dx = nodes[i].hx - nodes[j].hx, dy = nodes[i].hy - nodes[j].hy;
          ds.push([dx * dx + dy * dy, j]);
        }
        ds.sort((a, b) => a[0] - b[0]);
        const k = 2 + (Math.random() < 0.5 ? 1 : 0);
        for (let n = 0; n < k && n < ds.length; n++) {
          const j = ds[n][1], key = i < j ? i + "_" + j : j + "_" + i;
          if (!seen.has(key)) { seen.add(key); edges.push({ a: i, b: j, len: Math.sqrt(ds[n][0]) }); }
        }
      }
      adj = nodes.map(() => []);
      edges.forEach((e, ei) => { adj[e.a].push(ei); adj[e.b].push(ei); });
      pulses = [];
    }

    function fire(idx, depth, excl) {
      nodes[idx].lit = 1;
      const es = (adj[idx] || []).filter(ei => ei !== excl);
      for (let i = es.length - 1; i > 0; i--) { const j = (Math.random() * (i + 1)) | 0; const t = es[i]; es[i] = es[j]; es[j] = t; }
      const fan = depth === 0 ? Math.min(3, es.length) : Math.min(2, es.length);
      let fired = 0;
      for (const ei of es) {
        if (fired >= fan || pulses.length >= 84) break;
        if (depth > 0 && Math.random() > 0.62) continue;
        const e = edges[ei], to = e.a === idx ? e.b : e.a;
        pulses.push({ ei, from: idx, to, t: 0, sp: (0.5 + Math.random() * 0.35) / Math.max(20, e.len), depth, courier: 0 });
        fired++;
      }
    }

    function courier(idx, hops, excl) {
      nodes[idx].lit = 1;
      if (hops <= 0 || pulses.length >= 90) return;
      const es = (adj[idx] || []).filter(ei => ei !== excl);
      let bestEi = -1, bestDy = -1e9;
      for (const ei of es) {
        const e = edges[ei], to = e.a === idx ? e.b : e.a;
        const dy = nodes[to].y - nodes[idx].y;
        if (dy > bestDy) { bestDy = dy; bestEi = ei; }
      }
      if (bestEi < 0) return;
      const e = edges[bestEi], to = e.a === idx ? e.b : e.a;
      pulses.push({ ei: bestEi, from: idx, to, t: 0, sp: (0.42 + Math.random() * 0.2) / Math.max(20, e.len), depth: 0, courier: hops });
    }

    function frame(now) {
      const dt = Math.min(42, now - (prev || now)); prev = now;
      const tt = (now - t0) / 1000;
      ctx.clearRect(0, 0, W, H);
      for (const n of nodes) {
        n.x = n.hx + Math.cos(tt * n.sp + n.ph) * n.amp;
        n.y = n.hy + Math.sin(tt * n.sp + n.ph) * n.amp * 0.7;
        if (n.lit > 0) n.lit = Math.max(0, n.lit - dt / 680);
      }
      if (parallax) { tx += (mx - tx) * 0.06; ty += (my - ty) * 0.06; }
      ctx.save(); if (parallax) ctx.translate(tx * parallax, ty * parallax);

      ctx.lineWidth = 1; ctx.strokeStyle = `rgba(${base},${line})`;
      for (const e of edges) { const a = nodes[e.a], b = nodes[e.b]; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); }

      if (now - lastSeed > 440 && pulses.length < 46) {
        lastSeed = now;
        let pick = 0, bs = -1;
        for (let q = 0; q < 6; q++) { const idx = (Math.random() * nodes.length) | 0; const s = nodes[idx].bias * Math.random(); if (s > bs) { bs = s; pick = idx; } }
        fire(pick, 0, -1);
      }
      if (now - lastCourier > 1300 && pulses.length < 60) {
        lastCourier = now;
        let pick = 0, by = 1e9;
        for (let q = 0; q < 6; q++) { const idx = (Math.random() * nodes.length) | 0; if (nodes[idx].y < by) { by = nodes[idx].y; pick = idx; } }
        courier(pick, 7, -1);
      }

      const next = [];
      for (const p of pulses) {
        p.t += p.sp * dt;
        const e = edges[p.ei], from = nodes[p.from], to = nodes[p.to];
        const tc = p.t < 1 ? p.t : 1;
        const px = from.x + (to.x - from.x) * tc, py = from.y + (to.y - from.y) * tc;
        const big = p.courier > 0;
        const bx = from.x + (to.x - from.x) * Math.max(0, tc - 0.16);
        const by2 = from.y + (to.y - from.y) * Math.max(0, tc - 0.16);
        const grad = ctx.createLinearGradient(bx, by2, px, py);
        grad.addColorStop(0, `rgba(${glow},0)`); grad.addColorStop(1, `rgba(${glow},${big ? 0.7 : 0.5})`);
        ctx.strokeStyle = grad; ctx.lineWidth = big ? 2.2 : 1.6;
        ctx.beginPath(); ctx.moveTo(bx, by2); ctx.lineTo(px, py); ctx.stroke();
        ctx.beginPath(); ctx.arc(px, py, (big ? 2.8 : 2.1) + 1.4, 0, 6.2832);
        ctx.fillStyle = `rgba(${base},0.55)`; ctx.fill();
        ctx.beginPath(); ctx.arc(px, py, big ? 2.8 : 2.1, 0, 6.2832);
        ctx.fillStyle = `rgba(${glow},0.98)`; ctx.shadowColor = `rgba(${glow},0.95)`; ctx.shadowBlur = big ? 14 : 10;
        ctx.fill(); ctx.shadowBlur = 0;
        if (p.t >= 1) {
          if (p.courier > 0) courier(p.to, p.courier - 1, p.ei);
          else if (p.depth < 5 && Math.random() < 0.55) fire(p.to, p.depth + 1, p.ei);
          else nodes[p.to].lit = 1;
        } else next.push(p);
      }
      pulses = next;

      for (const n of nodes) {
        const lit = n.lit, col = lit > 0.06 ? glow : base;
        ctx.beginPath(); ctx.arc(n.x, n.y, n.r + lit * 1.7, 0, 6.2832);
        ctx.fillStyle = `rgba(${col},${Math.min(1, dotA + lit * 0.55)})`;
        if (lit > 0.06) { ctx.shadowColor = `rgba(${glow},${lit})`; ctx.shadowBlur = 13 * lit; }
        ctx.fill(); ctx.shadowBlur = 0;
      }
      ctx.restore();
      raf = requestAnimationFrame(frame);
    }

    function staticDraw() {
      ctx.clearRect(0, 0, W, H);
      ctx.lineWidth = 1; ctx.strokeStyle = `rgba(${base},0.16)`;
      for (const e of edges) { const a = nodes[e.a], b = nodes[e.b]; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); }
      for (const n of nodes) { ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, 6.2832); ctx.fillStyle = `rgba(${base},0.6)`; ctx.fill(); }
    }

    build();
    if (REDUCE) staticDraw(); else raf = requestAnimationFrame(frame);
    const ro = new ResizeObserver(() => { build(); if (REDUCE) staticDraw(); });
    ro.observe(parent);
    const io = new IntersectionObserver((es) => es.forEach((e) => {
      if (REDUCE) return;
      if (e.isIntersecting && !raf) { prev = 0; raf = requestAnimationFrame(frame); }
      else if (!e.isIntersecting && raf) { cancelAnimationFrame(raf); raf = 0; }
    }), { threshold: 0 });
    io.observe(parent);
    function onMove(e) { const r = parent.getBoundingClientRect(); mx = (e.clientX - r.left) - W / 2; my = (e.clientY - r.top) - H / 2; }
    if (parallax && !REDUCE) window.addEventListener("pointermove", onMove);

    return () => { if (raf) cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); if (parallax && !REDUCE) window.removeEventListener("pointermove", onMove); };
  }, []);
  return <canvas ref={ref} className={"neural-canvas " + className} aria-hidden="true"></canvas>;
}

Object.assign(window, { BrainSignals });

/* ---------------------------------------------------------
   BrainSpine — a brain-and-spine DIAGRAM that fires electrical
   signals. A glowing brain (node mesh in a brain silhouette)
   sits at the top; the spinal cord descends with peripheral
   nerve branches. The brain fires ambient activity, then sends
   bright signal pulses down the spine and out to the body.
   Sized to its parent (built for the dark doctor card).
   --------------------------------------------------------- */
function BrainSpine({ className = "", base = "150,226,216", glow = "94,240,228" }) {
  const ref = useRefM(null);
  useEffectM(() => {
    const canvas = ref.current, parent = canvas.parentElement, ctx = canvas.getContext("2d");
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    let W = 0, H = 0, raf = 0, prev = 0, t0 = performance.now();
    let N = [], E = [], brainAdj = [], brainIdx = [], spine = [], offBy = {};
    let bpulses = [], sigs = [], lastFire = 0, lastSig = 0;
    const D = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
    const LOBES = [[0, 0, 1.0], [-0.5, -0.42, 0.56], [0.5, -0.42, 0.56], [-0.74, -0.04, 0.5],
      [0.74, -0.04, 0.5], [-0.44, 0.42, 0.5], [0.44, 0.42, 0.5], [0, -0.64, 0.56], [0, 0.5, 0.5]];
    const inside = (nx, ny) => { for (const [lx, ly, lr] of LOBES) { const dx = nx - lx, dy = ny - ly; if (dx * dx + dy * dy < lr * lr) return true; } return false; };
    const mkNode = (x, y, o = {}) => { const i = N.length; N.push(Object.assign({ x, y, hx: x, hy: y, ph: Math.random() * 6.28, amp: 1.6, sp: 0.3 + Math.random() * 0.3, r: 1.5, lit: 0, brain: false, vert: false }, o)); return i; };

    function build() {
      const r = parent.getBoundingClientRect();
      W = Math.max(1, r.width); H = Math.max(1, r.height);
      canvas.width = W * dpr; canvas.height = H * dpr;
      canvas.style.width = W + "px"; canvas.style.height = H + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      N = []; E = []; brainIdx = []; spine = []; offBy = {}; bpulses = []; sigs = [];

      const cx = W * 0.5, cy = H * 0.23;
      const sx = Math.min(W * 0.34, 158), sy = sx * 0.82;
      const target = Math.round(Math.min(70, Math.max(40, sx * 0.34)));
      let tries = 0;
      while (brainIdx.length < target && tries < target * 70) {
        tries++;
        const nx = (Math.random() * 2 - 1) * 1.04, ny = (Math.random() * 2 - 1) * 1.04;
        if (!inside(nx, ny)) continue;
        if (Math.abs(nx) < 0.05 && Math.random() < 0.72) continue;        // central fissure
        brainIdx.push(mkNode(cx + nx * sx, cy + ny * sy, { amp: 2 + Math.random() * 3.5, r: Math.random() * 1.2 + 1.3, brain: true }));
      }
      // brain mesh (short k-nearest edges)
      const seen = new Set(), maxD = sx * 0.46;
      for (const i of brainIdx) {
        const ds = [];
        for (const j of brainIdx) if (j !== i) { const dx = N[i].x - N[j].x, dy = N[i].y - N[j].y; ds.push([dx * dx + dy * dy, j]); }
        ds.sort((a, b) => a[0] - b[0]);
        let k = 0;
        for (const [d2, j] of ds) { if (k >= 3) break; if (d2 > maxD * maxD) break; const key = i < j ? i + "_" + j : j + "_" + i; if (seen.has(key)) continue; seen.add(key); E.push({ a: i, b: j, w: 1 }); k++; }
      }
      brainAdj = N.map(() => []);
      E.forEach((e, ei) => { if (N[e.a].brain && N[e.b].brain) { brainAdj[e.a].push(ei); brainAdj[e.b].push(ei); } });

      // spinal cord
      let bn = brainIdx[0], best = -1e9;
      for (const i of brainIdx) { const s = N[i].y - Math.abs(N[i].x - cx) * 0.6; if (s > best) { best = s; bn = i; } }
      const topY = cy + sy * 0.9, botY = H * 0.63, nS = 7;
      for (let s = 0; s < nS; s++) {
        const tt = s / (nS - 1);
        const x = cx + Math.sin(tt * Math.PI * 1.4) * W * 0.04;
        const y = topY + (botY - topY) * tt;
        const idx = mkNode(x, y, { r: 1.7, vert: true });
        spine.push(idx);
        E.push({ a: s === 0 ? bn : spine[s - 1], b: idx, w: 2 });
      }
      // peripheral nerve branches
      for (let s = 1; s < nS - 1; s += 2) {
        offBy[spine[s]] = [];
        for (const side of [-1, 1]) {
          let from = spine[s]; const chain = []; const segs = 2;
          for (let k = 1; k <= segs; k++) {
            const x = N[spine[s]].x + side * (k / segs) * W * 0.34;
            const y = N[spine[s]].y + (k / segs) * H * 0.07;
            const idx = mkNode(x, y, { r: 1.3 });
            E.push({ a: from, b: idx, w: 1 }); from = idx; chain.push(idx);
          }
          offBy[spine[s]].push(chain);
        }
      }
    }

    function fire(idx, depth, excl) {
      N[idx].lit = 1;
      const es = (brainAdj[idx] || []).filter(e => e !== excl);
      for (let i = es.length - 1; i > 0; i--) { const j = (Math.random() * (i + 1)) | 0; const t = es[i]; es[i] = es[j]; es[j] = t; }
      const fan = depth === 0 ? 3 : 2; let f = 0;
      for (const ei of es) {
        if (f >= fan || bpulses.length >= 60) break;
        if (depth > 0 && Math.random() > 0.6) continue;
        const e = E[ei], to = e.a === idx ? e.b : e.a;
        bpulses.push({ ei, from: idx, to, t: 0, sp: (0.5 + Math.random() * 0.4) / Math.max(14, D(N[e.a], N[e.b])), depth }); f++;
      }
    }

    function dispatch() {
      let bn = brainIdx[0], best = -1e9;
      for (const i of brainIdx) { const s = N[i].y - Math.abs(N[i].x - W * 0.5) * 0.6; if (s > best) { best = s; bn = i; } }
      sigs.push({ seq: [bn].concat(spine), i: 0, t: 0, off: false });
      for (let q = 0; q < 4; q++) N[brainIdx[(Math.random() * brainIdx.length) | 0]].lit = 1;
    }

    function advance(s, dt) {
      let rem = 0.5 * dt;            // px/ms
      while (rem > 0 && s.i < s.seq.length - 1) {
        const a = N[s.seq[s.i]], b = N[s.seq[s.i + 1]], seg = Math.max(1, D(a, b));
        const step = rem / seg;
        if (s.t + step >= 1) {
          rem -= (1 - s.t) * seg; s.i++; s.t = 0;
          const node = s.seq[s.i]; N[node].lit = 1;
          if (!s.off && offBy[node] && Math.random() < 0.6) {
            for (const ch of offBy[node]) sigs.push({ seq: [node].concat(ch), i: 0, t: 0, off: true });
          }
        } else { s.t += step; rem = 0; }
      }
    }

    function frame(now) {
      const dt = Math.min(42, now - (prev || now)); prev = now;
      const tt = (now - t0) / 1000;
      ctx.clearRect(0, 0, W, H);
      for (const n of N) { n.x = n.hx + Math.cos(tt * n.sp + n.ph) * n.amp; n.y = n.hy + Math.sin(tt * n.sp + n.ph) * n.amp * 0.7; if (n.lit > 0) n.lit = Math.max(0, n.lit - dt / 700); }

      // vertebra discs (behind edges)
      for (const i of spine) { const n = N[i]; ctx.beginPath(); ctx.ellipse(n.x, n.y, 8.5, 3.6, 0, 0, 6.2832); ctx.strokeStyle = `rgba(${base},0.5)`; ctx.lineWidth = 1.8; ctx.stroke(); }
      // edges
      for (const e of E) { const A = N[e.a], B = N[e.b]; ctx.strokeStyle = `rgba(${base},${e.w > 1 ? 0.62 : 0.5})`; ctx.lineWidth = e.w > 1 ? 2.2 : 1.5; ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(B.x, B.y); ctx.stroke(); }

      if (now - lastFire > 440 && bpulses.length < 26) { lastFire = now; fire(brainIdx[(Math.random() * brainIdx.length) | 0], 0, -1); }
      if (now - lastSig > 1500) { lastSig = now; dispatch(); }

      // brain ambient pulses
      const nb = [];
      for (const p of bpulses) {
        p.t += p.sp * dt; const e = E[p.ei], F = N[p.from], T = N[p.to], tc = p.t < 1 ? p.t : 1;
        const px = F.x + (T.x - F.x) * tc, py = F.y + (T.y - F.y) * tc;
        ctx.beginPath(); ctx.arc(px, py, 2.4, 0, 6.2832); ctx.fillStyle = `rgba(${base},0.45)`; ctx.fill();
        ctx.beginPath(); ctx.arc(px, py, 1.7, 0, 6.2832); ctx.fillStyle = `rgba(${glow},0.95)`; ctx.shadowColor = `rgba(${glow},0.9)`; ctx.shadowBlur = 8; ctx.fill(); ctx.shadowBlur = 0;
        if (p.t >= 1) { if (p.depth < 4 && Math.random() < 0.5) fire(p.to, p.depth + 1, p.ei); else N[p.to].lit = 1; } else nb.push(p);
      }
      bpulses = nb;

      // signal couriers (down spine / out nerves)
      const ns = [];
      for (const s of sigs) {
        advance(s, dt);
        const a = N[s.seq[s.i]], b = N[s.seq[Math.min(s.i + 1, s.seq.length - 1)]], tc = Math.min(1, s.t);
        const px = a.x + (b.x - a.x) * tc, py = a.y + (b.y - a.y) * tc;
        const bx = a.x + (b.x - a.x) * Math.max(0, tc - 0.5), by = a.y + (b.y - a.y) * Math.max(0, tc - 0.5);
        const grad = ctx.createLinearGradient(bx, by, px, py); grad.addColorStop(0, `rgba(${glow},0)`); grad.addColorStop(1, `rgba(${glow},${s.off ? 0.6 : 0.85})`);
        ctx.strokeStyle = grad; ctx.lineWidth = s.off ? 2 : 3; ctx.beginPath(); ctx.moveTo(bx, by); ctx.lineTo(px, py); ctx.stroke();
        const rr = s.off ? 2.1 : 2.8;
        ctx.beginPath(); ctx.arc(px, py, rr + 1.3, 0, 6.2832); ctx.fillStyle = `rgba(${base},0.45)`; ctx.fill();
        ctx.beginPath(); ctx.arc(px, py, rr, 0, 6.2832); ctx.fillStyle = `rgba(${glow},1)`; ctx.shadowColor = `rgba(${glow},1)`; ctx.shadowBlur = s.off ? 12 : 16; ctx.fill(); ctx.shadowBlur = 0;
        if (s.i < s.seq.length - 1) ns.push(s);
      }
      sigs = ns;

      // nodes
      for (const n of N) {
        const lit = n.lit, col = lit > 0.06 ? glow : base;
        ctx.beginPath(); ctx.arc(n.x, n.y, n.r + 0.5 + lit * 1.6, 0, 6.2832);
        ctx.fillStyle = `rgba(${col},${Math.min(1, 0.78 + lit * 0.22)})`;
        ctx.shadowColor = `rgba(${glow},${0.45 + lit * 0.5})`; ctx.shadowBlur = 4 + 11 * lit;
        ctx.fill(); ctx.shadowBlur = 0;
      }
      raf = requestAnimationFrame(frame);
    }

    function staticDraw() {
      ctx.clearRect(0, 0, W, H);
      for (const i of spine) { const n = N[i]; ctx.beginPath(); ctx.ellipse(n.x, n.y, 7, 3, 0, 0, 6.2832); ctx.strokeStyle = `rgba(${base},0.32)`; ctx.lineWidth = 1.2; ctx.stroke(); }
      for (const e of E) { const A = N[e.a], B = N[e.b]; ctx.strokeStyle = `rgba(${base},${e.w > 1 ? 0.5 : 0.34})`; ctx.lineWidth = e.w > 1 ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(B.x, B.y); ctx.stroke(); }
      for (const n of N) { ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, 6.2832); ctx.fillStyle = `rgba(${base},0.6)`; ctx.fill(); }
    }

    build();
    if (REDUCE) staticDraw(); else raf = requestAnimationFrame(frame);
    const ro = new ResizeObserver(() => { build(); if (REDUCE) staticDraw(); });
    ro.observe(parent);
    const io = new IntersectionObserver((es) => es.forEach((e) => {
      if (REDUCE) return;
      if (e.isIntersecting && !raf) { prev = 0; raf = requestAnimationFrame(frame); }
      else if (!e.isIntersecting && raf) { cancelAnimationFrame(raf); raf = 0; }
    }), { threshold: 0 });
    io.observe(parent);
    return () => { if (raf) cancelAnimationFrame(raf); ro.disconnect(); io.disconnect(); };
  }, []);
  return <canvas ref={ref} className={"neural-canvas " + className} aria-hidden="true"></canvas>;
}

Object.assign(window, { BrainSpine });
