/* COPIED FROM CC-DT_Inventory dt_inventory/web/static/app.css (Scorecard shell,
   live 2026-09-30; commit in docs/design/SCORECARD-SOURCE.md). Kept verbatim except
   example figures in comments, neutralized. Site-specific rules go in
   static/pages/fixedops.css, never here. */
/* Inventory Dashboard - house design, hand-written, no framework, no build step.
   Tokens are the vendored martin-group-design ones (brand/tokens/*.css);
   everything here is layout on top of them.
   Desktop-first; breakpoints 1200 / 900 / 840 (hamburger) / 640 / 379 at the end. */

/* THE TOKENS ARE <link>ed FROM `_head.html`, NOT @import-ed HERE (fix round 2,
   close-out). An `@import url("brand/tokens/colors.css")` carries NO `?v=`, so
   the four token files were the only static URLs on the site the cache-buster
   could not reach: bumping ASSET_VERSION re-fetched app.css and then re-used
   the four cached token files behind it, which is exactly the stale-token bug
   the version exists to prevent. They are four <link>s in `_head.html` ahead of
   this file now -- same cascade order, one source of truth for the version. */

/* THE ONE TOKEN WE OVERRIDE. ARIAL FIRST, ruled by decision 35: this app
   follows Comar, which flipped to Arial after broken / partial Helvetica
   installs on Windows dealer boxes rendered every weight >= 600 as Bold
   Condensed Oblique. The design packet's D6 proposed Helvetica first; the
   project ruling wins, and DESIGN_SPEC §1.2 is corrected to match.
   THE CANONICAL HELVETICA-FIRST STACK STAYS SCOPED TO THE SWITCHER (panel and
   foot, further down this file): that component is estate chrome and must match
   the reference pixel for pixel wherever the body font differs. */
:root {
  --font-sans: Arial, 'Helvetica Neue', Helvetica, sans-serif;
  --radius: 4px;
  --radius-md: 6px;
  --sidebar-w: 216px;
  --topbar-h: 52px;
  --shadow-float: 0 2px 14px rgba(0, 0, 0, .14);
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--mg-page); color: var(--mg-text); font-family: var(--font-sans); font-size: 13px; line-height: 1.5; }
a { color: var(--mg-teal-deep); text-decoration: none; }
a:hover { color: var(--link-hover); text-decoration: underline; }
h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.2; }
h1 { font-size: 19px; } h2 { font-size: 16px; } h3 { font-size: 14px; }
p { margin: 0 0 8px; }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.muted { color: var(--mg-text-muted); } .faint { color: var(--mg-text-faint); }
.small { font-size: 12px; } .mono { font-family: var(--font-mono); }
.tabular { font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }

/* ---------- Shell ---------- */
.shell { display: flex; min-height: 100vh; }
/* Viewport-pinned rail (switcher-canonical.md §2): the rail sticks, the CONTENT
   scrolls, and .nav is the one band that scrolls internally -- so the logo plate
   above and the switcher foot below never leave the screen on a long inventory
   table. 100dvh second so it wins where supported. */
.sidebar { width: var(--sidebar-w); flex: 0 0 var(--sidebar-w); background: var(--mg-ink); color: var(--mg-ink-text); display: flex; flex-direction: column; position: sticky; top: 0; z-index: 10; height: 100vh; height: 100dvh; }
.logo-plate { background: #fff; padding: 12px 16px; border-bottom: 3px solid var(--mg-teal); display: flex; align-items: center; gap: 10px; }
.logo-plate img { height: 26px; width: auto; display: block; }
.logo-plate .app-name { font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--mg-text-2); font-weight: 600; }
.nav { padding: 10px 0; flex: 1; min-height: 0; overflow-y: auto; }
.nav-label { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-ink-label); padding: 12px 16px 4px; }
.nav a { display: flex; align-items: center; gap: 10px; padding: 9px 16px; color: var(--mg-ink-text); font-size: 13px; text-decoration: none; border-left: 3px solid transparent; }
.nav a:hover { background: var(--mg-ink-hover); color: var(--mg-ink-text-strong); text-decoration: none; }
.nav a.active { color: #fff; border-left-color: var(--mg-teal); background: var(--mg-ink-hover); }
.nav .glyph { width: 16px; text-align: center; color: var(--mg-ink-muted); }
.nav a.active .glyph { color: var(--mg-teal); }

/* ONE row: the nine-dots mount first, hard against the rail's left edge (that is
   what the asymmetric padding buys), identity beside it -- switcher-canonical.md
   §2. margin-top:auto pins the block to the foot even when .nav is short. The
   canonical font stack is restated because the panel and foot are ESTATE chrome
   and must match the reference even where the body font differs. */
.sidebar-foot {
  margin-top: auto;
  border-top: 1px solid var(--mg-ink-divider);
  padding: 12px 10px 12px 8px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--mg-ink-text);
  font-family: Helvetica, Arial, sans-serif;
}
/* Identity is name + email ONLY -- not a link, not a button, no role line, no
   sign-out (canonical §2/§4). min-width:0 is what lets the ellipsis engage
   inside a flex row. */
