diff --git a/Overlord-Server/public/assets/main.css b/Overlord-Server/public/assets/main.css index 21b9968..085af73 100644 --- a/Overlord-Server/public/assets/main.css +++ b/Overlord-Server/public/assets/main.css @@ -3342,6 +3342,150 @@ body.sb-open #sb-backdrop { display: block; } #notify-toggle.text-slate-300 { color: #cbd5e1; } #notify-toggle.border-emerald-500\/40 { border-color: rgba(16, 185, 129, 0.4); } +/* ============================================================ + TOPBAR — 3-column grid (brand · centered nav · utility) + ============================================================ */ + +.topbar-grid { + display: grid; + grid-template-columns: 1fr auto 1fr; + grid-template-areas: "left center right"; + align-items: center; + gap: 12px; + width: 100%; +} +.topbar-left { + grid-area: left; + display: flex; + align-items: center; + gap: 12px; + justify-self: start; + min-width: 0; +} +.topbar-center { + grid-area: center; + display: flex; + align-items: center; + justify-content: center; + justify-self: center; + min-width: 0; +} +.topbar-right { + grid-area: right; + display: flex; + align-items: center; + gap: 8px; + justify-self: end; + min-width: 0; +} + +@media (max-width: 767px) { + .topbar-grid { + grid-template-columns: 1fr auto; + grid-template-areas: + "left right" + "center center"; + } + .topbar-center { justify-self: stretch; } + .topbar-center.hidden { display: none; } +} + +/* ============================================================ + USER ACTIONS DROPDOWN (notify · hide · logout) + ============================================================ */ + +.user-actions-trigger { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 8px; + background: rgba(30, 41, 59, 0.7); + border: 1px solid rgba(51, 65, 85, 0.6); + color: #cbd5e1; + cursor: pointer; + transition: background 140ms ease, color 140ms ease, border-color 140ms ease; +} +.user-actions-trigger:hover { + background: rgba(51, 65, 85, 0.7); + color: #e2e8f0; +} +.user-actions-trigger[aria-expanded="true"] { + background: rgba(51, 65, 85, 0.85); + border-color: rgba(99, 102, 241, 0.3); + color: #e2e8f0; +} +.user-actions-dot { + position: absolute; + top: 5px; + right: 5px; + width: 8px; + height: 8px; + border-radius: 50%; + background: #f43f5e; + box-shadow: 0 0 0 2px rgba(8, 12, 24, 1); +} +.user-actions-dot.hidden { display: none; } + +.user-actions-menu { + min-width: 220px; + /* Anchor down-right of the trigger; override base centering */ + left: auto !important; + right: 0 !important; + transform: translateY(-4px) !important; +} +.user-actions-menu.nav-dd-open { + transform: translateY(0) !important; +} + +.user-actions-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 12px; + border-radius: 7px; + border: 0; + background: transparent; + color: #cbd5e1; + font-size: 0.84rem; + font-weight: 500; + text-align: left; + cursor: pointer; + transition: background 120ms ease, color 120ms ease; +} +.user-actions-item:hover { + background: rgba(99, 102, 241, 0.12); + color: #e2e8f0; +} +.user-actions-item--danger:hover { + background: rgba(244, 63, 94, 0.12); + color: #fecaca; +} +.user-actions-icon { + width: 16px; + text-align: center; + font-size: 0.9rem; + flex-shrink: 0; +} +.user-actions-label { flex: 1; } +.user-actions-shortcut { + margin-left: auto; + font-size: 0.7rem; + color: var(--cv-text-dim, #6b7488); + font-family: ui-monospace, "SFMono-Regular", monospace; +} +.user-actions-divider { + height: 1px; + background: rgba(255, 255, 255, 0.06); + margin: 4px 6px; +} + +/* The notify-badge inside the dropdown should stack right next to the label */ +.user-actions-item #notify-badge { margin-left: auto; } + /* ============================================================ TOPBAR DROPDOWN MENUS ============================================================ */ diff --git a/Overlord-Server/public/assets/nav/layout.js b/Overlord-Server/public/assets/nav/layout.js index 21b28bc..0c7e78f 100644 --- a/Overlord-Server/public/assets/nav/layout.js +++ b/Overlord-Server/public/assets/nav/layout.js @@ -58,10 +58,11 @@ function initDropdowns(navLinks) { function closeAll() { if (activeDropdown) { - const btn = activeDropdown.querySelector(".nav-dd-group-btn"); + const btn = activeDropdown._activeBtn || activeDropdown.querySelector(".nav-dd-group-btn, .user-actions-trigger"); const menu = activeDropdown.querySelector(".nav-dd-menu"); if (btn) btn.setAttribute("aria-expanded", "false"); if (menu) menu.classList.remove("nav-dd-open"); + activeDropdown._activeBtn = null; activeDropdown = null; } } @@ -80,7 +81,7 @@ function initDropdowns(navLinks) { return; } const menu = wrapper.querySelector(".nav-dd-menu"); - const btn = wrapper.querySelector(".nav-dd-group-btn"); + const btn = wrapper.querySelector(".nav-dd-group-btn, .user-actions-trigger"); if (!menu || !btn) return; e.preventDefault(); @@ -93,6 +94,7 @@ function initDropdowns(navLinks) { btn.setAttribute("aria-expanded", "true"); menu.classList.add("nav-dd-open"); activeDropdown = wrapper; + activeDropdown._activeBtn = btn; } }); @@ -109,6 +111,28 @@ function initDropdowns(navLinks) { }); } +/* ────────────────────────────────────────────── + NOTIFY BADGE MIRROR (utility dropdown) + ────────────────────────────────────────────── */ + +function initNotifyBadgeMirror(scope) { + if (!scope) return; + const badge = scope.querySelector("#notify-badge"); + const dot = scope.querySelector("#user-actions-dot"); + if (!badge || !dot) return; + + const sync = () => { + const visible = !badge.classList.contains("hidden"); + dot.classList.toggle("hidden", !visible); + }; + + sync(); + new MutationObserver(sync).observe(badge, { + attributes: true, + attributeFilter: ["class"], + }); +} + /* ────────────────────────────────────────────── SIDEBAR TREE EXPAND / COLLAPSE ────────────────────────────────────────────── */ @@ -141,107 +165,18 @@ function initSidebarTree(navLinks) { ────────────────────────────────────────────── */ function createTopbarController(host, refs) { - const { toggle, panel, navLinks, navUtility } = refs; - if (!toggle || !panel || !navLinks || !navUtility) { + const { panel, navLinks, navUtility } = refs; + if (!panel || !navLinks || !navUtility) { return { applyAdaptiveNavLayout: () => {} }; } - // Init dropdowns + // Dropdowns + notify-badge mirror; layout itself is pure CSS now. initDropdowns(navLinks); + initDropdowns(navUtility); + initNotifyBadgeMirror(navUtility); - const navOverflows = () => - panel.scrollWidth > panel.clientWidth + 1 || host.scrollWidth > host.clientWidth + 1; - - function resetInlineStyles() { - panel.style.display = ""; - panel.style.flexDirection = ""; - panel.style.alignItems = ""; - panel.style.gap = ""; - navLinks.style.flexDirection = ""; - navLinks.style.flexWrap = ""; - navLinks.style.alignItems = ""; - navLinks.style.justifyContent = ""; - navUtility.style.display = ""; - navUtility.style.width = ""; - navUtility.style.justifyContent = ""; - navUtility.style.flexWrap = ""; - } - - function applyAdaptiveNavLayout() { - if (window.innerWidth < MOBILE_BP) { - host.dataset.navMode = "mobile"; - panel.classList.add("hidden"); - resetInlineStyles(); - panel.dataset.open = "false"; - toggle.style.display = ""; - toggle.setAttribute("aria-expanded", "false"); - return; - } - - host.dataset.navMode = "desktop"; - panel.classList.remove("hidden"); - panel.style.display = "flex"; - panel.dataset.open = "true"; - navUtility.style.display = "flex"; - toggle.style.display = "none"; - toggle.setAttribute("aria-expanded", "false"); - - if (navOverflows()) { - host.dataset.navMode = "desktop-compact"; - navUtility.style.display = "none"; - if (navOverflows()) { - host.dataset.navMode = "compact"; - panel.style.display = "none"; - panel.dataset.open = "false"; - toggle.style.display = "inline-flex"; - } - } - } - - function openCompactPanel() { - panel.dataset.open = "true"; - panel.classList.remove("hidden"); - panel.style.display = "flex"; - panel.style.flexDirection = "column"; - panel.style.alignItems = "stretch"; - panel.style.gap = "10px"; - navLinks.style.flexDirection = "row"; - navLinks.style.flexWrap = "wrap"; - navLinks.style.alignItems = "center"; - navLinks.style.justifyContent = "flex-start"; - navUtility.style.display = "flex"; - navUtility.style.width = "100%"; - navUtility.style.justifyContent = "space-between"; - navUtility.style.flexWrap = "wrap"; - toggle.setAttribute("aria-expanded", "true"); - } - - function closeCompactPanel() { - panel.dataset.open = "false"; - panel.style.display = "none"; - if (host.dataset.navMode === "mobile") panel.classList.add("hidden"); - toggle.setAttribute("aria-expanded", "false"); - } - - toggle.addEventListener("click", () => { - const compact = - host.dataset.navMode === "compact" || host.dataset.navMode === "mobile"; - if (!compact) return; - if (panel.dataset.open === "true") { - closeCompactPanel(); - } else { - openCompactPanel(); - } - }); - - let resizeRaf = null; - window.addEventListener("resize", () => { - if (resizeRaf) cancelAnimationFrame(resizeRaf); - resizeRaf = requestAnimationFrame(applyAdaptiveNavLayout); - }); - - applyAdaptiveNavLayout(); - return { applyAdaptiveNavLayout }; + host.dataset.navMode = "desktop"; + return { applyAdaptiveNavLayout: () => {} }; } /* ────────────────────────────────────────────── diff --git a/Overlord-Server/public/assets/nav/template.js b/Overlord-Server/public/assets/nav/template.js index 8288901..cadd0d0 100644 --- a/Overlord-Server/public/assets/nav/template.js +++ b/Overlord-Server/public/assets/nav/template.js @@ -129,60 +129,61 @@ function mountTopbar(host) { const groupsHtml = NAV_GROUPS.map(dropdownGroup).join(""); host.innerHTML = ` -