1/** 2 * FrenzyGo â page navigation loader (progress bar + branded ring overlay). 3 * Uses sessionStorage so the loader survives full page navigations (mobile + desktop). 4 */ 5(function (global) { 6 'use strict'; 7 8 var NAV_KEY = 'fg-page-nav'; 9 var NAV_MAX_AGE_MS = 45000; 10 var OVERLAY_DELAY_MS = 120; 11 var MOBILE_OVERLAY_DELAY_MS = 0; 12 var MIN_VISIBLE_MS = 220; 13 14 var state = { 15 active: false, 16 overlayTimer: null, 17 startedAt: 0, 18 root: null, 19 bound: false, 20 lastNavAt: 0, 21 }; 22 23 function storageGet(key) { 24 try { 25 return global.sessionStorage.getItem(key); 26 } catch (e) { 27 return null; 28 } 29 } 30 31 function storageSet(key, value) { 32 try { 33 global.sessionStorage.setItem(key, value); 34 } catch (e) {} 35 } 36 37 function storageRemove(key) { 38 try { 39 global.sessionStorage.removeItem(key); 40 } catch (e) {} 41 } 42 43 function prefersReducedMotion() { 44 return global.matchMedia && global.matchMedia('(prefers-reduced-motion: reduce)').matches; 45 } 46 47 function isMobileViewport() { 48 return global.matchMedia && global.matchMedia('(max-width: 767px)').matches; 49 } 50 51 function isLoaderPage() { 52 var body = global.document && global.document.body; 53 if (!body) return false; 54 return body.classList.contains('fg-app') || body.classList.contains('fg-auth'); 55 } 56 57 function readPendingNavigation() { 58 var raw = storageGet(NAV_KEY); 59 if (!raw) return false; 60 var ts = Number(raw); 61 if (!ts || Number.isNaN(ts)) { 62 storageRemove(NAV_KEY); 63 return false; 64 } 65 if (Date.now() - ts > NAV_MAX_AGE_MS) { 66 storageRemove(NAV_KEY); 67 return false; 68 } 69 return true; 70 } 71 72 function buildRoot() { 73 var doc = global.document; 74 var root = doc.createElement('div'); 75 root.id = 'fg-page-loader-root'; 76 root.className = 'fg-page-loader'; 77 root.setAttribute('aria-hidden', 'true'); 78 root.setAttribute('aria-live', 'polite'); 79 root.setAttribute('role', 'status'); 80 81 var bar = doc.createElement('div'); 82 bar.className = 'fg-page-loader__bar'; 83 bar.setAttribute('aria-hidden', 'true'); 84 85 var overlay = doc.createElement('div'); 86 overlay.className = 'fg-page-loader__overlay'; 87 overlay.setAttribute('aria-hidden', 'true'); 88 89 var brand = doc.createElement('div'); 90 brand.className = 'fg-page-loader__brand'; 91 92 var spinner = doc.createElement('div'); 93 spinner.className = 'fg-page-loader__spinner'; 94 spinner.setAttribute('aria-hidden', 'true'); 95 96 var ring = doc.createElement('div'); 97 ring.className = 'fg-page-loader__ring'; 98 spinner.appendChild(ring); 99 100 var label = doc.createElement('p'); 101 label.className = 'fg-page-loader__label'; 102 label.textContent = 'Loadingâ¦'; 103 104 brand.appendChild(spinner); 105 brand.appendChild(label); 106 overlay.appendChild(brand); 107 root.appendChild(bar); 108 root.appendChild(overlay); 109 return root; 110 } 111 112 function ensureRoot() { 113 if (state.root) return state.root; 114 var doc = global.document; 115 if (!doc || !doc.body) return null; 116 117 var existing = doc.getElementById('fg-page-loader-root'); 118 if (existing) { 119 state.root = existing; 120 return existing; 121 } 122 123 var root = buildRoot(); 124 doc.body.appendChild(root); 125 state.root = root; 126 return root; 127 } 128 129 function clearOverlayTimer() { 130 if (state.overlayTimer) { 131 global.clearTimeout(state.overlayTimer); 132 state.overlayTimer = null; 133 } 134 } 135 136 function markNavigationPending() { 137 storageSet(NAV_KEY, String(Date.now())); 138 } 139 140 function clearNavigationPending() { 141 storageRemove(NAV_KEY); 142 } 143 144 function scheduleOverlay(immediate) { 145 clearOverlayTimer(); 146 if (prefersReducedMotion()) return; 147 148 var delay = immediate 149 ? 0 150 : isMobileViewport() 151 ? MOBILE_OVERLAY_DELAY_MS 152 : OVERLAY_DELAY_MS; 153 154 state.overlayTimer = global.setTimeout(function () { 155 if (state.active && state.root) { 156 state.root.classList.add('is-overlay'); 157 } 158 }, delay); 159 } 160 161 function start(opts) { 162 opts = opts || {}; 163 if (!isLoaderPage()) return; 164 165 var root = ensureRoot(); 166 if (!root) return; 167 168 if (!state.active) { 169 state.active = true; 170 state.startedAt = Date.now(); 171 } 172 173 root.classList.remove('is-done', 'is-finishing'); 174 root.classList.add('is-active'); 175 scheduleOverlay(!!opts.immediateOverlay); 176 } 177 178 function finish() { 179 clearNavigationPending(); 180 181 if (!state.root || !state.active) { 182 if (state.root) { 183 state.root.classList.remove('is-active', 'is-overlay', 'is-finishing'); 184 state.root.classList.add('is-done'); 185 } 186 state.active = false;
187 clearOverlayTimer(); 188 return; 189 } 190 191 clearOverlayTimer(); 192 var elapsed = Date.now() - state.startedAt; 193 var wait = Math.max(0, MIN_VISIBLE_MS - elapsed); 194 195 global.setTimeout(function () { 196 if (!state.root) return; 197 state.root.classList.remove('is-active', 'is-overlay'); 198 state.root.classList.add('is-finishing'); 199 global.setTimeout(function () { 200 if (!state.root) return; 201 state.root.classList.remove('is-finishing'); 202 state.root.classList.add('is-done'); 203 state.active = false; 204 }, 280); 205 }, wait); 206 } 207 208 function sameOriginHref(href) { 209 try { 210 var url = new URL(href, global.location.href); 211 return url.origin === global.location.origin; 212 } catch (e) { 213 return false; 214 } 215 } 216 217 function shouldNavigate(el, href, ev) { 218 if (!href || href.indexOf('#') === 0) return false; 219 if (ev && (ev.defaultPrevented || ev.button !== 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey)) { 220 return false; 221 } 222 if (el.getAttribute('target') === '_blank') return false; 223 if (el.hasAttribute('download')) return false; 224 if (el.getAttribute('rel') === 'external') return false; 225 if (el.dataset && el.dataset.fgNoLoader === 'true') return false; 226 if (/^(mailto:|tel:|javascript:)/i.test(href)) return false; 227 if (!sameOriginHref(href)) return false; 228 229 try { 230 var next = new URL(href, global.location.href); 231 if (next.pathname === global.location.pathname && next.search === global.location.search) { 232 return false; 233 } 234 } catch (e2) { 235 return false; 236 } 237 return true; 238 } 239 240 function findLink(el) { 241 while (el && el !== global.document.body) { 242 if (el.tagName === 'A' && el.href) return el; 243 el = el.parentElement; 244 } 245 return null; 246 } 247 248 function highlightNavItem(link) { 249 if (!link || !link.closest) return; 250 var item = link.closest('.fixed-footer.fg-bottom-nav .item'); 251 if (!item) return; 252 item.classList.add('fg-nav-tap-active'); 253 global.setTimeout(function () { 254 item.classList.remove('fg-nav-tap-active'); 255 }, 200); 256 } 257 258 function beginNavigation() { 259 var now = Date.now(); 260 if (now - state.lastNavAt < 450) return; 261 state.lastNavAt = now; 262 markNavigationPending(); 263 start({ immediateOverlay: true }); 264 } 265 266 function onPointerUp(ev) { 267 var link = findLink(ev.target); 268 if (!link) return; 269 if (!shouldNavigate(link, link.getAttribute('href') || link.href, ev)) return; 270 highlightNavItem(link); 271 beginNavigation(); 272 } 273 274 function onSubmit(ev) { 275 var form = ev.target; 276 if (!form || form.tagName !== 'FORM') return; 277 if (form.dataset && form.dataset.fgNoLoader === 'true') return; 278 if (form.getAttribute('target') === '_blank') return; 279 var method = (form.getAttribute('method') || 'get').toLowerCase(); 280 if (method === 'dialog') return; 281 if (form.dataset && (form.dataset.fgAjax === 'true' || form.classList.contains('fg-ajax-form'))) { 282 return; 283 } 284 beginNavigation(); 285 } 286 287 function isReloadNavigation() { 288 try { 289 var nav = performance.getEntriesByType && performance.getEntriesByType('navigation')[0]; 290 return !!(nav && nav.type === 'reload'); 291 } catch (e) { 292 return false; 293 } 294 } 295 296 function clearPendingNavigation() { 297 storageRemove(NAV_KEY); 298 } 299 300 function onPageReady() { 301 if (isReloadNavigation()) { 302 clearPendingNavigation(); 303 } 304 finish(); 305 } 306 307 function resumePendingNavigation() { 308 if (isReloadNavigation()) { 309 clearPendingNavigation(); 310 return; 311 } 312 if (!readPendingNavigation()) return; 313 start({ immediateOverlay: true }); 314 } 315 316 function bindEvents() { 317 if (state.bound) return; 318 state.bound = true; 319 global.document.addEventListener('click', onPointerUp, true); 320 global.addEventListener('pageshow', onPageReady); 321 global.addEventListener('load', onPageReady); 322 } 323 324 function init() { 325 if (!global.document || !global.document.body) return; 326 if (!isLoaderPage()) return; 327 328 ensureRoot(); 329 resumePendingNavigation(); 330 bindEvents(); 331 332 global.FGPageLoader = { 333 start: start, 334 finish: finish, 335 beginNavigation: beginNavigation, 336 }; 337 } 338 339 function boot() { 340 if (!global.document) return; 341 if (global.document.body && isLoaderPage() && readPendingNavigation() && !isReloadNavigation()) { 342 ensureRoot(); 343 start({ immediateOverlay: true }); 344 } 345 if (global.document.readyState === 'loading') { 346 global.document.addEventListener('DOMContentLoaded', init); 347 } else { 348 init(); 349 } 350 } 351 352 boot(); 353})(typeof window !== 'undefined' ? window : this);
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.