PageSourceSearch

https://www.scuadra.com.br/js/scuadrai-widget.js

js scuadra.com.br collected 2026-10-03 21:20:10 UTC 15,424 bytes, 279 lines download raw bytes

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.