PageSourceSearch

https://www.wildix.com/wp-content/plugins/pardot-form-generator/as…/js/pfg-form-steps.js?ver=0.5.7.1790175311

js wildix.com collected 2026-09-24 09:09:42 UTC 27,986 bytes, 699 lines download raw bytes

1(function () {
2    const FREE_DOMAINS = [
3        // US / International
4        'gmail.com','googlemail.com',
5        'yahoo.com','ymail.com','rocketmail.com',
6        'hotmail.com','outlook.com','live.com','msn.com',
7        'aol.com','aim.com','compuserve.com','netscape.net',
8        'icloud.com','me.com','mac.com',
9        'protonmail.com','tutanota.com','zoho.com','fastmail.com','mail.com',
10
11        // Germany / Austria / Switzerland
12        'gmx.de','gmx.net','gmx.com',
13        'web.de','freenet.de','online.de','arcor.de','t-online.de',
14
15        // France
16        'laposte.net','wanadoo.fr','orange.fr','sfr.fr','neuf.fr','free.fr',
17
18        // Italy
19        'libero.it','virgilio.it','tin.it','alice.it','tiscali.it',
20
21        // Spain
22        'hotmail.es','outlook.es','live.es','terra.es',
23
24        // UK & Ireland
25        'hotmail.co.uk','outlook.co.uk','live.co.uk',
26        'btinternet.com','talktalk.net','virginmedia.com','blueyonder.co.uk','sky.com',
27
28        // Netherlands / Belgium
29        'hotmail.nl','outlook.nl','live.nl',
30        'ziggo.nl','chello.nl','planet.nl','hetnet.nl','kpnmail.nl',
31        'skynet.be','hotmail.be',
32
33        // Nordics
34        'hotmail.se','outlook.se','live.se',
35        'telia.com','bredband.net','finland.fi','luukku.com',
36
37        // Eastern Europe
38        'seznam.cz','centrum.cz','volny.cz',
39        'onet.pl','wp.pl','o2.pl','interia.pl',
40
41        // Latin America
42        'hotmail.com.mx','outlook.com.mx','live.com.mx',
43        'hotmail.com.br','outlook.com.br','live.com.br','bol.com.br','uol.com.br',
44
45        // Russia
46        'mail.ru','bk.ru','inbox.ru','list.ru',
47        'yandex.ru','yandex.com','yandex.ua','yandex.kz','yandex.by','ya.ru',
48        'rambler.ru','lenta.ru','autorambler.ru','ro.ru','myrambler.ru',
49
50        // Ukraine
51        'ukr.net','i.ua','email.ua','meta.ua','online.ua','bigmir.net','voliacable.com'
52    ];
53
54    function debounce(fn, wait = 500) {
55        let t;
56        return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); };
57    }
58
59    // Single shared geo lookup for the whole page. Both the phone field's
60    // intl-tel-input.geoIpLookup and the Country <select> preselection consume
61    // this same promise, so the two UI elements can never disagree on which
62    // country the visitor is in. The REST response is `Cache-Control: private`
63    // so it can't be poisoned by a shared CDN edge cache.
64    // Resolves to the raw JSON payload `{country, country_name, reason}` or
65    // null if the lookup fails / network errors.
66    let pfgGeoPromise = null;
67    function pfgGeoFetch(url) {
68        if (!url) return Promise.resolve(null);
69        return fetch(url, { credentials: 'same-origin' })
70            .then(r => (r && r.ok) ? r.json() : null)
71            .catch(() => null);
72    }
73    function pfgGeoLookup() {
74        if (pfgGeoPromise) return pfgGeoPromise;
75        // Primary transport is admin-ajax (PFG_GEO.url); it stays reachable for
76        // anonymous visitors even when the REST API is locked down site-wide.
77        // If it yields nothing, fall back to the REST endpoint before giving up.
78        const ajaxUrl = (window.PFG_GEO && window.PFG_GEO.url) || '/wp-admin/admin-ajax.php?action=pfg_geo';
79        const restUrl = (window.PFG_GEO && window.PFG_GEO.restUrl) || '/wp-json/pfg/v1/geo';
80        pfgGeoPromise = pfgGeoFetch(ajaxUrl).then(function (j) {
81            if (j && j.country) return j;
82            // Only bother with the REST fallback if it's a different endpoint.
83            if (restUrl && restUrl !== ajaxUrl) {
84                return pfgGeoFetch(restUrl).then(function (j2) { return j2 || j; });
85            }
86            return j;
87        });
88        return pfgGeoPromise;
89    }
90
91    function initForm(form) {
92        if (form._pfgInitialized) return;
93        form._pfgInitialized = true;
94
95        const steps = Array.from(form.querySelectorAll('.pfg-step'));
96        const emailGlobal = form.querySelector('input[name="email"]');
97
98        // Email validity with business rule; show=true => native bubble
99        function validateEmailField(emailEl, { show = false } = {}) {
100            if (!emailEl) return true;
101            emailEl.setCustomValidity('');
102            let ok = emailEl.checkValidity();
103            if (ok && (emailEl.dataset.emailType || 'any').toLowerCase() === 'business') {
104                const domain = emailEl.value.split('@')[1]?.toLowerCase() || '';
105                if (FREE_DOMAINS.includes(domain)) {
106                    emailEl.setCustomValidity('Please use your business email, not a free provider.');
107                    ok = false;
108                }
109            }
110            if (!ok && show) emailEl.reportValidity();
111            return ok;
112        }
113
114        // Prevent auto bubbles on load/autofill. Show only after real interaction or when focused.
115        if (emailGlobal) {
116            let userTouched = false; // set by key/paste, not by autofill
117            const debouncedShow = debounce(() => {
118                // Show only if focused and user actually interacted
119                if (document.activeElement !== emailGlobal) return;
120                if (!userTouched) return;
121                if (!emailGlobal.value.trim()) return;
122                validateEmailField(emailGlobal, { show: true });
123            }, 500);
124
125            emailGlobal.addEventListener('keydown', () => { userTouched = true; });
126            emailGlobal.addEventListener('paste',   () => { userTouched = true; });
127
128            emailGlobal.addEventListener('input', () => {
129                // Debounced bubble only while focused and after user input
130                debouncedShow();
131            });
132
133            emailGlobal.addEventListener('blur', () => {
134                // On blur: show only if user interacted; do nothing for untouched/autofilled states
135                const required = emailGlobal.hasAttribute('required');
136                if (!required && !emailGlobal.value.trim()) return;
137                if (!userTouched) return;
138                validateEmailField(emailGlobal, { show: true });
139            });
140        }
141
142        const phoneInput = form.querySelector('input[name="phone"]');
143        let iti;
144
145        function validatePhoneField({ show = false } = {}) {
146            if (!phoneInput) return true;
147            if (!iti) {
148                if (show) phoneInput.reportValidity();
149                return phoneInput.checkValidity();
150            }
151            phoneInput.setCustomValidity('');
152            const value = phoneInput.value.trim();
153            const required = phoneInput.hasAttribute('required');
154            if (!value) {
155                if (required) {
156                    phoneInput.setCustomValidity('Please enter a valid phone number.');
157                    if (show) phoneInput.reportValidity();
158                    return false;
159                }
160                return true;
161            }
162            if (!iti.isValidNumber()) {
163                phoneInput.setCustomValidity('Please enter a valid phone number.');
164                if (show) phoneInput.reportValidity();
165                return false;
166            }
167            return true;
168        }
169
170        if (phoneInput && window.intlTelInput) {
171            iti = window.intlTelInput(phoneInput, {
172                initialCountry: "auto",
173                geoIpLookup(success) {
174                    // Goes through the shared pfgGeoLookup() so the phone flag
175                    // and the Country <select> resolve to the same country.
176                    pfgGeoLookup().then(function (j) {
177                        success((j && j.country) ? j.country : 'us');
178                    });
179                },
180                utilsScript: (window.PFG_ITI && window.PFG_ITI.utilsScript) || "",
181            });
182            let phoneTouched = false;
183            const debouncedPhoneShow = debounce(() => {
184                if (document.activeElement !== phoneInput) return;
185                if (!phoneTouched) return;
186                if (!phoneInput.value.trim()) return;
187                validatePhoneField({ show: true });
188            }, 500);
189            phoneInput.addEventListener('keydown', () => { phoneTouched = true; });
190            phoneInput.addEventListener('paste', () => { phoneTouched = true; });
191            phoneInput.addEventListener('input', () => {
192                phoneInput.setCustomValidity('');
193                debouncedPhoneShow();
194            });
195            phoneInput.addEventListener('blur', () => {
196                const required = phoneInput.hasAttribute('required');
197                if (!required && !phoneInput.value.trim()) return;
198                if (!phoneTouched) return;
199                validatePhoneField({ show: true });
200            });
201            phoneInput.addEventListener('countrychange', () => {
202                phoneInput.setCustomValidity('');
203                const stepEl = phoneInput.closest('.pfg-step');
204                if (stepEl) updateNextButton(stepEl);
205                debouncedPhoneShow();
206            });
207        }
208
209        // Client-side preselection for the Country <select>. The server now
210        // ships the dropdown with no preselected option (so page caches can't
211        // bake one visitor's geo into the HTML for everyone else). We hydrate
212        // the selection here using the same REST geo lookup that drives the
213        // phone flag, which guarantees both fields agree per visitor.
214        const countrySelects = Array.from(form.querySelectorAll('select[data-pfg-geo-country]'));
215        countrySelects.forEach(sel => {
216            // Respect any explicit server-side preselection (e.g. the
217            // `pfg/preselected_country` filter forcing a country on a
218            // locale-specific subdomain). If something is already selected,
219            // we don't touch it.
220            if (sel.value) return;
221            let userTouched = false;
222            sel.addEventListener('change', () => { userTouched = true; });
223            pfgGeoLookup().then(j => {
224                if (userTouched || sel.value) return;
225                const name = j && j.country_name;
226                if (!name) return;
227                const opt = Array.from(sel.options).find(o => o.value === name);
228                if (!opt) return;
229                sel.value = name;
230                // Setting .value programmatically doesn't fire `input`, so
231                // we dispatch it manually to re-run multi-step Next-button
232                // validation that listens on the form's input event.
233                sel.dispatchEvent(new Event('input', { bubbles: true }));
234            });
235        });
236
237        // Required-check for step; does not show bubbles
238        function stepRequiredValid(stepEl) {
239            const req = Array.from(stepEl.querySelectorAll('[required]'));
240            return req.every(i => {
241                if (i.type === 'checkbox' || i.type === 'radio') {
242                    const group = Array.from(stepEl.querySelectorAll(`[name="${i.name}"]`));
243                    return i.checked || group.some(x => x.checked);
244                }
245                return i.value.trim() !== '' && i.checkValidity();
246            });
247        }
248
249        // Enable/disable Next without showing bubbles
250        function updateNextButton(stepEl) {
251            if (stepEl.dataset.step == form.dataset.steps) return; // last step has no Next
252            let ok = stepRequiredValid(stepEl);
253            const emailInStep = stepEl.querySelector('input[type="email"], input[name="email"]');
254            if (emailInStep) {
255                if (emailInStep.value.trim()) {
256                    ok = ok && validateEmailField(emailInStep, { show: false });
257                } else if (emailInStep.hasAttribute('required')) {
258                    ok = false;
259                }
260            }
261            if (phoneInput && stepEl.contains(phoneInput)) {
262                ok = ok && validatePhoneField({ show: false });
263            }
264            const btn = stepEl.querySelector('.pfg-next');
265            if (btn) btn.disabled = !ok;
266        }
267
268        // Initialize Next button state if a multi-step form
269        if (steps.length) updateNextButton(steps[0]);
270
271        form.addEventListener('input', e => {
272            const stepEl = e.target.closest('.pfg-step');
273            if (stepEl) updateNextButton(stepEl);
274        });
275
276        // Step navigation with explicit bubble on invalid fields
277        let pushed = false;
278        form.addEventListener('click', e => {
279            if (e.target.matches('.pfg-next')) {
280                const cur = form.querySelector('.pfg-step.active') || steps[0]; // support single-step without .active
281                if (!cur) return;
282
283                if (!stepRequiredValid(cur)) {
284                    const bad = cur.querySelector(':invalid');
285                    if (bad) bad.reportValidity();
286                    return;
287                }
288
289                const emailInStep = cur.querySelector('input[type="email"], input[name="email"]');
290                if (emailInStep && !validateEmailField(emailInStep, { show: true })) return;
291
292                if (phoneInput && cur.contains(phoneInput) && !validatePhoneField({ show: true })) return;
293
294                const idx = steps.indexOf(cur);
295                if (steps[idx + 1]) {
296                    if (!pushed && (cur.classList.contains('pfg-step-1') || cur.dataset.step === '1')) {
297                        pushed = true;
298                        window.dataLayer = window.dataLayer || [];
299                        window.dataLayer.push({
300                            event: 'Form_step1Complete',
301                            formUrl: window.location.pathname || null,
302                            stepNumber: 1
303                        });
304                    }
305                    cur.classList.remove('active');
306                    steps[idx + 1].classList.add('active');
307                    updateNextButton(steps[idx + 1]);
308                }
309            }
310
311            if (e.target.matches('.pfg-prev')) {
312                const cur = form.querySelector('.pfg-step.active');
313                const idx = steps.indexOf(cur);
314                if (steps[idx - 1]) {
315                    cur.classList.remove('active');
316                    steps[idx - 1].classList.add('active');
317                    updateNextButton(steps[idx - 1]);
318                }
319            }
320        });
321
322        // Submit handling with explicit bubbles only on action
323        let isSubmitting = false;
324        const submitBtn = form.querySelector('.pfg-submit-btn, button[type="submit"], input[type="submit"]');
325
326        function goToStepForElement(el) {
327            const step = el?.closest?.('.pfg-step');
328            if (!step) return;
329            const current = form.querySelector('.pfg-step.active');
330            if (current && current !== step) current.classList.remove('active');
331            step.classList.add('active');
332            updateNextButton(step);
333        }
334
335        form.addEventListener('submit', e => {
336            if (isSubmitting) {
337                e.preventDefault();
338                return;
339            }
340
341            if (phoneInput && !validatePhoneField({ show: false })) {
342                e.preventDefault();
343                goToStepForElement(phoneInput);
344                validatePhoneField({ show: true });
345                return;
346            }
347
348            if (iti) {
349                phoneInput.value = iti.getNumber();
350            }
351
352            if (emailGlobal && !validateEmailField(emailGlobal, { show: false })) {
353                e.preventDefault();
354                goToStepForElement(emailGlobal);
355                validateEmailField(emailGlobal, { show: true });
356                return;
357            }
358
359            const firstInvalid = form.querySelector(':invalid');
360            if (firstInvalid) {
361                e.preventDefault();
362                goToStepForElement(firstInvalid);
363                firstInvalid.reportValidity();
364                return;
365            }
366
367            isSubmitting = true;
368            if (submitBtn) {
369                if (!submitBtn.classList.contains('pfg-submit-btn')) submitBtn.classList.add('pfg-submit-btn');
370                if (!submitBtn.querySelector('.pfg-btn-text')) {
371                    const span = document.createElement('span');
372                    span.className = 'pfg-btn-text';
373                    span.textContent = submitBtn.textContent || submitBtn.value || 'Submitting';
374                    if (submitBtn.tagName === 'INPUT') submitBtn.value = '';
375                    else submitBtn.textContent = '';
376                    submitBtn.appendChild(span);
377                }
378                submitBtn.classList.add('is-loading');
379                submitBtn.setAttribute('aria-disabled', 'true');
380                submitBtn.disabled = true;
381            }
382        });
383
384        if (submitBtn) {
385            submitBtn.addEventListener('click', (e) => {
386                if (isSubmitting) e.preventDefault();
387            });
388        }
389    }
390
391    function initAllFormsIn(root = document) {
392        root.querySelectorAll('form.pfg-form').forEach(initForm);
393    }
394
395    document.addEventListener('DOMContentLoaded', () => {
396        initAllFormsIn(document);
397    });
398
399    const mo = new MutationObserver(muts => {
400        muts.forEach(mut => {
401            mut.addedNodes.forEach(node => {
402                if (node.nodeType !== 1) return;
403                if (node.matches && node.matches('form.pfg-form')) initForm(node);
404                if (node.querySelectorAll) initAllFormsIn(node);
405            });
406        });
407    });
408    mo.observe(document.body, { childList: true, subtree: true });
409})();
410
411
412/* ── Referrer-based attribution (runs when NO UTM params are in the URL) ── */
413(function () {
414    const LSK = 'pfg_utms';
415    const TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
416
417    const INTERNAL_HOSTS = [
418        window.location.hostname.toLowerCase()
419    ];
420
421    const UPGRADE_DIRECT = true;
422
423    function readParamsLC() {
424        const s = window.location.search.replace(/^\?/, '');
425        const map = {};
426        if (!s) return map;
427        for (const pair of s.split('&')) {
428            if (!pair) continue;
429            const [kRaw, vRaw = ''] = pair.split('=');
430            if (!kRaw) continue;
431            map[decodeURIComponent(kRaw).toLowerCase()] = decodeURIComponent(vRaw.replace(/\+/g, ' '));
432        }
433        return map;
434    }
435
436    function loadStore() {
437        try {
438            const raw = localStorage.getItem(LSK);
439            if (!raw) return {};
440            const obj = JSON.parse(raw);
441            if (!obj || !obj.data || !obj.ts) return {};
442            if (Date.now() - obj.ts > TTL) return {};
443            return obj.data || {};
444        } catch (_) {
445            return {};
446        }
447    }
448
449    function saveStore(data) {
450        try {
451            localStorage.setItem(LSK, JSON.stringify({ ts: Date.now(), data }));
452        } catch (_) {}
453    }
454
455    function getReferrerHost() {
456        try {
457            if (!document.referrer) return '';
458            return new URL(document.referrer).hostname.toLowerCase();
459        } catch (_) {
460            return '';
461        }
462    }
463
464    function isInternalHost(host) {
465        if (!host) return false;
466        return INTERNAL_HOSTS.some(h => {
467            if (!h) return false;
468            if (host === h) return true;
469            return host.length > h.length && host.slice(-1 - h.length) === '.' + h;
470        });
471    }
472
473    function classifySearch(refHost) {
474        if (refHost === 'google.com' || refHost.endsWith('.google.com'))
475            return { utm_source: 'google', utm_medium: 'organic' };
476        if (refHost === 'bing.com' || refHost.endsWith('.bing.com'))
477            return { utm_source: 'bing', utm_medium: 'organic' };
478        if (refHost === 'duckduckgo.com' || refHost.endsWith('.duckduckgo.com'))
479            return { utm_source: 'duckduckgo', utm_medium: 'organic' };
480        if (refHost === 'search.yahoo.com' || refHost.endsWith('.yahoo.com'))
481            return { utm_source: 'yahoo', utm_medium: 'organic' };
482        if (refHost === 'ecosia.org' || refHost.endsWith('.ecosia.org'))
483            return { utm_source: 'ecosia', utm_medium: 'organic' };
484        return null;
485    }
486
487    function classifySocial(refHost) {
488        if (refHost.includes('facebook.') || refHost === 'fb.com')
489            return { utm_source: 'facebook', utm_medium: 'social' };
490        if (refHost.includes('instagram.'))
491            return { utm_source: 'instagram', utm_medium: 'social' };
492        if (refHost.includes('linkedin.'))
493            return { utm_source: 'linkedin', utm_medium: 'social' };
494        if (refHost.includes('t.co') || refHost.includes('twitter.') || refHost.includes('x.com'))
495            return { utm_source: 'twitter', utm_medium: 'social' };
496        if (refHost.includes('tiktok.'))
497            return { utm_source: 'tiktok', utm_medium: 'social' };
498        if (refHost.includes('youtube.') || refHost.includes('youtu.be'))
499            return { utm_source: 'youtube', utm_medium: 'social' };
500        return null;
501    }
502
503    function classifyByReferrer(refHost) {
504        if (!refHost) return { utm_source: '(direct)', utm_medium: '(none)' };
505        if (isInternalHost(refHost)) return null;
506        const se = classifySearch(refHost);
507        if (se) return se;
508        const so = classifySocial(refHost);
509        if (so) return so;
510        return { utm_source: refHost, utm_medium: 'referral' };
511    }
512
513    function campaignFromMedium(m) {
514        if (m === 'organic')     return 'organic';
515        if (m === 'referral')    return 'referral';
516        if (m === 'social')      return 'social';
517        if (m === 'cpc')         return 'paid';
518        if (m === 'paid-social') return 'paid-social';
519        if (m === '(none)')      return '(direct)';
520        return '';
521    }
522
523    function shouldWrite(store, candidate) {
524        const hasExisting = !!(store.utm_source || store.utm_medium);
525        if (!hasExisting) return true;
526        if (!UPGRADE_DIRECT) return false;
527        const isDirect = (store.utm_source === '(direct)' || store.utm_medium === '(none)' || store.utm_campaign === '(direct)');
528        const candidateIsBetter = candidate && candidate.utm_source && candidate.utm_medium &&
529            !(candidate.utm_source === '(direct)' && candidate.utm_medium === '(none)');
530        return isDirect && candidateIsBetter;
531    }
532
533    function mergeStore(store, candidate, extra) {
534        return Object.assign({}, store, candidate, extra);
535    }
536
537    const params = readParamsLC();
538
539    const hasUtmInUrl = !!(params.utm_source || params.utm_medium || params.utm_campaign || params.utm_term || params.utm_content);
540    if (hasUtmInUrl) return;
541
542    const store = loadStore();
543
544    let paidCandidate = null;
545    if (params.gclid || params.wbraid || params.gbraid) {
546        paidCandidate = { utm_source: 'google', utm_medium: 'cpc', utm_campaign: 'paid' };
547    } else if (params.msclkid) {
548        paidCandidate = { utm_source: 'bing', utm_medium: 'cpc', utm_campaign: 'paid' };
549    } else if (params.fbclid) {
550        paidCandidate = { utm_source: 'facebook', utm_medium: 'paid-social', utm_campaign: 'paid-social' };
551    }
552
553    if (paidCandidate && shouldWrite(store, paidCandidate)) {
554        const extraPaid = { landing_page: window.location.href.split('#')[0] };
555        if (document.referrer) extraPaid.referrer = document.referrer;
556        saveStore(mergeStore(store, paidCandidate, extraPaid));
557        return;
558    }
559
560    const refHost = getReferrerHost();
561    const fb = classifyByReferrer(refHost);
562    if (!fb) return;
563
564    if (!fb.utm_campaign) {
565        const camp = campaignFromMedium(fb.utm_medium);
566        if (camp) fb.utm_campaign = camp;
567    }
568
569    if (!shouldWrite(store, fb)) return;
570
571    const extra = { landing_page: window.location.href.split('#')[0] };
572    if (document.referrer) extra.referrer = document.referrer;
573
574    saveStore(mergeStore(store, fb, extra));
575})();
576
577
578/* ── UTM param capture & form injection ── */
579(function(){
580    const KEYS = [
581        'utm_source','utm_medium','utm_campaign','utm_term','utm_content',
582        'gclid','wbraid','gbraid','msclkid','fbclid',
583        'page','landing_page','referrer'
584    ];
585    const LSK  = 'pfg_utms';
586    const TTL  = 30 * 24 * 60 * 60 * 1000; // 30 days
587
588    function isEl(n){ return n && n.nodeType === 1; }
589
590    function readParamsLC() {
591        const s = window.location.search.replace(/^\?/,'');
592        const map = {};
593        if (!s) return map;
594        for (const pair of s.split('&')) {
595            if (!pair) continue;
596            const [kRaw, vRaw=''] = pair.split('=');
597            if (!kRaw) continue;
598            const k = decodeURIComponent(kRaw).toLowerCase();
599            const v = decodeURIComponent(vRaw.replace(/\+/g,' '));
600            map[k] = v;
601        }
602        return map;
603    }
604
605    function loadStore(){
606        try{
607            const raw = localStorage.getItem(LSK);
608            if (!raw) return {};
609            const obj = JSON.parse(raw);
610            if (!obj || !obj.data || !obj.ts) return {};
611            if (Date.now() - obj.ts > TTL) return {};
612            return obj.data;
613        }catch(_){ return {}; }
614    }
615    function saveStore(data){
616        try{ localStorage.setItem(LSK, JSON.stringify({ts: Date.now(), data})); }catch(_){}
617    }
618
619    const paramsLC = readParamsLC();
620    let   store    = loadStore();
621    let   changed  = false;
622
623    KEYS.forEach(k=>{
624        const v = paramsLC[k];
625        if (v !== undefined && v !== '') { store[k] = v; changed = true; }
626    });
627    if (changed) saveStore(store);
628
629    function getUtm(k){
630        const key = String(k).toLowerCase();
631        if (paramsLC[key]) return paramsLC[key];
632        return store[key] || '';
633    }
634
635    function getPageValue(){
636        return window.location.href.split('?')[0].replace(/^https?:\/\//,'');
637    }
638
639    function applyUtmsToForm(form){
640        if (!isEl(form)) return;
641
642        KEYS.forEach(key=>{
643            let v = getUtm(key);
644
645            if (key === 'page' && (!v || v === '')) {
646                v = getPageValue();
647            }
648
649            let inp = form.querySelector(`input[name="${key}"]`);
650            if (!inp) {
651                inp = document.createElement('input');
652                inp.type = 'hidden';
653                inp.name = key;
654                form.appendChild(inp);
655            }
656            inp.value = v;
657        });
658
659        form.dataset.pfgUtmsApplied = '1';
660    }
661
662    function applyAll(root=document){
663        if (!isEl(root) && root !== document) return;
664        const scope = root === document ? document : root;
665        if (!scope.querySelectorAll) return;
666        scope.querySelectorAll('form.pfg-form').forEach(applyUtmsToForm);
667    }
668
669    document.addEventListener('DOMContentLoaded', ()=>applyAll(document));
670    window.addEventListener('load', ()=>applyAll(document));
671    window.addEventListener('pageshow', ()=>applyAll(document));
672
673    (function(){
674        const _ps = history.pushState;
675        const _rp = history.replaceState;
676        history.pushState = function(){ const r=_ps.apply(this,arguments); setTimeout(()=>applyAll(document),0); return r; };
677        history.replaceState = function(){ const r=_rp.apply(this,arguments); setTimeout(()=>applyAll(document),0); return r; };
678        window.addEventListener('popstate', ()=>applyAll(document));
679    })();
680
681    const mo = new MutationObserver(muts=>{
682        for (const mut of muts){
683            for (const node of mut.addedNodes){
684                if (!isEl(node)) continue;
685                if (node.matches && node.matches('form.pfg-form')) applyUtmsToForm(node);
686                if (node.querySelectorAll){
687                    node.querySelectorAll('form.pfg-form').forEach(applyUtmsToForm);
688                }
689            }
690        }
691    });
692    mo.observe(document.body, {childList:true, subtree:true});
693
694    let tries = 0;
695    const retryId = setInterval(()=>{
696        applyAll(document);
697        if (++tries >= 10) clearInterval(retryId);
698    }, 300);
699})();

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.