PageSourceSearch

https://amicipeteco.it/modules/zootecblog/views/js/blog.js?v=1784203112

js amicipeteco.it collected 2026-10-10 00:58:18 UTC 6,793 bytes, 171 lines download raw bytes

1/* ZootecBlog — frontend behavior (vanilla, no deps).
2   - GA4 dataLayer push (blog_view_post)
3   - Smooth scroll anchor interni
4   - Analytics first-party via beacon → controllers/front/track.php
5     (view, engagement=scroll+tempo, product_click, internal/outbound click, share)
6     Consent-independent e cache-proof: gira sempre lato client, mentre un
7     contatore PHP server-side non girerebbe sulle pagine cachate (CF/SuperSpeed).
8*/
9(function () {
10  'use strict';
11
12  var article = document.querySelector('.zblog-article');
13  if (!article) return; // tracking solo sulla pagina articolo
14
15  /* ── GA4 dataLayer (invariato) ─────────────────────────────────────── */
16  try {
17    if (window.dataLayer) {
18      var titleEl = article.querySelector('.zblog-article__title');
19      window.dataLayer.push({
20        event: 'blog_view_post',
21        post_title: titleEl ? titleEl.textContent.trim() : '',
22        post_url: location.pathname,
23      });
24    }
25  } catch (e) {}
26
27  /* ── Smooth scroll anchor interni (invariato) ──────────────────────── */
28  var body = article.querySelector('.zblog-article__body');
29  document.querySelectorAll('.zblog-article__body a[href^="#"]').forEach(function (a) {
30    a.addEventListener('click', function (e) {
31      var t = document.getElementById(a.getAttribute('href').slice(1));
32      if (t) { e.preventDefault(); t.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
33    });
34  });
35
36  /* ── Analytics first-party ─────────────────────────────────────────── */
37  var postId = parseInt(article.getAttribute('data-zb-post') || '0', 10);
38  var trackUrl = article.getAttribute('data-zb-track') || '';
39  if (!postId || !trackUrl) return;
40
41  // ── Cookie/ID helpers ──
42  function getCookie(n) {
43    var m = document.cookie.match('(?:^|;)\\s*' + n + '=([^;]*)');
44    return m ? decodeURIComponent(m[1]) : '';
45  }
46  function setCookie(n, v, days) {
47    var d = new Date(); d.setTime(d.getTime() + days * 864e5);
48    document.cookie = n + '=' + encodeURIComponent(v) + ';expires=' + d.toUTCString() +
49      ';path=/;SameSite=Lax' + (location.protocol === 'https:' ? ';Secure' : '');
50  }
51  function rid() {
52    try {
53      var a = new Uint8Array(16); (window.crypto || {}).getRandomValues && crypto.getRandomValues(a);
54      return Array.prototype.map.call(a, function (b) { return ('0' + b.toString(16)).slice(-2); }).join('');
55    } catch (e) {
56      return (Date.now().toString(16) + Math.random().toString(16).slice(2)).slice(0, 32);
57    }
58  }
59
60  // Visitatore persistente (1 anno) — nuovo se il cookie non esisteva
61  var isNew = 0;
62  var visitor = getCookie('zb_v');
63  if (!visitor) { visitor = rid(); isNew = 1; setCookie('zb_v', visitor, 365); }
64  else { setCookie('zb_v', visitor, 365); } // refresh scadenza
65  // Sessione (per dedup engagement lato aggregazione)
66  var session = '';
67  try {
68    session = sessionStorage.getItem('zb_s') || '';
69    if (!session) { session = rid(); sessionStorage.setItem('zb_s', session); }
70  } catch (e) { session = rid(); }
71
72  // UTM dalla query string
73  var qs = new URLSearchParams(location.search || '');
74
75  function send(payload) {
76    payload.t = postId; payload.v = visitor; payload.s = session;
77    var json = JSON.stringify(payload);
78    try {
79      if (navigator.sendBeacon) {
80        var blob = new Blob([json], { type: 'application/json' });
81        if (navigator.sendBeacon(trackUrl, blob)) return;
82      }
83    } catch (e) {}
84    // Fallback
85    try { fetch(trackUrl, { method: 'POST', body: json, keepalive: true, headers: { 'Content-Type': 'application/json' } }); } catch (e) {}
86  }
87
88  // ── VIEW (subito) ──
89  send({
90    e: 'view',
91    nv: isNew,
92    ref: document.referrer || '',
93    utm_source: qs.get('utm_source') || '',
94    utm_medium: qs.get('utm_medium') || '',
95    utm_campaign: qs.get('utm_campaign') || '',
96  });
97
98  // ── ENGAGEMENT: scroll depth + tempo attivo ──
99  var maxScroll = 0, activeSec = 0, sentEngage = false;
100
101  function computeScroll() {
102    if (!body) return;
103    var rect = body.getBoundingClientRect();
104    var bodyTop = rect.top + window.pageYOffset;
105    var bodyH = body.offsetHeight || 1;
106    var seen = (window.pageYOffset + window.innerHeight) - bodyTop;
107    var pct = Math.round((seen / bodyH) * 100);
108    if (pct < 0) pct = 0; if (pct > 100) pct = 100;
109    if (pct > maxScroll) maxScroll = pct;
110  }
111  computeScroll();
112  window.addEventListener('scroll', computeScroll, { passive: true });
113  window.addEventListener('resize', computeScroll, { passive: true });
114
115  // Tempo attivo: +1s ogni secondo con tab visibile (cap 12h)
116  setInterval(function () {
117    if (document.visibilityState === 'visible' && activeSec < 43200) activeSec++;
118  }, 1000);
119
120  function sendEngagement() {
121    if (sentEngage) return;      // una sola volta per pagina
122    if (maxScroll === 0 && activeSec === 0) return;
123    sentEngage = true;
124    send({
125      e: 'engagement',
126      sc: maxScroll,
127      as: activeSec,
128      re: maxScroll >= 90 ? 1 : 0,
129    });
130  }
131  // pagehide copre desktop; visibilitychange hidden copre mobile/switch tab
132  window.addEventListener('pagehide', sendEngagement);
133  document.addEventListener('visibilitychange', function () {
134    if (document.visibilityState === 'hidden') sendEngagement();
135  });
136
137  // ── CLICK sui link del corpo articolo ──
138  var PRODUCT_RE = /\/(\d+)-[^\/?#]*\.html(?:[?#]|$)/;
139  if (body) {
140    body.addEventListener('click', function (ev) {
141      var a = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null;
142      if (!a) return;
143      var href = a.getAttribute('href') || '';
144      if (href === '' || href.charAt(0) === '#' || /^(mailto:|tel:|javascript:)/i.test(href)) return;
145
146      var url;
147      try { url = new URL(a.href, location.href); } catch (e) { return; }
148
149      if (url.host !== location.host) {
150        send({ e: 'outbound_click', url: url.href.slice(0, 600) });
151        return;
152      }
153      var m = url.pathname.match(PRODUCT_RE);
154      if (m) {
155        var pid = parseInt(m[1], 10) || 0;
156        // Attribuzione: memorizza l'articolo sorgente (last-click, 30gg)
157        setCookie('zb_src', postId, 30);
158        send({ e: 'product_click', pid: pid, url: url.pathname.slice(0, 600) });
159      } else {
160        send({ e: 'internal_click', url: url.pathname.slice(0, 600) });
161      }
162    }, true);
163  }
164
165  // ── SHARE ──
166  article.querySelectorAll('.zblog-share a[data-zb-share]').forEach(function (a) {
167    a.addEventListener('click', function () {
168      send({ e: 'share', sn: a.getAttribute('data-zb-share') || '' });
169    });
170  });
171})();

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.