.sidebar-foot-id { min-width: 0; flex: 1; }
.sidebar-user { font-weight: 600; color: var(--mg-ink-text-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-email { margin-top: 2px; font-size: 12px; color: var(--mg-ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Fallback line, rendered ONLY when the session carries no email. */
.sidebar-scope { margin-top: 2px; font-size: 11px; color: var(--mg-ink-muted); text-transform: capitalize; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* Consistent header height across every route (canonical §7). */
.topbar { min-height: var(--topbar-h); background: #fff; border-bottom: 1px solid var(--mg-border); display: flex; align-items: center; gap: 12px; padding: 0 24px; position: sticky; top: 0; z-index: 5; }
.topbar h1 { font-size: 17px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: .12em; }
.topbar .fresh { font-size: 12px; color: var(--mg-text-muted); white-space: nowrap; }
.hamburger { display: none; width: 44px; height: 40px; border: 1px solid var(--mg-border-control); background: #fff; border-radius: var(--radius); cursor: pointer; position: relative; }
.hamburger span, .hamburger span::before, .hamburger span::after { content: ""; position: absolute; left: 12px; width: 18px; height: 2px; background: var(--mg-ink); }
.hamburger span { top: 19px; } .hamburger span::before { top: -6px; left: 0; } .hamburger span::after { top: 6px; left: 0; }
.content { padding: 20px 24px 40px; flex: 1; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.page-head .sub { color: var(--mg-text-muted); font-size: 12px; }
.actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* ---------- Cards / tiles ---------- */
.card { background: var(--mg-card); border: 1px solid var(--mg-border); border-radius: var(--radius); padding: 14px 16px; }
.card + .card, .grid + .card, .card + .grid { margin-top: 16px; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.card-head h2 { font-size: 14px; }
.grid { display: grid; gap: 12px; }
.grid > * { min-width: 0; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tile { background: #fff; border: 1px solid var(--mg-border); border-radius: var(--radius); padding: 12px 14px; display: block; color: inherit; text-decoration: none; }
.tile:hover { text-decoration: none; border-color: var(--mg-teal-tint-border); background: var(--mg-strip); color: inherit; }
.tile .label { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-text-muted); font-weight: 600; }
.tile .value { font-size: 28px; font-weight: 600; line-height: 1.15; margin-top: 4px; font-variant-numeric: tabular-nums; }
.tile .meta { font-size: 12px; color: var(--mg-text-2); margin-top: 6px; }

/* ---------- Badges ---------- */
.badge { display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; border: 1px solid transparent; white-space: nowrap; line-height: 1.6; }
.badge.ok, .badge.good, .badge.active { background: var(--mg-green-tint); color: var(--mg-green); border-color: var(--mg-green-border); }
.badge.warn { background: var(--mg-amber-tint); color: var(--mg-amber-text); border-color: var(--mg-amber-border); }
.badge.bad, .badge.inactive { background: var(--mg-red-tint); color: var(--mg-red); border-color: var(--mg-red-border); }
.badge.neutral { background: var(--mg-neutral-tint); color: var(--mg-neutral); border-color: var(--mg-border); }
.badge.teal { background: var(--mg-teal-tint); color: var(--mg-teal-deep); border-color: var(--mg-teal-tint-border); }

/* ---------- Alerts ---------- */
.alert { padding: 8px 12px; border-radius: var(--radius); border: 1px solid; font-size: 13px; margin-bottom: 12px; }
.alert.ok { background: var(--mg-green-tint); border-color: var(--mg-green-border); color: var(--mg-green); }
.alert.warn { background: var(--mg-amber-tint); border-color: var(--mg-amber-border); color: var(--mg-amber-text); }
.alert.error { background: var(--mg-red-tint); border-color: var(--mg-red-border); color: var(--mg-red); }

/* ---------- Tables (house palette; stripe anything over ~4 rows) ---------- */
.table-wrap { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th { background: var(--mg-table-head); color: var(--mg-text-2); font-size: 11px; letter-spacing: .5px; text-transform: uppercase; text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--mg-border); white-space: nowrap; }
/* RULING 2026-09-12: every column HEADER on every table is centre-justified;
   BODY cells keep their own alignment (text left, numbers right) and stay
   congruent down the column. Scoped to `thead` on purpose -- `table.data th`
   above also styles the body's `th[scope="row"]` identity cells, and those stay
   left. Page stylesheets that give a shared class a right-alignment carry a
   matching `thead` override beside the original rule. */
table.data thead th { text-align: center; }
table.data td { padding: 7px 10px; border-bottom: 1px solid var(--mg-border-row); vertical-align: middle; }
table.data tbody tr:nth-child(even) td { background: var(--mg-zebra); }
table.data tbody tr:hover td { background: var(--mg-row-hover); }
/* Identity columns never wrap and never bare-truncate: font-size steps instead. */
td.identity, th.identity { white-space: nowrap; }
tr.is-inactive td { color: var(--mg-text-faint); }

/* ---------- Forms / buttons ---------- */
label.field { display: block; margin-bottom: 12px; }
label.field > span, .field-label { display: block; font-size: 12px; font-weight: 600; color: var(--mg-text-2); margin-bottom: 4px; }
input[type=text], input[type=email], select { width: 100%; font: inherit; font-size: 14px; padding: 8px 10px; border: 1px solid var(--mg-border-control); border-radius: var(--radius); background: #fff; color: var(--mg-text); }
input:focus, select:focus, button:focus-visible, a:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--mg-teal-tint), 0 0 0 3px var(--mg-teal); }
.check { display: flex; align-items: center; gap: 10px; padding: 8px 0; font-size: 14px; }
.check input { width: 20px; height: 20px; margin: 0; accent-color: var(--mg-teal); }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font: inherit; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: var(--radius); border: 1px solid var(--mg-border-control); background: #fff; color: var(--mg-text); cursor: pointer; text-decoration: none; min-height: 36px; }
.btn:hover { background: var(--mg-strip); text-decoration: none; color: var(--mg-text); }
.btn.primary { background: var(--mg-teal); border-color: var(--mg-teal); color: #fff; }
.btn.primary:hover { background: var(--mg-teal-hover); color: #fff; }
.btn.danger { color: var(--mg-red); border-color: var(--mg-red-border); }
.btn.danger:hover { background: var(--mg-red-tint); }
.btn.sm { padding: 4px 10px; min-height: 28px; font-size: 12px; }
.btn.block { width: 100%; }
.row-form { display: inline-flex; align-items: center; gap: 6px; margin: 0; }
.row-form select { width: auto; font-size: 12px; padding: 3px 6px; min-height: 28px; }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 14px; font-size: 13px; }
.kv dt { color: var(--mg-text-muted); } .kv dd { margin: 0; }

/* ==========================================================================
   App switcher -- nine dots.

   Transcribed from docs/handoffs/switcher-canonical.md (v3, 2026-09-29),
   which is the SINGLE SOURCE OF TRUTH for this component. Every value below is
   either the canonical literal or a --mg-* token whose value is IDENTICAL to it
   (checked one by one: teal #17A38C, teal-deep #0E7C6B, teal-tint #E2F3EF,
   teal-tint-border #B9E0D7, ink-hover #2E3836, ink-text #B9C2BF,
   ink-text-strong #E8ECEA, ink-muted #7E8A86, ink-divider #3A4442,
   row-hover #F6FAF9, text #222927, text-2 #5A6461, text-muted #6B7370,
   border #DDE1DF, card #FFFFFF, the amber trio #8A6D1F/#FBF3D9/#E8D89A,
   radius 4px, radius-md 6px, shadow-float). No "close-enough" token anywhere.

   base_dashboard.html mounts the partial TWICE: .switcher--foot in the dark
   rail foot and .switcher--head in the light topbar. Exactly one is visible at
   any width; the swap lives in the 840px query at the end of this file.
   ========================================================================== */

/* The trigger and its panel share ONE position:relative wrapper -- that wrapper
   is the panel's positioning context, so the panel is never clipped by .nav's
   scroller or any other overflow box (canonical §3; a clipped panel shipped live
   on Fleet 2026-08-23 and rendered tiles with the page bleeding through). */
.switcher { position: relative; display: inline-flex; }

/* --- §1 the dots trigger. The <summary> IS the button, so the marker is
   suppressed and the box is the canonical 6px pad, not a 36px tap square. */
.switcher-btn {
  display: inline-flex;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  cursor: pointer;
  list-style: none;
  transition: background-color 80ms linear, color 80ms linear, border-color 80ms linear;
}
.switcher-btn::-webkit-details-marker { display: none; }
/* Nine spans on a 3x3 grid painted with currentColor, so one color change on the
   button recolors all nine dots at once. */
.switcher-glyph { display: grid; grid-template-columns: repeat(3, 3px); gap: 3px; }
.switcher-dot { width: 3px; height: 3px; border-radius: 999px; background: currentColor; }

/* §0 focus ring: :focus-visible ONLY, so a mouse press never draws it. Shared by
   the trigger, the pill and the tile links. */
.switcher-btn:focus-visible,
.switcher-pill:focus-visible,
.switcher-back:focus-visible,
.switcher-app:focus-visible {
  outline: 2px solid var(--mg-teal);
  outline-offset: 1px;
  border-radius: 3px;
  box-shadow: none;
}

/* Trigger colors are per SURFACE (§1): rest is that surface's own nav-text
   color -- never brand teal at rest, never a filled rest button -- and hover and
   open share ONE treatment. */
.switcher--foot > .switcher-btn { color: var(--mg-ink-text); }
.switcher--foot > .switcher-btn:hover,
.switcher--foot[open] > .switcher-btn,
.switcher--foot.is-open > .switcher-btn { background: var(--mg-ink-hover); color: var(--mg-ink-text-strong); }
.switcher--head > .switcher-btn { color: var(--mg-text-muted); }
.switcher--head > .switcher-btn:hover,
.switcher--head[open] > .switcher-btn,
.switcher--head.is-open > .switcher-btn { background: var(--mg-row-hover); color: var(--mg-text); }

/* --- §3 the panel. One opaque box. No max-height/overflow: it must not scroll
   or clip. */
.switcher-panel {
  position: absolute;
  width: 268px;
  padding: 10px;
  background: var(--mg-card);
  color: var(--mg-text);
  border: 1px solid var(--mg-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  z-index: 40;
  font-family: Helvetica, Arial, sans-serif;
}
/* Opens UPWARD, left-aligned: at the foot of a pinned rail there is no room
   below. */
.switcher--foot .switcher-panel { bottom: calc(100% + 6px); left: 0; }
/* Opens DOWNWARD, right-anchored: the head mount sits at the bar's right edge,
   so anchoring right is what keeps it on screen. */
.switcher--head .switcher-panel { top: calc(100% + 6px); right: 0; max-width: calc(100vw - 28px); }

/* --- §4 the head row: "Switch Sites" + the sign-out pill. ALWAYS rendered,
   even with an empty app list -- sign-out has to survive a registry failure. */
.switcher-head-row { display: flex; align-items: center; gap: 8px; padding: 0 6px 8px; }
.switcher-head-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--mg-text-muted);
  min-width: 0;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* §5b drilled view: the back button stands in for the label with the label's
   exact look, but HUGS its text (margin-right:auto keeps the pill hard right) so
   the hit area is the words, not the row. */
.switcher-back {
  min-width: 0;
  margin: 0 auto 0 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--mg-text-muted);
  font-family: inherit;
  font-size: 10px;
  line-height: 1.5;
  letter-spacing: 1px;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.switcher-back:hover { color: var(--mg-text); }
/* The label, back buttons and member grids are all server-rendered; JS swaps
   them with the `hidden` attribute, which the display rules here (and the
   <380px `.switcher--head .switcher-grid` rule) would otherwise out-rank. */
.switcher .switcher-panel [hidden] { display: none; }
.switcher-signout { margin: 0; flex: none; }
/* The estate's ONE sign-out affordance at every width: the submit button of this
   app's existing POST /auth/logout form. */
.switcher-pill {
  flex: none;
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border: 1px solid var(--mg-teal);
  border-radius: 999px;
  background: var(--mg-teal);
  color: #FFFFFF;
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  line-height: 1.5;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  min-height: 0;
  transition: background-color 80ms linear, color 80ms linear, border-color 80ms linear;
}
.switcher-pill:hover { background: var(--mg-teal-deep); border-color: var(--mg-teal-deep); color: #FFFFFF; }

/* --- §5 the tile grid. */
.switcher-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.switcher-app {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 4px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: 11px;
  text-align: center;
  transition: background-color 80ms linear, color 80ms linear, border-color 80ms linear;
}
/* Long app names WRAP inside the tile rather than ellipsizing. */
.switcher-name { line-height: 1.2; }
a.switcher-app { color: var(--mg-text-2); text-decoration: none; }
/* The hover trio -- background, border, text -- plus the icon recolor. The global
   a:hover underline is cancelled: a tile is a box, not body copy. */
a.switcher-app:hover { background: var(--mg-row-hover); border-color: var(--mg-teal-tint-border); color: var(--mg-text); text-decoration: none; }
a.switcher-app:hover .switcher-icon { color: var(--mg-teal-deep); }
/* Current app: the same box as its neighbors, tinted and INERT (it renders as a
   div -- nothing to click, nothing to tab to). No hover change. */
.switcher-app.is-current {
  background: var(--mg-teal-tint);
  border-color: var(--mg-teal-tint-border);
  color: var(--mg-teal-deep);
  font-weight: 600;
  cursor: default;
}
.switcher-app.is-current .switcher-icon { color: var(--mg-teal-deep); }
/* §5b group tile: the §5 box as a <button>. The reset keeps the browser's
   button chrome (grey fill, line-height normal, shrink-to-fit, system font) off
   the tile -- and keeps .switcher-app's 11px: never the `font: inherit`
   shorthand here, which would out-specify it. */
button.switcher-app {
  width: 100%;
  margin: 0;
  background: none;
  color: var(--mg-text-2);
  font-family: inherit;
  font-weight: inherit;
  line-height: 1.5;
  cursor: pointer;
}
/* A group holding THIS app: the current tile's tint, but still a live button. */
.switcher-group.has-current {
  background: var(--mg-teal-tint);
  border-color: var(--mg-teal-tint-border);
  color: var(--mg-teal-deep);
  font-weight: 600;
}
.switcher-group.has-current .switcher-icon { color: var(--mg-teal-deep); }
/* Hover is the §5 link-tile trio + icon recolor, and it OVERRIDES the tint:
   button.switcher-group:hover (0,2,1) out-ranks .switcher-group.has-current. */
button.switcher-group:hover { background: var(--mg-row-hover); border-color: var(--mg-teal-tint-border); color: var(--mg-text); }
button.switcher-group:hover .switcher-icon { color: var(--mg-teal-deep); }
/* The trailing "›" drill-in cue; inherits the tile color. */
.switcher-cue { margin-left: 2px; }
/* The icon slot is the ONLY place registry markup is injected; currentColor on
   the artwork is what lets the tile states recolor it. */
.switcher-icon { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: var(--mg-text-muted); }
.switcher-icon svg { width: 24px; height: 24px; display: block; }
/* No-icon fallback: the app name's first character in the same slot. */
.switcher-letter { font-size: 13px; font-weight: 700; }
/* A maintenance row STAYS a clickable link and gains this badge. */
.switcher-maint {
  display: inline-block;
  margin-top: 2px;
  padding: 2px 6px;
  border: 1px solid var(--mg-amber-border);
  border-radius: 3px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--mg-amber-text);
  background: var(--mg-amber-tint);
}
/* Desktop-first: the head mount is the NARROW-width one, so it is off by default
   and the 840px query swaps the pair. */
.switcher--head { display: none; }

/* ---------- Login / error / not-provisioned ---------- */
.center-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.login-card { width: 100%; max-width: 420px; background: #fff; border: 1px solid var(--mg-border); border-radius: var(--radius-md); padding: 24px; }
.login-card .brand { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: 18px; }
.login-card .brand img { height: 32px; }
.login-card .brand .app { font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--mg-text-2); font-weight: 600; }

/* ---------- "Coming soon" placeholder ---------- */
.placeholder { text-align: center; padding: 48px 24px; color: var(--mg-text-muted); }
.placeholder .glyph-big { font-size: 34px; color: var(--mg-text-faint); display: block; margin-bottom: 10px; }
.placeholder h2 { font-size: 18px; color: var(--mg-text); margin-bottom: 8px; }


/* ==========================================================================
   PANEL CHROME AND SHARED COMPONENTS -- DESIGN_SPEC §2, §3, §6.2.
   Structure only; every colour is a --mg-* token, or the literal the design
   states where no token carries it (context strip, stamps, neutral badge).
   ========================================================================== */

/* ---------- Context strip (§2.2): 38px, every page ---------- */
.ctx-strip {
  min-height: 38px;
  background: #fff;
  border-bottom: 1px solid var(--mg-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 4px 24px;
  flex-wrap: wrap;
}
.crumbs { font-size: 12px; color: var(--mg-text-2); min-width: 0; }
.crumbs a { color: var(--mg-teal-deep); }
.crumbs .sep { color: var(--mg-text-faint); margin: 0 6px; }
.crumbs .crumb-here { color: var(--mg-text); font-weight: 600; }
/* The scope chip (ruling B3): what this page is filtered to, in words, beside
   the breadcrumb. NOT a badge tone -- a badge colour means a STATE (red = over,
   amber = estimate) and the scope is not a state. Neutral strip ink, one step
   up from the crumbs, with the Clear link in the house teal.
   `margin-right: auto` keeps the strip's two ends where the design put them:
   crumbs and chip left, selects and stamps right. */
.scope-chip { display: inline-flex; align-items: center; gap: 6px; margin-right: auto; font-size: 12px; background: var(--mg-strip); border: 1px solid var(--mg-border); border-radius: var(--radius); padding: 2px 8px; max-width: 100%; min-width: 0; }
.scope-chip-label { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-text-muted); font-weight: 600; }
.scope-chip-value { color: var(--mg-text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scope-chip-clear { display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; padding: 1px 8px; font-size: 11px; font-weight: 700; color: #0E7C6B; background: #FFFFFF; border: 1px solid #C9CFCC; border-radius: 3px; text-decoration: none; white-space: nowrap; }
.scope-chip-clear::before { content: "\D7"; font-size: 13px; line-height: 1; }
.scope-chip-clear:hover { color: #128A76; border-color: #17A38C; background: #F6FAF9; }
.scope-chip.is-all .scope-chip-value { color: var(--mg-text-2); font-weight: 400; }
/* The scope's own reset note (B4). The panels' `.filters-reset` is a block in a
   filter strip; this one has to sit in a 38px row, so it is the same amber in
   one line. */
.scope-reset { font-size: 11px; color: var(--mg-amber-text); background: var(--mg-amber-tint); border: 1px solid var(--mg-amber-border); border-radius: var(--radius); padding: 1px 8px; }
.scope { display: flex; align-items: center; gap: 8px; margin: 0; flex-wrap: wrap; }
.scope-label { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-text-muted); font-weight: 600; }
.scope select { width: auto; font-size: 12px; padding: 3px 6px; min-height: 26px; }
.scope-divider { width: 1px; align-self: stretch; background: var(--mg-border); margin: 2px 4px; }

/* Both stamps, stacked, 12px, bold values. THREE states: active, degraded
   (amber "N of 14 stores"), and not used on this page. */
.fresh-stamps { display: flex; flex-direction: column; gap: 1px; font-size: 12px; }
.stamp { display: flex; gap: 5px; align-items: baseline; white-space: nowrap; }
.stamp-label { color: #6B7370; }
.stamp-value { color: #222927; font-weight: 700; }
.stamp.is-unused .stamp-label, .stamp.is-unused .stamp-value { color: #98A09D; font-weight: 400; }
/* The reason is a real sentence a person is meant to read, so it keeps the
   legible muted ink rather than the faint one (REVIEW S C1). */
.stamp-reason { color: #6B7370; }
/* The degraded stamp: the amber lives on the chip, and the state class stays on
   the row so a future treatment has one hook. */
.stamp.is-degraded .stamp-label { color: var(--mg-amber-text); }
.stamp-degraded { color: var(--mg-amber-text); background: var(--mg-amber-tint); border: 1px solid var(--mg-amber-border); border-radius: 3px; padding: 0 5px; font-weight: 700; font-size: 10px; letter-spacing: .5px; text-transform: uppercase; }
/* The not-used stamp (Scorecard HANDOFF §9.12): "MAX as of <date> · not used
   on this page", no load time. On a phone the sentence is wider than 390px, so
   an unused stamp may wrap instead of overflowing the context strip. Shell rule,
   every page (moved here from sc-reports.css, 2026-09-30). */
.stamp.is-unused .stamp-loaded { display: none; }
.stamp.is-unused .stamp-reason::before { content: "· "; }
@media (max-width: 640px) { .stamp.is-unused { flex-wrap: wrap; white-space: normal; } }
/* The degraded stamp ("DealerTrack as of <date> · loaded <time> CT · 4 OF 14
   STORES") is wider than 375px too: it wraps on a phone the same way (Fixed
   Operations Reports, 2026-10-07). */
@media (max-width: 640px) { .stamp.is-degraded { flex-wrap: wrap; white-space: normal; } }

/* ---------- Page header (§2.3) ---------- */
.page-header { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 12px 24px 0; }
.page-title { font-size: 19px; font-weight: 700; flex: 1; min-width: 0; }
.snapshot { display: flex; align-items: center; gap: 8px; margin: 0; }
.snapshot select { width: auto; font-size: 12px; padding: 3px 6px; min-height: 28px; }
.snapshot-caption { font-size: 11px; color: var(--mg-text-muted); }
.page-actions { display: flex; gap: 8px; align-items: center; }

/* ---------- Filter strip (§2.4) ---------- */
.filter-strip {
  background: #FAFBFA;
  border-bottom: 1px solid var(--mg-border);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 24px; margin: 0 0 12px;
}
.pill { display: inline-flex; align-items: center; gap: 6px; }
.pill-label { font-size: 12px; color: var(--mg-text-2); }
.pill select, .pill input[type=search] { width: auto; font-size: 12px; padding: 4px 8px; min-height: 30px; }
/* The search field holds a SENTENCE of a placeholder -- "Search VIN, stock #,
   make or model" (round 5, B11) -- and a placeholder the reader cannot finish
   reading is a control that does not say what it searches. Wide enough for the
   longest one at 12px; on a phone the 640 block takes it to 100% anyway. */
.pill input[type=search] { min-width: 248px; }
.pill.is-pending { opacity: .55; }
.clear { font-size: 12px; }
.filter-count { margin-left: auto; font-size: 12px; color: var(--mg-text-2); font-variant-numeric: tabular-nums; }
.filters-reset { font-size: 12px; color: var(--mg-amber-text); background: var(--mg-amber-tint); border: 1px solid var(--mg-amber-border); border-radius: var(--radius); padding: 6px 10px; }

/* ---------- Badges: 10px/700 uppercase, .5px tracking, 3px radius ---------- */
.mg-badge { display: inline-flex; align-items: center; padding: 1px 5px; border: 1px solid transparent; border-radius: 3px; font-size: 10px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase; white-space: nowrap; }
.b-neutral { color: #4A5350; background: #ECEEED; border-color: #C9CFCC; }
.b-red { color: var(--mg-red); background: var(--mg-red-tint); border-color: var(--mg-red-border); }
.b-est { color: var(--mg-amber-text); background: var(--mg-amber-tint); border-color: var(--mg-amber-border); }
.b-act { color: var(--mg-green); background: var(--mg-green-tint); border-color: var(--mg-green-border); }
.b-mixed { color: #4A5350; background: #ECEEED; border-color: #C9CFCC; }
.b-pending { color: #6B7370; background: #ECEEED; border-color: #C9CFCC; font-weight: 400; }
.pending-caption { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--mg-text-muted); margin-top: 8px; }

/* ---------- DrillLink (§3, D3): dotted at rest, solid on hover ---------- */
a.drill { color: var(--mg-teal-deep); text-decoration: none; border-bottom: 1px dotted var(--mg-teal-deep); }
a.drill:hover { text-decoration: underline; border-bottom-color: transparent; }
/* A zero is NEVER a link. */
.zero { color: #C7CCCA; }

/* ---------- Value tones (D11: red and amber only in dense tables) ---------- */
.t-red { color: var(--mg-red); }
.t-amber { color: var(--mg-amber-text); }
.t-good { color: var(--mg-green); }
.t-neutral { color: #4A5350; }

/* ---------- LeadTile (§6.1, D17) ---------- */
.lead-tile { background: #fff; border: 1px solid var(--mg-border); border-radius: var(--radius); padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; color: inherit; text-decoration: none; }
a.lead-tile:hover { border-color: var(--mg-teal-tint-border); background: var(--mg-strip); text-decoration: none; color: inherit; }
.lead-tile .label { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-text-muted); font-weight: 600; }
.lead-tile .value { font-size: 28px; font-weight: 700; line-height: 1.15; }
.lead-tile.v-money .value { font-size: 22px; }
.lead-tile .tile-badges { display: flex; gap: 6px; }
/* Subtext WRAPS, never nowrap (D17). */
.lead-tile .sub { font-size: 12px; color: var(--mg-text-2); white-space: normal; }

/* ---------- SummaryTable (§7.2, D16) ---------- */
.summary-table th.identity, .summary-table td.identity { white-space: nowrap; }
/* Banding is BY DIVISION, never by row parity: divisions 1 and 3 on card white,
   division 2 on the zebra tone, so a division reads as one block. The generic
   even-row zebra is cancelled here for exactly that reason. */
.summary-table tbody tr:nth-child(even) td { background: transparent; }
.summary-table tbody tr.band-0 td, .summary-table tbody tr.band-0 th { background: var(--mg-zebra); }
.summary-table tbody tr:hover td { background: var(--mg-row-hover); }
.summary-table tr.r-division_total td, .summary-table tr.r-division_total th { background: #FAFBFA; font-weight: 600; border-top: 1px solid var(--mg-border); }
.summary-table tr.r-group_total td, .summary-table tr.r-group_total th { background: var(--mg-table-head); font-weight: 700; border-top: 1px solid #C9CFCC; }
/* Pending columns: reserved and inert, faint em dash, REGULAR-weight header, so
   the printed page still lines up column for column (D15). */
.summary-table th.is-pending { font-weight: 400; color: #6B7370; }
.summary-table td.is-pending { background: #ECEEED; }
.pending-cell { color: #98A09D; }

/* ---------- Two-line column headers (round 2 item 10) ----------------------
   The header was the widest thing in most numeric columns -- "60-Day Level" is
   wider than any count it labels -- so a nowrap header sets the width of the
   whole column. Headers therefore WRAP and the body keeps nowrap, the same
   trade the printed replicas already make (static/pages/reports.css §Header
   labels wrap). It is CSS and it is GENERIC: a <br> baked into a label string
   would travel into the CSV and the print footer, and the labels are renamed by
   other lanes.

   The ceilings are per column KIND (`hk-*`, emitted by the table macros from
   `column.kind`) rather than per column, so a column added to a page inherits
   the right budget with no rule written for it. They are CEILINGS, not widths:
   a cell still grows for its own content. The numbers are the reports' 78 / 64
   / 58 / 56 px stepped up ~10% for the dashboard's 13px body type against the
   reports' 11.5px. */
table.data thead th.wrap-h {
  white-space: normal;
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
  line-height: 1.1;
  vertical-align: bottom;
  font-size: 10.5px;
}
table.data thead th.hk-money { max-width: 86px; }
table.data thead th.hk-rate  { max-width: 72px; }
table.data thead th.hk-days  { max-width: 70px; }
table.data thead th.hk-count { max-width: 66px; }
table.data thead th.hk-pct   { max-width: 62px; }
/* An EXPLICIT two-line header (`label_lines` on the column spec) when the break
   point matters -- "Over / Under" above "60-Day Level" reads wrong broken any
   other way. Each line is its own block, so the two never re-flow into one.

   A DECLARED LINE ALSO NEVER BREAKS AGAIN (round 6 close-out, 2026-09-13).
   `display: block` stopped the lines re-flowing INTO each other; nothing
   stopped one of them wrapping inside itself. Measured at 1440x900 on the
   Photo-problems drill: "Photos on" / "MAX" came out as three uppercase lines
   ("PHOTOS" / "ON" / "MAX") because the 80px column was narrower than the
   letter-spaced first line, and the whole thead grew a row taller for it. A
   declared break point is the ONLY break point. */
table.data thead th.wrap-h .th-line { display: block; white-space: nowrap; }
/* AN EXPLICIT LABEL DROPS ITS KIND CEILING. The `hk-*` max-widths exist to make
   a long single-string label wrap; a `label_lines` header has already declared
   its break points, so the ceiling has no work left to do -- and under the
   `table-layout: auto` of `.tight-summary` it does harm: a max-width narrower
   than the longest UNBREAKABLE word does not shrink the word, it lets it spill
   over the next header ("Wholesale" across "Cost", round 3 item 2).
   Measured at 1440px 2026-09-12: the two `label_lines` headers in the app
   (over-age used "Wholesale / Due", over-age new "Over / Under / 60-Day Level")
   go to their natural 86px and 85px; every neighbouring column keeps the width
   it had. */
table.data thead th.wrap-h:has(.th-line) { max-width: none; }
/* The body never wraps: a number split over two lines is unreadable, and the
   header is the thing that was setting the width. */
table.data tbody td.tabular, .summary-table tbody td.right { white-space: nowrap; }

/* ---------- Division delineation (round 2 item 7) --------------------------
   Three signals, deliberately cheap: store rows INDENT inside their division,
   the division total keeps its colour block, and a heavier rule closes the
   block underneath it. A spacer row was the alternative and was rejected -- it
   breaks the zebra/banding parity and prints as a stray empty line. */
.summary-table tbody th.identity.indent-1 { padding-left: 26px; }
.summary-table tbody tr.div-rule td, .summary-table tbody tr.div-rule th {
  border-bottom: 2px solid #9AA5A1;
}
/* The phone gets the same STEP without the rule doing the work: a card list has
   no columns to line up, so the indent is a card inset plus a rail. */
.sum-card.indent-1 { margin-left: 10px; border-left: 3px solid var(--mg-border); }
.sum-card.div-rule { border-bottom: 2px solid #9AA5A1; margin-bottom: 12px; }

/* ---------- Whole-row drill (round 2 item 8) -------------------------------
   `data-href` on the <tr>, read by static/tables.js through ONE delegated
   listener. The round-1 note in _unit_list.html refused a row-level onclick
   because it would have interpolated a VIN into a JavaScript string literal on
   every row; an attribute is escaped by the template engine and read by a
   listener, which is the same affordance without that. The identity <a> stays,
   so the row still opens with no JS and still tabs like a link. */
tr[data-href] { cursor: pointer; }
tr[data-href]:focus-visible { outline: 2px solid var(--mg-teal); outline-offset: -2px; }
.footnotes { margin: 10px 0 0; padding-left: 18px; font-size: 12px; color: var(--mg-text-muted); }
.footnotes li { margin-bottom: 2px; }
.print-changes { margin-top: 12px; border-top: 1px solid var(--mg-border); padding-top: 8px; font-size: 12px; color: var(--mg-text-2); }
.print-changes-head { font-weight: 600; margin-bottom: 4px; }

/* ---------- UnitTable (§6.2) ---------- */
.unit-table td { height: 34px; }
.unit-table tbody tr.unit-row { cursor: pointer; }
.unit-table th a { color: inherit; }
/* VIN: mono 12px, NEVER wrapped and never truncated. */
.unit-table td.vin, .uc-line3 { font-size: 12px; white-space: nowrap; }
.code { font-size: 12px; }
.list-foot { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; font-size: 12px; color: var(--mg-text-2); margin-top: 8px; }

/* ---------- The 640 swap: ONE component, two renderings ---------- */
.t-cards { display: none; }
/* A16 (red team 2): `merch/_units.html` emits the SAME pair -- a wide table and
   a card list -- and its swap lived in `pages/merch.css`, which only three
   pages load. `/overage-used/wholesale-due` calls the macro and loads
   `turn.css`, so on a phone it drew the table AND all seventeen cards. The
   swap belongs to the component, not to a page stylesheet, so it sits here
   beside `.t-wide`/`.t-cards`: every caller of the macro gets ONE rendering.
   Base rule BEFORE the media block, same reason as `.rec-cards` below. */
.unit-cards { display: none; }
.unit-cards-head { font-size: 12px; color: var(--mg-text-muted); margin-bottom: 6px; }
.unit-card { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; border: 1px solid var(--mg-border); border-radius: var(--radius); color: inherit; text-decoration: none; margin-bottom: 6px; }
.unit-card:nth-of-type(even) { background: var(--mg-zebra); }
.unit-card:hover { text-decoration: none; color: inherit; border-color: var(--mg-teal-tint-border); }
.uc-line1 { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.uc-days { font-size: 16px; font-weight: 700; }
.uc-ident { display: flex; gap: 8px; font-size: 12px; color: var(--mg-text-2); }
.uc-line2 { font-size: 14px; font-weight: 600; }
.uc-line4 { display: flex; gap: 10px; align-items: baseline; justify-content: space-between; font-size: 13px; }
.uc-miles { color: var(--mg-text-2); }
.sum-card { border: 1px solid var(--mg-border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 6px; }
.sum-card.r-group_total { background: var(--mg-table-head); border-color: #C9CFCC; }
.sum-card.r-division_total { background: #FAFBFA; }
.sum-card-id { font-weight: 600; display: flex; gap: 8px; align-items: baseline; justify-content: space-between; }
.sum-card-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px 8px; }
.sum-cell { display: flex; flex-direction: column; min-height: 44px; justify-content: center; }
.sum-cell-label { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--mg-text-muted); }
.sum-cell-value { font-size: 15px; font-weight: 600; }
.sum-card-money { font-size: 12px; color: var(--mg-text-2); margin: 4px 0 0; }

/* ---------- Empty / loading (house defaults; not designed) ---------- */
.empty { padding: 24px; text-align: center; color: var(--mg-text-muted); }
.empty-mark { display: block; font-size: 22px; color: var(--mg-text-faint); margin-bottom: 6px; }
.empty-head { font-weight: 600; color: var(--mg-text); margin-bottom: 2px; }
.empty-detail { font-size: 12px; margin: 0; }
/* An hx-indicator STRIP, not a spinner: this brand barely moves. */
.loading-strip { height: 2px; background: var(--mg-teal); opacity: 0; transition: opacity 80ms linear; }
.htmx-request.loading-strip, .htmx-request .loading-strip { opacity: 1; }
.topbar-logo { display: none; height: 22px; }

/* ---------- Responsive (end of file, max-width only) ---------- */
@media (max-width: 1200px) {
  .tile .value { font-size: 24px; }
}
@media (max-width: 900px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 840px) {
  /* The whole rail -- its switcher foot included -- hides behind the hamburger
     here, so the dots move to the ALWAYS-visible topbar: canonical §2 forbids
     the switcher living inside a collapsed menu. Exactly one mount is visible at
     any width, which is also what keeps ONE sign-out pill on screen. */
  .switcher--head { display: inline-flex; }
  .switcher--foot { display: none; }
  /* The freshness stamp eats the width the title and the dots need on a phone. */
  .topbar .fresh { display: none; }
  .shell { display: block; }
  .sidebar { position: static; width: auto; height: auto; display: none; flex-direction: column; }
  .js-nav .sidebar { display: none; }
  /* ROUND 6 / B4 -- the drawer opens DOWNWARD, as an overlay.
     The phone drawer IS the desktop rail, and the rail is the first child of
     `.shell` while the sticky bar lives inside `.main` further down the DOM.
     As an in-flow block the open rail therefore inserted ~854px of content
     ABOVE the bar; Chrome's scroll anchoring moved the scroll position by the
     same amount to keep the page still, and the menu landed entirely off the
     top of the screen (measured rect top: -1754px at 390x844). Taking it OUT
     of flow is the whole fix: nothing above the bar moves, so there is nothing
     for scroll anchoring to compensate for.
     `--topbar-open-top` is written by nav.js from the bar's measured bottom
     edge, because the bar is 52px at 840 and ~74px at 390 where it wraps to a
     second row (A19). The fallback covers the no-JS-yet frame only. */
  .js-nav.nav-open .sidebar {
    display: flex;
    position: fixed;
    left: 0; right: 0;
    top: var(--topbar-open-top, 52px);
    bottom: 0;
    width: auto; height: auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* The page behind an overlay must not scroll. `overflow: hidden` on <html>
       (or on <body>, which propagates to it) is the usual scroll lock and it
       CANNOT be used here: it takes the viewport out of the scrolling box, the
       sticky topbar stops sticking, and the bar jumps to its static position --
       measured at 390x844 scrolled to 900px, the bar's rect went from [0, 74]
       to [-900, -826], i.e. the header vanished the moment the menu opened.
       The drawer covers everything below the bar, so every scroll gesture
       lands on it anyway; `contain` is what stops that scroll CHAINING to the
       report underneath once the drawer hits its end. */
    overscroll-behavior: contain;
    z-index: 20;                      /* over .topbar's z-index: 5 content */
    box-shadow: 0 10px 24px rgba(0, 0, 0, .35);
  }
  /* `.nav` is the band that actually scrolls (base rule: flex:1, min-height:0,
     overflow-y:auto), which is what keeps the switcher foot pinned to the
     bottom of the drawer rather than trailing off the end of the links. It is
     the INNER link of the scroll chain, so it states the containment too --
     measured at 360x640, where the 15 links overflow by 235px. */
  .js-nav.nav-open .nav { overscroll-behavior: contain; }
  /* The bar already carries the logo at this width (canonical §2.6), so the
     drawer's own plate is a second copy of it costing 60px of the menu. */
  .sidebar .logo-plate { display: none; }
  .no-js .sidebar { display: flex; }
  .hamburger { display: block; }
  .topbar { padding: 0 12px; }
  .content { padding: 16px 12px 40px; }
  .main { min-height: 100vh; }
}
@media (max-width: 640px) {
  .grid-4, .grid-3, .grid-2 { grid-template-columns: minmax(0, 1fr); }
}
/* §3: under 380px the header panel narrows so it stays fully on screen -- the
   268px box would overhang -- and the grid drops to 2 columns. */
@media (max-width: 379px) {
  .switcher--head .switcher-panel { width: 208px; }
  .switcher--head .switcher-grid { grid-template-columns: repeat(2, 1fr); }
}
/* Chrome never prints (canonical §6): the switcher is not page content. */
@media print {
  .switcher, .no-print { display: none; }
}

/* ---------- Panel responsive: the card swap and the phone context strip ----- */
@media (max-width: 840px) {
  /* Once the rail is behind the hamburger the brand row carries the logo at
     every width (§2.6): logo . nine dots . hamburger, always visible. */
  .topbar-logo { display: block; }
  .ctx-strip { padding: 6px 12px; align-items: flex-start; }
  .page-header { padding: 10px 12px 0; }
  .filter-strip { padding: 8px 12px; }
}
@media (max-width: 640px) {
  /* D20 -- the phone treatment is chosen per table, and BOTH renderings come
     from one macro, so this swap is the only thing deciding which is seen. No
     JS runs on resize and the two cannot drift. */
  .t-wide { display: none; }
  .t-cards { display: block; }
  /* A16: the round-2 unit lists swap the same way, and HERE rather than in
     merch.css so a page that does not load merch.css still gets one of them. */
  .unit-table { display: none; }
  .unit-cards { display: block; }
  /* 44px touch targets, 16px control fonts (§2.5). */
  .scope select, .pill select, .pill input[type=search], .snapshot select { min-height: 44px; font-size: 16px; width: 100%; }
  .scope { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; width: 100%; }
  .scope-label { grid-column: 1 / -1; }
  .fresh-stamps { grid-column: 1 / -1; }
  .scope-divider { display: none; }
  .filter-strip .pill { width: 100%; }
  .filter-count { margin-left: 0; }
  .sum-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ===========================================================================
   PHONE PASS -- round 4 lane F7 (red-team items A16, A17, A18, B15, B17, D1,
   D2). Everything below is `max-width: 640px` ONLY: desktop is byte-identical
   after this block, which is the house regression gate
   (~/.claude/guides/dashboard-mobile-design.md §1). Source order matters --
   this is the LAST block in the file, so it wins the ties it needs to win
   against the 640 blocks above it.
   =========================================================================== */
/* The record cards are a PHONE rendering only: above 640 the table is the
   rendering and the cards are not in the layout at all. Same contract as
   `.t-cards`, and DECLARED BEFORE THE MEDIA BLOCK on purpose: a base rule
   written after it wins on source order at every width and hides the cards
   from the phone too -- which, with `.t-wide` also hidden, leaves the page
   with no rendering of the table at all. (It did, for one build.) */
.rec-cards { display: none; }

/* B12: the jump strip is a PHONE rendering only, and its hidden default is
   declared BEFORE the media block for exactly the reason `.rec-cards` is --
   a base rule written after the block wins at every width. */
.jump-links { display: none; }
.jump-links {
  flex-wrap: wrap; align-items: center; gap: 6px 8px;
  margin: 0 0 12px; padding: 8px 10px;
  background: #FAFBFA; border: 1px solid var(--mg-border);
  border-radius: var(--radius); font-size: 12px; color: var(--mg-text-2);
}
.jump-links .jump-label { font-weight: 600; color: var(--mg-text); }
/* A CHIP, not a word in a dotted list: the strip wraps at 390px and a mid-dot
   separator is then orphaned at the end of a line. The border separates. */
.jump-links a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 10px; background: #FFFFFF; border: 1px solid var(--mg-border);
  border-radius: var(--radius); text-decoration: none;
}
.jump-links a:hover { border-color: var(--mg-teal-tint-border); text-decoration: none; }
.jump-foot { display: none; font-size: 12px; margin: 12px 0 0; }
.jump-foot a { display: inline-flex; align-items: center; min-height: 44px; }
/* An id'd wrapper around a card another lane owns adds no box of its own, but
   it DOES break `.card + .card` for whatever follows it -- the same adjacent
   sibling margin `.ov-stack` had to restate in units.css. */
.jump-anchor + .card, .jump-anchor + .grid, .card + .jump-anchor,
.jump-anchor + .jump-anchor { margin-top: 16px; }

@media (max-width: 640px) {

  /* ---- A16: a tappable count ---------------------------------------------
     The drill link is 7x21px on a phone ("2", "4", "6") because it is a bare
     inline <a> around one digit. Growing the ANCHOR would drag every dense
     table's row height with it, so the hit box is an invisible centred
     ::after overlay instead: 44px in both directions, zero layout effect, and
     the dotted underline still hugs the glyphs.
     `:not(.stretched)` keeps the hygiene table's whole-row link -- which is
     also a `.drill` and owns its own `inset: 0` overlay -- out of this rule.
     The guide's `:not()` trap (§8.3) does not bite: there is one rule here,
     so there is no tie for the added specificity to flip. */
  a.drill:not(.stretched) { position: relative; }
  a.drill:not(.stretched)::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, 44px); height: 44px;
  }
  /* Same overlay for the other three link shapes that are a figure or a word
     rather than a control: the balance band's cell links, the breadcrumb, and
     the replica's "show all units" line. Each is alone on its line or alone in
     its cell, so a 44px box cannot steal a neighbour's tap.
     DELIBERATELY NOT INCLUDED: the bare `<a>`s inside the report replicas'
     unit tables (12px rows, 675px wide, 348 of them). Those are a dense-scan
     surface and the house guide keeps dense rows compact on purpose -- a 44px
     overlay there would cover three rows and take the wrong tap. */
  a.cell-link, .crumbs a, .rpt-unit-all a { position: relative; display: inline-block; }
  a.cell-link::after, .crumbs a::after, .rpt-unit-all a::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, 44px); height: 44px;
  }

  /* Controls are 44px at this width -- the house floor (guide §6). `.btn` is
     36px and `.btn.sm` 28px by design on a desktop, where a mouse does not
     need either to be bigger. */
  .btn { min-height: 44px; }
  .btn.sm { min-height: 44px; padding: 6px 12px; font-size: 13px; }
  .pager-page, .pager-step,
  .rpt-unit-pager .pager-page, .rpt-unit-pager .pager-step {
    min-width: 44px; min-height: 44px; display: inline-flex;
    align-items: center; justify-content: center;
  }
  /* "Clear filters" is a link doing a control's job. */
  .clear, .filter-strip .clear, .hygiene-filters .clear {
    display: inline-flex; align-items: center; min-height: 44px;
  }

  /* ---- B17: the 11px type floor ------------------------------------------
     The reviewer measured 114 elements at 10px and a run at 8.5-9.5px. Only
     CONTENT is raised: the nine-dot switcher and the nav rail are estate
     chrome held to the canonical spec (switcher-canonical.md) and keep their
     sizes, which is why `.switcher-*`, `.app-name` and `.nav-label` are
     absent from this list. */
  .mg-badge, .badge { font-size: 11px; }
  table.data thead th.wrap-h { font-size: 11px; }
  .uc-label, .sum-cell-label, .scope-label, .scope-chip-label,
  .stamp-degraded, .not-set, .listing-copy .label, .pending-cell,
  .lead-tile .label { font-size: 11px; }

  /* ---- pricing: the unit card's fourth line -------------------------------
     `.uc-line4` is a space-between flex row whose two `.uc-pair` children are
     `min-width: auto`, so "INTERNET PRICE $NN,NNN" + "OFF BOOK -$N,NNN" came
     to 411px inside a 306px card and dragged the whole PAGE to 453px. It
     wraps now; the pairs themselves still never break mid-figure. */
  .uc-line4 { flex-wrap: wrap; row-gap: 2px; }
  .uc-line4 > * { min-width: 0; }
  .uc-pair { min-width: 0; }

  /* ---- A18: a table that stays a table keeps its identity column ----------
     Treatment 2 from the house guide: the wrapper already scrolls, so the
     first column pins and the rest slides under it. Enumerated per row state
     because these are real <table>s with the zebra painted per-<td> --
     `background: inherit` does not transfer (guide §7), and §8.1 is why every
     state is restated rather than one bare rule added.

     A17 (red team 2): `tfoot` is enumerated with `tbody`. The 60-day supply
     table foots its division and group totals in SQL and prints them in a
     <tfoot> -- so with the pin scoped to tbody alone, the totals were the ONE
     set of rows that lost their label when the table scrolled: "36.5 / 65.1 /
     51.4 / 44.0" under an empty store-name column. */
  .table-wrap > table.data > thead > tr > th:first-child,
  .table-wrap > table.data > tbody > tr > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr > th[scope="row"]:first-child {
    position: sticky; left: 0; z-index: 2;
    box-shadow: 1px 0 0 var(--mg-border);
  }
  .table-wrap > table.data > thead > tr > th:first-child {
    background: var(--mg-table-head); z-index: 3;
  }
  /* A20 (red team 2): NO TRUNCATION. This cap used to be
     `max-width: 116px; text-overflow: ellipsis`, which printed "Toyota
     Cleveland H..." on the home balance band -- a bare truncation of an
     identity, which the house rule forbids everywhere else on the site. The
     replacement is the policy widget's treatment: nowrap plus a font-size
     step down to the 11px floor, and the column takes the width the NAME
     needs. The table is inside a scroller, so a wider identity column costs
     sideways travel in the numbers, not a clipped name -- and the pin means
     the name is on screen for all of it. The longest roster name, "Martin
     Dodge Jeep Chrysler Ram" at 11px, measures ~140px of a 366px viewport. */
  .table-wrap > table.data > tbody > tr > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr > th[scope="row"]:first-child {
    background: #FFFFFF;
    max-width: none; overflow: visible; text-overflow: clip;
    white-space: nowrap; font-size: 12px; letter-spacing: -.1px;
  }
  .table-wrap > table.data > tbody > tr:nth-child(even) > th[scope="row"]:first-child {
    background: var(--mg-zebra);
  }
  .table-wrap > table.data > tbody > tr.r-division_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tbody > tr.k-division_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr.r-division_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr.k-division_total > th[scope="row"]:first-child {
    background: #FAFBFA;
  }
  .table-wrap > table.data > tbody > tr.r-group_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tbody > tr.k-group_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr.r-group_total > th[scope="row"]:first-child,
  .table-wrap > table.data > tfoot > tr.k-group_total > th[scope="row"]:first-child {
    background: var(--mg-table-head);
  }
  .table-wrap > table.data > tbody > tr.is-acked > th[scope="row"]:first-child {
    background: #FAFBFA;
  }

  /* A `table.data` that is NOT inside a scroll wrapper poisons the whole page
     (guide §3.2) -- the CarCutter "what it means" table is 397px in a 364px
     card and took /merchandising to 426px. It scrolls itself here rather than
     the page. The template that emits it belongs to another lane; a
     `.table-wrap` around it is the right permanent fix. */
  .card > table.data,
  .content > table.data { display: block; overflow-x: auto; max-width: 100%; }

  /* ---- A17: the record card -----------------------------------------------
     A shared list-of-records card, used by the hygiene queue and the users
     table. Both emit it from the SAME fragment as the table (one template,
     two renderings) so the two cannot drift -- the house rule. */
  .rec-cards { display: block; }
  .rec-card {
    border: 1px solid var(--mg-border); border-radius: var(--radius);
    padding: 10px 12px; margin-bottom: 8px; background: #FFFFFF;
  }
  .rec-card:nth-of-type(even) { background: var(--mg-zebra); }
  .rec-card.is-quiet { background: #FAFBFA; }
  .rec-card-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 8px; flex-wrap: wrap; margin-bottom: 4px;
  }
  .rec-card-id { font-weight: 600; font-size: 13px; min-width: 0; overflow-wrap: anywhere; }
  .rec-card-sub { font-size: 12px; color: var(--mg-text-2); margin: 0 0 4px; }
  .rec-card-grid {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px 10px; margin: 4px 0;
  }
  .rec-cell { display: flex; flex-direction: column; min-width: 0; }
  .rec-cell-label {
    font-size: 11px; letter-spacing: .6px; text-transform: uppercase;
    color: var(--mg-text-muted); font-weight: 600;
  }
  .rec-cell-value { font-size: 13px; overflow-wrap: anywhere; }

  /* The summary card's pills cell (see `_summary_table.html`): full card row,
     and the badge inside it may wrap -- a card has no sideways scroll to put
     an unbreakable 192px pill into. */
  .sum-cell-wide { grid-column: 1 / -1; }
  .sum-card .cell-pills { flex-wrap: wrap; }
  .sum-card .cell-pills .mg-badge { white-space: normal; }
  /* Actions sit on their own rule at the foot of the card, full width, 44px:
     the defect the reviewer hit was an Acknowledge button at x=823 on a 390px
     screen, and a card whose action is still off to one side has not fixed it. */
  .rec-card-actions {
    display: flex; flex-wrap: wrap; gap: 8px; align-items: stretch;
    border-top: 1px solid var(--mg-border); padding-top: 8px; margin-top: 6px;
  }
  .rec-card-actions > * { flex: 1 1 100%; }
  .rec-card-actions .btn { min-height: 44px; width: 100%; font-size: 14px; }
  .rec-card-actions input[type=text],
  .rec-card-actions select { min-height: 44px; font-size: 16px; width: 100%; }
  .rec-card-actions form { display: flex; flex-direction: column; gap: 6px; }

  /* ====== round 5, lane G7 (red team 2: A19, B12) ======================== */

  /* ---- A19: the page title is not an abbreviation -------------------------
     `.topbar h1` is `nowrap + ellipsis` so a long title cannot push the
     nine-dot switcher off the bar. On a 390px phone the bar also carries the
     hamburger (44px) and the logo, and the report titles are the long ones:
     "Martin Management Group -- Used Car Board" rendered as "Marti...".
     Truncating a HEADING is the same defect as truncating an identity, so the
     title drops to a SECOND ROW instead: `flex-wrap` on the bar, `order` puts
     the switcher up beside the hamburger, and the title takes the full width
     underneath and wraps to as many lines as it needs. The bar grows from
     52px to about 76px; nothing is sticky against `--topbar-h`, so nothing
     else moves. */
  .topbar { flex-wrap: wrap; align-items: center; padding-top: 6px; padding-bottom: 6px; row-gap: 2px; }
  .topbar h1 {
    order: 1; flex: 1 1 100%; min-width: 100%;
    white-space: normal; overflow: visible; text-overflow: clip;
    font-size: 15px; line-height: 1.25; padding-right: 0;
  }
  .topbar .switcher--head { order: 0; margin-left: auto; }

  /* Same defect one row down, and the same answer. `.page-head` is a
     space-between flex row: title on the left, Print / Download PDF / Export
     CSV on the right. At 390 the three buttons take 300px of the 366 and the
     heading is left with a 66px column -- "Over / Age / Used / Vehicle /
     Inventory", one word per line, which is how the report pages actually
     rendered. The heading takes its own full-width line and the buttons wrap
     underneath it. `.page-head` is the admin pages' version of the same row
     and gets the same treatment. */
  .page-header .page-title { flex: 1 1 100%; min-width: 100%; }
  .page-head { flex-direction: column; align-items: stretch; gap: 8px; }

  /* ---- ROUND 6 / B5: no Print, Download PDF or Export CSV on a phone ------
     All three are desktop-only acts. Printing and "save a PDF I will mail to
     the GM" happen at a desk; a downloaded CSV on a phone lands somewhere the
     reader cannot open it. They were also the 300px of buttons that pushed the
     page heading onto its own line above -- that rule stays, because the row
     still carries the snapshot picker on the report pages.
     ONE rule covers 22 pages: both header macros emit `.page-actions`
     (`_page_header.html`, `reports/_report_header.html`). The header does not
     collapse to nothing -- it keeps the title and the snapshot picker.
     NOT hidden: the inline "Export days supply (CSV)" prose link on Over-Age
     New, which is a sentence in the page body and not a `.page-actions`
     button. Print CSS hides the same buttons under `@media print` already. */
  .page-actions { display: none; }

  /* ---- B12: the jump strip on a long page ---------------------------------
     Data Hygiene is 13,500px on a phone, Pricing 14,000, Over-Age New 12,000,
     and a reader who wants the third card scrolls past two. The strip is
     PHONE ONLY (`display: none` above, base rule before this block): at 1440
     every one of these pages is two or three screens and a second navigation
     control would be chrome for nothing. It is plain anchors -- no JS, works
     with the browser's own back button, and `scroll-margin-top` keeps the
     sticky topbar off the heading it lands on. */
  .jump-links { display: flex; }
  .jump-anchor > .card, .card[id], .jump-anchor { scroll-margin-top: 96px; }
  .jump-foot { display: block; }

}

/* ---------------------------------------------------------------------------
   Pagination (templates/_pagination.html) -- SHARED by every paged list, so it
   lives here and not in a page stylesheet. Until 2026-09-30 it sat in
   pages/admin.css, and every non-admin page rendered the pager as a bare
   bulleted list. One row: page size, "Showing a-b of N", then the page
   numbers pushed right; it wraps (numbers onto their own line) when narrow.
   Scorecard values (HANDOFF §5/§6): controls 30px, 12px text, #C9CFCC
   borders, radius 4, current page teal.
   --------------------------------------------------------------------------- */
.pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
         margin: 14px 0 6px; font-size: 12px; }
.pager-size { display: flex; align-items: center; margin: 0; }
.pager-size .pill { display: inline-flex; align-items: center; gap: 8px;
                    margin: 0; padding: 0; background: none; border: 0; }
.pager-size .pill > span { font-size: 10px; font-weight: 700; letter-spacing: 1px; white-space: nowrap;
                           text-transform: uppercase; color: #6B7370; }
.pager-size select { height: 30px; padding: 0 26px 0 8px; font: inherit;
                     font-size: 12px; color: #222927; background-color: #FFFFFF;
                     border: 1px solid #C9CFCC; border-radius: 4px; }
.pager-showing { margin: 0; font-size: 12px; color: #5A6461;
                 font-variant-numeric: tabular-nums; }
.pager-pages { display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
               list-style: none; margin: 0 0 0 auto; padding: 0; }
.pager-pages li { display: inline-flex; margin: 0; padding: 0; }
.pager-pages li::marker { content: none; }
.pager-page, .pager-step {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 30px; padding: 0 10px; box-sizing: border-box;
  font-size: 12px; font-variant-numeric: tabular-nums; line-height: 1;
  color: #0E7C6B; text-decoration: none; background: #FFFFFF;
  border: 1px solid #C9CFCC; border-radius: 4px; }
a.pager-page:hover, a.pager-step:hover { background: #F6FAF9; color: #128A76;
                                         border-color: #17A38C; }
.pager-page.is-current { background: #17A38C; border-color: #17A38C;
                         color: #FFFFFF; font-weight: 700; }
.pager-step.is-off { color: #98A09D; background: #F7F8F7; border-color: #E6E9E7; }
.pager-gap { padding: 0 2px; color: #98A09D; font-size: 12px; }
@media (max-width: 640px) {
  .pager { gap: 10px 12px; }
  .pager-size select { height: 44px; font-size: 16px; }
  .pager-pages { margin-left: 0; width: 100%; }
}
