1var cartGamificationBanner = []; 2var cartGamificationBannerItems = null; 3 4var CGB_CAROUSEL_INTERVAL_MS = 4000; 5var CGB_VARIANTS = ['', 'OnCart', 'OnProduct', 'OnProductModal']; 6 7var cgbBannersByVariant = {}; 8 9function cgbGetEl(selector, parent) { 10 return (parent || document).querySelector(selector); 11} 12 13function cgbGetAllEls(selector, parent) { 14 return (parent || document).querySelectorAll(selector); 15} 16 17function cgbSetText(selector, value) { 18 var element = cgbGetEl(selector); 19 if (element && value !== undefined && value !== null) { 20 element.textContent = value; 21 } 22} 23 24cartGamificationBanner.parseStringToFloat = function (value) { 25 return parseFloat( 26 String(value) 27 .replace(/ /g, ' ') 28 .replace('R$ ', '') 29 .replace(/\./g, '') 30 .replace(',', '.') 31 ); 32}; 33 34cartGamificationBanner.parseStringToInteger = function (value) { 35 return parseInt( 36 String(value).replace('R$ ', '').replace(/\./g, '').replace(',', '.'), 37 10 38 ); 39}; 40 41cartGamificationBanner.parseFloatToCurrency = function (value) { 42 return value 43 .toLocaleString('pt-br', { style: 'currency', currency: 'BRL' }) 44 .replace('R$', ''); 45}; 46 47cartGamificationBanner.padNumber = function (number) { 48 return number < 10 ? '0' + number : String(number); 49}; 50 51cartGamificationBanner.isAppContext = function () { 52 return ( 53 window.location.hostname.indexOf('app.') !== -1 || 54 typeof window.flutter_inappwebview !== 'undefined' 55 ); 56}; 57 58function cgbShouldShowOnThisContext(banner) { 59 var exibicao = banner.exibicao; 60 if (!exibicao || exibicao === 'ambos') return true; 61 if (exibicao === 'app') return cartGamificationBanner.isAppContext(); 62 if (exibicao === 'site') return !cartGamificationBanner.isAppContext(); 63 return true; 64} 65 66cartGamificationBanner.encodeCartGamificationBannerName = function (banner) { 67 var decoder = document.createElement('textarea'); 68 decoder.innerHTML = banner.nome; 69 banner.nome = decoder.value 70 .normalize('NFD') 71 .replace(/[Ì-ͯ]/g, '') 72 .replace(/[^a-z0-9]/gi, ''); 73 return banner; 74}; 75 76/** 77 * Simula em cima de `subtotal` o efeito de uma regra de pagamento (PIX 10% off, 78 * boleto 5% off, etc.) sem precisar consultar o backend. Permite que o banner 79 * mostre o "preço final" estimado já durante a navegação. 80 * 81 * @param {number} subtotal Valor base (já em reais, número). 82 * @param {object} regraPagamento Objeto `{ valor, tipo, aplicacao }`: 83 * - `valor`: número > 0 (porcentagem ou valor fixo) 84 * - `tipo`: "porcentagem" | "percentual" | "valor" 85 * - `aplicacao`: "desconto" (subtrai) | "acrescimo" (soma) 86 * @returns {number} O subtotal já com a regra aplicada. Devolve o `subtotal` 87 * original quando a regra está mal formada. 88 */ 89cartGamificationBanner.aplicarRegraPagamento = function (subtotal, regraPagamento) { 90 if (!regraPagamento || typeof regraPagamento !== 'object') { 91 return subtotal; 92 } 93 94 var fator = parseFloat(regraPagamento.valor); 95 if (isNaN(fator) || fator <= 0) { 96 return subtotal; 97 } 98 99 var tipo = String(regraPagamento.tipo || '').toLowerCase(); 100 var aplicacao = String(regraPagamento.aplicacao || 'desconto').toLowerCase(); 101 var sinal = aplicacao === 'acrescimo' ? 1 : -1; 102 103 if (tipo === 'porcentagem' || tipo === 'percentual') { 104 return subtotal * (1 + sinal * (fator / 100)); 105 } 106 107 return subtotal + sinal * fator; 108}; 109 110/** 111 * Lê o JSON do banner que o backend imprimiu dentro do `<div cartGamificationBannerItemsâ¦>`. 112 * O backend concatena vários banners separados por vÃrgula (`{â¦},{â¦},`); para virar um 113 * array JSON válido, removemos a vÃrgula final e embrulhamos em `[ ⦠]`. 114 * 115 * @param {string} variant Sufixo da variante ("", "OnCart", "OnProduct", "OnProductModal"). 116 * @returns {Array<object>|null} Lista de banners parseados, ou null se vazio/inválido. 117 */ 118function cgbParseJsonFromContainer(variant) { 119 var container = cgbGetEl('[cartGamificationBannerItems' + variant + ']'); 120 if (!container) return null; 121 122 var raw = (container.innerHTML || '').trim(); 123 if (!raw) return null; 124 125 if (raw.charAt(raw.length - 1) === ',') { 126 raw = raw.slice(0, -1); 127 } 128 129 try { 130 return JSON.parse('[' + raw + ']'); 131 } catch (err) {
132 console.error('cartGamificationBanner: JSON inválido', err); 133 return null; 134 } 135} 136 137cartGamificationBanner.bannerItemHtml = function (banner, element) { 138 var variant = element || ''; 139 140 var iconHtml = banner.textoIcone 141 ? '<img class="cart-gamification-banner__icon" src="' + banner.textoIcone + '" alt="gamification" />' 142 : ''; 143 144 var fontSize = banner.tamanhoDaFonte ? 'font-size: ' + banner.tamanhoDaFonte + ';' : ''; 145 var fontWeight = banner.pesoDaFonte ? 'font-weight: ' + banner.pesoDaFonte + ';' : ''; 146 var barHeight = banner.tamanhoBarraProgresso ? 'height: ' + banner.tamanhoBarraProgresso + ';' : ''; 147 var barPosition = banner.posicaoBarraProgresso ? banner.posicaoBarraProgresso + ': 0;' : ''; 148 149 var countdownHtml = ''; 150 if (banner.cronometroData && banner.cronometroHora) { 151 countdownHtml = 152 '<span class="cart-gamification-banner__countdown" ' + 153 'cartGamificationBannerCountdown' + variant + ' ' + 154 'data-date="' + banner.cronometroData + '" ' + 155 'data-time="' + banner.cronometroHora + '">' + 156 '</span>'; 157 } 158 159 var progressStyle = banner.imagemDoProgresso 160 ? "background-image: url('" + banner.imagemDoProgresso + "');" 161 : 'background: ' + (banner.corDoProgresso || 'transparent') + ';'; 162 163 var initialStyle = banner.corDoFundo 164 ? 'background: ' + banner.corDoFundo + ';' 165 : "background-image: url('" + (banner.imagemDeFundo || '') + "');"; 166 167 return ( 168 '<div class="cart-gamification-banner__item" cartGamificationBannerName' + variant + '=' + banner.nome + '>' + 169 '<div class="cart-gamification-banner__text" style="color: ' + (banner.corDaFonte || '') + '; ' + fontSize + ' ' + fontWeight + '">' + 170 iconHtml + 171 '<span cartGamificationBannerText' + variant + '>' + banner.textoInicial + '</span>' + 172 countdownHtml + 173 '</div>' + 174 '<div class="cart-gamification-banner__progress-background" ' + 175 'style="' + progressStyle + ' width: 0%; ' + barHeight + ' ' + barPosition + '" ' + 176 'cartGamificationBannerProgress' + variant + '></div>' + 177 '<div class="cart-gamification-banner__initial-background" style="' + initialStyle + '"></div>' + 178 '</div>' 179 ); 180}; 181 182cartGamificationBanner.generateBanners = function (element) { 183 var variant = element || ''; 184 185 var parsedFromDom = cgbParseJsonFromContainer(variant); 186 if (parsedFromDom) { 187 cgbBannersByVariant[variant] = parsedFromDom; 188 if (variant === '') { 189 cartGamificationBannerItems = parsedFromDom; 190 } 191 } 192 193 var banners = cgbBannersByVariant[variant]; 194 if (!banners || !banners.length) { 195 return; 196 } 197 198 var html = ''; 199 for (var i = 0; i < banners.length; i++) { 200 var banner = banners[i]; 201 if (!cgbShouldShowOnThisContext(banner)) continue; 202 203 banner = cartGamificationBanner.encodeCartGamificationBannerName(banner); 204 html += cartGamificationBanner.bannerItemHtml(banner, variant); 205 } 206 207 var container = cgbGetEl('[cartGamificationBannerContainer' + variant + ']'); 208 if (!container) return; 209 210 container.innerHTML = html; 211 container.classList.add('cart-gamification-banner__active'); 212 213 cartGamificationBanner.initializeCarousel(variant); 214 cartGamificationBanner.initCountdowns(variant); 215 216 if (typeof cart !== 'undefined' && cart.subtotal !== undefined) { 217 cartGamificationBanner.calculateBannerProgressBarOnCheckout(variant); 218 } else { 219 cartGamificationBanner.calculateBannerProgressBar(variant); 220 } 221}; 222 223cartGamificationBanner.initializeCarousel = function (element) { 224 var variant = element || ''; 225 var bannerItems = cgbGetAllEls('[cartGamificationBannerName' + variant + ']'); 226 227 if (bannerItems.length < 2) return; 228 229 var firstItem = bannerItems[0]; 230 var itemHeight = firstItem.offsetHeight; 231 var currentIndex = 0; 232 var totalItems = bannerItems.length; 233 234 setInterval(function () { 235 currentIndex = (currentIndex + 1) % totalItems; 236 firstItem.style.marginTop = '-' + currentIndex * itemHeight + 'px'; 237 }, CGB_CAROUSEL_INTERVAL_MS); 238}; 239 240cartGamificationBanner.parseCronometroDate = function (dateStr, timeStr) { 241 var dateParts = String(dateStr).split('/'); 242 var timeParts = String(timeStr).split(':'); 243 if (dateParts.length !== 3 || timeParts.length !== 3) return null; 244 245 var targetDate = new Date( 246 +dateParts[2], +dateParts[1] - 1, +dateParts[0],
247 +timeParts[0], +timeParts[1], +timeParts[2] 248 ); 249 return isNaN(targetDate.getTime()) ? null : targetDate; 250}; 251 252cartGamificationBanner.initCountdowns = function (element) { 253 var variant = element || ''; 254 var countdownEls = cgbGetAllEls('[cartGamificationBannerCountdown' + variant + ']'); 255 256 for (var i = 0; i < countdownEls.length; i++) { 257 cartGamificationBanner.bindCountdown(countdownEls[i]); 258 } 259}; 260 261cartGamificationBanner.bindCountdown = function (countdownEl) { 262 var targetDate = cartGamificationBanner.parseCronometroDate( 263 countdownEl.getAttribute('data-date'), 264 countdownEl.getAttribute('data-time') 265 ); 266 if (!targetDate) return; 267 268 if (!cartGamificationBanner.tickCountdown(countdownEl, targetDate)) return; 269 270 var intervalId = setInterval(function () { 271 if (!cartGamificationBanner.tickCountdown(countdownEl, targetDate)) { 272 clearInterval(intervalId); 273 } 274 }, 1000); 275}; 276 277/** 278 * Renderiza um "tick" do cronômetro regressivo no `countdownEl`. Chamada a cada 279 * segundo pelo `bindCountdown`. Cobre 3 estados visuais: 280 * 281 * - **Tempo restante > 60s** â renderiza `dd:hh:mm:ss`, sem destaque. 282 * - **Tempo restante ⤠60s** â adiciona a classe `--urgent` (CSS pulsa a barra). 283 * - **Expirado (⤠0)** â adiciona `--expired` no `.cart-gamification-banner__item` pai 284 * (CSS esconde o item inteiro) e devolve `false` pro caller parar o `setInterval`. 285 * 286 * @param {Element} countdownEl O `<span>` que recebe o markup do tempo. 287 * @param {Date} targetDate Data-alvo do cronômetro. 288 * @returns {boolean} `true` enquanto o cronômetro deve continuar; `false` ao expirar. 289 */ 290cartGamificationBanner.tickCountdown = function (countdownEl, targetDate) { 291 var remainingMs = targetDate.getTime() - Date.now(); 292 293 if (remainingMs <= 0) { 294 var bannerItem = countdownEl.closest('.cart-gamification-banner__item'); 295 if (bannerItem) { 296 bannerItem.classList.add('cart-gamification-banner__item--expired'); 297 } 298 countdownEl.innerHTML = ''; 299 return false; 300 } 301 302 var totalSeconds = Math.floor(remainingMs / 1000); 303 var days = Math.floor(totalSeconds / 86400); 304 var hours = Math.floor((totalSeconds % 86400) / 3600); 305 var minutes = Math.floor((totalSeconds % 3600) / 60); 306 var seconds = totalSeconds % 60; 307 var pad = cartGamificationBanner.padNumber; 308 309 if (totalSeconds <= 60) { 310 countdownEl.classList.add('cart-gamification-banner__countdown--urgent'); 311 } else { 312 countdownEl.classList.remove('cart-gamification-banner__countdown--urgent'); 313 } 314 315 var html = ''; 316 if (days > 0) { 317 html += cartGamificationBanner.renderCountdownBlock(days, 'd'); 318 html += cartGamificationBanner.renderCountdownSeparator(); 319 } 320 html += cartGamificationBanner.renderCountdownBlock(pad(hours), 'h'); 321 html += cartGamificationBanner.renderCountdownSeparator(); 322 html += cartGamificationBanner.renderCountdownBlock(pad(minutes), 'm'); 323 html += cartGamificationBanner.renderCountdownSeparator(); 324 html += cartGamificationBanner.renderCountdownBlock(pad(seconds), 's'); 325 326 countdownEl.innerHTML = html; 327 return true; 328}; 329 330cartGamificationBanner.renderCountdownBlock = function (value, label) { 331 return ( 332 '<span class="cgb-countdown__block">' + 333 '<span class="cgb-countdown__value">' + value + '</span>' + 334 '<span class="cgb-countdown__label">' + label + '</span>' + 335 '</span>' 336 ); 337}; 338 339cartGamificationBanner.renderCountdownSeparator = function () { 340 return '<span class="cgb-countdown__sep">:</span>'; 341}; 342 343cartGamificationBanner.calculateBannerProgressBar = function (element) { 344 var variant = element || ''; 345 346 var banners = cgbBannersByVariant[variant]; 347 if (!banners || !banners.length) return; 348 349 for (var i = 0; i < banners.length; i++) { 350 cgbUpdateBannerItem(banners[i], variant); 351 } 352}; 353 354function cgbUpdateBannerItem(banner, variant) { 355 var cartValues = cgbReadCartValues(banner, variant); 356 if (!cartValues) return; 357 358 if (banner.regraPagamento && banner.regra !== 'itens') { 359 cartValues.cartValue = cartGamificationBanner.aplicarRegraPagamento( 360 cartValues.cartValue, 361 banner.regraPagamento 362 ); 363 } 364 365 var bannerEl = cgbGetEl('[cartGamificationBannerName' + variant + '=' + banner.nome + ']'); 366 if (!bannerEl) return; 367 368 var visualState = cgbComputeVisual(banner, cartValues, variant); 369 cgbApplyVisual(bannerEl, banner, visualState, variant); 370} 371 372/** 373 * Lê do DOM os valores do carrinho que esse banner precisa para o cálculo.
374 * O conjunto de valores muda conforme a `regra`: 375 * 376 * - `'itens'` â contagem inteira (quantos itens vs quantos faltam) 377 * - `'maior+itens'` â subtotal monetário + contagem de itens (precisa dos dois) 378 * - demais regras â apenas subtotal monetário 379 * 380 * Devolve `null` se algum dado obrigatório não estiver no DOM, para que o caller 381 * pule esse banner sem renderizar nada incorreto. 382 * 383 * @param {object} banner Configuração do banner vinda do JSON. 384 * @param {string} variant Sufixo da variante para montar o seletor. 385 * @returns {object|null} `{ cartValue, expectedValue, cartItemsValue?, expectedItemsValue? }` ou null. 386 */ 387function cgbReadCartValues(banner, variant) { 388 var valueEl = cgbGetEl('[cartGamification' + variant + banner.atributo + ']'); 389 if (!valueEl) return null; 390 391 var rawValue = valueEl.innerHTML; 392 if (rawValue === undefined) return null; 393 394 if (banner.regra === 'itens') { 395 return { 396 cartValue: cartGamificationBanner.parseStringToInteger(rawValue), 397 expectedValue: cartGamificationBanner.parseStringToInteger(banner.valorDesejado) 398 }; 399 } 400 401 if (banner.regra === 'maior+itens') { 402 var itemsEl = cgbGetEl('[cartGamification' + variant + 'Itens]'); 403 var rawItems = itemsEl ? itemsEl.innerHTML : undefined; 404 if (rawItems === undefined || banner.valorItensDesejado === undefined) return null; 405 406 return { 407 cartValue: cartGamificationBanner.parseStringToFloat(rawValue), 408 expectedValue: cartGamificationBanner.parseStringToFloat(banner.valorDesejado), 409 cartItemsValue: cartGamificationBanner.parseStringToInteger(rawItems), 410 expectedItemsValue: cartGamificationBanner.parseStringToInteger(banner.valorItensDesejado) 411 }; 412 } 413 414 return { 415 cartValue: cartGamificationBanner.parseStringToFloat(rawValue), 416 expectedValue: cartGamificationBanner.parseStringToFloat(banner.valorDesejado) 417 }; 418} 419 420/** 421 * Função pura que decide texto, largura e estado de "concluÃdo" para um banner, 422 * sem tocar no DOM. Quem aplica o resultado é o `cgbApplyVisual` depois. Trabalha em 3 etapas: 423 * 424 * 1. **Carrinho vazio** (`cartValue <= 0`) â mostra `textoInicial` e barra 0%. 425 * 2. **Regra aplicada** (`cgbApplyRule`) â calcula percentual e quanto falta. 426 * 3. **Substituição de placeholders** â `{value}` (valor faltante em BRL), 427 * `{items}` (qtde faltante), `{discount}` (cálculo entre preço por/original). 428 * 429 * Também faz o override "cliente bonificado por tabela de preço automática": 430 * mesmo sem bater a meta, marca como concluÃdo quando o flag está ativo. 431 * 432 * @param {object} banner Banner do JSON. 433 * @param {object} cartValues SaÃda de `cgbReadCartValues`. 434 * @param {string} variant Sufixo da variante. 435 * @returns {{text: string, width: string, finish: boolean}} 436 */ 437function cgbComputeVisual(banner, cartValues, variant) { 438 var fontSize = banner.tamanhoDaFonte ? 'font-size: ' + banner.tamanhoDaFonte + ';' : ''; 439 var fontWeight = banner.pesoDaFonte ? 'font-weight: ' + banner.pesoDaFonte + ';' : ''; 440 var finishText = banner.linkResultado 441 ? '<a href="' + banner.linkResultado + '" style="' + fontSize + ' ' + fontWeight + '">' + banner.textoResultado + '</a>' 442 : banner.textoResultado; 443 444 if (cartValues.cartValue <= 0) { 445 var initialText = banner.textoInicial; 446 if ((banner.regra || '').trim() === 'maior+itens') { 447 initialText = initialText.replace('{items}', cartValues.expectedItemsValue); 448 } 449 initialText = initialText.replace('{value}', banner.valorDesejado); 450 return { text: initialText, width: '0%', finish: false }; 451 } 452 453 var ruleResult = cgbApplyRule(banner.regra, cartValues); 454 455 if (banner.atributo === 'precoportabeladeprecoautomatica') { 456 var bonusFlagEl = cgbGetEl('[cartGamification' + variant + 'ClienteBonificadoTabelaDePrecoAutomatica]'); 457 if (bonusFlagEl && bonusFlagEl.innerHTML === 'true') { 458 ruleResult.finish = true; 459 ruleResult.width = '100%'; 460 } 461 } 462 463 var text = ruleResult.finish ? finishText : banner.textoAndamento; 464 465 if ( 466 banner.atributo === 'precopororiginal' || 467 banner.atributo === 'precoportabeladeprecoautomatica' 468 ) { 469 var currentPriceEl = cgbGetEl('[cartGamification' + variant + 'PrecoPorTabelaDePre
469coAutomatica]'); 470 var originalPriceEl = cgbGetEl('[cartGamification' + variant + 'PrecoPorOriginal]'); 471 if (currentPriceEl && originalPriceEl) { 472 var currentPrice = cartGamificationBanner.parseStringToFloat(currentPriceEl.innerHTML); 473 var originalPrice = cartGamificationBanner.parseStringToFloat(originalPriceEl.innerHTML); 474 text = text.replace('{discount}', cartGamificationBanner.parseFloatToCurrency(originalPrice - currentPrice)); 475 } 476 } 477 478 if (!ruleResult.finish) { 479 var missingLabel = banner.atributo === 'itens' 480 ? ruleResult.missingValue 481 : cartGamificationBanner.parseFloatToCurrency(ruleResult.missingValue); 482 483 if (banner.regra === 'maior+itens') { 484 text = text.replace('{items}', ruleResult.missingItems); 485 } 486 text = text.replace('{value}', missingLabel); 487 } 488 489 return { text: text, width: ruleResult.width, finish: ruleResult.finish }; 490} 491 492/** 493 * Aplica a regra (`maior`, `menor`, `maior+itens` ou default) e devolve quanto 494 * falta + qual a largura percentual da barra. 495 * 496 * - `maior` â atinge meta quando subtotal ⥠valorDesejado 497 * - `menor` â atinge meta quando subtotal ⤠valorDesejado (ex.: desafio inverso) 498 * - `maior+itens` â precisa bater AMBOS subtotal E contagem; o percentual usa o mais distante 499 * - default â mesma lógica de `maior` 500 * 501 * @param {string} rule Identificador da regra. 502 * @param {object} cartValues Valores lidos do DOM (`cartValue`, `expectedValue`, etc.). 503 * @returns {{finish: boolean, width: string, missingValue?: number, missingItems?: number}} 504 */ 505function cgbApplyRule(rule, cartValues) { 506 var cartValue = cartValues.cartValue; 507 var expected = cartValues.expectedValue; 508 var cartItems = cartValues.cartItemsValue; 509 var expectedItems = cartValues.expectedItemsValue; 510 511 if (rule === 'maior') { 512 if (cartValue >= expected) return { finish: true, width: '100%' }; 513 return { 514 finish: false, 515 width: ((cartValue * 100) / expected) + '%', 516 missingValue: expected - cartValue 517 }; 518 } 519 520 if (rule === 'menor') { 521 if (cartValue <= expected) return { finish: true, width: '100%' }; 522 return { 523 finish: false, 524 width: ((expected * 100) / cartValue) + '%', 525 missingValue: cartValue - expected 526 }; 527 } 528 529 if (rule === 'maior+itens') { 530 if (cartValue >= expected && cartItems >= expectedItems) { 531 return { finish: true, width: '100%' }; 532 } 533 var missingValue = Math.max(expected - cartValue, 0); 534 var missingItems = Math.max(expectedItems - cartItems, 0); 535 var percent = missingValue > 0 536 ? (cartValue * 100) / expected 537 : (cartItems * 100) / expectedItems; 538 return { 539 finish: false, 540 width: percent + '%', 541 missingValue: missingValue, 542 missingItems: missingItems 543 }; 544 } 545 546 if (cartValue >= expected) return { finish: true, width: '100%' }; 547 return { 548 finish: false, 549 width: ((cartValue * 100) / expected) + '%', 550 missingValue: expected - cartValue 551 }; 552} 553 554function cgbApplyVisual(bannerEl, banner, visualState, variant) { 555 var textEl = cgbGetEl('[cartGamificationBannerText' + variant + ']', bannerEl); 556 var progressEl = cgbGetEl('[cartGamificationBannerProgress' + variant + ']', bannerEl); 557 var contentEl = cgbGetEl('.cart-gamification-banner__text', bannerEl); 558 559 if (textEl) textEl.innerHTML = visualState.text; 560 if (progressEl) progressEl.style.width = visualState.width; 561 562 var resultEnableEl = cgbGetEl('[cartGamification' + variant + 'ResultEnable]', bannerEl); 563 if (resultEnableEl && resultEnableEl.value === 'true') { 564 visualState.finish = true; 565 } 566 567 if (progressEl) { 568 cgbPaintProgressBar(progressEl, banner, visualState.finish); 569 } 570 571 if (contentEl) { 572 var color = visualState.finish && banner.corDaFonteResultado 573 ? banner.corDaFonteResultado 574 : banner.corDaFonte; 575 if (color) { 576 contentEl.style.color = color; 577 if (banner.linkResultado) { 578 var linkEl = cgbGetEl('a', contentEl); 579 if (linkEl) linkEl.style.color = color; 580 } 581 } 582 } 583} 584 585function cgbPaintProgressBar(progressEl, banner, finish) { 586 var hasFinishStyle = finish && 587 ((banner.imagemDoResultado && banner.imagemDoResultado !== '') || 588 (banner.corDoResultado && banner.corDoResultado !== '')); 589 590 if (hasFinishStyle) { 591 if (banner.imagemDoResultado) { 592 progressEl.style.backgroundImage = "url('" + banner.imagemDoResultado + "')"; 593 } else { 594 progressEl.style.backgroundColor = banner.corDoResultado; 595 } 596 if (banner.tamanhoBarraProgressoResultado) { 597 progressEl.style.height = banner.tamanhoBarraProgressoResultado; 598 } 599 return; 600 } 601 602 if (banner.imagemDoProgresso) { 603 progressEl.style.backgroundImage = "url('" + banner.imagemDoProgresso + "')"; 604 } else if (banner.corDoProgresso) { 605 progressEl.style.backgroundColor = banner.corDoProgresso; 606 } 607 if (banner.tamanhoBarraProgresso) { 608 progressEl.style.height = banner.tamanhoBarraProgresso; 609 } 610 if (banner.corDaFonte) { 611 progressEl.style.color = banner.corDaFonte; 612 } 613} 614 615cartGamificationBanner.calculateBannerProgressBarOnCheckout = function (element) { 616 var variant = element || ''; 617 618 if (typeof cart === 'undefined') {
619 cartGamificationBanner.calculateBannerProgressBar(variant); 620 return; 621 } 622 623 cgbSetText('[cartGamification' + variant + 'PrecoDe]', cart.subtotalDe); 624 cgbSetText('[cartGamification' + variant + 'PrecoPor]', cart.subtotal); 625 cgbSetText('[cartGamification' + variant + 'PrecoPorOriginal]', cart.subtotalPorOriginal); 626 cgbSetText('[cartGamification' + variant + 'PrecoPorTabelaDePrecoAutomatica]', cart.subtotalPorTabelaDePrecoAutomatica); 627 cgbSetText('[cartGamification' + variant + 'ClienteBonificadoTabelaDePrecoAutomatica]', cart.customerEnableForAutomaticPricingTable); 628 cgbSetText('[cartGamification' + variant + 'Itens]', cart.totalItemsQuantity); 629 cgbSetText('[cartGamification' + variant + 'PrecoComRegraPagamento]', cart.subtotalCalculado); 630 631 cartGamificationBanner.calculateBannerProgressBar(variant); 632}; 633 634function cgbDetectActiveVariants() { 635 var activeVariants = []; 636 for (var i = 0; i < CGB_VARIANTS.length; i++) { 637 if (cgbGetEl('[cartGamificationBannerContainer' + CGB_VARIANTS[i] + ']')) { 638 activeVariants.push(CGB_VARIANTS[i]); 639 } 640 } 641 return activeVariants; 642} 643 644// Flag evita loop: o recalc escreve nos spans observados; libera em microtask 645// pra ignorar os MutationRecords gerados pelas próprias escritas. 646var cgbRecalcInProgress = false; 647 648function cgbRecalcAllVariants() { 649 if (cgbRecalcInProgress) return; 650 cgbRecalcInProgress = true; 651 652 try { 653 var activeVariants = cgbDetectActiveVariants(); 654 for (var i = 0; i < activeVariants.length; i++) { 655 cartGamificationBanner.calculateBannerProgressBarOnCheckout(activeVariants[i]); 656 } 657 } finally { 658 Promise.resolve().then(function () { 659 cgbRecalcInProgress = false; 660 }); 661 } 662} 663 664function cgbObserveBannerVisibility() { 665 if (typeof IntersectionObserver !== 'function') return; 666 667 var containers = []; 668 for (var i = 0; i < CGB_VARIANTS.length; i++) { 669 var container = cgbGetEl('[cartGamificationBannerContainer' + CGB_VARIANTS[i] + ']'); 670 if (container) containers.push(container); 671 } 672 if (!containers.length) return; 673 674 var observer = new IntersectionObserver(function (entries) { 675 for (var i = 0; i < entries.length; i++) { 676 if (!entries[i].isIntersecting) continue; 677 observer.unobserve(entries[i].target); 678 cgbRecalcAllVariants(); 679 } 680 }, { threshold: 0.01 }); 681 682 for (var j = 0; j < containers.length; j++) { 683 observer.observe(containers[j]); 684 } 685} 686 687/** 688 * Observa mudanças no texto dos `<span>` ocultos alimentados pelo cart.js 689 * (subtotal, contagem de itens, etc.). Sempre que o backend atualiza esses 690 * valores â após add/remove de item ou após troca da forma de pagamento â o 691 * MutationObserver dispara o recálculo do banner. 692 * 693 * Em vez de escutar um evento custom (que dependeria de biblioteca externa), 694 * observamos o resultado dele no DOM. Este é o sinal natural e independente. 695 */ 696function cgbObserveCartSpans() { 697 if (typeof MutationObserver !== 'function') return; 698 699 var watchedSpans = []; 700 var watchedAttrs = [ 701 'PrecoPor', 702 'PrecoDe', 703 'PrecoPorOriginal', 704 'PrecoPorTabelaDePrecoAutomatica', 705 'PrecoComRegraPagamento', 706 'Itens', 707 ]; 708 709 for (var v = 0; v < CGB_VARIANTS.length; v++) { 710 for (var a = 0; a < watchedAttrs.length; a++) { 711 var spanEl = cgbGetEl('[cartGamification' + CGB_VARIANTS[v] + watchedAttrs[a] + ']'); 712 if (spanEl) watchedSpans.push(spanEl); 713 } 714 } 715 if (!watchedSpans.length) return; 716 717 var observer = new MutationObserver(cgbRecalcAllVariants); 718 for (var i = 0; i < watchedSpans.length; i++) { 719 observer.observe(watchedSpans[i], { childList: true, characterData: true, subtree: true }); 720 } 721} 722 723function cgbInit() { 724 for (var i = 0; i < CGB_VARIANTS.length; i++) { 725 var variant = CGB_VARIANTS[i]; 726 if (cgbGetEl('[cartGamificationBannerItems' + variant + ']')) { 727 cartGamificationBanner.generateBanners(variant); 728 } 729 } 730 731 document.addEventListener('change', function (event) { 732 var target = event.target; 733 if (!target) return; 734 if (target.id === 'cart-items' || (target.closest && target.closest('#cart
734-items'))) { 735 cgbRecalcAllVariants(); 736 } 737 }); 738 739 cgbObserveBannerVisibility(); 740 cgbObserveCartSpans(); 741 742 cgbRecalcAllVariants(); 743 setTimeout(function () { 744 if (typeof cart !== 'undefined' && cart.subtotalValue > 0) { 745 cgbRecalcAllVariants(); 746 } 747 }, 600); 748} 749 750if (document.readyState === 'loading') { 751 document.addEventListener('DOMContentLoaded', cgbInit); 752} else { 753 cgbInit(); 754}
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.