/* ═════════════════════════════════════════════════════════════════
   module-chrome.css — Canonical chrome shared by all module admin SPAs
   ═════════════════════════════════════════════════════════════════

   This file is the single source of truth for SquareKeeper module
   admin chrome (sidebar, top bar, shell layout, base typography,
   color tokens, loading/denied screens).

   Imported by every module's admin entry HTML via:
     <link rel="stylesheet" href="/assets/css/module-chrome.css">

   Modules MUST use the canonical class names below for chrome
   elements. Page-specific content (stat grids, panels, tables,
   forms specific to a module's data) stays in each module's own
   inline <style> block — only the chrome lives here.

   Reference: Platform/SQUAREKEEPER_MODULE_UI_SPEC.md

   Owner: Platform. Module instances do NOT edit this file directly.
   ═════════════════════════════════════════════════════════════════ */

/* ── Reset + base ────────────────────────────────────────────── */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  background: var(--bg-app);
  color: var(--text-primary);
  -webkit-font-smoothing: antialiased;
  line-height: 1.45;
  font-size: 14px;
  overflow: hidden;
}
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; font-size: 14px; }
a { color: var(--accent-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Color tokens (dark theme default) ───────────────────────── */
:root {
  --bg-app: #0A0A0F;
  --bg-chrome: #06060C;
  --bg-panel: #111118;
  --bg-panel-hover: #1a1a28;
  --bg-elevated: #1E293B;
  --bg-modal: #0F0F17;
  --border-default: #1E293B;
  --border-hover: #334155;
  --text-primary: #E2E8F0;
  --text-secondary: #CBD5E1;
  --text-muted: #94A3B8;
  --text-faint: #64748B;
  --text-disabled: #475569;
  /* ── Brand accent: GREEN (2026-07-24 rebrand; was sky blue #11A6F6). ──
     Current roles (2026-08-03): #0A8A06 fills (+ WHITE ink) · #1ACD15 text/glyph
     accent on DARK · #0A7504 text-on-light. ⚠ #0A9C07 no longer holds ANY role
     here — it could not carry white as a fill (3.64:1), and as dark-ground text
     it measured only 4.27:1 on the queued #15281c chrome (§3.7), so Matt moved
     the dark text accent to #1ACD15 (9.21 app bg / 8.76 card / 7.24 chrome).
     ⚠ THE ROLES ARE SEPARATED ON PURPOSE. Blue let --accent-primary play both
     the text role and the fill role because white text passed on both. Green
     cannot: bright green fails as text-on-white (3.64:1) and dark green fails
     as a fill under dark text (3.07:1). So --accent-primary is the TEXT/glyph
     accent (theme-dependent) and --accent-fill/--accent-onfill are the FILL
     pair (theme-invariant). Every pair below is measured (scratchpad
     contrast.js, all PASS; active row 7.83 vs the 7.73 guard).
     ⚠⚠ REVERSED 2026-08-03 (Matt): text on a green fill is now ALWAYS WHITE.
     The fill moved #0A9C07 → #0A8A06 specifically so white reaches AA
     (4.515:1); the old #10180E ink measures 4.013:1 on it and FAILS. No green
     carries both inks — the optimum #098F06 gives white 4.25 / ink 4.26, both
     failing — so this was an either/or, not a preference. The former "NEVER
     white" rule and its all-caps exception are both retired. Full record:
     [[project_sk_green_v2_migration]]. The TEXT accent below is unaffected. */
  --accent-primary: #1ACD15;
  --accent-primary-light: #1ACD15;
  --accent-primary-dark: #0A7D05;
  --accent-primary-darker: #075802;
  --accent-active-bg: #04230A;
  --accent-active-subnav: #021505;
  --accent-soft: rgba(10, 156, 7, 0.15);
  /* Theme-invariant fill roles — same values in both theme blocks. */
  --accent-fill: #0A8A06;       /* SK Green v2 — white = 4.515:1 */
  --accent-fill-hover: #0A7504; /* DARKER on hover: white = 5.90:1. ⚠ the old
                                   #0AAD08 was BRIGHTER — white on it is 3.00:1 */
  --accent-onfill: #FFFFFF;     /* WHITE. ⚠ #10180E on this green is 4.01:1, FAILS */
  /* Semantic status colors — theme-AWARE, not agnostic: all twelve are
     redefined in the [data-theme="light"] block below (BRAND_STYLE_GUIDE
     §3.4). Never hardcode one theme's triplet — name the token. */
  --st-success-bg: #0D2818; --st-success-fg: #34D399; --st-success-br: #166534;
  --st-warn-bg: #2D1F04;    --st-warn-fg: #FBBF24;    --st-warn-br: #78350F;
  --st-danger-bg: #2D0A0A;  --st-danger-fg: #F87171;  --st-danger-br: #7F1D1D;
  --st-info-bg: #0A1A2D;    --st-info-fg: #60A5FA;    --st-info-br: #1E3A5F;
  /* Font stacks */
  --font-ui: 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'Segoe UI', system-ui, -apple-system, sans-serif;
  /* Module accent — each module declares its own --mod-X locally.
     Common module accents are registered in dashboard/dist/assets/css/style.css;
     module pages may override per their own data-identity needs. */
}

/* ── Light theme overrides (chrome stays dark; Slack/Notion pattern) */
[data-theme="light"] {
  --bg-app: #F8FAFC;
  --bg-chrome: #06060C;
  --bg-panel: #FFFFFF;
  --bg-panel-hover: #F1F5F9;
  --bg-elevated: #E2E8F0;
  --bg-modal: #FFFFFF;
  --border-default: #E2E8F0;
  --border-hover: #CBD5E1;
  --text-primary: #0F172A;
  --text-secondary: #1E293B;
  --text-muted: #475569;
  --text-faint: #64748B;
  --text-disabled: #94A3B8;
  /* Light-theme accent: the darker text-green — #0A9C07 is 3.64:1 on white
     and fails as text; #0A7504 measures 5.90:1 (website --clay). The fill
     tokens do NOT change per theme — a green button looks the same in both. */
  --accent-primary: #0A7504;
  --accent-primary-light: #0A7504;
  --accent-active-bg: #DAF4D2;
  --accent-soft: rgba(10, 156, 7, 0.10);
  --accent-fill: #0A8A06;       /* theme-INVARIANT — identical to the dark block */
  --accent-fill-hover: #0A7504;
  --accent-onfill: #FFFFFF;
  --st-success-bg: #D1FAE5; --st-success-fg: #047857; --st-success-br: #6EE7B7;
  --st-warn-bg: #FEF3C7;    --st-warn-fg: #B45309;    --st-warn-br: #FCD34D;
  --st-danger-bg: #FEE2E2;  --st-danger-fg: #B91C1C;  --st-danger-br: #FCA5A5;
  --st-info-bg: #DBEAFE;    --st-info-fg: #1D4ED8;    --st-info-br: #93C5FD;
}

/* ── Loading + denied screens ────────────────────────────────── */
.sm-loading-screen, .sm-denied-screen,
.loading-screen, .denied-screen {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 100vh; padding: 32px; text-align: center; gap: 16px;
}
.sm-loading-icon, .loading-icon {
  width: 96px; height: 96px; border-radius: 18px; opacity: 0.9;
}
.sm-loading-spinner, .loading-spinner {
  width: 32px; height: 32px;
  border: 3px solid var(--border-default); border-top-color: var(--accent-primary);
  border-radius: 50%; animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.sm-loading-text, .loading-text,
.sm-denied-screen p, .denied-screen p { color: var(--text-muted); }
.sm-denied-screen h1, .denied-screen h1 {
  color: var(--text-primary); font-size: 22px;
}

/* ── Shell layout ──────────────────────────────────────────────
   ⚠ The dvh fallback pair below, and the !important, are both deliberate.

   `100vh` on a mobile browser is the LARGE viewport — the height the page
   would have with the URL bar scrolled away — so a 100vh shell hangs its
   bottom ~110px underneath the visible browser chrome. That is what cut the
   sidebar footer (← Home / Logout / version block) off the bottom of every
   module's mobile drawer, and it clipped the bottom of #pageContent's scroll
   viewport for the same reason. `100dvh` tracks the VISIBLE viewport instead.
   On desktop dvh === vh, so this is a mobile-only change. The plain `100vh`
   line stays FIRST as the pre-dvh fallback (a browser without dvh drops the
   second declaration and lands exactly where it is today).

   !important because EVERY module still re-declares these two rules locally —
   TP admin style.css:194/200, PM style.css:206/209, EM admin.css:23/24, and
   MM/OM/SM/VP inline — and module CSS loads AFTER this sheet, so an ordinary
   rule here loses the cascade outright. Same defensive pattern, and the same
   underlying cause, as the .sidebar-nav overflow guard further down. Those
   local copies are now inert; deleting them is a separate cleanup.
   2026-07-22 — mobile drawer footer cut-off. */
#appLayout {
  display: flex;
  height: 100vh;
  height: 100dvh !important;
  overflow: hidden;
}
#mainArea {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
}
#pageContent {
  flex: 1; overflow-y: auto; padding: 24px;
  background: var(--bg-app);
}

