diff --git a/app/styles.css b/app/styles.css index 2e5f642..6aee23a 100644 --- a/app/styles.css +++ b/app/styles.css @@ -58,6 +58,34 @@ line-height: 1; flex: 0 0 auto; } + .topbar-panel-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 7px 12px; + border-radius: 999px; + border: 1px solid #3f3f46; + background: #27272a; + color: #f4f4f5; + cursor: pointer; + font-size: 13px; + font-weight: 600; + line-height: 1; + flex: 0 0 auto; + margin-left: auto; + } + .topbar-panel-toggle:hover { + background: #3f3f46; + } + .topbar-panel-toggle[hidden] { + display: none; + } + .topbar-panel-toggle[hidden] + .topbar-menu-toggle { + margin-left: auto; + } + .topbar-panel-toggle:not([hidden]) + .topbar-menu-toggle { + margin-left: 0; + } .topbar-menu-toggle:hover { background: #3f3f46; } @@ -158,7 +186,9 @@ } .topbar-menu-toggle { min-height: 38px; - margin-left: auto; + } + .topbar-panel-toggle { + min-height: 38px; } .topbar.is-menu-open { flex-wrap: nowrap; @@ -2123,13 +2153,6 @@ pointer-events: auto; } - .sidebar-popout-open { - position: absolute; - top: 10px; - right: 10px; - z-index: 5; - } - .detail-popout-open { position: absolute; top: 10px; @@ -5960,7 +5983,6 @@ min-height: 38px; padding: 8px 10px; } - .sidebar-popout-open, .detail-popout-open { position: fixed; top: auto; @@ -5976,9 +5998,6 @@ -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); } - .sidebar-popout-open { - right: 14px; - } .detail-popout-open { left: 14px; } diff --git a/app/ui-chrome.js b/app/ui-chrome.js index bab8f6f..d063044 100644 --- a/app/ui-chrome.js +++ b/app/ui-chrome.js @@ -6,6 +6,7 @@ const DEFAULT_DATASET_ENTRY_COLLAPSED = false; const DEFAULT_DATASET_DETAIL_COLLAPSED = true; const sidebarControllers = new WeakMap(); + const sidebarControllerRegistry = new Map(); const detailControllers = new WeakMap(); const AUTO_COLLAPSE_ENTRY_SELECTOR = [ ".planet-list-item", @@ -162,6 +163,131 @@ return resolveLayoutTarget(panel); } + function isLayoutVisible(layout) { + if (!(layout instanceof HTMLElement)) { + return false; + } + + if (layout.hidden || layout.closest("[hidden]")) { + return false; + } + + const section = layout.closest("section"); + if (section instanceof HTMLElement && section.hidden) { + return false; + } + + return true; + } + + function getActiveVisibleSidebarController() { + for (const controller of sidebarControllerRegistry.values()) { + if (!isLayoutVisible(controller.layout)) { + continue; + } + + return controller; + } + + return null; + } + + function getTopbarPanelToggleButton() { + const { topbarEl, menuToggleEl } = getTopbarElements(); + if (!(topbarEl instanceof HTMLElement) || !(menuToggleEl instanceof HTMLButtonElement)) { + return null; + } + + const existingButton = topbarEl.querySelector("#topbar-panel-toggle"); + if (existingButton instanceof HTMLButtonElement) { + return existingButton; + } + + const panelToggleEl = document.createElement("button"); + panelToggleEl.id = "topbar-panel-toggle"; + panelToggleEl.type = "button"; + panelToggleEl.className = "topbar-panel-toggle sidebar-popout-open"; + panelToggleEl.textContent = "Show Detail"; + panelToggleEl.setAttribute("aria-label", "Show detail view"); + panelToggleEl.hidden = true; + panelToggleEl.addEventListener("click", () => { + const controller = getActiveVisibleSidebarController(); + if (!controller) { + return; + } + + if (controller.layout.classList.contains("layout-sidebar-collapsed")) { + showSidebarOnly(controller.layout); + return; + } + + if (detailControllers.has(controller.layout)) { + showDetailOnly(controller.layout); + } + }); + + topbarEl.insertBefore(panelToggleEl, menuToggleEl); + return panelToggleEl; + } + + function syncTopbarPanelToggleButton() { + const panelToggleEl = getTopbarPanelToggleButton(); + if (!(panelToggleEl instanceof HTMLButtonElement)) { + return; + } + + const controller = getActiveVisibleSidebarController(); + if (!controller) { + panelToggleEl.hidden = true; + panelToggleEl.removeAttribute("aria-controls"); + panelToggleEl.setAttribute("aria-expanded", "false"); + return; + } + + const sidebarCollapsed = controller.layout.classList.contains("layout-sidebar-collapsed"); + const hasDetailController = detailControllers.has(controller.layout); + if (!sidebarCollapsed && !hasDetailController) { + panelToggleEl.hidden = true; + panelToggleEl.removeAttribute("aria-controls"); + panelToggleEl.setAttribute("aria-expanded", "false"); + return; + } + + panelToggleEl.hidden = false; + panelToggleEl.setAttribute("aria-controls", controller.panel.id); + panelToggleEl.setAttribute("aria-expanded", sidebarCollapsed ? "false" : "true"); + panelToggleEl.textContent = sidebarCollapsed ? "Show Panel" : "Show Detail"; + panelToggleEl.setAttribute("aria-label", sidebarCollapsed ? "Show entry panel" : "Show detail view"); + } + + function initializeTopbarPanelToggle() { + if (!document.body || document.body.dataset.topbarPanelToggleReady === "1") { + syncTopbarPanelToggleButton(); + return; + } + + document.body.dataset.topbarPanelToggleReady = "1"; + getTopbarPanelToggleButton(); + + const observer = new MutationObserver(() => { + window.requestAnimationFrame(() => { + syncTopbarPanelToggleButton(); + }); + }); + + observer.observe(document.body, { + subtree: true, + attributes: true, + attributeFilter: ["hidden", "class"] + }); + + window.addEventListener("resize", () => { + syncTopbarPanelToggleButton(); + }); + + syncTopbarPanelToggleButton(); + } + function scheduleAutoCollapse(layout) { if (!(layout instanceof HTMLElement)) { return; @@ -221,11 +347,6 @@ return; } - const header = panel.querySelector(".planet-list-header, .tarot-list-header"); - if (!(header instanceof HTMLElement)) { - return; - } - panel.dataset.sidebarPopoutReady = "1"; const sectionId = layout.closest("section")?.id || `layout-${index + 1}`; @@ -234,53 +355,30 @@ const storageKey = `${SIDEBAR_COLLAPSE_STORAGE_PREFIX}${sectionId}`; - const collapseBtn = document.createElement("button"); - collapseBtn.type = "button"; - collapseBtn.className = "sidebar-toggle-inline"; - collapseBtn.textContent = "Hide Panel"; - collapseBtn.setAttribute("aria-label", "Hide entry panel"); - collapseBtn.setAttribute("aria-controls", panelId); - header.appendChild(collapseBtn); - - const openBtn = document.createElement("button"); - openBtn.type = "button"; - openBtn.className = "sidebar-popout-open"; - openBtn.textContent = "Show Panel"; - openBtn.setAttribute("aria-label", "Show entry panel"); - openBtn.setAttribute("aria-controls", panelId); - openBtn.hidden = true; - layout.appendChild(openBtn); - const applyCollapsedState = (collapsed, persist = true) => { layout.classList.toggle("layout-sidebar-collapsed", collapsed); - collapseBtn.setAttribute("aria-expanded", collapsed ? "false" : "true"); - openBtn.setAttribute("aria-expanded", collapsed ? "false" : "true"); - openBtn.hidden = !collapsed; + syncTopbarPanelToggleButton(); if (persist) { saveSidebarCollapsedState(storageKey, collapsed); } }; - sidebarControllers.set(layout, { + const controller = { + layout, applyCollapsedState, panel, - collapseBtn, - openBtn, storageKey - }); + }; - collapseBtn.addEventListener("click", () => { - showDetailOnly(layout); - }); - - openBtn.addEventListener("click", () => { - showSidebarOnly(layout); - }); + sidebarControllers.set(layout, controller); + sidebarControllerRegistry.set(layout, controller); const storedCollapsed = loadSidebarCollapsedState(storageKey); applyCollapsedState(storedCollapsed == null ? DEFAULT_DATASET_ENTRY_COLLAPSED : storedCollapsed, false); }); + + syncTopbarPanelToggleButton(); } function initializeDetailPopouts() { @@ -322,6 +420,7 @@ layout.classList.toggle("layout-detail-collapsed", collapsed); detailPanel.setAttribute("aria-hidden", collapsed ? "true" : "false"); + syncTopbarPanelToggleButton(); if (persist) { saveSidebarCollapsedState(detailStorageKey, collapsed); @@ -503,6 +602,7 @@ initializeSidebarAutoCollapse(); bindTopbarMobileMenu(); bindTopbarDropdownInteractions(); + initializeTopbarPanelToggle(); } window.TarotChromeUi = { @@ -512,6 +612,7 @@ initializeDetailPopouts, initializeSidebarAutoCollapse, bindTopbarMobileMenu, + initializeTopbarPanelToggle, setSidebarCollapsed, setDetailCollapsed, showDetailOnly, diff --git a/index.html b/index.html index 72284db..51fd6a3 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - +
@@ -1086,7 +1086,7 @@ - +