1/** 2 * Popup Alert JavaScript 3 * Reads settings from window.popupAlertSettings and displays popup 4 */ 5 6(function() { 7 'use strict'; 8 9 // Check if settings exist 10 if (typeof window.popupAlertSettings === 'undefined' || !window.popupAlertSettings.popup) { 11 return; 12 } 13 14 var config = window.popupAlertSettings.popup; 15 16 // Configuration 17 var CONFIG = { 18 autoShowDelay: 500, // Half a second after page load 19 animationDuration: 500 20 }; 21 22 // Additional CTA Classes - Edit these to add custom classes to CTAs 23 var ADDITIONAL_POPUP_CTA_CLASSES = ''; // Additional classes for popup CTA button (e.g., 'btn-primary btn-large') 24 var ADDITIONAL_HERO_CTA_CLASSES = ''; // Additional classes for hero CTA button (e.g., 'btn-primary btn-large') 25 26 // Hero CTA Text Override - Set this to use different text on the hero button 27 // Leave as empty string '' to use the popup's CTA text instead 28 var HERO_CTA_TEXT_OVERRIDE = ''; 29 30 // Check if current page is the home page 31 function isHomePage() { 32 // Method 1: Check pathname is root (works for most sites) 33 var pathname = window.location.pathname; 34 var cleanPath = pathname.replace(/\/$/, '') || '/'; 35 if (cleanPath === '/') { 36 return true; 37 } 38 39 // Method 2: Check body or html for home-related classes (fallback) 40 // Covers: WordPress (page-template-home, home, front-page), 41 // Concrete CMS (ccm-page-id-1, page_1), 42 // generic (page-home) 43 var htmlClasses = document.documentElement.className || ''; 44 var bodyClasses = document.body ? (document.body.className || '') : ''; 45 var combined = ' ' + htmlClasses + ' ' + bodyClasses + ' '; 46 47 var homeIndicators = [ 48 /\bpage-template-home\b/i, 49 /\bfront-page\b/i, 50 /\bpage-home\b/i, 51 /\bccm-page-id-1\b/i, // Concrete CMS home page 52 /\bpage_1\b/i, // Concrete CMS home page alt 53 /\s+home\s+/i // generic "home" as standalone class 54 ]; 55 56 for (var i = 0; i < homeIndicators.length; i++) { 57 if (homeIndicators[i].test(combined)) { 58 return true; 59 } 60 } 61 62 return false; 63 } 64 65 // Check if popup should be shown 66 function shouldShowPopup() { 67 console.log('Popup Alert: Checking if should show popup...'); 68 69 // Check if enabled 70 if (!config.enabled) { 71 console.log('Popup Alert: Popup is disabled'); 72 return false; 73 } 74 console.log('Popup Alert: Popup is enabled'); 75 76 // Only show on home page 77 if (!isHomePage()) { 78 console.log('Popup Alert: Not on home page, pathname:', window.location.pathname); 79 return false; 80 } 81 console.log('Popup Alert: On home page, will check other conditions'); 82 83 // Check date range 84 var now = new Date(); 85 console.log('Popup Alert: Current date:', now); 86 87 // Check start date - if set, must be today or in the past 88 if (config.start_date) { 89 var startDate = new Date(config.start_date); 90 startDate.setHours(0, 0, 0, 0); // Start of day 91 var today = new Date(); 92 today.setHours(0, 0, 0, 0); 93 console.log('Popup Alert: Start date:', startDate); 94 if (today < startDate) { 95 console.log('Popup Alert: Not started yet'); 96 return false; // Not started yet 97 } 98 } 99 100 // Check end date - if set, must be today or in the future 101 if (config.end_date) { 102 var endDate = new Date(config.end_date); 103 endDate.setHours(23, 59, 59, 999); // End of day 104 var nowTime = new Date().getTime(); 105 console.log('Popup Alert: End date:', endDate); 106 console.log('Popup Alert: Current time:', nowTime); 107 console.log('Popup Alert: End time:', endDate.getTime()); 108 if (nowTime > endDate.getTime()) { 109 console.log('Popup Alert: Already ended - will not show'); 110 return false; // Already ended 111 } 112 } 113 114 console.log('Popup Alert: All checks passed, popup should show'); 115 return true; 116 } 117 118 // Create and inject popup HTML 119 function createPopup() { 120 console.log('Popup Alert: Creating popup HTML...'); 121 122 // Create overlay
123 var overlay = document.createElement('div'); 124 overlay.className = 'popup-overlay'; 125 overlay.id = 'popup-overlay'; 126 127 // Create popup container 128 var container = document.createElement('div'); 129 container.className = 'popup-container'; 130 container.id = 'popup-container'; 131 132 // Create close button 133 var closeBtn = document.createElement('button'); 134 closeBtn.className = 'popup-close'; 135 closeBtn.id = 'popup-close'; 136 closeBtn.setAttribute('aria-label', 'Close popup'); 137 closeBtn.innerHTML = '<span>×</span>'; 138 139 // Create content wrapper 140 var content = document.createElement('div'); 141 content.className = 'popup-content'; 142 143 // Add image if exists 144 if (config.img_url) { 145 var imageDiv = document.createElement('div'); 146 imageDiv.className = 'popup-image'; 147 var img = document.createElement('img'); 148 img.src = config.img_url; 149 img.alt = config.img_alt || ''; 150 if (config.img_width) img.width = config.img_width; 151 if (config.img_height) img.height = config.img_height; 152 imageDiv.appendChild(img); 153 content.appendChild(imageDiv); 154 } 155 156 // Add heading and description 157 if (config.heading || config.description) { 158 var textDiv = document.createElement('div'); 159 160 if (config.heading) { 161 var h2 = document.createElement('h2'); 162 h2.textContent = config.heading; 163 textDiv.appendChild(h2); 164 } 165 166 if (config.description) { 167 // Security: Use DOMParser to safely parse HTML and prevent XSS 168 var parser = new DOMParser(); 169 var doc = parser.parseFromString(config.description, 'text/html'); 170 var bodyContent = doc.body; 171 while (bodyContent.firstChild) { 172 textDiv.appendChild(bodyContent.firstChild); 173 } 174 } 175 176 content.appendChild(textDiv); 177 } 178 179 // Add CTA if exists 180 if (config.cta_link) { 181 var ctaDiv = document.createElement('div'); 182 ctaDiv.className = 'popup-cta'; 183 var ctaLink = document.createElement('a'); 184 ctaLink.href = config.cta_link; 185 ctaLink.className = 'btn' + (ADDITIONAL_POPUP_CTA_CLASSES ? ' ' + ADDITIONAL_POPUP_CTA_CLASSES : ''); 186 var ctaSpan = document.createElement('span'); 187 ctaSpan.textContent = config.cta_text || config.cta_name || 'Learn More'; 188 ctaLink.appendChild(ctaSpan); 189 ctaDiv.appendChild(ctaLink); 190 content.appendChild(ctaDiv); 191 } 192 193 // Assemble popup 194 container.appendChild(closeBtn); 195 container.appendChild(content); 196 overlay.appendChild(container); 197 198 // Inject before closing </main> tag, or fallback to body 199 var mainElement = document.querySelector('main'); 200 if (mainElement) { 201 mainElement.appendChild(overlay); 202 } else { 203 document.body.appendChild(overlay); 204 } 205 206 return { 207 overlay: overlay, 208 container: container, 209 closeBtn: closeBtn 210 }; 211 } 212 213 // Helper: check if an element's class list contains a token matching "xs" 214 // Only flags actual word-boundary matches like "xs_something", "something-xs", "xs" 215 // Does NOT flag substrings like "exposed", "boxshadow", "fixsubmenu" 216 function classHasXsToken(className) { 217 if (!className) return false;
218 // Split by whitespace and check each class name 219 var classes = className.split(/\s+/); 220 for (var i = 0; i < classes.length; i++) { 221 var cls = classes[i]; 222 // Match class names that start with "xs", end with "xs", contain "_xs_" or "-xs-", 223 // or are exactly "xs" 224 if (/^xs[_-]|[_-]xs$|[_-]xs[_-]|^xs$/i.test(cls)) { 225 return true; 226 } 227 } 228 return false; 229 } 230 231 // Inject popup CTA into hero section (if on home page) 232 function injectHeroCTA() { 233 // Check if already injected (prevent duplicate injections) 234 if (document.querySelector('.popup-hero-cta-injected')) { 235 console.log('Popup Alert: Hero CTA already injected, skipping'); 236 return; 237 } 238 239 // Check if on home page (using same logic as popup display) 240 if (!isHomePage()) { 241 console.log('Popup Alert: Not home page, skipping hero CTA injection'); 242 return; 243 } 244 245 // Check if popup has CTA configured 246 if (!config.cta_link) { 247 console.log('Popup Alert: No CTA link configured, skipping hero injection'); 248 return; 249 } 250 251 // Find hero element - look for any element with "hero" in class 252 var heroElements = document.querySelectorAll('[class*="hero"]'); 253 console.log('Popup Alert: Searching for elements with "hero" in class, found:', heroElements.length); 254 255 if (heroElements.length === 0) { 256 console.log('Popup Alert: No hero element found'); 257 return; 258 } 259 260 var heroElement = null; 261 var headerElement = document.querySelector('.header, header'); 262 263 // Filter hero elements - skip ones with "xs" token or inside header 264 for (var i = 0; i < heroElements.length; i++) { 265 var elem = heroElements[i]; 266 var className = elem.className || ''; 267 268 // Skip if class has an "xs" token (sticky mobile buttons, etc.) 269 if (classHasXsToken(className)) { 270 console.log('Popup Alert: Skipping element (has "xs" token):', elem, 'Classes:', className); 271 continue; 272 } 273 274 // Skip if element is inside .header or <header> 275 if (headerElement && headerElement.contains(elem)) { 276 console.log('Popup Alert: Skipping element (inside header):', elem); 277 continue; 278 } 279 280 // Found valid hero element 281 heroElement = elem; 282 console.log('Popup Alert: Selected hero element:', heroElement); 283 console.log('Popup Alert: Hero element classes:', heroElement.className); 284 break; 285 } 286 287 if (!heroElement) { 288 console.log('Popup Alert: No valid hero element found'); 289 return; 290 } 291 292 // Find all <a> tags with class "btn" within the hero 293 // Use [class*="btn"] selector to also catch "btn btn-lg" etc., 294 // then filter to actual btn class 295 var allLinks = heroElement.querySelectorAll('a'); 296 var existingBtns = []; 297 for (var j = 0; j < allLinks.length; j++) { 298 var link = allLinks[j]; 299 // Check if it has "btn" as a class token (not substring) 300 if (link.classList && link.classList.contains('btn')) { 301 existingBtns.push(link); 302 } 303 } 304 305 if (existingBtns.length === 0) { 306 console.log('Popup Alert: No existing buttons (a.btn) found in hero'); 307 return; 308 } 309 310 console.log('Popup Alert: Found', existingBtns.length, 'existing button(s) in hero'); 311 312 // Inject popup CTA after each existing button 313 existingBtns.forEach(function(existingBtn, index) { 314 var existingClasses = existingBtn.className; 315 console.log('Popup Alert: Existing button', index + 1, 'classes:', existingClasses); 316 317 // Create wrapper div 318 var wrapperDiv = document.createElement('div'); 319 wrapperDiv.className = 'popup-hero-cta-wrapper'; 320 wrapperDiv.style.marginTop = '15px'; 321 322 // Create new CTA link with same classes as existing button 323 var newCtaLink = document.createElement('a'); 324 newCtaLink.href = config.cta_link; 325 newCtaLink.className = existingClasses + ' popup-hero-cta-injected' + (ADDITIONAL_HERO_CTA_CLASSES ? ' ' + ADDITIONAL_HERO_CTA_CLASSES : ''); 326 var heroCtaSpan = document.createElement('span'); 327 // Use hero-specific text if provided, otherwise fall back to popup CTA text 328 var heroCtaText = HERO_CTA_TEXT_OVERRIDE || config.hero_cta_text || config.cta_text || config.cta_name || 'Learn More'; 329 heroCtaSpan.textContent = heroCtaText; 330 newCtaLink.setAttribute('title', heroCtaText); 331 newCtaLink.appendChild(heroCtaSpan); 332 333 wrapperDiv.appendChild(newCtaLink); 334 335 if (existingBtn.parentNode) { 336 existingBtn.parentNode.insertBefore(wrapperDiv, existingBtn.nextSibling); 337 console.log('Popup Alert: Successfully injected CTA after button', index + 1); 338 } else { 339 console.error('Popup Alert: Could not find parent node for button', index + 1); 340 } 341 }); 342 } 343 344 // Show Popup 345 function showPopup() { 346 console.log('Popup Alert: showPopup() called'); 347 var popup = createPopup(); 348 if (!popup || !popup.overlay) { 349 console.error('Popup Alert: Failed to create popup element'); 350 return; 351 } 352 console.log('Popup Alert: Popup created, showing...');
353 var overlay = popup.overlay; 354 var container = popup.container; 355 var closeBtn = popup.closeBtn; 356 357 container.classList.remove('slide-in'); 358 overlay.style.display = 'block'; 359 360 void overlay.offsetWidth; 361 void container.offsetWidth; 362 363 requestAnimationFrame(function() { 364 overlay.classList.add('active'); 365 setTimeout(function() { 366 container.classList.add('slide-in'); 367 }, 100); 368 }); 369 370 document.body.style.overflow = 'hidden'; 371 372 closeBtn.addEventListener('click', function(e) { 373 e.preventDefault(); 374 hidePopup(overlay, container); 375 }); 376 377 overlay.addEventListener('click', function(e) { 378 if (e.target === overlay) { 379 hidePopup(overlay, container); 380 } 381 }); 382 383 var escHandler = function(e) { 384 if (e.key === 'Escape' && overlay.classList.contains('active')) { 385 hidePopup(overlay, container); 386 document.removeEventListener('keydown', escHandler); 387 } 388 }; 389 document.addEventListener('keydown', escHandler); 390 } 391 392 // Hide Popup 393 function hidePopup(overlay, container) { 394 container.classList.remove('slide-in'); 395 396 setTimeout(function() { 397 overlay.classList.remove('active'); 398 setTimeout(function() { 399 overlay.style.display = 'none'; 400 overlay.remove(); 401 }, 300); 402 document.body.style.overflow = ''; 403 }, CONFIG.animationDuration); 404 } 405 406 // Auto-show popup on page load 407 function initAutoShow() { 408 console.log('Popup Alert: Initializing auto-show...'); 409 410 // Always try to inject hero CTA on home page (independent of popup display timing) 411 // Use a small delay to ensure DOM is fully ready 412 if (isHomePage() && config.enabled && config.cta_link) { 413 setTimeout(function() { 414 injectHeroCTA(); 415 }, 100); 416 } 417 418 if (shouldShowPopup()) { 419 console.log('Popup Alert: Will show popup in', CONFIG.autoShowDelay, 'ms'); 420 setTimeout(function() { 421 console.log('Popup Alert: Showing popup now'); 422 showPopup(); 423 }, CONFIG.autoShowDelay); 424 } else { 425 console.log('Popup Alert: Popup will not be shown'); 426 } 427 } 428 429 // Initialize on DOM ready 430 if (document.readyState === 'loading') { 431 document.addEventListener('DOMContentLoaded', initAutoShow); 432 } else { 433 initAutoShow(); 434 } 435 436 // Debug logging 437 console.log('Popup Alert: Script loaded'); 438 console.log('Popup Alert: Settings available?', typeof window.popupAlertSettings !== 'undefined'); 439 if (typeof window.popupAlertSettings !== 'undefined') { 440 console.log('Popup Alert: Popup enabled?', config.enabled); 441 console.log('Popup Alert: Full settings:', window.popupAlertSettings); 442 } 443 444})();; 445 446/** 447 * Alert Bar JavaScript 448 * Reads settings from window.popupAlertSettings and displays alert bar above header 449 */ 450 451(function() { 452 'use strict'; 453 454 // Check if settings exist 455 if (typeof window.popupAlertSettings === 'undefined' || !window.popupAlertSettings.alert_bar) { 456 return; 457 } 458 459 var config = window.popupAlertSettings.alert_bar; 460 461 // Configuration 462 var CONFIG = { 463 animationDuration: 300 464 }; 465 466 // Additional CTA Classes - Edit these to add custom classes to CTAs 467 var ADDITIONAL_ALERT_BAR_CTA_CLASSES = ''; // Additional classes for alert bar CTA button (e.g., 'btn-primary btn-large') 468 469 // Check if alert bar should be shown 470 function shouldShowAlertBar() { 471 // Check if enabled 472 if (!config.enabled) { 473 return false; 474 } 475 476 // Check date range 477 var now = new Date(); 478 479 // Check start date - if set, must be today or in the past 480 if (config.start_date) { 481 var startDate = new Date(config.start_date); 482 startDate.setHours(0, 0, 0, 0); // Start of day 483 var today = new Date(); 484 today.setHours(0, 0, 0, 0); 485 if (today < startDate) { 486 return false; // Not started yet 487 } 488 } 489 490 // Check end date - if set, must be today or in the future 491 if (config.end_date) { 492 var endDate = new Date(config.end_date); 493 endDate.setHours(23, 59, 59, 999); // End of day 494 var nowTime = now.getTime(); 495 if (nowTime >
495 endDate.getTime()) { 496 return false; // Already ended - IMPORTANT: This prevents display after end date 497 } 498 } 499 500 return true; 501 } 502 503 // Smart positioning - inject as first child inside header element 504 function positionAlertBar(alertBar) { 505 var header = document.querySelector('.header'); 506 var body = document.body; 507 508 if (header) { 509 // Insert as first child inside the header element 510 if (header.firstChild) { 511 header.insertBefore(alertBar, header.firstChild); 512 } else { 513 header.appendChild(alertBar); 514 } 515 } else { 516 // Fallback: top of body 517 body.insertBefore(alertBar, body.firstChild); 518 } 519 520 // Add class to body to indicate alert bar is present (CSS will handle spacing) 521 if (header) { 522 document.body.classList.add('alert-bar-active'); 523 } 524 } 525 526 // Create and inject alert bar HTML 527 function createAlertBar() { 528 // Create alert bar element 529 var alertBar = document.createElement('div'); 530 alertBar.className = 'alert-bar'; 531 alertBar.id = 'alert-bar'; 532 alertBar.style.backgroundColor = config.bg_color || '#4A90E2'; 533 alertBar.style.color = config.text_color || '#FFFFFF'; 534 535 // Create content wrapper 536 var content = document.createElement('div'); 537 content.className = 'alert-bar-content'; 538 539 // Create text container with inline CTA 540 var textContainer = document.createElement('div'); 541 textContainer.className = 'alert-bar-text'; 542 543 // Add text 544 if (config.text) { 545 textContainer.textContent = config.text; 546 } 547 548 // Add CTA inline if exists 549 if (config.cta_link) { 550 var ctaLink = document.createElement('a'); 551 ctaLink.href = config.cta_link; 552 // Add base btn class and additional classes if defined 553 ctaLink.className = 'btn' + (ADDITIONAL_ALERT_BAR_CTA_CLASSES ? ' ' + ADDITIONAL_ALERT_BAR_CTA_CLASSES : ''); 554 // Wrap CTA text in span element 555 var ctaSpan = document.createElement('span'); 556 ctaSpan.textContent = config.cta_text || config.cta_name || 'Learn More'; 557 ctaLink.appendChild(ctaSpan); 558 textContainer.appendChild(ctaLink); 559 } 560 561 content.appendChild(textContainer); 562 563 // Create close button 564 var closeBtn = document.createElement('button'); 565 closeBtn.className = 'alert-bar-close'; 566 closeBtn.setAttribute('aria-label', 'Close alert bar'); 567 closeBtn.innerHTML = '×'; 568 content.appendChild(closeBtn); 569 570 // Assemble alert bar 571 alertBar.appendChild(content); 572 573 return alertBar; 574 } 575 576 // Show Alert Bar 577 function showAlertBar() { 578 var alertBar = createAlertBar(); 579 580 // Position it 581 positionAlertBar(alertBar); 582 583 // Start hidden (above viewport) 584 alertBar.style.transform = 'translateY(-100%)'; 585 alertBar.style.display = 'block'; 586 587 // Force reflow 588 void alertBar.offsetWidth; 589 590 // Slide down animation 591 requestAnimationFrame(function() { 592 alertBar.classList.add('slide-down'); 593 alertBar.style.transform = 'translateY(0)'; 594 }); 595 596 // Close button handler 597 var closeBtn = alertBar.querySelector('.alert-bar-close'); 598 if (closeBtn) { 599 closeBtn.addEventListener('click', function(e) { 600 e.preventDefault(); 601 // No cookie - alert bar will show again on next visit 602 hideAlertBar(alertBar); 603 }); 604 } 605 606 return alertBar; 607 } 608 609 // Hide Alert Bar 610 function hideAlertBar(alertBar) { 611 // Slide up animation 612 alertBar.style.transform = 'translateY(-100%)'; 613 alertBar.classList.remove('slide-down'); 614 615 setTimeout(function() { 616 alertBar.style.display = 'none'; 617 618 // Remove alert bar active class (CSS will restore header spacing) 619 document.body.classList.remove('alert-bar-active'); 620 621 // Remove from DOM 622 alertBar.remove(); 623 }, CONFIG.animationDuration); 624 } 625 626 // Initialize on DOM ready
627 function init() { 628 if (shouldShowAlertBar()) { 629 // Wait a bit for page to load 630 setTimeout(function() { 631 showAlertBar(); 632 }, 100); 633 } 634 } 635 636 if (document.readyState === 'loading') { 637 document.addEventListener('DOMContentLoaded', init); 638 } else { 639 init(); 640 } 641 642})(); 643; 644
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.