1// Session manager with inactivity timeout (30 minutes) 2(function(){ 3 try { 4 var INACTIVITY_MS = 30 * 60 * 1000; 5 var now = Date.now(); 6 var ls = window.localStorage; 7 8 var lastActive = Number(ls.getItem('pj_last_active_ts') || 0); 9 var startTs = Number(ls.getItem('pj_session_start_ts') || 0); 10 var sessionId = ls.getItem('pj_session_id'); 11 12 var needNewSession = !startTs || (lastActive && (now - lastActive > INACTIVITY_MS)); 13 if (needNewSession) { 14 startTs = now; 15 sessionId = (Math.random().toString(36).slice(2) + now.toString(36)); 16 ls.setItem('pj_session_start_ts', String(startTs)); 17 ls.setItem('pj_session_id', sessionId); 18 } 19 20 ls.setItem('pj_last_active_ts', String(now)); 21 22 var lastPing = 0; 23 function pingActive() { 24 var t = Date.now(); 25 if (t - lastPing > 30000) { 26 ls.setItem('pj_last_active_ts', String(t)); 27 lastPing = t; 28 } 29 } 30 31 ['click', 'scroll', 'keydown', 'mousemove', 'touchstart', 'visibilitychange'].forEach(function (evt) { 32 window.addEventListener(evt, pingActive, { passive: true }); 33 }); 34 } catch (e) {} 35})(); 36 37// Mark browser session start for cross-tab article count (session cookie, cleared on browser close) 38(function() { 39 try { 40 if (!document.cookie.match(/(?:^|;\s*)pj_session_start=/)) { 41 document.cookie = 'pj_session_start=' + Date.now() + '; path=/; SameSite=Lax'; 42 } 43 } catch (e) {} 44})(); 45 46// email unput suggestions 47(function(){ 48 "use strict"; 49 50 // Shared constants for US-popular domains and TLDs 51 var EMAIL_DOMAINS_US = [ 52 "gmail.com","yahoo.com","ymail.com","rocketmail.com", 53 "outlook.com","hotmail.com","live.com","msn.com", 54 "icloud.com","me.com","mac.com","aol.com", 55 "comcast.net","verizon.net","att.net","sbcglobal.net", 56 "bellsouth.net","cox.net","charter.net","spectrum.net", 57 "optonline.net","earthlink.net" 58 ]; 59 var EMAIL_TLDS_US = ["com","net","org","us","edu","gov","mil"]; 60 61 function createHint(container) { 62 var hint = document.createElement("div"); 63 hint.className = "email-suggest-hint"; 64 hint.style.marginTop = "6px"; 65 hint.style.fontSize = "16px"; 66 hint.style.display = "none"; 67 hint.style.background = "#FEE2E2"; // light red background 68 hint.style.borderRadius = "6px"; 69 hint.style.padding = "8px 10px"; 70 hint.style.color = "#7F1D1D"; // dark red text 71 hint.style.display = "none"; 72 hint.style.alignItems = "center"; 73 hint.style.gap = "8px"; 74 hint.setAttribute('role', 'alert'); 75 hint.setAttribute('aria-live', 'polite'); 76 77 // Danger icon 78 var icon = document.createElement("span"); 79 icon.setAttribute('aria-hidden', 'true'); 80 icon.style.display = "inline-flex"; 81 icon.style.verticalAlign = "sub"; 82 icon.style.width = "18px"; 83 icon.style.height = "18px"; 84 icon.style.marginRight = "8px"; 85 icon.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10.29 3.859l-8.4 14.4A2 2 0 003.2 21h17.6a2 2 0 001.71-2.741l-8.4-14.4a2 2 0 00-3.42 0z" fill="#FCA5A5"/><path d="M12 9v4M12 17h.01" stroke="#DC2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 86 87 var label = document.createElement("span"); 88 label.textContent = "Did you mean: "; 89 label.style.fontWeight = "600"; 90 91 var link = document.createElement("a"); 92 link.href = ""; 93 link.style.textDecoration = "none"; 94 link.style.fontWeight = "700"; 95 link.style.cursor = "pointer"; 96 link.style.color = "#7C2D12"; 97 98 hint.appendChild(icon); 99 hint.appendChild(label); 100 hint.appendChild(link); 101 container.appendChild(hint); 102 103 return { box: hint, link: link }; 104 } 105 106 function attach(input) { 107 if (!input || input.__mcBound) return; 108 input.__mcBound = true; 109 110 var wrap = input.closest(".ginput_container, .gfield, .form-field") || input.parentNode; 111 var hint = createHint(wrap); 112 var last = null; 113 var blockedOnce = false;
114 115 function run() { 116 var raw = (input.value || ""); 117 var normalized = raw.replace(/\s+/g, '').replace(/@{2,}/g, '@').trim(); 118 var val = normalized; 119 if (!val || val.indexOf("@") < 1) { 120 hint.box.style.display = "none"; 121 last = null; 122 return; 123 } 124 if (!window.Mailcheck) return; 125 126 127 if (normalized !== raw.trim()) { 128 var parts = val.split("@"); 129 if (parts.length === 2) { 130 var dom = parts[1]; 131 if (dom.indexOf('.') !== -1 || EMAIL_DOMAINS_US.indexOf(dom) !== -1) { 132 last = val; 133 hint.link.textContent = last + "?"; 134 hint.box.style.display = "block"; 135 return; 136 } 137 } 138 } 139 140 Mailcheck.run({ 141 email: val, 142 domains: EMAIL_DOMAINS_US, 143 topLevelDomains: EMAIL_TLDS_US, 144 suggested: function (s) { 145 if (!s || !s.domain || s.domain.indexOf('.') === -1) { 146 last = null; 147 hint.box.style.display = "none"; 148 return; 149 } 150 last = s.full; 151 hint.link.textContent = last + "?"; 152 hint.box.style.display = "block"; 153 }, 154 empty: function () { 155 if (normalized !== raw.trim()) { 156 var parts = val.split("@"); 157 if (parts.length === 2) { 158 var dom = parts[1]; 159 if (dom.indexOf('.') !== -1) { 160 last = val; 161 hint.link.textContent = last + "?"; 162 hint.box.style.display = "block"; 163 return; 164 } 165 } 166 } 167 last = null; 168 hint.box.style.display = "none"; 169 } 170 }); 171 } 172 173 input.addEventListener("input", run); 174 input.addEventListener("blur", run); 175 176 hint.link.addEventListener("click", function (e) { 177 e.preventDefault(); 178 if (last) { 179 input.value = last; 180 hint.box.style.display = "none"; 181 input.dispatchEvent(new Event("input", { bubbles: true })); 182 } 183 }); 184 185 var form = input.form; 186 if (form) { 187 form.addEventListener("submit", function (e) { 188 if (last && input.value.trim() !== last && !blockedOnce) { 189 e.preventDefault(); 190 blockedOnce = true; 191 hint.box.style.display = "block"; 192 input.focus(); 193 } 194 }); 195 } 196 } 197 198 function init() { 199 var inputs = document.querySelectorAll("form[id^='gform_'] input#input_137_1[type='email'], form[id^='gform_'] input#input_163_1[type='email']"); 200 for (var i = 0; i < inputs.length; i++) attach(inputs[i]); 201 202 var mo = new MutationObserver(function (list) { 203 list.forEach(function (m) { 204 for (var i = 0; i < m.addedNodes.length; i++) { 205 var n = m.addedNodes[i]; 206 if (!(n instanceof Element)) continue;
207 var newInputs = n.matches && n.matches("input[type='email']") 208 ? [n] 209 : (n.querySelectorAll && n.querySelectorAll("input[type='email']")) || []; 210 for (var j = 0; j < newInputs.length; j++) attach(newInputs[j]); 211 } 212 }); 213 }); 214 mo.observe(document.documentElement || document.body, { childList: true, subtree: true }); 215 } 216 217 function start() { 218 if (!window.Mailcheck) { 219 setTimeout(start, 50); 220 return; 221 } 222 if (document.readyState === "loading") { 223 document.addEventListener("DOMContentLoaded", init); 224 } else { 225 init(); 226 } 227 } 228 229 start(); 230})(); 231 232// Move read-next-widget to sidebar if needed 233(function(){ 234 "use strict"; 235 236 function moveReadNextWidget() { 237 try { 238 var readNextWidget = document.querySelector('.read-next-widget'); 239 if (!readNextWidget) { 240 return; 241 } 242 243 var location = readNextWidget.getAttribute('data-location') || 'default'; 244 245 if (location !== 'sidebar') { 246 return; 247 } 248 249 var sidebar = document.querySelector('.sidebar'); 250 if (!sidebar) { 251 return; 252 } 253 254 // Create wrapper 255 var wrapper = document.createElement('div'); 256 wrapper.className = 'widget'; 257 wrapper.appendChild(readNextWidget.cloneNode(true)); 258 259 // Find first widget in sidebar 260 var firstWidget = sidebar.querySelector('.widget'); 261 262 if (firstWidget) { 263 // Insert after first widget 264 firstWidget.parentNode.insertBefore(wrapper, firstWidget.nextSibling); 265 } else { 266 // Insert at beginning of sidebar 267 sidebar.insertBefore(wrapper, sidebar.firstChild); 268 } 269 270 // Remove original 271 readNextWidget.remove(); 272 } catch (e) { 273 // Silent fail 274 } 275 } 276 277 if (document.readyState === 'loading') { 278 document.addEventListener('DOMContentLoaded', moveReadNextWidget); 279 } else { 280 moveReadNextWidget(); 281 } 282})(); 283 284// plan selection 285function selectPlanByIndex(idx) { 286 const fieldset = document.querySelector('#membership-widget fieldset.gfield--type-product.gfield--type-choice.gfield--input-type-radio'); 287 const form = document.querySelector('#membership-widget form'); 288 if (!fieldset || !form) return; 289 const radios = fieldset.querySelectorAll('input.gfield-choice-input[type="radio"]'); 290 if (!radios.length || !radios[idx]) return; 291 const target = radios[idx]; 292 if (!target.checked) { 293 target.checked = true; 294 target.dispatchEvent(new Event('change', { bubbles: true })); 295 } 296 297 try { 298 const startTs = Number(localStorage.getItem('pj_session_start_ts') || sessionStorage.getItem('pj_session_start_ts') || Date.now()); 299 const sec = Math.max(0, Math.round((Date.now() - startTs) / 1000)); 300 const durationInput = form.querySelector('input[name="input_16"]'); 301 if (durationInput) { 302 durationInput.value = String(sec); 303 } 304 } catch (e) {} 305 if (typeof form.requestSubmit === 'function') { 306 form.requestSubmit(); 307 } else { 308 form.submit(); 309 } 310} 311 312function setMembershipTracking() { 313 try { 314 const bannerRoot = document.querySelector('#membership-widget'); 315 if (bannerRoot) { 316 const fromInput = bannerRoot.querySelector('input[name="input_14"]'); 317 if (fromInput) { 318 fromInput.value = sessionStorage.getItem('pj_membership_from') || ''; 319 } 320 321 const readsInput = bannerRoot.querySelector('input[name="input_15"]'); 322 if (readsInput) { 323 let readCount = 0; 324 try { 325 const raw = localStorage.getItem('pj_articles_read'); 326 const arr = JSON.parse(raw || '[]'); 327 readCount = Array.isArray(arr) ? arr.length : 0; 328 } catch (e) { readCount = 0; } 329 readsInput.value = String(readCount); 330 } 331 } 332 333 // Set current URL with parameters to hidden field 334 const urlInput = document.querySelector('.widget_membership_widget input[name="input_17"]'); 335 if (urlInput) { 336 urlInput.value = window.location.href; 337 } 338 339 const membershipLinks = document.querySelectorAll('a[href*="/membership-plans"]'); 340 if (membershipLinks.length) { 341 const deviceType = window.innerWidth < 980 ? 'mobile' : 'desktop'; 342 membershipLinks.forEach((link) => { 343 let href = link.getAttribute('href'); 344 if (!/utm_source=[^_]+_(mobile|desktop)/.test(href)) { 345 href = href 346 .replace('utm_source=sidebar', 'utm_source=sidebar_' + deviceType) 347 .replace('utm_source=anchor', 'utm_source=anchor_' + deviceType) 348 .replace('utm_source=demo', 'utm_source=demo_' + deviceType); 349 link.setAttribute('href', href); 350 } 351 }); 352 } 353 } catch (e) {} 354} 355 356// Donation & membership timing tracking for GA4 via GTM 357(function() { 358 'use strict'; 359 360 if (typeof pj_forms === 'undefined') return; 361 var donationIds = pj_forms.donation || []; 362 var membershipIds = pj_forms.membership || []; 363 var allFormIds = donationIds.concat(membershipIds); 364 if (!allFormIds.length) return; 365 366 var PREFIX = 'pj_dt_'; 367 var pageLoadTime = Date.now(); 368 var intentFired = {}; 369 370 function getArticlesAllTime() { 371 try { 372 var arr = JSON.parse(localStorage.getItem('pj_articles_read') || '[]'); 373 return Array.isArray(arr) ? arr.length : 0; 374 } catch (e) { return 0; } 375 } 376 377 function getArticles7d() { 378 try { 379 var arr = JSON.parse(localStorage.getItem('pj_articles_read') || '[]'); 380 var cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000; 381 return Array.isArray(arr) ? arr.filter(function(r) { return r && r.ts > cutoff; }).length : 0; 382 } catch (e) { return 0; } 383 } 384 385 function getArticlesSession() { 386 try { 387 var m = document.cookie.match(/(?:^|;\s*)pj_session_start=([^;]*)/); 388 var sessionStart = m ? (parseInt(m[1], 10) || Date.now()) : Date.now(); 389 var maxWindow = 3 * 60 * 60 * 1000; 390 var effectiveStart = Math.max(sessionStart, Date.now() - maxWindow); 391 var arr = JSON.parse(localStorage.getItem('pj_articles_read') || '[]'); 392 return Array.isArray(arr) ? arr.filter(function(r) { return r && r.ts > effectiveStart; }).length : 0; 393 } catch (e) { return 0; } 394 } 395 396 var THANK_YOU_PATHS = [ 397 '/contribution-make-it-monthly', 398 '/contribution-increase-to-5', 399 '/contribution-thank-you-membership' 400 ]; 401 402 403 function trackIntent(formId) { 404 if (intentFired[formId]) return; 405 intentFired[formId] = true; 406 407 var form = document.getElementById('gform_' + formId); 408 if (!form) return; 409 410 var timeToIntent = Math.round((Date.now() - pageLoadTime) / 1000); 411 412 var widgetType = ''; 413 var sourceField = form.querySelector('input[name="input_13"]'); 414 if (sourceField) widgetType = sourceField.value; 415 416 var amount = 0; 417 var totalField = form.querySelector('input[name="input_5"]'); 418 if (totalField) amount = parseFloat(totalField.value.replace(/[^0-9.]/g, '')) || 0; 419 420 var frequency = ''; 421 var freqRadios = form.querySelectorAll('input[name="input_7"]'); 422 for (var i = 0; i < freqRadios.length; i++) { 423 if (freqRadios[i].checked) { frequency = freqRadios[i].value; break; } 424 } 425 426 try { 427 localStorage.setItem(PREFIX + 'ts', String(Date.now())); 428 localStorage.setItem(PREFIX + 'widget', widgetType); 429 } catch (e) {} 430 431 window.dataLayer = window.dataLayer || []; 432 window.dataLayer.push({ 433 event: 'donation_intent', 434 time_to_intent: timeToIntent, 435 widget_type: widgetType, 436 donation_amount: amount, 437 donation_frequency: frequency, 438 articles_all_time: getArticlesAllTime(), 439 articles_7d: getArticles7d(), 440 articles_session: getArticlesSession() 441 }); 442 } 443 444 function trackCompletion() { 445 var ts; 446 try { ts = localStorage.getItem(PREFIX + 'ts'); } catch (e) { return; } 447 if (!ts) return; 448 449 var age = Date.now() - parseInt(ts, 10); 450 if (age > 30 * 60 * 1000) { clearData(); return; } 451 452 window.dataLayer = window.dataLayer || []; 453 window.dataLayer.push({ 454 event: 'donation_complete', 455 time_to_complete: Math.round(age / 1000), 456 widget_type: localStorage.getItem(PREFIX + 'widget') || '', 457 }); 458 459 clearData(); 460 } 461 462 function clearData() { 463 ['ts', 'widget'].forEach(function(k) {
464 try { localStorage.removeItem(PREFIX + k); } catch (e) {} 465 }); 466 } 467 468 function hasConfirmationMessage() { 469 for (var i = 0; i < allFormIds.length; i++) { 470 if (document.getElementById('gform_confirmation_message_' + allFormIds[i])) return true; 471 } 472 return false; 473 } 474 475 function isCompletionPage() { 476 var path = window.location.pathname.replace(/\/+$/, ''); 477 for (var i = 0; i < THANK_YOU_PATHS.length; i++) { 478 if (path === THANK_YOU_PATHS[i]) return true; 479 } 480 return hasConfirmationMessage(); 481 } 482 483 document.addEventListener('gform/theme/scripts_loaded', function() { 484 gform.utils.addAsyncFilter('gform/submission/pre_submission', async function(data) { 485 if (data.submissionType !== gform.submission.SUBMISSION_TYPE_SUBMIT) return data; 486 var formId = parseInt(data.form.dataset.formid, 10); 487 if (allFormIds.indexOf(formId) !== -1) { 488 trackIntent(formId); 489 } 490 return data; 491 }); 492 }); 493 494 // Capture-phase click â backup for popup forms where GF never actually submits 495 document.addEventListener('click', function(e) { 496 var btn = e.target.closest('[id^="gform_submit_button_"]'); 497 if (!btn) return; 498 var match = btn.id.match(/gform_submit_button_(\d+)/); 499 if (!match) return; 500 var formId = parseInt(match[1], 10); 501 if (allFormIds.indexOf(formId) === -1) return; 502 setTimeout(function() { trackIntent(formId); }, 0); 503 }, true); 504 505 function init() { 506 if (isCompletionPage()) { 507 trackCompletion(); 508 return; 509 } 510 511 // Watch for dynamically appearing GF confirmation messages (e.g. membership) 512 try { 513 if (localStorage.getItem(PREFIX + 'ts')) { 514 var obs = new MutationObserver(function() { 515 if (hasConfirmationMessage()) { 516 trackCompletion(); 517 obs.disconnect(); 518 } 519 }); 520 obs.observe(document.body || document.documentElement, { childList: true, subtree: true }); 521 } 522 } catch (e) {} 523 } 524 525 if (document.readyState === 'loading') { 526 document.addEventListener('DOMContentLoaded', init); 527 } else { 528 init(); 529 } 530})(); 531 532// AB Testing variant selector 533(function(){ 534 "use strict"; 535 536 function selectVariantForBlock(variantsList) { 537 var variants = variantsList.querySelectorAll('.pj-ab-variant'); 538 if (!variants || variants.length === 0) { 539 return; 540 } 541 542 // Collect variants with their percentages 543 var variantData = []; 544 var totalPercentage = 0; 545 546 for (var i = 0; i < variants.length; i++) { 547 var percentage = parseFloat(variants[i].getAttribute('data-variant-percentage')) || 0; 548 if (percentage > 0) { 549 variantData.push({ 550 element: variants[i], 551 percentage: percentage 552 }); 553 totalPercentage += percentage; 554 } 555 } 556 557 if (variantData.length === 0 || totalPercentage === 0) { 558 // If no valid percentages, show first variant 559 if (variants.length > 0) { 560 variants[0].classList.add('current'); 561 } 562 return; 563 } 564 565 // Generate random number between 0 and total percentage 566 var random = Math.random() * totalPercentage; 567 var cumulative = 0; 568 569 // Select variant based on weighted probability 570 for (var j = 0; j < variantData.length; j++) { 571 cumulative += variantData[j].percentage; 572 if (random <= cumulative) { 573 variantData[j].element.classList.add('current'); 574 return; 575 } 576 } 577 578 // Fallback: show last variant if something went wrong 579 variantData[variantData.length - 1].element.classList.add('current'); 580 } 581 582 function selectABVariants() { 583 try { 584 var variantsLists = document.querySelectorAll('.pj-ab-variants-list'); 585 if (!variantsLists || variantsLists.length === 0) { 586 return; 587 } 588 589 // Process each variants list independently 590 for (var i = 0; i < variantsLists.length; i++) { 591 selectVariantForBlock(variantsLists[i]); 592 } 593 } catch (e) { 594 // Silent fail 595 } 596 } 597 598 if (document.readyState === 'loading') { 599 document.addEventListener('DOMContentLoaded', selectABVariants); 600 } else { 601 selectABVariants(); 602 } 603})();
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.