PageSourceSearch

https://www.theadviser.com.au/media/mod_kevelads/js/kevel.js?a9f118

js theadviser.com.au collected 2026-09-24 17:35:02 UTC 26,207 bytes, 714 lines download raw bytes

1(function () {
2    'use strict';
3
4    var EVENT_IDS = [800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814];
5    var EXCLUDE_USER_AGENTS = ['bot', 'uptimecrawler'];
6    var BREAKPOINT_DESKTOP = 1200;
7    var BREAKPOINT_TABLET = 720;
8    var HOST = 'delivery.momentummedia.com.au';
9    var DELIVERY_HOST_MAP = {
10        'e-10860.adzerk.net': HOST,
11        'static.adzerk.net': 's.zkcdn.net'
12    };
13    var IMAGE_PROXY_BASE = 'https://kevel.momentummedia.com.au/proxy/';
14    var SEGMENT_URL = '/index.php?option=com_ajax&plugin=kevelsegment&format=json';
15    var EVENT_INTERVALS = [5, 10, 15, 20, 30, 45, 60, 75, 90, 110, 130, 150, 180, 210, 240];
16
17    var zoneCounter = 1;
18    var client = null;
19    var debugStore = [];
20
21    function getBreakpointLabel() {
22        var w = window.innerWidth;
23        if (w >= BREAKPOINT_DESKTOP) return 'desktop';
24        if (w >= BREAKPOINT_TABLET) return 'tablet';
25        return 'mobile';
26    }
27
28    function extractDecisions(response, divIds, count) {
29        var result = [];
30        if (!response || !response.decisions) return result;
31        var decisions = response.decisions;
32
33        if (divIds) {
34            Object.keys(decisions).forEach(function (key) {
35                if (decisions[key] && decisions[key][0]) {
36                    result.push(decisions[key][0]);
37                }
38            });
39        } else if (count > 1) {
40            var list = decisions.div0;
41            if (Array.isArray(list)) {
42                result = list;
43            }
44        } else {
45            if (decisions.div0 && decisions.div0[0]) {
46                result.push(decisions.div0[0]);
47            }
48        }
49        return result;
50    }
51
52    // -- Bot guard --
53    function isBot() {
54        var ua = navigator.userAgent.toLowerCase();
55        return EXCLUDE_USER_AGENTS.some(function (p) { return ua.indexOf(p) !== -1; });
56    }
57
58    // -- User key (from utm_emailID URL param → localStorage) --
59    function initUserKey() {
60        var params = new URLSearchParams(window.location.search);
61        var emailId = params.get('utm_emailID') || '';
62        if (emailId) {
63            localStorage.setItem('kevel_user_key', emailId);
64        }
65        return emailId || localStorage.getItem('kevel_user_key') || '';
66    }
67
68    function getUserKey() {
69        return localStorage.getItem('kevel_user_key') || '';
70    }
71
72    // -- Segments (fetched only when kevel_user_key exists, stored in localStorage) --
73    function initSegments(userKey) {
74        if (!userKey) return;
75
76        var payload = { name: 'user', email: userKey };
77        fetch(SEGMENT_URL, {
78            method: 'POST',
79            body: JSON.stringify(payload)
80        })
81        .then(function (res) { return res.json(); })
82        .then(function (data) {
83            if (Array.isArray(data) && data.length > 0) {
84                localStorage.setItem('kevel-segment', data.join(','));
85            }
86        })
87        .catch(function () {});
88    }
89
90    // -- Breakpoint detection --
91    function getAdTypes(element) {
92        var w = window.innerWidth;
93        if (w >= BREAKPOINT_DESKTOP) return parseIntArray(element.dataset.kevelDesktopTypes);
94        if (w >= BREAKPOINT_TABLET)  return parseIntArray(element.dataset.kevelTabletTypes);
95        return parseIntArray(element.dataset.kevelMobileTypes);
96    }
97
98    function parseIntArray(str) {
99        if (!str) return [];
100        return str.split(',').map(function (s) { return parseInt(s, 10); });
101    }
102
103    // -- SDK client (singleton) --
104    function getClient(networkId, siteId) {
105        if (!client) {
106            client = new AdzerkDecisionSdk.Client({
107                networkId: networkId,
108                siteId: siteId,
109                host: HOST
110            });
111        }
112        return client;
113    }
114
115    // -- Build Decision request --
116    function buildRequest(element) {
117        var dataset = element.dataset;
118        var adTypes = getAdTypes(element);
119        var zoneIds = parseIntArray(dataset.kevelZoneIds);
120        var count = parseInt(dataset.kevelCount, 10) || 1;
121        var keyword = dataset.kevelKeyword || '';
122        var tags = dataset.kevelTags || '';
123
124        var keywordsArray = [];
125        var segment = localStorage.getItem('kevel-segment');
126        if (segment) {
127            keywordsArray = segment.split(',');
128        }
129        if (keyword) {
130            keywordsArray.push(keyword);
131        }
132
133        var divIds = dataset.kevelDivIds;
134        var placements;
135        if (divIds) {
136            var ids = divIds.split(',');
137            placements = ids.map(function (id, i) {
138                return {
139                    divName: '#' + id.trim(),
140                    adTypes: adTypes,
141                    zoneIds: [zoneIds[i % zoneIds.length]],
142                    properties: { url: location.pathname, tags: tags },
143                    eventIds: EVENT_IDS
144                };
145            });
146        } else {
147            placements = [{
148                adTypes: adTypes,
149                zoneIds: zoneIds,
150                count: count,
151                properties: { url: location.pathname, tags: tags },
152                eventIds: EVENT_IDS
153            }];
154        }
155
156        return {
157            placements: placements,
158            enableBotFiltering: true,
159            user: { key: getUserKey() },
160            keywords: keywordsArray,
161            ip: window.CLIENT_IP || null
162        };
163    }
164
165    // -- URL rewriting (branded domains) --
166    function rewriteUrls(html) {
167        if (!html) return html;
168        Object.keys(DELIVERY_HOST_MAP).forEach(function (from) {
169            html = html.split(from).join(DELIVERY_HOST_MAP[from]);
170        });
171        return html;
172    }
173
174    // -- Proxy image URL --
175    function proxyImageUrl(imgUrl) {
176        var parts = imgUrl.split('/Advertisers/');
177        if (parts.length < 2) return imgUrl;
178        var fileNameParts = parts[1].split('.');
179        return IMAGE_PROXY_BASE + '?id=' + fileNameParts[0] + '&format=' + fileNameParts[1];
180    }
181
182    // -- Render single decision --
183    function renderDecision(decision, targetEl) {
184        var content = decision.contents[0];
185        var impressionPixel = '<img height="0" width="0" src="' + decision.impressionUrl
186            + '" style="position:absolute;display:none;">';
187
188        if (content.type === 'raw' || content.type === 'html') {
189            var frameId = 'kevel-frame-' + decision.flightId + '-' + zoneCounter;
190            var w = parseInt(content.data.width, 10);
191            var h = parseInt(content.data.height, 10);
192            targetEl.innerHTML = rewriteUrls(
193                '<iframe id="' + frameId + '" frameborder="0" scrolling="no" width="'
194                + w + '" height="' + h + '"></iframe>' + impressionPixel
195            );
196            var doc = document.getElementById(frameId).contentWindow.document;
197            doc.open();
198            doc.write(rewriteUrls('<body style="margin:0;">' + content.body + '</body>'));
199            doc.close();
200        } else {
201            var body = content.body || '';
202            if (content.data && content.data.imageUrl) {
203                body = body.split(content.data.imageUrl).join(proxyImageUrl(content.data.imageUrl));
204            }
205            targetEl.innerHTML = rewriteUrls(body) + impressionPixel;
206        }
207
208        trackViewability(targetEl, decision.events);
209        zoneCounter++;
210    }
211
212    // -- Viewability tracking (IntersectionObserver + setTimeout) --
213    function trackViewability(element, events) {
214        if (!events || events.length === 0) return;
215
216        var eventIndex = 0;
217        var observer = new IntersectionObserver(function (entries) {
218            if (!entries[0].isIntersecting) return;
219
220            EVENT_INTERVALS.forEach(function (secs, i) {
221                if (i >= events.length) return;
222                setTimeout(function () {
223                    if (eventIndex <= i) {
224                        firePixel(rewriteUrls(events[i].url), element);
225                        eventIndex = i + 1;
226                    }
227                }, secs * 1000);
228            });
229
230            observer.disconnect();
231        }, { threshold: 0 });
232
233        observer.observe(element);
234    }
235
236    function firePixel(url, container) {
237        var img = document.createElement('img');
238        img.src = url;
239        img.style.display = 'none';
240        img.style.position = 'absolute';
241        img.width = 0;
242        img.height = 0;
243        container.appendChild(img);
244    }
245
246    // -- Multi-creative handler (promoted content) --
247    function renderMultiCreative(decisions, targetEl) {
248        var html = '';
249        decisions.forEach(function (decision) {
250            var content = decision.contents[0];
251            var impressionPixel = '<img height="0" width="0" src="' + decision.impressionUrl
252                + '" style="position:absolute;display:none;">';
253            if (content.type === 'html') {
254                html += '<div class="momentum-promotedcontent-item">'
255                    + rewriteUrls(content.body) + impressionPixel + '</div>';
256            }
257        });
258        targetEl.innerHTML = html;
259    }
260
261    // -- Slot event dispatch --
262    function dispatchSlotEvent(element, name, detail) {
263        element.dispatchEvent(new CustomEvent(name, {
264            bubbles: true,
265            detail: detail
266        }));
267    }
268
269    // -- Placeholder cleanup --
270    function clearPlaceholder(element) {
271        var ph = element.querySelector('.kevel-ph');
272        if (ph) ph.parentNode.removeChild(ph);
273    }
274
275    // -- Process a single kevel-zone element --
276    function processElement(element) {
277        var dataset = element.dataset;
278        var networkId = parseInt(dataset.kevelNetworkId, 10);
279        var siteId = parseInt(dataset.kevelSiteId, 10);
280        var count = parseInt(dataset.kevelCount, 10) || 1;
281        var divIds = dataset.kevelDivIds;
282
283        var adTypes = getAdTypes(element);
284        if (adTypes.length === 0) {
285            clearPlaceholder(element);
286            element.classList.add('hidden');
287            return;
288        }
289
290        var adzerk = getClient(networkId, siteId);
291        var request = buildRequest(element);
292
293        var entry = {
294            elementId: element.id,
295            classes: element.className,
296            timestamp: Date.now(),
297            breakpoint: getBreakpointLabel(),
298            request: request,
299            response: null,
300            decisions: [],
301            status: 'pending',
302            error: null
303        };
304        debugStore.push(entry);
305
306        adzerk.decisions.get(request).then(function (response) {
307            entry.response = response;
308            entry.decisions = extractDecisions(response, divIds, count);
309            entry.status = entry.decisions.length > 0 ? 'rendered' : 'empty';
310
311            if (divIds) {
312                var decisions = response.decisions;
313                
314                Object.keys(decisions).forEach(function (key) {
315                    var newKey = key;   
316                    var targetEl = document.querySelector(key);
317
318                    if (/-variant-\d+$/.test(key)) {
319                        newKey = key.replace(/-variant-\d+$/, '');
320                    }
321
322                    if (decisions[key] && decisions[key][0] && (!decisions[newKey] || !decisions[newKey][0])) {
323                        targetEl = document.querySelector(newKey);
324                        decisions[newKey] = decisions[key];
325                    }
326
327                    
328                    if (targetEl && decisions[key] && decisions[key][0]) {
329                        renderDecision(decisions[key][0], targetEl);
330                    } else if (targetEl) {
331                        clearPlaceholder(targetEl);
332                    }
333                });
334                clearPlaceholder(element);
335            } else if (count > 1) {
336                var list = response.decisions.div0;
337                if (list && list.length > 0) {
338                    renderMultiCreative(list, element);
339                } else {
340                    clearPlaceholder(element);
341                    element.classList.add('hidden');
342                }
343            } else {
344                var decision = response.decisions.div0 && response.decisions.div0[0];
345                if (decision) {
346                    renderDecision(decision, element);
347                } else {
348                    clearPlaceholder(element);
349                    element.classList.add('hidden');
350                }
351            }
352
353            if (entry.status === 'rendered') {
354                dispatchSlotEvent(element, 'kevel:loaded', {
355                    status: 'rendered',
356                    decisions: entry.decisions,
357                    response: response,
358                    breakpoint: entry.breakpoint
359                });
360            } else {
361                dispatchSlotEvent(element, 'kevel:empty', {
362                    status: 'empty',
363                    breakpoint: entry.breakpoint
364                });
365            }
366
367        }).catch(function (err) {
368            entry.status = 'error';
369            entry.error = err.message || String(err);
370            clearPlaceholder(element);
371            element.classList.add('hidden');
372            dispatchSlotEvent(element, 'kevel:error', {
373                status: 'error',
374                error: entry.error,
375                breakpoint: entry.breakpoint
376            });
377        });
378    }
379
380    // -- Debug panel --
381    var PANEL_ID = 'kevel-debug-panel';
382    var DEBUG_OUTLINE_CLASS = 'kevel-debug-outline';
383
384    var panelStyles = {
385        position: 'fixed',
386        bottom: '12px',
387        right: '12px',
388        width: '420px',
389        maxHeight: '80vh',
390        overflow: 'auto',
391        background: '#1e1e2e',
392        color: '#cdd6f4',
393        fontFamily: 'monospace',
394        fontSize: '12px',
395        border: '1px solid #45475a',
396        borderRadius: '8px',
397        zIndex: '999999',
398        boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
399        lineHeight: '1.5'
400    };
401
402    function applyStyles(el, styles) {
403        Object.keys(styles).forEach(function (k) { el.style[k] = styles[k]; });
404    }
405
406    function el(tag, styles, text) {
407        var node = document.createElement(tag);
408        if (styles) applyStyles(node, styles);
409        if (text !== undefined) node.textContent = text;
410        return node;
411    }
412
413    function statusColor(status) {
414        if (status === 'rendered') return '#a6e3a1';
415        if (status === 'pending') return '#f9e2af';
416        return '#f38ba8';
417    }
418
419    function statusIcon(status) {
420        if (status === 'rendered') return '\u2713';
421        if (status === 'pending') return '\u25CB';
422        return '\u2717';
423    }
424
425    function slotLabel(entry) {
426        var name = entry.elementId;
427        return name;
428    }
429
430    function truncate(str, max) {
431        if (!str) return '-';
432        return str.length > max ? str.substring(0, max) + '...' : str;
433    }
434
435    function buildHeader(panel, onClose) {
436        var header = el('div', {
437            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
438            padding: '10px 14px', borderBottom: '1px solid #45475a', background: '#181825'
439        });
440        header.appendChild(el('span', { fontWeight: 'bold', fontSize: '14px', color: '#89b4fa' }, 'Kevel Debug'));
441        var closeBtn = el('span', {
442            cursor: 'pointer', fontSize: '16px', color: '#6c7086', padding: '0 4px'
443        }, '\u2715');
444        closeBtn.addEventListener('click', onClose);
445        header.appendChild(closeBtn);
446        panel.appendChild(header);
447    }
448
449    function buildSummary(panel) {
450        var wrap = el('div', { padding: '8px 14px', borderBottom: '1px solid #313244', fontSize: '11px', color: '#a6adc8' });
451        var lines = [
452            'Page: ' + location.pathname,
453            'Slots: ' + debugStore.length + '  |  Breakpoint: ' + getBreakp
453ointLabel(),
454            'User key: ' + (getUserKey() || '(none)') + '  |  IP: ' + (window.CLIENT_IP || '(none)'),
455            'Segments: ' + (localStorage.getItem('kevel-segment') || '(none)')
456        ];
457        lines.forEach(function (line) {
458            wrap.appendChild(el('div', { padding: '1px 0' }, line));
459        });
460        panel.appendChild(wrap);
461    }
462
463    function buildSlotList(panel) {
464        var list = el('div', { padding: '6px 0' });
465
466        if (debugStore.length === 0) {
467            list.appendChild(el('div', { padding: '12px 14px', color: '#6c7086', fontStyle: 'italic' }, 'No ad slots processed yet.'));
468            panel.appendChild(list);
469            return;
470        }
471
472        debugStore.forEach(function (entry, idx) {
473            var row = el('div', {
474                padding: '6px 14px', cursor: 'pointer', display: 'flex',
475                alignItems: 'center', gap: '8px', borderBottom: '1px solid #313244'
476            });
477
478            row.addEventListener('mouseenter', function () { row.style.background = '#313244'; });
479            row.addEventListener('mouseleave', function () { row.style.background = 'transparent'; });
480
481            var icon = el('span', { color: statusColor(entry.status), flexShrink: '0' }, statusIcon(entry.status));
482            var name = el('span', { flex: '1', color: '#cdd6f4' }, slotLabel(entry));
483            var badge = el('span', { color: statusColor(entry.status), fontSize: '11px' }, entry.status);
484            row.appendChild(icon);
485            row.appendChild(name);
486            row.appendChild(badge);
487
488            if (entry.decisions.length > 0) {
489                var fl = el('span', { color: '#6c7086', fontSize: '11px', marginLeft: '6px' },
490                    'FL:' + entry.decisions[0].flightId);
491                row.appendChild(fl);
492            }
493
494            row.addEventListener('click', function () {
495                renderDetailView(panel, entry, idx);
496            });
497
498            list.appendChild(row);
499        });
500
501        panel.appendChild(list);
502    }
503
504    function renderDetailView(panel, entry, idx) {
505        while (panel.children.length > 1) {
506            panel.removeChild(panel.lastChild);
507        }
508
509        var backRow = el('div', {
510            padding: '8px 14px', cursor: 'pointer', borderBottom: '1px solid #45475a',
511            color: '#89b4fa', fontSize: '12px'
512        }, '\u25C2 Back');
513        backRow.addEventListener('click', function () {
514            while (panel.children.length > 1) {
515                panel.removeChild(panel.lastChild);
516            }
517            buildSummary(panel);
518            buildSlotList(panel);
519        });
520        panel.appendChild(backRow);
521
522        var body = el('div', { padding: '10px 14px' });
523
524        body.appendChild(sectionTitle(slotLabel(entry) + '  (#' + entry.elementId + ')'));
525        body.appendChild(kvLine('Status', entry.status, statusColor(entry.status)));
526        body.appendChild(kvLine('Time', new Date(entry.timestamp).toLocaleTimeString()));
527        if (entry.error) {
528            body.appendChild(kvLine('Error', entry.error, '#f38ba8'));
529        }
530
531        if (entry.decisions.length > 0) {
532            entry.decisions.forEach(function (d, di) {
533                body.appendChild(sectionTitle('Decision' + (entry.decisions.length > 1 ? ' ' + (di + 1) : '')));
534                body.appendChild(kvLine('Flight ID', d.flightId));
535                body.appendChild(kvLine('Campaign ID', d.campaignId));
536                body.appendChild(kvLine('Ad ID', d.adId));
537                body.appendChild(kvLine('Creative ID', d.creativeId));
538                if (d.contents && d.contents[0]) {
539                    var c = d.contents[0];
540                    body.appendChild(kvLine('Content type', c.type));
541                    if (c.data) {
542                        if (c.data.width && c.data.height) {
543                            body.appendChild(kvLine('Dimensions', c.data.width + ' \u00D7 ' + c.data.height));
544                        }
545                        if (c.data.imageUrl) {
546                            body.appendChild(kvLine('Image URL', truncate(c.data.imageUrl, 50)));
547                        }
548                    }
549                }
550                body.appendChild(kvLine('Click URL', truncate(d.clickUrl, 50)));
551                body.appendChild(kvLine('Impression URL', truncate(d.impressionUrl, 50)));
552
553                if (d.events && d.events.length > 0) {
554                    body.appendChild(sectionTitle('Events (' + d.events.length + ')'));
555                    d.events.forEach(function (evt, ei) {
556                        var secs = EVENT_INTERVALS[ei] !== undefined ? EVENT_INTERVALS[ei] + 's' : ei;
557                        body.appendChild(kvLine(secs, truncate(evt.url, 50)));
558                    });
559                }
560            });
561        }
562
563        var p = entry.request.placements[0];
564        body.appendChild(sectionTitle('Request'));
565        body.appendChild(kvLine('Zone IDs', p.zoneIds ? p.zoneIds.join(', ') : '-'));
566        body.appendChild(kvLine('Ad types', p.adTypes ? p.adTypes.join(', ') : '-'));
567        body.appendChild(kvLine('Keywords', entry.request.keywords.length > 0 ? entry.request.keywords.join(', ') : '-'));
568        body.appendChild(kvLine('Tags', (p.properties && p.properties.tags) || '-'));
569        body.appendChild(kvLine('Count', p.count || 1));
570        body.appendChild(kvLine('IP', entry.request.ip || '-'));
571        body.appendChild(kvLine('User key', entry.request.user.key || '-'));
572
573        body.appendChild(sectionTitle('Raw'));
574        body.appendChild(jsonToggle('Request JSON', entry.request));
575        body.appendChild(jsonToggle('Response JSON', entry.response));
576
577        panel.appendChild(body);
578    }
579
580    function sectionTitle(text) {
581        return el('div', {
582            padding: '8px 0 4px', marginTop: '6px', borderTop: '1px solid #313244',
583            color: '#89b4fa', fontWeight: 'bold', fontSize: '11px', textTransform: 'uppercase'
584        }, text);
585    }
586
587    function kvLine(label, value, valueColor) {
588        var row = el('div', { display: 'flex', padding: '1px 0', gap: '8px' });
589        row.appendChild(el('span', { color: '#6c7086', minWidth: '110px', flexShrink: '0' }, label));
590        row.appendChild(el('span', { color: valueColor || '#cdd6f4', wordBreak: 'break-all' },
591            value !== null && value !== undefined ? String(value) : '-'));
592        return row;
593    }
594
595    function jsonToggle(label, data) {
596        var wrap = el('div', { margin: '4px 0' });
597        var btn = el('span', {
598            cursor: 'pointer', color: '#89b4fa', textDecoration: 'underline', fontSize: '11px'
599        }, label);
600        var pre = el('pre', {
601            display: 'none', background: '#11111b', color: '#a6adc8', padding: '8px',
602            borderRadius: '4px', overflow: 'auto', maxHeight: '200px', margin: '4px 0 0',
603            fontSize: '11px', whiteSpace: 'pre-wrap', wordBreak: 'break-all'
604        });
605        try {
606            pre.textContent = JSON.stringify(data, null, 2);
607        } catch (e) {
608            pre.textContent = '(unable to serialize)';
609        }
610        btn.addEventListener('click', function () {
611            pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
612        });
613        wrap.appendChild(btn);
614        wrap.appendChild(pre);
615        return wrap;
616    }
617
618    function highlightSlots() {
619        debugStore.forEach(function (entry) {
620            var elem = document.getElementById(entry.elementId);
621            if (!elem) return;
622            elem.style.outline = '2px dashed ' + (entry.status === 'rendered' ? '#89b4fa' : '#f38ba8');
623            elem.style.outlineOffset = '2px';
624            elem.classList.add(DEBUG_OUTLINE_CLASS);
625        });
626    }
627
628    function removeHighlights() {
629        var elems = document.querySelectorAll('.' + DEBUG_OUTLINE_CLASS);
630        elems.forEach(function (elem) {
631            elem.style.outline = '';
632            elem.style.outlineOffset = '';
633            elem.classList.remove(DEBUG_OUTLINE_CLASS);
634        });
635    }
636
637    function renderDebugPanel() {
638        var panel = el('div');
639        panel.id = PANEL_ID;
640        applyStyles(panel, panelStyles);
641
642        buildHeader(panel, function () { destroyDebugPanel(); });
643        buildSummary(panel);
644        buildSlotList(panel);
645
646        document.body.appendChild(panel);
647        highlightSlots();
648    }
649
650    function destroyDebugPanel() {
651        var existing = document.getElementById(PANEL_ID);
652        if (existing) existing.parentNode.removeChild(existing);
653        removeHighlights();
654    }
655
656    window.kevelDebug = function () {
657        var existing = document.getElementById(PANEL_ID);
658        if (existing) {
659            destroyDebugPanel();
660        } else {
661            renderDebugPanel();
662        }
663    };
664
665    window.kevelDebug.log = function () {
666        var rows = [];
667        debugStore.forEach(function (e) {
668            if (e.decisions.length === 0) {
669                rows.push({
670                    element: e.elementId,
671                    status: e.status,
672                    decision: '-',
673                    flightId: '-',
674                    creativeId: '-',
675                    adId: '-',
676                    campaignId: '-',
677                    type: '-',
678                    breakpoint: e.breakpoint
679                });
680            } else {
681                e.decisions.forEach(function (d, i) {
682                    rows.push({
683                        element: e.elementId,
684                        status: e.status,
685                        decision: (i + 1) + '/' + e.decisions.length,
686                        flightId: d.flightId || '-',
687                        creativeId: d.creativeId || '-',
688                        adId: d.adId || '-',
689                        campaignId: d.campaignId || '-',
690                        type: d.contents && d.contents[0] ? d.contents[0].type : '-',
691                        breakpoint: e.breakpoint
692                    });
693                });
694            }
695        });
696        console.table(rows);
697    };
698
699    // -- Init --
700    function init() {
701        if (isBot()) return;
702
703        var userKey = initUserKey();
704        initSegments(userKey);
705
706        var elements = document.querySelectorAll('[data-kevel-zone-ids]');
707        elements.forEach(function (el) {
708            processElement(el);
709        });
710    }
711
712    init();
713
714})();

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.