(function(){ const THEME_KEY = 'force-monitor-theme'; const FULLSCREEN_INTENT_KEY = 'force-monitor-fullscreen-intent'; function byId(id){ return id ? document.getElementById(id) : null; } function getFullscreenIntent(){ try { return sessionStorage.getItem(FULLSCREEN_INTENT_KEY) === '1'; } catch (_) { return false; } } function setFullscreenIntent(enabled){ try { sessionStorage.setItem(FULLSCREEN_INTENT_KEY, enabled ? '1' : '0'); } catch (_) {} } function setTheme(theme, opts){ opts = opts || {}; const t = theme === 'light' ? 'light' : 'dark'; document.body.setAttribute('data-theme', t); try { localStorage.setItem(THEME_KEY, t); } catch (_) {} const btn = byId(opts.buttonId || 'theme-toggle'); if (btn) btn.textContent = t === 'light' ? 'Dark theme' : 'Light theme'; if (typeof opts.onChange === 'function') opts.onChange(t); return t; } function initTheme(opts){ opts = opts || {}; let theme = 'dark'; try { const stored = localStorage.getItem(THEME_KEY); if (stored === 'light' || stored === 'dark') theme = stored; else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) theme = 'light'; } catch (_) {} setTheme(theme, opts); const btn = byId(opts.buttonId || 'theme-toggle'); if (btn && !btn.dataset.themeBound) { btn.dataset.themeBound = '1'; btn.addEventListener('click', function(){ setTheme(document.body.getAttribute('data-theme') === 'light' ? 'dark' : 'light', opts); }); } return theme; } function updateFullscreenButton(buttonId){ const btn = byId(buttonId || 'fullscreen-toggle'); if (!btn) return; if (document.fullscreenElement) { btn.textContent = 'Exit fullscreen'; return; } btn.textContent = getFullscreenIntent() ? 'Restore fullscreen' : 'Enter fullscreen'; } async function requestFullscreenSafe(){ if (document.fullscreenElement) return true; if (!document.fullscreenEnabled) return false; try { await document.documentElement.requestFullscreen(); setFullscreenIntent(true); return true; } catch (err) { console.warn('Fullscreen restore/request blocked:', err); return false; } } async function toggleFullscreen(buttonId){ try { if (!document.fullscreenElement) { await requestFullscreenSafe(); } else { setFullscreenIntent(false); await document.exitFullscreen(); } } catch (err) { console.warn('Fullscreen error:', err); } finally { updateFullscreenButton(buttonId || 'fullscreen-toggle'); } } function bindFullscreenNavPersistence(){ if (document.documentElement.dataset.fsNavBound) return; document.documentElement.dataset.fsNavBound = '1'; document.addEventListener('click', function(ev){ const link = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null; if (!link) return; const href = link.getAttribute('href') || ''; const target = link.getAttribute('target') || ''; if (!href || href.startsWith('#') || target === '_blank' || link.hasAttribute('download')) return; try { const url = new URL(link.href, window.location.href); if (url.origin !== window.location.origin) return; } catch (_) { return; } if (document.fullscreenElement || getFullscreenIntent()) { setFullscreenIntent(true); } }, true); window.addEventListener('pageshow', function(){ document.querySelectorAll('#fullscreen-toggle, #fullscreen-btn').forEach(function(el){ updateFullscreenButton(el.id); }); }); } async function initFullscreen(opts){ opts = opts || {}; const buttonId = opts.buttonId || 'fullscreen-toggle'; const btn = byId(buttonId); if (btn && !btn.dataset.fsBound) { btn.dataset.fsBound = '1'; btn.addEventListener('click', function(){ toggleFullscreen(buttonId); }); } if (!document.documentElement.dataset.fsListenerBound) { document.documentElement.dataset.fsListenerBound = '1'; document.addEventListener('fullscreenchange', function(){ setFullscreenIntent(!!document.fullscreenElement); document.querySelectorAll('#fullscreen-toggle, #fullscreen-btn').forEach(function(el){ updateFullscreenButton(el.id); }); }); } bindFullscreenNavPersistence(); updateFullscreenButton(buttonId); if (getFullscreenIntent() && !document.fullscreenElement) { // Best effort only: some browsers require a fresh user gesture after navigation. requestAnimationFrame(function(){ requestFullscreenSafe().finally(function(){ updateFullscreenButton(buttonId); }); }); } } async function fetchJson(url, opts){ opts = opts || {}; const controller = new AbortController(); const timeoutMs = opts.timeoutMs || 8000; const timer = setTimeout(function(){ controller.abort(); }, timeoutMs); try { const res = await fetch(url, { method: opts.method || 'GET', headers: opts.headers || undefined, body: opts.body, cache: 'no-store', signal: controller.signal }); let data = null; try { data = await res.json(); } catch (_) { data = null; } if (!res.ok) { const err = new Error(data && data.error ? data.error : ('HTTP ' + res.status)); err.response = res; err.data = data; throw err; } return data; } finally { clearTimeout(timer); } } window.AppUI = { setTheme, initTheme, updateFullscreenButton, toggleFullscreen, initFullscreen, fetchJson, getFullscreenIntent, setFullscreenIntent }; })();