1(function () { 2 'use strict'; 3 4 var API_URL = 'https://scuadra.ngrok.app/api/public/chat'; 5 var HANDOFF_URL = 'https://scuadra.ngrok.app/api/public/chat/handoff'; 6 var WIDGET_TOKEN = 'scuadrai-9393f2bf4113bbe583a9c030'; 7 var BOT_NAME = 'ScuadrAI'; 8 var BOT_SUBTITLE = 'Consultora de Embalagens'; 9 var GREETING = 'Olá! Sou a ScuadrAI, consultora virtual da Scuadra. Como posso ajudar você a encontrar a embalagem ideal? ð'; 10 var PRIMARY_COLOR = '#16a34a'; // verde Scuadra 11 12 var conversationId = null; 13 var isOpen = false; 14 var isTyping = false; 15 var messageCount = 0; // conta pares de mensagens trocados 16 17 // Gera ID de sessão único 18 function genId() { 19 return 'w-' + Math.random().toString(36).slice(2) + Date.now().toString(36); 20 } 21 conversationId = sessionStorage.getItem('scuadrai_conv') || genId(); 22 sessionStorage.setItem('scuadrai_conv', conversationId); 23 24 // ââ Estilos ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 25 var style = document.createElement('style'); 26 style.textContent = [ 27 '#scuadrai-btn{position:fixed;bottom:24px;right:24px;z-index:9998;width:56px;height:56px;border-radius:50%;background:' + PRIMARY_COLOR + ';border:none;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.25);display:flex;align-items:center;justify-content:center;transition:transform .2s;}', 28 '#scuadrai-btn:hover{transform:scale(1.08);}', 29 '#scuadrai-btn svg{width:28px;height:28px;fill:#fff;}', 30 '#scuadrai-badge{position:absolute;top:-4px;right:-4px;background:#ef4444;color:#fff;font-size:11px;font-weight:700;border-radius:50%;width:18px;height:18px;display:none;align-items:center;justify-content:center;font-family:sans-serif;}', 31 '#scuadrai-window{position:fixed;bottom:90px;right:24px;z-index:9999;width:340px;max-width:calc(100vw - 48px);background:#fff;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.18);display:none;flex-direction:column;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;}', 32 '#scuadrai-header{background:' + PRIMARY_COLOR + ';padding:14px 16px;display:flex;align-items:center;gap:10px;}', 33 '#scuadrai-header .avatar{width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:18px;}', 34 '#scuadrai-header .info{flex:1;}', 35 '#scuadrai-header .name{color:#fff;font-weight:700;font-size:14px;line-height:1.2;}', 36 '#scuadrai-header .sub{color:rgba(255,255,255,.8);font-size:11px;}', 37 '#scuadrai-close{background:none;border:none;color:#fff;cursor:pointer;font-size:20px;line-height:1;padding:4px;}', 38 '#scuadrai-msgs{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:8px;max-height:340px;min-height:200px;background:#f9fafb;}', 39 '.sc-msg{max-width:85%;padding:9px 12px;border-radius:12px;font-size:13px;line-height:1.5;word-break:break-word;}', 40 '.sc-bot{background:#fff;border:1px solid #e5e7eb;border-radius:12px 12px 12px 2px;color:#1f2937;align-self:flex-start;}', 41 '.sc-user{background:' + PRIMARY_COLOR + ';color:#fff;border-radius:12px 12px 2px 12px;align-self:flex-end;}', 42 '.sc-typing{display:flex;gap:4px;align-items:center;padding:10px 14px;}', 43 '.sc-typing span{width:7px;height:7px;background:#9ca3af;border-radius:50%;animation:sc-bounce .9s infinite;}', 44 '.sc-typing span:nth-child(2){animation-delay:.2s;}', 45 '.sc-typing span:nth-child(3){animation-delay:.4s;}', 46 '@keyframes sc-bounce{0%,60%,100%{transform:translateY(0);}30%{transform:translateY(-6px);}}', 47 '#scuadrai-input-row{display:flex;gap:8px;padding:10px;border-top:1px solid #e5e7eb;background:#fff;}', 48 '#scuadrai-input{flex:1;border:1px solid #d1d5db;border-radius:20px;padding:8px 14px;font-size:13px;outline:none;font-family:inherit;}', 49 '#scuadrai-input:focus{border-color:' + PRIMARY_COLOR + ';}', 50 '#scuadrai-send{background:' + PRIMARY_COLOR + ';border:none;border-radius:50%;width:34px;height:34px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;}', 51 '#scuadrai-send svg{width:16px;height:16px;fill:#fff;}', 52 '#scuadrai-powered{text-align:center;font-size:10px;color:#9ca3af;padding:4px 0 8px;background:#fff;}', 53 '.sc-products{display:flex;flex-direction:column;gap:6px;margin-top:4px;align-self:flex-start;width:100%;max-width:85%;}', 54 '.sc-product-card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;display:flex;align-items:center;gap:8px;padding:6px 8px;font-size:12px;color:#1f2937;}', 55 '.sc-product-img{width:48px;height:48px;object-fit:contain;border-radius:6px;flex-shrink:0;background:#f3f4f6;}', 56 '.sc-product-img-placeholder{width:48px;height:48px;border-radius:6px;flex-shrink:0;background:#f3f4f6;display:flex;align-items:center;justify-content:center;font-size:20px;}', 57 '.sc-product-info{flex:1;min-width:0;}', 58 '.sc-product-name{font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}', 59 '.sc-product-detail{color:#6b7280;font-size:11px;margin-top:2px;}', 60 '#scuadrai-handoff-bar{display:none;padding:8px 10px;background:#f0fdf4;border-top:1px solid #bbf7d0;}', 61 '#scuadrai-handoff-btn{width:100%;background:' + PRIMARY_COLOR + ';color:#fff;border:none;border-radius:8px;padding:8px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;}', 62 '#scuadrai-modal{display:none;position:fixed;inset:0;z-index:10000;align-items:center;justify-content:center;background:rgba(0,0,0,.4);}', 63 '#scuadrai-modal-box{background:#fff;border-radius:16px;padding:24px;width:300px;max-width:calc(100vw - 32px);box-shadow:0 8px 32px rgba(0,0,0,.2);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;}', 64 '#scuadrai-modal-box h3{margin:0 0 8px;font-size:15px;color:#1f2937;}', 65 '#scuadrai-modal-box p{margin:0 0 16px;font-size:12px;color:#6b7280;}', 66 '#scuadrai-phone-input{width:100%;box-sizing:border-box;border:1px solid #d1d5db;border-radius:8px;padding:9px 12px;font-size:14px;outline:none;margin-bottom:12px;font-family:inherit;}', 67 '#scuadrai-phone-input:focus{border-color:' + PRIMARY_COLOR + ';}', 68 '#scuadrai-modal-actions{display:flex;gap:8px;}', 69 '#scuadrai-modal-confirm{flex:1;background:' + PRIMARY_COLOR + ';color:#fff;border:none;border-radius:8px;padding:9px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;}', 70 '#scuadrai-modal-cancel{background:#f3f4f6;color:#374151;border:none;border-radius:8px;padding:9px 14px;font-size:13px;
70cursor:pointer;font-family:inherit;}', 71 ].join(''); 72 document.head.appendChild(style); 73 74 // ââ HTML âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 75 var btn = document.createElement('button'); 76 btn.id = 'scuadrai-btn'; 77 btn.setAttribute('aria-label', 'Falar com ScuadrAI'); 78 btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/></svg><div id="scuadrai-badge"></div>'; 79 80 var win = document.createElement('div'); 81 win.id = 'scuadrai-window'; 82 win.innerHTML = [ 83 '<div id="scuadrai-header">', 84 ' <div class="avatar">ð¤</div>', 85 ' <div class="info"><div class="name">' + BOT_NAME + '</div><div class="sub">' + BOT_SUBTITLE + '</div></div>', 86 ' <button id="scuadrai-close" aria-label="Fechar">â</button>', 87 '</div>', 88 '<div id="scuadrai-msgs"></div>', 89 '<div id="scuadrai-input-row">', 90 ' <input id="scuadrai-input" type="text" placeholder="Digite sua dúvida..." maxlength="500" autocomplete="off"/>', 91 ' <button id="scuadrai-send" aria-label="Enviar"><svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg></button>', 92 '</div>', 93 '<div id="scuadrai-handoff-bar"><button id="scuadrai-handoff-btn">ð² Continuar pelo WhatsApp</button></div>', 94 '<div id="scuadrai-powered">Powered by ScuadrAI â¨</div>', 95 ].join(''); 96 97 var modal = document.createElement('div'); 98 modal.id = 'scuadrai-modal'; 99 modal.innerHTML = [ 100 '<div id="scuadrai-modal-box">', 101 ' <h3>Continuar pelo WhatsApp</h3>', 102 ' <p>Informe seu número e um consultor da Scuadra entrará em contato. A conversa continuará no seu WhatsApp.</p>', 103 ' <input id="scuadrai-phone-input" type="tel" placeholder="(11) 99999-9999" maxlength="20"/>', 104 ' <div id="scuadrai-modal-actions">', 105 ' <button id="scuadrai-modal-cancel">Cancelar</button>', 106 ' <button id="scuadrai-modal-confirm">Enviar</button>', 107 ' </div>', 108 '</div>', 109 ].join(''); 110 111 document.body.appendChild(btn); 112 document.body.appendChild(win); 113 document.body.appendChild(modal); 114 115 var msgs = document.getElementById('scuadrai-msgs'); 116 var input = document.getElementById('scuadrai-input'); 117 var badge = document.getElementById('scuadrai-badge'); 118 var unread = 0; 119 120 // ââ Funções âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 121 function addMsg(text, role) { 122 var el = document.createElement('div'); 123 el.className = 'sc-msg ' + (role === 'user' ? 'sc-user' : 'sc-bot'); 124 el.textContent = text; 125 msgs.appendChild(el); 126 msgs.scrollTop = msgs.scrollHeight; 127 return el; 128 } 129 130 function showTyping() { 131 var el = document.createElement('div'); 132 el.className = 'sc-msg sc-bot sc-typing'; 133 el.id = 'sc-typing-indicator'; 134 el.innerHTML = '<span></span><span></span><span></span>'; 135 msgs.appendChild(el); 136 msgs.scrollTop = msgs.scrollHeight; 137 } 138 139 function hideTyping() { 140 var el = document.getElementById('sc-typing-indicator'); 141 if (el) el.remove(); 142 } 143 144 function openChat() { 145 isOpen = true; 146 win.style.display = 'flex'; 147 unread = 0; 148 badge.style.display = 'none'; 149 if (msgs.children.length === 0) addMsg(GREETING, 'bot'); 150 setTimeout(function () { input.focus(); }, 100); 151 } 152 153 function closeChat() { 154 isOpen = false; 155 win.style.display = 'none'; 156 } 157 158 function getPageContext() { 159 var path = window.location.pathname.replace(/\.html$/, '').replace(/^\//, '').replace(/\/$/, '') || 'home'; 160 return path.split('/').pop() || 'home'; 161 } 162 163 function addProductCards(products) { 164 if (!products || products.length === 0) return; 165 var container = document.createElement('div'); 166 container.className = 'sc-products'; 167 products.forEach(function (p) { 168 var card = document.createElement('div'); 169 card.className = 'sc-product-card'; 170 var imgHtml = p.imagemUrl 171 ? '<img class="sc-product-img" src="' + p.imagemUrl + '" alt="' + (p.nome || '') + '" onerror="this.style.display=\'none\'">' 172 : '<div class="sc-product-img-placeholder">ð¦</div>'; 173 var detail = p.tamanho || ''; 174 card.innerHTML = imgHtml + '<div class="sc-product-info"><div class="sc-product-name">' + (p.nome || '') + '</div>' + (detail ? '<div class="sc-product-detail">' + detail + '</div>' : '') + '</div>'; 175 container.appendChild(card); 176 }); 177 msgs.appendChild(container); 178 msgs.scrollTop = msgs.scrollHeight; 179 } 180 181 function showHandoffBar() { 182 var bar = document.getElementById('scuadrai-handoff-bar'); 183 if (bar) bar.style.display = 'block'; 184 } 185 186 function openModal() { 187 modal.style.display = 'flex'; 188 setTimeout(function () { document.getElementById('scuadrai-phone-input').focus(); }, 100); 189 } 190 191 function closeModal() { 192 modal.style.display = 'none'; 193 document.getElementById('scuadrai-phone-input').value = ''; 194 } 195 196 function doHandoff() { 197 var phone = (document.getElementById('scuadrai-phone-input').value || '').trim(); 198 if (!phone) return; 199 var btn = document.getElementById('scuadrai-modal-confirm'); 200 btn.disabled = true; 201 btn.textContent = 'Enviando...'; 202 203 fetch(HANDOFF_URL, { 204 method: 'POST', 205 headers: { 'Content-Type': 'application/json', 'x-widget-token': WIDGET_TOKEN }, 206 body: JSON.stringify({ phone: phone, conversationId: conversationId }), 207 }) 208 .then(function (r) { return r.json(); }) 209 .then(function (data) { 210 closeModal(); 211 if (data.ok) { 212 addMsg('Perfeito! Você receberá uma mensagem no WhatsApp em breve. Até logo! ð', 'bot'); 213 document.getElementById('scuadrai-handoff-bar').style.display = 'none'; 214 input.disabled = true; 215 document.getElementById('scuadrai-send').disabled = true; 216 } else { 217 addMsg('Não conseguimos enviar. Verifique o número e tente novamente.', 'bot'); 218 } 219 }) 220 .catch(function () { 221 closeModal(); 222 addMsg('Erro ao enviar. Tente novamente.', 'bot'); 223 }) 224 .finally(function () { 225 btn.disabled = false;
226 btn.textContent = 'Enviar'; 227 }); 228 } 229 230 function sendMessage() { 231 var text = (input.value || '').trim(); 232 if (!text || isTyping) return; 233 input.value = ''; 234 addMsg(text, 'user'); 235 isTyping = true; 236 showTyping(); 237 238 var body = JSON.stringify({ message: text, conversationId: conversationId, pageContext: getPageContext() }); 239 fetch(API_URL, { 240 method: 'POST', 241 headers: { 'Content-Type': 'application/json', 'x-widget-token': WIDGET_TOKEN }, 242 body: body, 243 }) 244 .then(function (r) { return r.json(); }) 245 .then(function (data) { 246 hideTyping(); 247 isTyping = false; 248 var reply = data.reply || 'Desculpe, não consegui processar sua mensagem.'; 249 if (data.conversationId) conversationId = data.conversationId; 250 sessionStorage.setItem('scuadrai_conv', conversationId); 251 addMsg(reply, 'bot'); 252 if (data.suggestedProducts) addProductCards(data.suggestedProducts); 253 messageCount++; 254 if (messageCount >= 2) showHandoffBar(); 255 if (!isOpen) { 256 unread++; 257 badge.textContent = unread; 258 badge.style.display = 'flex'; 259 } 260 }) 261 .catch(function () { 262 hideTyping(); 263 isTyping = false; 264 addMsg('Erro de conexão. Tente novamente em instantes.', 'bot'); 265 }); 266 } 267 268 // ââ Eventos âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 269 btn.addEventListener('click', function () { isOpen ? closeChat() : openChat(); }); 270 document.getElementById('scuadrai-close').addEventListener('click', closeChat); 271 document.getElementById('scuadrai-send').addEventListener('click', sendMessage); 272 input.addEventListener('keydown', function (e) { if (e.key === 'Enter') sendMessage(); }); 273 document.getElementById('scuadrai-handoff-btn').addEventListener('click', openModal); 274 document.getElementById('scuadrai-modal-cancel').addEventListener('click', closeModal); 275 document.getElementById('scuadrai-modal-confirm').addEventListener('click', doHandoff); 276 document.getElementById('scuadrai-phone-input').addEventListener('keydown', function (e) { if (e.key === 'Enter') doHandoff(); }); 277 modal.addEventListener('click', function (e) { if (e.target === modal) closeModal(); }); 278 279})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.