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.