@layer theme { /* ---- the top-layout bar: :root[data-layout="top"], every width ---- * * A DELTA, not a layout. The bar itself — sticky, blurred, row, popup submenus, square icon * buttons, right cluster — is the unguarded base in 20-shell.css, the bar being what the chrome * IS unless the vertical sidebar overrides it. This file adds what the top-layout bar wants on * top of that: * - a designed bar height, and padding aligned to the content column; * - the menu on the brand's row instead of wrapped beneath it. * * No `@media` floor: "does the menu fit on the brand's row?" is a property of the CONTENT (5 * sections stock, eleven with a few luci-app-*), not of the viewport, so the whole bar is * MEASURED — fs-chrome.js shrinks the pills, collapses the poll pill to an icon, then stacks the * menu onto its own row, at any width. The under-item dropdown and its clamp therefore apply at * every width too. */ :root[data-layout="top"] .fs-sidebar { /* min-height, not height: the bar has a designed height but must be able to GROW — * a long hostname wraps onto a second line inside the capped brand (20-shell.css), * and a fixed height would clip it. */ min-height: var(--fs-bar-h); height: auto; gap: var(--fs-space-3); /* nowrap while the menu rides the brand's row. Without it flexbox wraps the BAR, carrying the * menu onto a second line by itself while its own pills stay in one row — a stacked bar in * everything but the styling. It also destroys the fits-on-one-row measurement, a menu handed * a whole row of course fitting. Forbidding the wrap forces the menu to share the row and to * wrap its OWN pills when it cannot, which is the signal fitChrome measures. */ flex-wrap: nowrap; /* align the bar's contents to the .fs-content column, so the brand and the menu line * up with the headings and the cards below */ padding: 0 max(20px, calc((100% - var(--fs-content-max)) / 2 + var(--fs-content-pad))); box-shadow: var(--fs-shadow-bar); } /* the menu rides the brand's row; its flex:1 opens the gap that pushes the right-hand * cluster to the content column's right edge */ :root[data-layout="top"] .fs-sidebar ul.nav { order: 2; flex: 1 1 auto; margin-inline-start: var(--fs-space-2); } :root[data-layout="top"] .fs-sidebar #indicators, :root[data-layout="top"] .fs-themerow, :root[data-layout="top"] .fs-logout { order: 3; margin-inline-start: 0; } /* The under-item dropdown anchor, its hover bridge and the clamp are NOT here: they belong to * the BAR and live unguarded in 20-shell.css. They used to be this file's, and that WAS issue * #19 — the sidebar layout collapsed onto a bar is the same chrome, and it inherited a panel * pinned to the bar's left edge with no bridge, unreachable by hover. This file is the top * layout's delta; a rule every bar state needs is not a delta. */ /* the mode strip (only rendered when a router has more than one top-level mode) takes * its own full-width row under the bar */ /* :not(:empty):not(.single) instead of a hide pair — the bar's base rule (20-shell.css) * already hides the strip; see the sidebar twin there for why the pair was folded. */ :root[data-layout="top"] .fs-modemenu:not(:empty):not(.single) { display: flex; order: 4; flex: 1 1 100%; margin: 0; gap: var(--fs-space-1); } /* the bar replaces the sidebar's 22px top padding with the top-nav's 26px */ :root[data-layout="top"] .fs-content { padding-top: var(--fs-space-6); } /* ---- the menu does not fit on the brand's row: .fs-bar-stack ---- * * Set by fs-chrome.js (fitChrome), not by a media query: whether the menu fits beside the brand * depends on how many sections the router HAS, so the width at which the bar must stack is a * property of the content and a hard-coded breakpoint gets it wrong in both directions. The JS * shrinks the pills first, then collapses the poll pill, and stacks only when even that still * overflows. */ :root[data-layout="top"] .fs-sidebar.fs-bar-stack { flex-wrap: wrap; height: auto; padding: var(--fs-space-2-5) var(--fs-space-4); } :root[data-layout="top"] .fs-sidebar.fs-bar-stack ul.nav { order: 3; flex: 1 1 100%; margin-inline-start: 0; } :root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators, :root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow, :root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-logout { order: 2; } :root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators, :root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow { margin-inline-start: auto; } :root[data-layout="top"] .fs-sidebar.fs-bar-stack #indicators:not(:empty) ~ .fs-themerow { margin-inline-start: 0; } /* Two .fs-themerow buttons, Search then Appearance: only the FIRST rendered far-edge item may * keep the auto inline-start margin, exactly as in the base bar, or flexbox splits the free * space between the two autos and Search drifts to mid-bar. The base `.fs-themerow ~ * .fs-themerow` guard is out-specified by the auto rule above (0,4,0 against 0,2,0), so it must * be restated here. Bites when #indicators is :empty — right after an SPA nav, before * the poll pill returns. */ :root[data-layout="top"] .fs-sidebar.fs-bar-stack .fs-themerow ~ .fs-themerow { margin-inline-start: 0; } /* The right cluster's own escalation — .fs-ind-compact and .fs-bar-actrow — is NOT here: it * belongs to the BAR, and the sidebar layout is a bar too once fitShell has stamped * data-narrow. Both live with the bar in 20-shell.css; this file is only the top layout's * delta, and the menu's .fs-bar-stack above is the part that really is top-only. */ }