document.documentElement.classList.add('js'); function readThemePreference(key, fallback) { try { return localStorage.getItem(key) || fallback; } catch (error) { return fallback; } } const moduleName = document.documentElement.dataset.module || ''; const mainThemeMode = readThemePreference('nexus.theme', 'day'); const mainThemeAccent = readThemePreference('nexus.accent', 'logo'); const moduleThemeMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : mainThemeMode; const moduleThemeAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : mainThemeAccent; function normalizeThemeValue(value, allowed, fallback) { return allowed.includes(value) ? value : fallback; } function storedTheme() { const rawMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : readThemePreference('nexus.theme', 'day'); const rawAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : readThemePreference('nexus.accent', 'logo'); const mode = moduleName ? normalizeThemeValue(rawMode, ['inherit', 'day', 'night'], 'inherit') : normalizeThemeValue(rawMode, ['day', 'night'], 'day'); const accent = moduleName ? normalizeThemeValue(rawAccent, ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'], 'inherit') : normalizeThemeValue(rawAccent, ['logo', 'pink', 'cyan', 'orange', 'green'], 'logo'); return { mode, accent }; } function applyTheme(mode, accent, persist = true) { const allowedModes = moduleName ? ['inherit', 'day', 'night'] : ['day', 'night']; const allowedAccents = moduleName ? ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'] : ['logo', 'pink', 'cyan', 'orange', 'green']; const normalizedMode = normalizeThemeValue(mode, allowedModes, moduleName ? 'inherit' : 'day'); const normalizedAccent = normalizeThemeValue(accent, allowedAccents, moduleName ? 'inherit' : 'logo'); const effectiveMode = normalizedMode === 'inherit' ? mainThemeMode : normalizedMode; const effectiveAccent = normalizedAccent === 'inherit' ? mainThemeAccent : normalizedAccent; document.documentElement.dataset.theme = effectiveMode; document.documentElement.dataset.accent = effectiveAccent; try { if (persist) { if (moduleName) { if (normalizedMode === 'inherit') { localStorage.removeItem(`nexus.module.${moduleName}.theme`); } else { localStorage.setItem(`nexus.module.${moduleName}.theme`, normalizedMode); } if (normalizedAccent === 'inherit') { localStorage.removeItem(`nexus.module.${moduleName}.accent`); } else { localStorage.setItem(`nexus.module.${moduleName}.accent`, normalizedAccent); } } else { localStorage.setItem('nexus.theme', normalizedMode); localStorage.setItem('nexus.accent', normalizedAccent); } } } catch (error) { // Ignore blocked storage; the current page still receives the theme. } return { mode: normalizedMode, accent: normalizedAccent }; } const initialTheme = applyTheme(moduleThemeMode, moduleThemeAccent, false); const themeModeSelect = document.querySelector('[data-theme-mode]'); const themeAccentSelect = document.querySelector('[data-theme-accent]'); if (themeModeSelect) { themeModeSelect.value = initialTheme.mode; themeModeSelect.addEventListener('change', () => { const current = storedTheme(); applyTheme(themeModeSelect.value, current.accent); }); } if (themeAccentSelect) { themeAccentSelect.value = initialTheme.accent; themeAccentSelect.addEventListener('change', () => { const current = storedTheme(); applyTheme(current.mode, themeAccentSelect.value); }); } for (const element of document.querySelectorAll('[data-reveal]')) { element.classList.add('reveal'); } const setupTabs = document.querySelectorAll('[data-setup-tab-target]'); if (setupTabs.length > 0) { const setupPanels = document.querySelectorAll('.setup-db-panel'); const setupControls = document.querySelectorAll('.setup-db-panel input, .setup-db-panel select, .setup-db-panel textarea'); for (const control of setupControls) { if (control.required) { control.dataset.setupRequired = 'true'; } } const activateSetupTab = (targetId) => { for (const tab of setupTabs) { const isActive = tab.dataset.setupTabTarget === targetId; tab.classList.toggle('is-active', isActive); tab.setAttribute('aria-selected', isActive ? 'true' : 'false'); } for (const panel of setupPanels) { const isActive = panel.id === targetId; panel.hidden = !isActive; for (const control of panel.querySelectorAll('input, select, textarea')) { if (control.dataset.setupRequired === 'true') { control.required = isActive; } } } }; for (const tab of setupTabs) { tab.addEventListener('click', () => { activateSetupTab(tab.dataset.setupTabTarget); }); } const activeSetupTab = document.querySelector('[data-setup-tab-target].is-active') || setupTabs[0]; activateSetupTab(activeSetupTab.dataset.setupTabTarget); } window.NexusModal = (() => { const openModals = new Set(); const syncBodyState = () => { document.body.classList.toggle('has-modal-open', openModals.size > 0); }; const create = (root, options = {}) => { if (!root) { return null; } const modal = { root, isOpen: () => root.classList.contains('is-open'), open() { root.classList.add('is-open'); root.setAttribute('aria-hidden', 'false'); openModals.add(root); syncBodyState(); if (typeof options.onOpen === 'function') { options.onOpen(); } const focusTarget = options.initialFocus ? root.querySelector(options.initialFocus) : null; if (focusTarget instanceof HTMLElement) { window.setTimeout(() => focusTarget.focus(), 20); } }, close() { root.classList.remove('is-open'); root.setAttribute('aria-hidden', 'true'); openModals.delete(root); syncBodyState(); if (typeof options.onClose === 'function') { options.onClose(); } }, }; if (!root.dataset.modalBound) { root.addEventListener('click', (event) => { if (event.target === root) { modal.close(); } }); root.dataset.modalBound = '1'; } return modal; }; document.addEventListener('keydown', (event) => { if (event.key !== 'Escape') { return; } const active = Array.from(openModals).pop(); if (!active) { return; } active.classList.remove('is-open'); active.setAttribute('aria-hidden', 'true'); openModals.delete(active); syncBodyState(); }); return { create }; })(); (() => { const root = document.getElementById('nexus-debug-root'); const dataNode = document.getElementById('nexus-debug-data'); if (!root || !dataNode) { return; } let payload = { enabled: false, entries: [] }; try { const parsed = JSON.parse(dataNode.textContent || '{}'); if (parsed && typeof parsed === 'object') { payload = parsed; } } catch (error) { payload = { enabled: false, entries: [] }; } const enabled = !!payload.enabled && document.body.dataset.nexusDebugEnabled === '1'; const isAdmin = document.body.dataset.nexusDebugAdmin === '1'; const entries = Array.isArray(payload.entries) ? payload.entries.slice(0, 250) : []; let sequence = entries.length; const debugBus = window.__nexusDebugBus || { enabled: false, listener: null }; debugBus.enabled = enabled; window.__nexusDebugBus = debugBus; if (!isAdmin || !enabled) { return; } const bugSvg = ''; root.innerHTML = ` `; const bugButton = root.querySelector('.nexus-debug-bug'); const badge = root.querySelector('.nexus-debug-badge'); const popup = root.querySelector('.nexus-debug-popup'); const closeButton = root.querySelector('[data-debug-close]'); const reloadButton = root.querySelector('[data-debug-reload]'); const copyButton = root.querySelector('[data-debug-copy]'); const clearButton = root.querySelector('[data-debug-clear]'); const list = root.querySelector('[data-debug-list]'); const updateBadge = () => { if (!badge) { return; } badge.textContent = String(entries.length); badge.hidden = entries.length === 0; }; const normalizeEntry = (entry) => { sequence += 1; return { id: entry && entry.id ? entry.id : `nexus-debug-${sequence}`, source: entry && entry.source ? String(entry.source) : 'nexus', type: entry && entry.type ? String(entry.type) : 'debug', label: entry && entry.label ? String(entry.label) : '', at: entry && (entry.at || entry.time) ? String(entry.at || entry.time) : new Date().toISOString(), payload: entry && typeof entry === 'object' ? entry : { value: entry }, }; }; const renderEntries = () => { if (!list) { return; } updateBadge(); if (!entries.length) { list.innerHTML = '
${escapeHtml(payloadText)}