PageSourceSearch

https://www.daop.it/assets/js/daop-track.js

js daop.it collected 2026-10-02 01:03:37 UTC 23,355 bytes, 433 lines download raw bytes

1/* DAOP - tracciamento dei clic che GA4 da solo non vede.
2 *
3 * Perche' esiste come file esterno e non come blocco inline: il blocco inline
4 * viveva nelle pagine scritte a mano, e sulle ~280 pagine generate ricopiarlo
5 * avrebbe voluto dire 2 KB in piu' su ognuna e dodici copie da tenere
6 * allineate. Cosi' e' una richiesta sola, in cache dalla seconda pagina in poi.
7 * Dal 12/08/2026 le copie inline non ci sono piu': questo file e' l'unico
8 * posto in cui si scrivono i clic, come cookie-consent.js e' l'unico posto in
9 * cui si inizializza GA4.
10 *
11 * Cosa NON serve tracciare qui: la navigazione interna qualunque. Ogni pagina
12 * del sito manda gia' un page_view (lo fa cookie-consent.js dopo il consenso),
13 * quindi "da questa scheda dove vanno dopo" si legge gia' in GA4 con
14 * un'esplorazione di percorso. Qui stanno i clic che NON producono un
15 * page_view: quelli che portano fuori dal sito o che aprono un'app.
16 *
17 * L'ECCEZIONE, dal 14/09/2026: i blocchi marcati `data-cta`. Il page_view dice
18 * da quale PAGINA si arriva, non da quale BLOCCO, e non dice quante volte quel
19 * blocco e' stato visto - quindi "il link ai corsi in coda alla scheda rende
20 * o no?" non aveva risposta. Per quei blocchi, e solo per quelli, partono
21 * `internal_cta_view` (una volta per pagina, quando il blocco e' a schermo) e
22 * `internal_cta_click`. I nomi sono quelli proposti da Giovanni nell'analisi
23 * del 14/09, apposta: chi legge i report e' lui.
24 *
25 * gtag esiste solo dopo il consenso ai cookie: ogni chiamata e' protetta, e
26 * senza consenso lo script gira a vuoto senza errori.
27 */
28(function () {
29  'use strict';
30
31  if (window.__daopTrackAttivo) return; // due include = un solo tracciamento
32  window.__daopTrackAttivo = true;
33
34  /* ── Contesto della pagina ─────────────────────────────────────────────
35     I generatori stampano tre meta sulle pagine che parlano di un evento o
36     di un comune. Dove non ci sono (home, rubriche, libri...) i campi
37     restano vuoti e semplicemente non vengono mandati: GA4 non riceve
38     parametri con valore vuoto, cosi' i report non si riempiono di "(not
39     set)" sulle pagine per cui la domanda non ha senso. */
40  function meta(nome) {
41    var m = document.querySelector('meta[name="' + nome + '"]');
42    var v = m && m.getAttribute('content');
43    return v ? v.trim() : '';
44  }
45
46  var CTX = {
47    event_title: meta('daop:evento'),
48    event_city: meta('daop:citta'),
49    event_province: meta('daop:provincia')
50  };
51
52  /* ── Contesto della RIGA ────────────────────────────────────────────────
53     I meta qui sopra descrivono la PAGINA, e su quasi tutto il sito basta:
54     una scheda evento parla di un evento solo. Su /corsi.html no — in una
55     pagina sola convivono N societa' e N corsi, quindi "chi ha ricevuto
56     questo clic" e' una domanda per elemento, non per documento.
57
58     La risposta sta gia' nel DOM: il generatore stampa `data-org` sia sulla
59     card del corso sia sulla scheda della realta' (e' lo stesso slug
60     dell'ancora #r-pgs-roccavione che si manda su WhatsApp), piu'
61     `data-org-nome` per il nome leggibile e `data-codice` per il codice del
62     foglio. Qui si risale l'albero e basta: nessun elenco da tenere
63     allineato, e un link che NON sta dentro una di quelle scatole — l'invito
64     alle societa' in fondo, per dire — non prende l'attribuzione di nessuno,
65     che e' il comportamento giusto.
66
67     A cosa serve: a rispondere a una realta' "quanti hanno aperto la tua
68     scheda, quanti sono andati sul tuo sito, quanti ti hanno chiamato"
69     senza ricostruire ogni volta l'organizzatore da `destination_url`.
70
71     `organizer_id` e' lo slug e NON deve mai cambiare: e' anche l'ancora che
72     gira nei messaggi, e in GA4 un id nuovo vuol dire una serie storica che
73     riparte da zero. Se nel foglio c'e' un CODICE, quello vince come
74     `course_id` proprio per questo — un corso che si rinomina non perde il
75     suo storico. */
76  function contesto_riga(el) {
77    var out = {};
78    if (!el || !el.closest) return out;
79    var box = el.closest('[data-org]');
80    if (box) {
81      var oid = box.getAttribute('data-org') || '';
82      var onome = box.getAttribute('data-org-nome') || '';
83      if (oid) out.organizer_id = oid;
84      if (onome) out.organizer_name = onome;
85    }
86    var card = el.closest('.event-card[data-org]');
87    if (card) {
88      var cid = card.getAttribute('data-codice') || card.id || '';
89      if (cid) out.course_id = cid;
90      var n = card.querySelector('.ev-name');
91      var testo = n && (n.textContent || '').trim();
92      if (testo) out.course_name = testo;
93    }
94    /* Le schede di /luoghi.html. Stessi parametri dei corsi, e non una
95       dimensione nuova, perche' la domanda e' la stessa - "a quale realta'
96       va questo clic" - e le quattro dei corsi sono gia' registrate in GA4.
97       L'id non si stampa in un attributo: e' l'id della riga, che esiste gia'
98       (#lg-...), quindi 890 righe non pagano un byte in piu'. Le copie nel
99       riquadro Sponsorizzati hanno "-ev" in coda: si toglie, se no lo stesso
100       posto sarebbe due righe nei report. `posizione` dice da dove e' partito
101       il clic, ed e' la prova di quanto vale il riquadro a chi lo compra. */
102    var lg = !box && el.closest('.lg-row');
103    if (lg) {
104      var lid = (lg.id || '').replace(/-ev$/, '');
105      if (lid) out.organizer_id = lid;
106      var ln = lg.querySelector('.lg-nome');
107      var lt = ln && (ln.textContent || '').trim();
108      if (lt) out.organizer_name = lt;
109      out.posizione = lg.closest('.lg-vetrina') ? 'sponsorizzati' : 'elenco';
110    }
111    // La riga "Sponsorizzato" in coda a una scheda evento: il posto e' quello
112    // dell'ancora, cosi' il report lo mette sulla stessa riga delle aperture.
113    var sp = el.closest('.ev-spons');
114    if (sp) {
115      var sa = sp.querySelector('a[href*="#lg-"]');
116      var sid = sa && (sa.getAttribute('href') || '').split('#')[1];
117      if (sid) out.organizer_id = sid;
118      if (sa && sa.textContent) out.organizer_name = sa.textContent.trim();
119      out.posizione = 'scheda_evento';
120    }
121    return out;
122  }
123
124  // Stesso motivo di cookie-consent.js: "/index.html" e "/" sono la stessa
125  // pagina e devono contare come una sola riga anche negli eventi di clic.
126  function percorso() {
127    return location.pathname.replace(/\/index\.html$/i, '/') || '/';
128  }
129
130  /* In quale famiglia del sito porta un link interno, per `destination_area`.
131     `destination_url` c'e' gia' e dice la pagina esatta; questo raggruppa, cosi'
132     "quanti vanno ai corsi" non chiede un filtro scritto a mano in GA4. Torna
133     '' per un link che esce dal sito: quelli hanno gia' il loro evento.
134     `evento` e' la scheda singola, `eventi` qualunque elenco di eventi. */
135  function area_destinazione(href) {
136    var u;
137    try { u = new URL(href, location.href); } catch (e) { return ''; }
138    if (u.host !== location.host && u.host.indexOf('daop.it') === -1) return '';
139    // Aperta da file:// su Windows, "/corsi.html" si risolve in /C:/corsi.html
140    // (la trappola gia' scritta in tests/_aiuto.js): online non capita mai, ma
141    // senza questa riga la prova vedrebbe "altro" dove il sito dice "corsi".
142    var p = u.pathname.replace(/^\/[A-Za-z]:(?=\/)/, '');
143    if (/^\/corsi(\.html$|\/)/.test(p)) return 'corsi';
144    if (p === '/luoghi.html' || p === '/piscine.html') return 'luoghi';
145    if (p === '/ginetto.html') return 'ginetto';
146    if (p.indexOf('/centri-') === 0) return 'centri';
147    if (p.indexOf('/eventi/comune/') === 0 ||
148        /^\/eventi\/(oggi|weekend)[^/]*\.html$/.test(p)) return 'eventi';
149    if (p.indexOf('/eventi/') === 0) return 'evento';
150    if (p === '/eventi.html' || /^\/(sagre|eventi)-provincia-/.test(p)) return 'eventi';
151    return 'altro';
152  }
153
154  /* ── Invio ─────────────────────────────────────────────────────────────
155     `event_name` non viene mandato come parametro: in GA4 e' gia' il nome
156     dell'evento stesso (la dimensione "Nome evento"), e un parametro
157     personalizzato con lo stesso nome andrebbe a sbattere contro quella
158     dimensione. Chi legge il report lo vede comunque, in colonna.
159     `link_url` resta accanto a `destination_url` per non spezzare la serie
160     storica: le esplorazioni gia' salvate in GA4 leggono quel nome. */
161  function track(nome, href, el) {
162    // `gtag` esiste sempre (e' lo stub che accoda): la domanda vera e' se
163    // l'utente ha acconsentito. Il flag lo mette cookie-consent.js.
164    if (!window.daopConsensoAnalytics || typeof gtag !== 'function') return;
165    var p = { page_path: percorso() };
166    // `apri_corso` non porta da nessuna parte: mandare una destinazione vuota
167    // riempirebbe il report di "(not set)" sulla meta' degli eventi.
168    if (href) { p.destination_url = href; p.link_url = href; }
169    for (var k in CTX) { if (CTX[k]) p[k] = CTX[k]; }
170    var riga = contesto_riga(el);
171    for (var j in riga) { p[j] = riga[j]; }
172    /* Da quale blocco marcato e' partito il clic (vedi internal_cta_view in
173       avvia()). Vale anche per `apri_ginetto` e `click_sponsorizzato`: la
174       domanda "da quale posto della pagina" e' la stessa. */
175    var blocco = el && el.closest && el.closest('[data-cta]');
176    if (blocco) p.cta_id = blocco.getAttribute('data-cta');
177    if (nome === 'internal_cta_click') p.destination_area = area_destinazione(href);
178    /* Quale guida. Sta in un attributo e non si deduce dal nome del file:
179       "estivi-2027.pdf" andrebbe spezzato in due qui dentro, e il giorno che il
180       nome cambia il report cambia in silenzio. Stessa ragione di
181       `organizer_name`. Va registrata in GA4 come dimensione evento prima di
182       pubblicare: non e' retroattiva. */
183    if (el && el.getAttribute) {
184      var g = el.getAttribute('data-guida');
185      if (g) { p.stagione = g; }
186    }
187    gtag('event', nome, p);
188  }
189
190  /* ── Che cosa e' stato cliccato ────────────────────────────────────────
191     L'ordine conta: i casi specifici stanno prima di quello generico, se no
192     una locandina su Supabase finirebbe in "sito organizzatore". */
193  var IMMAGINE = /\.(jpe?g|png|webp|avif|gif)(\?.*)?$/i;
194
195  function nome_evento(href) {
196    if (href.indexOf('mailto:') === 0) return 'click_email';
197    if (href.indexOf('tel:') === 0) return 'click_telefono';
198    if (href.indexOf('ginettoapp.it') !== -1) return 'apri_ginetto';
199    if (href.indexOf('gioco.sane-italia.it') !== -1) return 'gioca_ora';
200    if (href.indexOf('instagram.com') !== -1 ||
201        href.indexOf('facebook.com') !== -1 ||
202        href.indexOf('youtube.com') !== -1) return 'click_social';
203    // I tre sotto valgono soprattutto sulle pagine evento: dicono se la
204    // scheda ha fatto il suo mestiere. Chi apre la mappa o si segna la data
205    // sul calendario ci sta andando davvero, ed e' un segnale piu' forte di
206    // qualunque tempo sulla pagina.
207    if (href.indexOf('google.com/maps') !== -1) return 'click_come_arrivare';
208    if (href.indexOf('calendar.google.com') !== -1) return 'aggiungi_calendario';
209    if (href.indexOf('/storage/v1/object/public/locandine/') !== -1 ||
210        href.indexOf('/assets/miniature/') !== -1) return 'click_locandina';
211    /* La guida stagionale ha un nome suo e sta PRIMA del ramo generico dei
212       PDF. Nel secchio `scarica_materiale` finirebbe insieme a qualunque
213       allegato di qualunque organizzatore, e l'unica domanda che la guida pone
214       — quanti se la portano via ogni mille visite — si potrebbe rispondere
215       solo filtrando `destination_url` a mano, cioe' mai. E' esattamente il
216       difetto che l'invito al canale ha avuto per una settimana. */
217    if (href.indexOf('/guide/') !== -1 &&
218        href.slice(-4).toLowerCase() === '.pdf') return 'scarica_guida';
219    if (href.slice(-4).toLowerCase() === '.pdf') return 'scarica_materiale';
220    if (href.indexOf('amazon.') !== -1 || href.indexOf('amzn.') !== -1) return 'click_amazon';
221    // Generico: qualunque altro link che porta fuori da daop.it. Sulle schede
222    // evento e' il sito di chi organizza, scritto nella descrizione o nei
223    // recapiti; e' l'unico posto da cui puo' arrivare un link esterno.
224    if (/^https?:\/\//i.test(href) && href.indexOf('daop.it') === -1) {
225      if (IMMAGINE.test(href)) return 'click_locandina';
226      return 'click_sito_organizzatore';
227    }
228    return null;
229  }
230
231  /* ── Antirimbalzo ──────────────────────────────────────────────────────
232     Un clic solo deve valere un evento solo. Serve perche' su alcuni link
233     convivono piu' comportamenti (locandina.js apre il riquadro, il link
234     resta un href vero) e perche' un doppio clic involontario e' comune sui
235     telefoni. Stesso evento + stessa destinazione entro 800 ms: si scarta. */
236  var ultimo = { chiave: '', quando: 0 };
237  function invia(nome, href, el) {
238    // L'id della scatola entra nella chiave perche' `apri_corso` non ha una
239    // destinazione: senza, due corsi aperti in fila entro 800 ms sarebbero la
240    // stessa chiave e il secondo si perderebbe.
241    var box = el && el.closest && (el.closest('[data-org]') || el.closest('.lg-row'));
242    var chiave = nome + '|' + href + '|' + ((box && box.id) || '');
243    var ora = Date.now();
244    if (chiave === ultimo.chiave && ora - ultimo.quando < 800) return;
245    ultimo.chiave = chiave;
246    ultimo.quando = ora;
247    track(nome, href, el);
248  }
249
250  function avvia() {
251    /* Un ascoltatore solo, in delega sul document, invece di uno per ogni
252       <a>: su eventi.html i link sono ~1.500 e attaccarli a mano era lavoro
253       al caricamento sulla pagina piu' pesante del sito (11.000 nodi, il
254       punto dolente documentato in CLAUDE.md). In delega il costo e' zero e
255       vale anche per i link che compaiono dopo, senza riagganciare niente. */
256    document.addEventListener('click', function (ev) {
257      var a = ev.target.closest && ev.target.closest('a[href]');
258      if (a) {
259        var href = a.getAttribute('href') || '';
260        // L'unico link INTERNO che si conta, e il perche' e' commerciale: e'
261        // uno spazio venduto, e a chi lo compra si deve poter dire quante
262        // volte e' stato toccato. Il page_view di /luoghi.html che segue non
263        // lo direbbe - non sa da quale riga di quale scheda si e' arrivati.
264        if (a.closest('.ev-spons')) { invia('click_sponsorizzato', href, a); return; }
265        var nome = nome_evento(href);
266        if (nome) { invia(nome, href, a); return; }
267        // Un link interno dentro un blocco marcato. Fuori da quei blocchi la
268        // navigazione interna resta affidata ai page_view, vedi in testa.
269        if (a.closest('[data-cta]') && area_destinazione(href)) {
270          invia('internal_cta_click', href, a);
271        }
272        return;
273      }
274      /* L'APERTURA DI UNA SCHEDA LUOGO, il denominatore che mancava a
275         /luoghi.html: GA4 contava i clic DALLA scheda (mappe, telefono, sito)
276         ma non quante volte era stata aperta, quindi a un cliente si poteva
277         dire "47 hanno chiesto le indicazioni" e non su quante. Stessa regola
278         di apri_corso qui sotto: al momento del capture il <details> e' ancora
279         nello stato vecchio, quindi `open` falso vuol dire che si sta aprendo,
280         e richiudere non conta. */
281      var sum = ev.target.closest && ev.target.closest('.lg-row > summary');
282      if (sum) {
283        if (!sum.parentElement.open) invia('apri_luogo', '', sum.parentElement);
284        return;
285      }
286      /* L'APERTURA DI UNA SCHEDA CORSO, che e' il denominatore di tutto il
287         resto. /corsi.html e' UNA pagina: il suo page_view dice "qualcuno ha
288         aperto l'elenco", non "qualcuno ha guardato i corsi della PGS
289         Roccavione". Senza questo evento a una realta' si potrebbe dire "3
290         clic al tuo sito" ma non su quante volte, che e' l'unica forma in cui
291         quel numero vuol dire qualcosa — lo stesso buco gia' scritto in
292         CLAUDE.md per le schede di luoghi.html.
293
294         Si conta solo l'APERTURA: al momento del capture `aria-expanded` ha
295         ancora il valore vecchio, quindi "false" vuol dire che sta aprendo.
296         Chiudere una riga non e' un secondo interessamento.
297
298         Il selettore chiede `data-org`, che oggi lo stampa il solo
299         genera_corsi.py: le ~300 schede evento hanno le stesse `.ev-row` e
300         contarle qui vorrebbe dire moltiplicare gli eventi su tutto il sito
301         per una domanda che li' nessuno ha fatto. */
302      var apri = ev.target.closest && ev.target.closest('.event-card[data-org] .ev-row');
303      if (apri) {
304        if (apri.getAttribute('aria-expanded') === 'false') invia('apri_corso', '', apri);
305        return;
306      }
307      // Sulle schede evento la locandina non e' dentro un link: e' l'immagine
308      // grande in colonna, che locandina.js apre nel riquadro. Senza questo
309      // ramo il clic piu' significativo della scheda non si vedrebbe.
310      var img = ev.target.closest && ev.target.closest('img.ev-loc');
311      if (img) invia('click_locandina', img.getAttribute('src') || '', img);
312    }, true);
313
314    /* LA VISTA DI UN BLOCCO MARCATO (`data-cta`), una volta per pagina.
315       E' il denominatore del clic: 20 clic sul link ai corsi non dicono niente
316       se non si sa se il blocco l'hanno visto in cento o in diecimila. Prima
317       quel numero si stimava da scroll_depth e dall'altezza misurata a mano
318       del blocco; cosi' si legge.
319
320       "Visto" vuol dire meta' del blocco a schermo, oppure - per un blocco
321       piu' alto dello schermo - una fetta pari al 40% dello schermo.
322
323       Senza consenso non si manda e NON si segna come visto: il blocco resta
324       osservato, e al prossimo passaggio di soglia (basta scorrere) si
325       riprova. Segnarlo lo stesso vorrebbe dire perdere per sempre la vista di
326       chi accetta il banner dopo aver gia' visto il blocco, cioe' contare clic
327       senza la loro vista.
328
329       Costa zero sulle pagine che non hanno blocchi marcati: senza elementi
330       l'osservatore non nasce nemmeno. */
331    var blocchi = document.querySelectorAll('[data-cta]');
332    if (blocchi.length && 'IntersectionObserver' in window) {
333      var visti = {};
334      var oss = new IntersectionObserver(function (voci) {
335        voci.forEach(function (v) {
336          if (!v.isIntersecting) return;
337          if (v.intersectionRatio < 0.5 &&
338              v.intersectionRect.height < window.innerHeight * 0.4) return;
339          var id = v.target.getAttribute('data-cta');
340          if (visti[id]) { oss.unobserve(v.target); return; }
341          if (!window.daopConsensoAnalytics || typeof gtag !== 'function') return;
342          visti[id] = true;
343          oss.unobserve(v.target);
344          var p = { page_path: percorso(), cta_id: id };
345          for (var k in CTX) { if (CTX[k]) p[k] = CTX[k]; }
346          gtag('event', 'internal_cta_view', p);
347        });
348      }, { threshold: [0, 0.25, 0.5, 0.75, 1] });
349      [].forEach.call(blocchi, function (b) { oss.observe(b); });
350    }
351
352    /* "Vicino a me". Il filtro per distanza (daop-vicino.js) non chiama gtag
353       da solo: emette un evento DOM e lo raccogliamo qui, cosi' gtag resta
354       scritto in un posto solo - la stessa regola per cui cookie-consent.js e'
355       l'unico posto in cui GA4 si inizializza.
356
357       NON si manda mai la posizione. Partono due cose sole: COME e' stato
358       scelto il centro (gps, comune, gradino) e QUANTI chilometri. Le
359       coordinate restano nel browser, che e' anche quello che la pagina
360       promette a chi la usa: mandarle a GA4 renderebbe quella riga una bugia.
361
362       A cosa serve misurarlo: a decidere se la funzione vale su altre pagine,
363       e soprattutto se chi la usa poi apre una scheda. Il resto del percorso
364       si legge gia' con i page_view. */
365    document.addEventListener('daop:vicino', function (ev) {
366      var d = (ev && ev.detail) || {};
367      var p = { page_path: percorso() };
368      if (d.metodo) p.metodo_posizione = d.metodo;
369      if (d.raggio) p.raggio_km = d.raggio;
370      for (var k in CTX) { if (CTX[k]) p[k] = CTX[k]; }
371      // Stesso antirimbalzo dei clic: chi prova tre gradini di fila e' una
372      // persona che sta scegliendo, non tre eventi.
373      var chiave = 'vicino|' + d.metodo + '|' + d.raggio;
374      var ora = Date.now();
375      if (chiave === ultimo.chiave && ora - ultimo.quando < 800) return;
376      ultimo.chiave = chiave;
377      ultimo.quando = ora;
378      if (!window.daopConsensoAnalytics || typeof gtag !== 'function') return;
379      gtag('event', 'vicino_a_me', p);
380    });
381
382    /* La mappa di luoghi.html (daop-mappa.js). Come "vicino a me", il modulo
383       non chiama gtag: emette un evento DOM. Parte una volta per pagina, alla
384       prima apertura: e' il numero che dice se la mappa serve, e va letto
385       contro i page_view di /luoghi.html con la regola dei 2,5 per utente. */
386    document.addEventListener('daop:mappa', function (ev) {
387      if (!window.daopConsensoAnalytics || typeof gtag !== 'function') return;
388      var d = (ev && ev.detail) || {};
389      var p = { page_path: percorso() };
390      if (d.tipo) p.destination_area = d.tipo;
391      for (var k in CTX) { if (CTX[k]) p[k] = CTX[k]; }
392      gtag('event', 'apri_mappa', p);
393    });
394
395    /* Profondita' di scroll: evento a 25/50/75/100%. Su una scheda evento
396       dice se sono arrivati in fondo, dove stanno la firma e i link alle
397       landing. E' voluto e NON e' un doppione dell'evento automatico
398       `scroll` di GA4: quello scatta una volta sola, al 90%, e non distingue
399       chi si ferma a meta'. Se in GA4 si vogliono vedere solo questi, si
400       toglie "Scorrimenti" da Misurazione avanzata. */
401    var soglie = [25, 50, 75, 100], raggiunte = {}, inCoda = false;
402    window.addEventListener('scroll', function () {
403      // Il calcolo legge scrollHeight/clientHeight, che forzano il layout:
404      // farlo a ogni evento di scroll su un documento da 11.000 nodi si
405      // sente. Una lettura per frame basta e avanza.
406      if (inCoda) return;
407      inCoda = true;
408      requestAnimationFrame(function () {
409        inCoda = false;
410        var h = document.documentElement;
411        var scrollabile = h.scrollHeight - h.clientHeight;
412        if (scrollabile <= 0) return;
413        var perc = (h.scrollTop || document.body.scrollTop) / scrollabile * 100;
414        soglie.forEach(function (s) {
415          if (perc >= s && !raggiunte[s]) {
416            raggiunte[s] = true;
417            if (window.daopConsensoAnalytics && typeof gtag === 'function') {
418              gtag('event', 'scroll_depth', { percent_scroll: s, page_path: percorso() });
419            }
420          }
421        });
422      });
423    }, { passive: true });
424  }
425
426  // Lo script e' caricato con defer, quindi il DOM c'e' gia'; il ramo
427  // DOMContentLoaded resta per sicurezza se qualcuno lo includesse senza defer.
428  if (document.readyState === 'loading') {
429    document.addEventListener('DOMContentLoaded', avvia);
430  } else {
431    avvia();
432  }
433})();

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.