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, '&').replace(/"/g, '"'); } 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.