/* ── Sidebar ─────────────────────────────────────────────────── */
#sidebar {
  width: 220px; min-width: 220px; flex-shrink: 0;
  height: 100vh;                 /* fallback — see the #appLayout note above */
  height: 100dvh !important;     /* visible viewport, so the footer isn't clipped */
  background: var(--bg-chrome);
  border-right: 1px solid #1E293B;
  display: flex; flex-direction: column; overflow-y: auto;
}

.sidebar-brand {
  padding: 18px 16px 12px;
  border-bottom: 1px solid #1E293B;
  text-align: center;
}
/* Logo wrap — transparent container, 54x54 icon. NO white background.
   !important enforces against PM's legacy white-box pattern.

   54px (was 72px) as of 2026-07-23. The brand block cost 242px before a single
   nav row existed — roughly six nav rows, or two thirds of a small phone's
   viewport once the footer is counted. Shrinking the icon to 75% was the first
   cut: piloted on OM 2026-07-22 behind an [data-mod="operations"] scope, then
   rolled out suite-wide by DELETING that scope — not by copying rules into seven
   modules. Folded into this one rule so there is no second .sidebar-logo-wrap
   block shadowing it by source order.

   The matching cut is per-module markup: drop the "Licensed to <org>" line.
   Never restate these sizes in module-local CSS — they are !important, so an
   override would have to out-!important them, which is exactly the fork this arc
   has been deleting. (The per-module ACCENT rules further down stay [data-mod]
   scoped on purpose; that scoping is real, this one was temporary.) */
.sidebar-logo-wrap {
  width: 54px !important;
  height: 54px !important;
  border-radius: 9px !important;
  margin: 0 auto 10px !important;
  overflow: hidden !important;
  background: transparent !important;
  padding: 0 !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}
