PageSourceSearch

https://www.italiaatavola.net/iat-top-live.js?v=4

js italiaatavola.net collected 2026-09-24 09:12:28 UTC 9,396 bytes, 192 lines download raw bytes

1/**
2 * iat-top-live.js — RENDERER UNICO dei banner top (leaderboard 936x120).
3 *
4 * Iniettato da nginx (sub_filter, prima di </head>) su TUTTE le pagine statiche/articoli
5 * servite da disco, dove l'HTML è no-cache: così ogni pagina prende SEMPRE l'ultima versione
6 * di questo script e i banner si aggiornano OVUNQUE entro pochi secondi, senza rigenerare.
7 *
8 * Unico canale dati: fetch /api/public/top-banners (cache breve, solo banner ATTIVI = finestra
9 * date valida + creatività). Ignora il pool "cotto" (data-top-pools) e DISABILITA il rotator
10 * baked vecchio (iat-top-rotator.js) per evitare doppio render / banner scaduti.
11 *
12 * Regole invariate:
13 *  - mobile (<=700px): UN banner a caso tra TUTTI (sx ∪ dx, dedup per id);
14 *  - desktop: 1 sx + 1 dx, mai lo stesso id (fallback: un solo banner centrato).
15 * Contatori impression/click via /api/public/banners/:id/track-widget.
16 */
17(function () {
18  'use strict';
19  if (window.__iatTopLiveInstalled) return;
20  window.__iatTopLiveInstalled = true;
21  // Disabilita il rotator baked vecchio (che leggerebbe il pool cotto/scaduto): il suo IIFE
22  // controlla questo flag ed esce. Essendo iniettato nel <head> con defer, giriamo prima di lui.
23  window.__iatTopRotatorInstalled = true;
24
25  function track(bid, cid, type) {
26    if (!bid) return;
27    if (type === 'impression' && document.prerendering) {
28      document.addEventListener('prerenderingchange', function () { track(bid, cid, type); }, { once: true });
29      return;
30    }
31    var body = JSON.stringify({ type: type, creativeId: cid || '' });
32    var url = '/api/public/banners/' + encodeURIComponent(bid) + '/track-widget';
33    if (type === 'click' && navigator.sendBeacon) {
34      navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));
35      return;
36    }
37    fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, keepalive: true }).catch(function () {});
38  }
39
40  function esc(s) { return String(s || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;'); }
41  function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
42
43  function adHtml(it, eager) {
44    var img = '<img class="imgBanner" src="' + esc(it.img) + '" alt="" aria-hidden="true" width="936" height="120" loading="'
45      + (eager ? 'eager" fetchpriority="high"' : 'lazy"') + ' decoding="async">';
46    return it.url
47      ? '<a class="iat-top-ad" href="' + esc(it.url) + '" aria-label="' + esc(it.alt || 'Banner') + '" rel="sponsored noopener">' + img + '</a>'
48      : '<div class="iat-top-ad" role="img" aria-label="' + esc(it.alt || 'Banner') + '">' + img + '</div>';
49  }
50
51  // Collasso anti-AdBlock: se l'immagine del banner viene BLOCCATA (AdBlock filtra gli
52  // URL sotto /images/banners/) o fallisce (404), il wrapper .iat-top-ad resta iniettato
53  // e mostrerebbe un box bianco col bordo (aspect-ratio 936/120). Qui nascondo lo slot con
54  // immagine fallita e, se NESSUNO slot ha un'immagine valida, collasso .container-banner-top.
55  // Su caricamento normale (immagini OK) non tocca nulla → zero CLS.
56  function imgLoadedOk(im) { return !!(im && im.complete && im.naturalWidth > 0); }
57  function onImgOutcome(slot, ok) {
58    var box = document.getElementById('iatTopAds');
59    if (!box) return;
60    if (!ok && slot) slot.style.display = 'none';
61    var imgs = box.querySelectorAll('.iat-top-ad-slot img.imgBanner');
62    var anyOk = false, anyPending = false;
63    for (var i = 0; i < imgs.length; i++) {
64      if (!imgs[i].complete) anyPending = true;
65      else if (imgs[i].naturalWidth > 0) anyOk = true;
66    }
67    if (anyOk || anyPending) return;               // c'è un banner valido o si aspetta
68    if (box.parentNode) box.parentNode.style.display = 'none'; // tutti falliti → collassa
69  }
70
71  function render(slot, it, eager) {
72    if (!slot || !it) return;
73    slot.innerHTML = adHtml(it, eager);
74    track(it.id, it.cid, 'impression');
75    var a = slot.querySelector('a');
76    if (a) a.addEventListener('click', function () { track(it.id, it.cid, 'click'); });
77    var im = slot.querySelector('img.imgBanner');
78    if (im) {
79      if (im.complete) { onImgOutcome(slot, imgLoadedOk(im)); }   // già risolta (cache)
80      else {
81        im.addEventListener('load', function () { onImgOutcome(slot, true); });
82        im.addEventListener('error', function () { onImgOutcome(slot, false); });
83      }
84    }
85  }
86
87  var POOLS = null;      // pool LIVE dall'endpoint
88  var lastMobile = null;
89
90  function draw() {
91    var box = document.getElementById('iatTopAds');
92    if (!box) return;
93    var pools = POOLS || {};
94    var sx = (pools.sx || []).filter(function (it) { return it && it.img; });
95    var dx = (pools.dx || []).filter(function (it) { return it && it.img; });
96    var slots = box.querySelectorAll('.iat-top-ad-slot');
97    var slotSx = slots[0], slotDx = slots[1];
98    var hide = function (el) { if (el) el.style.display = 'none'; };
99    var hideAll = function () { if (box.parentNode) box.parentNode.style.display = 'none'; };
100
101    box.classList.remove('iat-top-ads--single');
102    if (box.parentNode) box.parentNode.style.display = '';
103    if (slotSx) slotSx.style.display = '';
104    if (slotDx) slotDx.style.display = '';
105
106    var isMobile = window.matchMedia('(max-width:700px)').matches;
107    lastMobile = isMobile;
108    if (isMobile) {
109      var seen = {}, all = [];
110      sx.concat(dx).forEach(function (it) { if (!seen[it.id]) { seen[it.id] = 1; all.push(it); } });
111      if (!all.length) { hideAll(); return; }
112      hide(slotDx);
113      render(slotSx, pick(all), true);
114      return;
115    }
116
117    var left = sx.length ? pick(sx) : null;
118    var right = dx.length ? pick(dx) : null;
119    if (left && right && String(left.id) === String(right.id)) {
120      var altDx = dx.filter(function (it) { return String(it.id) !== String(left.id); });
121      if (altDx.length) { right = pick(altDx); }
122      else {
123        var altSx = sx.filter(function (it) { return String(it.id) !== String(right.id); });
124        if (altSx.length) left = pick(altSx); else right = null;
125      }
126    }
127    if (!left && !right) { hideAll(); return; }
128    if (left && !right) { box.classList.add('iat-top-ads--single'); hide(slotDx); render(slotSx, left, true); return; }
129    if (!left && right) { box.classList.add('iat-top-ads--single'); hide(slotSx); render(slotDx, right, true); return; }
130    render(slotSx, left, true);
131    render(slotDx, right, false);
132  }
133
134  function start() {
135    var done = false;
136    function go() { if (done) return; done = true; draw(); }
137    // Usa il fetch PRE-AVVIATO nel <head> (window.__iatTopPoolsP): parte durante il parsing,
138    // così quando il DOM è pronto il pool è già arrivato → nessun ritardo percepibile. Se non
139    // c'è (fallback), lo avvia ora.
140    var pending = window.__iatTopPoolsP;
141    if (!pending || typeof pending.then !== 'function') {
142      pending = fetch('/api/public/top-banners', { credentials: 'omit', cache: 'no-cache' })
143        .then(function (r) { return (r && r.ok) ? r.json() : null; })
144        .catch(function () { return null; });
145    }
146    var to = setTimeout(go, 2500); // safety: se il fetch si impianta, disegna comunque
147    pending
148      .then(function (p) { if (p && (Array.isArray(p.sx) || Array.isArray(p.dx))) POOLS = p; })
149      .catch(function () {})
150      .then(function () { clearTimeout(to); go(); });
151  }
152
153  // ── Banner sidebar SEZIONI (.sec-banner-card) ──────────────────────────────
154  // Nelle pagine sezione i banner 436x240 sono "cotti" nell'HTML e non ruotano. Qui,
155  // come la home, li rimpiazziamo a OGNI caricamento con banner presi dal pool LIVE
156  // (solo 436x240, gate date lato server) → cambiano ovunque senza rigenerare.
157  function rotateSectionCards() {
158    var cards = document.querySelectorAll('.sec-banner-card');
159    if (!cards.length) return;
160    fetch('/api/sidebar-widgets', { credentials: 'same-origin', cache: 'no-store' })
161      .then(function (r) { return (r && r.ok) ? r.json() : null; })
162      .then(function (data) {
163        var pool = (data && data.banners ? data.banners : []).filter(function (b) { return b && b.imageUrl && !b.adNetwork; });
164        if (!pool.length) return;
165        for (var i = pool.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = pool[i]; pool[i] = pool[j]; pool[j] = t; }
166        Array.prototype.forEach.call(cards, function (card, idx) {
167          var b = pool[idx % pool.length];
168          if (!b) return;
169          var im = card.querySelector('img');
170          if (im) { im.src = b.imageUrl; im.setAttribute('alt', b.alt || b.nome || ''); }
171          var a = card.querySelector('a');
172          if (a && b.url) { a.setAttribute('href', b.url); }
173          card.setAttribute('data-bid', String(b.id || ''));
174          card.setAttribute('data-cid', String(b.creativeId || ''));
175        });
176      })
177      .catch(function () {});
178  }
179
180  function boot() { start(); rotateSectionCards(); }
181  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
182  else boot();
183
184  // Re-render al cambio fascia mobile↔desktop (usa il pool già scaricato).
185  var _rsT;
186  window.addEventListener('resize', function () {
187    clearTimeout(_rsT);
188    _rsT = setTimeout(function () {
189      if (window.matchMedia('(max-width:700px)').matches !== lastMobile) draw();
190    }, 150);
191  });
192}());

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.