PageSourceSearch

https://www.brotherssupply.com/wp-content/plugins/hozio-studio/com…/callback/callback.js?ver=c39fbd3ab78e

js brotherssupply.com collected 2026-10-09 00:30:06 UTC 25,855 bytes, 526 lines download raw bytes

1/* ═══════════════════════════════════════════════════════════════════════════
2   INSTANT CALLBACK REQUEST (W-041) — progressive-enhancement behavior.
3
4   The <form> is server-rendered but JS-ENHANCED: recording the lead needs the
5   async submit to the REST route, so a JS-off visitor cannot submit — they get
6   a <noscript> direct-call fallback to the business phone instead (a callback
7   widget's honest no-JS path). callback.js adds the conversion layer — masked
8   input, inline validation, PARTIAL-lead capture, an async submit with a busy
9   CTA + AbortController timeout, and the success swap — all guarded so a load
10   failure degrades to the same direct-call fallback.
11
12   SETTINGS CONTRACT — data-attributes on the .hzo-callback root (written by the
13   [hzo_callback] shortcode or by hand):
14     data-hzo-callback-id               opaque instance id (server resolves the
15                                        webhook/SMS/form destination from it —
16                                        creds never reach the client)
17     data-hzo-callback-nonce            REST nonce (action hzo_callback_lead)
18     data-hzo-callback-submit           form | webhook | sms
19     data-hzo-callback-success          countdown | thankyou | tel
20     data-hzo-callback-partial          "true"|"false" — partial-lead capture
21     data-hzo-callback-source           campaign tag (UTM auto-merged)
22     data-hzo-callback-response-window  "5 min" — parsed to countdown seconds
23     data-hzo-callback-countdown        int seconds | "off" (overrides the parse)
24     data-hzo-callback-redirect         URL — navigate on success (optional)
25     data-hzo-callback-promise / -after-promise   copy-swap text for hourschange
26     data-hzo-callback-cta / -after-cta           CTA-label swap text
27     data-hzo-callback-success-title / -text      success copy ({phone}{name}
28                                                  {response-window} tokens)
29
30   REUSE (guarded, never a hard dep):
31     • hzo.hours (W-002) + `hzo:hourschange` — the ONE hours signal. On flip we
32       swap promise/cta TEXT only; we never re-parse hours.
33     • hzo.announce (W-021) — success/error strings are announced from the
34       persistent live region OUTSIDE the swapped form subtree (G30).
35
36   EVENT — emits `hzo:callback { status:'partial'|'complete', phone }` (bubbles
37   from .hzo-callback). Also mirrors to dataLayer (generate_lead / phone_call).
38
39   Idempotent (guarded by data-hzo-callback-ready); reduced-motion safe (the ring
40   stops to static remaining-time text; the spinner slows, never freezes — CSS).
41   IIFE, no build, no jQuery. Public surface: window.hzo.callback = { init, setup }.
42   ═══════════════════════════════════════════════════════════════════════════ */
43(function () {
44    'use strict';
45
46    window.hzo = window.hzo || {};
47
48    var REST_URL = '/wp-json/hozio-studio/v1/callback-lead';
49
50    /* ── i18n helpers (guarded; fall back to the literal) ── */
51    function __(s) {
52        return (window.wp && wp.i18n && wp.i18n.__) ? wp.i18n.__(s, 'hozio-studio') : s;
53    }
54    function sprintf() {
55        var args = arguments, s = String(args[0]);
56        if (window.wp && wp.i18n && wp.i18n.sprintf) { return wp.i18n.sprintf.apply(null, args); }
57        var i = 1;
58        return s.replace(/%(?:\d+\$)?[sd]/g, function () { return args[i++]; });
59    }
60    function announce(msg, level) {
61        if (window.hzo && typeof window.hzo.announce === 'function') {
62            window.hzo.announce(msg, level || 'polite');
63        }
64    }
65
66    /* ── Digits / phone helpers (US 10-digit; strips a leading country 1) ── */
67    function digitsOf(v) {
68        var d = String(v == null ? '' : v).replace(/\D/g, '');
69        if (d.length === 11 && d.charAt(0) === '1') { d = d.slice(1); }
70        return d;
71    }
72    function isValidPhone(v) { return digitsOf(v).length === 10; }
73
74    /* ── (000) 000-0000 mask applied live to the tel field ── */
75    function maskPhone(raw) {
76        var d = String(raw || '').replace(/\D/g, '');
77        if (d.length === 11 && d.charAt(0) === '1') { d = d.slice(1); }
78        d = d.slice(0, 10);
79        var out = '';
80        if (d.length > 0) { out = '(' + d.slice(0, 3); }
81        if (d.length >= 4) { out += ') ' + d.slice(3, 6); }
82        if (d.length >= 7) { out += '-' + d.slice(6, 10); }
83        return out;
84    }
85
86    /* ── "5 min" / "2 minutes" / "30 sec" / "90" → seconds (locale-agnostic) ── */
87    function parseSeconds(text) {
88        if (text == null) { return 0; }
89        var s = String(text).toLowerCase().replace(',', '.');
90        var m = s.match(/(\d+(?:\.\d+)?)/);
91        if (!m) { return 0; }
92        var n = parseFloat(m[1]);
93        if (isNaN(n)) { return 0; }
94        if (/hour|hr/.test(s)) { return Math.round(n * 3600); }
95        if (/\bsec|\bs\b/.test(s)) { return Math.round(n); }
96        // default unit is minutes ("5 min", bare "5")
97        return Math.round(n * 60);
98    }
99
100    /* ── Read the campaign source + merge UTM params ── */
101    function resolveSource(root) {
102        var src = root.getAttribute('data-hzo-callback-source') || '';
103        try {
104            var q = new URLSearchParams(window.location.search);
105            var utm = [];
106            ['utm_source', 'utm_medium', 'utm_campaign'].forEach(function (k) {
107                var v = q.get(k);
108                if (v) { utm.push(k + '=' + v); }
109            });
110            if (utm.length) { src = (src ? src + '|' : '') + utm.join('&'); }
111        } catch (e) { /* URLSearchParams absent — source stays as authored */ }
112        return src;
113    }
114
115    /* ── dataLayer mirror (measurement adapter) ── */
116    function pushDataLayer(obj) {
117        window.dataLayer = window.dataLayer || [];
118        window.dataLayer.push(obj);
119    }
120
121    /* ── Emit the public hzo:callback event ── */
122    function emit(root, status, phone) {
123        var detail = { status: status, phone: phone };
124        var ev;
125        try { ev = new CustomEvent('hzo:callback', { bubbles: true, detail: detail }); }
126        catch (e) { ev = document.createEvent('CustomEvent'); ev.initCustomEvent('hzo:callback', true, false, detail); }
127        root.dispatchEvent(ev);
128        if (status === 'complete') {
129            pushDataLayer({ event: 'generate_lead', lead_status: 'complete', source: 'callback' });
130        } else if (status === 'partial') {
131            pushDataLayer({ event: 'generate_lead', lead_status: 'partial', source: 'callback' });
132        }
133    }
134
135    /* ── Build the lead payload the REST route expects ── */
136    function payloadFor(root, status) {
137        var phoneEl = root.querySelector('input[name="phone"]');
138        var nameEl = root.querySelector('input[name="name"]');
139        var winEl = root.querySelector('[name="window"]:checked, input[type="hidden"][name="window"]');
140        // Carry the TCPA consent state to the server so the recorded lead holds
141        // evidence of consent (and the server can enforce consent-required).
142        var consentEl = root.querySelector('input[name="consent"]');
143        return {
144            id: root.getAttribute('data-hzo-callback-id') || '',
145            nonce: root.getAttribute('data-hzo-callback-nonce') || '',
146            phone: digitsOf(phoneEl ? phoneEl.value : ''),
147            name: nameEl ? nameEl.value : '',
148            window: winEl ? winEl.value : '',
149            consent: (consentEl && consentEl.checked) ? '1' : '',
150            source: resolveSource(root),
151            status: status,
152            token: root.__hzoToken,
153            ts: Date.now()
154        };
155    }
156
157    /* ── Fire a PARTIAL lead. Uses sendBeacon on pagehide (fire-and-forget), a
158         normal fetch on blur. De-duped client-side by the last number sent +
159         server-side by token. ── */
160    function sendPartial(root, useBeacon) {
161        if (root.getAttribute('data-hzo-callback-partial') === 'false') { return; }
162        var phoneEl = root.querySelector('input[name="phone"]');
163        if (!phoneEl || !isValidPhone(phoneEl.value)) { return; }
164        var digits = digitsOf(phoneEl.value);
165        // Only once per CHANGED valid number (blur path); pagehide always allowed
166        // to flush a final unsubmitted state.
167        if (!useBeacon && root.__hzoPartialSent === digits) { return; }
168        root.__hzoPartialSent = digits;
169
170        var body = JSON.stringify(payloadFor(root, 'partial'));
171
172        if (useBeacon && navigator.sendBeacon) {
173            try {
174                var blob = new Blob([body], { type: 'application/json' });
175                navigator.sendBeacon(REST_URL, blob);
176            } catch (e) { /* best-effort */ }
177        } else if (window.fetch) {
178            try {
179                fetch(REST_URL, {
180                    method: 'POST',
181                    headers: { 'Content-Type': 'application/json' },
182                    body: body,
183                    keepalive: true,
184                    credentials: 'same-origin'
185                })['catch'](function () { /* partial is best-effort; superseded by complete/pagehide */ });
186            } catch (e) { /* ignore */ }
187        }
188        emit(root, 'partial', digits);
189    }
190
191    /* ── Validation UI ── */
192    function showError(root, msg) {
193        var phoneEl = root.querySelector('input[name="phone"]');
194        var errEl = root.querySelector('.hzo-callback__error');
195        if (phoneEl) {
196            phoneEl.setAttribute('aria-invalid', 'true');
197            phoneEl.classList.add('hzo-callback__input--error');
198        }
199        if (errEl) { errEl.textContent = msg; errEl.hidden = false; }
200        announce(msg, 'assertive');
201    }
202    function clearError(root) {
203        var phoneEl = root.querySelector('input[name="phone"]');
204        var errEl = root.querySelector('.hzo-callback__error');
205        if (phoneEl) { phoneEl.removeAttribute('aria-invalid'); phoneEl.classList.remove('hzo-callback__input--error'); }
206        if (errEl) { errEl.hidden = true; errEl.textContent = ''; }
207    }
208
209    /* ── Loading lock (never `disabled` — aria-busy + a JS re-entrancy flag so
210         focus is never stranded, G22 / PRESERVE-2) ── */
211    function setLoading(root, on) {
212        var btn = root.querySelector('.hzo-callback__submit');
213        root.__hzoBusy = !!on;
214        if (!btn) { return; }
215        if (on) { btn.classList.add('is-loading'); btn.setAttribute('aria-busy', 'true'); }
216        else { btn.classList.remove('is-loading'); btn.removeAttribute('aria-busy'); }
217    }
218
219    /* ── Success swap: replace the form body with the role=status region,
220         move focus to its heading, announce from the persistent region. ── */
221    function showSuccess(root) {
222        var mode = root.getAttribute('data-hzo-callback-success') || 'countdown';
223        var region = root.querySelector('[data-hzo-callback-success-region]');
224        if (!region) { return; }
225
226        var phoneEl = root.querySelector('input[name="phone"]');
227        var nameEl = root.querySelector('input[name="name"]');
228        var phoneDisp = maskPhone(phoneEl ? phoneEl.value : '');
229        var name = nameEl ? String(nameEl.value || '').trim() : '';
230        var respWindow = root.getAttribute('data-hzo-callback-response-window') || '';
231
232        function interp(t) {
233            return String(t || '')
234                .replace(/\{phone\}/g, phoneDisp)
235                .replace(/\{name\}/g, name)
236                .replace(/\{response-window\}/g, respWindow);
237        }
238
239        var headingId = region.getAttribute('data-hzo-callback-heading-id') || '';
240        var title = interp(root.getAttribute('data-hzo-callback-success-title') || '') ||
241            (name ? sprintf(__('Thanks, %s — you’re on the list.'), name) : __('Thanks — you’re on the list.'));
242        var text = interp(root.getAttribute('data-hzo-callback-success-text') || '') ||
243            (respWindow
244                ? sprintf(__('We’ll call %1$s within %2$s.'), phoneDisp || __('you'), respWindow)
245                : sprintf(__('We’ll call %s shortly.'), phoneDisp || __('you')));
246
247        var html = '<h3 class="hzo-callback__success-title" id="' + headingId + '" tabindex="-1">' + escapeHtml(title) + '</h3>';
248        html += '<p class="hzo-callback__success-text">' + escapeHtml(text) + '</p>';
249
250        // Hide the input controls (keep them in the DOM for JS-off resubmit safety
251        // if success is reverted; we simply collapse them).
252        var controls = root.querySelectorAll('.hzo-callback__field, .hzo-callback__window, .hzo-callback__consent, .hzo-callback__submit, .hzo-callback__promise');
253        for (var i = 0; i < controls.length; i++) { controls[i].hidden = true; }
254
255        if (mode === 'countdown') {
256            html += '<div class="hzo-callback__countdown" data-hzo-callback-ring>' +
257                '<span class="hzo-callback__countdown-num" data-hzo-callback-num aria-hidden="true"></span>' +
258                '<span class="hzo-callback__countdown-text" data-hzo-callback-cd-text></span></div>';
259        }
260        if (mode === 'tel') {
261            var href = region.getAttribute('data-hzo-callback-tel-href') || '';
262            var disp = region.getAttribute('data-hzo-callback-tel-disp') || '';
263            if (href) {
264                html += '<a class="hzo-callback__success-tel" href="tel:' + escapeAttr(href) + '" data-hzo-callback-copy="' + escapeAttr(disp) + '">' +
265                    sprintf(__('Call us now: %s'), escapeHtml(disp)) + '</a>';
266            }
267        }
268
269        region.innerHTML = html;
270        region.hidden = false;
271
272        // Announce from the persistent region (G30) BEFORE moving focus.
273        announce(title + '. ' + text, 'polite');
274
275        // Move focus to the success heading (never <body>, PRESERVE-2).
276        var h = region.querySelector('#' + (window.CSS && CSS.escape ? CSS.escape(headingId) : headingId)) || region.querySelector('.hzo-callback__success-title');
277        if (h && typeof h.focus === 'function') { try { h.focus(); } catch (e) { } }
278
279        if (mode === 'countdown') { startCountdown(root, respWindow); }
280    }
281
282    /* ── Countdown: one rAF-throttled clock; the ticking number is NOT announced
283         each second (only the initial title/text was announced above). Reduced
284         motion → the ring is CSS-static; the number still updates as text. ── */
285    function startCountdown(root, respWindow) {
286        var explicit = root.getAttribute('data-hzo-callback-countdown');
287        if (explicit && explicit.toLowerCase() === 'off') { return; }
288        var total = (explicit && /^\d+$/.test(explicit)) ? parseInt(explicit, 10) : parseSeconds(respWindow);
289        if (!total || total <= 0) { return; }
290
291        var numEl = root.querySelector('[data-hzo-callback-num]');
292        var txtEl = root.querySelector('[data-hzo-callback-cd-text]');
293        var ring = root.querySelector('[data-hzo-callback-ring]');
294        var start = (window.performance && performance.now) ? performance.now() : Date.now();
295        var raf = window.requestAnimationFrame || function (fn) { return setTimeout(function () { fn(Date.now()); }, 250); };
296        var caf = window.cancelAnimationFrame || clearTimeout;
297
298        function fmt(sec) {
299            var m = Math.floor(sec / 60), s = sec % 60;
300            return m > 0 ? (m + ':' + (s < 10 ? '0' : '') + s) : String(s);
301        }
302        function frame(now) {
303            if (!root.__hzoAlive) { return; }
304            var elapsed = ((now || Date.now()) - start) / 1000;
305            var remain = Math.max(0, Math.ceil(total - elapsed));
306            if (numEl) { numEl.textContent = fmt(remain); }
307            if (txtEl) {
308                txtEl.textContent = remain > 0
309                    ? sprintf(__('Calling you back in %s…'), fmt(remain))
310                    : __('Calling you now…');
311            }
312            if (ring) {
313                ring.style.setProperty('--hzo-cb-progress', String(Math.min(1, elapsed / total)));
314            }
315            if (remain > 0) { root.__hzoRaf = raf(frame); }
316        }
317        root.__hzoAlive = true;
318        root.__hzoRaf = raf(frame);
319        // Clean up if the node is removed.
320        root.__hzoCancelCd = function () { root.__hzoAlive = false; if (root.__hzoRaf) { caf(root.__hzoRaf); } };
321    }
322
323    /* ── HTML escaping (we build innerHTML from user-entered values) ── */
324    function escapeHtml(s) {
325        return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
326            return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
327        });
328    }
329    function escapeAttr(s) { return escapeHtml(s).replace(/`/g, '&#96;'); }
330
331    /* ── The submit flow ── */
332    function onSubmit(root, e) {
333        if (e) { e.preventDefault(); }
334        if (root.__hzoBusy) { return; } // re-entrancy lock (blocks the double-tap → duplicate)
335
336        // Honeypot: a filled hidden field = a bot; silently no-op (pretend success
337        // is unnecessary — just drop).
338        var hp = root.querySelector('input[name="hzo_cb_company"]');
339        if (hp && hp.value) { return; }
340
341        var phoneEl = root.querySelector('input[name="phone"]');
342        if (!phoneEl || !isValidPhone(phoneEl.value)) {
343            showError(root, __('Enter a valid 10-digit phone number.'));
344            if (phoneEl && typeof phoneEl.focus === 'function') { phoneEl.focus(); }
345            return;
346        }
347        // consent-required: surface an inline error on click (never a dead button).
348        var consent = root.querySelector('[data-hzo-callback-consent-required]');
349        if (consent && !consent.checked) {
350            showError(root, __('Please agree to be contacted so we can call you back.'));
351            if (typeof consent.focus === 'function') { consent.focus(); }
352            return;
353        }
354        clearError(root);
355
356        setLoading(root, true); // <100ms, before any network
357
358        var digits = digitsOf(phoneEl.value);
359        var body = JSON.stringify(payloadFor(root, 'complete'));
360
361        // AbortController timeout (~10s) — no engine-wide timeout exists, so state one.
362        var controller = ('AbortController' in window) ? new AbortController() : null;
363        var timer = setTimeout(function () { if (controller) { controller.abort(); } }, 10000);
364
365        function done(ok, offline) {
366            clearTimeout(timer);
367            setLoading(root, false);
368            if (ok) {
369                root.__hzoSubmitted = true; // pagehide must not fire a redundant partial
370                emit(root, 'complete', digits);
371                var redirect = root.getAttribute('data-hzo-callback-redirect');
372                if (redirect) { window.location.href = redirect; return; }
373                showSuccess(root);
374            } else {
375                var msg = offline
376                    ? __('You appear to be offline — reconnect and tap Call me back again.')
377                    : __('Couldn’t send your request — check your connection and tap Call me back again.');
378                showError(root, msg);
379            }
380        }
381
382        if (!window.fetch) {
383            // No fetch → let the native form POST proceed (server records it).
384            clearTimeout(timer);
385            setLoading(root, false);
386            if (root.submit) { HTMLFormElement.prototype.submit.call(root); }
387            return;
388        }
389
390        if (navigator && navigator.onLine === false) { done(false, true); return; }
391
392        var opts = {
393            method: 'POST',
394            headers: { 'Content-Type': 'application/json' },
395            body: body,
396            credentials: 'same-origin'
397        };
398        if (controller) { opts.signal = controller.signal; }
399
400        fetch(REST_URL, opts).then(function (res) {
401            return res.ok ? res.json()['catch'](function () { return { ok: true }; }) : Promise.reject(res);
402        }).then(function () {
403            done(true);
404        })['catch'](function () {
405            // Reconcile on every catch (G1): release lock, preserve values, show a
406            // recoverable inline error; distinguish offline from server error.
407            done(false, (navigator && navigator.onLine === false));
408        });
409    }
410
411    /* ── hzo:hourschange consumer — swap promise/cta TEXT only (no re-parse) ── */
412    function reflectHours(root) {
413        if (!(window.hzo && window.hzo.hours)) { return; }
414        var after;
415        try { after = window.hzo.hours.state() === 'closed'; } catch (e) { return; }
416
417        var promiseEl = root.querySelector('[data-hzo-callback-promise-el]');
418        var ctaEl = root.querySelector('[data-hzo-callback-cta-el] .hzo-callback__submit-label') ||
419            root.querySelector('.hzo-callback__submit-label');
420
421        var promise = root.getAttribute('data-hzo-callback-promise') || '';
422        var afterPromise = root.getAttribute('data-hzo-callback-after-promise') || '';
423        var cta = root.getAttribute('data-hzo-callback-cta') || '';
424        var afterCta = root.getAttribute('data-hzo-callback-after-cta') || '';
425
426        root.classList.toggle('is-after-hours', after);
427        if (promiseEl && (after ? afterPromise : promise)) {
428            promiseEl.textContent = after && afterPromise ? afterPromise : promise;
429        }
430        if (ctaEl && (after ? afterCta : cta)) {
431            ctaEl.textContent = after && afterCta ? afterCta : cta;
432        }
433    }
434    function reflectHoursAll() {
435        var roots = document.querySelectorAll('[data-hzo-callback]');
436        for (var i = 0; i < roots.length; i++) { reflectHours(roots[i]); }
437    }
438
439    /* ── Per-instance wiring ── */
440    function setup(root) {
441        if (!root || root.getAttribute('data-hzo-callback-ready') === 'true') { return; }
442        root.setAttribute('data-hzo-callback-ready', 'true');
443        root.__hzoToken = 'cb-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
444
445        var phoneEl = root.querySelector('input[name="phone"]');
446        if (phoneEl) {
447            phoneEl.addEventListener('input', function () {
448                var pos = phoneEl.selectionStart, before = phoneEl.value;
449                phoneEl.value = maskPhone(phoneEl.value);
450                // keep caret sane on append
451                if (pos === before.length) { try { phoneEl.setSelectionRange(phoneEl.value.length, phoneEl.value.length); } catch (e) { } }
452                if (isValidPhone(phoneEl.value)) { clearError(root); }
453            });
454            phoneEl.addEventListener('blur', function () {
455                if (isValidPhone(phoneEl.value)) { sendPartial(root, false); }
456            });
457        }
458
459        root.addEventListener('submit', function (e) { onSubmit(root, e); });
460
461        // Initial hours reflect (server already stamped is-after-hours; this
462        // reconciles against the live client model if present).
463        reflectHours(root);
464    }
465
466    function init(scope) {
467        var roots = (scope || document).querySelectorAll('[data-hzo-callback]:not([data-hzo-callback-ready="true"])');
468        for (var i = 0; i < roots.length; i++) { setup(roots[i]); }
469    }
470
471    /* ── Page-level listeners (bound once) ── */
472    if (!window.hzo.__callbackBound) {
473        window.hzo.__callbackBound = true;
474
475        // Hours flip → swap copy on every instance.
476        document.addEventListener('hzo:hourschange', reflectHoursAll);
477
478        // Abandon capture: flush a partial for any unsubmitted, valid instance.
479        function flushPartials() {
480            var roots = document.querySelectorAll('[data-hzo-callback][data-hzo-callback-ready="true"]');
481            for (var i = 0; i < roots.length; i++) {
482                var r = roots[i];
483                if (r.__hzoSubmitted) { continue; }
484                sendPartial(r, true);
485            }
486        }
487        window.addEventListener('pagehide', flushPartials);
488        document.addEventListener('visibilitychange', function () {
489            if (document.visibilityState === 'hidden') { flushPartials(); }
490        });
491
492        // success=tel "Call us now" — mirror the intent to dataLayer as
493        // phone_call, and (desktop, where tel: no-ops) offer click-to-copy (§13).
494        document.addEventListener('click', function (e) {
495            var a = e.target && e.target.closest ? e.target.closest('.hzo-callback__success-tel') : null;
496            if (!a) { return; }
497            pushDataLayer({
498                event: 'phone_call',
499                phone_number: (a.getAttribute('href') || '').replace(/^tel:/, ''),
500                source: 'callback'
501            });
502            var copy = a.getAttribute('data-hzo-callback-copy');
503            if (copy && navigator.clipboard && navigator.clipboard.writeText) {
504                navigator.clipboard.writeText(copy).then(function () {
505                    announce(sprintf(__('Phone number copied: %s'), copy), 'polite');
506                })['catch'](function () { /* clipboard blocked — the tel link still works */ });
507            }
508        });
509
510        // bfcache restore → clear any stuck busy lock (G58 analog).
511        window.addEventListener('pageshow', function (e) {
512            if (e.persisted) {
513                var roots = document.querySelectorAll('[data-hzo-callback]');
514                for (var i = 0; i < roots.length;
514 i++) { setLoading(roots[i], false); }
515            }
516        });
517    }
518
519    window.hzo.callback = { init: init, setup: setup };
520
521    if (document.readyState === 'loading') {
522        document.addEventListener('DOMContentLoaded', function () { init(); });
523    } else {
524        init();
525    }
526})();

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.