.sidebar-logo-wrap img,
.sidebar-logo-wrap img#sidebarLogo {
  width: 54px !important;
  height: 54px !important;
  border-radius: 0 !important;
  object-fit: contain !important;
  display: block !important;
  max-width: none !important;
  max-height: none !important;
  /* The 2026-07-30 tile-family icons are self-contained rounded tiles with
     TRANSPARENT corners (the rounded square doesn't fill the square frame).
     The old blue icons were full-bleed, so this never showed; the new ones let
     the near-black sidebar (#06060C) show through all 4 corners, reading as
     "the icon's corners are cut off / black". Scaling the tile up inside the
     wrap's overflow:hidden + border-radius:9px clip pushes those transparent
     corner arcs OUT of the frame, so the checkerboard fills to the rounded edge
     (full-bleed, like the old icons) — the display change, art files untouched.
     1.18 clears the tile's corner radius without cropping any symbol (verified
     against the fullest, the Vendor hard-hat figure). */
  transform: scale(1.18) !important;
}

/* Title block — canonical AND module-divergent class aliases.
   PM uses .sb-title > .mod-name, EM uses .sb-title (as the text element),
   TP/VP/SM/MM/OM use .sb-title-main. All forced to the same look. */
.sb-title-main,
.sb-title .mod-name,
.sb-title > span.mod-name {
  color: #E2E8F0 !important;
  font-size: 14px !important;
  font-weight: 700 !important;
  letter-spacing: 1px !important;
  text-transform: uppercase !important;
  line-height: 1.2 !important;
  display: block !important;
}
/* EM uses .sb-title as the title text directly (not a container). Style as title. */
.sidebar-brand .sb-title:not(:has(span)):not(:has(div)) {
  color: #E2E8F0 !important;
  font-size: 14px !important;
  font-weight: 700 !important;
  letter-spacing: 1px !important;
  text-transform: uppercase !important;
  line-height: 1.2 !important;
}

/* Subtitle "by SquareKeeper" — all variant class names get italic */
.sb-title-sub,
.sb-sub,
.sb-title .by,
.sb-title > span.by {
  color: #94A3B8 !important;
  font-size: 11px !important;
  font-style: italic !important;
  font-weight: 400 !important;
  margin-top: 2px !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  display: block !important;
}

/* "Licensed to <org>" line — all variant class names */
.sidebar-org,
.sb-licensed,
.sb-title .license,
.sb-title > span.license {
  color: #94A3B8 !important;
  font-size: 11px !important;
  font-weight: 400 !important;
  font-style: normal !important;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif !important;
  margin-top: 6px !important;
  line-height: 1.35 !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  display: block !important;
}

/* Welcome line — no bold anywhere. Even modules with .user-name span.
   font-family forced to sans-serif (TP local CSS forces monospace). */
.sidebar-welcome,
#sidebarWelcome {
  padding: 12px 16px !important;
  color: #CBD5E1 !important;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif !important;
  font-size: 13px !important;
  font-weight: 400 !important;
  border-bottom: 1px solid #1E293B !important;
  text-align: center !important;
  letter-spacing: normal !important;
  text-transform: none !important;
}
.sidebar-welcome .user-name,
#sidebarWelcome .user-name,
.sidebar-welcome strong,
#sidebarWelcome strong {
  font-weight: 400 !important;
  color: #CBD5E1 !important;
  font-family: inherit !important;
}

.sidebar-nav {
  flex: 1;
  padding: 8px 0;
  /* No internal overflow — the outer #sidebar handles scroll if needed.
     Internal overflow here causes a scrollbar mid-sidebar when nav is tall. */
}

/* Defensive override: VP and SM (and possibly future modules) still ship a
   local `.sidebar-nav { overflow-y: auto }` rule in their inline CSS. This
   selector wins specificity (#sidebar parent) AND uses !important so the
   stubby mid-sidebar scrollbar bug PM had can never come back, regardless
   of what a module's local CSS declares. */
#sidebar .sidebar-nav,
#sidebar > .sidebar-nav {
  overflow: visible !important;
  overflow-y: visible !important;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  color: #CBD5E1;
  font-size: 14px;
  font-weight: 500;
  border-left: 3px solid transparent;
  cursor: pointer;
  user-select: none;
  text-decoration: none;
}
.nav-item:hover {
  background: rgba(255, 255, 255, 0.04);
  color: #E2E8F0;
  text-decoration: none;
}
/* Active nav row — UNIFIED THEME-INVARIANT PILL (Matt 2026-08-11, QA-passed on OM
   v1.16.4 then promoted suite-wide). The pill is #DAF4D2 with #15281C ink and a
   #0A8A06 border in BOTH themes — deliberately literal, not tokens (like the Office
   Assistant panel), because the whole point is that it does NOT flip with theme.
   The authoritative copy is the #sidebar block below, which pins all three at
   (1,2,0) so a module's own (0,n,0) active rule can no longer re-fork it. */
.nav-item.active {
  background: #DAF4D2;
  color: #15281C;
  border-left-color: #0A8A06;
}
.nav-icon { font-size: 16px; line-height: 1; }
/* Canonical nav-icon sizing — PROMOTED from OM's local override 2026-07-23
   (MODULE_NAV_TEMPLATE_SPEC §6 "known divergence": OM scoped 18px/22px and
   sub-items 14px/18px locally while the shared sheet said a flat 16px. The spec
   said decide during rollout and "do not leave both" — Matt's call was promote
   OM's). The fixed width + centring is what makes glyphs of different visual
   weight line up in a column; a bare font-size does not. !important because
   module sheets load AFTER this one, so a stale local re-declaration (EM had
   width:20px) would otherwise silently win and re-fork the suite. */
