// app.jsx — XubeAi Neural-Glass landing page
// React 18 + Babel inline. Tweaks persisted via the host EDITMODE block.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── Tweak defaults ───────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#FF5F03",
  "shaderIntensity": 1.0,
  "shaderSpeed": 1.0,
  "cursorEffect": true,
  "engineWidget": true
}/*EDITMODE-END*/;

const ACCENT_GLOW = {
  "#FF5F03": "rgba(255, 95, 3, 0.55)",
  "#8B5CF6": "rgba(139, 92, 246, 0.55)",
  "#22D3EE": "rgba(34, 211, 238, 0.55)",
  "#A3E635": "rgba(163, 230, 53, 0.55)",
};
const ACCENT_SOFT = {
  "#FF5F03": "rgba(255, 95, 3, 0.12)",
  "#8B5CF6": "rgba(139, 92, 246, 0.12)",
  "#22D3EE": "rgba(34, 211, 238, 0.12)",
  "#A3E635": "rgba(163, 230, 53, 0.12)",
};

// ── Custom cursor ────────────────────────────────────────────────────────
function Cursor({ enabled }) {
  const dotRef = useRef(null);
  const ringRef = useRef(null);
  const target = useRef({ x: 0, y: 0 });
  const pos = useRef({ x: 0, y: 0 });
  const ringPos = useRef({ x: 0, y: 0 });

  useEffect(() => {
    if (!enabled) {
      document.body.style.cursor = "auto";
      return;
    }
    document.body.style.cursor = "none";
    const move = (e) => {
      target.current = { x: e.clientX, y: e.clientY };
    };
    const over = (e) => {
      const hover = e.target.closest("a, button, .case, .cap-card, [data-hover]");
      if (dotRef.current) dotRef.current.classList.toggle("is-hover", !!hover);
      if (ringRef.current) ringRef.current.classList.toggle("is-hover", !!hover);
    };
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseover", over);
    let raf;
    const tick = () => {
      pos.current.x += (target.current.x - pos.current.x) * 0.35;
      pos.current.y += (target.current.y - pos.current.y) * 0.35;
      ringPos.current.x += (target.current.x - ringPos.current.x) * 0.14;
      ringPos.current.y += (target.current.y - ringPos.current.y) * 0.14;
      if (dotRef.current) {
        dotRef.current.style.transform = `translate(${pos.current.x}px, ${pos.current.y}px) translate(-50%, -50%)`;
      }
      if (ringRef.current) {
        ringRef.current.style.transform = `translate(${ringPos.current.x}px, ${ringPos.current.y}px) translate(-50%, -50%)`;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => {
      window.removeEventListener("mousemove", move);
      window.removeEventListener("mouseover", over);
      cancelAnimationFrame(raf);
      document.body.style.cursor = "auto";
    };
  }, [enabled]);

  if (!enabled) return null;
  return (
    <>
      <div ref={ringRef} className="cursor-ring" />
      <div ref={dotRef} className="cursor-dot" />
    </>
  );
}

// ── BlurText ─────────────────────────────────────────────────────────────
function BlurText({ text, className, delay = 0, stagger = 0.06 }) {
  const words = text.split(" ");
  return (
    <span className={className}>
      {words.map((w, i) => (
        <span key={i} className="blur-word" style={{
          animationDelay: `${delay + i * stagger}s`,
          marginRight: "0.25em",
        }}>{w}</span>
      ))}
    </span>
  );
}

// ── RollText (sliding hover text) ────────────────────────────────────────
function RollText({ children, className }) {
  return (
    <span className={`roll-clip ${className || ""}`}>
      <span className="roll">
        <span>{children}</span>
        <span>{children}</span>
      </span>
    </span>
  );
}

// XCube — wireframe isometric cube. The X is formed by the cube's two
// visible space diagonals (corner-to-corner through the cube), highlighted
// against a faint wireframe so the X reads as structural, not pasted on.
function XCube({ size, className = "", style }) {
  return (
    <span className={`xcube ${className}`}
          style={{ width: size, height: size, ...style }}
          aria-hidden>
      <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
        {/* Faint face fills to give the wireframe a sense of depth */}
        <polygon points="20,30 50,15 80,30 50,45"
                 fill="var(--xcube-top, rgba(255,95,3,0.10))" />
        <polygon points="80,30 80,75 50,90 50,45"
                 fill="var(--xcube-right, rgba(0,0,0,0.42))" />
        <polygon points="20,30 20,75 50,90 50,45"
                 fill="var(--xcube-front, rgba(0,0,0,0.22))" />

        {/* Wireframe: outer hexagon silhouette + inner Y to the front-top corner */}
        <g fill="none" stroke="currentColor" strokeOpacity="0.38"
           strokeWidth="1.1" strokeLinejoin="round" strokeLinecap="round">
          <polygon points="20,30 50,15 80,30 80,75 50,90 20,75" />
          <line x1="50" y1="45" x2="20" y2="30" />
          <line x1="50" y1="45" x2="80" y2="30" />
          <line x1="50" y1="45" x2="50" y2="90" />
        </g>

        {/* Highlighted X — the cube's two visible space diagonals.
            (Outer drop-shadow on .xcube provides the glow halo.) */}
        <g stroke="currentColor" strokeLinecap="round" strokeWidth="2.6">
          <line x1="20" y1="30" x2="80" y2="75" />
          <line x1="80" y1="30" x2="20" y2="75" />
        </g>

        {/* Bright vertex dots at the four X tips for an "edge-anchored" feel */}
        <g fill="currentColor">
          <circle cx="20" cy="30" r="2" />
          <circle cx="80" cy="30" r="2" />
          <circle cx="20" cy="75" r="2" />
          <circle cx="80" cy="75" r="2" />
        </g>
      </svg>
    </span>
  );
}

// ── Arrow icon ───────────────────────────────────────────────────────────
const Arrow = ({ size = 14, className = "" }) => (
  <svg className={className} width={size} height={size} viewBox="0 0 14 14" fill="none" aria-hidden>
    <path d="M3.5 10.5L10.5 3.5M10.5 3.5H4.5M10.5 3.5V9.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);

// ── Navbar ───────────────────────────────────────────────────────────────
function Navbar({ onJoin }) {
  const links = [
    { href: "#automation", label: "AI Automation" },
    { href: "#marketing", label: "AI Marketing" },
    { href: "#services", label: "Services" },
    { href: "#work-projects", label: "Work" },
  ];
  return (
    <nav className="nav glass">
      <div className="nav-brand">
        <span className="nav-brand-mark" data-x-target>
          <XCube size="30px" />
        </span>
        <span className="nav-brand-name">XubeAi</span>
      </div>
      {links.map((l) => (
        <a key={l.label} href={l.href} className="nav-link">
          <RollText>{l.label}</RollText>
        </a>
      ))}
      <a href="#contact" className="nav-cta" data-hover onClick={(e)=>{e.preventDefault(); onJoin && onJoin();}}>
        Start a project <Arrow className="nav-cta-arrow" />
      </a>
    </nav>
  );
}

// ── Hero ─────────────────────────────────────────────────────────────────
function Hero({ onJoin }) {
  return (
    <section className="hero">
      <canvas id="shader-hero" />
      <div className="hero-vignette" />
      <div className="hero-grain" />
      <div className="hero-inner">
        <div className="hero-eyebrow glass">
          <span className="pulse" />
          AI AUTOMATION · MARKETING · SERVICES
        </div>
        <h1 className="headline">
          <span className="headline-row">
            <BlurText text="AI that" className="headline-serif" delay={0.1} />
          </span>
          <span className="headline-row">
            <BlurText text="does the" className="headline-main glow" delay={0.45} stagger={0.08} />
          </span>
          <span className="headline-row">
            <BlurText text="work." className="headline-main" delay={0.7} stagger={0.08} />
          </span>
        </h1>
        <p className="hero-sub">
          XubeAi is an AI studio. We <b style={{color:'var(--ink)',fontWeight:500}}>create &amp; post</b> your content,
          build <b style={{color:'var(--ink)',fontWeight:500}}>AI tools</b> for your business, and ship
          <b style={{color:'var(--ink)',fontWeight:500}}> SEO-ready websites</b> &mdash; run by a small, hands-on team.
        </p>
        <div className="practice-pills practice-pills-3">
          <a className="practice-pill glass" href="#automation" data-hover>
            <span className="practice-pill-num">/01</span>
            <span className="practice-pill-name">AI&nbsp;Automation</span>
            <span className="practice-pill-meta">agents · workflows · tools</span>
          </a>
          <a className="practice-pill glass" href="#marketing" data-hover>
            <span className="practice-pill-num">/02</span>
            <span className="practice-pill-name">AI&nbsp;Marketing</span>
            <span className="practice-pill-meta">content · posting · SEO</span>
          </a>
          <a className="practice-pill glass" href="#services" data-hover>
            <span className="practice-pill-num">/03</span>
            <span className="practice-pill-name">Services</span>
            <span className="practice-pill-meta">websites · software · data</span>
          </a>
        </div>
        <div className="hero-actions">
          <button className="btn-primary" data-hover onClick={onJoin}>
            <RollText>Start a project</RollText>
            <Arrow size={14} className="btn-primary-arrow" />
          </button>
          <a className="btn-ghost" href="#automation" data-hover>
            <span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%",
              background: "var(--accent)", boxShadow: "0 0 8px var(--accent-glow)" }} />
            See what we do
          </a>
        </div>
      </div>
      <div className="scroll-cue">
        <span>Scroll</span>
        <span className="scroll-cue-line" />
      </div>
    </section>
  );
}

// ── Engine Pulse widget (floating dashboard) ─────────────────────────────
function EnginePulse() {
  const [bars, setBars] = useState(() => Array.from({ length: 28 }, () => Math.random() * 0.7 + 0.2));
  const [tokens, setTokens] = useState(143820);
  const [reqs, setReqs] = useState(0);
  const [latency, setLatency] = useState(42);

  useEffect(() => {
    const id = setInterval(() => {
      setBars((b) => {
        const next = b.slice(1);
        next.push(Math.random() * 0.85 + 0.15);
        return next;
      });
      setTokens((t) => t + Math.floor(Math.random() * 280));
      setReqs((r) => r + Math.floor(Math.random() * 4));
      setLatency(() => 36 + Math.floor(Math.random() * 18));
    }, 900);
    return () => clearInterval(id);
  }, []);

  return (
    <div className="engine-pulse glass-strong">
      <div className="engine-pulse-hd">
        <div className="engine-pulse-hd-l">
          <span className="dot" />
          Studio Pulse
        </div>
        <div className="engine-pulse-hd-r">{new Date().toISOString().slice(11, 19)}Z</div>
      </div>
      <div className="engine-pulse-row">
        <span className="k">tokens/sec</span>
        <span className="v">{tokens.toLocaleString()}</span>
      </div>
      <div className="engine-pulse-row">
        <span className="k">automations live</span>
        <span className="v">2,148 <span style={{ color: "var(--accent)" }}>↑</span></span>
      </div>
      <div className="engine-pulse-row">
        <span className="k">jobs in queue</span>
        <span className="v">37 <span style={{ color: "var(--accent)" }}>↑</span></span>
      </div>
      <div className="engine-pulse-row">
        <span className="k">p95 latency</span>
        <span className="v">{latency}ms</span>
      </div>
      <div className="engine-pulse-bar">
        {bars.map((b, i) => (
          <i key={i} style={{ height: `${b * 100}%` }} />
        ))}
      </div>
    </div>
  );
}

// ── Capability card visuals (canvas-based "video" stand-ins) ─────────────
function NodeGraphCanvas({ active, accent }) {
  const ref = useRef(null);
  const stateRef = useRef({ nodes: [], edges: [], t: 0 });
  useEffect(() => {
    const c = ref.current;
    if (!c) return;
    const ctx = c.getContext("2d");
    const N = 22;
    const nodes = Array.from({ length: N }, () => ({
      x: Math.random(), y: Math.random(),
      vx: (Math.random() - 0.5) * 0.0008,
      vy: (Math.random() - 0.5) * 0.0008,
      p: Math.random(),
    }));
    stateRef.current.nodes = nodes;

    let raf, w, h;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(c);

    const draw = () => {
      stateRef.current.t += active ? 0.012 : 0.002;
      const t = stateRef.current.t;
      ctx.clearRect(0, 0, w, h);
      // edges
      ctx.lineWidth = 0.6;
      for (let i = 0; i < N; i++) {
        for (let j = i + 1; j < N; j++) {
          const a = nodes[i], b = nodes[j];
          const dx = (a.x - b.x) * w, dy = (a.y - b.y) * h;
          const d = Math.hypot(dx, dy);
          if (d < 120) {
            const o = (1 - d / 120) * (active ? 0.35 : 0.15);
            ctx.strokeStyle = `rgba(255,255,255,${o})`;
            ctx.beginPath();
            ctx.moveTo(a.x * w, a.y * h);
            ctx.lineTo(b.x * w, b.y * h);
            ctx.stroke();
          }
        }
      }
      // nodes
      for (let i = 0; i < N; i++) {
        const n = nodes[i];
        n.x += n.vx; n.y += n.vy;
        if (n.x < 0 || n.x > 1) n.vx *= -1;
        if (n.y < 0 || n.y > 1) n.vy *= -1;
        const pulse = (Math.sin(t * 2 + n.p * 6.28) + 1) / 2;
        const r = 2 + pulse * (active ? 3 : 1.2);
        ctx.fillStyle = active
          ? `rgba(255,255,255,${0.65 + pulse * 0.35})`
          : `rgba(255,255,255,${0.35 + pulse * 0.2})`;
        ctx.beginPath();
        ctx.arc(n.x * w, n.y * h, r, 0, Math.PI * 2);
        ctx.fill();
        if (active && pulse > 0.85) {
          ctx.fillStyle = accent;
          ctx.beginPath();
          ctx.arc(n.x * w, n.y * h, r * 2.2, 0, Math.PI * 2);
          ctx.fill();
        }
      }
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function CopyStreamCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let w, h, raf;
    const lines = [
      "headline_v01 → \"Sleep like the future.\"",
      "headline_v02 → \"Built for restless minds.\"",
      "headline_v03 → \"Engineered quiet.\"",
      "cta_v01 → \"Start your 100-night trial\"",
      "cta_v02 → \"Find your calibration\"",
      "subhead_v04 → \"Adaptive coil intelligence.\"",
      "subhead_v05 → \"Pressure-mapped for your body.\"",
      "tagline_v02 → \"Rest, redesigned.\"",
      "body_v07 → \"A mattress that learns…\"",
    ];
    let scroll = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const draw = () => {
      ctx.clearRect(0, 0, w, h);
      scroll += active ? 0.6 : 0.1;
      const lh = 22;
      const total = lines.length * lh;
      ctx.font = "11px 'JetBrains Mono', ui-monospace, monospace";
      for (let i = 0; i < lines.length * 3; i++) {
        const y = (i * lh - (scroll % total) + h * 0.18);
        if (y < -lh || y > h) continue;
        const txt = lines[i % lines.length];
        const colon = txt.indexOf("→");
        ctx.fillStyle = "rgba(255,255,255,.32)";
        ctx.fillText(txt.slice(0, colon), 22, y);
        ctx.fillStyle = accent;
        ctx.fillText("→", 22 + ctx.measureText(txt.slice(0, colon)).width + 2, y);
        ctx.fillStyle = "rgba(255,255,255,.88)";
        ctx.fillText(txt.slice(colon + 1), 22 + ctx.measureText(txt.slice(0, colon + 2)).width + 4, y);
      }
      // top/bottom fade
      const g1 = ctx.createLinearGradient(0, 0, 0, 90);
      g1.addColorStop(0, "rgba(15,15,18,1)");
      g1.addColorStop(1, "rgba(15,15,18,0)");
      ctx.fillStyle = g1; ctx.fillRect(0, 0, w, 90);
      const g2 = ctx.createLinearGradient(0, h - 90, 0, h);
      g2.addColorStop(0, "rgba(15,15,18,0)");
      g2.addColorStop(1, "rgba(15,15,18,1)");
      ctx.fillStyle = g2; ctx.fillRect(0, h - 90, w, 90);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function BidWaveCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const draw = () => {
      t += active ? 0.024 : 0.006;
      ctx.clearRect(0, 0, w, h);
      // grid
      ctx.strokeStyle = "rgba(255,255,255,.04)";
      ctx.lineWidth = 1;
      for (let x = 0; x <= w; x += 40) {
        ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
      }
      for (let y = 0; y <= h; y += 30) {
        ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
      }
      // three traces
      const traces = [
        { off: 0, amp: 0.18, freq: 0.012, col: "rgba(255,255,255,.32)" },
        { off: 1.6, amp: 0.13, freq: 0.018, col: "rgba(255,255,255,.55)" },
        { off: 3.2, amp: 0.22, freq: 0.009, col: accent },
      ];
      for (const tr of traces) {
        ctx.strokeStyle = tr.col;
        ctx.lineWidth = tr === traces[2] ? 1.6 : 1;
        ctx.beginPath();
        for (let x = 0; x <= w; x += 3) {
          const yn = 0.5
            + Math.sin(x * tr.freq + t * 1.4 + tr.off) * tr.amp
            + Math.sin(x * tr.freq * 2.3 + t * 0.7 + tr.off) * tr.amp * 0.45;
          if (x === 0) ctx.moveTo(x, yn * h);
          else ctx.lineTo(x, yn * h);
        }
        ctx.stroke();
        if (tr === traces[2]) {
          // glow
          ctx.shadowBlur = 12;
          ctx.shadowColor = accent;
          ctx.stroke();
          ctx.shadowBlur = 0;
        }
      }
      // bid markers
      const markers = Math.floor((Math.sin(t) + 1) * 3) + 2;
      for (let i = 0; i < markers; i++) {
        const x = ((t * 30 + i * 70) % (w + 80)) - 40;
        const y = h * 0.5 + Math.sin(x * 0.009 + t * 1.4 + 3.2) * h * 0.22;
        ctx.fillStyle = active ? accent : "rgba(255,255,255,.5)";
        ctx.beginPath(); ctx.arc(x, y, 3.5, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = "rgba(255,255,255,.8)";
        ctx.font = "9.5px 'JetBrains Mono', monospace";
        ctx.fillText(`$${(2.4 + i * 0.31).toFixed(2)}`, x + 8, y - 6);
      }
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function FunnelHeatmapCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const cols = 24, rows = 10;
    const cells = Array.from({ length: cols * rows }, (_, i) => ({
      phase: Math.random() * 6.28,
      base: 0.3 + Math.random() * 0.5,
      x: i % cols, y: Math.floor(i / cols),
    }));
    const draw = () => {
      t += active ? 0.04 : 0.01;
      ctx.clearRect(0, 0, w, h);
      const cw = w / cols, ch = h / rows;
      const pad = 2;
      for (const c2 of cells) {
        // funnel shape: top rows wide, narrows by row
        const rowReach = 1 - (c2.y / rows) * 0.45;
        const margin = (1 - rowReach) * (w / 2);
        const x0 = margin + c2.x * (w - margin * 2) / cols;
        const wv = (w - margin * 2) / cols;
        const v = c2.base * (0.5 + 0.5 * Math.sin(t + c2.phase + c2.x * 0.3));
        const alpha = v * (active ? 1 : 0.5);
        // heat gradient: white core → accent edge
        ctx.fillStyle = v > 0.65
          ? `rgba(255,255,255,${alpha})`
          : v > 0.4
            ? accent
            : "rgba(255,255,255,.08)";
        ctx.globalAlpha = v > 0.4 ? alpha : 0.6;
        ctx.fillRect(x0 + pad, c2.y * ch + pad, wv - pad * 2, ch - pad * 2);
      }
      ctx.globalAlpha = 1;
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function AudienceClusterCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const clusters = [
      { cx: 0.28, cy: 0.38, n: 18, col: "white" },
      { cx: 0.7, cy: 0.4, n: 14, col: accent },
      { cx: 0.5, cy: 0.72, n: 22, col: "white" },
    ];
    const pts = [];
    for (const cl of clusters) {
      for (let i = 0; i < cl.n; i++) {
        const a = Math.random() * 6.28;
        const r = Math.random() * 0.13;
        pts.push({
          x: cl.cx + Math.cos(a) * r, y: cl.cy + Math.sin(a) * r,
          col: cl.col, cl, phase: Math.random() * 6.28,
        });
      }
    }
    const draw = () => {
      t += active ? 0.018 : 0.005;
      ctx.clearRect(0, 0, w, h);
      // boundaries (soft circles)
      for (const cl of clusters) {
        const grad = ctx.createRadialGradient(cl.cx * w, cl.cy * h, 0, cl.cx * w, cl.cy * h, 130);
        grad.addColorStop(0, cl.col === accent ? "rgba(255,95,3,.18)" : "rgba(255,255,255,.10)");
        grad.addColorStop(1, "rgba(0,0,0,0)");
        ctx.fillStyle = grad;
        ctx.beginPath(); ctx.arc(cl.cx * w, cl.cy * h, 130, 0, Math.PI * 2); ctx.fill();
      }
      // points
      for (const p of pts) {
        const jx = Math.sin(t + p.phase) * 0.006;
        const jy = Math.cos(t * 1.2 + p.phase) * 0.006;
        const x = (p.x + jx) * w, y = (p.y + jy) * h;
        const pulse = (Math.sin(t * 2 + p.phase) + 1) / 2;
        ctx.fillStyle = p.col === accent ? accent : `rgba(255,255,255,${0.5 + pulse * 0.4})`;
        ctx.beginPath(); ctx.arc(x, y, 1.8 + pulse * 1.2, 0, Math.PI * 2); ctx.fill();
      }
      // labels
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.45)";
      ctx.fillText("CLUSTER_A · 18.4k", 0.28 * w - 50, 0.38 * h - 60);
      ctx.fillStyle = accent;
      ctx.fillText("CLUSTER_B · 7.2k", 0.7 * w - 50, 0.4 * h - 60);
      ctx.fillStyle = "rgba(255,255,255,.45)";
      ctx.fillText("CLUSTER_C · 23.1k", 0.5 * w - 50, 0.72 * h + 80);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function AttributionFlowCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const sources = ["paid_social", "search", "display", "organic", "email"];
    const targets = ["awareness", "consider", "convert"];
    const draw = () => {
      t += active ? 0.02 : 0.005;
      ctx.clearRect(0, 0, w, h);
      const lx = w * 0.18, rx = w * 0.82;
      // nodes
      sources.forEach((s, i) => {
        const y = (i + 0.5) * (h / sources.length);
        ctx.fillStyle = "rgba(255,255,255,.7)";
        ctx.font = "10px 'JetBrains Mono', monospace";
        ctx.textAlign = "right";
        ctx.fillText(s, lx - 12, y + 3);
        ctx.fillStyle = "rgba(255,255,255,.18)";
        ctx.beginPath(); ctx.arc(lx, y, 3, 0, Math.PI * 2); ctx.fill();
      });
      targets.forEach((s, i) => {
        const y = (i + 0.5) * (h / targets.length);
        ctx.fillStyle = "rgba(255,255,255,.7)";
        ctx.font = "10px 'JetBrains Mono', monospace";
        ctx.textAlign = "left";
        ctx.fillText(s, rx + 12, y + 3);
        ctx.fillStyle = accent;
        ctx.beginPath(); ctx.arc(rx, y, 4, 0, Math.PI * 2); ctx.fill();
      });
      // flowing dots along curves
      const flows = 16;
      for (let i = 0; i < flows; i++) {
        const sIdx = i % sources.length;
        const tIdx = (i * 7) % targets.length;
        const y0 = (sIdx + 0.5) * (h / sources.length);
        const y1 = (tIdx + 0.5) * (h / targets.length);
        const phase = (t * 0.4 + i * 0.13) % 1;
        const ease = phase;
        const cx = (lx + rx) / 2;
        // cubic bezier eval
        const x = (1 - ease) ** 3 * lx + 3 * (1 - ease) ** 2 * ease * cx + 3 * (1 - ease) * ease ** 2 * cx + ease ** 3 * rx;
        const y = (1 - ease) ** 3 * y0 + 3 * (1 - ease) ** 2 * ease * y0 + 3 * (1 - ease) * ease ** 2 * y1 + ease ** 3 * y1;
        ctx.fillStyle = `rgba(255,255,255,${0.6 - phase * 0.4})`;
        ctx.beginPath(); ctx.arc(x, y, 2, 0, Math.PI * 2); ctx.fill();
        // trail line
        ctx.strokeStyle = `rgba(255,255,255,${0.04})`;
        ctx.lineWidth = 0.8;
        ctx.beginPath();
        ctx.moveTo(lx, y0);
        ctx.bezierCurveTo(cx, y0, cx, y1, rx, y1);
        ctx.stroke();
      }
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function AgentLoopCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    // 4-node loop: think → act → observe → reflect
    const nodes = [
      { x: 0.28, y: 0.32, label: "think" },
      { x: 0.72, y: 0.32, label: "act" },
      { x: 0.72, y: 0.72, label: "observe" },
      { x: 0.28, y: 0.72, label: "reflect" },
    ];
    const draw = () => {
      t += active ? 0.022 : 0.006;
      ctx.clearRect(0, 0, w, h);
      // ring connecting
      ctx.strokeStyle = "rgba(255,255,255,.12)";
      ctx.lineWidth = 1;
      ctx.beginPath();
      nodes.forEach((n, i) => {
        const m = nodes[(i + 1) % nodes.length];
        if (i === 0) ctx.moveTo(n.x * w, n.y * h);
        ctx.lineTo(m.x * w, m.y * h);
      });
      ctx.closePath(); ctx.stroke();
      // active edge
      const idx = Math.floor((t * 0.8) % nodes.length);
      const a = nodes[idx], b = nodes[(idx + 1) % nodes.length];
      const ph = (t * 0.8) % 1;
      ctx.strokeStyle = accent;
      ctx.lineWidth = 1.6;
      ctx.shadowBlur = 12; ctx.shadowColor = accent;
      ctx.beginPath();
      ctx.moveTo(a.x * w, a.y * h);
      ctx.lineTo(a.x * w + (b.x - a.x) * w * ph, a.y * h + (b.y - a.y) * h * ph);
      ctx.stroke();
      ctx.shadowBlur = 0;
      // nodes
      nodes.forEach((n, i) => {
        const on = i === idx || i === (idx + 1) % nodes.length;
        ctx.fillStyle = on ? accent : "rgba(255,255,255,.18)";
        ctx.beginPath(); ctx.arc(n.x * w, n.y * h, on ? 7 : 5, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = on ? "#fff" : "rgba(255,255,255,.55)";
        ctx.font = "10px 'JetBrains Mono', monospace";
        ctx.textAlign = "center";
        ctx.fillText(n.label, n.x * w, n.y * h + 24);
      });
      // tool calls floating
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      const tools = ["db.query()", "fetch()", "writeFile()", "notify()"];
      ctx.fillText(`tool: ${tools[idx]}`, 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function WorkflowCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    // pipeline boxes left → right
    const stages = ["intake", "classify", "draft", "review", "ship"];
    const draw = () => {
      t += active ? 0.03 : 0.008;
      ctx.clearRect(0, 0, w, h);
      const pad = 24;
      const boxW = (w - pad * 2 - (stages.length - 1) * 14) / stages.length;
      const boxH = 50;
      const y = h / 2 - boxH / 2;
      stages.forEach((s, i) => {
        const x = pad + i * (boxW + 14);
        const active2 = (Math.sin(t - i * 0.8) > 0.4);
        ctx.fillStyle = active2 ? "rgba(255,255,255,.08)" : "rgba(255,255,255,.03)";
        ctx.strokeStyle = active2 ? accent : "rgba(255,255,255,.12)";
        ctx.lineWidth = 1;
        ctx.beginPath();
        if (ctx.roundRect) ctx.roundRect(x, y, boxW, boxH, 6);
        else ctx.rect(x, y, boxW, boxH);
        ctx.fill(); ctx.stroke();
        ctx.fillStyle = active2 ? "#fff" : "rgba(255,255,255,.45)";
        ctx.font = "10px 'JetBrains Mono', monospace";
        ctx.textAlign = "center";
        ctx.fillText(s, x + boxW / 2, y + boxH / 2 + 3);
        // arrow
        if (i < stages.length - 1) {
          ctx.strokeStyle = "rgba(255,255,255,.18)";
          ctx.beginPath();
          ctx.moveTo(x + boxW + 2, y + boxH / 2);
          ctx.lineTo(x + boxW + 12, y + boxH / 2);
          ctx.stroke();
        }
      });
      // tokens moving
      for (let i = 0; i < 6; i++) {
        const ph = ((t * 0.5 + i * 0.18) % 1);
        const x = pad + ph * (w - pad * 2);
        const yo = y + boxH / 2 + Math.sin(t * 2 + i) * 2;
        ctx.fillStyle = i % 3 === 0 ? accent : "rgba(255,255,255,.55)";
        ctx.beginPath(); ctx.arc(x, yo, 2.4, 0, Math.PI * 2); ctx.fill();
      }
      // status line
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      ctx.fillText("workflow: invoice-triage.v3 · 14 tickets/min", 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function WebsiteBuildCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    // a wireframe being assembled progressively
    const blocks = [
      { x: 0.08, y: 0.16, w: 0.84, h: 0.08, delay: 0 },  // nav
      { x: 0.08, y: 0.30, w: 0.52, h: 0.22, delay: 0.4 },// hero text
      { x: 0.64, y: 0.30, w: 0.28, h: 0.22, delay: 0.8 },// hero image
      { x: 0.08, y: 0.58, w: 0.24, h: 0.30, delay: 1.2 },// card 1
      { x: 0.38, y: 0.58, w: 0.24, h: 0.30, delay: 1.5 },// card 2
      { x: 0.68, y: 0.58, w: 0.24, h: 0.30, delay: 1.8 },// card 3
    ];
    const draw = () => {
      t += active ? 0.025 : 0.006;
      ctx.clearRect(0, 0, w, h);
      // browser chrome
      ctx.fillStyle = "rgba(255,255,255,.04)";
      ctx.fillRect(0.06 * w, 0.06 * h, 0.88 * w, 0.04 * h);
      ctx.fillStyle = "rgba(255,255,255,.18)";
      [0, 1, 2].forEach((i) => {
        ctx.beginPath();
        ctx.arc(0.08 * w + i * 10, 0.08 * h, 2.5, 0, Math.PI * 2);
        ctx.fill();
      });
      // assembly cycle: loop blocks in
      const cycle = 4.5;
      const tc = t % cycle;
      blocks.forEach((b, i) => {
        const local = Math.max(0, tc - b.delay);
        const e = Math.min(1, local / 0.6);
        const ease = e * e * (3 - 2 * e);
        ctx.globalAlpha = ease;
        ctx.fillStyle = "rgba(255,255,255,.06)";
        ctx.strokeStyle = (i === 1 || tc > 3.5) ? accent : "rgba(255,255,255,.2)";
        ctx.lineWidth = 1;
        const bw = b.w * w * ease;
        ctx.fillRect(b.x * w, b.y * h, bw, b.h * h);
        ctx.strokeRect(b.x * w, b.y * h, bw, b.h * h);
        // skeleton lines
        if (ease > 0.7) {
          ctx.fillStyle = "rgba(255,255,255,.12)";
          for (let li = 0; li < 3; li++) {
            ctx.fillRect(b.x * w + 8, b.y * h + 10 + li * 8, Math.min(bw - 16, 60 + li * 12), 3);
          }
        }
        ctx.globalAlpha = 1;
      });
      // status
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      ctx.fillText(`build: marketing-site/index.tsx · ${tc < 2 ? "compiling" : "hot-reloading"}…`, 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function RagCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    // documents on the left → embeddings cloud → answer on right
    const docs = Array.from({ length: 7 }, (_, i) => ({ y: 0.12 + i * 0.1 }));
    const points = Array.from({ length: 40 }, () => ({
      x: 0.42 + Math.random() * 0.18,
      y: 0.18 + Math.random() * 0.64,
      phase: Math.random() * 6.28,
    }));
    const draw = () => {
      t += active ? 0.028 : 0.008;
      ctx.clearRect(0, 0, w, h);
      // docs
      docs.forEach((d, i) => {
        const on = Math.floor(t * 1.2) % docs.length === i;
        ctx.fillStyle = on ? "rgba(255,255,255,.12)" : "rgba(255,255,255,.04)";
        ctx.strokeStyle = on ? accent : "rgba(255,255,255,.14)";
        ctx.lineWidth = 1;
        ctx.fillRect(0.06 * w, d.y * h, 0.12 * w, 0.06 * h);
        ctx.strokeRect(0.06 * w, d.y * h, 0.12 * w, 0.06 * h);
        ctx.fillStyle = on ? accent : "rgba(255,255,255,.3)";
        for (let li = 0; li < 3; li++) {
          ctx.fillRect(0.07 * w, d.y * h + 6 + li * 4, 0.08 * w, 1.5);
        }
        // arrow line to embedding cluster
        if (on) {
          ctx.strokeStyle = `rgba(255,95,3,.3)`;
          ctx.strokeStyle = accent;
          ctx.globalAlpha = 0.4;
          ctx.beginPath();
          ctx.moveTo(0.18 * w, d.y * h + 0.03 * h);
          ctx.lineTo(0.42 * w, 0.5 * h);
          ctx.stroke();
          ctx.globalAlpha = 1;
        }
      });
      // embeddings cluster
      points.forEach((p) => {
        const jx = Math.sin(t + p.phase) * 0.008;
        const jy = Math.cos(t * 1.4 + p.phase) * 0.008;
        const pulse = (Math.sin(t * 2 + p.phase) + 1) / 2;
        ctx.fillStyle = `rgba(255,255,255,${0.3 + pulse * 0.4})`;
        ctx.beginPath();
        ctx.arc((p.x + jx) * w, (p.y + jy) * h, 1.6 + pulse * 0.8, 0, Math.PI * 2);
        ctx.fill();
      });
      // answer block on right with citations
      ctx.fillStyle = "rgba(255,255,255,.06)";
      ctx.strokeStyle = accent;
      ctx.lineWidth = 1;
      ctx.fillRect(0.7 * w, 0.28 * h, 0.24 * w, 0.44 * h);
      ctx.strokeRect(0.7 * w, 0.28 * h, 0.24 * w, 0.44 * h);
      ctx.fillStyle = "rgba(255,255,255,.7)";
      for (let li = 0; li < 6; li++) {
        const lw = 0.22 - (li % 3) * 0.04;
        ctx.fillRect(0.71 * w, 0.31 * h + li * 12, lw * w, 2);
      }
      // citation markers
      ctx.fillStyle = accent;
      ctx.font = "8px 'JetBrains Mono', monospace";
      [1, 4, 7].forEach((li, idx) => {
        ctx.fillText(`[${idx + 1}]`, 0.91 * w, 0.32 * h + li * 12 + 2);
      });
      // status
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      ctx.fillText("rag: 18,402 chunks · k=8 · grounded", 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function ApiMeshCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const services = [
      { x: 0.5, y: 0.5, label: "core", hub: true },
      { x: 0.18, y: 0.22, label: "stripe" },
      { x: 0.82, y: 0.22, label: "hubspot" },
      { x: 0.85, y: 0.55, label: "warehouse" },
      { x: 0.68, y: 0.84, label: "shopify" },
      { x: 0.32, y: 0.84, label: "slack" },
      { x: 0.15, y: 0.55, label: "s3" },
    ];
    const draw = () => {
      t += active ? 0.022 : 0.005;
      ctx.clearRect(0, 0, w, h);
      const hub = services[0];
      // links
      services.slice(1).forEach((s, i) => {
        ctx.strokeStyle = "rgba(255,255,255,.10)";
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(hub.x * w, hub.y * h);
        ctx.lineTo(s.x * w, s.y * h);
        ctx.stroke();
        // packet
        const ph = ((t * 0.5 + i * 0.18) % 1);
        const dir = (i % 2 === 0) ? ph : (1 - ph);
        const px = hub.x * w + (s.x - hub.x) * w * dir;
        const py = hub.y * h + (s.y - hub.y) * h * dir;
        ctx.fillStyle = i % 3 === 0 ? accent : "rgba(255,255,255,.7)";
        ctx.beginPath(); ctx.arc(px, py, 2.4, 0, Math.PI * 2); ctx.fill();
      });
      // nodes
      services.forEach((s) => {
        ctx.fillStyle = s.hub ? accent : "rgba(255,255,255,.12)";
        ctx.strokeStyle = s.hub ? accent : "rgba(255,255,255,.3)";
        ctx.lineWidth = 1;
        ctx.beginPath(); ctx.arc(s.x * w, s.y * h, s.hub ? 14 : 9, 0, Math.PI * 2);
        ctx.fill(); ctx.stroke();
        ctx.fillStyle = s.hub ? "#0A0A0C" : "rgba(255,255,255,.7)";
        ctx.font = s.hub ? "bold 9px 'JetBrains Mono', monospace" : "9px 'JetBrains Mono', monospace";
        ctx.textAlign = "center";
        ctx.fillText(s.label, s.x * w, s.y * h + 3);
      });
      // status
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      ctx.fillText("mesh: 7 services · 142 req/s · 0 errors", 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

function ModelOpsCanvas({ active, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const evals = ["faithfulness", "relevance", "safety", "tone", "latency"];
    const draw = () => {
      t += active ? 0.022 : 0.005;
      ctx.clearRect(0, 0, w, h);
      const pad = 26;
      const startY = 56;
      const rowH = 24;
      evals.forEach((label, i) => {
        const y = startY + i * rowH;
        ctx.fillStyle = "rgba(255,255,255,.55)";
        ctx.font = "10px 'JetBrains Mono', monospace";
        ctx.textAlign = "left";
        ctx.fillText(label, pad, y + 8);
        // track
        ctx.fillStyle = "rgba(255,255,255,.06)";
        ctx.fillRect(pad + 90, y + 2, w - pad - 90 - pad - 36, 8);
        // value (animated)
        const v = 0.5 + 0.5 * Math.sin(t * 0.8 + i * 0.8);
        const score = 0.65 + v * 0.32;
        const bw = (w - pad - 90 - pad - 36) * score;
        ctx.fillStyle = score > 0.85 ? accent : "rgba(255,255,255,.55)";
        ctx.fillRect(pad + 90, y + 2, bw, 8);
        ctx.fillStyle = score > 0.85 ? accent : "rgba(255,255,255,.7)";
        ctx.font = "9.5px 'JetBrains Mono', monospace";
        ctx.textAlign = "right";
        ctx.fillText(score.toFixed(2), w - pad, y + 8);
      });
      // header
      ctx.font = "9.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.textAlign = "left";
      ctx.fillText("evals: claude-3.5-sonnet · run #1,418 · 12.4k samples", 20, 24);
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [active, accent]);
  return <canvas ref={ref} className="cap-canvas" />;
}

// ── Capability card ──────────────────────────────────────────────────────
function CapCard({ num, title, desc, tag, accent, Visual }) {
  const [hover, setHover] = useState(false);
  return (
    <div className="cap-card glass"
         onMouseEnter={() => setHover(true)}
         onMouseLeave={() => setHover(false)}
         data-hover>
      <div className="cap-card-vis">
        <Visual active={hover} accent={accent} />
        <div style={{
          position: "absolute", inset: 0,
          background: "linear-gradient(180deg, rgba(15,15,18,.0) 30%, rgba(15,15,18,.78) 100%)",
        }} />
      </div>
      <div className="cap-card-content">
        <div className="cap-card-num">{num}</div>
        <h3 className="cap-card-title" style={{ marginTop: 10 }}>{title}</h3>
        <p className="cap-card-desc">{desc}</p>
      </div>
      <div className="cap-card-foot">
        <span className="cap-card-tag">{tag}</span>
        <span className="cap-card-arrow"><Arrow size={14} /></span>
      </div>
    </div>
  );
}

// ── Practice sections ────────────────────────────────────────────────────
function AIAutomation({ accent }) {
  const items = [
    { num: "01 / Automation", title: "AI Agents",          desc: "Domain-tuned agents for support, ops and research — with tool-use, memory and human-in-the-loop.",                 tag: "AUTONOMOUS",        Visual: AgentLoopCanvas },
    { num: "02 / Automation", title: "Workflow Software",   desc: "Internal apps that replace spreadsheets and SOPs. Built in weeks, owned by your team.",                            tag: "OWN THE STACK",     Visual: WorkflowCanvas },
    { num: "03 / Automation", title: "RAG & Knowledge",     desc: "Search and chat over your docs, contracts and code — with citations and evals you can defend.",                  tag: "GROUNDED",          Visual: RagCanvas },
    { num: "04 / Automation", title: "Integrations & APIs", desc: "Wire your CRM, billing, warehouse and data lake together. Typed contracts; no glue-code rot.",                    tag: "TYPED + TESTED",    Visual: ApiMeshCanvas },
    { num: "05 / Automation", title: "Custom AI Tools",     desc: "Bespoke tools built around your workflow — the thing you wish existed but no SaaS sells.",                  tag: "BESPOKE",          Visual: NodeGraphCanvas },
    { num: "06 / Automation", title: "Chatbots & Assistants", desc: "Customer-facing and internal assistants that answer from your own data, day and night.",                 tag: "24/7",             Visual: ModelOpsCanvas },
  ];
  return (
    <section className="section" id="automation">
      <div className="cap-wrap">
        <div className="cap-side">
          <div className="practice-badge accent">
            <span className="practice-badge-dot" />
            Practice 01
          </div>
          <div className="eyebrow" style={{ marginTop: 18 }}><span className="idx">/01</span> AI Automation</div>
          <h2 className="section-title">
            AI <span className="em">tools</span> built for your business.
          </h2>
          <p>
            Agents, workflows and custom tools that take the repetitive work off your team's plate —
            built to your process, and owned by you.
          </p>
          <div className="meta">
            <div className="row"><span>Cadence</span><span className="v">2-week sprints</span></div>
          </div>
        </div>
        <div className="cap-grid">
          {items.map((it) => <CapCard key={it.num} {...it} accent={accent} />)}
        </div>
      </div>
    </section>
  );
}

function AIMarketing({ accent }) {
  const items = [
    { num: "01 / Marketing", title: "Content Generation",   desc: "On-brand posts, blogs, captions and scripts — drafted in your voice, at the volume your channels need.",     tag: "ON-BRAND",      Visual: CopyStreamCanvas },
    { num: "02 / Marketing", title: "Auto-Posting",         desc: "Queue once and we publish across your channels on a schedule — timed, formatted and posted automatically.", tag: "HANDS-OFF",     Visual: AttributionFlowCanvas },
    { num: "03 / Marketing", title: "SEO Content",          desc: "Keyword-researched articles and landing pages built to rank — and keep ranking as you publish.",          tag: "BUILT TO RANK", Visual: FunnelHeatmapCanvas },
    { num: "04 / Marketing", title: "Repurposing",          desc: "Turn one asset into ten — a video into clips, a post into a thread, a blog into a newsletter.",          tag: "ONE TO MANY",   Visual: BidWaveCanvas },
    { num: "05 / Marketing", title: "Brand Voice",          desc: "A model tuned to your tone and guidelines, so every piece sounds like you wrote it.",                    tag: "YOUR VOICE",    Visual: AudienceClusterCanvas },
    { num: "06 / Marketing", title: "Performance Insights",  desc: "See what's landing across channels and double down — without drowning in dashboards.",                   tag: "WHAT WORKS",    Visual: NodeGraphCanvas },
  ];
  return (
    <section className="section" id="marketing">
      <div className="cap-wrap">
        <div className="cap-side">
          <div className="practice-badge">
            <span className="practice-badge-dot" />
            Practice 02
          </div>
          <div className="eyebrow" style={{ marginTop: 18 }}><span className="idx">/02</span> AI Marketing</div>
          <h2 className="section-title">
            Show up <span className="em">everywhere,</span> automatically.
          </h2>
          <p>
            AI that creates and publishes your content — posts, articles and campaigns drafted in
            your voice and pushed to every channel, on schedule.
          </p>
          <div className="meta">
            <div className="row"><span>Channels</span><span className="v">social · blog · email</span></div>
            <div className="row"><span>Cadence</span><span className="v">daily, automated</span></div>
            <div className="row"><span>SEO</span><span className="v">built in</span></div>
          </div>
        </div>
        <div className="cap-grid">
          {items.map((it) => <CapCard key={it.num} {...it} accent={accent} />)}
        </div>
      </div>
    </section>
  );
}

function StudioServices({ accent }) {
  const items = [
    { num: "01 / Services", title: "SEO Websites",          desc: "High-craft, fast websites and storefronts — built to be found on Google and to convert.",                    tag: "SEO-OPTIMIZED", Visual: WebsiteBuildCanvas },
    { num: "02 / Services", title: "Custom Software",        desc: "Product MVPs, internal tools and dashboards — scoped tight and shipped fast.",                              tag: "SHIP FAST",     Visual: WorkflowCanvas },
    { num: "03 / Services", title: "Data & Dashboards",      desc: "Pipelines, warehouses and the dashboards on top — so the whole team sees the same numbers.",                    tag: "SINGLE SOURCE", Visual: ApiMeshCanvas },
    { num: "04 / Services", title: "AI Strategy",            desc: "Where AI actually moves the needle for you — a prioritized roadmap, not a slide deck.",                          tag: "ROADMAP",       Visual: NodeGraphCanvas },
    { num: "05 / Services", title: "Knowledge Assistants",   desc: "Internal copilots grounded in your docs and data, with the guardrails to roll them out safely.",                tag: "GROUNDED",      Visual: RagCanvas },
    { num: "06 / Services", title: "Ongoing Support",        desc: "Design, build and content help on a retainer — a team on call when you need it, off when you don't.",           tag: "RETAINER",      Visual: ModelOpsCanvas },
  ];
  return (
    <section className="section" id="services">
      <div className="cap-wrap">
        <div className="cap-side">
          <div className="practice-badge accent">
            <span className="practice-badge-dot" />
            Practice 03
          </div>
          <div className="eyebrow" style={{ marginTop: 18 }}><span className="idx">/03</span> Services</div>
          <h2 className="section-title">
            Everything <span className="em">else</span> you need shipped.
          </h2>
          <p>
            Sites, software, data and strategy — done by a small, hands-on team, billed by sprint, not seat.
            The same team that builds your automation and marketing.
          </p>
          <div className="meta">
            <div className="row"><span>Engagements</span><span className="v">Sprint · Build · Retainer</span></div>
            <div className="row"><span>Cadence</span><span className="v">2-week sprints</span></div>
          </div>
        </div>
        <div className="cap-grid">
          {items.map((it) => <CapCard key={it.num} {...it} accent={accent} />)}
        </div>
      </div>
    </section>
  );
}

// ── Case studies ─────────────────────────────────────────────────────────
function CaseMediaCanvas({ kind, accent }) {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current; if (!c) return;
    const ctx = c.getContext("2d");
    let raf, w, h, t = 0;
    const resize = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = c.clientWidth; h = c.clientHeight;
      c.width = w * dpr; c.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(c);
    const draw = () => {
      t += 0.008;
      ctx.clearRect(0, 0, w, h);
      // base
      const grad = ctx.createLinearGradient(0, 0, w, h);
      if (kind === 0) { grad.addColorStop(0, "#1a1a22"); grad.addColorStop(1, "#0e0e12"); }
      if (kind === 1) { grad.addColorStop(0, "#16161c"); grad.addColorStop(1, "#0a0a0c"); }
      if (kind === 2) { grad.addColorStop(0, "#1d1820"); grad.addColorStop(1, "#101015"); }
      if (kind === 3) { grad.addColorStop(0, "#12161a"); grad.addColorStop(1, "#0a0d0f"); }
      ctx.fillStyle = grad;
      ctx.fillRect(0, 0, w, h);

      if (kind === 0) {
        // big slow waves — film-like
        for (let i = 0; i < 5; i++) {
          ctx.strokeStyle = `rgba(255,255,255,${0.05 + i * 0.02})`;
          ctx.lineWidth = 1;
          ctx.beginPath();
          for (let x = 0; x <= w; x += 6) {
            const y = h * 0.5 + Math.sin(x * 0.006 + t + i * 0.4) * 80 + i * 18 - 40;
            if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
          }
          ctx.stroke();
        }
        ctx.strokeStyle = accent;
        ctx.lineWidth = 1.6;
        ctx.beginPath();
        for (let x = 0; x <= w; x += 4) {
          const y = h * 0.5 + Math.sin(x * 0.009 + t * 1.4) * 60 + Math.cos(x * 0.004 + t) * 20;
          if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        ctx.shadowBlur = 20; ctx.shadowColor = accent; ctx.stroke(); ctx.shadowBlur = 0;
      }
      if (kind === 1) {
        // grid morph
        for (let y = 0; y < h; y += 24) {
          for (let x = 0; x < w; x += 24) {
            const d = Math.sin((x + y) * 0.012 + t) * 0.5 + 0.5;
            const s = 2 + d * 8;
            ctx.fillStyle = d > 0.78 ? accent : `rgba(255,255,255,${0.05 + d * 0.25})`;
            ctx.fillRect(x, y, s, s);
          }
        }
      }
      if (kind === 2) {
        // particle storm
        for (let i = 0; i < 90; i++) {
          const a = i * 0.34 + t;
          const r = 60 + (i % 9) * 18 + Math.sin(t + i) * 14;
          const x = w / 2 + Math.cos(a) * r;
          const y = h / 2 + Math.sin(a * 1.1) * r * 0.7;
          const o = 0.2 + ((Math.sin(t * 2 + i) + 1) / 2) * 0.7;
          ctx.fillStyle = (i % 7 === 0) ? accent : `rgba(255,255,255,${o})`;
          ctx.beginPath(); ctx.arc(x, y, i % 7 === 0 ? 2.4 : 1.4, 0, Math.PI * 2); ctx.fill();
        }
      }
      if (kind === 3) {
        // ascending bars
        const bars = 22;
        const bw = w / bars;
        for (let i = 0; i < bars; i++) {
          const v = 0.3 + 0.7 * (0.5 + 0.5 * Math.sin(i * 0.5 + t * 1.5));
          const bh = v * h * 0.8;
          ctx.fillStyle = (i > bars * 0.7) ? accent : `rgba(255,255,255,${0.18 + v * 0.3})`;
          ctx.fillRect(i * bw + 4, h - bh, bw - 8, bh);
        }
      }

      // overlay tag
      ctx.font = "10.5px 'JetBrains Mono', monospace";
      ctx.fillStyle = "rgba(255,255,255,.32)";
      ctx.fillText("LIVE PREVIEW · 00:" + String(Math.floor(t * 4) % 60).padStart(2, "0"), 20, 24);
      // film border crosshair
      ctx.strokeStyle = "rgba(255,255,255,.18)";
      ctx.lineWidth = 1;
      const cs = 10;
      ctx.beginPath(); ctx.moveTo(20, h - 20 - cs); ctx.lineTo(20, h - 20); ctx.lineTo(20 + cs, h - 20); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(w - 20 - cs, h - 20); ctx.lineTo(w - 20, h - 20); ctx.lineTo(w - 20, h - 20 - cs); ctx.stroke();

      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, [kind, accent]);
  return <canvas ref={ref} className="case-canvas" />;
}

function CaseCard({ name, headline, roi, roiLabel, kind, accent, tall, tagline }) {
  const cardRef = useRef(null);
  const circleRef = useRef(null);
  const onMove = (e) => {
    const r = cardRef.current.getBoundingClientRect();
    if (circleRef.current) {
      circleRef.current.style.left = (e.clientX - r.left) + "px";
      circleRef.current.style.top = (e.clientY - r.top) + "px";
    }
  };
  return (
    <div ref={cardRef} className={`case${tall ? " tall" : ""}`} onMouseMove={onMove} data-hover>
      <div className="case-media">
        <CaseMediaCanvas kind={kind} accent={accent} />
        <div className="case-reveal">
          <div ref={circleRef} className="case-reveal-circle">
            <div className="case-reveal-label">
              View project <Arrow size={14} />
            </div>
          </div>
        </div>
      </div>
      <div className="case-info">
        <div>
          {tagline && <div className="case-tagline">{tagline}</div>}
          <h3 className="case-name">{name}</h3>
          <p className="case-headline">{headline}</p>
        </div>
        <div className="case-roi">
          <div className="num">{roi}</div>
          <div className="lbl">{roiLabel}</div>
        </div>
      </div>
    </div>
  );
}

// ── Selected work (real projects) ────────────────────────────────────────
function Work({ accent }) {
  const projects = [
    { name: "Medical Clinic Toolkit", headline: "AI-assisted intake, scribing and patient-message drafting for a medical clinic — built HIPAA-compliant end to end.", tagline: "HEALTHCARE · HIPAA",    roi: "HIPAA", roiLabel: "compliant by design",       kind: 0 },
    { name: "Voice Call Assistant",   headline: "A voice phone-call assistant with long-term memory — it books, answers and follows up, remembering every caller.", tagline: "VOICE AI · MEMORY",     roi: "24/7",  roiLabel: "calls handled with context",  kind: 2 },
    { name: "Content Autopilot",      headline: "Automated content marketing for a growing brand — posts and articles drafted in-voice, scheduled and published.",  tagline: "CONTENT AUTOMATION",   roi: "Daily", roiLabel: "posts shipped on autopilot",   kind: 3 },
  ];
  return (
    <section className="section" id="work-projects">
      <div className="eyebrow"><span className="idx">/04</span> Selected work</div>
      <h2 className="section-title">
        Built and shipped, <span className="em">for real.</span>
      </h2>
      <p className="sat-lede" style={{ marginTop: 24, maxWidth: "60ch" }}>
        A few recent projects. Client names withheld for privacy — happy to walk you
        through the details on a call.
      </p>
      <div className="work-grid">
        {projects.map((p) => <CaseCard key={p.name} {...p} accent={accent} />)}
      </div>
    </section>
  );
}

function CaseStudies({ accent }) {
  return null;
}


// ── Metrics strip ────────────────────────────────────────────────────────
function Metrics() {
  const items = [
    { v: "2",     em: "wk", lbl: "Typical sprint length" },
    { v: "4",     em: "",  lbl: "Ways to start" },
    { v: "∞",     em: "",  lbl: "You own the code" },
  ];
  return (
    <div className="metrics">
      {items.map((m) => (
        <div className="metric" key={m.lbl}>
          <div className="v">{m.v}<span className="em">{m.em}</span></div>
          <div className="lbl">{m.lbl}</div>
        </div>
      ))}
    </div>
  );
}

// ── Footer / Final CTA ───────────────────────────────────────────────────
function Footer({ onJoin }) {
  return (
    <footer className="footer">
      <canvas id="shader-footer" />
      <div className="footer-vignette" />
      <div className="footer-inner">
        <div className="eyebrow"><span className="idx">/05</span> The Close</div>
        <h2 className="footer-headline" style={{ marginTop: 28 }}>
          <span className="row serif">Let's</span>
          <span className="row main">build it.</span>
        </h2>
        <div className="footer-cta-row">
          <p className="footer-cta-text">
            Tell us what's slowing your team down. We'll come back with the
            fastest path to shipping it — automation, marketing, or a build.
          </p>
          <button className="btn-massive" data-hover onClick={onJoin}>
            <RollText>Start a project</RollText>
            <Arrow size={18} className="btn-massive-arrow" />
          </button>
        </div>
      </div>
      <div className="footer-base">
        <div className="l">
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--accent)",
            boxShadow: "0 0 8px var(--accent-glow)" }} />
          XubeAi — AI AUTOMATION, MARKETING & SERVICES · ©2026
        </div>
        <div className="r">
          <a href="#">Twitter</a>
          <a href="#">LinkedIn</a>
          <a href="#">Manifesto</a>
          <a href="#">Privacy</a>
        </div>
      </div>
    </footer>
  );
}

// ── X Intro (FLIP big X → small nav mark) ────────────────────────────────
function XIntro({ onDone }) {
  const glyphRef = useRef(null);
  const wrapRef = useRef(null);
  const [done, setDone] = useState(false);

  useEffect(() => {
    // Force a clean start so a restored scroll position doesn't fire `scroll`
    // before the intro is on stage.
    if ("scrollRestoration" in history) history.scrollRestoration = "manual";
    window.scrollTo(0, 0);

    const navMark = document.querySelector(".nav-brand-mark");
    if (navMark) navMark.classList.add("is-hidden");

    let triggered = false;
    const fly = () => {
      if (triggered) return;
      triggered = true;
      const g = glyphRef.current;
      const w = wrapRef.current;
      const target = document.querySelector(".nav-brand-mark .xcube")
                     || document.querySelector(".nav-brand-mark");
      const innerCube = g && g.querySelector(".xcube");
      const srcEl = innerCube || g;
      if (!srcEl || !w || !target) {
        cleanup();
        return;
      }
      // FLIP: read both rects
      const src = srcEl.getBoundingClientRect();
      const dst = target.getBoundingClientRect();
      const scale = dst.width / src.width;
      const dx = (dst.left + dst.width / 2) - (src.left + src.width / 2);
      const dy = (dst.top + dst.height / 2) - (src.top + src.height / 2);
      // Apply transform — CSS handles the easing
      g.style.transform = `translate(${dx}px, ${dy}px) scale(${scale})`;
      g.style.transformOrigin = "center";
      w.classList.add("is-flying");
      // Reveal the real nav mark partway through so the transition reads continuous
      setTimeout(() => target.classList.remove("is-hidden"), 950);
      setTimeout(() => {
        setDone(true);
        onDone && onDone();
      }, 1200);
    };

    const cleanup = () => {
      navMark?.classList.remove("is-hidden");
      setDone(true);
      onDone && onDone();
    };

    // Only listen for dismissal AFTER the intro has had stage time.
    // Otherwise a browser-emitted wheel/scroll on layout dismisses it
    // before the user ever sees the cube.
    const MIN_STAGE_MS = 1600;
    const onWheel = () => fly();
    const onTouch = () => fly();
    const onScroll = () => { if (window.scrollY > 0) fly(); };
    const onKey = (e) => {
      if (["ArrowDown", "ArrowUp", "PageDown", "PageUp", " ", "Home", "End", "Enter"].includes(e.key)) fly();
    };
    const armId = setTimeout(() => {
      window.addEventListener("wheel", onWheel, { passive: true });
      window.addEventListener("touchmove", onTouch, { passive: true });
      window.addEventListener("scroll", onScroll, { passive: true });
      window.addEventListener("keydown", onKey);
    }, MIN_STAGE_MS);
    // Backstop: auto-fly so the page never stays gated behind the X.
    const autoFly = setTimeout(fly, 3600);

    return () => {
      clearTimeout(armId);
      clearTimeout(autoFly);
      window.removeEventListener("wheel", onWheel);
      window.removeEventListener("touchmove", onTouch);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("keydown", onKey);
    };
  }, []);

  if (done) return null;
  return (
    <div ref={wrapRef} className="x-intro with-veil">
      <span ref={glyphRef} className="x-intro-glyph">
        <XCube size="min(54vmin, 460px)" />
      </span>
      <div className="x-intro-caption">
        XubeAi <span className="pip" /> Marketing &amp; Build <span className="pip" /> Scroll
      </div>
    </div>
  );
}

// ── Waitlist modal ───────────────────────────────────────────────────────
function Waitlist({ open, onClose }) {
  const [email, setEmail] = useState("");
  const [company, setCompany] = useState("");
  const [role, setRole] = useState("automation");
  const [submitted, setSubmitted] = useState(false);
  if (!open) return null;
  const submit = (e) => { e.preventDefault(); setSubmitted(true); };
  return (
    <div className="wl-overlay" onClick={onClose}>
      <div className="wl-modal glass-strong" onClick={(e) => e.stopPropagation()}>
        <button className="wl-close" onClick={onClose} aria-label="Close">✕</button>
        {!submitted ? (
          <>
            <div className="eyebrow"><span className="idx">/01</span> Start a project</div>
            <h3 className="wl-title">Tell us what to <span className="em">build</span>.</h3>
            <p className="wl-lede">
              A few details and we'll come back within two business days with a fast path to shipping it.
            </p>
            <form className="wl-form" onSubmit={submit}>
              <label>
                <span>Work email</span>
                <input type="email" required value={email}
                       onChange={(e) => setEmail(e.target.value)}
                       placeholder="you@company.com" />
              </label>
              <label>
                <span>Company</span>
                <input type="text" required value={company}
                       onChange={(e) => setCompany(e.target.value)}
                       placeholder="Acme Inc." />
              </label>
              <label>
                <span>I'm interested in</span>
                <div className="wl-radio">
                  {[
                    { v: "automation", l: "AI automation — agents & workflows" },
                    { v: "marketing",  l: "AI marketing — growth & creative" },
                    { v: "services",   l: "Services — a site, software or data" },
                  ].map((o) => (
                    <label key={o.v} className={role === o.v ? "on" : ""}>
                      <input type="radio" name="role" value={o.v} checked={role === o.v}
                             onChange={() => setRole(o.v)} />
                      <span>{o.l}</span>
                    </label>
                  ))}
                </div>
              </label>
              <button type="submit" className="btn-primary" style={{ alignSelf: "flex-start" }}>
                <RollText>Send it over</RollText>
                <Arrow size={14} className="btn-primary-arrow" />
              </button>
            </form>
          </>
        ) : (
          <>
            <div className="eyebrow"><span className="idx">/✓</span> Got it</div>
            <h3 className="wl-title">Talk <span className="em">soon.</span></h3>
            <p className="wl-lede">
              We'll be in touch within two business days with a fast path to shipping it.
              In the meantime, reply to the confirmation email with anything else you want us to see.
            </p>
            <button className="btn-ghost" onClick={onClose}>Back to site</button>
          </>
        )}
      </div>
    </div>
  );
}

// ── App ──────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [waitlistOpen, setWaitlistOpen] = useState(false);
  const openWaitlist = () => setWaitlistOpen(true);
  const heroShader = useRef(null);
  const footerShader = useRef(null);

  // Accent CSS vars
  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty("--accent", t.accent);
    root.style.setProperty("--accent-glow", ACCENT_GLOW[t.accent] || "rgba(255,95,3,.55)");
    root.style.setProperty("--accent-soft", ACCENT_SOFT[t.accent] || "rgba(255,95,3,.12)");
    if (heroShader.current) heroShader.current.setAccent(t.accent);
    if (footerShader.current) footerShader.current.setAccent(t.accent);
  }, [t.accent]);

  useEffect(() => {
    if (heroShader.current) heroShader.current.setIntensity(t.shaderIntensity);
    if (footerShader.current) footerShader.current.setIntensity(t.shaderIntensity * 0.8);
  }, [t.shaderIntensity]);

  useEffect(() => {
    if (heroShader.current) heroShader.current.setSpeed(t.shaderSpeed);
    if (footerShader.current) footerShader.current.setSpeed(t.shaderSpeed);
  }, [t.shaderSpeed]);

  // Init shaders once the canvases mount
  useEffect(() => {
    const hero = document.getElementById("shader-hero");
    if (hero && window.NeuralShader) {
      heroShader.current = window.NeuralShader.init(hero, {
        accent: t.accent,
        intensity: t.shaderIntensity,
        speed: t.shaderSpeed,
        boomerang: 0,
        trackMouse: true,
      });
    }
    const ftr = document.getElementById("shader-footer");
    if (ftr && window.NeuralShader) {
      footerShader.current = window.NeuralShader.init(ftr, {
        accent: t.accent,
        intensity: t.shaderIntensity * 0.8,
        speed: t.shaderSpeed * 0.9,
        boomerang: 1,
        trackMouse: false,
      });
    }
    return () => {
      heroShader.current?.destroy();
      footerShader.current?.destroy();
    };
  }, []);

  return (
    <>
      <Cursor enabled={t.cursorEffect} />
      <Navbar onJoin={openWaitlist} />
      <Hero onJoin={openWaitlist} />
      {t.engineWidget && <EnginePulse />}
      <AIAutomation accent={t.accent} />
      <AIMarketing accent={t.accent} />
      <Metrics />
      <StudioServices accent={t.accent} />
      <Work accent={t.accent} />
      <Footer onJoin={openWaitlist} />
      <Waitlist open={waitlistOpen} onClose={() => setWaitlistOpen(false)} />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Brand accent" />
        <TweakColor
          label="Accent"
          value={t.accent}
          options={["#FF5F03", "#8B5CF6", "#22D3EE", "#A3E635"]}
          onChange={(v) => setTweak("accent", v)}
        />
        <TweakSection label="Shader" />
        <TweakSlider
          label="Intensity"
          value={t.shaderIntensity}
          min={0.3} max={2.0} step={0.05}
          onChange={(v) => setTweak("shaderIntensity", v)}
        />
        <TweakSlider
          label="Speed"
          value={t.shaderSpeed}
          min={0.2} max={2.5} step={0.05}
          onChange={(v) => setTweak("shaderSpeed", v)}
        />
        <TweakSection label="Interface" />
        <TweakToggle label="Custom cursor"
          value={t.cursorEffect} onChange={(v) => setTweak("cursorEffect", v)} />
        <TweakToggle label="Studio Pulse widget"
          value={t.engineWidget} onChange={(v) => setTweak("engineWidget", v)} />
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
