/* global React, ReactDOM */ const { useState: uS, useEffect: uE, useRef: uRf } = React; const Ic = window.SBIcon; /* ---- CURSOR ---- */ const isTouch = () => window.matchMedia('(hover: none)').matches; function Cursor() { const ref = uRf(null); uE(() => { if (isTouch()) return; // desabilita em touch devices const el = ref.current; let mx = -100, my = -100; const move = e => { mx = e.clientX; my = e.clientY; el.style.left = mx + 'px'; el.style.top = my + 'px'; }; const down = e => { el.classList.add('clicking'); const r = document.createElement('div'); r.className = 'cursor-ripple'; r.style.left = e.clientX + 'px'; r.style.top = e.clientY + 'px'; document.body.appendChild(r); setTimeout(() => r.remove(), 600); }; const up = () => el.classList.remove('clicking'); const over = e => { if (e.target.closest('a,button')) el.classList.add('hovering'); }; const out = e => { if (e.target.closest('a,button')) el.classList.remove('hovering'); }; document.addEventListener('mousemove', move); document.addEventListener('mousedown', down); document.addEventListener('mouseup', up); document.addEventListener('mouseover', over); document.addEventListener('mouseout', out); return () => { document.removeEventListener('mousemove', move); document.removeEventListener('mousedown', down); document.removeEventListener('mouseup', up); document.removeEventListener('mouseover', over); document.removeEventListener('mouseout', out); }; }, []); if (isTouch()) return null; return
; } /* ---- SCROLL REVEAL ---- */ function observeReveal(io) { document.querySelectorAll('.reveal:not(.visible)').forEach(el => io.observe(el)); } function useReveal() { uE(() => { /* reveal geral */ const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); } }); }, { threshold: 0.12 }); observeReveal(io); // re-observa após 300ms para pegar qualquer elemento adicionado depois do mount const t = setTimeout(() => observeReveal(io), 300); /* linhas divisórias nas seções */ const secIo = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); }); }, { threshold: 0.1 }); document.querySelectorAll('.section').forEach(el => secIo.observe(el)); /* compare rows em cascata */ const rowIo = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { const rows = e.target.querySelectorAll('.compare-row'); rows.forEach((r, i) => setTimeout(() => r.classList.add('visible'), i * 80)); rowIo.unobserve(e.target); } }); }, { threshold: 0.1 }); document.querySelectorAll('.compare').forEach(el => rowIo.observe(el)); /* ∞ refaz ao voltar pro topo */ const heroIo = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { const counter = document.querySelector('.inf-counter'); if (counter) { counter.classList.remove('inf-done'); counter.style.filter = 'blur(3.5px)'; counter.textContent = '0'; } } }); }, { threshold: 0.5 }); const heroEl = document.querySelector('.hero'); if (heroEl) heroIo.observe(heroEl); return () => { clearTimeout(t); io.disconnect(); rowIo.disconnect(); heroIo.disconnect(); }; }, []); } /* ---------------- STARFIELD ---------------- */ function Starfield() { const ref = uRf(null); uE(() => { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; const c = ref.current, ctx = c.getContext('2d'); let paused = false; const visObs = new IntersectionObserver(([e]) => { paused = !e.isIntersecting; }, { threshold: 0 }); visObs.observe(c); let raf, w, h, stars = [], shooting = [], t = 0, frameCount = 0; const dpr = Math.min(window.devicePixelRatio || 1, 2); function mkStar(cx, cy) { const a = Math.random() * Math.PI * 2; const r = Math.pow(Math.random(), 0.55) * Math.max(w, h) * 0.62; const x = cx + Math.cos(a) * r * (0.92 + Math.random() * 0.3); const y = cy + Math.sin(a) * r * 0.72; const d = Math.hypot(x - cx, y - cy) / (Math.max(w, h) * 0.6); const b = Math.max(0, 1 - d) * (0.4 + Math.random() * 0.6); /* drift: velocidade suave aleatória */ return { x, y, ox: x, oy: y, /* posição base */ s: (Math.random() * 1.3 + 0.2) * dpr, b, tw: Math.random() * 6.283, sp: 0.6 + Math.random() * 1.8, /* velocidade de piscar mais variada */ dx: (Math.random() - 0.5) * 0.08 * dpr, /* drift x */ dy: (Math.random() - 0.5) * 0.04 * dpr, /* drift y */ dr: Math.random() * 6.283, /* fase do drift */ dsp: 0.2 + Math.random() * 0.5, /* velocidade do drift */ }; } function build() { stars = []; const n = Math.floor((w * h) / (1700 * dpr * dpr)); const cx = w * 0.5, cy = h * 0.3; for (let i = 0; i < n; i++) stars.push(mkStar(cx, cy)); } function spawnShooting() { /* estrela cadente parte do topo-direita em diagonal */ const sx = w * (0.3 + Math.random() * 0.6); const sy = h * (Math.random() * 0.35); const angle = Math.PI * (0.18 + Math.random() * 0.14); /* ~30-40° */ shooting.push({ x: sx, y: sy, len: (80 + Math.random() * 140) * dpr, angle, life: 1, spd: (6 + Math.random() * 6) * dpr }); } function resize() { w = c.width = window.innerWidth * dpr; h = c.height = window.innerHeight * dpr; c.style.width = window.innerWidth + 'px'; c.style.height = window.innerHeight + 'px'; build(); } /* mouse */ let mx = -9999, my = -9999; const onMouse = e => { mx = e.clientX * dpr; my = e.clientY * dpr; }; window.addEventListener('mousemove', onMouse, { passive: true }); let nextShoot = 3; function draw() { if (paused) { raf = requestAnimationFrame(draw); return; } ctx.clearRect(0, 0, w, h); frameCount++; /* glow central */ const g = ctx.createRadialGradient(w * 0.5, h * 0.28, 0, w * 0.5, h * 0.28, h * 0.62); g.addColorStop(0, 'rgba(58,60,68,0.18)'); g.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = g; ctx.fillRect(0, 0, w, h); t += 0.022; /* ---- estrelas ---- */ for (const st of stars) { /* piscar: dupla frequência para efeito mais orgânico */ const tw = 0.45 + 0.35 * Math.sin(t * st.sp + st.tw) + 0.2 * Math.sin(t * st.sp * 2.3 + st.tw * 1.7); /* drift orbital suave */ let ox = st.ox + Math.sin(t * st.dsp + st.dr) * st.dx * 18; let oy = st.oy + Math.cos(t * st.dsp + st.dr) * st.dy * 14; /* repulsão do cursor */ const dist = Math.hypot(ox - mx, oy - my); const repel = 90 * dpr; if (dist < repel && dist > 0) { const force = (1 - dist / repel) * 22 * dpr; ox += (ox - mx) / dist * force; oy += (oy - my) / dist * force; } const alpha = Math.min(1, Math.max(0, st.b * tw)); /* tamanho pulsa levemente */ const sr = st.s * (0.85 + 0.15 * Math.sin(t * st.sp * 1.4 + st.tw)); ctx.globalAlpha = alpha; /* estrelas mais brilhantes ganham glow — calculado a cada 2 frames */ if (alpha > 0.7 && sr > 1.2 * dpr && frameCount % 2 === 0) { const grd = ctx.createRadialGradient(ox, oy, 0, ox, oy, sr * 3); grd.addColorStop(0, 'rgba(210,215,225,0.6)'); grd.addColorStop(1, 'rgba(210,215,225,0)'); ctx.fillStyle = grd; ctx.beginPath(); ctx.arc(ox, oy, sr * 3, 0, 6.283); ctx.fill(); } ctx.fillStyle = '#d4d8e2'; ctx.beginPath(); ctx.arc(ox, oy, sr, 0, 6.283); ctx.fill(); } /* ---- estrelas cadentes ---- */ nextShoot -= 0.022; if (nextShoot <= 0) { spawnShooting(); nextShoot = 4 + Math.random() * 8; } for (let i = shooting.length - 1; i >= 0; i--) { const sh = shooting[i]; sh.x += Math.cos(sh.angle) * sh.spd; sh.y += Math.sin(sh.angle) * sh.spd; sh.life -= 0.028; if (sh.life <= 0 || sh.x > w || sh.y > h) { shooting.splice(i, 1); continue; } const tx = sh.x - Math.cos(sh.angle) * sh.len; const ty = sh.y - Math.sin(sh.angle) * sh.len; const grad = ctx.createLinearGradient(tx, ty, sh.x, sh.y); grad.addColorStop(0, 'rgba(255,255,255,0)'); grad.addColorStop(1, `rgba(255,255,255,${sh.life * 0.85})`); ctx.globalAlpha = 1; ctx.strokeStyle = grad; ctx.lineWidth = 1.2 * dpr; ctx.beginPath(); ctx.moveTo(tx, ty); ctx.lineTo(sh.x, sh.y); ctx.stroke(); } ctx.globalAlpha = 1; raf = requestAnimationFrame(draw); } resize(); draw(); window.addEventListener('resize', resize); return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); window.removeEventListener('mousemove', onMouse); visObs.disconnect(); }; }, []); return ; } /* ---------------- NAV ---------------- */ function Nav({ onLogin, onSignup, onPanel, user }) { const [sc, setSc] = uS(false); const [active, setActive] = uS(''); const links = [ { href:'#como', label:'Como funciona' }, { href:'#recursos',label:'Recursos' }, { href:'#demo', label:'Painel ao vivo' }, { href:'#planos', label:'Planos' }, { href:'#faq', label:'FAQ' }, ]; uE(() => { const f = () => setSc(window.scrollY > 24); window.addEventListener('scroll', f); f(); return () => window.removeEventListener('scroll', f); }, []); uE(() => { const ids = links.map(l => l.href.slice(1)); const ios = ids.map(id => { const el = document.getElementById(id); if (!el) return null; const io = new IntersectionObserver( ([e]) => { if (e.isIntersecting) setActive('#'+id); }, { rootMargin:'-40% 0px -50% 0px' } ); io.observe(el); return io; }); return () => ios.forEach(io => io && io.disconnect()); }, []); return ( ); } /* ---------------- 777 COUNTER ---------------- */ function Counter777() { const [val, setVal] = uS(0); const runAnim = () => { setVal(0); let n = 0; function tick() { n++; const progress = n / 60; const eased = Math.pow(progress, 2); setVal(Math.round(eased * 777)); if (n >= 60) { setVal(777); return; } setTimeout(tick, 20 + progress * 40); } setTimeout(tick, 600); }; uE(() => { runAnim(); }, []); uE(() => { const hero = document.querySelector('.hero'); if (!hero) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) runAnim(); }, { threshold: 0.5 }); io.observe(hero); return () => io.disconnect(); }, []); return +{val}; } /* ---------------- INFINITY COUNTER ---------------- */ function InfinityCounter() { const [val, setVal] = uS(0); const [done, setDone] = uS(false); const runAnim = () => { setDone(false); setVal(0); let n = 0; function tick() { n++; const progress = n / 100; const eased = Math.pow(progress, 2.5); setVal(Math.round(eased * 100)); if (n >= 100) { setDone(true); return; } setTimeout(tick, 60 - progress * 52); } setTimeout(tick, 400); }; uE(() => { runAnim(); }, []); uE(() => { const hero = document.querySelector('.hero'); if (!hero) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) runAnim(); }, { threshold: 0.5 }); io.observe(hero); return () => io.disconnect(); }, []); return ( {done ? '∞' : val} ); } /* ---------------- HERO PANEL ---------------- */ function HeroPanel() { const [wins, setWins] = uS(Array.from({length:12},()=>0)); const [phase, setPhase] = uS(0); const [profit, setProfit] = uS(0); const [log, setLog] = uS({icon:'·', label:'Aguardando...', detail:'', color:'#52545a'}); const [logVis, setLogVis] = uS(true); const [cycleNum, setCycleNum] = uS(1); const [elapsed, setElapsed] = uS(0); const [flashWin, setFlashWin] = uS(-1); const [hovWin, setHovWin] = uS(-1); const pixValues = [47,23,61,31,54,19,42,38,27,65,33,50]; // stats por janela: [ciclos, sacado] const winStats = React.useRef(Array.from({length:12},()=>({ciclos:0,sacado:0,perfil:0}))); const flashTimer = React.useRef(null); const showLog = (entry) => { setLogVis(false); setTimeout(() => { setLog(entry); setLogVis(true); }, 260); }; const delay = ms => new Promise(r => setTimeout(r, ms)); // timer uE(() => { const t = setInterval(() => setElapsed(e => e+1), 1000); return () => clearInterval(t); }, []); const fmt = s => `${String(Math.floor(s/60)).padStart(2,'0')}:${String(s%60).padStart(2,'0')}`; uE(() => { let alive = true; async function cycle(cn) { setWins(Array.from({length:12},()=>0)); setPhase(0); setProfit(0); setElapsed(0); showLog({icon:'·', label:'Iniciando...', detail:'', color:'#52545a'}); await delay(600); setPhase(1); for (let i = 0; i < 12; i++) { if (!alive) return; await delay(550); winStats.current[i].perfil = cn*12 + i + 1; setWins(prev => { const n=[...prev]; n[i]=1; return n; }); showLog({icon:'↻', label:`Perfil #${String(cn*12+i+1).padStart(2,'0')}`, detail:'abrindo navegador...', color:'#94a3b8'}); } await delay(700); setPhase(2); setWins(Array.from({length:12},()=>2)); showLog({icon:'⚡', label:'Acelerador ativo', detail:'+2.3× velocidade', color:'#facc15'}); await delay(7400); if (!alive) return; setPhase(3); setWins(Array.from({length:12},()=>3)); let total = 0; for (let i = 0; i < pixValues.length; i++) { if (!alive) return; await delay(650); total += pixValues[i]; setProfit(total); // flash numa janela aleatória let wi = Math.floor(Math.random()*12); const curFlash = flashWin; while (wi === curFlash) wi = Math.floor(Math.random()*12); winStats.current[wi].sacado += pixValues[i]; winStats.current[wi].ciclos += 1; if (flashTimer.current) clearTimeout(flashTimer.current); setFlashWin(wi); flashTimer.current = setTimeout(() => setFlashWin(-1), 500); showLog({icon:'✓', label:'PIX sacado', detail:`R$ ${pixValues[i]},00`, color:'#4ade80'}); } await delay(2500); if (alive) { setCycleNum(c=>c+1); cycle(cn+1); } } winStats.current = Array.from({length:12},()=>({ciclos:0,sacado:0,perfil:0})); cycle(0); return () => { alive = false; }; }, []); const winClass = (s, i) => { let c = s===1?' on': s===2?' on accel': s===3?' on done': ''; if (flashWin===i) c += ' flash'; return c; }; return (
SpiderBot · operando ciclo {cycleNum} · {fmt(elapsed)}
ao vivo
{wins.map((s,i) => (
setHovWin(i)} onMouseLeave={()=>setHovWin(-1)}>
{s===3 && }
{s > 0 && }
{s===2 &&
} {s===3 &&
} {hovWin===i && s>0 && (
Perfil #{String(winStats.current[i].perfil).padStart(2,'0')} {winStats.current[i].sacado>0 && R$ {winStats.current[i].sacado}} {s===1?'abrindo':s===2?'acelerando':'concluído'}
)}
))}
{log.icon} {log.label} {log.detail} {profit > 0 && R$ {profit.toLocaleString('pt-BR')}}
); } /* ---------------- HERO ---------------- */ function Hero() { return (
Automação multi-perfil para CPA

Otimize sua operação de CPAcom a operação via API!

Enquanto você cadastra uma conta por vez, o SpiderBot abre dezenas de navegadores indetectáveis em paralelo, cria contas, abre o jogo de sua escolha, acelera e ainda saca no seu pix.
Tudo no automático, você apenas precisa clicar em começar.

{/* pills row */}
{[ {ic:'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z', label:'Bot Indetectável'}, {ic:'M3 3l18 18M10.6 10.6a2 2 0 002.8 2.8M9.4 5.3A9.5 9.5 0 0112 5c5 0 9 4.5 9 7a12 12 0 01-2.4 3.2M6.5 6.9C4.3 8.3 3 10.6 3 12c0 2.5 4 7 9 7 1.4 0 2.7-.3 3.8-.9', label:'Operação via API'}, {ic:'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z', label:'PIX Automático'}, ].map(({ic,label},i) => ( {ic && } {label} ))}
{/* trust card */}
{'★'.repeat(5)} 4.9/5 · operadores ativos
{['MULTI-PERFIL','ANTI-CAPTCHA','PIX AUTOMÁTICO','SUPORTE 24/7'].map(b => ( {b} ))}
99.9% UPTIME · TODOS OS SISTEMAS OPERACIONAIS
); } /* ---------------- LOGO BAR ---------------- */ function LogoBar() { const items = [['cpu','Anti-fingerprint'],['swap','Proxies residenciais'],['shield','Captcha solver'],['key','PIX automático'],['monitor','Grade de telas'],['bolt','Acelerador de jogos'],['eyeoff','Operação oculta via API']]; const row = [...items, ...items, ...items]; return (
{row.map(([i,t], idx) => ( {t} ))}
); } /* ---------------- STATS DASHBOARD ---------------- */ function StatBand() { const s = [['77×','sua produção manual',1],['24/7','operação sem parar',0],['12.480','perfis rodados/dia',0],['99,2%','cadastros concluídos',0]]; return (
{s.map(([v,l,red],i) => (
{v}
{l}
))}
); } /* ---------------- COMO FUNCIONA ---------------- */ function Como() { const steps = [ ['01','sliders','Configure a operação','Escolha perfil de dispositivo, quantas janelas abrir, valores de depósito e quais fluxos rodar. Salve como predefinição e esqueça.'], ['02','grid','Dispare em massa','Um clique no Play e o bot abre todos os navegadores, gera identidades únicas e distribui proxy por janela. Em segundos.'], ['03','bolt','Lucre no automático','Cadastro, captcha e chave PIX acontecem sozinhos. Você acompanha tudo pelo log e só entra pra iniciar outro ciclo.'], ]; const secRef = uRf(null); const fill1Ref = uRf(null); const fill2Ref = uRf(null); uE(() => { function onScroll() { const sec = secRef.current; const f1 = fill1Ref.current; const f2 = fill2Ref.current; if (!sec || !f1 || !f2) return; const rect = sec.getBoundingClientRect(); const vh = window.innerHeight; /* progresso geral: começa quando o topo da seção entra em tela, termina quando o fundo sai */ const start = vh * 0.8; /* começa quando top do section está a 80% da tela */ const end = vh * 0.2; /* termina quando top está a 20% da tela */ const p = Math.min(1, Math.max(0, (start - rect.top) / (start - end))); /* linha 1: p de 0→0.5 mapeia para 0→100% */ const p1 = Math.min(1, Math.max(0, p / 0.5)); /* linha 2: p de 0.5→1 mapeia para 0→100% */ const p2 = Math.min(1, Math.max(0, (p - 0.5) / 0.5)); f1.style.width = (p1 * 100) + '%'; f2.style.width = (p2 * 100) + '%'; } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => window.removeEventListener('scroll', onScroll); }, []); return (

Do 0 à escala em 3 passos

{steps.map(([n,ic,t,d], i) => (
{n}

{t}

{d}

))}
); } /* ---------------- RECURSOS ---------------- */ function FeatCard({ cls, ic, t, d, tag }) { return (
{tag && {tag}}

{t}

{d}

); } function Recursos() { return (

Tudo que sua operação de CPA precisa pra escalar

); } /* ---------------- COMPARATIVO ---------------- */ function Compare() { const rows = [ ['Contas por hora', '4 a 6', '∞', true], ['Captcha', 'você resolve', 'automático', false], ['Chave PIX', 'digitada à mão', 'vinculada sozinha', false], ['IP / fingerprint', 'sempre o mesmo', 'único por janela', false], ['Operação de madrugada', 'você dormindo', 'rodando 24/7', false], ['Escala', 'limitada às suas mãos', 'ilimitada', false], ]; return (

Na mão vs SpiderBot

Na mão
SpiderBot
{rows.map(([l,them,us,big],i) => (
{l}
{them}
{big ? {us} : us}
))}
); } /* ---------------- DEMO ---------------- */ /* ---------------- LUCRO CHART ---------------- */ function LucroChart() { const canvasRef = uRf(null); const secRef = uRf(null); const tooltipRef = uRf(null); const [animated, setAnimated] = uS(false); const dataRef = uRf({ manualPts:[], botPts:[], PAD:{}, cw:0, ch:0, H:0, W:0, maxY:0, HOURS:0 }); uE(() => { const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setAnimated(true); io.disconnect(); } }, { threshold: 0.2 }); if (secRef.current) io.observe(secRef.current); return () => io.disconnect(); }, []); uE(() => { if (!animated) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const dpr = Math.min(window.devicePixelRatio || 1, 2); const W = canvas.parentElement.offsetWidth; const H = 340; canvas.width = W * dpr; canvas.height = H * dpr; canvas.style.width = W + 'px'; canvas.style.height = H + 'px'; ctx.scale(dpr, dpr); const HOURS = 24; const VALUE = 5; const PAD = { t:28, r:28, b:48, l:72 }; const cw = W - PAD.l - PAD.r; const ch = H - PAD.t - PAD.b; /* gera ganho variável por hora — tendência + ruído */ function genCumulative(basePerH, noise) { const pts = [0]; for (let i = 1; i <= HOURS; i++) { const delta = Math.max(0, basePerH + (Math.random()-0.5)*2*noise); pts.push(pts[i-1] + delta * VALUE); } return pts; } const manualVals = genCumulative(5, 3); const botVals = genCumulative(40, 14); const maxY = botVals[HOURS]; function toX(h) { return PAD.l + (h / HOURS) * cw; } function toY(v) { return PAD.t + ch - (v / maxY) * ch; } const manualPts = manualVals.map((v,i) => ({ x:toX(i), y:toY(v), v })); const botPts = botVals.map((v,i) => ({ x:toX(i), y:toY(v), v })); dataRef.current = { manualPts, botPts, PAD, cw, ch, H, W, maxY, HOURS }; function render(prog) { ctx.clearRect(0,0,W,H); /* grid */ for (let i=0; i<=4; i++) { const y = PAD.t + (ch/4)*i; ctx.strokeStyle = 'rgba(255,255,255,.06)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(PAD.l,y); ctx.lineTo(PAD.l+cw,y); ctx.stroke(); const val = maxY - (maxY/4)*i; ctx.fillStyle='rgba(255,255,255,.28)'; ctx.font='10px monospace'; ctx.textAlign='right'; ctx.fillText('R$'+Math.round(val).toLocaleString('pt-BR'), PAD.l-8, y+4); } ctx.fillStyle='rgba(255,255,255,.28)'; ctx.textAlign='center'; [0,4,8,12,16,20,24].forEach(h => ctx.fillText(h+'h', toX(h), H-PAD.b+16)); const maxStep = prog * HOURS; function drawLine(pts, color, dash, lw) { ctx.beginPath(); ctx.moveTo(pts[0].x, pts[0].y); for (let i=1; i<=HOURS; i++) { const t = maxStep - (i-1); if (t <= 0) break; const frac = Math.min(1,t); const px = pts[i-1].x + (pts[i].x - pts[i-1].x)*frac; const py = pts[i-1].y + (pts[i].y - pts[i-1].y)*frac; ctx.lineTo(px,py); } ctx.strokeStyle=color; ctx.lineWidth=lw; if (dash) ctx.setLineDash([5,4]); else ctx.setLineDash([]); ctx.stroke(); ctx.setLineDash([]); } /* area bot */ const areaPath = new Path2D(); areaPath.moveTo(botPts[0].x, toY(0)); areaPath.lineTo(botPts[0].x, botPts[0].y); for (let i=1; i<=HOURS; i++) { const t = maxStep-(i-1); if(t<=0) break; const frac=Math.min(1,t); areaPath.lineTo(botPts[i-1].x+(botPts[i].x-botPts[i-1].x)*frac, botPts[i-1].y+(botPts[i].y-botPts[i-1].y)*frac); } const lastVisibleX = toX(Math.min(HOURS, maxStep)); areaPath.lineTo(lastVisibleX, toY(0)); areaPath.closePath(); const grad = ctx.createLinearGradient(0,PAD.t,0,PAD.t+ch); grad.addColorStop(0,'rgba(220,80,60,.2)'); grad.addColorStop(1,'rgba(220,80,60,0)'); ctx.fillStyle=grad; ctx.fill(areaPath); drawLine(manualPts,'rgba(255,255,255,.4)',true,1.8); drawLine(botPts,'#e05040',false,2.5); } let prog = 0, raf; function animate() { render(prog); prog = Math.min(1, prog+0.008); if (prog<1) raf = requestAnimationFrame(animate); } animate(); /* tooltip */ const tip = tooltipRef.current; function onClick(e) { const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const { manualPts, botPts, PAD, cw, HOURS } = dataRef.current; const hFrac = (mx - PAD.l) / cw; const hIdx = Math.round(hFrac * HOURS); if (hIdx < 0 || hIdx > HOURS) { tip.style.display='none'; return; } const m = manualPts[hIdx], b = botPts[hIdx]; const tipX = mx + 16 + 210 > W ? mx - 226 : mx + 16; tip.style.display = 'block'; tip.style.left = tipX + 'px'; tip.style.top = '20px'; const h1 = document.createElement('div'); h1.className='tip-h'; h1.textContent=`${hIdx}h de operação`; const h2 = document.createElement('div'); h2.className='tip-m'; h2.textContent=`Manual · R$${Math.round(m.v).toLocaleString('pt-BR')}`; const h3 = document.createElement('div'); h3.className='tip-b'; h3.textContent=`SpiderBot · R$${Math.round(b.v).toLocaleString('pt-BR')}`; tip.replaceChildren(h1, h2, h3); } function onLeave() { tip.style.display='none'; } canvas.addEventListener('mousemove', onClick); canvas.addEventListener('mouseleave', onLeave); return () => { cancelAnimationFrame(raf); canvas.removeEventListener('mousemove',onClick); canvas.removeEventListener('mouseleave',onLeave); }; }, [animated]); return (

Quanto você deixa de ganhar por hora

Com 5 contas/hora na mão versus 40 com o SpiderBot e R$5 de lucro por conta. A diferença é absurda.

Operação manual · ~5 contas/h SpiderBot · ~40 contas/h
* Ganhos por hora variam conforme disponibilidade da plataforma. R$5 de lucro médio por conta com acelerador de jogo ativo.
); } function Demo() { const [fs, setFs] = uS(false); uE(() => { const k = e => { if (e.key === 'Escape') setFs(false); }; window.addEventListener('keydown', k); return () => window.removeEventListener('keydown', k); }, []); uE(() => { document.body.style.overflow = fs ? 'hidden' : ''; }, [fs]); const Panel = window.SpiderBotPanel; return (

Veja o SpiderBot. É de verdade.

Navegue pelas abas, monte a grade de telas e aperte ▶ Play pra ver o bot provisionar as contas em tempo real.

Dica:▶ Play no rodapé do Início e acompanhe em Telas ou Contas
{Panel ? :
Carregando painel…
}
); } /* ---------------- PLANOS ---------------- */ function Planos({ onBuy }) { const [prices, setPrices] = uS({ 'Diário': 40, 'Semanal': 67, 'Mensal': 197, 'Vitalício': 397 }); uE(() => { fetch(`${API_BASE}/api/prices`).then(r => r.json()).then(setPrices).catch(() => {}); }, []); // Lançamento oficial: 07/09/2026 às 00h (horário de Brasília, UTC-3). // Antes disso as vendas ficam travadas; quando o cronômetro zera, o botão // "Comprar agora" volta a funcionar sozinho (o servidor também só libera nessa data). const LAUNCH_TS = new Date('2026-09-07T00:00:00-03:00').getTime(); const [now, setNow] = uS(Date.now()); uE(() => { const id = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(id); }, []); const launched = now >= LAUNCH_TS; const diff = Math.max(0, LAUNCH_TS - now); const pad = n => String(n).padStart(2, '0'); const timeBoxes = [ [Math.floor(diff / 86400000), 'dias'], [Math.floor((diff % 86400000) / 3600000), 'horas'], [Math.floor((diff % 3600000) / 60000), 'min'], [Math.floor((diff % 60000) / 1000), 'seg'], ]; const plans = [ { name:'Diário', tier:'Acesso 24h', desc:'Pra testar a potência da automação por um dia inteiro.', cur:'R$', val:String(prices['Diário']), old:'', cycle:'à vista no PIX', flag:null, renewal:null, feats:[['Acesso completo por 24h',1],['Liberação na hora',1],['Perfis ilimitados na sessão',1],['Suporte básico',1],['Atualizações futuras',0]] }, { name:'Semanal', tier:'7 dias', desc:'Uma semana inteira rodando pra validar a operação de verdade.', cur:'R$', val:String(prices['Semanal']), old:'', cycle:'à vista no PIX', flag:null, renewal:null, feats:[['Acesso completo por 7 dias',1],['Liberação na hora',1],['Todos os módulos liberados',1],['Suporte prioritário WhatsApp',1],['Atualizações futuras',0]] }, { name:'Mensal', tier:'30 dias', desc:'O equilíbrio entre custo e operação séria.', cur:'R$', val:String(prices['Mensal']), old:'', cycle:'primeiro mês · à vista no PIX', flag:null, renewal:null, feats:[['Acesso completo por 30 dias',1],['Todos os módulos liberados',1],['Onboarding individual',1],['Suporte prioritário WhatsApp',1],['Todas as atualizações inclusas',1]] }, { name:'Vitalício', tier:'Para sempre', desc:'A liberdade total. Um único aporte, acesso perpétuo.', cur:'R$', val:String(prices['Vitalício']), old:'', cycle:'pagamento único · sem mensalidade', flag:['Mais escolhido','red'], feats:[['Acesso vitalício garantido',1],['Zero mensalidades',1],['Prioridade máxima em updates',1],['Suporte VIP WhatsApp',1],['Acesso a recursos beta',1]] }, ]; return (

Escolha seu nível de operação

Do acesso semanal ao vitalício. Todos com liberação imediata.

{!launched && (
As vendas abrem em 07 de setembro
{timeBoxes.map(([v,l]) => (
{pad(v)}
{l}
))}
Garanta seu acesso assim que o cronômetro zerar.
)}
{plans.map(p => (
{p.flag && {p.flag[0]}}
{p.name}
{p.tier}

{p.desc}

{p.old}
{p.cur}{p.val}
{p.cycle}
{p.renewal &&
{p.renewal}
}
    {p.feats.map(([f,on],i) => (
  • {f}
  • ))}
{launched ? ( ) : ( )}
))}
{[['lock','Pagamento 100% seguro'],['activity','Liberação imediata e suporte 24/7']].map(([i,t])=>( {t} ))}
); } /* ---------------- GARANTIA ---------------- */ function Guarantee() { return (

Satisfação garantida ou seu dinheiro de volta

Teste sem medo. Se o SpiderBot não escalar a sua operação como prometido, é só chamar no suporte que devolvemos cada centavo.

); } /* ---------------- DEPOIMENTOS ---------------- */ function Depo() { const t = [ ['Subi de 8 cadastros por dia pra mais de 100. O painel é absurdo de prático.','Operador CPA','Vitalício'], ['Já usei outro bot da concorrência e o SpiderBot é bem mais leve e fácil de mexer. Configurei em minutos e já estava rodando.','@cpa_rafa01','Vitalício'], ['O captcha solver sozinho já paga o plano. Indetectável de verdade.','Gestor de tráfego','Semanal'], ['Comecei no semanal, antes de acabar a semana já migrei pro vitalício. Valeu cada centavo.','Operador','Semanal'], ]; return (

Quem usa o SpiderBot não volta atrás

{t.map(([q,a,p],i) => (
{[0,0,0,0,0].map((_,k)=>)}

"{q}"

{a}{p}
))}
); } /* ---------------- FAQ ---------------- */ function Faq() { const [open, setOpen] = uS(0); const qa = [ ['Isso realmente funciona ou é mais um produto promessa?','Funciona. O SpiderBot é usado por centenas de operadores CPA ativos hoje. Você pode testar o painel ao vivo direto nessa página antes de comprar, sem cadastro e sem pagar nada. Se não funcionar pra você, devolvemos o valor.'], ['É difícil de mexer?','Não. A interface é visual: você escolhe quantas janelas abrir, configura o fluxo uma vez e aperta Play. Temos vídeos explicativos passo a passo pra te guiar. E se ainda tiver dúvida, nos planos Mensal e Vitalício a gente entra numa call contigo e explica tudo ao vivo até você estar rodando.'], ['O bot é indetectável de verdade?','Sim. Cada janela roda num browser separado com fingerprint único: user-agent, canvas, fontes, timezone e comportamento de mouse randomizados. Cada janela usa um IP residencial diferente. O objetivo é simular tráfego humano real em cada perfil.'], ['Quantas janelas consigo abrir ao mesmo tempo?','Não tem limite fixo no software. Depende da capacidade da sua máquina. Operadores com setups intermediários rodam 20 a 40 janelas simultâneas sem problema. Quanto mais RAM e CPU, mais janelas.'], ['Como funciona o pagamento e quanto tempo até liberar?','Pagamento à vista no PIX. A liberação é imediata: você recebe as credenciais e já ativa o SpiderBot em minutos.'], ['O plano Vitalício realmente não tem mensalidade?','Exato. Um único pagamento e acesso pra sempre, incluindo todas as atualizações futuras e recursos em beta. Sem surpresas, sem renovação.'], ['E se eu não gostar depois de comprar?','Se o SpiderBot não funcionar como prometido, é só chamar no suporte que devolvemos cada centavo. Sem prazo limite, sem burocracia, sem enrolação.'], ['O suporte responde rápido?','Sim. O suporte é via WhatsApp e respondemos em minutos. Em todos os planos o atendimento é prioritário — e se em algum momento demorarmos pra te responder, a gente compensa com dias extras de acesso.'], ['Preciso de um computador específico?','Qualquer computador Windows com boa RAM e conexão estável funciona. Quanto mais recursos de hardware, mais janelas simultâneas você consegue rodar.'], ]; return (

Perguntas frequentes

{qa.map(([q,a],i) => (
setOpen(open===i?-1:i)}> {q}

{a}

))}
); } /* ---------------- WHATSAPP FLUTUANTE ---------------- */ /* ---------------- COUPON POPUP ---------------- */ function CouponPopup() { const CODE = '07'; const DISCOUNT = '7%'; const TOTAL_MINS = 15; const [visible, setVisible] = uS(false); const [copied, setCopied] = uS(false); const [secs, setSecs] = uS(TOTAL_MINS * 60); /* aparece após 2s — uma vez por dia */ uE(() => { const today = new Date().toDateString(); if (localStorage.getItem('sb_popup_date') === today) return; const t = setTimeout(() => setVisible(true), 2000); return () => clearTimeout(t); }, []); /* countdown */ uE(() => { if (!visible) return; const id = setInterval(() => setSecs(s => { if (s <= 1) { clearInterval(id); setVisible(false); return 0; } return s - 1; }), 1000); return () => clearInterval(id); }, [visible]); const markSeen = () => localStorage.setItem('sb_popup_date', new Date().toDateString()); const close = () => { markSeen(); setVisible(false); }; const use = () => { navigator.clipboard.writeText(CODE).catch(() => {}); setCopied(true); markSeen(); setTimeout(() => { setVisible(false); document.getElementById('planos')?.scrollIntoView({ behavior: 'smooth' }); }, 900); }; const mm = String(Math.floor(secs / 60)).padStart(2, '0'); const ss = String(secs % 60).padStart(2, '0'); if (!visible) return null; return (
Oferta exclusiva para você
Válida por tempo limitado
🔥 ÚLTIMOS 16 CUPONS ⏱ Oferta por tempo limitado

Ganhe {DISCOUNT} de desconto na sua compra.

Seu cupom
{CODE}
Expira em {mm}:{ss}
); } function WaButton() { return ( ); } /* ---------------- CTA + FOOTER ---------------- */ function CtaFooter() { return ( <>

Pronto pra escalar sua operação de CPA?

Configure uma vez e deixe o SpiderBot fazer o resto. Tempo é dinheiro.

Escolher meu plano
); } /* ============================================================ AUTH SYSTEM — server-side session (token em localStorage) ============================================================ */ const SB_TOKEN_KEY = 'sb_token'; function getStoredToken() { return localStorage.getItem(SB_TOKEN_KEY) || null; } function setStoredToken(t) { localStorage.setItem(SB_TOKEN_KEY, t); } function clearStoredToken() { localStorage.removeItem(SB_TOKEN_KEY); } async function apiCall(method, endpoint, body, token) { const headers = { 'Content-Type': 'application/json' }; if (token) headers['Authorization'] = `Bearer ${token}`; const r = await fetch(`${API_BASE}${endpoint}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); const data = await r.json(); if (!r.ok) throw new Error(data.error || 'Erro desconhecido'); return data; } /* ---- overlay lock helper ---- */ function useLockBody() { uE(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []); } /* ---------------- AUTH MODAL ---------------- */ function AuthModal({ view: initView, onClose, onSuccess, pendingPlan }) { const [view, setView] = uS(initView || 'login'); const [loading, setLoading] = uS(false); const [err, setErr] = uS(''); // dados temporários entre steps const [pending, setPending] = uS({}); // { name, email, pass } const [code, setCode] = uS(''); const [newPass, setNewPass] = uS(''); const [newPass2,setNewPass2]= uS(''); const [cooldown,setCooldown]= uS(0); const coolRef = uRf(null); useLockBody(); uE(() => { const k = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }, []); uE(() => () => clearInterval(coolRef.current), []); const go = v => { setErr(''); setCode(''); setView(v); }; const startCooldown = () => { setCooldown(60); coolRef.current = setInterval(() => setCooldown(s => { if (s <= 1) { clearInterval(coolRef.current); return 0; } return s-1; }), 1000); }; /* ─── ENVIAR CÓDIGO ─── */ const sendCode = async (email, type) => { setLoading(true); setErr(''); try { await apiCall('POST', '/api/auth/send-code', { email, type }); startCooldown(); return true; } catch(e) { setErr(e.message); return false; } finally { setLoading(false); } }; /* ─── SUBMIT por view ─── */ const submit = async e => { e.preventDefault(); setErr(''); const fd = new FormData(e.target); const name = (fd.get('name') || '').trim(); const email = (fd.get('email') || '').trim().toLowerCase(); const pass = fd.get('password') || ''; const pass2 = fd.get('password2') || ''; if (!email.includes('@')) { setErr('E-mail inválido.'); return; } /* LOGIN */ if (view === 'login') { if (pass.length < 6) { setErr('Senha com no mínimo 6 caracteres.'); return; } setLoading(true); try { const d = await apiCall('POST', '/api/auth/login', { email, password: pass }); setStoredToken(d.token); onSuccess({ ...d.user, _token: d.token }); } catch(e) { setErr(e.message); } finally { setLoading(false); } return; } /* SIGNUP — step 1: vai direto pra tela do código, envia em background */ if (view === 'signup') { if (!name) { setErr('Informe seu nome.'); return; } if (pass.length < 6) { setErr('Senha com no mínimo 6 caracteres.'); return; } if (pass !== pass2) { setErr('As senhas não coincidem.'); return; } setPending({ name, email, pass }); go('signup-code'); // vai imediatamente startCooldown(); sendCode(email, 'register'); // manda em background sem await return; } /* FORGOT — step 1: vai direto pra tela do código, envia em background */ if (view === 'forgot') { setPending({ email }); go('forgot-code'); // vai imediatamente startCooldown(); sendCode(email, 'reset'); // manda em background sem await return; } }; /* SIGNUP — step 2: confirma código */ const confirmSignupCode = async () => { setErr(''); if (code.trim().length < 6) { setErr('Digite o código completo.'); return; } setLoading(true); try { const d = await apiCall('POST', '/api/auth/register', { name: pending.name, email: pending.email, password: pending.pass, code: code.trim() }); setStoredToken(d.token); onSuccess({ ...d.user, _token: d.token }); } catch(e) { setErr(e.message); } finally { setLoading(false); } }; /* FORGOT — step 2: valida código + nova senha */ const submitReset = async () => { setErr(''); if (!code.trim()) { setErr('Digite o código.'); return; } if (newPass.length < 6) { setErr('Senha com no mínimo 6 caracteres.'); return; } if (newPass !== newPass2) { setErr('As senhas não coincidem.'); return; } setLoading(true); try { await apiCall('POST', '/api/auth/reset-password', { email: pending.email, code: code.trim(), newPassword: newPass }); go('reset-done'); } catch(e) { setErr(e.message); } finally { setLoading(false); } }; const CodeInput = () => (
setCode(e.target.value.replace(/\D/g,'').slice(0,6))} inputMode="numeric" autoComplete="one-time-code"/>

Enviado para {pending.email}

); return (
e.target===e.currentTarget && onClose()}>
{/* ── LOGIN ── */} {view === 'login' && (
{pendingPlan &&
Comprando: {pendingPlan.name} — R$ {pendingPlan.val}
}
{err &&
{err}
}
)} {/* ── SIGNUP step 1 ── */} {view === 'signup' && (
{pendingPlan &&
Comprando: {pendingPlan.name} — R$ {pendingPlan.val}
}
{err &&
{err}
}
)} {/* ── SIGNUP step 2: código ── */} {view === 'signup-code' && (

Verifique seu e-mail e insira o código de 6 dígitos para confirmar o cadastro.

{err &&
{err}
}
)} {/* ── FORGOT step 1 ── */} {view === 'forgot' && (

Informe seu e-mail. Se ele estiver cadastrado, você receberá um código em breve.

{err &&
{err}
}
)} {/* ── FORGOT step 2: código + nova senha ── */} {view === 'forgot-code' && (
🔐
setNewPass(e.target.value)} autoComplete="new-password"/>
setNewPass2(e.target.value)} autoComplete="new-password"/>
{err &&
{err}
}
)} {/* ── RESET done ── */} {view === 'reset-done' && (

Senha redefinida com sucesso!

)}
{view === 'login' && <>Não tem conta?{' '}} {(view === 'signup' || view === 'signup-code') && <>Já tem conta?{' '}}
); } /* ---------------- RENEWAL MODAL ---------------- */ function RenewalModal({ user, expiredPlan, onClose, onConfirm }) { const [selected, setSelected] = uS(null); useLockBody(); uE(() => { const k = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }, []); const options = [ { id:'mensal', label:'Renovar Mensal', price:'197', tag:'Renovação', desc:'Mais 30 dias completos de acesso.', feats:['Acesso completo por 30 dias','Todos os módulos','Suporte prioritário'], highlight:false, }, { id:'vitalicio', label:'Upgrade Vitalício', price:'397', tag:'Mais escolhido', desc:'Um pagamento. Acesso para sempre.', feats:['Acesso vitalício','Zero mensalidades','Prioridade em updates','Suporte VIP'], highlight:true, }, ]; const chosen = options.find(o => o.id === selected); const PLAN_CATALOG = { mensal: { name:'Mensal', val:'197', cycle:'por mês', feats:[['Acesso por 30 dias',1],['Todos os módulos',1],['Suporte prioritário',1],['Call de onboarding',1]] }, vitalicio:{ name:'Vitalício', val:'397', cycle:'único', feats:[['Acesso vitalício',1],['Zero mensalidades',1],['Todos os módulos',1],['Prioridade em updates',1],['Suporte VIP',1]] }, }; const confirm = () => { if (!chosen) return; onConfirm(PLAN_CATALOG[chosen.id]); // abre PurchaseModal real }; return (
e.target===e.currentTarget && onClose()}>
<>
Seu plano expirou
Escolha como quer continuar:
{options.map(opt => (
setSelected(opt.id)} > {opt.highlight &&
{opt.tag}
}
{opt.label}
R$ {opt.price}
{opt.desc}
    {opt.feats.map((f,i) =>
  • ✓ {f}
  • )}
))}
Pagamento seguro · Liberação imediata
); } /* ---------------- PURCHASE MODAL ---------------- */ /* Endereço da API. - Em produção: definido por window.SB_API_BASE no index.html. String vazia = mesma origem (nginx faz proxy de /api -> node:3001). - Em localhost: cai no dev server da API automaticamente. */ const API_BASE = (typeof window !== 'undefined' && typeof window.SB_API_BASE === 'string') ? window.SB_API_BASE : (/^(localhost|127\.0\.0\.1|\[::1\])$/.test(location.hostname) ? 'http://localhost:3001' : ''); // Order bump no checkout do bot — preço espelha o server/index.js e o preço // real do spiderdash.com.br (admin solo R$30/mês + R$29,90/operador, com // desconto por período igual ao do site), só pra exibição antes de confirmar; // quem manda de verdade é o servidor. const SPIDERDASH_ADDON_PRICE = 30; const SPIDERDASH_OPERATOR_PRICE = 29.90; const SPIDERDASH_MAX_OPERATORS = 20; const SPIDERDASH_PERIODS = { Mensal: { months: 1, discount: 0 }, Trimestral: { months: 3, discount: 0.10 }, Semestral: { months: 6, discount: 0.15 }, Anual: { months: 12, discount: 0.25 }, }; const SPIDERDASH_PERIOD_KEYS = Object.keys(SPIDERDASH_PERIODS); const spiderDashAmount = (operators, period = 'Mensal') => { const monthly = SPIDERDASH_ADDON_PRICE + operators * SPIDERDASH_OPERATOR_PRICE; const { months, discount } = SPIDERDASH_PERIODS[period] || SPIDERDASH_PERIODS.Mensal; return Math.round(monthly * months * (1 - discount) * 100) / 100; }; const fmtBRL = n => n.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); /* formata CPF: 000.000.000-00 */ function fmtCpf(v) { return v.replace(/\D/g,'').slice(0,11) .replace(/(\d{3})(\d)/,'$1.$2') .replace(/(\d{3})(\d)/,'$1.$2') .replace(/(\d{3})(\d{1,2})$/,'$1-$2'); } /* valida CPF com dígito verificador */ function validCpf(v) { const c = v.replace(/\D/g,''); if (c.length !== 11) return false; if (/^(\d)\1{10}$/.test(c)) return false; // todos iguais (ex: 111.111.111-11) let s = 0; for (let i = 0; i < 9; i++) s += Number(c[i]) * (10 - i); let r = (s * 10) % 11; if (r === 10 || r === 11) r = 0; if (r !== Number(c[9])) return false; s = 0; for (let i = 0; i < 10; i++) s += Number(c[i]) * (11 - i); r = (s * 10) % 11; if (r === 10 || r === 11) r = 0; return r === Number(c[10]); } /* formata telefone: (00) 00000-0000 */ function fmtPhone(v) { return v.replace(/\D/g,'').slice(0,11) .replace(/(\d{2})(\d)/,'($1) $2') .replace(/(\d{5})(\d{1,4})$/,'$1-$2'); } function QRCodeCanvas({ code }) { const ref = uRf(null); uE(() => { if (!ref.current || !code) return; ref.current.innerHTML = ''; if (window.QRCode) { new window.QRCode(ref.current, { text: code, width: 180, height: 180, colorDark: '#ffffff', colorLight: '#0a0a0a', correctLevel: window.QRCode.CorrectLevel.M, }); } }, [code]); return
; } function PurchaseModal({ user, plan, onClose, onConfirm, onOpenPanel }) { const [step, setStep] = uS('review'); // review | form | pix | done | error const [cpf, setCpf] = uS(''); const [phone, setPhone] = uS(''); const [pixCode, setPixCode] = uS(''); const [identifier, setIdentifier] = uS(''); const [copied, setCopied] = uS(false); const [errMsg, setErrMsg] = uS(''); const [coupon, setCoupon] = uS(''); const [couponInfo, setCouponInfo] = uS(null); // { label, saved, finalAmount } const [couponErr, setCouponErr] = uS(''); const [couponLoading, setCouponLoading] = uS(false); const [spiderDash, setSpiderDash] = uS(false); // order bump const [spiderDashOperators, setSpiderDashOperators] = uS(0); // assentos extras, além do admin const [spiderDashPeriod, setSpiderDashPeriod] = uS('Mensal'); const pollRef = uRf(null); useLockBody(); // addon = (admin R$30 + operadores extras R$29,90 cada) × período com desconto — mesma conta do spiderdash.com.br. const spiderDashTotal = spiderDashAmount(spiderDashOperators, spiderDashPeriod); // baseTotal = plano + addon SEM desconto; o cupom (quando aplicado) já // desconta em cima dessa soma inteira — não só do plano (ver validateCoupon). const baseTotal = Number(plan.val) + (spiderDash ? spiderDashTotal : 0); const totalAmount = couponInfo ? couponInfo.finalAmount : baseTotal; uE(() => { const k = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }, [step]); /* limpa polling ao desmontar */ uE(() => () => clearInterval(pollRef.current), []); const startPolling = (id) => { let attempts = 0; const MAX = 100; // ~5 minutos (100 × 3s) pollRef.current = setInterval(async () => { attempts++; try { const r = await fetch(`${API_BASE}/api/payment-status/${id}`); const data = await r.json(); if (data.status === 'completed') { clearInterval(pollRef.current); setStep('done'); onConfirm(plan); } else if (data.status === 'failed') { clearInterval(pollRef.current); setErrMsg('Pagamento não aprovado. Tente novamente.'); setStep('error'); } else if (attempts >= MAX) { clearInterval(pollRef.current); setErrMsg('Tempo limite atingido. Se o pagamento foi feito, entre em contato com o suporte.'); setStep('error'); } } catch (_) { /* ignora erros de rede — continua tentando */ } }, 3000); }; const validateCoupon = async (code = coupon.trim().toUpperCase(), spiderDashOverride = spiderDash, operatorsOverride = spiderDashOperators, periodOverride = spiderDashPeriod) => { if (!code) return; setCouponLoading(true); setCouponErr(''); setCouponInfo(null); try { // o cupom desconta a SOMA (plano + SpiderDash, se marcado), não só o bot const d = await apiCall('POST', '/api/validate-coupon', { code, plan: plan.name, spiderDash: spiderDashOverride, spiderDashOperators: operatorsOverride, spiderDashPeriod: periodOverride }); setCouponInfo(d); } catch (e) { setCouponErr(e.message); } finally { setCouponLoading(false); } }; // se já tinha cupom aplicado e o usuário mexe no SpiderDash (marca/desmarca, // muda o nº de operadores ou o período), o desconto muda de base — reaplica // o mesmo cupom automaticamente (com o valor NOVO, não o antigo — os // setters são assíncronos) em vez de deixar desatualizado. const toggleSpiderDash = checked => { setSpiderDash(checked); if (couponInfo) validateCoupon(couponInfo.code, checked, spiderDashOperators, spiderDashPeriod); }; const changeOperators = delta => { const next = Math.max(0, Math.min(SPIDERDASH_MAX_OPERATORS, spiderDashOperators + delta)); setSpiderDashOperators(next); if (couponInfo) validateCoupon(couponInfo.code, spiderDash, next, spiderDashPeriod); }; const changePeriod = period => { setSpiderDashPeriod(period); if (couponInfo) validateCoupon(couponInfo.code, spiderDash, spiderDashOperators, period); }; const createPayment = async () => { const rawCpf = cpf.replace(/\D/g,''); const rawPhone = phone.replace(/\D/g,''); if (!validCpf(rawCpf)) return setErrMsg('CPF inválido'); if (rawPhone.length < 10) return setErrMsg('Telefone inválido'); setErrMsg(''); setStep('pix'); try { const data = await apiCall('POST', '/api/create-payment', { plan: plan.name, cpf: rawCpf, phone: rawPhone, couponCode: couponInfo?.code || null, spiderDash, spiderDashOperators, spiderDashPeriod }, user._token ); setPixCode(data.pix_code); setIdentifier(data.identifier); startPolling(data.identifier); } catch (err) { setErrMsg(err.message); setStep('error'); } }; const copyCode = () => { navigator.clipboard.writeText(pixCode).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2500); }); }; return (
e.target===e.currentTarget && onClose()}>
{/* STEP 1 — resumo + dados + cupom (tudo numa tela só) */} {(step === 'review' || step === 'form') && ( <>
{/* resumo do plano — preço base do bot; o total real (com cupom e/ou SpiderDash) fica no resumo mais abaixo */}
{plan.name}
R$ {plan.val}
{plan.cycle}
Conta: {user.email}
{/* CPF + telefone */}
setCpf(fmtCpf(e.target.value))} inputMode="numeric" />
setPhone(fmtPhone(e.target.value))} inputMode="tel" />
{/* cupom */}
{ setCoupon(e.target.value.toUpperCase()); setCouponInfo(null); setCouponErr(''); }} onKeyDown={e => e.key === 'Enter' && validateCoupon()} style={{flex:1,textTransform:'uppercase',fontFamily:'var(--ff-mono)',letterSpacing:'.06em'}} />
{couponInfo && (
{couponInfo.label} aplicado
)} {couponErr &&
{couponErr}
}
{/* order bump — SpiderDash */} {spiderDash && (
e.stopPropagation()}>
Operadores no plano O admin já está incluso.
{spiderDashOperators}
)} {spiderDash && (
e.stopPropagation()}> {SPIDERDASH_PERIOD_KEYS.map(key => { const { months, discount } = SPIDERDASH_PERIODS[key]; const total = spiderDashAmount(spiderDashOperators, key); const perMonth = Math.round((total / months) * 100) / 100; return (
changePeriod(key)}>
{key} {discount > 0 && {Math.round(discount*100)}% OFF}
R$ {fmtBRL(perMonth)}/mês{months>1?` · ${months} meses`:' · à vista'}
R$ {fmtBRL(total)}
); })}
)} {(spiderDash || couponInfo) && (
{plan.name}R$ {plan.val} {spiderDash && <>SpiderDash{spiderDashPeriod!=='Mensal'?` (${spiderDashPeriod})`:''}R$ {fmtBRL(spiderDashTotal)}} {couponInfo && <>Cupom ({couponInfo.code})− R$ {fmtBRL(baseTotal - couponInfo.finalAmount)}} TotalR$ {fmtBRL(totalAmount)}
)} {errMsg &&
{errMsg}
}
Pagamento seguro · Liberação imediata
)} {/* STEP 3 — QR Code + aguardando */} {step === 'pix' && (

Escaneie o QR Code ou copie o código PIX

{pixCode ?
:
} {pixCode && ( )}
Aguardando confirmação do pagamento...

O acesso é liberado automaticamente após o PIX ser processado

)} {/* STEP 4 — confirmado */} {step === 'done' && (

Pagamento confirmado!

Seu plano {plan.name} está ativo.

)} {/* STEP error */} {step === 'error' && (

Ops!

{errMsg}

)}
); } /* ---------------- TOKEN BOX (1 — oculto por padrão) ---------------- */ function TokenBox({ token }) { const [copied, setCopied] = uS(false); const [revealed, setRevealed] = uS(false); // sem token (plano expirado, por ex.) — não tem o que mostrar/copiar/revelar. if (!token) return
Token indisponível — plano expirado.
; const copy = () => { navigator.clipboard.writeText(token).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); }; const masked = token.replace(/[A-Z0-9]/g, '•'); return (
{revealed ? token : masked}
); } /* ---------------- USER PANEL MODAL ---------------- */ function UserPanelModal({ user: initialUser, onClose, onLogout, onBuy }) { // versao exibida no botao de download: vem do mesmo latest.json que o bot // consulta para se atualizar, entao nunca fica desencontrada do binario. const [verInstalador, setVerInstalador] = uS(''); uE(() => { let vivo = true; fetch('/updates/latest.json', { cache: 'no-store' }) .then(r => r.ok ? r.json() : null) .then(j => { if (vivo && j && j.version) setVerInstalador('v' + String(j.version).split('+')[0]); }) .catch(() => {}); return () => { vivo = false; }; }, []); const [user, setUser] = uS(initialUser); // 5 — lembrar última aba const savedTab = (() => { try { return localStorage.getItem('sb_tab'); } catch { return null; } })(); const defaultTab = initialUser.plan ? (savedTab || 'inicio') : 'conta'; const [tab, setTab] = uS(defaultTab); const [editingName, setEditingName] = uS(false); const [nameVal, setNameVal] = uS(user.name || ''); const [saved, setSaved] = uS(false); const [confirmLogout, setConfirmLogout] = uS(false); const [changingPass, setChangingPass] = uS(false); const [passForm, setPassForm] = uS({ current:'', next:'', next2:'' }); const [passErr, setPassErr] = uS(''); const [passOk, setPassOk] = uS(false); const [passLoading, setPassLoading] = uS(false); const [payments, setPayments] = uS(null); const fileRef = uRf(null); useLockBody(); const switchTab = t => { setTab(t); try { localStorage.setItem('sb_tab', t); } catch {} if (t === 'licenca') loadPayments(); }; uE(() => { const k = e => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }, []); const updateUser = patch => { setUser(u => ({ ...u, ...patch })); }; const saveName = async () => { if (nameVal.trim()) { try { await apiCall('PATCH', '/api/auth/me', { name: nameVal.trim() }, user._token); updateUser({ name: nameVal.trim() }); } catch (_) {} } setEditingName(false); setSaved(true); setTimeout(() => setSaved(false), 2000); }; const submitChangePass = async () => { setPassErr(''); setPassOk(false); if (!passForm.current) { setPassErr('Informe a senha atual.'); return; } if (passForm.next.length < 6) { setPassErr('Nova senha: mínimo 6 caracteres.'); return; } if (passForm.next !== passForm.next2) { setPassErr('As senhas não coincidem.'); return; } setPassLoading(true); try { await apiCall('POST', '/api/auth/change-password', { currentPassword: passForm.current, newPassword: passForm.next }, user._token); setPassOk(true); setPassForm({ current:'', next:'', next2:'' }); setTimeout(() => { setPassOk(false); setChangingPass(false); }, 2500); } catch(e) { setPassErr(e.message); } finally { setPassLoading(false); } }; const loadPayments = async () => { if (payments !== null) return; try { const d = await apiCall('GET', '/api/payments/history', null, user._token); setPayments(d); } catch { setPayments([]); } }; // 4 — comprime foto para 200x200 antes de salvar const handlePhoto = e => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = ev => { const img = new Image(); img.onload = () => { const size = 200; const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); const ratio = Math.max(size/img.width, size/img.height); const w = img.width*ratio, h = img.height*ratio; ctx.drawImage(img, (size-w)/2, (size-h)/2, w, h); updateUser({ photo: canvas.toDataURL('image/jpeg', 0.82) }); }; img.src = ev.target.result; }; reader.readAsDataURL(file); }; const initials = (user.name || user.email).slice(0,2).toUpperCase(); const memberSince = new Date(user.createdAt||Date.now()).toLocaleDateString('pt-BR',{month:'short',year:'numeric'}); const token = user.plan?.active ? user.sbToken : null; // 3 — expiração vem do servidor (plan.expiresAt, plan.active) const isExpired = user.plan && !user.plan.active; const isVitalicio = user.plan?.name === 'Vitalício'; // "Teste" é medido em horas (2h), não em dias — precisa de label e barra // de progresso próprias, senão qualquer coisa abaixo de 24h vira "1d". const isHourPlan = user.plan?.name === 'Teste'; const msLeft = user.plan?.expiresAt ? (new Date(user.plan.expiresAt) - Date.now()) : null; const hoursLeft = msLeft !== null ? Math.max(0, Math.ceil(msLeft / 3600000)) : null; const totalMs = (user.plan?.activatedAt && user.plan?.expiresAt) ? (new Date(user.plan.expiresAt) - new Date(user.plan.activatedAt)) : null; const daysLeft = user.plan?.expiresAt ? Math.max(0, Math.ceil((new Date(user.plan.expiresAt) - Date.now()) / 86400000)) : null; const planDuration = user.plan?.name === 'Diário' ? 1 : user.plan?.name === 'Semanal' ? 7 : user.plan?.name === 'Mensal' ? 30 : null; const daysProgress = isHourPlan ? (totalMs && msLeft !== null ? Math.max(0, Math.min(100, (msLeft / totalMs) * 100)) : null) : (planDuration && daysLeft !== null ? Math.max(0, Math.min(100, (daysLeft / planDuration) * 100)) : null); const isExpiring = !isExpired && (isHourPlan || (daysLeft !== null && daysLeft <= (planDuration === 1 ? 0 : 3))); const canUpgrade = user.plan && !isVitalicio; const TABS = user.plan ? [['inicio','Início'],['licenca','Licença'],['tutorial','Tutorial'],['conta','Conta']] : [['conta','Conta'],['tutorial','Tutorial']]; return (
e.target===e.currentTarget && onClose()}>
{/* header fixo */}
fileRef.current?.click()}> {user.photo ? :
{initials}
}
{user.name || user.email.split('@')[0]}
{user.email}
{user.plan && ( isExpired ? (
Expirado {user.plan.name}
) : daysLeft !== null ? (
{isHourPlan ? `${hoursLeft}h` : `${daysLeft}d`} restantes
) : ( ● Vitalício ) )}
{/* tabs */}
{TABS.map(([id,label]) => ( ))}
{/* banner plano expirado */} {isExpired && (
⚠ Seu plano expirou. Renove para continuar usando o SpiderBot.
)} {/* conteúdo */}
{/* ── INÍCIO ── */} {tab === 'inicio' && user.plan && (
Tudo pronto, {(user.name||user.email.split('@')[0]).split(' ')[0]} 🚀
{isExpired ? <>Seu plano {user.plan.name} expirou. : <>Seu plano {user.plan.name} está ativo. Siga os passos abaixo para começar.}
{[ ['1','Baixe o SpiderBot','Clique em Download e instale o executável.'], ['2','Insira sua licença','Abra o app e cole o token na tela de ativação.'], ['3','Configure e rode','Adicione seus links, ajuste o fluxo e dê Play.'], ].map(([n,t,d]) => (
{n}
{t}
{d}
))}
Download SpiderBot {verInstalador} {canUpgrade && ( )}
)} {/* ── LICENÇA ── */} {tab === 'licenca' && user.plan && (
{user.plan.name}
Ativo desde {new Date(user.plan.activatedAt||Date.now()).toLocaleDateString('pt-BR')}
● Ativo
{(user.plan.feats||[]).filter(([,v])=>v).map(([f],i)=>( ✓ {f} ))}
{canUpgrade && ( )}
Token de licença
Cole esse token na tela de ativação do SpiderBot. Não compartilhe com ninguém.
{/* histórico de pagamentos */}
Histórico de pagamentos
{payments === null ?
Carregando...
: payments.length === 0 ?
Nenhum pagamento registrado.
:
{payments.map((p,i) => (
{p.plan} {p.couponCode && {p.couponCode}} {new Date(p.createdAt).toLocaleDateString('pt-BR')}
{p.baseAmount && p.baseAmount !== (p.finalAmount ?? p.amount) && ( R${p.baseAmount} )} R$ {p.finalAmount ?? p.amount ?? '—'} {p.status==='completed'?'✓ Pago':p.status==='failed'?'✗ Falhou':'⏳ Pendente'}
))}
}
)} {/* ── TUTORIAL ── */} {tab === 'tutorial' && ( )} {/* ── CONTA ── */} {tab === 'conta' && (
Nome de usuário
{editingName ? (
setNameVal(e.target.value)} onKeyDown={e=>e.key==='Enter'&&saveName()} autoFocus/>
) : (
{user.name || '—'}
)} {saved &&
✓ Salvo
}
E-mail
{user.email}
Foto de perfil
Membro desde
{memberSince}
{/* trocar senha */}
Senha
{changingPass && (
setPassForm(f=>({...f,current:e.target.value}))}/> setPassForm(f=>({...f,next:e.target.value}))}/> setPassForm(f=>({...f,next2:e.target.value}))}/> {passErr &&
{passErr}
} {passOk &&
✓ Senha alterada com sucesso!
}
)}
{!user.plan && ( )} {confirmLogout ? (
Tem certeza que quer sair?
) : ( )}
)}
); } /* ---------------- APP ---------------- */ function App() { useReveal(); const [user, setUser] = uS(null); const [authReady, setAuthReady] = uS(false); const [modal, setModal] = uS(null); /* verifica sessão no servidor ao carregar */ uE(() => { const tok = getStoredToken(); if (!tok) { setAuthReady(true); return; } fetch(`${API_BASE}/api/auth/me`, { headers: { Authorization: `Bearer ${tok}` } }) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.user) setUser({ ...d.user, _token: tok }); else clearStoredToken(); }) .catch(() => {}) .finally(() => setAuthReady(true)); }, []); const login = (u) => { setUser(u); setModal(null); }; const logout = () => { const tok = getStoredToken(); if (tok) fetch(`${API_BASE}/api/auth/logout`, { method:'POST', headers:{ Authorization:`Bearer ${tok}` } }).catch(()=>{}); clearStoredToken(); setUser(null); setModal(null); }; const openAuth = (view, pendingPlan) => setModal({ type:'auth', view, pendingPlan }); const openPanel = () => setModal({ type:'panel' }); const handleBuy = plan => { if (!user) { openAuth('login', plan); } else { setModal({ type:'purchase', plan }); } }; /* após pagamento confirmado: re-busca dados atualizados do servidor */ const handlePurchaseConfirm = async () => { const tok = getStoredToken(); if (!tok) return; try { const d = await apiCall('GET', '/api/auth/me', null, tok); if (d?.user) setUser({ ...d.user, _token: tok }); } catch (_) {} }; const handleAuthSuccess = (u) => { login(u); if (modal?.pendingPlan) { setTimeout(() => setModal({ type:'purchase', plan: modal.pendingPlan }), 300); } }; /* banner de expiração — usa dados do servidor */ const isHourPlan = user?.plan?.name === 'Teste'; // medido em horas, não em dias — precisa de label própria const msLeft = user?.plan?.expiresAt ? (new Date(user.plan.expiresAt) - Date.now()) : null; const userDaysLeft = user?.plan?.expiresAt ? Math.max(0, Math.ceil((new Date(user.plan.expiresAt) - Date.now()) / 86400000)) : null; const userPlanDuration = user?.plan?.name === 'Diário' ? 1 : user?.plan?.name === 'Semanal' ? 7 : user?.plan?.name === 'Mensal' ? 30 : null; const showExpiryBanner = (user?.plan && !user.plan.active) || (isHourPlan && msLeft !== null && msLeft > 0) || (!isHourPlan && userDaysLeft !== null && userDaysLeft <= (userPlanDuration === 1 ? 0 : 3)); const expiryLabel = user?.plan && !user.plan.active ? 'expirou' : isHourPlan ? `expira em ${Math.max(1, Math.round((msLeft||0)/3600000))}h` : `expira em ${userDaysLeft} dia${userDaysLeft>1?'s':''}`; uE(() => { if (showExpiryBanner) document.body.classList.add('has-banner'); else document.body.classList.remove('has-banner'); return () => document.body.classList.remove('has-banner'); }, [showExpiryBanner]); return ( <> {(!user || !user.plan?.active) && } {showExpiryBanner && user?.plan && (
⚠ Seu plano {user.plan.name} {expiryLabel}.
)}