.nav-item .nav-icon,
.nav-group-header .nav-icon {
  font-size: 18px !important;
  line-height: 1 !important;
  width: 22px !important;
  text-align: center !important;
}
.nav-subitem .nav-icon {
  font-size: 14px !important;
  width: 18px !important;
  text-align: center !important;
}
/* SVG line-icon glyphs (nav-icons.js) replacing the emoji entities. The
   .nav-icon span keeps its fixed width above for column alignment; font-size
   sizes an emoji but does NOT size an <svg> child, so the svg is sized here.
   currentColor is inherited from the row, which is why an active row's icon
   tints with the accent — the emoji could not. Slightly larger than the emoji
   px equivalents because a stroked line icon reads smaller than a filled emoji
   at the same size. Suite rollout piloted on OM 2026-07-24. */
.nav-item .nav-icon svg,
.nav-group-header .nav-icon svg {
  width: 22px; height: 22px; display: block; margin: 0 auto;
}
.nav-subitem .nav-icon svg {
  width: 18px; height: 18px; display: block; margin: 0 auto;
}
.nav-label { /* default; module may extend */ }

/* Nav groups (collapsible) — used by some modules */
.nav-group-header {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px;
  color: #CBD5E1;
  font-size: 14px;
  font-weight: 500;
  border-left: 3px solid transparent;
  cursor: pointer;
  user-select: none;
  justify-content: space-between;
}
.nav-group-header:hover {
  background: rgba(255, 255, 255, 0.04);
  color: #E2E8F0;
}
/* Group-header label — icon + text must be ONE flex item, because
   .nav-group-header is justify-content: space-between and a bare icon span
   plus a bare text node would spread into three items with the label stranded
   in the middle. Wrapping them keeps the chevron alone on the right.
   Added 2026-07-22 with OM's adoption; PM/TP/MM use the same shape. */
.nav-group-header > .nav-group-title {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.nav-chevron {
  font-size: 10px;
  transition: transform 0.15s;
}
.nav-group.collapsed .nav-chevron { transform: rotate(-90deg); }
.nav-subitems {
  display: flex; flex-direction: column;
}
.nav-group.collapsed .nav-subitems { display: none; }
.nav-subitem {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 16px 8px 40px;
  /* Sub-item ink = the SAME #CBD5E1 as top-level rows (Matt, 2026-08-10 — the
     old #94A3B8 measured a passing 7.88:1 on the rail but read dim next to the
     14px/500 mains). Hierarchy is carried by size (13 vs 14px), weight
     (400 vs 500) and the 40px indent — not by a second ink. */
  color: #CBD5E1;
  font-size: 13px;
  font-weight: 400;
  border-left: 3px solid transparent;
  text-decoration: none;
  cursor: pointer;
}
.nav-subitem:hover {
  background: rgba(255, 255, 255, 0.03);
  color: #E2E8F0;
}
.nav-subitem.active {
  background: #DAF4D2;
  color: #15281C;
  border-left-color: #0A8A06;
}

/* ── Defensive nav INK block — chrome must survive module `a` rules ─────────
   EM ships `[data-theme="light"] a { color:#0A7504 }` in its admin.css for
   content links. That selector (0,1,1) out-specifics every bare class row
   above (0,1,0), so EM's light theme repainted ALL nav anchors dark green on
   the dark rail — measured 3.4:1 at 13px, a straight AA fail (found by Matt
   2026-08-10). Same pattern as the #sidebar overflow guard above: the
   #sidebar prefix (1,1,0 / 1,2,0) outranks any theme-scoped anchor rule a
   module sheet can reasonably write, so chrome ink stays canonical no matter
   what loads after it. Values duplicate the rows above ON PURPOSE — keep
   them in sync. */
#sidebar .nav-item,
#sidebar .nav-group-header { color: #CBD5E1; }
#sidebar .nav-item:hover,
#sidebar .nav-group-header:hover { color: #E2E8F0; }
#sidebar .nav-subitem { color: #CBD5E1; }
#sidebar .nav-subitem:hover { color: #E2E8F0; }
/* Authoritative active-pill declaration (see the .nav-item.active note above).
   Pins all THREE properties at #sidebar specificity (1,2,0) so a module's own
   bare (0,n,0) active rule — the source of the pre-2026-08-11 light-mode fork
   across OM/MM vs TP/VP/PM/SM/EM — can no longer win. Theme-invariant literals:
   #DAF4D2 pill, #15281C ink (13.2:1), #0A8A06 border, identical in both themes. */
#sidebar .nav-item.active,
#sidebar .nav-subitem.active {
  background: #DAF4D2;
  color: #15281C;
  border-left-color: #0A8A06;
}
/* Belt-and-suspenders against a module's own [data-theme="light"] #sidebar rule
   (1,3,0) — matches that specificity and pins the same literals, so nothing a
   module sheet can reasonably write flips the pill in light theme. */
[data-theme="light"] #sidebar .nav-item.active,
[data-theme="light"] #sidebar .nav-subitem.active {
  background: #DAF4D2;
  color: #15281C;
  border-left-color: #0A8A06;
}

.nav-divider {
  height: 1px;
  background: #1E293B;
  margin: 6px 0;
}

/* Sidebar footer (CONNECTED badge + Sign out button) */
.sidebar-footer {
  padding: 12px 16px;
  border-top: 1px solid #1E293B;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.btn-back, .btn-logout {
  background: transparent;
  color: #CBD5E1;
  border: 1px solid #334155;
  border-radius: 4px;
  padding: 7px 10px;
  font-size: 12px;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  font-family: inherit;
}
.btn-back:hover {
  background: #1a1a28;
  color: #E2E8F0;
  text-decoration: none;
}
.btn-logout:hover {
  background: #1a1a28;
  color: #F87171;
  border-color: #7F1D1D;
}

