1/** 2 * Tuiuti â Letreiro Inteligente 3 * Motor de rolagem contÃnua, relógio ao vivo, atualização assÃncrona 4 * e prioridade automática para mensagens emergenciais. 5 * 6 * Sem dependências externas. 7 */ 8(function () { 9 'use strict'; 10 11 if (typeof window === 'undefined' || !window.TL_DATA) { 12 return; 13 } 14 15 var DATA = window.TL_DATA; 16 var MOBILE_BP = 782; 17 18 /* ------------------------------------------------------------------ */ 19 /* Utilidades */ 20 /* ------------------------------------------------------------------ */ 21 22 function el(tag, cls) { 23 var n = document.createElement(tag); 24 if (cls) { 25 n.className = cls; 26 } 27 return n; 28 } 29 30 function isMobile() { 31 return window.matchMedia('(max-width: ' + MOBILE_BP + 'px)').matches; 32 } 33 34 function prefersReducedMotion() { 35 return ( 36 window.matchMedia && 37 window.matchMedia('(prefers-reduced-motion: reduce)').matches 38 ); 39 } 40 41 function hash(str) { 42 var h = 5381; 43 for (var i = 0; i < str.length; i++) { 44 h = ((h << 5) + h + str.charCodeAt(i)) | 0; 45 } 46 return String(h); 47 } 48 49 /* ------------------------------------------------------------------ */ 50 /* Relógio sincronizado com o fuso do servidor */ 51 /* ------------------------------------------------------------------ */ 52 53 var Clock = { 54 delta: 0, 55 tz: null, 56 fmt: null, 57 fmtSec: null, 58 59 sync: function (feed) { 60 if (feed && feed.servidor_iso) { 61 var serverMs = Date.parse(feed.servidor_iso); 62 if (!isNaN(serverMs)) { 63 // gerado_em está em segundos; usa-o como referência de emissão. 64 this.delta = serverMs - Date.now(); 65 // Compensa a latência já decorrida se o payload veio embutido. 66 if (Math.abs(this.delta) > 24 * 3600 * 1000) { 67 this.delta = 0; 68 } 69 } 70 } 71 var tz = feed && feed.fuso ? feed.fuso : null; 72 if (tz && /^[A-Za-z]+\/[A-Za-z_\-+0-9]+$/.test(tz)) { 73 this.tz = tz; 74 } 75 this.buildFormatters(); 76 }, 77 78 buildFormatters: function () { 79 var base = { hour: '2-digit', minute: '2-digit', hour12: false }; 80 var withSec = { 81 hour: '2-digit', 82 minute: '2-digit', 83 second: '2-digit', 84 hour12: false 85 }; 86 if (this.tz) { 87 base.timeZone = this.tz; 88 withSec.timeZone = this.tz; 89 } 90 try { 91 this.fmt = new Intl.DateTimeFormat('pt-BR', base); 92 this.fmtSec = new Intl.DateTimeFormat('pt-BR', withSec); 93 } catch (e) { 94 this.fmt = null; 95 this.fmtSec = null; 96 } 97 }, 98 99 now: function () { 100 return new Date(Date.now() + this.delta); 101 }, 102 103 text: function (withSeconds) { 104 var d = this.now(); 105 var f = withSeconds ? this.fmtSec : this.fmt; 106 if (f) { 107 return f.format(d).replace(/:/g, withSeconds ? ':' : 'h'); 108 } 109 var hh = String(d.getHours()).padStart(2, '0'); 110 var mm = String(d.getMinutes()).padStart(2, '0'); 111 if (!withSeconds) { 112 return hh + 'h' + mm; 113 } 114 return hh + ':' + mm + ':' + String(d.getSeconds()).padStart(2, '0'); 115 }, 116 117 iso: function () { 118 return this.now().toISOString(); 119 } 120 }; 121 122 /* ------------------------------------------------------------------ */ 123 /* Construção de itens */ 124 /* ------------------------------------------------------------------ */ 125 126 function buildItem(item, cfg) { 127 var wrap = el('span', 'tl-item tl-tipo-' + String(item.tipo || 'mensagem').replace(/[^a-z0-9_-]/gi, '')); 128 wrap.setAttribute('data-tl-id', item.id || ''); 129 wrap.setAttribute('data-tl-tipo', item.tipo || ''); 130 131 if (item.cor) { 132 wrap.style.setProperty('--tl-item-cor', item.cor); 133 } 134 135 if (cfg.icones && item.icone) { 136 var ic = el('span', 'tl-icone'); 137 ic.setAttribute('aria-hidden', 'true'); 138 ic.textContent = item.icone; 139 wrap.appendChild(ic); 140 } 141 142 if (cfg.selos && item.selo) { 143 var selo = el('span', 'tl-selo'); 144 selo.textContent = item.selo; 145 wrap.appendChild(selo); 146 } 147 148 var texto = String(item.texto || ''); 149 var alvo; 150 151 if (item.link) { 152 alvo = el('a', 'tl-texto'); 153 alvo.href = item.link; 154 } else { 155 alvo = el('span', 'tl-texto'); 156 } 157 158 if (texto.indexOf('{{HORA}}') !== -1) { 159 var partes = texto.split('{{HORA}}'); 160 for (var i = 0; i < partes.length; i++) { 161 if (partes[i]) { 162 alvo.appendChild(document.createTextNode(partes[i])); 163 } 164 if (i < partes.length - 1) { 165 var t = el('time', 'tl-relogio'); 166 alvo.appendChild(t); 167 } 168 } 169 } else { 170 alvo.textContent = texto; 171 } 172 173 wrap.appendChild(alvo); 174 175 var frag = document.createDocumentFragment(); 176 frag.appendChild(wrap); 177 var sep = el('span', 'tl-sep'); 178 sep.setAttribute('aria-hidden', 'true'); 179 frag.appendChild(sep); 180 181 return frag; 182 } 183 184 function buildSeq(itens, cfg) { 185 var seq = el('div', 'tl-seq'); 186 for (var i = 0; i < itens.length; i++) { 187 seq.appendChild(buildItem(itens[i], cfg)); 188 } 189 return seq; 190 } 191 192 /* ------------------------------------------------------------------ */ 193 /* Motor do letreiro */ 194 /* ------------------------------------------------------------------ */ 195 196 function Letreiro(root) { 197 this.root = root; 198 this.viewport = root.querySelector('.tl-viewport'); 199 this.track = root.querySelector('.tl-track'); 200 this.btn = root.querySelector('.tl-btn-pausa'); 201 202 this.feed = DATA.inicial || { itens: [], emergencias: [], config: {} }; 203 this.cfg = this.feed.config || {}; 204 205 this.offset = 0; 206 this.seqWidth = 0; 207 this.viewWidth = 0; 208 this.running = false;
209 this.pausedByUser = false; 210 this.pausedByHover = false; 211 this.pausedByVisibility = false; 212 this.pausedByViewport = false; 213 this.lastTs = 0; 214 this.laps = 0; 215 this.emergencyLaps = 0; 216 this.pendingFeed = null; 217 this.currentHash = ''; 218 this.reduced = false; 219 this.statsQueue = []; 220 this.itemMetrics = []; 221 this.destroyed = false; 222 223 this.init(); 224 } 225 226 Letreiro.prototype.init = function () { 227 Clock.sync(this.feed); 228 229 this.reduced = 230 !!this.cfg.reducedMotion && prefersReducedMotion(); 231 232 if (this.reduced) { 233 this.root.setAttribute('data-tl-reduzido', '1'); 234 } 235 236 this.render(this.feed, true); 237 this.bind(); 238 this.startClock(); 239 this.scheduleRefresh(); 240 241 this.root.setAttribute('data-tl-pronto', '1'); 242 243 if (!this.reduced) { 244 this.start(); 245 } 246 }; 247 248 /** 249 * Decide quais itens exibir agora (emergência tem prioridade). 250 */ 251 Letreiro.prototype.pickItems = function (feed) { 252 var emerg = feed.emergencias || []; 253 var itens = feed.itens || []; 254 255 if (!emerg.length) { 256 this.root.classList.remove('tl-modo-emergencia'); 257 return itens; 258 } 259 260 var exclusiva = !!this.cfg.emergenciaExclusiva;
261 var reps = parseInt(this.cfg.emergenciaRepeticoes, 10); 262 if (isNaN(reps)) { 263 reps = 3; 264 } 265 266 // reps = 0 â sempre exclusiva enquanto houver emergência. 267 if (exclusiva && (reps === 0 || this.emergencyLaps < reps)) { 268 this.root.classList.add('tl-modo-emergencia'); 269 return emerg; 270 } 271 272 // Após as repetições, intercala: emergências primeiro, depois o ciclo normal. 273 this.root.classList.remove('tl-modo-emergencia'); 274 return emerg.concat(itens); 275 }; 276 277 /** 278 * Monta o conteúdo e mede as dimensões. 279 */ 280 Letreiro.prototype.render = function (feed, first) { 281 var itens = this.pickItems(feed); 282 283 if (!itens.length) { 284 this.root.style.display = 'none'; 285 return; 286 } 287 this.root.style.display = ''; 288 289 var novoHash = hash( 290 JSON.stringify( 291 itens.map(function (i) { 292 return [i.id, i.texto, i.icone, i.cor, i.selo].join('|'); 293 }) 294 ) 295 ); 296 297 // Primeira carga: reaproveita o HTML impresso pelo servidor, mas só se 298 // ele corresponder ao feed. Uma página servida de cache pode trazer 299 // conteúdo antigo â nesse caso remontamos a partir do feed. 300 if (first && this.track.querySelector('.tl-seq')) { 301 if (this.domCombina(itens)) { 302 this.currentHash = novoHash; 303 this.measure(); 304 this.fill(itens); 305 return; 306 } 307 // Divergência: cai no caminho de remontagem abaixo. 308 this.currentHash = ''; 309 } 310 311 if (novoHash === this.currentHash) { 312 return; 313 } 314 this.currentHash = novoHash; 315 316 while (this.track.firstChild) { 317 this.track.removeChild(this.track.firstChild); 318 } 319 this.track.appendChild(buildSeq(itens, this.cfg)); 320 321 this.offset = 0; 322 this.measure(); 323 this.fill(itens); 324 this.paint(); 325 }; 326 327 /** 328 * O HTML já presente na página corresponde aos itens do feed? 329 * 330 * Protege contra HTML servido de cache (plugin de cache, CDN) mais antigo 331 * que o feed embutido: sem isso o letreiro exibiria conteúdo desatualizado 332 * até a primeira atualização assÃncrona. 333 */ 334 Letreiro.prototype.domCombina = function (itens) { 335 var seq = this.track.querySelector('.tl-seq'); 336 if (!seq) { 337 return false; 338 } 339 var nodes = seq.querySelectorAll('.tl-item'); 340 if (nodes.length !== itens.length) { 341 return false; 342 } 343 for (var i = 0; i < nodes.length; i++) { 344 if (nodes[i].getAttribute('data-tl-id') !== String(itens[i].id || '')) { 345 return false; 346 } 347 } 348 return true; 349 }; 350 351 /** 352 * Duplica a sequência até cobrir a viewport (loop sem emenda). 353 */ 354 Letreiro.prototype.fill = function (itens) { 355 var base = this.track.querySelector('.tl-seq'); 356 if (!base) { 357 return; 358 } 359 360 // Remove clones anteriores. 361 var seqs = this.track.querySelectorAll('.tl-seq'); 362 for (var i = seqs.length - 1; i >= 1; i--) { 363 this.track.removeChild(seqs[i]); 364 } 365 366 this.seqWidth = base.getBoundingClientRect().width; 367 this.viewWidth = this.viewport.getBoundingClientRect().width; 368 369 if (this.seqWidth <= 0) { 370 return; 371 } 372 373 // Precisa de largura total >= largura da sequência + viewport. 374 var alvo = this.seqWidth + this.viewWidth; 375 var total = this.seqWidth; 376 var guarda = 0; 377 378 while (total < alvo && guarda < 24) { 379 var clone = base.cloneNode(true); 380 clone.setAttribute('aria-hidden', 'true'); 381 this.track.appendChild(clone); 382 total += this.seqWidth; 383 guarda++; 384 } 385 386 this.computeMetrics(itens); 387 }; 388 389 /** 390 * Larguras individuais dos itens, para estatÃstica de tempo em tela. 391 */ 392 Letreiro.prototype.computeMetrics = function (itens) { 393 this.itemMetrics = []; 394 if (!this.cfg.stats) { 395 return; 396 } 397 var base = this.track.querySelector('.tl-seq'); 398 if (!base) { 399 return; 400 } 401 var nodes = base.querySelectorAll('.tl-item'); 402 for (var i = 0; i < nodes.length && i < itens.length; i++) { 403 this.itemMetrics.push({ 404 id: itens[i].id || '', 405 tipo: itens[i].tipo || 'outro', 406 w: nodes[i].getBoundingClientRect().width 407 }); 408 } 409 }; 410 411 Letreiro.prototype.measure = function () { 412 var base = this.track.querySelector('.tl-seq'); 413 this.seqWidth = base ? base.getBoundingClientRect().width : 0; 414 this.viewWidth = this.viewport.getBoundingClientRect().width; 415 }; 416 417 Letreiro.prototype.speed = function () { 418 var v = isMobile() ? this.cfg.velocidadeMobile : this.cfg.velocidade; 419 v = parseFloat(v); 420 return isNaN(v) || v <= 0 ? 60 : v; 421 }; 422 423 Letreiro.prototype.paint = function () { 424 var x = this.offset; 425 if (this.cfg.direcao === 'direita') { 426 x = this.offset - this.seqWidth; 427 } 428 this.track.style.transform = 'translate3d(' + x.toFixed(2) + 'px,0,0)'; 429 }; 430 431 /* ---------------- Loop de animação ---------------- */ 432 433 Letreiro.prototype.start = function () { 434 if (this.running || this.reduced || this.destroyed) { 435 return; 436 } 437 this.running = true; 438 this.lastTs = 0; 439 var self = this; 440 this.rafId = window.requestAnimationFrame(function (ts) { 441 self.frame(ts); 442 }); 443 }; 444 445 Letreiro.prototype.stop = function () { 446 this.running = false;
447 if (this.rafId) { 448 window.cancelAnimationFrame(this.rafId); 449 this.rafId = null; 450 } 451 }; 452 453 Letreiro.prototype.isPaused = function () { 454 return ( 455 this.pausedByUser || 456 this.pausedByHover || 457 this.pausedByVisibility || 458 this.pausedByViewport 459 ); 460 }; 461 462 Letreiro.prototype.frame = function (ts) { 463 if (!this.running || this.destroyed) { 464 return; 465 } 466 467 var self = this; 468 this.rafId = window.requestAnimationFrame(function (t) { 469 self.frame(t); 470 }); 471 472 if (!this.lastTs) { 473 this.lastTs = ts; 474 return; 475 } 476 477 var dt = (ts - this.lastTs) / 1000; 478 this.lastTs = ts; 479 480 // Protege contra saltos grandes (aba em segundo plano). 481 if (dt > 0.25) { 482 dt = 0.25; 483 } 484 485 if (this.isPaused() || this.seqWidth <= 0) { 486 return; 487 } 488 489 var delta = this.speed() * dt; 490 491 if (this.cfg.direcao === 'direita') { 492 this.offset += delta; 493 if (this.offset >= this.seqWidth) { 494 this.offset -= this.seqWidth; 495 this.onLap(); 496 } 497 } else { 498 this.offset -= delta; 499 if (this.offset <= -this.seqWidth) { 500 this.offset += this.seqWidth; 501 this.onLap(); 502 } 503 } 504 505 this.paint(); 506 }; 507 508 /** 509 * Chamado a cada volta completa: ponto seguro para trocar o conteúdo. 510 */ 511 Letreiro.prototype.onLap = function () { 512 this.laps++; 513 514 if (this.root.classList.contains('tl-modo-emergencia')) { 515 this.emergencyLaps++; 516 } 517 518 this.collectStats(); 519 520 if (this.pendingFeed) { 521 var f = this.pendingFeed; 522 this.pendingFeed = null; 523 this.feed = f; 524 this.cfg = f.config || this.cfg; 525 Clock.sync(f); 526 this.render(f, false); 527 this.tickClock(); 528 return; 529 } 530 531 // Após esgotar as repetições exclusivas, remonta incluindo o ciclo normal. 532 if ( 533 this.root.classList.contains('tl-modo-emergencia') &&
534 parseInt(this.cfg.emergenciaRepeticoes, 10) > 0 && 535 this.emergencyLaps >= parseInt(this.cfg.emergenciaRepeticoes, 10) 536 ) { 537 this.render(this.feed, false); 538 this.tickClock(); 539 } 540 }; 541 542 /* ---------------- EstatÃsticas ---------------- */ 543 544 Letreiro.prototype.collectStats = function () { 545 if (!this.cfg.stats || !this.itemMetrics.length) { 546 return; 547 } 548 var v = this.speed(); 549 if (v <= 0) { 550 return; 551 } 552 for (var i = 0; i < this.itemMetrics.length; i++) { 553 var m = this.itemMetrics[i]; 554 this.statsQueue.push({ 555 id: m.id, 556 tipo: m.tipo, 557 ms: Math.round(((m.w + this.viewWidth) / v) * 1000) 558 }); 559 } 560 if (this.statsQueue.length >= 40) { 561 this.flushStats(); 562 } 563 }; 564 565 Letreiro.prototype.flushStats = function (useBeacon) { 566 if (!this.cfg.stats || !this.statsQueue.length || !DATA.statsUrl) { 567 return; 568 } 569 var payload = JSON.stringify({ eventos: this.statsQueue.slice(0, 200) }); 570 this.statsQueue = []; 571 572 try { 573 if (useBeacon && navigator.sendBeacon) { 574 navigator.sendBeacon( 575 DATA.statsUrl, 576 new Blob([payload], { type: 'application/json' }) 577 ); 578 return; 579 } 580 window.fetch(DATA.statsUrl, { 581 method: 'POST', 582 headers: { 'Content-Type': 'application/json' }, 583 body: payload, 584 keepalive: true, 585 credentials: 'omit' 586 })['catch'](function () {}); 587 } catch (e) { 588 /* silencioso */ 589 } 590 }; 591 592 /* ---------------- Relógio ---------------- */ 593 594 Letreiro.prototype.startClock = function () { 595 if (!this.cfg.mostrarHora) { 596 return; 597 } 598 var self = this; 599 this.tickClock(); 600 this.clockTimer = window.setInterval( 601 function () { 602 self.tickClock(); 603 }, 604 this.cfg.mostrarSegundos ? 1000 : 15000 605 ); 606 }; 607 608 Letreiro.prototype.tickClock = function () { 609 var nodes = this.root.querySelectorAll('.tl-relogio'); 610 if (!nodes.length) { 611 return; 612 } 613 var txt = Clock.text(!!this.cfg.mostrarSegundos); 614 var iso = Clock.iso(); 615 for (var i = 0; i < nodes.length; i++) { 616 if (nodes[i].textContent !== txt) { 617 nodes[i].textContent = txt; 618 nodes[i].setAttribute('datetime', iso); 619 } 620 } 621 }; 622 623 /* ---------------- Atualização assÃncrona ---------------- */ 624 625 Letreiro.prototype.scheduleRefresh = function () { 626 var ms = parseInt(this.cfg.intervalo, 10); 627 if (isNaN(ms) || ms < 30000) { 628 ms = 300000; 629 } 630 var self = this; 631 this.refreshTimer = window.setInterval(function () { 632 self.refresh(); 633 }, ms); 634 }; 635 636 Letreiro.prototype.refresh = function () { 637 if (this.destroyed || !DATA.feedUrl || document.hidden) { 638 return; 639 } 640 var self = this; 641 window 642 .fetch(DATA.feedUrl, { credentials: 'omit' }) 643 .then(function (r) { 644 return r.ok ? r.json() : null; 645 }) 646 .then(function (json) { 647 if (!json || self.destroyed) { 648 return; 649 } 650 if (json.ativo === false) { 651 self.root.style.display = 'none'; 652 self.stop(); 653 return; 654 } 655 656 var temEmergNova = 657 (json.emergencias || []).length > 0 && 658 (self.feed.emergencias || []).length === 0; 659 660 if (temEmergNova) { 661 // Emergência entra imediatamente, sem esperar a volta. 662 self.feed = json; 663 self.cfg = json.config || self.cfg; 664 self.emergencyLaps = 0; 665 Clock.sync(json); 666 self.render(json, false); 667 self.tickClock(); 668 return; 669 } 670 671 self.pendingFeed = json; 672 673 // Se estiver parado (pausado/sem movimento), aplica direto. 674 if (self.reduced || self.isPaused()) { 675 self.feed = json; 676 self.cfg = json.config || self.cfg; 677 self.pendingFeed = null; 678 Clock.sync(json); 679 self.render(json, false); 680 self.tickClock(); 681 } 682 }) 683 ['catch'](function () {}); 684 }; 685 686 /* ---------------- Eventos ---------------- */ 687 688 Letreiro.prototype.bind = function () { 689 var self = this; 690 691 if (this.cfg.pausaHover) { 692 this.root.addEventListener('mouseenter', function () { 693 self.pausedByHover = true; 694 }); 695 this.root.addEventListener('mouseleave', function () { 696 self.pausedByHover = false; 697 }); 698 } 699 700 if (this.cfg.pausaFoco) { 701 this.root.addEventListener('focusin', function () { 702 self.pausedByHover = true; 703 }); 704 this.root.addEventListener('focusout', function () { 705 if (!self.root.contains(document.activeElement)) { 706 self.pausedByHover = false;
707 } 708 }); 709 } 710 711 if (this.btn) { 712 this.btn.addEventListener('click', function () { 713 self.pausedByUser = !self.pausedByUser; 714 self.btn.setAttribute('aria-pressed', self.pausedByUser ? 'true' : 'false'); 715 self.btn.setAttribute( 716 'aria-label', 717 self.pausedByUser 718 ? (DATA.i18n && DATA.i18n.retomar) || 'Retomar letreiro' 719 : (DATA.i18n && DATA.i18n.pausar) || 'Pausar letreiro' 720 ); 721 }); 722 } 723 724 document.addEventListener('visibilitychange', function () { 725 self.pausedByVisibility = document.hidden; 726 if (!document.hidden) { 727 self.lastTs = 0; 728 } 729 }); 730 731 if ('IntersectionObserver' in window) { 732 var io = new IntersectionObserver( 733 function (entries) { 734 for (var i = 0; i < entries.length; i++) { 735 self.pausedByViewport = !entries[i].isIntersecting; 736 if (entries[i].isIntersecting) { 737 self.lastTs = 0; 738 } 739 } 740 }, 741 { threshold: 0 } 742 ); 743 io.observe(this.root); 744 this.io = io; 745 } 746 747 var resizeTimer = null; 748 window.addEventListener('resize', function () { 749 window.clearTimeout(resizeTimer); 750 resizeTimer = window.setTimeout(function () { 751 var itens = self.pickItems(self.feed); 752 self.measure(); 753 self.fill(itens); 754 if (Math.abs(self.offset) > self.seqWidth) { 755 self.offset = 0; 756 } 757 self.paint(); 758 }, 180); 759 }); 760 761 // Recalcula quando as fontes terminam de carregar. 762 if (document.fonts && document.fonts.ready) { 763 document.fonts.ready.then(function () { 764 var itens = self.pickItems(self.feed); 765 self.measure(); 766 self.fill(itens); 767 self.paint(); 768 }); 769 } 770 771 window.addEventListener('pagehide', function () { 772 self.flushStats(true); 773 }); 774 775 document.addEventListener('visibilitychange', function () { 776 if (document.hidden) { 777 self.flushStats(true); 778 } 779 }); 780 781 if (window.matchMedia) { 782 var mq = window.matchMedia('(prefers-reduced-motion: reduce)'); 783 var onChange = function () { 784 var novo = !!self.cfg.reducedMotion && mq.matches; 785 if (novo === self.reduced) { 786 return; 787 } 788 self.reduced = novo; 789 self.root.setAttribute('data-tl-reduzido', novo ? '1' : '0'); 790 if (novo) { 791 self.stop(); 792 self.track.style.transform = ''; 793 } else { 794 self.offset = 0; 795 self.paint(); 796 self.start(); 797 } 798 }; 799 if (mq.addEventListener) { 800 mq.addEventListener('change', onChange); 801 } else if (mq.addListener) { 802 mq.addListener(onChange); 803 } 804 } 805 }; 806 807 Letreiro.prototype.destroy = function () { 808 this.destroyed = true; 809 this.stop(); 810 window.clearInterval(this.clockTimer); 811 window.clearInterval(this.refreshTimer); 812 if (this.io) { 813 this.io.disconnect(); 814 } 815 }; 816 817 /* ------------------------------------------------------------------ */ 818 /* Inicialização */ 819 /* ------------------------------------------------------------------ */ 820 821 function boot() { 822 var nodes = document.querySelectorAll('.tl-letreiro'); 823 for (var i = 0; i < nodes.length; i++) { 824 if (nodes[i].__tlInstance) { 825 continue; 826 } 827 try { 828 nodes[i].__tlInstance = new Letreiro(nodes[i]); 829 } catch (e) { 830 nodes[i].setAttribute('data-tl-pronto', '1'); 831 if (window.console && window.console.warn) { 832 window.console.warn('[Letreiro Tuiuti]', e); 833 } 834 } 835 } 836 } 837 838 if (document.readyState === 'loading') { 839 document.addEventListener('DOMContentLoaded', boot); 840 } else { 841 boot(); 842 } 843 844 window.TuiutiLetreiro = { boot: boot }; 845})();
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.