1/** 2 * Logic for the starting support popup. 3 * 4 * NOTE: The popup will now only be shown after the user performs an interaction 5 * (mouse move/wheel, click, touchstart, keyboard, or input). Listeners are 6 * removed once the popup is shown to avoid side-effects and to prevent the 7 * popup from appearing in thumbnails/screenshots. 8 */ 9export function show() { 10 // If the user has permanently dismissed the popup, don't show it. 11 if (localStorage.getItem('popupNeverShow') === 'true') return; 12 13 // Guard to ensure we only show once per page load 14 if (window.__startingPopupShownOnce) return; 15 16 // Inject animation styles (only once) 17 if (!document.getElementById('popup-animations-style')) { 18 const style = document.createElement('style'); 19 style.id = 'popup-animations-style'; 20 style.textContent = ` 21 @keyframes popupFadeIn { from { opacity: 0; } to { opacity: 1; } } 22 @keyframes popupFadeOut { from { opacity: 1; } to { opacity: 0; } } 23 @keyframes popupScaleIn { 24 from { transform: scale(0.9) translateY(20px); opacity: 0; } 25 to { transform: scale(1) translateY(0); opacity: 1; } 26 } 27 @keyframes popupScaleOut { 28 from { transform: scale(1) translateY(0); opacity: 1; } 29 to { transform: scale(0.9) translateY(-20px); opacity: 0; } 30 } 31 .popup-overlay-entering { animation: popupFadeIn 0.4s ease forwards; } 32 .popup-overlay-exiting { animation: popupFadeOut 0.3s ease forwards; } 33 .popup-modal-entering { animation: popupScaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } 34 .popup-modal-exiting { animation: popupScaleOut 0.3s ease forwards; } 35 `; 36 document.head.appendChild(style); 37 } 38 39 const closedInitially = localStorage.getItem('popupClosedInitally') === 'true'; 40 41 // Create a lazy-show function that builds and appends the modal when triggered 42 function createAndShowPopup() { 43 // Prevent double-show 44 if (window.__startingPopupShownOnce) return; 45 window.__startingPopupShownOnce = true; 46 47 // Create the overlay container 48 const overlay = document.createElement('div'); 49 overlay.id = 'starting-popup-overlay'; 50 overlay.className = 'popup-overlay-entering'; 51 overlay.style.cssText = ` 52 position: fixed; 53 top: 0; 54 left: 0; 55 width: 100%; 56 height: 100%; 57 background-color: rgba(0, 0, 0, 0.85); 58 display: flex; 59 justify-content: center; 60 align-items: center; 61 z-index: 10000; /* Ensure it's above everything else */ 62 backdrop-filter: blur(8px); 63 -webkit-backdrop-filter: blur(8px); 64 opacity: 0; 65 `; 66 67 // Create the modal content using existing mode-styled classes 68 const modal = document.createElement('div'); 69 modal.className = 'modal-content popup-modal-entering'; 70 modal.style.maxWidth = '420px'; 71 modal.style.padding = '30px'; 72 modal.style.opacity = '0'; 73 74 // Conditional checkbox for "Never show again" 75 let checkboxHtml = ''; 76 if (closedInitially) { 77 checkboxHtml = ` 78 <div class="checkbox-group" style="justify-content: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--mode-form-border);"> 79 <input type="checkbox" id="never-show-starting-popup"> 80 <label for="never-show-starting-popup" style="margin-bottom: 0; font-size: 0.9em; cursor: pointer;">Don't show this again</label> 81 </div> 82 `; 83 } 84 85 modal.innerHTML = ` 86 <h2 style="color: var(--mode-primary); margin-top: 0; font-size: 1.8em;">Support the Project!</h2> 87 <p style="color: var(--mode-label); line-height: 1.6; font-size: 1.05em; margin-bottom: 25px;"> 88 If you find this tool helpful, please consider giving it a <b>Like</b>! <br><br> 89 Your support helps us reach the <b>Hot Page</b> so more developers can discover and use these tools. 90 </p> 91 <div style="display: flex; gap: 12px; justify-content: center;"> 92 <button id="btn-liked" class="modal-close-button" style="flex: 1; padding: 12px;">I Liked!</button> 93 <button id="btn-later" class="modal-close-button" style="flex: 1; padding: 12px; background: transparent; border: 1px solid var(--mode-primary); color: var(--mode-primary);">Maybe later</button> 94 </div> 95 ${checkboxHtml} 96 `; 97 98 overlay.appendChild(modal); 99 document.body.appendChild(overlay); 100 101 // Common close logic 102 const handleDismiss = (shouldSaveInitial) => { 103 if (shouldSaveInitial) { 104 localStorage.setItem('popupClosedInitally', 'true'); 105 } 106 107 const neverCheckbox = document.getElementById('never-show-starting-popup'); 108 if (neverCheckbox && neverCheckbox.checked) { 109 localStorage.setItem('popupNeverShow', 'true'); 110 } 111 112 // Exit animations 113 overlay.classList.remove('popup-overlay-entering'); 114 modal.classList.remove('popup-modal-entering'); 115 overlay.classList.add('popup-overlay-exiting'); 116 modal.classList.add('popup-modal-exiting'); 117 118 overlay.addEventListener('animationend', () => { 119 overlay.remove(); 120 }, { once: true }); 121 }; 122 123 // Button Listeners 124 document.getElementById('btn-liked').addEventListener('click', () => { 125 const neverCheckbox = document.getElementById('never-show-starting-popup'); 126 if (neverCheckbox && neverCheckbox.checked) { 127 localStorage.setItem('popupNeverShow', 'true'); 128 } 129 130 // Change content to thank the user 131 modal.innerHTML = ` 132 <h2 style="color: var(--mode-primary); margin-top: 0; font-size: 1.8em;">Thank You!</h2> 133 <p style="color: var(--mode-label); line-height: 1.6; font-size: 1.1em;"> 134 We really appreciate your support! <br><br> 135 Closing in a moment... 136 </p> 137 <div style="font-size: 2em; margin-top: 10px;">â¤ï¸</div> 138 `; 139 140 // Set flag and close after delay 141 localStorage.setItem('popupClosedInitally', 'true'); 142 setTimeout(() => { 143 overlay.classList.remove('popup-overlay-entering'); 144 modal.classList.remove('popup-modal-entering'); 145 overlay.classList.add('popup-overlay-exiting'); 146 modal.classList.add('popup-modal-exiting'); 147 148 overlay.addEventListener('animationend', () => { 149 overlay.remove(); 150 }, { once: true }); 151 }, 2500); 152 }); 153 154 document.getElementById('btn-later').addEventListener('click', () => handleDismiss(false)); 155 } 156 157 // Remove registered listeners once triggered 158 function removeTriggerListeners() {
159 triggerEvents.forEach(({ target, type, handler }) => { 160 try { target.removeEventListener(type, handler, listenerOptions); } catch (e) { /* ignore */ } 161 }); 162 } 163 164 // When any of these interactions happen, show the popup once and cleanup listeners. 165 const listenerOptions = { passive: true, capture: true }; 166 let triggered = false; 167 168 function interactionHandler(e) { 169 if (triggered) return; 170 triggered = true; 171 removeTriggerListeners(); 172 // Small delay to avoid interfering with immediate UI events; also prevents popup in thumbnails. 173 setTimeout(createAndShowPopup, 100); 174 } 175 176 // Define a variety of reasonable user interactions to listen for 177 const triggerEvents = [ 178 { target: window, type: 'mousemove', handler: interactionHandler }, 179 { target: window, type: 'wheel', handler: interactionHandler }, 180 { target: window, type: 'keydown', handler: interactionHandler }, 181 { target: window, type: 'touchstart', handler: interactionHandler }, 182 { target: document, type: 'click', handler: interactionHandler }, 183 { target: document, type: 'input', handler: interactionHandler } 184 ]; 185 186 // Attach listeners 187 triggerEvents.forEach(({ target, type, handler }) => { 188 try { target.addEventListener(type, handler, listenerOptions); } catch (e) { /* ignore */ } 189 }); 190 191 // Safety: also ensure we don't wait forever â show popup after a long delay (e.g., 30s) 192 const LONG_TIMEOUT_MS = 30000; 193 const fallbackTimer = setTimeout(() => { 194 if (!window.__startingPopupShownOnce && !localStorage.getItem('popupNeverShow')) { 195 removeTriggerListeners(); 196 createAndShowPopup(); 197 } 198 }, LONG_TIMEOUT_MS); 199 200 // If user disabled popup permanently while on-page (rare), ensure listeners removed 201 const checkNeverShowInterval = setInterval(() => { 202 if (localStorage.getItem('popupNeverShow') === 'true') { 203 removeTriggerListeners(); 204 clearInterval(checkNeverShowInterval); 205 clearTimeout(fallbackTimer); 206 } 207 if (window.__startingPopupShownOnce) { 208 clearInterval(checkNeverShowInterval); 209 clearTimeout(fallbackTimer); 210 } 211 }, 1000); 212}
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.