1/** 2 * Web Development Offer Popup Script 3 */ 4(function () { 5 'use strict'; 6 7 var STORAGE_KEY = 'stOfferPopupMinimized'; 8 var SHOWN_KEY = 'stOfferPopupShownAt'; 9 var SHOW_DELAY_MS = 2500; 10 var HIDE_TRANSITION_MS = 450; 11 var RESURFACE_TTL_MS = 24 * 60 * 60 * 1000; 12 13 var popup = document.getElementById('stOfferPopup'); 14 var bubble = document.getElementById('stOfferBubble'); 15 16 var timerEls = document.querySelectorAll('.st-offer-timer-value, .st-theme-coupon-timer-value'); 17 18 if (timerEls.length) { 19 var pad = function (num) { 20 return (num < 10 ? '0' : '') + num; 21 }; 22 23 var cell = function (val, unit) { 24 return '<span class="st-offer-timer-cell"><b>' + val + '</b><i>' + unit + '</i></span>'; 25 }; 26 27 var tickTimer = function () { 28 var now = Date.now(); 29 30 Array.prototype.forEach.call(timerEls, function (el) { 31 var deadline = parseInt(el.getAttribute('data-deadline'), 10) * 1000; 32 var diff = deadline - now; 33 var wrap = el.closest('.st-offer-popup-timer, .st-theme-coupon-timer'); 34 35 if (isNaN(deadline) || diff <= 0) { 36 if (wrap) { 37 wrap.hidden = true; 38 } 39 return; 40 } 41 42 var days = Math.floor(diff / 86400000); 43 var hours = Math.floor((diff % 86400000) / 3600000); 44 var mins = Math.floor((diff % 3600000) / 60000); 45 var secs = Math.floor((diff % 60000) / 1000); 46 47 el.innerHTML = 48 cell(pad(days), 'd') + 49 '<span class="st-offer-timer-sep">:</span>' + 50 cell(pad(hours), 'h') + 51 '<span class="st-offer-timer-sep">:</span>' + 52 cell(pad(mins), 'm') + 53 '<span class="st-offer-timer-sep">:</span>' + 54 cell(pad(secs), 's'); 55 }); 56 }; 57 58 tickTimer(); 59 window.setInterval(tickTimer, 1000); 60 } 61 62 if (!popup || !bubble) { 63 return; 64 } 65 66 function readStore(key) { 67 try { 68 return window.localStorage.getItem(key); 69 } catch (e) { 70 return null; 71 } 72 } 73 74 function writeStore(key, value) { 75 try { 76 window.localStorage.setItem(key, value); 77 } catch (e) { 78 /* localStorage unavailable â state just won't persist */ 79 } 80 } 81 82 function removeStore(key) { 83 try { 84 window.localStorage.removeItem(key); 85 } catch (e) { 86 /* localStorage unavailable â state just won't persist */ 87 } 88 } 89 90 function isMinimized() { 91 return readStore(STORAGE_KEY) === '1'; 92 } 93 94 function setMinimized(state) { 95 if (state) { 96 writeStore(STORAGE_KEY, '1'); 97 } else { 98 removeStore(STORAGE_KEY); 99 } 100 } 101 102 function hasSeenOffer() { 103 var shownAt = parseInt(readStore(SHOWN_KEY), 10); 104 return !isNaN(shownAt) && (Date.now() - shownAt) < RESURFACE_TTL_MS; 105 } 106 107 function markOfferShown() { 108 writeStore(SHOWN_KEY, String(Date.now())); 109 } 110 111 function showBubble() { 112 bubble.hidden = false; 113 void bubble.offsetWidth; 114 bubble.classList.add('is-visible'); 115 } 116 117 function showPopup() { 118 setMinimized(false); 119 markOfferShown(); 120 121 bubble.classList.remove('is-visible'); 122 window.setTimeout(function () { 123 bubble.hidden = true; 124 }, 400); 125 126 popup.hidden = false; 127 // Force reflow so the transition runs after unhide. 128 void popup.offsetWidth; 129 popup.classList.add('is-visible'); 130 } 131 132 function minimizePopup() { 133 setMinimized(true); 134 135 popup.classList.remove('is-visible'); 136 137 window.setTimeout(function () { 138 popup.hidden = true; 139 showBubble(); 140 bubble.focus(); 141 }, HIDE_TRANSITION_MS); 142 } 143 144 var closeBtn = document.getElementById('stOfferPopupClose'); 145 146 if (closeBtn) { 147 closeBtn.addEventListener('click', minimizePopup); 148 } 149 150 bubble.addEventListener('click', showPopup); 151 152 // Allow Escape to minimize while focus is inside the popup. 153 popup.addEventListener('keydown', function (event) { 154 if (event.key === 'Escape' || event.keyCode === 27) { 155 minimizePopup(); 156 } 157 }); 158 159 // Info icon: click copies the email to the clipboard and shows feedback. 160 var infoWrap = popup.querySelector('.st-offer-info'); 161 162 if (infoWrap) { 163 var infoBtn = infoWrap.querySelector('.st-offer-info-btn'); 164 var infoTip = infoWrap.querySelector('.st-offer-info-tip'); 165 var copiedTimer = null; 166 167 function fallbackCopy(text, done) { 168 var ta = document.createElement('textarea'); 169 ta.value = text; 170 ta.setAttribute('readonly', ''); 171 ta.style.position = 'fixed'; 172 ta.style.top = '-9999px'; 173 document.body.appendChild(ta); 174 ta.select(); 175 try { 176 document.execCommand('copy'); 177 done(); 178 } catch (e) { 179 /* copy failed â keep default tooltip */ 180 } 181 document.body.removeChild(ta); 182 } 183 184 function showCopied() { 185 infoWrap.classList.add('is-open'); 186 if (infoTip) { 187 infoTip.classList.add('is-copied'); 188 } 189 window.clearTimeout(copiedTimer); 190 copiedTimer = window.setTimeout(function () { 191 if (infoTip) { 192 infoTip.classList.remove('is-copied'); 193 } 194 infoWrap.classList.remove('is-open'); 195 }, 2000); 196 } 197 198 if (infoBtn) { 199 infoBtn.addEventListener('click', function () { 200 infoBtn.setAttribute('aria-expanded', 'true'); 201 202 var email = infoBtn.getAttribute('data-email') || ''; 203 if (!email) { 204 return; 205 } 206 207 if (navigator.clipboard && navigator.clipboard.writeText) { 208 navigator.clipboard.writeText(email).then(showCopied, function () { 209 fallbackCopy(email, showCopied); 210 }); 211 } else { 212 fallbackCopy(email, showCopied); 213 } 214 }); 215 } 216 217 // Reset state when clicking anywhere outside it. 218 document.addEventListener('click', function (event) { 219 if (infoWrap.classList.contains('is-open') && !infoWrap.contains(event.target)) { 220 infoWrap.classList.remove('is-open'); 221 if (infoTip) { 222 infoTip.classList.remove('is-copied'); 223 } 224 if (infoBtn) { 225 infoBtn.setAttribute('aria-expanded', 'false'); 226 } 227 } 228 }); 229 } 230 231 // On each page load, restore the state from the previous page: 232 // the popup auto-opens only on the first view within 24h â after 233 // that (or once minimized) only the bubble is shown.
234 if (isMinimized() || hasSeenOffer()) { 235 popup.hidden = true; 236 showBubble(); 237 } else { 238 window.setTimeout(showPopup, SHOW_DELAY_MS); 239 } 240})();
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.