# teste form

> Source: https://7ball.com.br/teste-form/  
> Author: mais  
> Date: 2025-03-31T23:22:22-03:00

/* Botão Flutuante */ .floating-button { position: fixed; bottom: 30px; right: 30px; background-color: #25D366; color: white; padding: 12px 24px; border-radius: 50px; box-shadow: 0 4px 12px rgba(37, 211, 102, 0.2); cursor: pointer; z-index: 1000; font-weight: 500; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; border: none; font-size: 14px; } .floating-button i { font-size: 18px; } .floating-button .button-text { display: inline; } @media (max-width: 768px) { .floating-button { padding: 16px; border-radius: 50%; width: 56px; height: 56px; display: flex; justify-content: center; bottom: 20px; right: 20px; } .floating-button i { font-size: 24px; margin: 0; } .floating-button .button-text { display: none; } } .floating-button:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(37, 211, 102, 0.3); background-color: #22c55e; } /* Modal */ .modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1001; opacity: 0; transition: opacity 0.3s ease; } .modal-overlay.active { opacity: 1; } .form-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.8); background-color: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); z-index: 1002; opacity: 0; transition: all 0.3s ease; } .form-container.active { transform: translate(-50%, -50%) scale(1); opacity: 1; } .form-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .form-header h3 { color: #333; margin: 0; } .modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #000000 !important; padding: 0; line-height: 1; } .form-group { display: flex; flex-direction: column; gap: 5px; margin-bottom: 15px; } .form-group label { font-weight: 500; color: #333; } .form-group textarea { padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; width: 100%; box-sizing: border-box; min-height: 42px; max-height: 42px; font-family: inherit; line-height: 20px; display: block; resize: none; overflow: hidden; } /* Estilos do Dropdown */ .dropdown { position: relative; } .dropdown-content { display: none; position: absolute; top: 100%; left: 0; width: 100%; max-height: 200px; overflow-y: auto; background: white; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 1000; } .dropdown-content.active { display: block; } .dropdown-item { padding: 10px; cursor: pointer; transition: background 0.2s; } .dropdown-item:hover { background: #f5f5f5; } .dropdown-search { position: sticky; top: 0; background: white; padding: 10px; border-bottom: 1px solid #ddd; } .dropdown-search textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; min-height: 36px; max-height: 36px; resize: none; } .hidden { display: none; } .submit-button { background-color: #25D366; color: white; padding: 12px; border: none; border-radius: 5px; cursor: pointer; font-weight: 500; transition: background-color 0.3s ease; width: 100%; } .submit-button:hover { background-color: #22c55e; } .submit-button:disabled { background-color: #93e6b0; cursor: not-allowed; } .submit-button .loading { display: none; } .submit-button.is-loading .loading { display: inline-block; } .submit-button.is-loading .button-text { display: none; } @keyframes spin { to { transform: rotate(360deg); } } .loading-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid #ffffff; border-radius: 50%; border-top-color: transparent; animation: spin 0.8s linear infinite; margin-right: 8px; vertical-align: middle; } .geo-pill-original { display:flex; align-items:center; gap:8px; padding:10px 12px; background:#f0fdf4; border:1px solid #86efac; border-radius:8px; font-size:13px; color:#166534; margin-bottom:12px; } .geo-pill-original .geo-icon { font-size:16px; line-height:1; } .geo-pill-original .geo-text { flex:1; font-weight:500; } .geo-pill-original .geo-alterar { border:0; background:transparent; color:#166534; text-decoration:underline; cursor:pointer; font-size:12px; padding:0; } .geo-loading-original { padding:10px 12px; background:#f9fafb; border:1px dashed #d1d5db; border-radius:8px; font-size:13px; color:#6b7280; text-align:center; margin-bottom:12px; } .geo-loading-original .geo-spinner { display:inline-block; width:12px; height:12px; border:2px solid #d1d5db; border-top-color:#374151; border-radius:50%; animation:geo-spin-orig 0.7s linear infinite; margin-right:6px; vertical-align:middle; } @keyframes geo-spin-orig { to { transform:rotate(360deg); } } .geo-pill-original[hidden], .geo-loading-original[hidden], #geo-manual-original[hidden] { display: none !important; } /* ===================================================== * MODAL v2 — redesenho UX/UI (mantém IDs/classes originais) * ===================================================== */ /* Reset + design tokens (escopado para não vazar) */ .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.55); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); z-index: 10001; opacity: 0; transition: opacity 240ms cubic-bezier(.4,0,.2,1); } .modal-overlay.active { opacity: 1; } .form-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -46%) scale(.98); width: min(94vw, 440px); max-height: 90vh; overflow-y: auto; background: #ffffff; border-radius: 20px; padding: 28px 26px 24px; box-shadow: 0 10px 15px -3px rgba(0,0,0,.10), 0 30px 60px -15px rgba(0,0,0,.20); z-index: 10002; opacity: 0; transition: all 260ms cubic-bezier(.4,0,.2,1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, 'Helvetica Neue', Arial, sans-serif; color: #0f172a; } .form-container.active { transform: translate(-50%, -50%) scale(1); opacity: 1; } /* Header */ .form-container .form-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 20px; } .form-container .form-header h3 { margin: 0 0 4px; padding: 0; font-size: 17px; font-weight: 700; letter-spacing: -0.01em; color: #0f172a; line-height: 1.25; } .form-container .form-header .form-subtitle { display: block; font-size: 13px; color: #475569; font-weight: 400; line-height: 1.4; } .form-container .modal-close { width: 32px; height: 32px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; border: 0; background: #f1f5f9; color: #64748b; border-radius: 10px; cursor: pointer; font-size: 18px; line-height: 1; transition: background 150ms, color 150ms; } .form-container .modal-close:hover { background: #e2e8f0; color: #0f172a; } .form-container .modal-close svg { width: 14px; height: 14px; } /* Form groups */ .form-container .form-group { margin-bottom: 14px; } .form-container label { display: block; font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: #334155; text-transform: uppercase; margin-bottom: 6px; } /* Inputs / textareas */ .form-container textarea, .form-container input[type=text], .form-container input[type=tel], .form-container input[type=email] { width: 100%; box-sizing: border-box; font-family: inherit; font-size: 16px; color: #0f172a; background: #ffffff; border: 1.5px solid #cbd5e1; border-radius: 12px; padding: 12px 14px; resize: none; overflow: hidden; min-height: 46px; line-height: 1.4; transition: border-color 150ms, background 150ms, box-shadow 150ms; } .form-container textarea::placeholder, .form-container input::placeholder { color: #64748b; } .form-container textarea:focus, .form-container input:focus { outline: none; border-color: #10b981; background: #ffffff; box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.12); } .form-container textarea[readonly] { cursor: pointer; } /* --- Geo pill (detectado) --- */ .geo-pill-original { display: flex; align-items: center; gap: 10px; padding: 12px 14px; background: linear-gradient(180deg, #ecfdf5 0%, #f0fdf4 100%); border: 1.5px solid #86efac; border-radius: 12px; font-size: 14px; color: #14532d; margin-bottom: 14px; transition: border-color 150ms; } .geo-pill-original .geo-icon { width: 18px; height: 18px; flex-shrink: 0; color: #16a34a; display: inline-flex; align-items: center; justify-content: center; } .geo-pill-original .geo-icon svg { width: 100%; height: 100%; } .geo-pill-original .geo-text { flex: 1; font-weight: 600; } .geo-pill-original .geo-alterar { border: 0; background: transparent; color: #15803d; font-size: 12px; font-weight: 500; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; padding: 4px 6px; border-radius: 6px; transition: background 150ms; } .geo-pill-original .geo-alterar:hover { background: rgba(21, 128, 61, 0.08); } /* --- Geo loading --- */ .geo-loading-original { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 14px; background: #f8fafc; border: 1.5px dashed #cbd5e1; border-radius: 12px; font-size: 13px; color: #64748b; margin-bottom: 14px; } .geo-loading-original .geo-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid #cbd5e1; border-top-color: #10b981; border-radius: 50%; animation: geo-spin-orig 0.6s linear infinite; } @keyframes geo-spin-orig { to { transform: rotate(360deg); } } /* Force hidden to work over display:flex */ .geo-pill-original[hidden], .geo-loading-original[hidden], #geo-manual-original[hidden] { display: none !important; } /* --- Submit button --- */ .form-container .submit-button { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 20px; font-size: 15px; font-weight: 600; color: #ffffff; background: linear-gradient(180deg, #22c55e 0%, #16a34a 100%); border: 0; border-radius: 14px; cursor: pointer; box-shadow: 0 4px 12px rgba(22, 163, 74, 0.25); transition: transform 100ms, box-shadow 150ms, filter 150ms; margin-top: 4px; } .form-container .submit-button:hover { box-shadow: 0 6px 16px rgba(22, 163, 74, 0.35); filter: brightness(1.03); } .form-container .submit-button:active { transform: translateY(1px); } .form-container .submit-button:disabled { opacity: .7; cursor: not-allowed; filter: grayscale(.3); } .form-container .submit-button svg { width: 20px; height: 20px; flex-shrink: 0; } .form-container .submit-button .loading { display: none; align-items: center; gap: 8px; } .form-container .submit-button.is-loading .button-text { display: none; } .form-container .submit-button.is-loading .loading { display: inline-flex; } .form-container .submit-button .loading-spinner { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* --- Trust indicators abaixo do botão --- */ .form-trust { margin-top: 12px; display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; font-size: 12px; color: #475569; font-weight: 500; } .form-trust .ti-item { display: inline-flex; align-items: center; gap: 4px; } .form-trust .ti-item svg { width: 12px; height: 12px; color: #22c55e; flex-shrink: 0; } .form-privacy-note { font-size: 11px; color: #64748b; text-align: center; margin-top: 10px; line-height: 1.5; } /* --- Dropdowns (fallback: manual state/city) --- */ .form-container .dropdown { position: relative; } .form-container .dropdown-content { position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,.10); max-height: 240px; overflow: hidden; display: none; z-index: 10003; } .form-container .dropdown-content.active { display: block; } .form-container .dropdown-search { padding: 8px; border-bottom: 1px solid #e2e8f0; } .form-container .dropdown-search textarea { width: 100%; border: 1px solid #e2e8f0; padding: 8px 10px; font-size: 16px; min-height: 36px; border-radius: 8px; background: #f8fafc; } .form-container #stateList, .form-container #cityList { max-height: 180px; overflow-y: auto; } .form-container .dropdown-item { padding: 10px 14px; font-size: 14px; cursor: pointer; color: #334155; transition: background 100ms; } .form-container .dropdown-item:hover { background: #f1f5f9; color: #0f172a; } /* --- Mobile --- */ @media (max-width: 480px) { .form-container { width: 100vw; max-width: none; border-radius: 20px 20px 0 0; top: auto; bottom: 0; transform: translate(-50%, 100%); left: 50%; max-height: 92vh; } .form-container.active { transform: translate(-50%, 0); } } /* === ANTI-ZOOM iOS: fonte >= 16px vence qualquer regra do tema === */ #contact-form #name, #contact-form #phone, #contact-form #state, #contact-form #city, #contact-form #stateSearch, #contact-form #citySearch, .form-container textarea, .form-container input, .form-container input[type=text], .form-container input[type=tel], .form-container input[type=email] { font-size: 16px !important; }    Fale com Especialista      

### Conectando você a um especialista

        Nome *    Telefone *     Localização *  Identificando sua região...    — alterar    Estado            Cidade               Continuar   Enviando...      Conectamos a loja mais próxima a você   Identificamos sua região automaticamente para direcionar ao especialista certo.

        .form-container .iti { width: 100%; display: block; } /* deixa o intl-tel-input calcular o padding-left conforme o DDI (não forçar) */ .form-container .iti--separate-dial-code input#phone { background: #ffffff; } .form-container .iti__selected-dial-code { font-size: 15px; } .iti__country-list { z-index: 10010 !important; max-height: 190px; text-align: left; } .iti__flag-box, .iti__flag { }   document.addEventListener('DOMContentLoaded', function () { // Elementos do DOM const stateField = document.querySelector('#state'); const cityField = document.querySelector('#city'); const stateDropdown = document.querySelector('#stateDropdown'); const cityDropdown = document.querySelector('#cityDropdown'); const stateList = document.querySelector('#stateList'); const cityList = document.querySelector('#cityList'); const stateSearch = document.querySelector('#stateSearch'); const citySearch = document.querySelector('#citySearch'); // Telefone com bandeira + DDI (intl-tel-input), padrão Brasil (+55) (function(){ var pf = document.querySelector('#phone'); if (!pf) return; function maskBR(v){ v=(v||'').replace(/\D/g,'').slice(0,11); if(v.length<=2)return v.length?'('+v:v; if(v.length<=6)return '('+v.slice(0,2)+') '+v.slice(2); if(v.length<=10)return '('+v.slice(0,2)+') '+v.slice(2,6)+'-'+v.slice(6); return '('+v.slice(0,2)+') '+v.slice(2,7)+'-'+v.slice(7); } function doInit(){ if (pf.dataset.itiInit) return true; if (typeof window.intlTelInput !== 'function') return false; pf.dataset.itiInit = '1'; var iti = window.intlTelInput(pf, { initialCountry: 'br', separateDialCode: true, preferredCountries: ['br','pt','us','ar','py','uy'], utilsScript: 'https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/18.2.1/js/utils.min.js' }); window._phoneIti = iti; // Formata inteligente: se vier número completo (autofill com DDI), remove o 55 e formata só o nacional function _smartFormat(){ try { var raw = pf.value; var digits = (raw || '').replace(/\D/g, ''); var hasPlus = /^\s*\+/.test(raw); var iso = iti.getSelectedCountryData().iso2; // Número completo com DDI: tem '+' OU 12/13 dígitos começando em 55 (autofill/paste) var looksIntl = hasPlus || ((digits.length === 12 || digits.length === 13) && digits.slice(0,2) === '55'); if (looksIntl) { var natl = (digits.slice(0,2) === '55' && digits.length >= 12) ? digits.slice(2) : digits; pf.value = (iso === 'br') ? maskBR(natl) : natl; return; } if (iso === 'br') pf.value = maskBR(raw); } catch(_){} } pf.addEventListener('input', _smartFormat); // autofill às vezes não dispara 'input' — reprocessa on change/blur/animationstart pf.addEventListener('change', _smartFormat); pf.addEventListener('blur', _smartFormat); pf.addEventListener('animationstart', function(e){ if (String(e.animationName).indexOf('autofill') > -1) _smartFormat(); }); pf.addEventListener('keydown', function(e){ if (e.key === 'Enter') e.preventDefault(); }); return true; } // tenta iniciar; se a lib ainda não carregou (defer do WP Rocket), re-tenta var tries = 0; (function tryInit(){ if (doInit()) return; if (tries++ > 40) return; // ~8s, mantém input simples se falhar setTimeout(tryInit, 200); })(); })(); // === GEO DETECTION (detecção IP + prefetch + cache local) === const geoLoadingEl = document.getElementById('geo-loading-original'); const geoPillEl = document.getElementById('geo-pill-original'); const geoTextEl = document.getElementById('geo-text-original'); const geoAlterarBtn = document.getElementById('geo-alterar-original'); const geoManualEl = document.getElementById('geo-manual-original'); const GEO_CACHE_KEY = 'sevenballs_geo_v1'; const GEO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24h let leadGeoSource = 'fallback_manual'; function _readGeoCache() { try { const raw = localStorage.getItem(GEO_CACHE_KEY); if (!raw) return null; const obj = JSON.parse(raw); if (!obj || !obj.ts || (Date.now()-obj.ts) > GEO_CACHE_TTL) return null; return obj.data; } catch(_) { return null; } } function _writeGeoCache(d) { try { localStorage.setItem(GEO_CACHE_KEY, JSON.stringify({ts:Date.now(),data:d})); } catch(_) {} } let _geoPrefetch = null; async function _geoFetchTimeout(url, ms) { const ctrl = new AbortController(); const to = setTimeout(() => ctrl.abort(), ms); try { const r = await fetch(url, { cache:'no-store', signal: ctrl.signal }); clearTimeout(to); return r; } catch(e) { clearTimeout(to); return null; } } async function _fetchGeoRaw() { try { const r = await _geoFetchTimeout('https://ipwho.is/', 2500); if (r && r.ok) { const j = await r.json(); if (j && j.success && j.country_code === 'BR' && j.city && j.region_code) return { city: j.city, region: j.region, region_code: j.region_code, source: 'ipwho.is-client' }; } } catch(_) {} try { const r = await _geoFetchTimeout('https://ipapi.co/json/', 2500); if (r && r.ok) { const j = await r.json(); if (j && !j.error && (j.country_code === 'BR' || j.country === 'BR') && j.city && j.region_code) return { city: j.city, region: j.region, region_code: j.region_code, source: 'ipapi.co-client' }; } } catch(_) {} try { const url = (window.SEVENBALLS_AJAX_URL || '/wp-admin/admin-ajax.php') + '?action=geo_detect'; const r = await fetch(url, { cache:'no-store', credentials:'same-origin' }); if (r.ok) { const j = await r.json(); if (j && j.success && j.data && j.data.city && j.data.region_code) return j.data; } } catch(_) {} return null; } async function prefetchGeoOrig() { const c = _readGeoCache(); if (c) return c; if (_geoPrefetch) return _geoPrefetch; _geoPrefetch = (async () => { const g = await _fetchGeoRaw(); if (g) _writeGeoCache(g); return g; })(); return _geoPrefetch; } prefetchGeoOrig().catch(() => {}); function slugNormOrig(s) { return (s||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/[^a-z0-9\s]/g,' ').replace(/\s+/g,' ').trim(); } function _levenOrig(a,b){if(a===b)return 0;if(!a.length)return b.length;if(!b.length)return a.length;let p=new Array(b.length+1);for(let i=0;i<=b.length;i++)p[i]=i;for(let i=1;i<=a.length;i++){const c=[i];for(let j=1;j<=b.length;j++){c[j]=Math.min(c[j-1]+1,p[j]+1,p[j-1]+(a[i-1]===b[j-1]?0:1));}p=c;}return p[b.length];} function _findCityFuzzyOrig(list, name) { const t = slugNormOrig(name); if (!t) return null; let hit = list.find(c => slugNormOrig(c.nome) === t); if (hit) return hit; hit = list.find(c => slugNormOrig(c.nome).includes(t) || t.includes(slugNormOrig(c.nome))); if (hit) return hit; let best=null, bd=Infinity; for (const c of list) { const d=_levenOrig(slugNormOrig(c.nome),t); if (d<bd){bd=d;best=c;} } return (best && bd<=2) ? best : null; } function _showGeoPillOrig(cityHit, stateHit) { geoLoadingEl.hidden = true; geoManualEl.hidden = true; geoPillEl.hidden = false; geoTextEl.textContent = cityHit ? `${cityHit.nome} / ${stateHit.sigla}` : `${stateHit.nome} / ${stateHit.sigla} (cidade não identificada — clique alterar)`; } function _showGeoManualOrig() { geoLoadingEl.hidden = true; geoPillEl.hidden = true; geoManualEl.hidden = false; if (typeof renderStates === 'function' && states && states.length) renderStates(); leadGeoSource = 'fallback_manual'; } if (geoAlterarBtn) geoAlterarBtn.addEventListener('click', () => { geoPillEl.hidden = true; geoManualEl.hidden = false; if (typeof renderStates === 'function' && states && states.length) renderStates(); leadGeoSource = 'user_edited'; }); let _geoRan = false; async function _runGeoOrig() { if (_geoRan) return; _geoRan = true; const TIMEOUT_MS = 3500; const to = new Promise((_,rej) => setTimeout(() => rej(new Error('geo_timeout')), TIMEOUT_MS)); try { await Promise.race([_geoInner(), to]); } catch(err) { console.warn('[contact-form] geo failed:', err && err.message); _showGeoManualOrig(); } } async function _geoInner() { // Aguarda estados carregados (carregarEstados já roda no init) let tries = 0; while ((!states || !states.length) && tries < 30) { await new Promise(r => setTimeout(r, 100)); tries++; } if (!states || !states.length) { return _showGeoManualOrig(); } const geo = await prefetchGeoOrig(); if (!geo || !geo.city || !geo.region_code) return _showGeoManualOrig(); const stateHit = states.find(s => s.sigla === geo.region_code); if (!stateHit) return _showGeoManualOrig(); selectedStateId = stateHit.id; selectedStateSigla = stateHit.sigla; stateField.value = stateHit.nome; // Carrega cidades do estado detectado direto do local (rápido) let cityList = []; try { const r = await fetch(`/wp-content/uploads/ibge/municipios/${stateHit.id}.json`, { cache:'force-cache' }); if (r.ok) cityList = await r.json(); } catch(_) {} if (!cityList.length) { try { const r = await fetch(`https://servicodados.ibge.gov.br/api/v1/localidades/estados/${stateHit.id}/municipios`); if (r.ok) cityList = await r.json(); } catch(_) {} } if (!cityList.length) return _showGeoManualOrig(); cities = cityList.sort((a,b) => a.nome.localeCompare(b.nome)); const cityHit = _findCityFuzzyOrig(cities, geo.city); if (!cityHit) return _showGeoPillOrig(null, stateHit); // pill mostra cidade "não identificada" cityField.value = cityHit.nome; _showGeoPillOrig(cityHit, stateHit); leadGeoSource = 'ip_auto'; } // Dispara ao abrir o modal (via mutation observer no overlay) const _modalOverlayObs = document.querySelector('.modal-overlay'); if (_modalOverlayObs) { const _obs = new MutationObserver(() => { if (_modalOverlayObs.style.display === 'block' || _modalOverlayObs.classList.contains('active')) { _runGeoOrig(); } }); _obs.observe(_modalOverlayObs, { attributes: true, attributeFilter: ['style','class'] }); } // Estados e cidades let states = []; let cities = []; let selectedStateId = null; // Função para enviar notificação no Telegram function notificarFalhaApi(mensagem) { fetch('https://api.telegram.org/bot7558735382:AAG-JQ6lIiCpGN-4BoA0RAqi5wxakRnz7UU/sendMessage', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `chat_id=-4971269108&text=${encodeURIComponent(mensagem)}` }).catch(telegramError => { console.error('Erro ao enviar notificação do Telegram:', telegramError); }); } // Carregar estados com Fallback (IBGE -> Brasil API) async function carregarEstados() { // 1) Local (WP uploads) — sem depender de API externa try { const rLocal = await fetch('/wp-content/uploads/ibge/estados.json', { cache: 'force-cache' }); if (rLocal.ok) { const dataLocal = await rLocal.json(); states = dataLocal.sort((a, b) => a.nome.localeCompare(b.nome)); renderStates(); return; } } catch (_) {} // 2) Fallback IBGE try { const response = await fetch('https://servicodados.ibge.gov.br/api/v1/localidades/estados'); if (!response.ok) throw new Error('IBGE fora'); const data = await response.json(); states = data.sort((a, b) => a.nome.localeCompare(b.nome)); renderStates(); } catch (error) { console.warn('IBGE falhou, tentando Brasil API para estados...'); try { const responseFallback = await fetch('https://brasilapi.com.br/api/ibge/uf/v1'); if (!responseFallback.ok) throw new Error('Brasil API fora'); const dataFallback = await responseFallback.json(); states = dataFallback.sort((a, b) => a.nome.localeCompare(b.nome)); renderStates(); } catch (fallbackError) { console.error('Ambas as APIs de estados falharam:', fallbackError); notificarFalhaApi('IBGE e Brasil API estão fora do ar para Estados'); } } } carregarEstados(); // Normaliza texto p/ busca: minúsculo + remove acentos ("são" == "sao") function _norm(s) { return (s || '').toString().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim(); } // Converte nome de estado OU sigla para a sigla UF (2 letras) — padroniza analytics function toUF(v) { if (!v) return ''; const s = String(v).trim(); if (/^[A-Za-z]{2}$/.test(s)) return s.toUpperCase(); const hit = (states || []).find(st => _norm(st.nome) === _norm(s)); return hit ? hit.sigla : s; } // Funções de renderização function renderStates(filter = '') { const q = _norm(filter); const filtered = states.filter(state => _norm(state.nome).includes(q) || _norm(state.sigla).includes(q) ); stateList.innerHTML = filtered.map(state => `<div class="dropdown-item" data-id="${state.id}" data-sigla="${state.sigla}" data-value="${state.nome}">${state.nome} - ${state.sigla}` ).join(''); // Adicionar eventos aos itens stateList.querySelectorAll('.dropdown-item').forEach(item => { item.addEventListener('click', () => { const stateId = item.dataset.id; const stateName = item.dataset.value; const stateSigla = item.dataset.sigla; selectState(stateId, stateName, stateSigla); }); }); } function renderCities(filter = '') { const q = _norm(filter); const filtered = cities.filter(city => _norm(city.nome).includes(q) ); cityList.innerHTML = filtered.map(city => `${city.nome}` ).join(''); cityList.querySelectorAll('.dropdown-item').forEach(item => { item.addEventListener('click', () => { const cityName = item.dataset.value; selectCity(cityName); }); }); } // Funções de seleção com Fallback para Cidades async function selectState(id, name, sigla) { selectedStateId = id; stateField.value = name; stateDropdown.classList.remove('active'); stateSearch.value = ''; cityField.value = ''; cityField.placeholder = 'Carregando cidades...'; // 1) Local (WP uploads) — sem depender do IBGE try { const rLocal = await fetch(`/wp-content/uploads/ibge/municipios/${id}.json`, { cache: 'force-cache' }); if (rLocal.ok) { cities = (await rLocal.json()).sort((a, b) => a.nome.localeCompare(b.nome)); cityField.placeholder = 'Selecione a cidade'; renderCities(); return; } } catch (_) {} // 2) Fallback IBGE oficial try { const response = await fetch(`https://servicodados.ibge.gov.br/api/v1/localidades/estados/${id}/municipios`); if (!response.ok) throw new Error('IBGE fora'); const data = await response.json(); cities = data.sort((a, b) => a.nome.localeCompare(b.nome)); cityField.placeholder = 'Selecione a cidade'; renderCities(); } catch (error) { console.warn('IBGE falhou, tentando Brasil API para cidades...'); try { const responseFallback = await fetch(`https://brasilapi.com.br/api/ibge/municipios/v1/${sigla}?providers=dados-abertos-br,gov,wikipedia`); if (!responseFallback.ok) throw new Error('Brasil API fora'); const dataFallback = await responseFallback.json(); // IMPORTANTE: Aqui garantimos que o codigo_ibge vire o 'id' em formato de número inteiro (Number), exatamente como o webhook espera cities = dataFallback.map(city => ({ id: parseInt(city.codigo_ibge, 10), nome: city.nome })).sort((a, b) => a.nome.localeCompare(b.nome)); cityField.placeholder = 'Selecione a cidade'; renderCities(); } catch (fallbackError) { console.error('Ambas as APIs de cidades falharam:', fallbackError); cityField.placeholder = 'Erro ao carregar cidades'; notificarFalhaApi(`IBGE e Brasil API estão fora do ar para cidades do estado ${sigla}`); } } } function selectCity(name) { cityField.value = name; cityDropdown.classList.remove('active'); citySearch.value = ''; } // Event Listeners para busca stateSearch.addEventListener('input', (e) => { renderStates(e.target.value); }); citySearch.addEventListener('input', (e) => { renderCities(e.target.value); }); stateField.addEventListener('click', () => { stateDropdown.classList.add('active'); cityDropdown.classList.remove('active'); setTimeout(() => stateSearch.focus(), 0); }); cityField.addEventListener('click', () => { if (selectedStateId) { cityDropdown.classList.add('active'); stateDropdown.classList.remove('active'); setTimeout(() => citySearch.focus(), 0); } }); stateSearch.addEventListener('click', (e) => e.stopPropagation()); citySearch.addEventListener('click', (e) => e.stopPropagation()); document.addEventListener('click', (e) => { if (!e.target.closest('.dropdown')) { stateDropdown.classList.remove('active'); cityDropdown.classList.remove('active'); } }); const floatingButton = document.querySelector('.floating-button'); const modalOverlay = document.querySelector('.modal-overlay'); const formContainer = document.querySelector('.form-container'); const closeButton = document.querySelector('.modal-close'); const contactForm = document.querySelector('#contact-form'); const phoneInput = document.querySelector('#phone'); function formatPhone(value) { value = value.replace(/\D/g, ''); if (value.length > 0) { value = '(' + value; } if (value.length > 3) { value = value.substring(0, 3) + ') ' + value.substring(3); } if (value.length > 10) { value = value.substring(0, 10) + '-' + value.substring(10); } return value; } phoneInput.addEventListener('input', function(e) { let value = e.target.value; e.target.value = formatPhone(value); }); function openModal(type = 'geral') { const url = window.location.href.toLowerCase(); const formTitle = document.querySelector('.form-header h3'); // Mantém "Entre em contato" para /corporativo (B2B), demais páginas usam o título padrão da campanha if (type === 'corporativo' || url.includes('corporativo')) { formTitle.textContent = 'Entre em contato'; formTitle.style.fontSize = ''; formContainer.dataset.popupType = 'corporativo'; } else if (type === 'reebok' || url.includes('reebok')) { formTitle.textContent = 'Conectando você a um especialista Reebok'; formTitle.style.fontSize = ''; formContainer.dataset.popupType = 'reebok'; } else { formTitle.textContent = 'Conectando você a um especialista'; formTitle.style.fontSize = ''; formContainer.dataset.popupType = type; } modalOverlay.style.display = 'block'; requestAnimationFrame(() => { modalOverlay.classList.add('active'); formContainer.classList.add('active'); // Auto-focus no campo Nome (desktop; evita no mobile pra não abrir teclado de cara) try { if (window.matchMedia && window.matchMedia('(min-width: 768px)').matches) { var nf = document.querySelector('#name'); if (nf) setTimeout(function(){ nf.focus(); }, 260); } } catch(_) {} }); // Analytics: form_open try { if (typeof __sevenballs_track === 'function') __sevenballs_track('form_open', { integrador_id: (typeof getIntegradorId === 'function' ? String(getIntegradorId()) : '') }); } catch(_) {} } function closeModal() { modalOverlay.classList.remove('active'); formContainer.classList.remove('active'); setTimeout(() => { modalOverlay.style.display = 'none'; document.querySelector('#name').value = ''; document.querySelector('#phone').value = ''; document.querySelector('#state').value = ''; document.querySelector('#city').value = ''; stateSearch.value = ''; citySearch.value = ''; selectedStateId = null; // Reseta o estado da detecção geo para re-detectar na próxima abertura // (evita pill "fantasma" mostrando cidade com os campos já limpos) try { _geoRan = false; if (typeof geoLoadingEl !== 'undefined' && geoLoadingEl) geoLoadingEl.hidden = false; if (typeof geoPillEl !== 'undefined' && geoPillEl) geoPillEl.hidden = true; if (typeof geoManualEl !== 'undefined' && geoManualEl) geoManualEl.hidden = true; } catch(_) {} }, 300); } floatingButton.addEventListener('click', () => openModal()); closeButton.addEventListener('click', (e) => { e.preventDefault(); closeModal(); }); modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { closeModal(); } }); const botoesGerais = document.querySelectorAll('[id="abre-popup-geral"]'); botoesGerais.forEach(function(botao) { botao.addEventListener('click', function(e) { e.preventDefault(); openModal('geral'); }); }); const botoesReebok = document.querySelectorAll('[id="abre-popup-reebok"]'); botoesReebok.forEach(function(botao) { botao.addEventListener('click', function(e) { e.preventDefault(); openModal('reebok'); }); }); function isOnCorporativoPage() { const url = window.location.href.toLowerCase(); const path = new URL(url).pathname.toLowerCase(); return path.includes('/corporativo') || path === '/corporativo/' || path.includes('corporativo'); } function getIntegradorId() { if (formContainer.dataset.popupType === 'corporativo') { return 11; } const url = window.location.href.toLowerCase(); if (url.includes('reebok')) { return 11; } else if (url.includes('servicos')) { return 8; } else if (url.includes('linha-de-acessorios')) { return 13; } return formContainer.dataset.popupType === 'reebok' ? 11 : 8; } function getUtmParameters() { const urlParams = new URLSearchParams(window.location.search); const utmParams = {}; ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid'].forEach(param => { const value = urlParams.get(param); if (value) { utmParams[param] = value; } }); return utmParams; } function getCookies() { const cookies = {}; document.cookie.split(';').forEach(cookie => { const [name, value] = cookie.split('=').map(c => c.trim()); if (name) { cookies[name] = value || ''; } }); return { cookies }; } contactForm.addEventListener('submit', async function(e) { e.preventDefault(); const submitButton = this.querySelector('button[type="submit"]'); submitButton.disabled = true; submitButton.classList.add('is-loading'); const nameRaw = document.querySelector('#name').value.trim().slice(0, 80); const name = nameRaw.replace(/\s+/g, ' '); const nameParts = name ? name.split(' ').filter(Boolean) : []; const hasFullName = nameParts.length >= 2 && nameParts[nameParts.length - 1].length >= 2; // Telefone completo com DDI (intl-tel-input). Fallback: nacional + DDI do país selecionado const _phEl = document.querySelector('#phone'); let phone = _phEl ? _phEl.value.trim() : ''; try { const _iti = window._phoneIti; if (_iti) { const _full = _iti.getNumber(); // E.164 (ex: +5541997778887) se válido if (_full) { phone = _full; } else { const _dc = (_iti.getSelectedCountryData() || {}).dialCode || '55'; phone = phone ? ('+' + _dc + ' ' + phone) : ''; } } } catch(_) {} const state = document.querySelector('#state').value.trim(); const city = document.querySelector('#city').value.trim(); if (name && phone && state && city && selectedStateId) { const selectedCity = cities.find(c => c.nome === city); const cityId = selectedCity ? selectedCity.id : null; const forceIntegradorId = isOnCorporativoPage() ? 11 : getIntegradorId(); // Gera eventId para deduplicação pixel browser + CAPI servidor const eventId = (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID() : Math.random().toString(36).substring(2) + Date.now().toString(36); // Dispara o pixel no browser com o mesmo eventId (deduplicação) if (typeof fbq !== 'undefined') { fbq('track', 'Lead', {}, { eventID: eventId }); } // IMPORTANTE: Aqui garantimos os tipos exatos do seu webhook anterior const postData = { name: name, // nome completo (nome + sobrenome no mesmo campo) phone: phone, state: state, stateId: String(selectedStateId), // Força a ser string (ex: "41") city: city, cityId: Number(cityId), // Força a ser número (ex: 4106902) currentUrl: window.location.href, integrador_id: forceIntegradorId, eventId: eventId, ...getUtmParameters(), ...getCookies() }; // ===== Envio do lead ===== // 1) sempre tentamos o webhook principal // 2) independente do resultado, salvamos no WordPress com status // 3) se falhar, ainda notificamos o Telegram (comportamento legado) let webhookOk = false; let webhookStatus = 0; let webhookBody = ''; let webhookError = ''; let webhookData = null; try { const response = await fetch('https://fabriks.wedeveloper.com.br/public/receive-lead-form', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(postData) }); webhookStatus = response.status; try { webhookBody = await response.clone().text(); } catch(_) {} if (response.ok) { webhookOk = true; try { webhookData = await response.json(); } catch(_) {} } else { webhookError = 'HTTP ' + response.status; } } catch (err) { webhookError = (err && err.message) ? err.message : String(err); } // ---- Salva SEMPRE no WordPress (sent ou failed) ---- let telegramSent = false; try { const _fd = new FormData(); _fd.append('action', 'save_failed_lead'); _fd.append('status', webhookOk ? 'sent' : 'failed'); _fd.append('name', name); _fd.append('phone', phone); _fd.append('state', state); _fd.append('stateId', String(selectedStateId)); _fd.append('city', city); _fd.append('cityId', (cityId == null ? '' : String(cityId))); _fd.append('currentUrl', window.location.href); _fd.append('referrer', document.referrer || ''); _fd.append('integrador_id', String(forceIntegradorId)); _fd.append('http_status', String(webhookStatus)); _fd.append('error_message', webhookError); _fd.append('response_body', (webhookBody || '').slice(0, 4000)); _fd.append('geo_source', leadGeoSource); _fd.append('extra', JSON.stringify({ utm: getUtmParameters(), cookies: getCookies(), postData: postData })); await fetch((window.SEVENBALLS_AJAX_URL || '/wp-admin/admin-ajax.php'), { method: 'POST', body: _fd, credentials: 'same-origin', keepalive: true }); } catch (saveErr) { console.error('Falha ao salvar lead no WP:', saveErr); } // Analytics: form_submit try { if (typeof __sevenballs_track === 'function') __sevenballs_track('form_submit', { submit_status: webhookOk ? 'sent' : 'failed', city: city, state: toUF(state), integrador_id: forceIntegradorId, utm_source: (getUtmParameters().utm_source || ''), geo_source: leadGeoSource }); } catch(_) {} // ---- Se webhook foi OK: redireciona/agradece ---- if (webhookOk) { submitButton.disabled = false; submitButton.classList.remove('is-loading'); if (webhookData && webhookData.vendedor && webhookData.vendedor.link_whatsapp) { window.location.href = webhookData.vendedor.link_whatsapp; } else { alert(`Obrigado ${name}! Em breve entraremos em contato.`); } closeModal(); } else { // ---- Webhook falhou: dispara Telegram como antes ---- console.error('Erro ao enviar dados para webhook:', webhookError); const leadData = `Lead recebido: Nome: ${name} Telefone: ${phone} Estado: ${state} Cidade: ${city} URL: ${window.location.href} Integrador ID: ${forceIntegradorId}`; try { await fetch('https://api.telegram.org/bot7558735382:AAG-JQ6lIiCpGN-4BoA0RAqi5wxakRnz7UU/sendMessage', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `chat_id=-4971269108&text=${encodeURIComponent(leadData)}` }); telegramSent = true; alert(`Obrigado ${name}! Em breve entraremos em contato.`); closeModal(); } catch (telegramError) { console.error('Erro ao enviar para Telegram:', telegramError); alert('Desculpe, houve um erro ao enviar seus dados. Por favor, tente novamente.'); } submitButton.disabled = false; submitButton.classList.remove('is-loading'); } } else { const missing = []; if (!name) missing.push('Nome'); if (!phone) missing.push('Telefone'); if (!state) missing.push('Estado'); if (!city) missing.push('Cidade'); alert('Por favor, preencha: ' + missing.join(', ') + '.'); submitButton.disabled = false; submitButton.classList.remove('is-loading'); } }); });