// Premium hero graph variants. Selected via TWEAKS.graphStyle.
// All variants share the same node taxonomy but render very differently.

// Robust alpha injection for any oklch / rgb / hex string.
// Strategy: paint a 1px swatch into a hidden canvas, then read back rgba bytes.
const _swatchCtx = (() => {
  if (typeof document === 'undefined') return null;
  const c = document.createElement('canvas');
  c.width = c.height = 1;
  return c.getContext('2d', { willReadFrequently: true });
})();
const _swatchCache = new Map();
function colorWithAlpha(color, alpha) {
  if (!_swatchCtx) return color;
  const cacheKey = color + '|base';
  let rgb = _swatchCache.get(cacheKey);
  if (!rgb) {
    try {
      _swatchCtx.clearRect(0, 0, 1, 1);
      _swatchCtx.fillStyle = '#000';
      _swatchCtx.fillStyle = color; // browser parses oklch/hex/etc.
      _swatchCtx.fillRect(0, 0, 1, 1);
      const d = _swatchCtx.getImageData(0, 0, 1, 1).data;
      rgb = [d[0], d[1], d[2]];
      _swatchCache.set(cacheKey, rgb);
    } catch (e) {
      return color;
    }
  }
  const a = Math.max(0, Math.min(1, alpha));
  return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${a})`;
}

const GRAPH_DATA = (() => {
  // Categorias principais (CORE) — espelham as cards de Integrações
  const CORE = [
    { id:'crm',  label:'CRM' },
    { id:'erp',  label:'ERP' },
    { id:'canal',label:'Canais' },
    { id:'help', label:'Helpdesk' },
    { id:'ai',   label:'IA' },
    { id:'acs',  label:'ACS' },
    { id:'db',   label:'Dados' },
    { id:'voice',label:'Voz' },
  ];
  // RING1: marcas reais que conectamos hoje (alinhado com a seção Integrações)
  const RING1 = [
    'IXC Provedor','Hubsoft','Trello','Opa! Suite',
    'WhatsApp Business','Conecta SIP-AI','Twilio','OpenAI',
    'Anthropic','IXC ACS','Postgres','E-mail',
  ];
  // RING2: capacidades operacionais que rodam por cima das integrações
  const RING2 = [
    '2ª via','Status do plano','Agendamento','Cobrança',
    'Suporte L1','Reset CPE','Velocidade','Diagnóstico',
    'Pré-venda','Onboarding','Upsell','CSAT',
    'Triagem','Roteamento','Transcrição','Resumo',
  ];
  // RING3: protocolos / blocos técnicos que tornam tudo isso possível
  const RING3 = [
    'Webhooks','REST API','SDK','SIP/RTP',
    'TR-069','SNMP','RAG','Embeddings',
    'LGPD','Logs auditáveis','SSO','Guardrails',
    'Cron','Eventos','Filas','Fallback humano',
  ];
  return { CORE, RING1, RING2, RING3 };
})();

// ─────────────────────────────────────────────────────────────────────────
// VARIANT 1: CONSTELLATION
// Obsidian-graph-view inspired. Curved bezier edges, individual node halos,
// floating particles, additive glow, radial gradient backdrop.
// ─────────────────────────────────────────────────────────────────────────
const HeroGraphConstellation = ({ accent = "oklch(0.82 0.18 125)" }) => {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;

    const { CORE, RING1, RING2, RING3 } = GRAPH_DATA;

    // Build nodes
    const nodes = [];
    nodes.push({ id:'hub', label:'ConectaAI', kind:'hub', r: 18, baseAngle: 0, ring: 0, orbitSpeed: 0 });
    CORE.forEach((c, i) => nodes.push({ id:c.id, label:c.label, kind:'core', r: 8,
      baseAngle: (i / CORE.length) * Math.PI * 2 - Math.PI/2, ring: 1, orbitSpeed: 0.00006 }));
    RING1.forEach((label, i) => nodes.push({ id:'r1_'+i, label, kind:'leaf1', r: 5.5,
      baseAngle: (i / RING1.length) * Math.PI * 2, ring: 2, orbitSpeed: -0.00004 }));
    RING2.forEach((label, i) => nodes.push({ id:'r2_'+i, label, kind:'leaf2', r: 4,
      baseAngle: (i / RING2.length) * Math.PI * 2 + 0.1, ring: 3, orbitSpeed: 0.00003 }));
    RING3.forEach((label, i) => nodes.push({ id:'r3_'+i, label, kind:'leaf3', r: 3,
      baseAngle: (i / RING3.length) * Math.PI * 2 - 0.15, ring: 4, orbitSpeed: -0.000022 }));
    nodes.forEach(n => { n.x = 0; n.y = 0; });
    const byId = Object.fromEntries(nodes.map(n => [n.id, n]));

    // Edges
    const EDGES = [];
    CORE.forEach(c => EDGES.push(['hub', c.id]));
    for (let i = 0; i < RING1.length; i++) {
      const coreIdx = Math.floor(i / 2) % CORE.length;
      EDGES.push([CORE[coreIdx].id, 'r1_'+i]);
    }
    for (let i = 0; i < RING2.length; i++) {
      const r1Idx = Math.floor(i / 1.5) % RING1.length;
      EDGES.push(['r1_'+r1Idx, 'r2_'+i]);
    }
    for (let i = 0; i < RING3.length; i++) {
      EDGES.push(['r2_'+(i % RING2.length), 'r3_'+i]);
    }
    // Tangential connections within each ring (textura)
    for (let i = 0; i < RING1.length; i++) {
      EDGES.push(['r1_'+i, 'r1_'+((i+1) % RING1.length)]);
    }
    const edges = EDGES.filter(([a,b]) => byId[a] && byId[b])
      .map(([a,b]) => ({ a: byId[a], b: byId[b], pulses: [] }));

    // Floating particles (background dust)
    const particles = Array.from({ length: 60 }, () => ({
      x: Math.random(), y: Math.random(),
      vx: (Math.random() - 0.5) * 0.0002,
      vy: (Math.random() - 0.5) * 0.0002,
      r: Math.random() * 1.4 + 0.3,
      a: Math.random() * 0.4 + 0.1,
    }));

    function getRadii() {
      const isFB = wrap.classList.contains('hero-graph-fullbleed');
      // Em modo card: círculo (usa min). Em fullbleed: elipse (rx baseado em W, ry baseado em H)
      // pra preencher melhor a largura do hero.
      if (isFB) {
        const mx = W * 0.5;
        const my = H * 0.5;
        return {
          rCore:  Math.min(mx, my) * 0.18,
          r1: { rx: mx * 0.36, ry: my * 0.46 },
          r2: { rx: mx * 0.62, ry: my * 0.72 },
          r3: { rx: mx * 0.88, ry: my * 0.94 },
        };
      }
      const m = Math.min(W, H);
      return { rCore: m*0.18, r1: m*0.32, r2: m*0.42, r3: m*0.51 };
    }
    function spawnPulse() {
      const hubEdges = edges.filter(e => e.a.kind==='hub' || e.b.kind==='hub');
      const pool = Math.random() < 0.6 ? hubEdges : edges;
      const e = pool[Math.floor(Math.random() * pool.length)];
      if (!e) return;
      e.pulses.push({ t: 0, dir: Math.random()<0.5?1:-1, speed: 0.005 + Math.random()*0.012 });
    }
    function resize() {
      const rect = wrap.getBoundingClientRect();
      const isFullbleed = wrap.classList.contains('hero-graph-fullbleed');
      W = Math.max(320, Math.min(2400, rect.width || 800));
      H = Math.max(320, Math.min(isFullbleed ? 2000 : 900, rect.height || 600));
      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);
    }
    resize();
    const ro = new ResizeObserver(resize); ro.observe(wrap);
    const mouse = { x:-9999, y:-9999, active:false };
    const isFB = wrap.classList.contains('hero-graph-fullbleed');
    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      const x = e.clientX - r.left, y = e.clientY - r.top;
      mouse.x = x; mouse.y = y;
      mouse.active = x >= 0 && x <= r.width && y >= 0 && y <= r.height;
    };
    const onLeave = () => { mouse.active = false; mouse.x = -9999; mouse.y = -9999; };
    // Em fullbleed o backdrop é pointer-events:none, então escutamos no window
    // pra continuar reagindo ao mouse mesmo com elementos por cima.
    const target = isFB ? window : canvas;
    target.addEventListener('mousemove', onMove);
    if (!isFB) canvas.addEventListener('mouseleave', onLeave);

    function step(ts) {
      const cx = W/2, cy = H/2;
      const { rCore, r1, r2, r3 } = getRadii();
      for (const n of nodes) {
        if (n.kind === 'hub') { n.x = cx; n.y = cy; continue; }
        const radius = n.kind==='core' ? rCore : n.ring===2 ? r1 : n.ring===3 ? r2 : r3;
        const rx = typeof radius === 'object' ? radius.rx : radius;
        const ry = typeof radius === 'object' ? radius.ry : radius;
        const a = n.baseAngle + ts * n.orbitSpeed;
        const wobble = Math.sin(ts*0.0008 + n.baseAngle*3) * 5;
        const tx = cx + Math.cos(a) * (rx + wobble);
        const ty = cy + Math.sin(a) * (ry + wobble);
        let mx = 0, my = 0;
        if (mouse.active) {
          const dx = n.x - mouse.x, dy = n.y - mouse.y;
          const d2 = dx*dx + dy*dy + 0.01;
          if (d2 < 18000) {
            const d = Math.sqrt(d2);
            const f = 35 / Math.max(1, d/8);
            mx = (dx/d) * f; my = (dy/d) * f;
          }
        }
        n.x += (tx + mx - n.x) * 0.07;
        n.y += (ty + my - n.y) * 0.07;
      }
      for (const e of edges) {
        e.pulses = e.pulses.filter(p => { p.t += p.speed; return p.t <= 1; });
      }
      for (const p of particles) {
        p.x += p.vx; p.y += p.vy;
        if (p.x < 0) p.x = 1; if (p.x > 1) p.x = 0;
        if (p.y < 0) p.y = 1; if (p.y > 1) p.y = 0;
      }
    }

    function draw(ts) {
      const cx = W/2, cy = H/2;
      const { rCore, r1, r2, r3 } = getRadii();

      // Backdrop: transparente (deixa o fundo da página passar)
      ctx.clearRect(0, 0, W, H);

      // Floating dust
      for (const p of particles) {
        ctx.fillStyle = `rgba(255,255,255,${p.a})`;
        ctx.beginPath(); ctx.arc(p.x*W, p.y*H, p.r, 0, Math.PI*2); ctx.fill();
      }

      // Subtle ring guides
      ctx.strokeStyle = 'rgba(255,255,255,.035)';
      ctx.lineWidth = 1;
      [rCore, r1, r2, r3].forEach(r => {
        const rx = typeof r === 'object' ? r.rx : r;
        const ry = typeof r === 'object' ? r.ry : r;
        ctx.beginPath();
        if (ctx.ellipse) ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI*2);
        else ctx.arc(cx, cy, rx, 0, Math.PI*2);
        ctx.stroke();
      });

      // EDGES with bezier curves
      ctx.lineCap = 'round';
      for (const e of edges) {
        const dx = e.b.x - e.a.x, dy = e.b.y - e.a.y;
        const d = Math.sqrt(dx*dx + dy*dy);
        if (d < 0.1) continue;
        const ux = dx/d, uy = dy/d;
        const x1 = e.a.x + ux * e.a.r, y1 = e.a.y + uy * e.a.r;
        const x2 = e.b.x - ux * e.b.r, y2 = e.b.y - uy * e.b.r;
        // Bezier control point: pulled slightly toward center for organic curve
        const mx = (x1+x2)/2, my = (y1+y2)/2;
        const tcx = cx + (mx - cx) * 0.55;
        const tcy = cy + (my - cy) * 0.55;
        const isHub = e.a.kind==='hub' || e.b.kind==='hub';
        const isCore = e.a.kind==='core' || e.b.kind==='core';
        ctx.strokeStyle = isHub ? 'rgba(255,255,255,.28)'
                       : isCore ? 'rgba(255,255,255,.13)'
                       : 'rgba(255,255,255,.07)';
        ctx.lineWidth = isHub ? 1.2 : 0.7;
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.quadraticCurveTo(tcx, tcy, x2, y2);
        ctx.stroke();

        // Pulses traveling along bezier
        for (const p of e.pulses) {
          const t = p.dir === 1 ? p.t : 1 - p.t;
          // Quadratic bezier point
          const bp = (t) => {
            const u = 1 - t;
            return [
              u*u*x1 + 2*u*t*tcx + t*t*x2,
              u*u*y1 + 2*u*t*tcy + t*t*y2,
            ];
          };
          const t0 = Math.max(0, t - 0.18);
          const [px, py] = bp(t);
          const [tx, ty] = bp(t0);
          const grad = ctx.createLinearGradient(tx, ty, px, py);
          grad.addColorStop(0, 'rgba(255,255,255,0)');
          grad.addColorStop(1, accent);
          ctx.strokeStyle = grad; ctx.lineWidth = 1.8;
          ctx.beginPath(); ctx.moveTo(tx, ty); ctx.lineTo(px, py); ctx.stroke();
          // Glowing dot
          ctx.shadowBlur = 12; ctx.shadowColor = accent;
          ctx.fillStyle = accent;
          ctx.beginPath(); ctx.arc(px, py, 2.6, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 0;
        }
      }

      // NODES with individual glow
      for (const n of nodes) {
        if (n.kind === 'hub') {
          // Massive halo
          const pulse = (Math.sin(ts*0.0018) + 1) / 2;
          const haloR = 70 + pulse * 18;
          const grad = ctx.createRadialGradient(n.x, n.y, n.r, n.x, n.y, haloR);
          grad.addColorStop(0, colorWithAlpha(accent, 0.55));
          grad.addColorStop(0.4, colorWithAlpha(accent, 0.18));
          grad.addColorStop(1, 'rgba(0,0,0,0)');
          ctx.fillStyle = grad;
          ctx.beginPath(); ctx.arc(n.x, n.y, haloR, 0, Math.PI*2); ctx.fill();
          // Inner ring
          ctx.strokeStyle = accent;
          ctx.lineWidth = 1.5;
          ctx.beginPath(); ctx.arc(n.x, n.y, n.r + 8 + pulse*3, 0, Math.PI*2); ctx.stroke();
        } else if (n.kind === 'core') {
          // Soft halo
          const grad = ctx.createRadialGradient(n.x, n.y, n.r, n.x, n.y, n.r + 14);
          grad.addColorStop(0, 'rgba(255,255,255,.25)');
          grad.addColorStop(1, 'rgba(255,255,255,0)');
          ctx.fillStyle = grad;
          ctx.beginPath(); ctx.arc(n.x, n.y, n.r + 14, 0, Math.PI*2); ctx.fill();
        } else if (n.ring === 2) {
          const grad = ctx.createRadialGradient(n.x, n.y, n.r, n.x, n.y, n.r + 8);
          grad.addColorStop(0, 'rgba(255,255,255,.12)');
          grad.addColorStop(1, 'rgba(255,255,255,0)');
          ctx.fillStyle = grad;
          ctx.beginPath(); ctx.arc(n.x, n.y, n.r + 8, 0, Math.PI*2); ctx.fill();
        }

        // Solid dot
        ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI*2);
        if (n.kind === 'hub') {
          ctx.shadowBlur = 24; ctx.shadowColor = accent;
          ctx.fillStyle = accent; ctx.fill();
          ctx.shadowBlur = 0;
          ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(255,255,255,.9)'; ctx.stroke();
        } else if (n.kind === 'core') {
          ctx.fillStyle = 'rgba(255,255,255,.95)'; ctx.fill();
        } else if (n.ring === 2) {
          ctx.fillStyle = 'rgba(255,255,255,.78)'; ctx.fill();
        } else if (n.ring === 3) {
          ctx.fillStyle = 'rgba(255,255,255,.55)'; ctx.fill();
        } else {
          ctx.fillStyle = 'rgba(255,255,255,.38)'; ctx.fill();
        }

        // Labels: hub + core + ring2 (RING1) sempre. Ring 3 (RING2) também em modo fullbleed.
        const isFB = wrap.classList.contains('hero-graph-fullbleed');
        if (n.kind === 'hub' || n.kind === 'core' || n.ring === 2 || (isFB && n.ring === 3)) {
          const text = n.kind === 'hub' ? 'ConectaAI' : n.label;
          ctx.font = n.kind === 'hub' ? '600 13px ui-sans-serif, system-ui'
                    : n.kind === 'core' ? '500 11px ui-monospace, monospace'
                    : n.ring === 2 ? '400 10px ui-monospace, monospace'
                    : '400 9px ui-monospace, monospace';
          ctx.textAlign = 'center'; ctx.textBaseline = 'top';
          const ty = n.y + n.r + 6;
          if (n.kind === 'hub' || n.kind === 'core') {
            const w = ctx.measureText(text).width;
            ctx.fillStyle = 'rgba(0,0,0,.55)';
            ctx.fillRect(n.x - w/2 - 4, ty - 1, w + 8, n.kind==='hub'?17:14);
          }
          ctx.fillStyle = n.kind === 'hub' ? '#fff'
                        : n.kind === 'core' ? 'rgba(255,255,255,.92)'
                        : n.ring === 2 ? 'rgba(255,255,255,.55)'
                        : 'rgba(255,255,255,.32)';
          ctx.fillText(text, n.x, ty);
        }
      }
    }

    let raf = 0, lastPulse = 0;
    const tick = (ts) => {
      if (ts - lastPulse > 200) {
        spawnPulse();
        if (Math.random() < 0.7) spawnPulse();
        if (Math.random() < 0.4) spawnPulse();
        lastPulse = ts;
      }
      step(ts); draw(ts);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      target.removeEventListener('mousemove', onMove);
      if (!isFB) canvas.removeEventListener('mouseleave', onLeave);
    };
  }, [accent]);

  return (
    <div ref={wrapRef} className="hero-graph hero-graph-constellation hero-graph-fullbleed">
      <canvas ref={canvasRef} />
      <div className="hero-graph-meta">
        <span>● contexto sendo puxado em tempo real</span>
        <span>72 nós · 100+ conexões ativas</span>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────
// VARIANT 2: ORBITAL 3D
// Rings tilted in perspective, planetary feel. Parallax via mouse.
// ─────────────────────────────────────────────────────────────────────────
const HeroGraphOrbital = ({ accent = "oklch(0.82 0.18 125)" }) => {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;

    const { CORE, RING1, RING2, RING3 } = GRAPH_DATA;

    // Each ring orbits at a tilted angle (perspective by squashing y)
    const RINGS = [
      { items: CORE.map(c => c.label), labelKind: 'core', tilt: 0.42, baseRadius: 0.20, speed: 0.00006, r: 8 },
      { items: RING1, labelKind: 'leaf1', tilt: 0.38, baseRadius: 0.32, speed: -0.00004, r: 5.5 },
      { items: RING2, labelKind: 'leaf2', tilt: 0.34, baseRadius: 0.42, speed: 0.000028, r: 4 },
      { items: RING3, labelKind: 'leaf3', tilt: 0.30, baseRadius: 0.52, speed: -0.000018, r: 3 },
    ];
    // Build flat node list
    const nodes = [{ id:'hub', kind:'hub', r: 18, label: 'ConectaAI' }];
    RINGS.forEach((ring, ri) => {
      ring.items.forEach((label, i) => {
        nodes.push({
          id: `r${ri}_${i}`, kind: ring.labelKind, label, r: ring.r,
          ringIdx: ri, indexInRing: i, totalInRing: ring.items.length,
        });
      });
    });
    nodes.forEach(n => { n.x = 0; n.y = 0; n.depth = 0; });

    // Edges: hub→core, then chain
    const edges = [];
    // hub to all core
    nodes.filter(n => n.ringIdx === 0).forEach(n => edges.push({ a: nodes[0], b: n, pulses: [] }));
    // core → ring1 (each core to 2)
    const coreNodes = nodes.filter(n => n.ringIdx === 0);
    const r1Nodes = nodes.filter(n => n.ringIdx === 1);
    r1Nodes.forEach((n, i) => {
      const c = coreNodes[Math.floor(i/2) % coreNodes.length];
      edges.push({ a: c, b: n, pulses: [] });
    });

    function getCenter() { return { cx: W/2, cy: H * 0.5 }; }
    function project(ringIdx, t, ts) {
      const ring = RINGS[ringIdx];
      const m = Math.min(W, H);
      const radius = ring.baseRadius * m;
      const a = t * Math.PI * 2 + ts * ring.speed;
      const { cx, cy } = getCenter();
      const x = cx + Math.cos(a) * radius;
      const y = cy + Math.sin(a) * radius * ring.tilt; // squash y for perspective
      const depth = Math.sin(a); // -1 (back) to 1 (front)
      return { x, y, depth };
    }
    function spawnPulse() {
      const e = edges[Math.floor(Math.random() * edges.length)];
      e.pulses.push({ t: 0, dir: Math.random()<0.5?1:-1, speed: 0.006 + Math.random()*0.012 });
    }
    function resize() {
      const rect = wrap.getBoundingClientRect();
      W = Math.max(320, Math.min(2200, rect.width || 800));
      H = Math.max(320, Math.min(900, rect.height || 600));
      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);
    }
    resize();
    const ro = new ResizeObserver(resize); ro.observe(wrap);

    const mouse = { x: 0, y: 0 };
    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      mouse.x = (e.clientX - r.left - W/2) / W;
      mouse.y = (e.clientY - r.top - H/2) / H;
    };
    const onLeave = () => { mouse.x = 0; mouse.y = 0; };
    canvas.addEventListener('mousemove', onMove);
    canvas.addEventListener('mouseleave', onLeave);

    function draw(ts) {
      const { cx, cy } = getCenter();
      // Backdrop
      const bg = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(W,H)*0.65);
      bg.addColorStop(0, 'rgba(22, 26, 32, 1)');
      bg.addColorStop(0.5, 'rgba(10, 12, 16, 1)');
      bg.addColorStop(1, 'rgba(3, 4, 6, 1)');
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, W, H);

      // Update positions
      const parallaxX = mouse.x * 24;
      const parallaxY = mouse.y * 14;
      nodes[0].x = cx + parallaxX * 0.3;
      nodes[0].y = cy + parallaxY * 0.3;
      nodes[0].depth = 1;

      for (const n of nodes) {
        if (n.kind === 'hub') continue;
        const t = n.indexInRing / n.totalInRing;
        const p = project(n.ringIdx, t, ts);
        // Apply parallax stronger to outer rings
        n.x = p.x + parallaxX * (0.4 + n.ringIdx * 0.15);
        n.y = p.y + parallaxY * (0.4 + n.ringIdx * 0.15);
        n.depth = p.depth;
      }

      // Draw orbital ellipses (faint)
      ctx.strokeStyle = 'rgba(255,255,255,.05)';
      ctx.lineWidth = 1;
      RINGS.forEach((ring) => {
        const m = Math.min(W, H);
        const rx = ring.baseRadius * m;
        const ry = rx * ring.tilt;
        ctx.beginPath();
        ctx.ellipse(cx + parallaxX * 0.5, cy + parallaxY * 0.5, rx, ry, 0, 0, Math.PI*2);
        ctx.stroke();
      });

      // Draw edges (sorted by avg depth so back ones first)
      const sortedEdges = [...edges].sort((a, b) => (a.a.depth + a.b.depth) - (b.a.depth + b.b.depth));
      for (const e of sortedEdges) {
        const dx = e.b.x - e.a.x, dy = e.b.y - e.a.y;
        const d = Math.sqrt(dx*dx + dy*dy);
        if (d < 0.1) continue;
        const ux = dx/d, uy = dy/d;
        const x1 = e.a.x + ux * e.a.r, y1 = e.a.y + uy * e.a.r;
        const x2 = e.b.x - ux * e.b.r, y2 = e.b.y - uy * e.b.r;
        const avgDepth = (e.a.depth + e.b.depth) / 2;
        const opacity = 0.08 + (avgDepth + 1) * 0.10;
        const isHub = e.a.kind === 'hub' || e.b.kind === 'hub';
        ctx.strokeStyle = `rgba(255,255,255,${isHub ? opacity*1.6 : opacity})`;
        ctx.lineWidth = isHub ? 1.1 : 0.7;
        ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();

        for (const p of e.pulses) {
          const t = p.dir === 1 ? p.t : 1 - p.t;
          const px = x1 + (x2-x1)*t, py = y1 + (y2-y1)*t;
          const t0 = Math.max(0, t - 0.16);
          const tx = x1 + (x2-x1)*t0, ty = y1 + (y2-y1)*t0;
          const grad = ctx.createLinearGradient(tx, ty, px, py);
          grad.addColorStop(0, 'rgba(255,255,255,0)');
          grad.addColorStop(1, accent);
          ctx.strokeStyle = grad; ctx.lineWidth = 1.6;
          ctx.beginPath(); ctx.moveTo(tx, ty); ctx.lineTo(px, py); ctx.stroke();
          ctx.shadowBlur = 12; ctx.shadowColor = accent;
          ctx.fillStyle = accent;
          ctx.beginPath(); ctx.arc(px, py, 2.4, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 0;
        }
        e.pulses = e.pulses.filter(p => { p.t += p.speed; return p.t <= 1; });
      }

      // Draw nodes back-to-front (sort by depth)
      const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
      for (const n of sortedNodes) {
        const depthFactor = (n.depth + 1) / 2; // 0 (back) to 1 (front)
        const sizeMul = 0.6 + depthFactor * 0.6; // back=0.6x, front=1.2x
        const r = n.r * sizeMul;

        if (n.kind === 'hub') {
          const pulse = (Math.sin(ts*0.0018) + 1) / 2;
          const haloR = 75 + pulse * 18;
          const grad = ctx.createRadialGradient(n.x, n.y, n.r, n.x, n.y, haloR);
          grad.addColorStop(0, colorWithAlpha(accent, 0.5));
          grad.addColorStop(0.4, colorWithAlpha(accent, 0.18));
          grad.addColorStop(1, 'rgba(0,0,0,0)');
          ctx.fillStyle = grad;
          ctx.beginPath(); ctx.arc(n.x, n.y, haloR, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 24; ctx.shadowColor = accent;
          ctx.fillStyle = accent;
          ctx.beginPath(); ctx.arc(n.x, n.y, n.r, 0, Math.PI*2); ctx.fill();
          ctx.shadowBlur = 0;
          ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(255,255,255,.9)'; ctx.stroke();
        } else {
          const alpha = 0.35 + depthFactor * 0.55;
          ctx.fillStyle = `rgba(255,255,255,${alpha})`;
          ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI*2); ctx.fill();
        }

        // Labels for hub + core + front-half ring1
        const showLabel = n.kind === 'hub' ||
                         (n.ringIdx === 0) ||
                         (n.ringIdx === 1 && n.depth > -0.2);
        if (showLabel) {
          const text = n.kind === 'hub' ? 'ConectaAI' : n.label;
          ctx.font = n.kind === 'hub' ? '600 13px ui-sans-serif, system-ui'
                    : n.ringIdx === 0 ? '500 11px ui-monospace, monospace'
                    : '400 10px ui-monospace, monospace';
          ctx.textAlign = 'center'; ctx.textBaseline = 'top';
          const ty = n.y + r + 6;
          if (n.kind === 'hub' || n.ringIdx === 0) {
            const w = ctx.measureText(text).width;
            ctx.fillStyle = 'rgba(0,0,0,.55)';
            ctx.fillRect(n.x - w/2 - 4, ty - 1, w + 8, n.kind==='hub'?17:14);
          }
          ctx.fillStyle = n.kind === 'hub' ? '#fff'
                        : n.ringIdx === 0 ? 'rgba(255,255,255,.92)'
                        : `rgba(255,255,255,${0.4 + depthFactor*0.4})`;
          ctx.fillText(text, n.x, ty);
        }
      }
    }

    let raf = 0, lastPulse = 0;
    const tick = (ts) => {
      if (ts - lastPulse > 220) {
        spawnPulse();
        if (Math.random() < 0.6) spawnPulse();
        lastPulse = ts;
      }
      draw(ts);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      canvas.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('mouseleave', onLeave);
    };
  }, [accent]);

  return (
    <div ref={wrapRef} className="hero-graph hero-graph-orbital">
      <canvas ref={canvasRef} />
      <div className="hero-graph-meta">
        <span>● contexto sendo puxado em tempo real</span>
        <span>órbita planetária · 72 nós ativos</span>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────
// VARIANT 3: DATAFLOW HORIZONTAL
// Left: source systems. Center: ConectaAI agent core. Right: actions.
// Pulses travel left→right and right→left through the agent.
// ─────────────────────────────────────────────────────────────────────────
const HeroGraphDataflow = ({ accent = "oklch(0.82 0.18 125)" }) => {
  const canvasRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    let W = 0, H = 0;

    const SOURCES = [
      'IXC Provedor','Hubsoft','IXC ACS',
      'WhatsApp Business','Conecta SIP-AI','Twilio',
      'Opa! Suite','Trello','E-mail',
      'OpenAI','Anthropic','Postgres',
      'Webhooks','REST API','SDK',
    ];
    const ACTIONS = [
      'Resolver L1',
      'Emitir 2ª via',
      'Reset do CPE',
      'Diagnóstico de link',
      'Status do plano',
      'Agendar visita',
      'Negociar acordo',
      'Atualizar CRM',
      'Abrir chamado',
      'Encaminhar humano',
      'Notificar técnico',
      'Pesquisa CSAT',
    ];

    let sources = [], actions = [];
    function layout() {
      sources = SOURCES.map((label, i) => ({
        label,
        x: W * 0.12,
        y: 0,
        idx: i, total: SOURCES.length,
      }));
      actions = ACTIONS.map((label, i) => ({
        label,
        x: W * 0.88,
        y: 0,
        idx: i, total: ACTIONS.length,
      }));
      const padTop = 30, padBot = 30;
      sources.forEach((s, i) => { s.y = padTop + (i / (SOURCES.length - 1)) * (H - padTop - padBot); });
      actions.forEach((a, i) => { a.y = padTop + (i / (ACTIONS.length - 1)) * (H - padTop - padBot); });
    }

    // Pulses: source → hub → action
    const pulses = [];
    function spawnPulse() {
      const src = sources[Math.floor(Math.random() * sources.length)];
      const act = actions[Math.floor(Math.random() * actions.length)];
      if (!src || !act) return;
      pulses.push({ src, act, t: 0, speed: 0.004 + Math.random() * 0.005 });
    }

    function resize() {
      const rect = wrap.getBoundingClientRect();
      W = Math.max(320, Math.min(2200, rect.width || 800));
      H = Math.max(320, Math.min(900, rect.height || 600));
      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);
      layout();
    }
    resize();
    const ro = new ResizeObserver(resize); ro.observe(wrap);

    function bezierPoint(t, p0, p1, p2, p3) {
      const u = 1 - t;
      return [
        u*u*u*p0[0] + 3*u*u*t*p1[0] + 3*u*t*t*p2[0] + t*t*t*p3[0],
        u*u*u*p0[1] + 3*u*u*t*p1[1] + 3*u*t*t*p2[1] + t*t*t*p3[1],
      ];
    }

    function draw(ts) {
      // Backdrop
      const cx = W/2, cy = H/2;
      const bg = ctx.createLinearGradient(0, 0, W, 0);
      bg.addColorStop(0, 'rgba(8, 10, 14, 1)');
      bg.addColorStop(0.5, 'rgba(15, 18, 24, 1)');
      bg.addColorStop(1, 'rgba(8, 10, 14, 1)');
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, W, H);

      // Hub area glow
      const hubGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.min(W, H) * 0.35);
      hubGrad.addColorStop(0, colorWithAlpha(accent, 0.10));
      hubGrad.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = hubGrad;
      ctx.fillRect(0, 0, W, H);

      // Column labels
      ctx.font = '500 10px ui-monospace, monospace';
      ctx.textBaseline = 'top';
      ctx.fillStyle = 'rgba(255,255,255,.4)';
      ctx.textAlign = 'left';
      ctx.fillText('SISTEMAS · 26', 24, 14);
      ctx.textAlign = 'right';
      ctx.fillText('AÇÕES · 12', W - 24, 14);
      ctx.textAlign = 'center';
      ctx.fillText('AGENTE', cx, 14);

      // Faint divider lines
      ctx.strokeStyle = 'rgba(255,255,255,.05)';
      ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(W * 0.30, 28); ctx.lineTo(W * 0.30, H - 24); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(W * 0.70, 28); ctx.lineTo(W * 0.70, H - 24); ctx.stroke();

      // Background connection lines (faint, all sources to hub, hub to all actions)
      ctx.lineCap = 'round';
      for (const s of sources) {
        ctx.strokeStyle = 'rgba(255,255,255,.05)';
        ctx.lineWidth = 0.6;
        const p0 = [s.x + 6, s.y];
        const p1 = [W * 0.32, s.y];
        const p2 = [W * 0.42, cy];
        const p3 = [cx - 30, cy];
        ctx.beginPath(); ctx.moveTo(p0[0], p0[1]);
        ctx.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
        ctx.stroke();
      }
      for (const a of actions) {
        ctx.strokeStyle = 'rgba(255,255,255,.05)';
        ctx.lineWidth = 0.6;
        const p0 = [cx + 30, cy];
        const p1 = [W * 0.58, cy];
        const p2 = [W * 0.68, a.y];
        const p3 = [a.x - 6, a.y];
        ctx.beginPath(); ctx.moveTo(p0[0], p0[1]);
        ctx.bezierCurveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
        ctx.stroke();
      }

      // Pulses
      for (let i = pulses.length - 1; i >= 0; i--) {
        const p = pulses[i];
        p.t += p.speed;
        if (p.t > 1) { pulses.splice(i, 1); continue; }
        // 0 → 0.5: source → hub. 0.5 → 1: hub → action.
        let pos, segT;
        if (p.t < 0.5) {
          segT = p.t * 2;
          const p0 = [p.src.x + 6, p.src.y];
          const p1 = [W * 0.32, p.src.y];
          const p2 = [W * 0.42, cy];
          const p3 = [cx - 30, cy];
          pos = bezierPoint(segT, p0, p1, p2, p3);
        } else {
          segT = (p.t - 0.5) * 2;
          const p0 = [cx + 30, cy];
          const p1 = [W * 0.58, cy];
          const p2 = [W * 0.68, p.act.y];
          const p3 = [p.act.x - 6, p.act.y];
          pos = bezierPoint(segT, p0, p1, p2, p3);
        }
        // Trail
        const trail = 8;
        for (let k = 0; k < trail; k++) {
          const tk = Math.max(0, p.t - k * 0.012);
          let pk;
          if (tk < 0.5) {
            const stk = tk * 2;
            const p0 = [p.src.x + 6, p.src.y];
            const p1 = [W * 0.32, p.src.y];
            const p2 = [W * 0.42, cy];
            const p3 = [cx - 30, cy];
            pk = bezierPoint(stk, p0, p1, p2, p3);
          } else {
            const stk = (tk - 0.5) * 2;
            const p0 = [cx + 30, cy];
            const p1 = [W * 0.58, cy];
            const p2 = [W * 0.68, p.act.y];
            const p3 = [p.act.x - 6, p.act.y];
            pk = bezierPoint(stk, p0, p1, p2, p3);
          }
          const alpha = (1 - k / trail) * 0.35;
          ctx.fillStyle = colorWithAlpha(accent, alpha);
          ctx.beginPath(); ctx.arc(pk[0], pk[1], 1.8, 0, Math.PI*2); ctx.fill();
        }
        ctx.shadowBlur = 12; ctx.shadowColor = accent;
        ctx.fillStyle = accent;
        ctx.beginPath(); ctx.arc(pos[0], pos[1], 2.6, 0, Math.PI*2); ctx.fill();
        ctx.shadowBlur = 0;
      }

      // Source nodes (left column)
      for (const s of sources) {
        ctx.fillStyle = 'rgba(255,255,255,.85)';
        ctx.beginPath(); ctx.arc(s.x, s.y, 3.5, 0, Math.PI*2); ctx.fill();
        ctx.font = '400 11px ui-monospace, monospace';
        ctx.textAlign = 'right';
        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'rgba(255,255,255,.78)';
        ctx.fillText(s.label, s.x - 10, s.y);
      }

      // Action nodes (right column)
      for (const a of actions) {
        ctx.fillStyle = accent;
        ctx.shadowBlur = 6; ctx.shadowColor = accent;
        ctx.beginPath(); ctx.arc(a.x, a.y, 3.8, 0, Math.PI*2); ctx.fill();
        ctx.shadowBlur = 0;
        ctx.font = '400 11px ui-monospace, monospace';
        ctx.textAlign = 'left';
        ctx.textBaseline = 'middle';
        ctx.fillStyle = 'rgba(255,255,255,.85)';
        ctx.fillText(a.label, a.x + 10, a.y);
      }

      // Hub: large central pill
      const pulse = (Math.sin(ts*0.002) + 1) / 2;
      const hubR = 38 + pulse * 4;
      const haloR = hubR + 30 + pulse * 12;
      const halo = ctx.createRadialGradient(cx, cy, hubR, cx, cy, haloR);
      halo.addColorStop(0, colorWithAlpha(accent, 0.4));
      halo.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = halo;
      ctx.beginPath(); ctx.arc(cx, cy, haloR, 0, Math.PI*2); ctx.fill();

      ctx.shadowBlur = 28; ctx.shadowColor = accent;
      ctx.fillStyle = accent;
      ctx.beginPath(); ctx.arc(cx, cy, hubR, 0, Math.PI*2); ctx.fill();
      ctx.shadowBlur = 0;
      ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(255,255,255,.9)';
      ctx.beginPath(); ctx.arc(cx, cy, hubR, 0, Math.PI*2); ctx.stroke();

      // Hub label
      ctx.font = '600 16px ui-sans-serif, system-ui';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillStyle = '#0a0a0a';
      ctx.fillText('ConectaAI', cx, cy - 3);
      ctx.font = '400 9px ui-monospace, monospace';
      ctx.fillStyle = 'rgba(0,0,0,.7)';
      ctx.fillText('72 nós · agente ativo', cx, cy + 13);
    }

    let raf = 0, lastPulse = 0;
    const tick = (ts) => {
      if (ts - lastPulse > 140) {
        spawnPulse();
        if (Math.random() < 0.7) spawnPulse();
        lastPulse = ts;
      }
      draw(ts);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
    };
  }, [accent]);

  return (
    <div ref={wrapRef} className="hero-graph hero-graph-dataflow">
      <canvas ref={canvasRef} />
      <div className="hero-graph-meta">
        <span>● dados fluindo em tempo real</span>
        <span>26 sistemas → ConectaAI → 12 ações</span>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────
// Selector
// ─────────────────────────────────────────────────────────────────────────
const HeroGraphPicker = ({ style = 'constellation', accent }) => {
  if (style === 'orbital') return <HeroGraphOrbital accent={accent} />;
  if (style === 'dataflow') return <HeroGraphDataflow accent={accent} />;
  return <HeroGraphConstellation accent={accent} />;
};

Object.assign(window, {
  HeroGraphConstellation,
  HeroGraphOrbital,
  HeroGraphDataflow,
  HeroGraphPicker,
});