/* Connection status badge (when modules surface it) */
.status-badge {
  padding: 4px 10px;
  font-size: 12px;
  font-family: var(--font-mono);
  border-radius: 4px;
  text-align: center;
}
.status-badge.connected {
  background: var(--st-success-bg);
  color: var(--st-success-fg);
  border: 1px solid var(--st-success-br);
}
.status-badge.disconnected {
  background: var(--st-danger-bg);
  color: var(--st-danger-fg);
  border: 1px solid var(--st-danger-br);
}

/* Version block (bottom of sidebar) — NO copyright text per 2026-05-13 policy.
   Format: icon · module name · version number. Copyright lives in top bar only. */
.sidebar-version {
  padding: 12px 16px;
  border-top: 1px solid #1E293B;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: #64748B;
  font-family: var(--font-mono);
  font-size: 10px;
  text-align: center;
}
.sidebar-version img {
  width: 22px; height: 22px;
  border-radius: 4px;
  opacity: 0.7;
}
.ver-name { color: #94A3B8; }
.ver-num  { color: #64748B; }

/* ── Top bar ─────────────────────────────────────────────────── */
#topBar {
  background: var(--bg-chrome);
  border-bottom: 1px solid #1E293B;
  padding: 14px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  /* Per-module color stripe along the top — see [data-mod=*] rules below.
     box-shadow inset gives the stripe without changing height/layout. */
}
#pageTitle {
  color: var(--accent-primary);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* ── Per-module color accents ────────────────────────────────────────
   Sourced from Platform/dashboard/dist/assets/css/style.css :root vars.
   Duplicated here because modules don't load Platform's style.css.
   When unifying color tokens later, this block becomes the canonical
   chrome copy.
   Driven by <html data-mod="..."> set by module-chrome.js's URL-slug
   detection. Two accent surfaces:
   1. 3px top-stripe via box-shadow inset on #topBar (peripheral cue)
   2. #pageTitle text color (central scannable cue)
   ─────────────────────────────────────────────────────────────────── */
/* ⚠ ALL GREEN as of 2026-08-02 (Matt's call) — see the long note in
   style.css's matching block for the reasoning. Tokens are KEPT per-module and
   aliased, not deleted, so re-differentiating later is a one-line change.

   ⚠ Alias `--accent-primary`, NOT style.css's `--accent`: this sheet is the
   standalone copy modules load, and its role-correct token is named
   --accent-primary (#1ACD15 dark / #0A7504 light — SK Green v2; see the two
   :root blocks at the top of this file, and note the retired #0A9C07 held the
   dark half before 2026-08-03). #pageTitle below uses these as TEXT, where a
   bright green measures 3.64:1 on white and fails AA.

   Consequence to know: the per-module top-stripe and title colour were the
   "which module am I in" peripheral cue. All-green retires that cue by design —
   wayfinding now rests on the sidebar icon and the page title text. */
:root {
  --mod-parking:     var(--accent-primary);
  --mod-maintenance: var(--accent-primary);
  --mod-employee:    var(--accent-primary);
  --mod-security:    var(--accent-primary);
  --mod-tenant:      var(--accent-primary);
  --mod-vendor:      var(--accent-primary);
  --mod-operations:  var(--accent-primary);
}
html[data-mod="parking"]       #topBar { box-shadow: inset 0 3px 0 0 var(--mod-parking); }
html[data-mod="maintenance"]   #topBar { box-shadow: inset 0 3px 0 0 var(--mod-maintenance); }
html[data-mod="employee"]      #topBar { box-shadow: inset 0 3px 0 0 var(--mod-employee); }
html[data-mod="security"]      #topBar { box-shadow: inset 0 3px 0 0 var(--mod-security); }
html[data-mod="tenant-portal"] #topBar { box-shadow: inset 0 3px 0 0 var(--mod-tenant); }
html[data-mod="vendor-portal"] #topBar { box-shadow: inset 0 3px 0 0 var(--mod-vendor); }
html[data-mod="operations"]    #topBar { box-shadow: inset 0 3px 0 0 var(--mod-operations); }

html[data-mod="parking"]       #pageTitle { color: var(--mod-parking); }
html[data-mod="maintenance"]   #pageTitle { color: var(--mod-maintenance); }
html[data-mod="employee"]      #pageTitle { color: var(--mod-employee); }
html[data-mod="security"]      #pageTitle { color: var(--mod-security); }
html[data-mod="tenant-portal"] #pageTitle { color: var(--mod-tenant); }
html[data-mod="vendor-portal"] #pageTitle { color: var(--mod-vendor); }
html[data-mod="operations"]    #pageTitle { color: var(--mod-operations); }
/* Top-bar right side — banner text "<Module> by SquareKeeper · © SquareKeeper, LLC".
   Modules use divergent class names: .topbar-meta (VP/SM/TP) or .topbar-right (MM/OM).
   Aliased here + forced to sans-serif 12px so all 7 modules render identically. */
.topbar-meta,
.topbar-right,
#topBar .topbar-meta,
#topBar .topbar-right {
  display: flex !important;
  align-items: center !important;
  gap: 8px !important;
  color: #94A3B8 !important;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif !important;
  font-size: 12px !important;
  font-weight: 400 !important;
  letter-spacing: normal !important;
  text-transform: none !important;
  white-space: nowrap !important;
}
.topbar-meta img,
.topbar-right img,
#topBar .topbar-meta img,
#topBar .topbar-right img {
  width: 16px !important;
  height: 16px !important;
  opacity: 0.6 !important;
  border-radius: 3px !important;
}

