PageSourceSearch

https://gadgetfee.com/wp-content/plugins/grimfaste-tracker/assets/js/tracker.js?ver=2.19.0

js gadgetfee.com collected 2026-10-03 21:50:19 UTC 25,628 bytes, 755 lines download raw bytes

1(function() {
2    'use strict';
3
4    // Abort if config missing
5    if (typeof grimfasteConfig === 'undefined') return;
6
7    var config = grimfasteConfig;
8    var queue = [];
9
10    // Scroll depth tracking state
11    var maxScrollDepth = 0;
12
13    // Time-on-page tracking state
14    var activeTimeStart = Date.now();
15    var totalActiveTime = 0;
16    var isPageVisible = true;
17
18    // Adaptive intervals based on traffic tier (configured from server)
19    // Defaults to 'default' tier (30 min) if not specified
20    var TRAFFIC_TIER = config.trafficTier || 'default';
21    var TIER_SETTINGS = {
22        realtime: { interval: 5000,    batchSize: 10, flushOnUnload: true },  // 5s, real-time tracking
23        high:     { interval: 30000,   batchSize: 5,  flushOnUnload: true },  // 30s, high traffic
24        medium:   { interval: 60000,   batchSize: 3,  flushOnUnload: true },  // 60s, medium traffic
25        low:      { interval: 120000,  batchSize: 2,  flushOnUnload: true },  // 2min, low traffic
26        default:  { interval: 1800000, batchSize: 1,  flushOnUnload: true }   // 30min, default (cost-optimized)
27    };
28
29    var tierConfig = TIER_SETTINGS[TRAFFIC_TIER] || TIER_SETTINGS.default;
30    var BATCH_INTERVAL = tierConfig.interval;
31    var BATCH_SIZE = tierConfig.batchSize;
32
33    // Generate or retrieve session ID
34    function getSessionId() {
35        var sessionId = sessionStorage.getItem('grimfaste_session_id');
36        if (!sessionId) {
37            sessionId = 'sess_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
38            sessionStorage.setItem('grimfaste_session_id', sessionId);
39        }
40        return sessionId;
41    }
42
43    var sessionId = getSessionId();
44
45    function getCookie(name) {
46        var parts = document.cookie ? document.cookie.split('; ') : [];
47        for (var i = 0; i < parts.length; i++) {
48            var part = parts[i];
49            var eq = part.indexOf('=');
50            var key = eq >= 0 ? part.slice(0, eq) : part;
51            if (key === name) {
52                return decodeURIComponent(eq >= 0 ? part.slice(eq + 1) : '');
53            }
54        }
55        return '';
56    }
57
58    function setCookie(name, value, days) {
59        var cookie = name + '=' + encodeURIComponent(value) + '; path=/; SameSite=Lax';
60        if (days && days > 0) {
61            cookie += '; max-age=' + Math.round(days * 86400);
62        }
63        if (window.location.protocol === 'https:') {
64            cookie += '; Secure';
65        }
66        document.cookie = cookie;
67    }
68
69    function campaignCookieKey(prefix, externalKey) {
70        return prefix + encodeURIComponent(externalKey || '').replace(/%/g, '_');
71    }
72
73    // Amazon domains
74    var amazonDomains = [
75        'amazon.com', 'amazon.de', 'amazon.co.uk', 'amazon.fr',
76        'amazon.it', 'amazon.es', 'amazon.ca', 'amazon.com.au',
77        'amazon.co.jp', 'amazon.in', 'amzn.to', 'amzn.eu', 'a.co'
78    ];
79
80    // Check if URL is external
81    function isExternalLink(url) {
82        try {
83            var linkHost = new URL(url).hostname;
84            return linkHost !== window.location.hostname;
85        } catch (e) {
86            return false;
87        }
88    }
89
90    // Check if URL is Amazon
91    function isAmazonLink(url) {
92        try {
93            var hostname = new URL(url).hostname.toLowerCase();
94            for (var i = 0; i < amazonDomains.length; i++) {
95                if (hostname === amazonDomains[i] || hostname.endsWith('.' + amazonDomains[i])) {
96                    return true;
97                }
98            }
99            return false;
100        } catch (e) {
101            return false;
102        }
103    }
104
105    // Get common event data
106    function getCommonData() {
107        return {
108            url: window.location.href,
109            title: document.title,
110            referrer: document.referrer || undefined,
111            userAgent: navigator.userAgent,
112            sessionId: sessionId
113        };
114    }
115
116    // Throttle helper
117    function throttle(fn, delay) {
118        var lastCall = 0;
119        return function() {
120            var now = Date.now();
121            if (now - lastCall >= delay) {
122                lastCall = now;
123                fn();
124            }
125        };
126    }
127
128    // Update max scroll depth
129    var updateScrollDepth = throttle(function() {
130        var scrollTop = window.pageYOffset || document.documentElement.scrollTop || 0;
131        var windowHeight = window.innerHeight || document.documentElement.clientHeight || 0;
132        var docHeight = Math.max(
133            document.body.scrollHeight || 0,
134            document.documentElement.scrollHeight || 0,
135            document.body.offsetHeight || 0,
136            document.documentElement.offsetHeight || 0
137        );
138        if (docHeight > 0) {
139            var depth = Math.round((scrollTop + windowHeight) / docHeight * 100);
140            if (depth > maxScrollDepth) {
141                maxScrollDepth = Math.min(depth, 100);
142            }
143        }
144    }, 200);
145
146    // Get accumulated active time in milliseconds
147    function getActiveTime() {
148        var elapsed = totalActiveTime;
149        if (isPageVisible) {
150            elapsed += Date.now() - activeTimeStart;
151        }
152        return elapsed;
153    }
154
155    // Pause/resume active time tracking
156    function pauseActiveTime() {
157        if (isPageVisible) {
158            totalActiveTime += Date.now() - activeTimeStart;
159            isPageVisible = false;
160        }
161    }
162
163    function resumeActiveTime() {
164        if (!isPageVisible) {
165            activeTimeStart = Date.now();
166            isPageVisible = true;
167        }
168    }
169
170    // Extract WordPress search query from URL
171    function getSearchQuery() {
172        try {
173            var params = new URLSearchParams(window.location.search);
174            return params.get('s') || undefined;
175        } catch (e) {
176            return undefined;
177        }
178    }
179
180    // Extract the newsletter campaign tag (utm_campaign) from the landing URL.
181    // Owned-audience (#03): only meaningful for email-sourced visits, but we send
182    // it whenever present so the backend can join an email landing view to a
183    // specific newsletter campaign. Additive + optional.
184    function getUtmCampaign() {
185        try {
186            var params = new URLSearchParams(window.location.search);
187            var campaign = params.get('utm_campaign');
188            return campaign ? campaign.slice(0, 200) : undefined;
189        } catch (e) {
190            return undefined;
191        }
192    }
193
194    // Convert internal event shape to backend batch format
195    function serializeEvent(eventType, data) {
196        var normalizedType = eventType === 'pageview' ? 'pageView' : eventType;
197        var event = {
198            type: normalizedType,
199            url: data.url,
200            title: data.title,
201            referrer: data.referrer,
202            userAgent: data.userAgent,
203            sessionId: data.sessionId,
204            timestamp: Date.now()
205        };
206        if (config.isTest) {
207            event.isTest = true; // A6: Owner-Testmodus (gf_debug)
208        }
209
210        if (normalizedType === 'pageView') {
211            var searchQuery = getSearchQuery();
212            if (searchQuery) {
213                event.searchQuery = searchQuery;
214            }
215            var utmCampaign = getUtmCampaign();
216            if (utmCampaign) {
217                event.utmCampaign = utmCampaign;
218            }
219        }
220
221        if (normalizedType === 'click') {
222            event.clickedUrl = data.clickedUrl;
223            event.isAmazon = !!data.isAmazon;
224            event.amazonAsin = data.amazonAsin;
225            if (data.campaignExternalKey) {
226                event.campaignExternalKey = data.campaignExternalKey;
227                event.campaignBoxType = data.campaignBoxType || data.boxType || '';
228            }
229            if (data.boxType) {
230                event.boxType = data.boxType;
231                event.boxAsin = data.boxAsin || '';
232                event.boxTemplate = data.boxTemplate || '';
233            }
234            if (data.boxSource) event.boxSource = data.boxSource;
235            if (data.boxPosition) event.boxPosition = data.boxPosition;
236        }
237
238        if (normalizedType === 'update') {
239            event.scrollDepth = data.scrollDepth;
240            event.timeOnPage = data.timeOnPage;
241        }
242
243        return event;
244    }
245
246    // Send batched events
247    function sendBatch(events, useBeacon) {
248        var url = config.apiUrl.replace(/\/$/, '') + '/api/track';
249        var payload = JSON.stringify({
250            siteApiKey: config.apiKey,
251            events: events
252        });
253
254        // Use beacon on unload paths when available.
255        if (useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
256            try {
257                var blob = new Blob([payload], { type: 'text/plain' });
258                if (navigator.sendBeacon(url, blob)) {
259                    return;
260                }
261            } catch (e) {}
262        }
263
264        if (typeof fetch !== 'undefined') {
265            fetch(url, {
266                method: 'POST',
267                headers: { 'Content-Type': 'application/json' },
268                body: payload,
269                keepalive: true
270            }).catch(function() {});
271        }
272    }
273
274    // Queue event
275    function queueEvent(type, data) {
276        var eventData = Object.assign({}, getCommonData(), data);
277        queue.push(serializeEvent(type, eventData));
278
279        if (queue.length >= BATCH_SIZE) {
280            flushQueue(false);
281        }
282    }
283
284    // Flush queue
285    function flushQueue(useBeacon) {
286        if (queue.length === 0) return;
287
288        var events = queue.splice(0, queue.length);
289        sendBatch(events, useBeacon);
290    }
291
292    // Queue a final update event with scroll depth and time on page
293    function queueUpdateEvent() {
294        queueEvent('update', {
295            scrollDepth: maxScrollDepth,
296            timeOnPage: getActiveTime()
297        });
298    }
299
300    // Track pageview
301    function trackPageview() {
302        queueEvent('pageview', {});
303    }
304
305    var campaignGeoRequested = false;
306    var campaignImpressionKeys = {};
307    var campaignImpressionQueue = {};
308    var campaignImpressionTimer = null;
309    var campaignObserver = null;
310
311    function getCampaignBoxes() {
312        return Array.prototype.slice.call(document.querySelectorAll('.grimfaste-campaign-wrap[data-gf-campaign]'));
313    }
314
315    function getCampaignExternalKey(box) {
316        return box ? (box.getAttribute('data-gf-campaign') || '') : '';
317    }
318
319    function closestCampaignBox(node) {
320        while (node && node.getAttribute) {
321            if (node.classList && node.classList.contains('grimfaste-campaign-wrap')) {
322                return node;
323            }
324            node = node.parentElement;
325        }
326        return null;
327    }
328
329    function isCampaignSuppressed(box) {
330        return box && box.getAttribute('data-gf-suppressed') === '1';
331    }
332
333    function setCampaignSuppressed(box) {
334        if (!box) return;
335        box.setAttribute('data-gf-suppressed', '1');
336        box.hidden = true;
337    }
338
339    function isCampaignVisible(box) {
340        if (!box || box.hidden || isCampaignSuppressed(box)) return false;
341        if (!box.getClientRects || box.getClientRects().length === 0) return false;
342        var style = window.getComputedStyle ? window.getComputedStyle(box) : null;
343        return !style || (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0');
344    }
345
346    function applyCampaignDismissState(box) {
347        var key = getCampaignExternalKey(box);
348        if (!key) return;
349
350        if (getCookie(campaignCookieKey('gf_camp_', key))) {
351            setCampaignSuppressed(box);
352            return;
353        }
354
355        var cap = parseInt(box.getAttribute('data-gf-seen-cap') || '0', 10);
356        if (cap > 0) {
357            var seen = parseInt(getCookie(campaignCookieKey('gf_camp_seen_', key)) || '0', 10);
358            if (seen >= cap) {
359                setCampaignSuppressed(box);
360            }
361        }
362    }
363
364    function recordCampaignSeen(box) {
365        var key = getCampaignExternalKey(box);
366        if (!key) return;
367
368        var cap = parseInt(box.getAttribute('data-gf-seen-cap') || '0', 10);
369        if (cap <= 0) return;
370
371        var days = parseInt(box.getAttribute('data-gf-seen-days') || '7', 10);
372        var cookieName = campaignCookieKey('gf_camp_seen_', key);
373        var seen = parseInt(getCookie(cookieName) || '0', 10);
374        setCookie(cookieName, String(seen + 1), days > 0 ? days : 7);
375    }
376
377    function applyCampaignGeo(country) {
378        var boxes = getCampaignBoxes();
379        country = (country || '').toUpperCase();
380
381        for (var i = 0; i < boxes.length; i++) {
382            var box = boxes[i];
383            var geo = box.getAttribute('data-gf-geo');
384            if (!geo || isCampaignSuppressed(box)) continue;
385
386            var allowed = geo.toUpperCase().split(/\s*,\s*/);
387            if (country && allowed.indexOf(country) !== -1) {
388                box.hidden = false;
389            } else {
390                box.hidden = true;
391            }
392        }
393    }
394
395    function initCampaignGeo() {
396        var geoBoxes = document.querySelectorAll('.grimfaste-campaign-wrap[data-gf-geo]');
397        if (!geoBoxes.length) return;
398
399        var cachedCountry = getCookie('gf_geo_country');
400        if (cachedCountry) {
401            applyCampaignGeo(cachedCountry);
402            return;
403        }
404
405        if (campaignGeoRequested || typeof fetch === 'undefined') return;
406        campaignGeoRequested = true;
407
408        fetch(config.apiUrl.replace(/\/$/, '') + '/api/geo', {
409            method: 'GET',
410            credentials: 'omit'
411        }).then(function(response) {
412            return response && response.ok ? response.json() : null;
413        }).then(function(body) {
414            var country = body && body.country ? String(body.country).toUpperCase() : '';
415            if (!country) return;
416            setCookie('gf_geo_country', country);
417            applyCampaignGeo(country);
418        }).catch(function() {});
419    }
420
421    function queueCampaignImpression(box) {
422        var key = getCampaignExternalKey(box);
423        if (!key || campaignImpressionKeys[key] || !isCampaignVisible(box)) return;
424
425        campaignImpressionKeys[key] = true;
426        campaignImpressionQueue[key] = (campaignImpressionQueue[key] || 0) + 1;
427        recordCampaignSeen(box);
428
429        if (!campaignImpressionTimer) {
430            campaignImpressionTimer = setTimeout(function() {
431                flushCampaignImpressions(false);
432            }, 250);
433        }
434    }
435
436    function flushCampaignImpressions(useBeacon) {
437        if (campaignImpressionTimer) {
438            clearTimeout(campaignImpressionTimer);
439            campaignImpressionTimer = null;
440        }
441
442        var impressions = [];
443        for (var key in campaignImpressionQueue) {
444            if (Object.prototype.hasOwnProperty.call(campaignImpressionQueue, key)) {
445                impressions.push({ externalKey: key, count: campaignImpressionQueue[key] });
446            }
447        }
448
449        if (!impressions.length) return;
450        campaignImpressionQueue = {};
451
452        var url = config.apiUrl.replace(/\/$/, '') + '/api/campaigns/impression';
453        var payload = JSON.stringify({ impressions: impressions });
454
455        if (typeof fetch !== 'undefined') {
456            fetch(url, {
457                method: 'POST',
458                headers: {
459                    'Content-Type': 'application/json',
460                    'X-API-Key': config.apiKey
461                },
462                body: payload,
463                keepalive: true
464            }).catch(function() {});
465            return;
466        }
467
468        if (useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
469            try {
470                navigator.sendBeacon(url, new Blob([payload], { type: 'text/plain' }));
471            } catch (e) {}
472        }
473    }
474
475    function initCampaignImpressions() {
476        var boxes = getCampaignBoxes();
477        if (!boxes.length) return;
478
479        if (typeof IntersectionObserver === 'undefined') {
480            setTimeout(function() {
481                for (var i = 0; i < boxes.length; i++) {
482                    queueCampaignImpression(boxes[i]);
483                }
484            }, 500);
485            return;
486        }
487
488        campaignObserver = new IntersectionObserver(function(entries) {
489            for (var i = 0; i < entries.length; i++) {
490                var entry = entries[i];
491                if (entry.isIntersecting && entry.intersectionRatio > 0) {
492                    queueCampaignImpression(entry.target);
493                    if (campaignImpressionKeys[getCampaignExternalKey(entry.target)]) {
494                        campaignObserver.unobserve(entry.target);
495                    }
496                }
497            }
498        }, { threshold: 0.25 });
499
500        for (var j = 0; j < boxes.length; j++) {
501            campaignObserver.observe(boxes[j]);
502        }
503    }
504
505    function initCampaignBoxes() {
506        var boxes = getCampaignBoxes();
507        if (!boxes.length) return;
508
509        for (var i = 0; i < boxes.length; i++) {
510            applyCampaignDismissState(boxes[i]);
511        }
512
513        document.addEventListener('click', function(event) {
514            var target = event.target;
515            while (target && (!target.getAttribute || !target.getAttribute('data-gf-dismiss-campaign'))) {
516                target = target.parentElement;
517            }
518            if (!target || !target.getAttribute) return;
519
520            var key = target.getAttribute('data-gf-dismiss-campaign');
521            var box = target.closest ? target.closest('.grimfaste-campaign-wrap') : closestCampaignBox(target);
522            if (key) {
523                setCookie(campaignCookieKey('gf_camp_', key), '1', 30);
524            }
525            setCampaignSuppressed(box);
526        }, true);
527
528        initCampaignGeo();
529        initCampaignImpressions();
530    }
531
532    /* ── Wave-1 A1 (2026-07-27): product-box impressions ──────────────────
533     * Mirrors the campaign impression beacon for ALL Grimfaste product-box
534     * wrappers, keyed by template|source|position. This is the denominator
535     * that makes box CTR computable server-side (boxImpressionDaily). */
536    var boxImpressionQueue = {};
537    var boxImpressionTimer = null;
538    var boxObserver = null;
539
540    function getProductBoxes() {
541        return Array.prototype.slice.call(
542            document.querySelectorAll('.grimfaste-products-wrap')
543        );
544    }
545
546    function getBoxTemplate(box) {
547        var m = (box.className || '').match(/grimfaste-template-([a-z0-9_-]+)/);
548        return m ? m[1] : 'unknown';
549    }
550
551    function queueBoxImpression(box) {
552        if (!box || box.getAttribute('data-gf-imp') === '1') return;
553        box.setAttribute('data-gf-imp', '1');
554
555        var key = getBoxTemplate(box)
556            + '|' + (box.getAttribute('data-gf-source') || 'unknown')
557            + '|' + (box.getAttribute('data-gf-pos') || 'unknown');
558        boxImpressionQueue[key] = (boxImpressionQueue[key] || 0) + 1;
559
560        if (!boxImpressionTimer) {
561            boxImpressionTimer = setTimeout(function() {
562                flushBoxImpressions(false);
563            }, 250);
564        }
565    }
566
567    function flushBoxImpressions(useBeacon) {
568        if (boxImpressionTimer) {
569            clearTimeout(boxImpressionTimer);
570            boxImpressionTimer = null;
571        }
572
573        var impressions = [];
574        for (var key in boxImpressionQueue) {
575            if (Object.prototype.hasOwnProperty.call(boxImpressionQueue, key)) {
576                var parts = key.split('|');
577                impressions.push({
578                    template: parts[0],
579                    source: parts[1],
580                    position: parts[2],
581                    count: boxImpressionQueue[key]
582                });
583            }
584        }
585
586        if (!impressions.length) return;
587        boxImpressionQueue = {};
588
589        var url = config.apiUrl.replace(/\/$/, '') + '/api/boxes/impression';
590        var payload = JSON.stringify({ impressions: impressions });
591
592        if (typeof fetch !== 'undefined') {
593            fetch(url, {
594                method: 'POST',
595                headers: {
596                    'Content-Type': 'application/json',
597                    'X-API-Key': config.apiKey
598                },
599                body: payload,
600                keepalive: true
601            }).catch(function() {});
602            return;
603        }
604
605        if (useBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
606            try {
607                navigator.sendBeacon(url, new Blob([payload], { type: 'text/plain' }));
608            } catch (e) {}
609        }
610    }
611
612    function initBoxImpressions() {
613        var boxes = getProductBoxes();
614        if (!boxes.length) return;
615
616        if (typeof IntersectionObserver === 'undefined') {
617            setTimeout(function() {
618                for (var i = 0; i < boxes.length; i++) {
619                    queueBoxImpression(boxes[i]);
620                }
621            }, 500);
622            return;
623        }
624
625        boxObserver = new IntersectionObserver(function(entries) {
626            for (var i = 0; i < entries.length; i++) {
627                var entry = entries[i];
628                if (entry.isIntersecting && entry.intersectionRatio > 0) {
629                    queueBoxImpression(entry.target);
630                    boxObserver.unobserve(entry.target);
631                }
632            }
633        }, { threshold: 0.25 });
634
635        for (var j = 0; j < boxes.length; j++) {
636            boxObserver.observe(boxes[j]);
637        }
638    }
639
640    // Handle click events
641    function handleClick(event) {
642        var target = event.target;
643
644        // Find anchor element
645        while (target && target.tagName !== 'A') {
646            target = target.parentElement;
647        }
648
649        if (!target || !target.href) return;
650
651        var url = target.href;
652
653        // Only track external links
654        if (!isExternalLink(url)) return;
655
656        var isAmazon = isAmazonLink(url);
657        var eventData = {
658            articleUrl: window.location.href,
659            clickedUrl: url,
660            isAmazon: isAmazon
661        };
662
663        var boxType = target.getAttribute('data-grimfaste-box');
664        if (boxType) {
665            eventData.boxType = boxType;
666            eventData.boxAsin = target.getAttribute('data-grimfaste-asin') || '';
667            eventData.boxTemplate = target.getAttribute('data-grimfaste-template') || '';
668            eventData.campaignExternalKey = target.getAttribute('data-grimfaste-campaign') || '';
669            eventData.campaignBoxType = target.getAttribute('data-grimfaste-campaign-type') || boxType;
670            // Wave-1 A2: cascade source + placement live on the box wrapper.
671            var gfWrap = target.closest ? target.closest('.grimfaste-products-wrap') : null;
672            if (gfWrap) {
673                eventData.boxSource = gfWrap.getAttribute('data-gf-source') || '';
674                eventData.boxPosition = gfWrap.getAttribute('data-gf-pos') || '';
675            }
676
677            // Fire local tracking pixel for box clicks
678            if (config.localTrackUrl) {
679                var img = new Image();
680                img.src = config.localTrackUrl
681                    + '&nonce=' + encodeURIComponent(config.localTrackNonce || '')
682                    + '&block_type=' + encodeURIComponent(boxType)
683                    + '&asin=' + encodeURIComponent(eventData.boxAsin)
684                    + '&template=' + encodeURIComponent(eventData.boxTemplate)
685                    + '&post_id=' + encodeURIComponent(config.pageData.postId || '');
686            }
687        }
688
689        queueEvent('click', eventData);
690
691        // Flush immediately for Amazon links
692        if (isAmazon) {
693            flushQueue(true);
694        }
695    }
696
697    // Initialize tracker
698    function init() {
699        initCampaignBoxes();
700        initBoxImpressions();
701
702        // Track pageview
703        trackPageview();
704
705        // Listen for clicks
706        document.addEventListener('click', handleClick, true);
707
708        // Listen for scroll to track max depth
709        window.addEventListener('scroll', updateScrollDepth, { passive: true });
710        // Capture initial scroll depth (e.g. page loaded scrolled or short page)
711        updateScrollDepth();
712
713        // Periodic flush
714        setInterval(function() {
715            flushQueue(false);
716        }, BATCH_INTERVAL);
717
718        // Flush on visibility change + time tracking
719        document.addEventListener('visibilitychange', function() {
720            if (document.visibilityState === 'hidden') {
721                pauseActiveTime();
722                queueUpdateEvent();
723                flushQueue(true);
724                flushCampaignImpressions(true);
725                flushBoxImpressions(true);
726            } else {
727                resumeActiveTime();
728            }
729        });
730
731        // Flush on page unload
732        window.addEventListener('pagehide', function() {
733            pauseActiveTime();
734            queueUpdateEvent();
735            flushQueue(true);
736            flushCampaignImpressions(true);
737            flushBoxImpressions(true);
738        });
739
740        window.addEventListener('beforeunload', function() {
741            pauseActiveTime();
742            queueUpdateEvent();
743            flushQueue(true);
744            flushCampaignImpressions(true);
745            flushBoxImpressions(true);
746        });
747    }
748
749    // Start tracking
750    if (document.readyState === 'loading') {
751        document.addEventListener('DOMContentLoaded', init);
752    } else {
753        init();
754    }
755})();

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.