feat: Phase 2 frontend shell
Sidebar with collapsible nav, dark/light theme tokens, and localStorage-backed state for collapse/theme/active view. Chaos factor UI is display-only pending Phase 3 backend wiring.
This commit is contained in:
@@ -1 +1,85 @@
|
||||
// Mythic Oracle — entry, sidebar routing, campaign context
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
sidebarCollapsed: 'mo-sidebar-collapsed',
|
||||
activeView: 'mo-active-view',
|
||||
theme: 'mo-theme',
|
||||
};
|
||||
|
||||
const CHAOS_MIN = 1;
|
||||
const CHAOS_MAX = 9;
|
||||
|
||||
function initSidebarCollapse() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggle = document.getElementById('collapseToggle');
|
||||
|
||||
const collapsed = localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === 'true';
|
||||
sidebar.classList.toggle('collapsed', collapsed);
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const isCollapsed = sidebar.classList.toggle('collapsed');
|
||||
localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, isCollapsed);
|
||||
});
|
||||
}
|
||||
|
||||
function initNavRouting() {
|
||||
const navItems = document.querySelectorAll('.nav-item[data-view]');
|
||||
const views = document.querySelectorAll('.view');
|
||||
|
||||
function activateView(viewId) {
|
||||
navItems.forEach((item) => {
|
||||
item.classList.toggle('active', item.dataset.view === viewId);
|
||||
});
|
||||
views.forEach((view) => {
|
||||
view.classList.toggle('active', view.id === `view-${viewId}`);
|
||||
});
|
||||
}
|
||||
|
||||
navItems.forEach((item) => {
|
||||
item.addEventListener('click', () => {
|
||||
const viewId = item.dataset.view;
|
||||
activateView(viewId);
|
||||
localStorage.setItem(STORAGE_KEYS.activeView, viewId);
|
||||
});
|
||||
});
|
||||
|
||||
const savedView = localStorage.getItem(STORAGE_KEYS.activeView);
|
||||
const validView = savedView && document.getElementById(`view-${savedView}`);
|
||||
activateView(validView ? savedView : 'oracle');
|
||||
}
|
||||
|
||||
function initThemeToggle() {
|
||||
const toggle = document.getElementById('themeToggle');
|
||||
const root = document.documentElement;
|
||||
|
||||
const savedTheme = localStorage.getItem(STORAGE_KEYS.theme) || 'dark';
|
||||
root.setAttribute('data-theme', savedTheme);
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const nextTheme = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
root.setAttribute('data-theme', nextTheme);
|
||||
localStorage.setItem(STORAGE_KEYS.theme, nextTheme);
|
||||
});
|
||||
}
|
||||
|
||||
function initChaosFactorControls() {
|
||||
const value = document.getElementById('chaosValue');
|
||||
const minus = document.getElementById('chaosMinus');
|
||||
const plus = document.getElementById('chaosPlus');
|
||||
|
||||
function setValue(next) {
|
||||
value.textContent = Math.min(CHAOS_MAX, Math.max(CHAOS_MIN, next));
|
||||
}
|
||||
|
||||
minus.addEventListener('click', () => setValue(Number(value.textContent) - 1));
|
||||
plus.addEventListener('click', () => setValue(Number(value.textContent) + 1));
|
||||
}
|
||||
|
||||
function init() {
|
||||
initSidebarCollapse();
|
||||
initNavRouting();
|
||||
initThemeToggle();
|
||||
initChaosFactorControls();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
Reference in New Issue
Block a user