(function() { 'use strict'; var ENDPOINT = 'https://analytics.perceptum.nl/api/ingest'; var API_KEY = 'lv_live_g4BPVde7QJwg_kAvPZD1ab7XKV3FCCX0'; var HEARTBEAT_INTERVAL = 5000; // Persistent visitor ID (localStorage - NOT a cookie, GDPR-friendly) var visitorId = localStorage.getItem('lv_vid'); var isNewVisitor = !visitorId; if (!visitorId) { visitorId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substr(2) + Date.now().toString(36) + Math.random().toString(36).substr(2); localStorage.setItem('lv_vid', visitorId); } // Session management with page sequence tracking var storedSession = sessionStorage.getItem('lv_session'); var sessionData = storedSession ? JSON.parse(storedSession) : null; // Check if session is still valid (within 30 min) var isValidSession = sessionData && (Date.now() - sessionData.lastActivity < 1800000); var sessionId = isValidSession ? sessionData.id : (crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substr(2) + Date.now().toString(36)); var pageSeq = isValidSession ? sessionData.pageSeq + 1 : 1; // Track if this is a new session (for new visitor detection) var isFirstPageOfSession = !isValidSession || pageSeq === 1; // Save session state sessionStorage.setItem('lv_session', JSON.stringify({ id: sessionId, pageSeq: pageSeq, lastActivity: Date.now() })); var pageEnteredAt = Date.now(); var activeTime = 0; var lastActiveTimestamp = Date.now(); var isVisible = !document.hidden; var maxScrollDepth = 0; function getScrollDepth() { var scrollTop = window.scrollY || document.documentElement.scrollTop; var docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); var winHeight = window.innerHeight; if (docHeight <= winHeight) return 100; return Math.min(100, Math.round((scrollTop + winHeight) / docHeight * 100)); } document.addEventListener('visibilitychange', function() { if (document.hidden) { activeTime += Date.now() - lastActiveTimestamp; isVisible = false; } else { lastActiveTimestamp = Date.now(); isVisible = true; } }); var scrollThrottle = null; window.addEventListener('scroll', function() { if (scrollThrottle) return; scrollThrottle = setTimeout(function() { scrollThrottle = null; var depth = getScrollDepth(); if (depth > maxScrollDepth) maxScrollDepth = depth; }, 200); }, { passive: true }); function buildPayload(eventType) { var currentActive = isVisible ? activeTime + (Date.now() - lastActiveTimestamp) : activeTime; // Update session activity timestamp var sess = JSON.parse(sessionStorage.getItem('lv_session') || '{}'); sess.lastActivity = Date.now(); sessionStorage.setItem('lv_session', JSON.stringify(sess)); return { sid: sessionId, vid: visitorId, url: window.location.pathname + window.location.search, title: document.title, ref: document.referrer || null, active_ms: currentActive, scroll_depth: maxScrollDepth, is_visible: isVisible, screen: screen.width + 'x' + screen.height, viewport: window.innerWidth + 'x' + window.innerHeight, lang: navigator.language, event: eventType, ts: Date.now(), page_seq: pageSeq, is_new: isNewVisitor && isFirstPageOfSession }; } function send(payload) { var data = JSON.stringify(payload); if (payload.event === 'leave' && navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT + '?key=' + API_KEY, data); } else { fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + API_KEY }, body: data, keepalive: true }).catch(function() {}); } } function startHeartbeat() { setInterval(function() { if (isVisible) send(buildPayload('heartbeat')); }, HEARTBEAT_INTERVAL); } send(buildPayload('pageview')); maxScrollDepth = getScrollDepth(); startHeartbeat(); window.addEventListener('beforeunload', function() { send(buildPayload('leave')); }); document.addEventListener('visibilitychange', function() { if (document.hidden) send(buildPayload('leave')); }); // Expose public API for custom event tracking window.liveview = { track: function(eventName, properties) { if (!eventName || typeof eventName !== 'string') return; var payload = { sid: sessionId, vid: visitorId, url: window.location.pathname + window.location.search, event: 'custom', event_name: eventName, properties: properties || {}, ts: Date.now() }; fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + API_KEY }, body: JSON.stringify(payload), keepalive: true }).catch(function() {}); }, // Get visitor ID (useful for integrations) getVisitorId: function() { return visitorId; }, getSessionId: function() { return sessionId; } }; })();