(() => { const assetTokenStorageKey = "chesscubing-arena-asset-token"; let viewportStarted = false; let audioContext = null; let authModalMessageHandler = null; let menuScrollStarted = false; let menuLastScrollY = 0; let menuAnimationFrame = 0; let appliedBodyStateClasses = []; function syncViewportHeight() { const visibleHeight = window.visualViewport?.height ?? window.innerHeight; const viewportTopOffset = window.visualViewport?.offsetTop ?? 0; const viewportHeight = Math.max( visibleHeight + viewportTopOffset, window.innerHeight, document.documentElement.clientHeight, ); document.documentElement.style.setProperty("--app-visible-height", `${Math.round(visibleHeight)}px`); document.documentElement.style.setProperty("--app-viewport-top", `${Math.round(viewportTopOffset)}px`); document.documentElement.style.setProperty("--app-viewport-height", `${Math.round(viewportHeight)}px`); } function startViewport() { if (viewportStarted) { return; } viewportStarted = true; syncViewportHeight(); window.addEventListener("load", syncViewportHeight); window.addEventListener("resize", syncViewportHeight); window.addEventListener("scroll", syncViewportHeight, { passive: true }); window.addEventListener("pageshow", syncViewportHeight); window.addEventListener("orientationchange", syncViewportHeight); window.visualViewport?.addEventListener("resize", syncViewportHeight); window.visualViewport?.addEventListener("scroll", syncViewportHeight); window.setTimeout(syncViewportHeight, 0); window.setTimeout(syncViewportHeight, 150); window.setTimeout(syncViewportHeight, 400); window.requestAnimationFrame(() => window.requestAnimationFrame(syncViewportHeight)); } function setMenuHidden(hidden) { document.body.classList.toggle("site-menu-hidden", hidden); } function syncMenuVisibility() { menuAnimationFrame = 0; const menu = document.querySelector(".site-menu-shell"); if (!menu) { setMenuHidden(false); menuLastScrollY = window.scrollY || window.pageYOffset || 0; return; } const modalIsOpen = document.querySelector(".modal:not(.hidden)"); if (modalIsOpen) { setMenuHidden(false); menuLastScrollY = window.scrollY || window.pageYOffset || 0; return; } const currentScrollY = Math.max(window.scrollY || window.pageYOffset || 0, 0); const delta = currentScrollY - menuLastScrollY; if (currentScrollY <= 24 || delta < -10) { setMenuHidden(false); } else if (delta > 10 && currentScrollY > 120) { setMenuHidden(true); } menuLastScrollY = currentScrollY; } function queueMenuVisibilitySync() { if (menuAnimationFrame) { return; } menuAnimationFrame = window.requestAnimationFrame(syncMenuVisibility); } function startMenuScrollTracking() { if (menuScrollStarted) { queueMenuVisibilitySync(); return; } menuScrollStarted = true; menuLastScrollY = window.scrollY || window.pageYOffset || 0; window.addEventListener("scroll", queueMenuVisibilitySync, { passive: true }); window.addEventListener("pageshow", queueMenuVisibilitySync); window.addEventListener("resize", queueMenuVisibilitySync); window.addEventListener("hashchange", queueMenuVisibilitySync); window.setTimeout(queueMenuVisibilitySync, 0); window.setTimeout(queueMenuVisibilitySync, 150); } function getAudioContext() { const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) { return null; } if (!audioContext) { try { audioContext = new AudioContextClass(); } catch { return null; } } return audioContext; } function primeAudio() { const context = getAudioContext(); if (!context || context.state === "running") { return; } context.resume().catch(() => undefined); } function playCubePhaseAlert() { primeAudio(); const context = getAudioContext(); if (!context || context.state !== "running") { return false; } const pattern = [ { frequency: 740, offset: 0, duration: 0.12, gain: 0.035 }, { frequency: 988, offset: 0.18, duration: 0.12, gain: 0.04 }, { frequency: 1318, offset: 0.36, duration: 0.2, gain: 0.05 }, ]; const startAt = context.currentTime + 0.02; pattern.forEach(({ frequency, offset, duration, gain }) => { const oscillator = context.createOscillator(); const envelope = context.createGain(); const toneStartAt = startAt + offset; oscillator.type = "triangle"; oscillator.frequency.setValueAtTime(frequency, toneStartAt); envelope.gain.setValueAtTime(0.0001, toneStartAt); envelope.gain.exponentialRampToValueAtTime(gain, toneStartAt + 0.02); envelope.gain.exponentialRampToValueAtTime(0.0001, toneStartAt + duration); oscillator.connect(envelope); envelope.connect(context.destination); oscillator.start(toneStartAt); oscillator.stop(toneStartAt + duration + 0.03); }); return true; } window.chesscubingPage = { setBodyState(page, bodyClass) { if (page) { document.body.dataset.page = page; } else { delete document.body.dataset.page; } for (const className of appliedBodyStateClasses) { document.body.classList.remove(className); } appliedBodyStateClasses = (bodyClass || "") .split(/\s+/) .map((value) => value.trim()) .filter((value) => value.length > 0); if (appliedBodyStateClasses.length > 0) { document.body.classList.add(...appliedBodyStateClasses); } queueMenuVisibilitySync(); }, }; window.chesscubingViewport = { start: startViewport, }; window.chesscubingMenu = { start: startMenuScrollTracking, sync: queueMenuVisibilitySync, }; window.chesscubingStorage = { getMatchState(storageKey, windowNameKey) { try { const raw = window.localStorage.getItem(storageKey); if (raw) { return raw; } } catch { } try { if (!window.name || !window.name.startsWith(windowNameKey)) { return null; } return window.name.slice(windowNameKey.length); } catch { return null; } }, setMatchState(storageKey, windowNameKey, value) { try { window.name = `${windowNameKey}${value}`; } catch { } try { window.localStorage.setItem(storageKey, value); } catch { } }, clearMatchState(storageKey, windowNameKey) { try { window.localStorage.removeItem(storageKey); } catch { } try { if (window.name && window.name.startsWith(windowNameKey)) { window.name = ""; } } catch { } }, }; window.chesscubingAudio = { prime: primeAudio, playCubePhaseAlert, }; window.chesscubingBrowser = { async forceRefresh(path) { const refreshToken = `${Date.now()}`; try { window.sessionStorage.setItem(assetTokenStorageKey, refreshToken); } catch { } if ("caches" in window) { try { const cacheKeys = await window.caches.keys(); await Promise.all(cacheKeys.map((cacheKey) => window.caches.delete(cacheKey))); } catch { } } if ("serviceWorker" in navigator) { try { const registrations = await navigator.serviceWorker.getRegistrations(); await Promise.all(registrations.map((registration) => registration.update().catch(() => undefined))); await Promise.all(registrations.map((registration) => registration.unregister().catch(() => undefined))); } catch { } } const targetUrl = new URL(path, window.location.href); targetUrl.searchParams.set("refresh", refreshToken); window.location.replace(targetUrl.toString()); }, }; window.chesscubingAuthModal = { registerListener(dotNetReference) { if (authModalMessageHandler) { window.removeEventListener("message", authModalMessageHandler); } authModalMessageHandler = (event) => { if (event.origin !== window.location.origin) { return; } const data = event.data; if (!data || data.source !== "chesscubing-auth-modal" || typeof data.status !== "string") { return; } dotNetReference.invokeMethodAsync("HandleAuthModalMessage", data.status).catch(() => undefined); }; window.addEventListener("message", authModalMessageHandler); }, unregisterListener() { if (!authModalMessageHandler) { return; } window.removeEventListener("message", authModalMessageHandler); authModalMessageHandler = null; }, notifyParent(status) { if (!window.parent || window.parent === window) { return; } window.parent.postMessage( { source: "chesscubing-auth-modal", status, }, window.location.origin, ); }, }; startViewport(); startMenuScrollTracking(); })();