diff --git a/frontend/packages/dashboard/src/common.css b/frontend/packages/dashboard/src/common.css index 47ec4c26..cfe2adc8 100644 --- a/frontend/packages/dashboard/src/common.css +++ b/frontend/packages/dashboard/src/common.css @@ -501,22 +501,27 @@ body.side-panel-resizing * { cursor: ew-resize !important; } ST4TS / S3TT1NGS are full-bleed with their own `.-main` padding. */ .page-content { padding: 0 1.5em; } -/* ─── server warnings banner ────────────────────────────────────────── - Generic top-of-page strip, injected at the top of by - common.js (renderServerWarnings) on every page. One row per warning; - colour comes from the per-warning `level` (warn = amber, crit = red). - `position: sticky; top: 0` keeps it pinned above the page chrome so an - operator sees it on any page no matter how far they've scrolled. */ -.server-warnings { +/* ─── sticky top region ─────────────────────────────────────────────── + One sticky container per page holding the warning banner above the + page chrome (tab bar / page header). common.js (ensureStickyTop) + wraps the page's chrome in this on first render and injects the banner + as its first child, so the banner + chrome stack in a single sticky + context — pinned together — instead of two separate `top:0` stickies + colliding (the banner used to overlay the tab bar). */ +.sticky-top { position: sticky; top: 0; z-index: 50; } -/* The bar is prepended as a direct child of and spans the full - width edge-to-edge. The dashboard + H0M3 are full-bleed at the body - level — their 1.5em horizontal gutter lives on an inner `.page-content` - wrapper (see below), so the banner is full-width for free with no - breakout. FL0W + L0GS are full-bleed too. */ + +/* ─── server warnings banner ────────────────────────────────────────── + Generic top-of-page strip, injected as the first child of `.sticky-top` + by common.js (renderServerWarnings) on every page. One row per warning; + colour comes from the per-warning `level` (warn = amber, crit = red). + Stickiness lives on the `.sticky-top` wrapper, not here. The bar spans + the full width edge-to-edge; the dashboard + H0M3 are full-bleed at the + body level (their 1.5em gutter lives on an inner `.page-content` + wrapper), so the banner is full-width for free. */ .server-warnings[hidden] { display: none; } .server-warn { text-align: center; diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index 8a05f6ee..fb423e42 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -634,6 +634,28 @@ export const NOTIF = (() => { // `level` (`warn` amber / `crit` red). Adding a new system warning is a // backend-only change. The bar is injected at the top of so no // page needs to add markup. + +// The sticky top region — one sticky container holding the warning +// banner above the page's chrome (tab bar / page header), so the banner +// stacks with the chrome instead of being overlaid by it (two separate +// `top:0` stickies would otherwise collide). Built once by wrapping the +// page's existing chrome element; pages without a chrome (e.g. the H0M3 +// hub) get a banner-only sticky region at the top of . +function ensureStickyTop() { + let top = document.querySelector('.sticky-top'); + if (top) return top; + top = document.createElement('div'); + top.className = 'sticky-top'; + const chrome = document.querySelector('.dashboard-chrome, .page-header'); + if (chrome && chrome.parentNode) { + chrome.parentNode.insertBefore(top, chrome); + top.append(chrome); + } else { + document.body.prepend(top); + } + return top; +} + function ensureServerWarningsBar() { let bar = document.getElementById('server-warnings'); if (!bar) { @@ -642,7 +664,7 @@ function ensureServerWarningsBar() { bar.className = 'server-warnings'; bar.setAttribute('role', 'alert'); bar.hidden = true; - document.body.prepend(bar); + ensureStickyTop().prepend(bar); } return bar; } diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index dc5dd382..11f765e5 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -27,9 +27,9 @@ body.dashboard-shell { display: flex; align-items: center; gap: 0.75em; - position: sticky; - top: 0; - z-index: 25; + /* Stickiness lives on the `.sticky-top` wrapper (common.css) that + common.js wraps this chrome in, alongside the server-warning banner, + so they stack in one sticky context instead of colliding. */ background: color-mix(in srgb, var(--bg) 86%, transparent); -webkit-backdrop-filter: blur(8px) saturate(120%); backdrop-filter: blur(8px) saturate(120%); diff --git a/frontend/packages/shared/src/chrome.css b/frontend/packages/shared/src/chrome.css index be32deef..ee8778ab 100644 --- a/frontend/packages/shared/src/chrome.css +++ b/frontend/packages/shared/src/chrome.css @@ -15,9 +15,9 @@ */ .page-header { - position: sticky; - top: 0; - z-index: 25; + /* Stickiness lives on the `.sticky-top` wrapper (common.css) that + common.js wraps this header in, alongside the server-warning banner, + so the two stack instead of colliding. */ background: color-mix(in srgb, var(--bg) 92%, transparent); -webkit-backdrop-filter: blur(8px) saturate(120%); backdrop-filter: blur(8px) saturate(120%);