/* Admin Sidebar Navigation Shell — page-level styles (docs/plans/2026-07-16-001-feat-admin-sidebar-nav-shell-plan.md, U5/U6).
 *
 * This file only covers concerns that don't belong inside a single component's own
 * <style scoped> block:
 *   1. The KTD2 override that hides nav.js's old chrome on /admin/users/ only.
 *   2. The offset the existing page content needs so it isn't covered by the new fixed
 *      top bar / rail.
 *
 * Layout approach: AdminTopBar.vue and AdminSidebar.vue's `rail` variant position themselves
 * with `position: fixed` (viewport-relative), not a page-level CSS Grid — this sidesteps
 * document-flow height bugs entirely (a grid relies on its container correctly computing to
 * 100vh; fixed positioning doesn't care about document flow or ancestor height at all). This
 * mirrors the common "app shell" pattern (e.g. Tailwind UI's sidebar layouts): a fixed rail +
 * fixed top bar, with the scrollable content area offset by margin to clear both.
 *
 * ── KTD2 override — hooks U6 must add ───────────────────────────────────────────────────
 * Class name: `admin-sidebar-shell-active`
 * U6 must add this class to a shared ancestor of BOTH nav.js's rendered `#nav` markup and the
 * new shell markup below — the simplest choice is `<body>`. It hides `#nav` ENTIRELY (not just
 * specific children) — `#nav`'s own `navbar-fixed` class (Materialize framework CSS, not this
 * repo's) reserves flow-space for the nav's height regardless of what's inside it, which is
 * exactly what pushed the new top bar down from the true top of the page when only `#nav`'s
 * children were hidden. `#leaveMessageModal` (also rendered inside `#nav` by nav.js) is
 * confirmed unused anywhere on this page — the new drawer's "Contact Support" link is a plain
 * mailto: link instead, so hiding `#nav` outright has no functional cost. nav.js itself keeps
 * executing unmodified underneath (sign-out delegation, page-title logic).
 *
 * ── Content offset — hooks U6 must add ──────────────────────────────────────────────────
 * Class names: `admin-sidebar-shell__topbar`, `admin-sidebar-shell__rail`, `admin-sidebar-shell__main`
 * U6 wraps the AdminTopBar mount, the AdminSidebar (`rail` variant) mount, and the existing
 * `<div id="admin">...</div>` content, e.g.:
 *
 *   <div class="admin-sidebar-shell">
 *     <div class="admin-sidebar-shell__topbar" id="admin-topbar-mount"></div>
 *     <div class="admin-sidebar-shell__rail" id="admin-sidebar-rail-mount"></div>
 *     <div class="admin-sidebar-shell__main">
 *       <div id="admin"> ... existing page content, unchanged ... </div>
 *     </div>
 *   </div>
 *
 * The `topbar`/`rail` wrapper divs need no CSS of their own — their mounted component is
 * `position: fixed`, so the wrapper's own box collapses to nothing and doesn't participate in
 * layout; only `.admin-sidebar-shell__main` needs an offset so it isn't covered.
 *
 * The `drawer` variant of AdminSidebar.vue is NOT part of this at all — it's a `position: fixed`
 * overlay (see AdminSidebar.vue's own scoped styles) and can be mounted anywhere in the DOM.
 */

.admin-sidebar-shell-active #nav {
	display: none;
}

/* Rail width; AdminSidebar.vue's collapsed (icons-only) rail narrows it via admin-rail-collapse.js */
:root {
	--admin-rail-width: 240px;
}
html.admin-rail-collapsed {
	--admin-rail-width: 64px;
}

/* Early chrome: the shell mounts as soon as the account data is here (ChromeReady), while #authed
   is still hidden behind the loader. Show the top bar and rail, keep the page content hidden, and
   put the loader in the content area. */
body.admin-sidebar-shell-active.admin-shell-early #authed.hidden {
	display: block !important;
}
body.admin-sidebar-shell-active.admin-shell-early #authed.hidden > :not(.admin-sidebar-shell):not(#admin-sidebar-drawer-mount):not(#admin-upgrade-modal-mount),
body.admin-sidebar-shell-active.admin-shell-early #authed.hidden .admin-sidebar-shell__main {
	display: none !important;
}
body.admin-sidebar-shell-active.admin-shell-early #loading:not(.hidden) {
	margin-top: 64px;
}
@media (min-width: 992px) {
	body.admin-sidebar-shell-active.admin-shell-early #loading:not(.hidden) {
		margin-left: var(--admin-rail-width);
	}
}