/* ── Light theme: the SIDEBAR STAYS DARK ──────────────────────────────────
   --bg-chrome is #06060C in BOTH theme blocks (Slack/Notion pattern), so the
   light-theme accent tokens — which are tuned for a WHITE page — must not be
   allowed to paint the nav. --accent-active-bg is a near-white #DBEAFE; on the
   dark rail that renders a white pill whose text measures 2.20:1 (fails AA, and
   fails even the 3:1 large-text floor). Pinning the active row to the dark-theme
   values measures 7.73:1 on the same rail.

   This lived as a copy-pasted local in OM, TP and MM (all three byte-identical)
   while PM, EM, SM, VP and Works had no guard at all — i.e. five modules shipped
   the 2.20:1 row in light theme. Promoted here 2026-07-23 so it is structural,
   not something each module has to remember; the three locals were swept.
   Measured, not eyeballed (spec §10.4). .nav-subitem.active's GROUND is not
   affected: --accent-active-subnav has no light override, stays #021505, so
   the sub-nav pill can never go near-white; its dark ink #1ACD15 measures
   8.80:1 on it. ✅ The flipping-INK gap (--accent-primary-light goes #0A7504
   in light, 3.20:1 on that ground) is CLOSED 2026-08-10 — both .active inks
   are pinned in the defensive #sidebar block up at the nav rules. */
[data-theme="light"] .nav-item.active {
  /* Green rebrand 2026-07-24: the same pinned-dark-values guard, in green.
     #1ACD15 on #04230A measures 7.83:1 — above the 7.73 the blue guard set. */
  background: #04230A;
  color: #1ACD15;
  border-left-color: #0A8A06;
}

/* Light-theme top bar */
[data-theme="light"] #topBar {
  background: #FFFFFF;
  border-bottom-color: #CBD5E1;
}
/* Top-bar meta/banner ink on the WHITE light bar. The .topbar-meta alias rule
   above pins #94A3B8 with !important — 2.56:1 here, and no module can out-rank
   it — so this override carries the same selector set + !important to win.
   Light --text-muted #475569 = 7.58:1 on #FFFFFF; dark is untouched
   (#94A3B8 on #06060C = 7.88:1). */
[data-theme="light"] .topbar-meta,
[data-theme="light"] .topbar-right,
[data-theme="light"] #topBar .topbar-meta,
[data-theme="light"] #topBar .topbar-right {
  color: var(--text-muted) !important;
}
[data-theme="light"] #pageTitle {
  color: #0A7504; /* green rebrand — 5.90:1 on the white top bar */
}
/* Per-module accents win over the light-theme default. Order matters —
   these must come AFTER the light-theme rule above. */
html[data-mod="parking"][data-theme="light"]       #pageTitle { color: var(--mod-parking); }
html[data-mod="maintenance"][data-theme="light"]   #pageTitle { color: var(--mod-maintenance); }
html[data-mod="employee"][data-theme="light"]      #pageTitle { color: var(--mod-employee); }
html[data-mod="security"][data-theme="light"]      #pageTitle { color: var(--mod-security); }
html[data-mod="tenant-portal"][data-theme="light"] #pageTitle { color: var(--mod-tenant); }
html[data-mod="vendor-portal"][data-theme="light"] #pageTitle { color: var(--mod-vendor); }
html[data-mod="operations"][data-theme="light"]    #pageTitle { color: var(--mod-operations); }

/* ── Canonical KPI stat tiles + section headings ──────────────────────
   2026-06-29 suite-audit visual-convergence fix. Every module previously
   forked .stat-box / .stat-label / .stat-value / .stat-hint /
   .page-section-heading locally, so KPI tiles drifted in typeface across the
   suite: EM rendered sans labels, PM + EM used the bare `monospace` keyword
   (Courier on Windows, not Consolas), TP-admin used an Apple ui-monospace
   stack, SM + VP rendered sans 24px numbers, and label sizes ranged 10-13px.
   Like the .topbar-meta / .topbar-right alias above, these rules force the
   canonical typography across ALL the divergent module class names so the
   suite renders identically. Two module naming schemes are aliased:
     .stat-box .label / .value / .hint        (EM, OM, MM, PM)
     .stat-label / .stat-value / .stat-hint   (SM, VP, TP-admin)
   Tile NUMBER colour is intentionally NOT forced — modules keep their
   per-tile semantic colour (inline hex today, or the .info/.warn/.danger/
   .success modifiers below); only typography + the muted label/section
   colour is normalised. Local module forks are now dead overrides; a
   follow-up can delete them and convert inline number colours to the
   modifier classes. NOTE: --font-mono now ALIASES --font-ui ('Segoe UI', …)
   — monospace is retired suite-wide (2026-07-31, BRAND_STYLE_GUIDE §4.2), so
   these tiles render the one UI typeface.
   ─────────────────────────────────────────────────────────────────── */
.stat-box {
  background: var(--bg-panel) !important;
  border: 1px solid var(--border-default) !important;
  border-radius: 8px !important;
  padding: 16px !important;
  box-shadow: none !important;
}
.stat-box:hover { border-color: var(--border-hover) !important; }

/* Tile title — small uppercase mono label */
.stat-box .label, .stat-label {
  font-family: var(--font-mono) !important;
  font-size: 11px !important;
  font-weight: 400 !important;
  letter-spacing: 1px !important;
  text-transform: uppercase !important;
  color: var(--text-muted) !important;
}

/* Tile number — big value. Typography forced; colour left to the module
   (inline semantic hex, or the modifier classes below). */
.stat-box .value, .stat-value {
  font-family: var(--font-mono) !important;
  font-size: 28px !important;
  font-weight: 700 !important;
  letter-spacing: normal !important;
}

