1jQuery(document).ready(function($) { 2 if (typeof window.popupData === 'undefined' || window.popupData.length === 0) return; 3 4 // Helper: Check if popup should show based on frequency 5 function shouldShowPopup(popup) { 6 if (popup.frequency === 'always_show') { 7 return true; // Always fire, no checks 8 } 9 const key = 'popup_' + popup.id; 10 const now = new Date().toISOString().split('T')[0]; // YYYY-MM-DD for daily 11 12 if (popup.frequency === 'once_per_session') { 13 return !sessionStorage.getItem(key); 14 } else if (popup.frequency === 'once_per_day') { 15 const lastShown = localStorage.getItem(key + '_date'); 16 return lastShown !== now; 17 } 18 return true; 19 } 20 21 // Helper: Mark popup as shown (skip for always_show) 22 function markShown(popup) { 23 if (popup.frequency === 'always_show') return; // No storage needed 24 25 const key = 'popup_' + popup.id; 26 if (popup.frequency === 'once_per_session') { 27 sessionStorage.setItem(key, 'shown'); 28 } else if (popup.frequency === 'once_per_day') { 29 const now = new Date().toISOString().split('T')[0]; 30 localStorage.setItem(key + '_date', now); 31 } 32 } 33 34 // Find first eligible popup and show it 35 const eligiblePopup = window.popupData.find(shouldShowPopup); 36 if (!eligiblePopup) return; 37 38 // Create and inject popup HTML with ARIA 39 const popupHtml = ` 40 <div id="popup-overlay" class="popup-overlay" role="dialog" aria-modal="true" aria-labelledby="popup-title" tabindex="-1"> 41 <div class="popup-content-wrap"> 42 <div class="popup-content"> 43 <button class="popup-close" aria-label="Close popup">×</button> 44 <h2 id="popup-title">${eligiblePopup.headline}</h2> 45 ${eligiblePopup.text ? `<p>${eligiblePopup.text}</p>` : ''} 46 ${eligiblePopup.link_url ? `<a href="${eligiblePopup.link_url}" class="popup-link" target="_blank" rel="noopener noreferrer">${eligiblePopup.link_text || 'Learn More'}</a>` : ''} 47 </div> 48 </div> 49 </div> 50 `; 51 $('body').append(popupHtml); 52 53 // Mark as shown immediately (if applicable) 54 markShown(eligiblePopup); 55 56 const $overlay = $('#popup-overlay'); 57 const $closeBtn = $('.popup-close'); 58 const $focusableEls = $overlay.find('button, a, [tabindex]:not([tabindex="-1"])'); // All focusables inside 59 60 // Focus trap + ESC: Listen on overlay for tab, but document for ESC 61 function handleKeydown(e) { 62 if (e.key === 'Tab') { 63 const first = $focusableEls.first()[0]; 64 const last = $focusableEls.last()[0]; 65 if (e.shiftKey) { 66 if (document.activeElement === first) { 67 e.preventDefault(); 68 last.focus(); 69 } 70 } else { 71 if (document.activeElement === last) { 72 e.preventDefault(); 73 first.focus(); 74 } 75 } 76 } 77 } 78 79 function closePopup() { 80 $overlay.off('keydown', handleKeydown); // Clean up trap 81 $(document).off('keydown.popup-esc'); // Clean up ESC 82 $overlay.fadeOut(300, function() { 83 $(this).remove(); 84 $('body').removeAttr('aria-hidden'); // Restore background 85 }); 86 } 87 88 // Open: Focus management 89 function openPopup() { 90 $('body').attr('aria-hidden', 'true'); // Hide background from screen readers 91 $closeBtn.focus(); // Start focus on close for easy dismiss 92 $overlay.on('keydown', handleKeydown); // Trap tab inside 93 $(document).on('keydown.popup-esc', function(e) { // Global ESC 94 if (e.key === 'Escape' && $overlay.is(':visible')) { 95 closePopup(); 96 } 97 }); 98 } 99 100 // Close handlers 101 $overlay.on('click', function(e) { 102 if (e.target === this) closePopup(); 103 }); 104 $closeBtn.on('click', closePopup); 105 106 // Show with fade-in 107 $overlay.fadeIn(300, openPopup); 108});
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.