/* ============================================================ 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 (
setHovering(true)} onMouseLeave={() => { setHovering(false); setNear(null); }} style={{ position: 'relative', width: '100%', minHeight: 460, transform: `perspective(1200px) rotateY(${tilt.x * 4}deg) rotateX(${-tilt.y * 4}deg)`, transformStyle: 'preserve-3d', transition: 'transform 240ms var(--ar-ease)', }} > {/* ambient glow */}
{/* particles */}
{particles.map((p, i) => ( ))}
{/* connecting lines */} {geo && ( {lines.map(l => ( {/* soft glow underlay */} {/* crisp line */} {/* travelling data dot */} ))} )} {/* ===== layout: 3 columns (row) / stacked (col) ===== */}
{/* --- WhatsApp client --- */}
Cliente
online · WhatsApp
Olá! Vocês atendem hoje? Queria agendar uma reunião
{[0, 1, 2].map(d => )}
{/* --- AI Agent core --- */}
AR
Agente IA AR
analisando intenção
intenção: agendamento
{/* --- CRM / destinations --- */}
{dests.map((d, i) => (
cardRefs.current[i] = el} style={carDestCardStyle(near === i)}>
{d.label}
{d.sub}
))}
{/* live badge */}
resolvido em 38s
); }; /* ---- sub-pieces ---- */ const carWaCardStyle = (active) => ({ borderRadius: 16, overflow: 'hidden', width: '100%', background: 'var(--ar-bg-card)', border: `1px solid ${active ? 'rgba(0,212,255,0.55)' : 'rgba(255,255,255,0.10)'}`, boxShadow: active ? '0 26px 60px -24px rgba(0,0,0,0.8), 0 0 30px -6px rgba(0,212,255,0.4)' : '0 22px 50px -22px rgba(0,0,0,0.72)', transition: 'border-color 240ms, box-shadow 240ms', }); const carDestCardStyle = (active) => ({ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 13, background: active ? 'linear-gradient(135deg, rgba(0,94,255,0.18), rgba(16,24,43,0.96))' : 'var(--ar-bg-card)', border: `1px solid ${active ? 'rgba(0,212,255,0.5)' : 'rgba(255,255,255,0.10)'}`, boxShadow: active ? '0 18px 40px -18px rgba(0,0,0,0.8), 0 0 22px -8px rgba(0,212,255,0.45)' : '0 12px 30px -16px rgba(0,0,0,0.6)', transition: 'border-color 240ms, box-shadow 240ms, background 240ms', }); const CarBubble = ({ children, delay }) => (
{children}
); /* CarTilt — parallax + subtle 3D tilt on hover */ const CarTilt = ({ children, parallax = { x: 0, y: 0 }, active, compact, style }) => { const ref = React.useRef(null); const [t, setT] = React.useState({ rx: 0, ry: 0 }); const onMove = (e) => { const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); const px = (e.clientX - r.left) / r.width - 0.5; const py = (e.clientY - r.top) / r.height - 0.5; const m = compact ? 6 : 8; setT({ rx: -py * m, ry: px * m }); }; return (
setT({ rx: 0, ry: 0 })} style={{ transform: `translate(${parallax.x}px, ${parallax.y}px) perspective(800px) rotateX(${t.rx}deg) rotateY(${t.ry}deg)`, transition: 'transform 180ms var(--ar-ease)', transformStyle: 'preserve-3d', willChange: 'transform', ...style, }} >{children}
); }; window.CentralAR = CentralAR;