/* Tile subtitle / caption (e.g. "Click to view") */
.stat-box .hint, .stat-hint, .stat-sub {
  font-family: var(--font-ui) !important;
  font-size: 11px !important;
  font-weight: 400 !important;
  color: var(--text-faint) !important;
}

/* Section heading (e.g. "RECENT ACTIVITY") — pairs with the mono tile label.
   Aliases the explicit section-heading class names modules use. */
.page-section-heading, .section-label, .sect-heading {
  font-family: var(--font-mono) !important;
  font-size: 13px !important;
  font-weight: 700 !important;
  letter-spacing: 0.5px !important;
  text-transform: uppercase !important;
  color: var(--text-muted) !important;
}

/* Canonical semantic tile-number colours (theme-aware via --st-*-fg). NOT
   !important so an intentional module modifier rule still wins; this just
   gives every module the same default mapping for the convention. */
.stat-box .value.info, .stat-value.info { color: var(--st-info-fg); }
.stat-box .value.success, .stat-value.success { color: var(--st-success-fg); }
.stat-box .value.warn, .stat-value.warn { color: var(--st-warn-fg); }
.stat-box .value.danger, .stat-value.danger { color: var(--st-danger-fg); }

/* ── Toast (modules may render their own) ──────────────────────
   2026-06-01 audit fix #4 (toast-cascade collision). This shared base used to
   default to the ANIMATION-START state — transform: translateX(-50%) translateY(100px)
   + opacity: 0 — that only resolved to visible when a `.visible` class was added.
   No module anywhere in the suite ever added `.visible` (verified), so every module
   whose local toast() didn't itself reset transform/opacity inherited the offscreen
   start state and rendered invisible (MM dashboard, VP/SM/EM admin) or mispositioned
   (PM web). The base is now the SHOWN state: visible, fixed bottom-center. OM (and any
   module rendering into its own .toast-container) overrides position:static locally to
   stack toasts in its corner; modules with no container inherit this centered placement
   (the bug was invisibility, not centering). The dead `.toast.visible` rule is removed. */
.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-elevated);
  color: var(--text-primary);
  border: 1px solid var(--border-hover);
  border-radius: 6px;
  padding: 12px 20px;
  font-size: 14px;
  opacity: 1;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 9999;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.toast.success { border-color: var(--st-success-br); }
.toast.error   { border-color: var(--st-danger-br); }
.toast.warn    { border-color: var(--st-warn-br); }

/* ============================================================
   SETTINGS PRIMITIVES  —  the standardized module Settings surface.
   Green-bar section headers (white ALL-CAPS, /manual style, always
   expanded) + ONE typeface underneath (hierarchy by weight/color, never
   a second face). Per BRAND_STYLE_GUIDE §8.1/§8.3; retires the §4.2
   mono-for-data doctrine on settings surfaces (Matt, 2026-07-31, after
   the OM pilot went FULL PASS). Namespaced .sk-set-* so adopting it is an
   opt-in swap. Action buttons inside a section come OFF the module's green (see
   the .btn-green / .sk-set-btn rule at the end of this block) so green stays
   reserved for the header bars.
   ============================================================ */

/* A section = one card with a full-bleed green header bar. */
.sk-set-section {
  background: var(--bg-panel);
  border: 1px solid var(--border-default);
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 16px;
  overflow: hidden;
}
.sk-set-section:last-child { margin-bottom: 0; }

/* Header = full-bleed green bar (negative margins pull it to the card edges),
   white ALL-CAPS label + optional white-outline status pill. Static — no collapse. */
.sk-set-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: -20px -20px 16px;
  background: var(--accent-fill);
  padding: 9px 16px;
}
.sk-set-label {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #FFFFFF;
  margin: 0;
}
/* Status pill on the green bar — white-outline chip (state is in the text). */
.sk-set-pill {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.65);
  background: rgba(255,255,255,0.16);
  color: #FFFFFF;
  white-space: nowrap;
}

/* One-line "what this does" description, directly under the heading. */
.sk-set-desc {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 0 0 14px;
}

/* Read-out rows: bold-muted key + regular value — SAME face + size. */
.sk-set-rows { display: flex; flex-direction: column; gap: 8px; }
.sk-set-row { display: flex; gap: 12px; font-size: 13px; line-height: 1.5; }
.sk-set-row-k { color: var(--text-muted); font-weight: 600; flex: 0 0 165px; }
.sk-set-row-v { color: var(--text-primary); }

/* Form fields: bold-muted label above the control (sentence case, one face). */
.sk-set-field { margin-bottom: 14px; }
.sk-set-field:last-child { margin-bottom: 0; }
.sk-set-field > label,
.sk-set-flabel {
  display: block;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.sk-set-field input[type="text"],
.sk-set-field input[type="email"],
.sk-set-field input[type="number"],
.sk-set-field input[type="tel"],
.sk-set-field input[type="url"],
.sk-set-field input[type="search"],
.sk-set-field input[type="password"],
.sk-set-field select,
.sk-set-field textarea,
.sk-set-input {
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-app);
  border: 1px solid var(--border-default);
  border-radius: 6px;
  padding: 12px;
  font-size: 15px;
  font-family: var(--font-ui);
  color: var(--text-primary);
}
.sk-set-field input:focus,
.sk-set-field select:focus,
.sk-set-field textarea:focus,
.sk-set-input:focus { outline: none; border-color: #0A8A06; }

/* 2-col field grid (§8.3), collapses on mobile. */
.sk-set-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .sk-set-grid { grid-template-columns: 1fr; } }

/* Choice rows (radio / checkbox lists). */
.sk-set-choices { display: flex; flex-direction: column; gap: 10px; }
.sk-set-choice {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-primary);
}
.sk-set-choice input { margin-top: 3px; }
.sk-set-choice-sub {
  display: block;
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 2px;
}

