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.