/* ── First-paint skeleton ─────────────────────────────────────────────────
 * mountAdminSidebarShell() (admin-sidebar-shell.js) mounts AdminTopBar/AdminSidebar behind a
 * lazy `import()`; until that chunk downloads and Vue mounts, #admin-topbar-mount/
 * #admin-sidebar-rail-mount are empty. But `.admin-sidebar-shell__main`'s margin-top/margin-left
 * above are static (part of the initial HTML, not toggled once Vue mounts), so that reserved
 * band was rendering as a blank strip/column on first paint instead of nav chrome, worst on a
 * cold cache. This is a CSS-only skeleton matching each real component's exact position/size/
 * colors (AdminTopBar.vue / AdminSidebar.vue's own scoped styles) as static markup INSIDE the
 * mount divs (src/admin/users/index.html) — no JS of its own needed to remove it:
 *   - #admin-topbar-mount is `app.mount()`'s target — Vue clears an element's existing children
 *     before rendering into it, so the topbar skeleton disappears the instant the app mounts.
 *   - #admin-sidebar-rail-mount is a `<Teleport>` target instead (app.mount()'s target is
 *     #admin-topbar-mount only), which does NOT clear pre-existing static content on its own —
 *     admin-sidebar-shell.js explicitly empties it right before creating the app, so the rail
 *     skeleton is a deliberate one-line cleanup, not automatic like the topbar's.
 * Sized/positioned to match AdminTopBar.vue/AdminSidebar.vue exactly so nothing shifts when the
 * real component takes over.
 */