/* Action row: buttons + optional inline status. */
.sk-set-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.sk-set-status { font-size: 13px; color: var(--text-muted); }

/* Action buttons come OFF green (Matt: "if everything is special, nothing is
   special"; OM settings pilot FULL PASS 2026-07-31). A settings action button —
   the module's green primary (.btn-green) OR one explicitly opted in with
   .sk-set-btn — adopts an outline look so green stays reserved for the header bars.
   Container-scoped (.sk-set-section … = 0,2,0 specificity) so it wins over each
   module's local .btn / .btn-green (0,1,0) even though module CSS loads AFTER this
   shared sheet. Transparent + ~2px --text-faint border (measured 3.95:1 dark /
   4.76:1 light on --bg-panel — clears WCAG 1.4.11's 3:1 non-text floor; the button
   is transparent so the border is its ONLY affordance, and touch has no :hover) +
   --text-primary text; hover darkens the border to --text-muted + a faint fill.
   Destructive buttons (.btn-red / .btn.danger) keep their own red; a secondary
   .btn-outline / .btn.secondary keeps the module's subdued base outline (primary/
   secondary hierarchy) unless a module opts it in locally (e.g. OM's "Reauthorize"). */
.sk-set-section .btn-green,
.sk-set-section .sk-set-btn {
  background: transparent;
  border: 2px solid var(--text-faint);
  color: var(--text-primary);
}
.sk-set-section .btn-green:hover,
.sk-set-section .sk-set-btn:hover {
  background: var(--bg-panel-hover);
  border-color: var(--text-muted);
  color: var(--text-primary);
  opacity: 1;
}

/* ── Responsive: mobile drawer (≤900px) ──────────────────────────
   Below 900px the fixed 220px sidebar becomes an off-canvas drawer.
   module-chrome.js injects #chromeHamburger as the FIRST child of
   #topBar (an in-flow flex child — not a fixed overlay — so it sits
   IN the bar exactly like MM/OM's own .hamburger), and #chromeBackdrop
   as a <body> child (a full-viewport scrim — parent doesn't matter).
   Toggling .chrome-drawer-open on #sidebar slides it in; .active on
   #chromeBackdrop dims the page.

   Desktop (>900px) is completely unaffected: the injected hamburger
   and backdrop are display:none, and #sidebar keeps its static
   220px column. The .active / .chrome-drawer-open visual states are
   scoped INSIDE the media query, so even if the JS leaves a class
   on after a resize-to-desktop, nothing lingers visually.

   #chromeHamburger styling is a deliberate 1:1 copy of MM/OM's
   .hamburger (transparent bg, subtle border, muted text) — the
   look Matt signed off on.

   Owner: Platform. Pairs with module-chrome.js.
   ──────────────────────────────────────────────────────────────── */

/* Injected hamburger — in the DOM at all widths, hidden on desktop.
   Style copied 1:1 from MM/OM's local .hamburger. */
#chromeHamburger {
  display: none;
  background: none;
  border: 1px solid var(--border-default);
  color: var(--text-muted);
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 14px;
  line-height: 1;
}
#chromeHamburger:hover {
  border-color: var(--border-hover);
  color: var(--text-primary);
}
/* Light theme puts a WHITE top bar behind this button ([data-theme="light"] #topBar
   below), so the dark-theme greys invert on it. Measured on #FFFFFF: the old
   #94A3B8 glyph was 2.56:1 and the --border-default outline 1.23:1 — the button
   was a near-invisible smudge. Those values came from MM/OM's local .hamburger,
   written when the light top bar was still dark, and were copied here verbatim.
   The base rule's --text-muted already resolves correctly in light (#475569 =
   7.58:1), so the fix is to STOP overriding the colour and only strengthen the
   border. Dark theme is untouched (glyph 7.88:1). 2026-07-22, chrome consolidation. */
[data-theme="light"] #chromeHamburger { border-color: var(--text-faint); }        /* 4.76:1 on white */
[data-theme="light"] #chromeHamburger:hover { border-color: var(--text-secondary); }

#chromeBackdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 190;
}

/* Smooth slide — harmless on desktop (transform never changes there) */
#sidebar { transition: transform 0.22s ease; }

@media (max-width: 900px) {
  /* Sidebar → off-canvas drawer */
  #sidebar {
    position: fixed;
    top: 0; left: 0;
    height: 100vh;               /* fallback — see the #appLayout note above */
    height: 100dvh !important;   /* the drawer must end where the browser chrome starts */
    z-index: 200;
    transform: translateX(-100%);
    box-shadow: 2px 0 16px rgba(0, 0, 0, 0.45);
  }
  #sidebar.chrome-drawer-open { transform: translateX(0); }

  /* Hamburger becomes a visible in-flow flex child of #topBar */
  #chromeHamburger { display: inline-flex; align-items: center; }
  #chromeBackdrop.active { display: block; }

  /* Main area reclaims the full width the sidebar used to hold */
  #mainArea { width: 100%; }

  /* Pack hamburger + page title hard-left; drop the overflow-prone meta.
     "<Module> by SquareKeeper · © SquareKeeper, LLC" runs off-screen on
     phones — and the page title now carries the module name anyway.
     !important overrides any module-local #topBar justify-content. */
  #topBar {
    justify-content: flex-start !important;
    gap: 12px !important;
    padding: 14px 16px !important;
  }
  .topbar-meta,
  .topbar-right,
  .top-right,
  #topBar .topbar-meta,
  #topBar .topbar-right,
  #topBar .top-right { display: none !important; }

  /* Content breathes tighter on small screens */
  #pageContent { padding: 14px; }
}
