PageSourceSearch

https://sanlorenzoprimero.com.ar/wp-content/plugins/Bot-plugin/Bot_scripts.js?ver=1.0.0.1790959039

js sanlorenzoprimero.com.ar collected 2026-10-02 16:38:58 UTC 22,536 bytes, 615 lines download raw bytes

1console.log("📌 [FAQ] BOT_SCRIPT JS SE CARGÓ ✔️");
2
3document.addEventListener('DOMContentLoaded', () => {
4
5    /* Root fijo */
6  let root = document.getElementById('bot-bot-root');
7  if (!root) {
8    root = document.createElement('div');
9    root.id = 'bot-bot-root';
10    root.setAttribute('aria-live', 'polite');
11    root.setAttribute('aria-atomic', 'true');
12    document.body.appendChild(root);
13  }
14
15  const launcher = document.getElementById('bot-launcher');
16  const chat     = document.getElementById('bot-chat');
17  if (!launcher || !chat) return;
18  if (launcher && launcher.parentNode !== root) root.appendChild(launcher);
19  if (chat && chat.parentNode !== root) root.appendChild(chat);
20  /* Sorteo dentro del chat */
21  
22    const overlay = document.getElementById('bot-sorteo');
23    if (overlay && overlay.parentNode !== chat) chat.appendChild(overlay);
24
25    const status    = document.getElementById('bot-status');
26    const openBtn   = document.getElementById('bot-sorteo-btn');
27    const chatClose = document.getElementById('bot-close');
28    const chatInput = document.getElementById('bot-input');
29    
30  
31  const BASES_PDF_URL = '/wp-content/themes/SANLO2020/sorteo/bases-sorteo-bot.pdf';
32
33  function setStatus(txt){
34    if(!status) return;
35    const onlineEl  = document.getElementById('statusOnline');
36    const offlineEl = document.getElementById('statusOffline');
37    if (txt === 'Online') {
38        onlineEl?.classList.remove('hidden');
39        onlineEl?.classList.add('visible');
40        offlineEl?.classList.remove('visible');
41        offlineEl?.classList.add('hidden');
42    } else { 
43        offlineEl?.classList.remove('hidden');
44        offlineEl?.classList.add('visible');
45        onlineEl?.classList.remove('visible');
46        onlineEl?.classList.add('hidden');
47    }   
48    /*
49    status.classList.toggle('online', txt === 'Online');
50    const label = status.querySelector('.label');
51    if (label) label.textContent = txt;*/
52  }
53
54  function lockScroll(){
55    const sbw = window.innerWidth - document.documentElement.clientWidth;
56    const y = window.scrollY || document.documentElement.scrollTop || 0;
57    document.body.classList.add('bot-scroll-lock');
58    document.body.style.position = 'fixed';
59    document.body.style.top = `-${y}px`;
60    document.body.style.left = '0';
61    document.body.style.right = '0';
62    document.body.style.width = '100%';
63    if (sbw > 0) document.body.style.paddingRight = sbw + 'px';
64    chat.dataset.scrollY = String(y);
65  }
66  function unlockScroll(){
67    const y = parseInt(chat.dataset.scrollY || '0', 10);
68    document.body.classList.remove('bot-scroll-lock');
69    document.body.style.position = '';
70    document.body.style.top = '';
71    document.body.style.left = '';
72    document.body.style.right = '';
73    document.body.style.width = '';
74    document.body.style.paddingRight = '';
75    window.scrollTo(0, y);
76  }
77  function openChat(){
78    if (chat.classList.contains('open')) return;
79    chat.classList.add('open');
80    chat.classList.remove('hidden');
81    lockScroll();
82    setStatus('Online');
83    setTimeout(()=> chatInput?.focus(), 140);
84  }
85  function closeChat(){
86    if (!chat.classList.contains('open')) return;
87    chat.classList.remove('open');
88    chat.classList.add('hidden');
89    //setSorteoOpen(false);
90    unlockScroll();
91    setStatus('Offline');
92  }
93
94  if (launcher) launcher.onclick = null;
95  launcher?.addEventListener('click', (e)=>{
96    e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
97    chat.classList.contains('open') ? closeChat() : openChat();
98  }, true);
99
100  document.addEventListener('keydown', (e)=>{ if(e.key === 'Escape'){
101    if (overlay?.classList.contains('open')) { setSorteoOpen(false); }  // ← guard contra null
102    else { closeChat(); }
103  }} , true);
104
105  /* === NUEVO: X del header del chat, en CAPTURA (cierra siempre) === */
106  chatClose?.addEventListener('click', (e) => {
107    e.preventDefault();
108    e.stopPropagation();
109    e.stopImmediatePropagation();
110    closeChat();
111  }, true);
112
113  /* Overlay sorteo */
114  function resetVideoPoster(){
115    const vid = document.getElementById('cs-video');
116    if (!vid) return;
117    try{
118      vid.pause();
119      if (!isNaN(vid.duration)) vid.currentTime = 0;
120      vid.load();
121    }catch(_){}
122  }
123
124function setSorteoOpen(open){
125  if (!overlay) return;
126
127  overlay.classList.toggle('open', open);
128  overlay.style.display = open ? 'flex' : 'none';
129  overlay.style.pointerEvents = open ? 'auto' : 'none';
130  openBtn?.setAttribute('aria-expanded', String(open));
131  chat?.classList.toggle('sorteo-open', open);
132
133  const inputEl = document.getElementById('bot-input');
134  if (inputEl) {
135    if (open) { inputEl.setAttribute('disabled','disabled'); inputEl.blur(); }
136    else { inputEl.removeAttribute('disabled'); }
137  }
138
139  const body = overlay.querySelector('.cs-body');
140
141  if (open) {
142    // limpiar banners residuales (p.ej., “ya estás participando”)
143    window.bot_UI?.clearBanner?.();
144
145    // reset de scroll para que no quede “corrido”
146    try { overlay.scrollTop = 0; } catch(_) {}
147    try { body?.scrollTo?.({ top: 0 }); } catch(_) {}
148
149    // anti-rebote: si no hay contenido para scroll, ocultar overflow
150    if (body) {
151      const noScroll = body.scrollHeight <= body.clientHeight + 1;
152      body.style.overflow = noScroll ? 'hidden' : 'auto';
153    }
154
155    setTimeout(resetVideoPoster, 0);
156  } else {
157    document.getElementById('cs-video')?.pause?.();
158
159    // limpiar overlays secundarios al cerrar
160    document.getElementById('bot_overlay_prizes')?.remove();
161    document.getElementById('bot_host_overlay_form')?.remove();
162    document.getElementById('bot_golden_ticket')?.remove();
163
164    // limpiar banners y restaurar overflow para la próxima apertura
165    window.bot_UI?.clearBanner?.();
166    if (body) body.style.overflow = '';
167
168    setTimeout(()=> chatInput?.focus(), 140);
169  }
170}
171  setSorteoOpen(false);
172
173  openBtn?.addEventListener('click', (e)=>{
174    e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
175	if (!overlay) return;
176    setSorteoOpen(!overlay.classList.contains('open'));
177  }, true);
178
179  // X de overlay (captura, blindada)
180  const csClose = document.getElementById('cs-close');
181  ['click','pointerup','touchend'].forEach(evt=>{
182    csClose?.addEventListener(evt, (e)=>{
183      e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
184      setSorteoOpen(false);
185    }, { capture:true, passive:false });
186  });
187
188  // Navegación interna (video y bases)
189  overlay?.addEventListener('click', (e)=>{
190    const btn = e.target.closest?.('[data-go]');
191    if (!btn) return;
192    const v = btn.dataset.go;
193
194    if (v === 'video') {
195      const scroller = overlay.querySelector('.cs-body');
196      const media    = overlay.querySelector('.cs-media');
197      if (scroller && media) {
198        const top = media.offsetTop - 8;
199        scroller.scrollTo({ top, behavior: 'smooth' });
200      }
201      const vid = document.getElementById('cs-video');
202      if (vid) { vid.muted = true; vid.play().catch(()=>{}); }
203      return;
204    }
205    if (v === 'bases') {
206      try { window.open(BASES_PDF_URL, '_blank', 'noopener,noreferrer'); }
207      catch(_) { location.href = BASES_PDF_URL; }
208      return;
209    }
210  }, true);
211  
212  botAccions.init('btnSecciones', 'seccionesPopover', 'closeSecciones');
213  botAccions.init('btnSecciones-mobile', 'seccionesPopover', 'closeSecciones');
214  botAccions.init('btnFAQ', 'seccionesPopoverFAQ', 'closeSeccionesFAQ');
215  botAccions.init('btnFAQ-mobile', 'seccionesPopoverFAQ', 'closeSeccionesFAQ');
216  botAccions.init('btnFixture', 'FixturePopover', 'closeFixture');
217  botAccions.init('btnFixture-mobile', 'FixturePopover', 'closeSecciones');
218  botAccions.init('btnProximo', 'ProximoPopover', 'closeProximo');
219  botAccions.init('btnProximo-mobile', 'ProximoPopover', 'closeSecciones');
220  botAccions.init('btnHamburger', 'mobileMenuDropdown', 'closeMenu');
221  botAccions.initDetails();  
222});
223
224
225var botAccions = {
226    init: (btnId, popoverId, closeId)=> {        
227    const btn = document.getElementById(btnId);
228    const pop = document.getElementById(popoverId);
229    const closeBtn = document.getElementById(closeId);
230
231    // ✅ GUARD: si no existe en esta página, no rompas todo el JS
232    if (!btn || !pop) {
233      console.log("📌 [FAQ] init skip (faltan nodos):", { btnId, popoverId, closeId, btn: !!btn, pop: !!pop });
234      return;
235    }
236
237    botAccions.addEvents(btn, closeBtn, pop);
238	},
239    ismobile: () => {
240        return window.innerWidth <= 520;
241    },
242    placePopover: (btn, pop) => {
243        const r = btn.getBoundingClientRect();
244        const popW = pop.offsetWidth || 360;
245        const margin = 10;
246        
247        // Posición por defecto: debajo del botón
248        let left = r.left + window.scrollX;        
249        let top  = r.bottom + window.scrollY - 25;
250        
251        if(botAccions.ismobile()){
252            // Posición por defecto: a la derecha del botón
253            left = left + 10;
254            top  = top - 50;
255        }
256        
257        // Evitar que se salga por la derecha
258        const maxLeft = window.scrollX + document.documentElement.clientWidth - popW - margin;
259        left = Math.min(left, maxLeft);
260        left = Math.max(left, window.scrollX + margin);
261
262        pop.style.left = `${left}px`;
263        pop.style.top  = `${top}px`;
264    }, 
265    openPopover: (btn, pop, closeBtn) => {        
266        if (!btn || !pop) return;
267        botAccions.placePopover(btn, pop, closeBtn);
268        pop.classList.add('is-open');
269        pop.setAttribute('aria-hidden', 'false');        
270    },
271    closePopover: (pop) => {        
272        pop.classList.remove('is-open');
273        pop.setAttribute('aria-hidden', 'true');
274        botAccions.closeMobilemenu();
275    },
276    setBotTextValue: (text) => {
277        const inputEl = document.getElementById('bot-input');
278        if (!inputEl) return;       
279        inputEl.value = text;
280        //bot-send
281        const sendBtn = document.getElementById('bot-send');
282        sendBtn?.click();
283    },  
284    closeAllPopovers: () => {
285        const popovers = document.querySelectorAll('.cuervo-popover.is-open');    
286        popovers.forEach(pop => botAccions.closePopover(pop));
287    },  
288    addEvents: (btn, closeBtn, pop)=> {
289        if (!btn || !pop) {
290		  console.log("📌 [FAQ] addEvents skip (btn/pop null)", { btn: !!btn, pop: !!pop });
291		  return;
292		}
293		
294       btn.addEventListener('click', (e) => {
295		e.preventDefault();
296		e.stopPropagation();
297		e.stopImmediatePropagation();
298
299		const wasOpen = pop.classList.contains('is-open');
300
301		// cerrá todos los demás
302		botAccions.closeAllPopovers();
303
304		// toggle del actual
305		if (wasOpen) botAccions.closePopover(pop);
306		else botAccions.openPopover(btn, pop, closeBtn);
307
308	   }, true); // 👈 CAPTURE
309
310        closeBtn?.addEventListener('click', (e) => {
311            e.stopPropagation();
312            botAccions.closePopover(pop);
313        });
314
315        // Click afuera
316        document.addEventListener('click', (e) => {
317            if (!pop.classList.contains('is-open')) return;
318            if (pop.contains(e.target) || btn.contains(e.target)) return;
319            botAccions.closePopover(pop);
320        });
321
322        // ESC
323        document.addEventListener('keydown', (e) => {
324            if (e.key === 'Escape') botAccions.closePopover(pop);
325        });
326
327        // Reposicionar si cambia el viewport
328        window.addEventListener('resize', () => {
329            if (pop.classList.contains('is-open')) botAccions.placePopover(btn, pop);
330        });
331
332        // Acción al tocar una sección
333        pop.addEventListener('click', (e) => {
334            const item = e.target.closest('.cuervo-item');
335            if (!item) return;
336
337            const section = item.dataset.section;
338            botAccions.closePopover(pop);
339
340            // 👇 Integración: acá disparás tu lógica
341            // Ejemplo: enviar al chat / abrir módulo / navegar
342            console.log('Sección elegida:', section);
343
344            botAccions.setBotTextValue(section);
345
346            // Si usás una función de tu bot:
347            // window.CuervoIA?.openSection?.(section);
348            //bot-input
349        });
350
351        //add option to handle error forms.
352        const formType = document.getElementById('faqcTipo');
353        if(formType){
354            formType.addEventListener('change', (e) => {
355                const selectedValue = e.target.value;   
356                botAccions.hideForms();
357                const formToShow = document.getElementById(`form${selectedValue.charAt(0).toUpperCase() + selectedValue.slice(1)}`);;
358                if(formToShow){
359                    formToShow.style.display = 'block';
360                } 
361            });
362            
363            const forms = {
364              error:     document.querySelector('#formError'),
365              mejora:    document.querySelector('#formMejora'),
366              colaborar: document.querySelector('#formColaborar'),
367              juego:     document.querySelector('#formJuego')
368            };
369            const toast = document.getElementById("faqcToast") || document.getElementById("faqToast"); 
370			
371            /*
372            Object.values(forms).forEach(f=>{
373              f?.addEventListener('submit', (e)=>{
374                e.preventDefault();
375                if (toast){
376                  toast.textContent='¡Listo! Gracias por tu aporte 💙❤️';
377                  toast.setAttribute('aria-hidden','false');
378                  setTimeout(()=> toast.setAttribute('aria-hidden','true'), 2200);
379                }
380                e.target.reset();
381              }, true);
382            });
383          */  
384        }
385    },
386    hideForms: () => {
387        const forms = document.querySelectorAll('.faqc-form');     
388        forms.forEach(form => {
389            form.style.display = 'none';            
390        } 
391        );
392    },
393    initDetails: () => {
394        const detailButtons = document.querySelectorAll('.showDetail');
395        detailButtons.forEach(button => {            
396            button.addEventListener('click', (e) => {                
397                const isExpanded = button.getAttribute('aria-expanded') === 'true';
398                
399                button.setAttribute('aria-expanded', String(!isExpanded));
400                const detailPanel = button.nextElementSibling;
401                if (detailPanel) {
402                    detailPanel.classList.add('visible');
403                    detailPanel.classList.remove('hidden');
404                }                
405                botAccions.hideAllDetails(button.getAttribute('data-action'));                 
406                
407            });
408        });
409    }, hideAllDetails: (currentButton) => {
410        const detailPanels = document.querySelectorAll('.detailed-answer'); 
411        detailPanels.forEach(panel => {
412            const panelFor = panel.getAttribute('data-for');
413            if(panelFor === currentButton) return;
414            panel.classList.add('hidden');
415            panel.classList.remove('visible');
416        }
417        );
418    },
419    closeMobilemenu: () => {
420        const mobileMenu = document.getElementById('mobileMenuDropdown');
421        if (mobileMenu && mobileMenu.classList.contains('is-open')) {
422            mobileMenu.classList.remove('is-open');
423            mobileMenu.setAttribute('aria-hidden', 'true');
424        }
425    }
426       
427};
428
429(() => {
430  	// Bloqueo selectpicker SOLO para #faqcTipo
431	try{
432	  const $ = window.jQuery || window.$;
433	  if ($?.fn?.selectpicker){
434		const _orig = $.fn.selectpicker;
435		$.fn.selectpicker = function(...args){
436		  const tocaFaq = (this.is?.('#faqcTipo')) || (this.find?.('#faqcTipo').length>0);
437		  if (tocaFaq) return this; // no lo “enchufes”
438		  return _orig.apply(this, args);
439		};
440	  }
441	}catch{}
442	
443  const $ = (s, r=document) => r.querySelector(s);
444
445  // Tomar SIEMPRE el último #faqcOverlay y subirlo al <body> (evita stacking raros)
446  const overlays = Array.from(document.querySelectorAll('#seccionesPopoverFAQ'));
447  if (!overlays.length){ console.warn('[FAQ] No encontré #seccionesPopoverFAQ'); return; }
448  const overlay = overlays[overlays.length - 1];
449  const panel   = overlay.querySelector('.faqc-panel');
450
451  function pinToTop(){
452    try{ if (overlay.parentNode !== document.body) document.body.appendChild(overlay); }catch(_){}
453    (panel || overlay).setAttribute('tabindex','-1'); // foco para ESC
454  }
455  pinToTop();
456  // re-pin suave por si otros scripts inyectan cosas encima
457  { let n=0; const iv=setInterval(()=>{ pinToTop(); if(++n>12) clearInterval(iv); },150); setTimeout(()=>clearInterval(iv),2500); }
458
459  // Estado inicial
460  overlay.setAttribute('aria-hidden','true');
461
462  // Triggers
463  const btnOpenMain   = $('#faqcOpen');        // botón grande (fuera del chat)
464  const btnOpenHeader = $('#bot-faq-btn');   // botón del header del chat (si existe)
465  const btnClose      = $('#faqcClose');       // X del panel
466  const toast         = $('#faqcToast');
467
468	function detoxFaqcTipo(){
469  const tipo = document.querySelector('#faqcOverlay #faqcTipo');
470  if (!tipo) return;
471  const wrap = tipo.nextElementSibling;
472  if (wrap?.classList?.contains('bootstrap-select')){
473    wrap.remove();
474    tipo.classList.remove('bs-select-hidden');
475    tipo.style.display = 'block';
476  }
477  }
478
479  function openFAQ(e){
480    if(e){ e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation?.(); }
481    pinToTop();
482    overlay.setAttribute('aria-hidden','false');
483	detoxFaqcTipo();
484	if (!openFAQ._bsIv) openFAQ._bsIv = setInterval(detoxFaqcTipo, 400);
485    document.body.classList.add('faqc-open');
486    (panel || overlay).focus?.();
487    if(!openFAQ._iv){ openFAQ._iv=setInterval(pinToTop,300); }
488  }
489  function closeFAQ(e){
490    if(e){ e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation?.(); }
491    overlay.setAttribute('aria-hidden','true');
492    document.body.classList.remove('faqc-open');
493    if(openFAQ._iv){ clearInterval(openFAQ._iv); openFAQ._iv=null; }
494	if (openFAQ._bsIv){ clearInterval(openFAQ._bsIv); openFAQ._bsIv = null; }
495  }
496
497  // Binds (en captura, para ganar a listeners del theme)
498  btnOpenMain   ?.addEventListener('click', openFAQ,  true);
499  btnOpenHeader ?.addEventListener('click', openFAQ,  true);
500  btnClose      ?.addEventListener('click', closeFAQ, true);
501  overlay.addEventListener('click', e=>{ if(e.target===overlay) closeFAQ(e); }, true);
502  document.addEventListener('keydown', e=>{ if(e.key==='Escape' && overlay.getAttribute('aria-hidden')==='false') closeFAQ(e); }, true);
503
504  // Acordeones
505  overlay.addEventListener('click', (e)=>{
506    const btn = e.target.closest?.('[data-acc]'); if(!btn) return;
507    btn.type='button';
508    const acc = btn.closest('.faqc-acc'); if(!acc) return;
509    const ex  = acc.getAttribute('aria-expanded')==='true';
510    acc.setAttribute('aria-expanded', String(!ex));
511  }, true);
512
513  // Selector de tipo (muestra cada form)
514  const tipo  = overlay.querySelector('#faqcTipo');
515  // --- Anti bootstrap-select: forzar el <select> nativo en este acordeón ---
516  if (tipo && tipo.nextElementSibling && tipo.nextElementSibling.classList.contains('bootstrap-select')) {
517  tipo.nextElementSibling.remove();           // saca el wrapper del plugin
518  tipo.classList.remove('bs-select-hidden');  // muestra el <select> real
519  tipo.style.display = 'block';
520  }
521 
522  const forms = {
523    error:     overlay.querySelector('#formError'),
524    mejora:    overlay.querySelector('#formMejora'),
525    colaborar: overlay.querySelector('#formColab'),
526    juego:     overlay.querySelector('#formJuego')
527  };
528  function show(kind){ 
529    Object.keys(forms).forEach(k=>{ if(forms[k]) forms[k].style.display = (k===kind?'block':'none'); }); 
530  }
531  if (tipo){ 
532    tipo.addEventListener('change', e=> show(e.target.value)); show(tipo.value || 'error'); 
533  }
534
535	overlay.addEventListener('click', e => {
536	  if (e.target.closest('.faqc-btn--pri')) {
537		const t = document.getElementById('faqcToast');
538		if (t) { t.setAttribute('aria-hidden','false'); setTimeout(()=>t.setAttribute('aria-hidden','true'), 2200); }
539	  }
540	}, true);
541
542  // Helpers consola
543  window.FAQ_OPEN  = () => openFAQ();
544  window.FAQ_CLOSE = () => closeFAQ();
545
546  console.log('[FAQ] listo (estética + fix). Duplicados:', overlays.length);
547		
548})();
549
550// ✅ [FAQ] Listener GLOBAL: siempre captura submit de los forms del FAQ
551document.addEventListener("submit", async (e) => {
552  const form = e.target.closest(".faqc-form");
553  if (!form) return;
554
555  e.preventDefault();
556
557  console.log("📌 [FAQ] submit capturado", form);
558
559  const kind = form.getAttribute("data-kind") || "";
560  const formData = new FormData(form);
561  const payload = { kind };
562
563  formData.forEach((value, key) => {
564    payload[key] = value;
565  });
566
567  console.log("📌 [FAQ] payload", payload);
568
569  try {
570    const res = await fetch("https://casla-rag-service.onrender.com/faqc-submit", {
571      method: "POST",
572      headers: { "Content-Type": "application/json" },
573      body: JSON.stringify(payload),
574    });
575
576    console.log("📌 [FAQ] backend status", res.status);
577
578    if (!res.ok) throw new Error("HTTP " + res.status);
579
580    const toast = document.getElementById("faqcToast");
581    if (toast) {
582      toast.textContent = "¡Listo! Gracias por tu aporte 💙❤️";
583      toast.setAttribute("aria-hidden", "false");
584      setTimeout(() => toast.setAttribute("aria-hidden", "true"), 2200);
585    }
586
587    form.reset();
588  } catch (err) {
589    console.error("📌 [FAQ] error enviando", err);
590    alert("❌ Falló el envío. Intentá de nuevo.");
591  }
592}, true);
593
594
595// 💬 FUNCIONES DE ACLARACIÓN IA
596  function getClarification(length) {
597    const short = [
598      "Soy una IA 🤖, y a veces me puedo equivocar. ¡Chequeá la info!",
599      "Atención, soy IA 👀. Puedo pifiarla. Verificá si algo suena raro.",
600    ];
601    const medium = [
602      "Ojo, soy una IA 🤖. Trato de ayudar, pero no siempre acierto. Contrastá los datos si tenés dudas.",
603      "Soy IA y me esfuerzo por responder bien, pero no soy infalible 🙈. Lo único que tengo seguro es el 💙❤️ por el Ciclón.",
604    ];
605    const long = [
606      "¡Hola! Soy una IA 🤖 y aunque intento darte la mejor respuesta posible, puedo fallar. Contrastá la info y avisame si hay errores. Mi amor por San Lorenzo es 100% real 💙❤️.",
607      "¿Me creés todo? Ojo, soy una IA 🤖 y a veces meto la pata. Verificá siempre. Lo único que no dudo es que banco al Ciclón a muerte 🔵🔴.",
608    ];
609
610    let pool = short;
611    if (length > 300) pool = long;
612    else if (length > 150) pool = medium;
613
614    return pool[Math.floor(Math.random() * pool.length)];
615  }

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.