/* ============================================================ CentralAR.jsx — "Central Inteligente de Atendimento AR" Cena em camadas: Cliente (WhatsApp) → Agente IA AR → CRM/Agenda/ Planilha/Equipe. Linhas curvas luminosas, partículas, dados em trânsito, tilt 3D no mouse, destaque do bloco mais próximo. ============================================================ */ const CentralAR = ({ tilt = { x: 0, y: 0 } }) => { const wrapRef = React.useRef(null); const waRef = React.useRef(null); const agentRef = React.useRef(null); const cardRefs = React.useRef([]); const [geo, setGeo] = React.useState(null); const [near, setNear] = React.useState(null); // 'wa' | 'agent' | 0..3 const [hovering, setHovering] = React.useState(false); const rafRef = React.useRef(0); const dests = [ { id: 'crm', icon: 'user-check', label: 'Lead qualificado', sub: 'CRM · score 88', tone: '#1FD17B' }, { id: 'agenda', icon: 'calendar-check', label: 'Reunião sugerida', sub: 'Agenda · ter 14h', tone: '#00D4FF' }, { id: 'follow', icon: 'bell-ring', label: 'Follow-up programado', sub: 'em 2 dias', tone: '#7CB0FF' }, { id: 'sheet', icon: 'table-2', label: 'Planilha atualizada', sub: 'linha +1', tone: '#3D8BFF' }, ]; // ---- measurement (responsive line geometry) ---- React.useLayoutEffect(() => { const measure = () => { const wrap = wrapRef.current; if (!wrap || !waRef.current || !agentRef.current) return; const cr = wrap.getBoundingClientRect(); const rect = (el) => { const r = el.getBoundingClientRect(); return { x: r.left - cr.left + r.width / 2, y: r.top - cr.top + r.height / 2, left: r.left - cr.left, right: r.right - cr.left, top: r.top - cr.top, bottom: r.bottom - cr.top, }; }; const isCol = cr.width < 560; setGeo({ w: cr.width, h: cr.height, isCol, wa: rect(waRef.current), agent: rect(agentRef.current), cards: cardRefs.current.filter(Boolean).map(rect), }); }; measure(); const ro = new ResizeObserver(measure); if (wrapRef.current) ro.observe(wrapRef.current); window.addEventListener('resize', measure); const t = setTimeout(measure, 350); // após fontes/imagens return () => { ro.disconnect(); window.removeEventListener('resize', measure); clearTimeout(t); }; }, []); React.useEffect(() => { window.lucide && window.lucide.createIcons(); }, [geo]); // ---- nearest-block detection (throttled w/ rAF) ---- const onSceneMove = (e) => { if (!geo || !wrapRef.current) return; if (rafRef.current) return; rafRef.current = requestAnimationFrame(() => { rafRef.current = 0; const cr = wrapRef.current.getBoundingClientRect(); const mx = e.clientX - cr.left, my = e.clientY - cr.top; const pts = [['wa', geo.wa], ['agent', geo.agent], ...geo.cards.map((c, i) => [i, c])]; let best = null, bd = Infinity; for (const [id, p] of pts) { const d = (p.x - mx) ** 2 + (p.y - my) ** 2; if (d < bd) { bd = d; best = id; } } setNear(bd < 200 * 200 ? best : null); }); }; // ---- path helpers ---- const anchorOut = (r) => geo.isCol ? { x: r.x, y: r.bottom } : { x: r.right, y: r.y }; const anchorIn = (r) => geo.isCol ? { x: r.x, y: r.top } : { x: r.left, y: r.y }; const curve = (a, b, towardA) => { const cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2; const ctrl = towardA ? { x: cx, y: a.y } : { x: cx, y: b.y }; if (geo.isCol) { ctrl.x = a.x; ctrl.y = cy; } return `M ${a.x} ${a.y} Q ${ctrl.x} ${ctrl.y} ${b.x} ${b.y}`; }; const particles = React.useMemo(() => Array.from({ length: 16 }, (_, i) => ({ left: Math.random() * 100, top: Math.random() * 100, size: 1.5 + Math.random() * 2.5, delay: Math.random() * 6, dur: 7 + Math.random() * 7, })), []); const tx = tilt.x * 12, ty = tilt.y * 12; // build line set let lines = []; if (geo) { const aIn = anchorIn(geo.agent), aOut = anchorOut(geo.agent); lines.push({ key: 'in', d: curve(anchorOut(geo.wa), aIn, false), active: near === 'wa' || near === 'agent', dur: 2.0, color: '#25D366' }); geo.cards.forEach((c, i) => { lines.push({ key: 'o' + i, d: curve(aOut, anchorIn(c), true), active: near === i || near === 'agent', dur: 1.7 + i * 0.18, begin: (i * 0.4) + 's', color: dests[i].tone }); }); } const geoKey = geo ? `${Math.round(geo.w)}x${Math.round(geo.h)}` : '0'; return (