Excludes analytics/data, vendor, support, .env, ACME tokens. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
120 lines
3.6 KiB
JavaScript
120 lines
3.6 KiB
JavaScript
/* Version: 1.0.0 | Created: 2026-04-08 | Updated: 2026-04-08 */
|
|
/* HELP4BIS Analytics Tracker — lightweight, self-hosted, privacy-first */
|
|
(function() {
|
|
'use strict';
|
|
|
|
var endpoint = '/analytics/track.php';
|
|
|
|
function getPagePath() {
|
|
return window.location.pathname || '/';
|
|
}
|
|
|
|
function getReferrer() {
|
|
var ref = document.referrer || '';
|
|
// Strip own domain
|
|
if (ref.indexOf(window.location.hostname) !== -1) return '';
|
|
return ref;
|
|
}
|
|
|
|
function getScreenSize() {
|
|
return window.screen.width + 'x' + window.screen.height;
|
|
}
|
|
|
|
function getUtmParams() {
|
|
var params = {};
|
|
var search = window.location.search;
|
|
if (!search) return params;
|
|
var pairs = search.substring(1).split('&');
|
|
for (var i = 0; i < pairs.length; i++) {
|
|
var kv = pairs[i].split('=');
|
|
var key = decodeURIComponent(kv[0]);
|
|
if (key.indexOf('utm_') === 0) {
|
|
params[key] = decodeURIComponent(kv[1] || '');
|
|
}
|
|
}
|
|
return params;
|
|
}
|
|
|
|
function track(eventType, data) {
|
|
var payload = {
|
|
event: eventType,
|
|
page: getPagePath(),
|
|
title: document.title,
|
|
referrer: getReferrer(),
|
|
screen: getScreenSize(),
|
|
language: navigator.language || '',
|
|
utm: getUtmParams(),
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
if (data) {
|
|
for (var key in data) {
|
|
if (data.hasOwnProperty(key)) payload[key] = data[key];
|
|
}
|
|
}
|
|
|
|
// Use sendBeacon for reliability, fallback to fetch
|
|
var body = JSON.stringify(payload);
|
|
if (navigator.sendBeacon) {
|
|
navigator.sendBeacon(endpoint, body);
|
|
} else {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', endpoint, true);
|
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
xhr.send(body);
|
|
}
|
|
}
|
|
|
|
// Track page view on load
|
|
track('pageview');
|
|
|
|
// Track time on page (fires on unload)
|
|
var startTime = Date.now();
|
|
window.addEventListener('beforeunload', function() {
|
|
var duration = Math.round((Date.now() - startTime) / 1000);
|
|
if (duration > 1) {
|
|
track('duration', { seconds: duration });
|
|
}
|
|
});
|
|
|
|
// Track outbound link clicks
|
|
document.addEventListener('click', function(e) {
|
|
var link = e.target.closest('a');
|
|
if (!link) return;
|
|
var href = link.getAttribute('href') || '';
|
|
// CTA buttons (contact, get started, etc.)
|
|
if (link.classList.contains('btn') || link.classList.contains('cta')) {
|
|
track('cta_click', { href: href, text: (link.textContent || '').trim().substring(0, 80) });
|
|
}
|
|
// Outbound links
|
|
if (href.indexOf('http') === 0 && href.indexOf(window.location.hostname) === -1) {
|
|
track('outbound', { href: href });
|
|
}
|
|
// Phone/email clicks
|
|
if (href.indexOf('tel:') === 0 || href.indexOf('mailto:') === 0) {
|
|
track('contact_click', { href: href });
|
|
}
|
|
});
|
|
|
|
// Track form submissions
|
|
document.addEventListener('submit', function(e) {
|
|
var form = e.target;
|
|
track('form_submit', { form_id: form.id || '', form_action: form.action || '' });
|
|
});
|
|
|
|
// Track scroll depth (25%, 50%, 75%, 100%)
|
|
var scrollMarks = { 25: false, 50: false, 75: false, 100: false };
|
|
window.addEventListener('scroll', function() {
|
|
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
var docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
|
if (docHeight <= 0) return;
|
|
var pct = Math.round((scrollTop / docHeight) * 100);
|
|
[25, 50, 75, 100].forEach(function(mark) {
|
|
if (pct >= mark && !scrollMarks[mark]) {
|
|
scrollMarks[mark] = true;
|
|
track('scroll', { depth: mark });
|
|
}
|
|
});
|
|
});
|
|
})();
|