PageSourceSearch

https://www.kvom.com/wp-content/plugins/srr-ads/includes/js/adblock-lazyload.js?ver=4.13.0

js kvom.com collected 2026-09-26 10:44:47 UTC 26,441 bytes, 690 lines download raw bytes

1(function () {
2  function detectDevice() {
3    return (window.matchMedia && window.matchMedia("(max-width: 991px)").matches)
4      ? "phone"
5      : "computer";
6  }
7
8  function normalizePagePath(path) {
9    try {
10      let p = String(path || "").trim();
11      if (!p) return "/";
12
13      if (/^[a-z][a-z0-9+.-]*:\/\//i.test(p) || p.indexOf("//") === 0) {
14        try {
15          p = new URL(p, (window.location && window.location.origin) ? window.location.origin : undefined).pathname || "/";
16        } catch (e) {}
17      }
18
19      const hashIdx = p.indexOf("#");
20      if (hashIdx >= 0) p = p.slice(0, hashIdx);
21      const queryIdx = p.indexOf("?");
22      if (queryIdx >= 0) p = p.slice(0, queryIdx);
23
24      p = p.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
25      if (!p) p = "/";
26      if (p.charAt(0) !== "/") p = "/" + p;
27      if (p.length > 1) p = p.replace(/\/+$/, "");
28      return p || "/";
29    } catch (e) {
30      return "/";
31    }
32  }
33
34  function shuffle(arr) {
35    for (let i = arr.length - 1; i > 0; i--) {
36      const j = Math.floor(Math.random() * (i + 1));
37      const tmp = arr[i];
38      arr[i] = arr[j];
39      arr[j] = tmp;
40    }
41    return arr;
42  }
43
44  function buildAdSlide(ad, block) {
45    // Source-of-truth slide node is the legacy wrapper itself
46    // (.adr.ad_###) directly under .adblock_carousel
47    const wrap = document.createElement("div");
48    wrap.className = "adr ad_" + ad.id;
49
50    // IMPORTANT: Impression tracking must be enforced at render time.
51    // Only mark the ad as trackable when the server says tracking is enabled.
52    // impressions-beacon observes `[data-ad-id]` elements.
53    if (ad.track) {
54      wrap.setAttribute("data-ad-id", String(ad.id));
55    }
56
57    // Keep the configured block dimensions as the maximum size,
58    // but allow the slide to shrink responsively with its container.
59    const bw = parseInt(block.width, 10);
60    const bh = parseInt(block.height, 10);
61    if (Number.isFinite(bw) && bw > 0) {
62      wrap.style.maxWidth = bw + "px";
63      wrap.style.width = "100%";
64    }
65    if (Number.isFinite(bh) && bh > 0) {
66      if (Number.isFinite(bw) && bw > 0) {
67        // Allows the height to scale with the width while preserving ratio.
68        wrap.style.aspectRatio = bw + " / " + bh;
69        wrap.style.height = "auto";
70      } else {
71        // Without a width, we can't safely scale; reserve height only.
72        wrap.style.height = bh + "px";
73      }
74    }
75    wrap.style.margin = "0 auto"; // Center within container when maxWidth is set
76    wrap.style.padding = "0";
77    wrap.style.overflow = "hidden";
78
79    // Image ad
80    if (ad.type === "image_ad") {
81      const imgUrl =
82        (block._device === "phone" && ad.mobile_img_url)
83          ? ad.mobile_img_url
84          : ad.img_url;
85
86      const img = document.createElement("img");
87      img.src = imgUrl || "";
88      img.alt = ad.title || "Advertisement";
89      img.className = "adimage ultra-trackable" + (ad.track ? " impressionAdEnabled" : "");
90      img.style.display = "block";
91      // FIX: Ensure image scales responsively within its container
92      img.style.width = "100%";
93      img.style.maxWidth = "100%";
94      img.style.height = "auto";
95      // For carousel slides (absolute positioned), use object-fit to maintain aspect ratio
96      img.style.objectFit = "contain";
97
98      if (ad.link) {
99        const a = document.createElement("a");
100        a.href = ad.link;
101        a.target = ad.target || "_self";
102        a.rel = "nofollow noopener";
103        a.setAttribute("aria-label", ad.title || "Advertisement");
104        a.className = "adlink ad_" + ad.id + (ad.track ? " impressionAdEnabled" : "");
105        // FIX: Ensure link wrapper doesn't constrain image responsiveness
106        a.style.display = "block";
107        a.style.width = "100%";
108        a.style.height = "100%";
109        // NOTE: impressions-beacon needs to find data-ad-id; it's on <img> already
110        a.appendChild(img);
111        wrap.appendChild(a);
112      } else {
113        wrap.appendChild(img);
114      }
115
116      return wrap;
117    }
118
119    // Non-image ad
120    const inner = document.createElement("div");
121    inner.innerHTML = ad.content_html || "";
122    try {
123      const scripts = inner.querySelectorAll ? inner.querySelectorAll('script') : [];
124      if (scripts && scripts.length) {
125        scripts.forEach(function(oldScript){
126          try {
127            const s = document.createElement('script');
128            try {
129              if (oldScript.attributes && oldScript.attributes.length) {
130                for (let i = 0; i < oldScript.attributes.length; i++) {
131                  const a = oldScript.attributes[i];
132                  if (a && a.name) s.setAttribute(a.name, a.value || '');
133                }
134              }
135            } catch (e) {}
136            const code = oldScript.text || oldScript.textContent || '';
137            if (code && !s.src) {
138              s.text = code;
139            }
140            if (oldScript.parentNode) {
141              oldScript.parentNode.replaceChild(s, oldScript);
142            }
143          } catch (e) {}
144        });
145      }
146    } catch (e) {}
147    wrap.appendChild(inner);
148
149    return wrap;
150  }
151
152
153  /**
154   * Ad Rotation: the payload carries every eligible variant, and the browser
155   * picks one per page view. This has to happen here rather than on the server,
156   * because the payload is cached and shared — a server-side pick would show the
157   * same variant to everyone in that cache window and skew the A/B split.
158   *
159   * Even split: start at a random offset per browser, then step through the list
160   * on each following view. Starting everyone at index 0 would hand the first
161   * variant nearly every first impression.
162   */
163  function pickRotationVariant(ad) {
164    if (!ad || !Array.isArray(ad.variants) || ad.variants.length === 0) return ad;
165    const variants = ad.variants;
166    if (variants.length === 1) return variants[0];
167
168    const key = "srrAds:rot:" + (ad.rotation_parent || ad.id || "x");
169    let i;
170    try {
171      const stored = window.sessionStorage.getItem(key);
172      i = stored === null ? Math.floor(Math.random() * variants.length) : (parseInt(stored, 10) || 0);
173      window.sessionStorage.setItem(key, String((i + 1) % variants.length));
174    } catch (e) {
175      i = Math.floor(Math.random() * variants.length);
176    }
177    return variants[((i % variants.length) + variants.length) % variants.length];
178  }
179
180  function renderBlock(container, payload) {
181    if (!payload || !payload.block) return;
182
183    // If the server explicitly said this block has no ads, hide the placeholder
184    if (payload.success === false || (Array.isArray(payload.ads) && payload.ads.length === 0)) {
185      container.innerHTML = "";
186      container.style.display = "none";
187      container.classList.add("srr-adblock-empty");
188      return;
189    }
190
191    const block = payload.block || {};
192    block._device = detectDevice();
193
194    let ads = Array.isArray(payload.ads) ? payload.ads.slice() : [];
195
196    // Swap each Ad Rotation entry for one of its variants before anything else
197    // looks at the list, so ordering, dedupe and markup all use the real ad.
198    ads = ads.map(function (ad) {
199      return (ad && Array.isArray(ad.variants) && ad.variants.length) ? pickRotationVariant(ad) : ad;
200    }).filter(Boolean);
201
202    if (!ads.length) {
203      container.innerHTML = "";
204      container.style.display = "none";
205      container.classList.add("srr-adblock-empty");
206      return;
207    }
208
209    // Normalize order (support old values)
210    const rawOrder = String(block.order || "").toLowerCase();
211    const order =
212      (rawOrder === "rand" || rawOrder === "random") ? "random" :
213      (rawOrder === "menu_order" || rawOrder === "menuorder") ? "menu_order" :
214      (rawOrder === "date" || rawOrder.includes("date") || rawOrder.includes("sequential")) ? "menu_order" :
215      "random";
216
217    const intervalSecRaw = parseInt(block.interval, 10);
218    const intSec = (Number.isFinite(intervalSecRaw) && intervalSecRaw > 0) ? intervalSecRaw : 7;
219    const timerMs = intSec * 1000;
220    const anim = String(block.anim || "opacity");
221
222    // Outer wrapper
223    const outer = document.createElement("div");
224    outer.className = "singlead";
225    // FIX: Ensure outer wrapper is responsive
226    outer.style.width = "100%";
227    outer.style.maxWidth = "100%";
228
229    const blockEl = document.createElement("div");
230    blockEl.id = "adblock_" + payload.id;
231    blockEl.className = "adblock";
232    blockEl.setAttribute("data-block-id", String(payload.id));
233    // FIX: Ensure block element is responsive
234    blockEl.style.width = "100%";
235    blockEl.style.maxWidth = "100%";
236
237    const blockType = String(block.type || "slider").toLowerCase();
238
239    if (blockType === "single") {
240      // "Single Rotator" means: show ONE ad per instance of the block on the page,
241      // with NO animation/rotation.
242      // When the same block appears multiple times on the page, pick the next ad
243      // so each occurrence displays a different one.
244      const key = String(payload.id);
245
246      const rawAds = Array.isArray(payload.ads) ? payload.ads.slice() : [];
247      if (!rawAds.length) {
248        container.innerHTML = "";
249        return;
250      }
251
252      // Sort ads deterministically by menu_order (if present) then by ID.
253      // Used both as the ordering for menu_order blocks and as a stable signature
254      // for cache-invalidation comparison when the ad set changes.
255      const sortedAds = rawAds.slice().sort(function(a, b) {
256        const orderA = parseInt(a.menu_order, 10) || 0;
257        const orderB = parseInt(b.menu_order, 10) || 0;
258        if (orderA !== orderB) return orderA - orderB;
259        const idA = parseInt(a.id, 10) || 0;
260        const idB = parseInt(b.id, 10) || 0;
261        return idA - idB;
262      });
263
264      // Respect the block's configured order. For random blocks, shuffle once per
265      // page load so repeater copies cycle through a fresh random sequence.
266      const iterationOrder = (order === "random") ? shuffle(rawAds.slice()) : sortedAds;
267
268      // Signature from sorted ad IDs (order-independent) so random shuffles don't
269      // look like a changed ad set.
270      const sig = sortedAds.map((a) => String(a && a.id ? a.id : '')).join(',');
271      window.__srrSingleRotatorState = window.__srrSingleRotatorState || {};
272      const state = window.__srrSingleRotatorState[key] || { idx: 0, sig: '', order: [] };
273
274      if (!state.order || !state.order.length || state.sig !== sig) {
275        state.order = iterationOrder;
276        state.idx = 0;
277        state.sig = sig;
278      }
279
280      const picked = state.order[state.idx % state.order.length];
281      state.idx = state.idx + 1;
282      window.__srrSingleRotatorState[key] = state;
283
284      // FIX: Use mobile dimensions when on phone and mobile ads exist
285      const mobileBw = parseInt(block.mobile_width, 10);
286      const mobileBh = parseInt(block.mobile_height, 10);
287      const useMobileDims = (block._device === "phone" && Number.isFinite(mobileBw) && mobileBw > 0 && Number.isFinite(mobileBh) && mobileBh > 0);
288
289      // Create modified block with effective dimensions for buildAdSlide
290      const effectiveBlock = useMobileDims
291        ? Object.assign({}, block, { width: mobileBw, height: mobileBh })
292        : block;
293
294      const slide = buildAdSlide(picked, effectiveBlock);
295      slide.style.position = "relative";
296      slide.style.display = "block";
297
298      blockEl.appendChild(slide);
299      outer.appendChild(blockEl);
300
301      container.innerHTML = "";
302      container.appendChild(outer);
303      // FIX: Clear placeholder's inline styles that could conflict with ad sizing
304      container.style.height = "auto";
305      container.style.aspectRatio = "";
306      container.style.maxWidth = "";
307
308      if (window.dispatchEvent) {
309        try {
310          window.dispatchEvent(new CustomEvent("srr_ads:block_rendered", { detail: { id: payload.id } }));
311        } catch (e) {}
312      }
313      return;
314    }
315
316    if (order === "random") shuffle(ads);
317
318    if (blockType === "vertical") {
319      const widthPx = parseInt(block.width, 10);
320      const heightPx = parseInt(block.height, 10);
321      const verthPx = parseInt(block.verth, 10);
322      const verts = parseInt(block.verts, 10);
323
324      const vertical = document.createElement("div");
325      vertical.id = "vertical-container_" + payload.id;
326      vertical.setAttribute("data-block-id", String(payload.id));
327      if (Number.isFinite(widthPx) && widthPx > 0) vertical.setAttribute("data-width", String(widthPx));
328      if (Number.isFinite(heightPx) && heightPx > 0) vertical.setAttribute("data-height", String(heightPx));
329      if (Number.isFinite(verthPx) && verthPx > 0) vertical.setAttribute("data-verth", String(verthPx));
330      if (Number.isFinite(verts) && verts > 0) vertical.setAttribute("data-verts", String(verts));
331      // FIX: Ensure vertical container is responsive
332      vertical.style.width = "100%";
333      vertical.style.maxWidth = "100%";
334
335      const rotator = document.createElement("div");
336      rotator.className = "adrotate_" + payload.id;
337      // FIX: Ensure rotator is responsive
338      rotator.style.width = "100%";
339      rotator.style.maxWidth = "100%";
340
341      // Vertical blocks are continuous scroll: stack ads naturally (no absolute positioning)
342      ads.forEach(function (ad) {
343        const node = buildAdSlide(ad, block);
344        node.style.position = "relative";
345        node.style.display = "block";
346        if (Number.isFinite(widthPx) && widthPx > 0) {
347          node.style.maxWidth = widthPx + "px";
348          node.style.width = "100%";
349        }
350        if (Number.isFinite(heightPx) && heightPx > 0) {
351          if (Number.isFinite(widthPx) && widthPx > 0) {
352            node.style.aspectRatio = widthPx + " / " + heightPx;
353            node.style.height = "auto";
354          } else {
355            node.style.height = heightPx + "px";
356          }
357        }
358        rotator.appendChild(node);
359      });
360
361      vertical.appendChild(rotator);
362      blockEl.appendChild(vertical);
363      outer.appendChild(blockEl);
364
365      container.innerHTML = "";
366      container.appendChild(outer);
367
368      // The placeholder shortcode reserves height using ad-block-verth.
369      // The runtime vertical slider clamps verth to the total slide height.
370      // Clear the placeholder's inline styles so vertical container controls sizing.
371      container.style.height = "auto";
372      container.style.aspectRatio = "";
373      container.style.maxWidth = "";
374
375      if (window.SRRAdsRuntime && typeof window.SRRAdsRuntime.initAll === "function") {
376        window.__srrAdsRuntimeKick && clearTimeout(window.__srrAdsRuntimeKick);
377        window.__srrAdsRuntimeKick = setTimeout(function () {
378          try { window.SRRAdsRuntime.initAll(document); } catch(e) {}
379        }, 50);
380      }
381
382      if (window.dispatchEvent) {
383        try {
384          window.dispatchEvent(new CustomEvent("srr_ads:block_rendered", { detail: { id: payload.id } }));
385        } catch (e) {}
386      }
387      return;
388    }
389
390    // Carousel container (this is what SRRAdsRuntime should initialize)
391    const carousel = document.createElement("div");
392    carousel.className = "adblock_carousel";
393    carousel.id = "seq_" + payload.id;
394
395    carousel.setAttribute("data-timer", String(timerMs));
396    carousel.setAttribute("data-interval", String(timerMs));
397    carousel.setAttribute("data-anim", anim);
398    carousel.setAttribute("data-animation", anim);
399
400    // IMPORTANT for animation stacking
401    carousel.style.position = "relative";
402    carousel.style.overflow = "hidden";
403    // FIX: Ensure carousel fills its container width for responsive scaling
404    carousel.style.width = "100%";
405
406    // Determine effective dimensions: use mobile dimensions if on phone and mobile ads exist
407    const rawBw = parseInt(block.width, 10);
408    const rawBh = parseInt(block.height, 10);
409    const mobileBw = parseInt(block.mobile_width, 10);
410    const mobileBh = parseInt(block.mobile_height, 10);
411    const useMobileDims = (block._device === "phone" && Number.isFinite(mobileBw) && mobileBw > 0 && Number.isFinite(mobileBh) && mobileBh > 0);
412    const bw = useMobileDims ? mobileBw : rawBw;
413    const bh = useMobileDims ? mobileBh : rawBh;
414
415    // Set max dimensions from block config so carousel doesn't exceed intended size
416    if (Number.isFinite(bw) && bw > 0) {
417      carousel.style.maxWidth = bw + "px";
418      carousel.style.margin = "0 auto"; // Center the carousel
419    }
420    // Use aspect-ratio on carousel to maintain proportions and give it intrinsic height
421    if (Number.isFinite(bw) && bw > 0 && Number.isFinite(bh) && bh > 0) {
422      carousel.style.aspectRatio = bw + " / " + bh;
423      // FIX: Mark carousel as using responsive aspect-ratio so runtime doesn't override with fixed height
424      carousel.setAttribute("data-responsive-ar", "1");
425    }
426
427    // Slides: source-of-truth `.adr` elements directly under `.adblock_carousel`
428    ads.forEach(function (ad) {
429      const slide = buildAdSlide(ad, block);
430      slide.style.position = "absolute";
431      slide.style.inset = "0";
432      slide.style.display = "none";
433      slide.style.opacity = "1";
434      slide.style.transform = "translate3d(0,0,0) scale(1)";
435      slide.style.willChange = "opacity, transform";
436      // FIX: Override wrapper maxWidth for carousel slides - let carousel control dimensions
437      slide.style.maxWidth = "100%";
438      slide.style.width = "100%";
439      slide.style.height = "100%";
440      carousel.appendChild(slide);
441    });
442
443    // Show only the first slide initially
444    const slides = carousel.querySelectorAll(":scope > .adr");
445    if (slides.length) {
446      slides.forEach((s, i) => { s.style.display = (i === 0 ? "block" : "none"); });
447    }
448
449    blockEl.appendChild(carousel);
450    outer.appendChild(blockEl);
451
452    container.innerHTML = "";
453    container.appendChild(outer);
454
455    // FIX: Clear placeholder's inline styles that could conflict with carousel sizing
456    // The placeholder had aspect-ratio based on block dimensions (e.g., 728/90)
457    // but mobile ads may have different dimensions (e.g., 300/250)
458    container.style.height = "auto";
459    container.style.aspectRatio = "";
460    container.style.maxWidth = "";
461
462    // Kick the new runtime (NOT legacy)
463    if (window.SRRAdsRuntime && typeof window.SRRAdsRuntime.initAll === "function") {
464      // debounce so 5 blocks don't create 5 init storms
465      window.__srrAdsRuntimeKick && clearTimeout(window.__srrAdsRuntimeKick);
466      window.__srrAdsRuntimeKick = setTimeout(function () {
467        try { window.SRRAdsRuntime.initAll(document); } catch(e) {}
468      }, 50);
469    }
470
471    // Optional hook
472    if (window.dispatchEvent) {
473      try {
474        window.dispatchEvent(new CustomEvent("srr_ads:block_rendered", { detail: { id: payload.id } }));
475      } catch (e) {}
476    }
477  }
478
479  function loadOne(el) {
480    if (el.__srrLoaded) return;
481
482    // IMPORTANT:
483    // Read ID from all known placeholder attributes
484    const id =
485      el.getAttribute("data-srr-adblock-id") ||
486      el.getAttribute("data-ad-block-id") ||   // <-- your placeholder_func
487      el.getAttribute("data-block-id") ||
488      el.getAttribute("data-id");
489
490    if (!id) return;
491
492    const device = detectDevice();
493
494    const pagePath = normalizePagePath((window.location && window.location.pathname) ? window.location.pathname : "/");
495
496    const base =
497      (window.srrAdsLazyload && window.srrAdsLazyload.restBase)
498        ? window.srrAdsLazyload.restBase
499        : null;
500
501    // Cache revision stamped on the placeholder by PHP ("{global}.{block}"), falling
502    // back to the site-wide revision. The coordinator keys its browser-side payload
503    // cache on this, so an ad or ad-block save retires cached copies immediately
504    // rather than after the payload timeout.
505    const rev =
506      el.getAttribute("data-srr-rev") ||
507      ((window.srrAdsLazyload && window.srrAdsLazyload.rev != null) ? String(window.srrAdsLazyload.rev) : "0");
508
509    if (!base) {
510      // No REST base configured = no fetch possible
511      // Keep placeholder visible.
512      return;
513    }
514
515    // Use shared coordinator to prevent duplicate REST calls (lazyload + SPA re-init).
516    // This reduces PHP concurrency by enforcing one network call per block/device.
517    el.__srrLoaded = true;
518    const coord = window.SRRAdsPayloadCoordinator;
519    const getter = coord && typeof coord.getPayload === "function"
520      ? coord.getPayload
521      : null;
522
523    const p = getter
524        ? getter(id, device, base, rev)
525        : fetch(
526          base.replace(/\/$/, "") + "/" + encodeURIComponent(id) + "?device=" + encodeURIComponent(device) + "&page_path=" + encodeURIComponent(pagePath),
527          { credentials: "same-origin" }
528        )
529          .then(function (r) { return r.json(); });
530
531    p.then(function (payload) {
532        try {
533          renderBlock(el, payload);
534        } catch (e) {
535          // allow retry if render threw
536          el.__srrLoaded = false;
537          return;
538        }
539
540        // If renderBlock no-ops (invalid/empty payload), the placeholder text can remain
541        // while __srrLoaded stays true, which prevents future retries and can strand SPA pages.
542        // Only mark as truly loaded if real ad markup exists.
543        try {
544          if (!(el.querySelector && el.querySelector(".adblock, .adblock_carousel, .adr"))) {
545            el.__srrLoaded = false;
546          }
547        } catch (e) {
548          // On any unexpected DOM exception, allow retry.
549          el.__srrLoaded = false;
550        }
551      })
552      .catch(function () {
553        // allow retry if it failed
554        el.__srrLoaded = false;
555      });
556  }
557
558  function observeLazyBlocks() {
559    // IMPORTANT:
560    // Support ALL placeholder attribute variants:
561    // - data-srr-adblock-id (new)
562    // - data-ad-block-id (your placeholder_func)
563    // - .srr-adblock-placeholder (your placeholder class)
564    // - .srr-adblock-lazy (older)
565    // - data-block-id / data-id (misc)
566    const blocks = document.querySelectorAll(
567      "[data-srr-adblock-id], [data-ad-block-id], .srr-adblock-placeholder, .srr-adblock-lazy, [data-block-id].srr-adblock-lazy"
568    );
569
570    if (!blocks.length) return;
571
572    // IntersectionObserver: load when near viewport
573    if ("IntersectionObserver" in window) {
574      const io = new IntersectionObserver(function (entries) {
575        entries.forEach(function (entry) {
576          if (entry.isIntersecting) {
577            loadOne(entry.target);
578            io.unobserve(entry.target);
579          }
580        });
581      }, { root: null, rootMargin: "1200px 0px", threshold: 0.01 });
582
583      blocks.forEach(function (el) {
584        // If already contains real ads, skip
585        if (el.querySelector && el.querySelector(".adblock, .adblock_carousel, .adr")) return;
586        io.observe(el);
587      });
588
589      return;
590    }
591
592    // Fallback: no IO -> immediate load
593    blocks.forEach(function (el) { loadOne(el); });
594  }
595
596  // Avoid runaway loops: re-scan a few times after page changes, then stop
597  let scanBurstTimer = null;
598  function scanBurst() {
599    let runs = 0;
600    clearInterval(scanBurstTimer);
601    scanBurstTimer = setInterval(function () {
602      runs++;
603      observeLazyBlocks();
604      if (runs >= 6) { // ~6 * 1500ms = 9 seconds then stop
605        clearInterval(scanBurstTimer);
606        scanBurstTimer = null;
607      }
608    }, 1500);
609  }
610
611  function init() {
612    observeLazyBlocks();
613  }
614
615  function onPageChangeLikeEvent() {
616    // StreamPlayer/SPA may reuse DOM nodes. If a placeholder node was previously loaded,
617    // it can retain the in-memory `__srrLoaded` flag and never re-hydrate, leaving
618    // the user stuck on the placeholder text (e.g. "Loading advertisement").
619    // Reset the flag for placeholders that do not currently contain rendered ad markup.
620    try {
621      const blocks = document.querySelectorAll(
622        "[data-srr-adblock-id], [data-ad-block-id], .srr-adblock-placeholder, .srr-adblock-lazy, [data-block-id].srr-adblock-lazy"
623      );
624      blocks.forEach(function(el){
625        if (!el) return;
626        el.__srrLoaded = false;
627      });
628    } catch (e) {}
629    scanBurst();
630  }
631
632  if (document.readyState === "loading") {
633    document.addEventListener("DOMContentLoaded", function () {
634      init();
635      scanBurst();
636    });
637  } else {
638    init();
639    scanBurst();
640  }
641
642  // StreamPlayer / SPA hooks.
643  // "pageContentLoaded" (on window) is what srr-streamplayer really dispatches;
644  // the "streamplayer:*" names are legacy and never fired by anything.
645  window.addEventListener("pageContentLoaded", onPageChangeLikeEvent);
646  document.addEventListener("pageContentLoaded", onPageChangeLikeEvent);
647  document.addEventListener("streamplayer:contentreplaced", onPageChangeLikeEvent);
648  document.addEventListener("streamplayer:pagechange", onPageChangeLikeEvent);
649  document.addEventListener("srr_ads:page_change", onPageChangeLikeEvent);
650  window.addEventListener("popstate", onPageChangeLikeEvent);
651
652  // If you use Barba, hook it too (won’t throw if Barba is missing)
653  try {
654    if (typeof Barba !== "undefined" && Barba.Dispatcher && Barba.Dispatcher.on) {
655      Barba.Dispatcher.on("newPageReady", onPageChangeLikeEvent);
656    }
657  } catch (e) {}
658
659  // MutationObserver: catch placeholders inserted after the initial scan burst.
660  // StreamPlayer, deferred widgets, or other plugins may inject ad placeholders
661  // after the 9-second scanBurst window closes. Without this, those placeholders
662  // would stay stuck on "Loading advertisement" forever.
663  var moDebounce = null;
664  var PLACEHOLDER_SELECTOR = "[data-srr-adblock-id], [data-ad-block-id], .srr-adblock-placeholder, .srr-adblock-lazy";
665  try {
666    if ("MutationObserver" in window) {
667      var mo = new MutationObserver(function () {
668        if (moDebounce) clearTimeout(moDebounce);
669        moDebounce = setTimeout(function () {
670          var blocks = document.querySelectorAll(PLACEHOLDER_SELECTOR);
671          if (!blocks || !blocks.length) return;
672          blocks.forEach(function (el) {
673            if (el.__srrLoaded) return;
674            if (el.querySelector && el.querySelector(".adblock, .adblock_carousel, .adr")) return;
675            loadOne(el);
676          });
677        }, 200);
678      });
679      // Start observing once the body is available
680      function startMutationObserver() {
681        if (document.body) {
682          mo.observe(document.body, { childList: true, subtree: true });
683        } else {
684          setTimeout(startMutationObserver, 50);
685        }
686      }
687      startMutationObserver();
688    }
689  } catch (e) {}
690})();

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.