.admin-sidebar-shell__topbar-skeleton {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 22;
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 64px;
	padding: 0 20px;
	background: var(--color-bg-white, #fff);
	border-bottom: 1px solid var(--color-border, #d9e2ec);
}

.admin-sidebar-shell__rail-skeleton {
	position: fixed;
	top: 64px;
	left: 0;
	bottom: 0;
	width: var(--admin-rail-width);
	padding: 16px 12px;
	background: #f9f9f9;
	border-right: 1px solid var(--color-border-light, #bcccdc);
}

/* Matches AdminSidebar.vue's own `@media (max-width: 992px) { .admin-sidebar--rail { display:
   none } }` — the rail (real or skeleton) is desktop-only. */
@media (max-width: 992px) {
	.admin-sidebar-shell__rail-skeleton {
		display: none;
	}
}

.admin-sidebar-shell__skeleton-bar,
.admin-sidebar-shell__skeleton-row {
	background: var(--color-bg-light, #f5f5f5);
	border-radius: 4px;
	animation: admin-sidebar-shell-skeleton-pulse 1.4s ease-in-out infinite;
}

.admin-sidebar-shell__skeleton-bar--brand {
	width: 120px;
	height: 28px;
}

.admin-sidebar-shell__skeleton-bar--search {
	width: 240px;
	height: 36px;
	border-radius: 18px;
}

.admin-sidebar-shell__skeleton-bar--avatar {
	width: 32px;
	height: 32px;
	border-radius: 50%;
}

.admin-sidebar-shell__skeleton-row {
	height: 36px;
	margin-bottom: 12px;
}

/* Varied widths so the rail skeleton reads as a list of labels, not a stack of identical bars. */
.admin-sidebar-shell__skeleton-row:nth-child(2) {
	width: 85%;
}

.admin-sidebar-shell__skeleton-row:nth-child(3) {
	width: 70%;
}

.admin-sidebar-shell__skeleton-row:nth-child(4) {
	width: 90%;
}

.admin-sidebar-shell__skeleton-row:nth-child(5) {
	width: 75%;
}

@keyframes admin-sidebar-shell-skeleton-pulse {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.5;
	}
}

/* AdminTopBar.vue is `position: fixed; top:0; height:64px` (its own scoped styles) at every
   width. AdminSidebar.vue's `rail` variant is `position: fixed; top:64px; left:0; bottom:0;
   width: var(--admin-rail-width)` (its own scoped styles), visible only at >=992px. Offsets here must match both
   exactly. */
.admin-sidebar-shell__main {
	margin-top: 64px;
	transition: margin-left 0.2s ease;
}

@media (min-width: 992px) {
	.admin-sidebar-shell__main {
		margin-left: var(--admin-rail-width);
	}
}

/* Full-area panel: covers the content area only, so the top bar and rail stay usable (used instead
   of a Materialize modal for the column settings). Offsets match .admin-sidebar-shell__main above.
   z-index sits under Materialize's open modals (overlay 1002+), so a modal can still open on top. */
.admin-fullpage-panel {
	position: fixed;
	top: 64px;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1000;
	overflow-y: auto;
	background: var(--color-bg-white, #fff);
}

@media (min-width: 992px) {
	.admin-fullpage-panel {
		left: var(--admin-rail-width);
	}
}

/* Header / scrolling body / footer layout, for panels with side-by-side columns that scroll on their own */
.admin-fullpage-panel--cols {
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.admin-fullpage-panel--cols.hidden {
	display: none;
}

.admin-fullpage-panel__header {
	padding: 20px 32px 8px;
}

.admin-fullpage-panel__body {
	flex: 1;
	min-height: 0;
	display: flex;
	padding: 0 32px 16px;
}

/* 61px = 60px content + 1px top border (border-box), the same total as AdminSidebar.vue's
   .admin-sidebar__legal-footer, so the two footer bars line up across the bottom edge. */
.admin-fullpage-panel__footer {
	box-sizing: border-box;
	height: 61px;
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 12px;
	padding: 0 32px;
	background: #fafafa;
	border-top: 1px solid var(--color-border-light, #bcccdc);
}

/* .ledger-admin-controls (main.css) is the pre-existing "floating left admin controls" toolbar,
   shared by several pages (users, ledgers, payments, requests, data-room, etc.) — none of it is
   hidden by the sidebar shell; every page keeps its own floating widget alongside the new rail.
   But the widget is `position: fixed; left: 0` (main.css), same as `.admin-sidebar--rail`. Left
   at 0 it renders in front of the rail at the true page edge instead of docking beside it.
   Offset by the rail's width, matching `.admin-sidebar-shell__main`'s margin-left above — only
   at >=992px, since below that the rail isn't persistent (drawer is a toggled overlay, not part
   of layout). */
@media (min-width: 992px) {
	.admin-sidebar-shell-active .ledger-admin-controls {
		left: var(--admin-rail-width);
	}
}

/* The page's original bottom <footer id="footer"> (Terms of Service link + "Powered by Nth
   Round") is now redundant — AdminSidebar.vue's rail carries the same copy in its own
   .admin-sidebar__legal-footer. Hidden via CSS rather than removed from the DOM: several
   call sites in shared_assets/index.js call `document.getElementById('footer')` without a
   null-guard (e.g. on `/updates/view`, on incomplete-user redirects), so deleting the element
   outright risks a null-reference error if one of those paths ever runs on this page. `#footer`
   is `position: fixed` (main.css's `.fixed-footer`), so hiding it doesn't affect document flow
   or require any other offset change. */
.admin-sidebar-shell-active #footer {
	display: none;
}

/* #6164: these admin pages' tables (.table-wrap-f > .sm-hz-scroll-no-margin-f > table, e.g.
   #campaignSortTable) have no desktop overflow containment of their own, so a wide table grows
   the whole page past the space available beside the fixed rail instead of scrolling within its
   own box -- dragging the rest of each page's header/stats/etc. sideways along with it. Reported
   on /admin/voting/; the other 6 pages here share the identical .table-wrap-f markup shape and
   already carry the sidebar shell, so they have the same latent bug even though unreported.
   #admin/fundraising/ is deliberately excluded -- it doesn't have the shell yet (see main.css's
   sticky-header rule for the same exclusion), so it has no fixed rail to overflow under.
   Mirrors #tableWrap/.sm-hz-scroll's existing containment for the cap table (same file, same
   shape of wrapper) -- deliberately does NOT touch the sticky-header `top` offset main.css's
   thead th rule uses; that's what #6165 changed and had to be reverted for (a sticky-header/
   table-row interaction, unrelated to this overflow containment). Scoped under each page's own
   section id (not main.css's shared .table-wrap-f class) so a page that doesn't carry the shell
   yet is never affected by this rule regardless of whether it's listed here.
   overflow-x only, deliberately not the `overflow` shorthand #tableWrap uses: unlike cap table,
   these .table-wrap-f divs (there are several per page -- main list, results, per-participant
   responses) sit inside a Materialize tabs panel, which has its own height/overflow handling;
   clipping the vertical axis here compounded with that and clipped/mispositioned rows. */
@media (min-width: 668px) {
	#votingAdmin .table-wrap-f,
	#buysellAdmin .table-wrap-f,
	#paymentsAdmin .table-wrap-f,
	#requestsAdmin .table-wrap-f,
	#signaturesAdmin .table-wrap-f,
	#surveyingAdmin .table-wrap-f,
	#enrollAdmin .table-wrap-f {
		overflow-x: clip;
		max-width: 100%;
	}
	#votingAdmin .sm-hz-scroll-no-margin-f,
	#buysellAdmin .sm-hz-scroll-no-margin-f,
	#paymentsAdmin .sm-hz-scroll-no-margin-f,
	#requestsAdmin .sm-hz-scroll-no-margin-f,
	#signaturesAdmin .sm-hz-scroll-no-margin-f,
	#surveyingAdmin .sm-hz-scroll-no-margin-f,
	#enrollAdmin .sm-hz-scroll-no-margin-f {
		overflow-x: auto;
	}
}

/* #6309: same #6164 containment for Ledgers' main tab tables, which use the non-`-f`
   .table-wrap > .sm-hz-scroll-no-margin shape (e.g. custom columns like Sender Basis
   pushed the transactions table past its card at laptop widths). */
@media (min-width: 668px) {
	#valuationsTable .table-wrap,
	#transactionsTable .table-wrap,
	#eventsTable .table-wrap {
		overflow-x: clip;
		max-width: 100%;
	}
	#valuationsTable .sm-hz-scroll-no-margin,
	#transactionsTable .sm-hz-scroll-no-margin,
	#eventsTable .sm-hz-scroll-no-margin {
		overflow-x: auto;
	}
}

/* #6533: historic cap table banner sticks just under the top bar; z-index stays below the top
   bar's (22) so its dropdowns still open over the banner. */
.admin-sidebar-shell__main > #hist-preview-alert {
	position: sticky;
	top: 64px;
	z-index: 21;
}

.admin-sidebar-shell__main > #hist-preview-alert .alert {
	position: static;
}
