(function (window, document) { 'use strict'; var storageKey = 'iouAnalyticsCampaign'; var scrollMilestones = [25, 50, 75, 100]; function safeJsonParse(value) { try { return JSON.parse(value); } catch (error) { return null; } } function safeStorageGet() { try { return window.localStorage.getItem(storageKey); } catch (error) { return null; } } function safeStorageSet(value) { try { window.localStorage.setItem(storageKey, value); } catch (error) { return; } } function sanitizeValue(value) { if (value === null || value === undefined || value === '') { return null; } return String(value).replace(/\s+/g, ' ').trim().substring(0, 100); } function cleanParams(params) { var cleaned = {}; var key; for (key in params) { if (!Object.prototype.hasOwnProperty.call(params, key)) { continue; } if (typeof params[key] === 'boolean' || typeof params[key] === 'number') { cleaned[key] = params[key]; continue; } var sanitized = sanitizeValue(params[key]); if (sanitized !== null) { cleaned[key] = sanitized; } } return cleaned; } function getQueryParams() { return new window.URLSearchParams(window.location.search); } function getCampaignFromQuery(queryParams) { var campaign = { source: sanitizeValue(queryParams.get('utm_source')), medium: sanitizeValue(queryParams.get('utm_medium')), campaign: sanitizeValue(queryParams.get('utm_campaign')), term: sanitizeValue(queryParams.get('utm_term')), content: sanitizeValue(queryParams.get('utm_content')), gclid: sanitizeValue(queryParams.get('gclid')), fbclid: sanitizeValue(queryParams.get('fbclid')) }; if (campaign.source || campaign.medium || campaign.campaign || campaign.term || campaign.content || campaign.gclid || campaign.fbclid) { campaign.capturedAt = new Date().toISOString(); return campaign; } return null; } function getStoredCampaign() { return safeJsonParse(safeStorageGet()) || {}; } function persistCampaign(campaign) { if (!campaign) { return; } safeStorageSet(JSON.stringify(campaign)); } function buildBaseContext(options) { var queryParams = getQueryParams(); var campaign = getStoredCampaign(); var locale = sanitizeValue(options.locale || queryParams.get('locale') || document.documentElement.lang); var debugModeEnabled = !!window.iouAnalyticsDebugMode; return cleanParams({ page_type: options.pageType, page_location: window.location.href, page_path: window.location.pathname, page_title: document.title, locale: locale, debug_mode: debugModeEnabled, campaign_source: campaign.source, campaign_medium: campaign.medium, campaign_name: campaign.campaign, campaign_term: campaign.term, campaign_content: campaign.content }); } function dispatchEvent(name, params) { var eventParams = cleanParams(params || {}); if (typeof window.gtag === 'function') { window.gtag('event', name, eventParams); return; } if (Array.isArray(window.dataLayer)) { window.dataLayer.push(Object.assign({ event: name }, eventParams)); } } function trackEvent(name, options, params) { dispatchEvent(name, Object.assign({}, buildBaseContext(options), params || {})); } function trackLandingSignals(options) { var queryParams = getQueryParams(); var campaignFromQuery = getCampaignFromQuery(queryParams); var referrerHost = null; persistCampaign(campaignFromQuery); if (document.referrer) { try { referrerHost = new window.URL(document.referrer).hostname; } catch (error) { referrerHost = sanitizeValue(document.referrer); } } trackEvent('page_context', options, { has_utm: !!campaignFromQuery, has_referrer: !!document.referrer, referrer_host: referrerHost }); if (campaignFromQuery || document.referrer) { trackEvent('traffic_origin_detected', options, { origin_type: campaignFromQuery ? 'campaign' : 'referrer_only', referrer_host: referrerHost }); } } function bindTrackedClicks(options, clickDefinitions) { (clickDefinitions || []).forEach(function (definition) { var elements = document.querySelectorAll(definition.selector); Array.prototype.forEach.call(elements, function (element) { element.addEventListener('click', function () { var params = typeof definition.params === 'function' ? definition.params(element) : (definition.params || {}); trackEvent(definition.eventName, options, params); }); }); }); } function observeSections(options, sections) { if (!window.IntersectionObserver || !sections || !sections.length) { return; } var seenSections = {}; var observer = new window.IntersectionObserver(function (entries) { entries.forEach(function (entry) { var sectionName = entry.target.getAttribute('data-analytics-section') || entry.target.id; if (!entry.isIntersecting || seenSections[sectionName]) { return; } seenSections[sectionName] = true; trackEvent('section_view', options, { section_name: sectionName }); }); }, { threshold: 0.55 }); sections.forEach(function (selector) { var element = document.querySelector(selector); if (element) { observer.observe(element); } }); } function trackScrollDepth(options) { var triggered = {}; function handler() { var doc = document.documentElement; var scrollHeight = Math.max(doc.scrollHeight, document.body.scrollHeight) - window.innerHeight; var progress = scrollHeight > 0 ? Math.round((window.scrollY / scrollHeight) * 100) : 100; scrollMilestones.forEach(function (milestone) { if (!triggered[milestone] && progress >= milestone) { triggered[milestone] = true; trackEvent('scroll_depth', options, { percent_scrolled: milestone }); } }); } window.addEventListener('scroll', handler, { passive: true }); handler(); } function decorateShareUrl(baseUrl, params) { var url = new window.URL(baseUrl, window.location.origin); Object.keys(params).forEach(function (key) { if (params[key] !== null && params[key] !== undefined && params[key] !== '') { url.searchParams.set(key, params[key]); } }); return url.toString(); } window.iouAnalytics = { init: function (options) { var settings = options || {}; trackLandingSignals(settings); bindTrackedClicks(settings, settings.clicks || []); observeSections(settings, settings.sections || []); if (settings.trackScrollDepth) { trackScrollDepth(settings); } }, track: function (eventName, options, params) { trackEvent(eventName, options || {}, params || {}); }, decorateShareUrl: decorateShareUrl }; })(window, document);