/*
 * BSBC Gross Profit Operations Scorecard — dashboard-only styles.
 *
 * Loaded SECOND, after /styles.css, by the five dashboard pages
 * (dashboard/index.html, store/, HR/, locationreport/, admin/). The site file
 * carries everything the dashboard shares with the rest of the site — tokens,
 * reset, page frame, logo, lock screen, top bar and page tabs, the generic
 * table look, the footer, the loading/error states. What is left here is
 * either a dashboard-only component or a dashboard-specific override of one
 * of those.
 *
 * ORDER MATTERS: link /styles.css BEFORE this file on every page. Several
 * rules below override a site rule of the same specificity and depend on
 * coming later; swap the two <link>s and they silently stop applying.
 *
 * Organised top-to-bottom in the order things appear on the page, so finding
 * a rule means scrolling to roughly where you see the thing. The numbers are
 * the original ones and the gaps are real: sections 1 Tokens, 2 Base, 3 Logo,
 * 4 Lock screen, 11 Notes and footer and 12 States now live in /styles.css,
 * as do the shared parts of 5 Header, 9 Tables and 13 Responsive.
 *
 * Note `.card` below is the chart panel. The homepage and 404 page have an
 * unrelated `.card` in /home.css; the two never load together, which is the
 * point of the split. Keep it that way rather than renaming either.
 *
 *   5. Header          the dashboard-data bits only — see /styles.css for the rest
 *  5b. Scope selector
 *  5c. Location chips
 *   6. KPI cards
 *   7. Goal rails
 *  7b. Week range picker
 *   8. Charts
 *  8a. Collapsible sections
 *   9. Tables          dashboard-only columns, shading and widgets
 *  10. Rankings
 *  13. Responsive      the dashboard-only rules at ≤640px
 *  14. Print export (HR Overview)
 *  15. Admin panel
 */

/* ── 5. Header ─────────────────────────────────────────────────────────── */
/* Only the parts tied to dashboard data; the chrome around them (top bar,
   brand block, title, page tabs, `section`, `h2.sec`) is in /styles.css. */

/* The selected store's name, under the section heading on the detail page. */
.storehead {
	display: flex;
	align-items: baseline;
	gap: 10px;
	flex-wrap: wrap;
	margin-top: 14px;
}
.storehead b {
	font-size: 17px;
	font-weight: 800;
	letter-spacing: -0.01em;
}
.storehead span {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
}

/* In the rankings, a location name links to its detail page. Every row's
   name sits inside .namewithgm (§10) beside the GM avatar, an inline-flex
   box, and a text decoration never propagates into an atomic inline — so the
   name's own span (.gm-storename) has to be underlined in its own right, or
   no location would look clickable at all. */
#ranktable td.name,
#ranktable td.name .gm-storename {
	cursor: pointer;
	text-decoration: underline;
	text-decoration-color: var(--rule);
	text-underline-offset: 3px;
}
#ranktable td.name:hover,
#ranktable td.name:hover .gm-storename {
	color: var(--brand);
	text-decoration-color: var(--brand);
}
/* …except the Average row, which is not a location and goes nowhere. */
#ranktable tr.totalrow td.name,
#ranktable tr.totalrow td.name .gm-storename {
	cursor: default;
	text-decoration: none;
	color: inherit;
}

/* ── 5b. Scope selector ────────────────────────────────────────────────── */
/* Sits above the KPI section and controls it plus the trend charts. */
.scopebar {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-wrap: wrap;
	margin-top: 30px;
	padding: 12px 16px;
	background: var(--panel);
	border: 1px solid var(--rule);
}
.scopebar label {
	flex: 0 0 auto;
}
.scopebar select {
	flex: 0 1 280px;
	padding: 8px 10px;
	font-family: var(--mono);
	font-size: 13px;
	font-weight: 600;
	color: var(--ink);
	background: var(--panel);
	border: 1px solid var(--rule);
	border-radius: 0;
	cursor: pointer;
}
.scopebar select:hover {
	border-color: var(--slate);
}
.scopebar select:focus-visible {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
}
.scopenote {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
	flex: 1 1 auto;
}
/* When a single store is selected, tint the bar so it is obvious at a glance
   that the numbers below are not company-wide. */
.scopebar.scoped {
	border-left: 4px solid var(--brand);
}
.scopebar.scoped .scopenote {
	color: var(--brand);
}

/* ── 5c. Location chips (Store Detail) ─────────────────────────────────── */
.pickbar {
	margin-top: 30px;
	padding: 12px 16px 14px;
	background: var(--panel);
	border: 1px solid var(--rule);
	border-left: 4px solid var(--brand);
}
.pickhead {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 10px;
}
/* Plain layout box — gap for whatever's inside, no look of its own. The
   Clear/Select all groups add `.segbtns` (see the "Week range picker"
   section below) to look like the range presets instead; the HR Export PDF
   row below (`.alignactions.pickactions`) stays a bare `.linkbtn` and must
   NOT get `.segbtns` — it is one standalone action, not a selection bar. */
.pickactions {
	display: flex;
	gap: 14px;
}
/* A standalone text-link action — "Export PDF", "Admin sign out" — that is
   not part of a selection bar and keeps its own underlined-text look. */
.linkbtn {
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
	text-decoration: underline;
	text-underline-offset: 3px;
}
.linkbtn:hover {
	color: var(--ink);
}

/* The Export PDF row on the HR Overview, between the KPI cards and the
   collapsible errors table. Not nested in a .pickhead, so it needs its own
   top margin rather than inheriting one. */
.alignactions {
	margin-top: 14px;
}

.chips {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
.chip {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	padding: 6px 11px;
	background: var(--panel);
	border: 1px solid var(--rule);
	cursor: pointer;
	font-family: var(--mono);
	font-size: 11.5px;
	font-weight: 600;
	color: var(--muted);
	white-space: nowrap;
}
.chip:hover {
	border-color: var(--slate);
	color: var(--ink);
}
.chip:focus-visible {
	outline: 2px solid var(--slate);
	outline-offset: 1px;
}
/* Selected chips carry their line colour, so the selector doubles as the
   chart legend — the same store is the same colour in both places. */
.chip[aria-pressed="true"] {
	color: var(--ink);
	border-color: currentColor;
	background: var(--rule-soft);
}
.chip .swatch {
	width: 10px;
	height: 10px;
	flex: 0 0 auto;
	border-radius: 50%;
	/* Unselected chips show a hollow ring: the colour is reserved, not in use. */
	background: transparent;
	box-shadow: inset 0 0 0 1.5px var(--faint);
}
.chip[aria-pressed="true"] .swatch {
	box-shadow: none;
}

/* The selection bar as ONE component: Store Detail's Locations chip panel,
   its separate week range panel, and the Overview's range-only panel all
   wrap in `.pickbar.filterbar` (2026-09-18 — both now drive several
   sections, not just the trend charts, so they moved out of #s-trend/the
   page top and stopped looking like different boxes on the two pages;
   2026-09-24 — Store Detail's chips and range row split into TWO separate
   `.pickbar.filterbar` panels, one above #s-current and one below it,
   because the Metrics overview between them stopped following the range
   and only the chips still drive it — see store/index.html's HTML comments
   and store.js's renderLatestWeek()). `.filterbar` on `.pickbar` is a
   marker rather than a change to the shared rule — the base `.pickbar`
   panel look (padding/background/red-accented border) is exactly what
   every one of these panels wants, so nothing to override there.
   `.rangebar-inline` on `.rangebar` is the part that DOES need to differ:
   inside a `.pickbar.filterbar` panel the range row must not look like a
   second bordered/backgrounded box nested in the first, so it sheds its
   own border, background and outer margin and gets a plain rule-divider
   instead — see `.rangebar.rangebar-inline` (and its `:first-child` case)
   in the "Week range picker" section below, next to the base `.rangebar`
   rule it overrides. Since 2026-09-24 every page's range row is its own
   panel's only child, so the plain (non-`:first-child`) case of
   `.rangebar.rangebar-inline` — a range row stacked under chips in the same
   panel — is currently unused; the rule is kept rather than deleted in
   case a future page goes back to combining the two. HR Overview and
   Location Report keep using bare `.pickbar` (chips, no range row) and are
   untouched by either modifier. */

/* The summary line under the Location Report's chips. */
.pickbar .scopenote {
	display: block;
	margin-top: 10px;
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
}

/* "42d left" / "18d overdue" under a certification's expiry date. Inherits the
   cell's traffic-light colour, so the date and the countdown always agree. */
.certnote {
	font-family: var(--mono);
	font-size: 10px;
	opacity: 0.75;
	margin-top: 1px;
}

/* The same swatch, inline in the comparison table's first column. */
.swatch {
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	margin-right: 8px;
	vertical-align: baseline;
}

/* ── 6. KPI cards ──────────────────────────────────────────────────────── */
/* The 1px gap over a --rule background is a hairline-grid trick: the parent's
   colour shows through the gaps, so there are no doubled borders between
   cells and no border-collapse needed. Used again for .movers. */
.kpis {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
	gap: 1px;
	background: var(--rule);
	border: 1px solid var(--rule);
	margin-top: 18px;
}/*
#rostersummary {
	display: grid;
	justify-content: center;
	margin-inline: auto;
	grid-template-columns: repeat(auto-fit, minmax(168px, 215px));
	gap: 1px;
	background: var(--rule);
	border: 1px solid var(--rule);
	margin-top: 18px;
}*/
.kpi {
	background: var(--panel);
	padding: 15px 16px 14px;
}
.kpi .k {
	font-family: var(--mono);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--faint);
}
.kpi .v {
	font-family: var(--mono);
	font-size: 25px;
	font-weight: 600;
	letter-spacing: -0.02em;
	margin: 5px 0 2px;
	font-variant-numeric: tabular-nums;
}
.kpi .d {
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--muted);
}
.kpi .d b {
	font-weight: 600;
}
.up {
	color: var(--good);
}
.down {
	color: var(--bad);
}
.flat {
	color: var(--muted);
}

/* ── 7. Goal rails ─────────────────────────────────────────────────────── */
.rails {
	background: var(--panel);
	border: 1px solid var(--rule);
	padding: 6px 20px 14px;
	margin-top: 18px;
}
.rail {
	display: grid;
	grid-template-columns: 118px 1fr 96px;
	gap: 16px;
	align-items: center;
	padding: 15px 0;
	border-bottom: 1px solid var(--rule-soft);
}
.rail:last-child {
	border-bottom: 0;
}
.rail .name {
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--slate);
}
.rail .val {
	font-family: var(--mono);
	font-size: 16px;
	font-weight: 600;
	text-align: right;
	font-variant-numeric: tabular-nums;
}
.railtrack {
	position: relative;
	height: 30px;
}
.railtrack svg {
	display: block;
	width: 100%;
	height: 30px;
	overflow: visible;
}
.raillabel {
	font-family: var(--mono);
	font-size: 10px;
	color: var(--faint);
	margin-top: -2px;
}
/* How far off the goal, under the value. Sits opposite .raillabel so the goal
   and the distance from it read as a pair across the rail. */
.railgap {
	font-family: var(--mono);
	font-size: 10px;
	font-weight: 500;
	margin-top: 1px;
	/* Inherits the value's colour, which already encodes good vs bad. */
	opacity: 0.85;
}

/* ── 7b. Week range picker ─────────────────────────────────────────────── */
/* Two dropdowns for an explicit span, plus presets for the common spans. */
.rangebar {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	margin-top: 18px;
	padding: 10px 14px;
	background: var(--panel);
	border: 1px solid var(--rule);
}
/* Both pages now put their range row inside a `.pickbar.filterbar` panel
   (§5c above), so it must stop looking like its own bordered/backgrounded
   box and read as one row of that panel instead. `.rangebar.rangebar-inline`
   (both classes, not `.rangebar-inline` alone) is deliberate: a single-class
   override sits earlier in this file than the base `.rangebar` rule above,
   and two same-specificity class selectors are decided by SOURCE ORDER, not
   which classes the element carries — the base rule would win and every
   property here would be silently ignored (this happened once already; see
   the "no effect" case in HANDOFF.md's traps). The compound selector's
   higher specificity (0,2,0 beats 0,1,0) makes the override win regardless
   of where either rule sits in the file.
   Since 2026-09-24 the range row is EVERY page's own panel's only content —
   Store Detail split its combined filter bar into a Locations panel and a
   separate range panel below #s-current (see store/index.html), so it no
   longer sits stacked under chips in the same panel the way it did through
   2026-09-18–2026-09-23. The `:first-child` case right after this rule is
   therefore what actually applies everywhere today; the plain (non-
   `:first-child`) rule below is kept for a page that might combine chips
   and range again, but nothing currently matches it without also matching
   `:first-child`. */
.rangebar.rangebar-inline {
	margin-top: 12px;
	padding: 12px 0 0;
	background: none;
	border: 0;
	border-top: 1px solid var(--rule);
}
/* The range row is `.pickbar.filterbar`'s only child on both pages now (see
   the comment above), so the divider and the margin/padding the plain rule
   above adds would just be a stray rule and a gap above nothing. `:first-
   child` on the compound selector keeps the (0,3,0) specificity that
   already beats the base `.rangebar` rule above — see the comment on
   `.rangebar.rangebar-inline` for why that has to win regardless of source
   order. */
.rangebar.rangebar-inline:first-child {
	margin-top: 0;
	padding-top: 0;
	border-top: 0;
}
.rangebar select {
	padding: 6px 8px;
	font-family: var(--mono);
	font-size: 12px;
	font-weight: 600;
	color: var(--ink);
	background: var(--panel);
	border: 1px solid var(--rule);
	border-radius: 0;
	cursor: pointer;
}
.rangebar select:hover {
	border-color: var(--slate);
}
.rangebar select:focus-visible {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
}
.rangesep {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
}
/* Segmented button group — one visual language shared by the week-range
   presets and the Clear/Select all (Show all/Select all) pick-actions groups
   (2026-09-18), so a selection bar never mixes this with underlined
   `.linkbtn` text for the same kind of control. `.segbtns` carries the
   group's look (the 1px-gap-over-a-var(--rule)-background trick draws the
   hairline dividers between buttons, same as `.kpis`); `.rangepresets` is
   only ever used alongside it (grouped selectors below, so there's no
   override-order pitfall to mind) and keeps just the two things that stay
   preset-only: `margin-left: auto` (pinned to the panel's right edge) and
   the flex-wrap behaviour that lets it drop to a second row at ≤640px (see
   the button rule in that media query, further down, which is also
   preset-only — a 2-button pick-actions group never needs it). Pick-actions
   buttons never carry `aria-pressed` — they're momentary actions, not
   toggles — so the "pressed" dark-fill rule below stays scoped to
   `.rangepresets` alone and never shows on a `.segbtns` pick-actions group. */
.rangepresets,
.segbtns {
	display: flex;
	flex-wrap: wrap;
	gap: 1px;
	background: var(--rule);
	border: 1px solid var(--rule);
}
.rangepresets {
	margin-left: auto;
}
/* `.pickactions` sets its own `gap: 14px` for the plain-layout case (the HR
   Export PDF row); a `.pickactions.segbtns` group needs the tighter 1px
   segmented-button gap instead. The compound selector's higher specificity
   makes it win regardless of source order — see the
   `.rangebar.rangebar-inline` comment above for why that matters here too. */
.pickactions.segbtns {
	gap: 1px;
}
.rangepresets button,
.segbtns button {
	background: var(--panel);
	border: 0;
	padding: 6px 12px;
	cursor: pointer;
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
	letter-spacing: 0.02em;
	white-space: nowrap;
}
.rangepresets button:hover,
.segbtns button:hover {
	background: #f6f6f4;
}
.rangepresets button[aria-pressed="true"] {
	background: var(--ink);
	color: #fff;
}
.rangepresets button:disabled,
.segbtns button:disabled {
	opacity: 0.35;
	cursor: default;
}
.rangepresets button:disabled:hover,
.segbtns button:disabled:hover {
	background: var(--panel);
}

/* ── 8. Charts ─────────────────────────────────────────────────────────── */
.chartgrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
	gap: 18px;
	margin-top: 18px;
}
.card {
	background: var(--panel);
	border: 1px solid var(--rule);
	padding: 16px 18px 12px;
}
.card h3 {
	font-size: 13px;
	font-weight: 700;
	margin: 0 0 2px;
	letter-spacing: -0.005em;
}
.card .csub {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
	margin: 0 0 10px;
}
.card svg {
	display: block;
	width: 100%;
	height: auto;
}

/* ── 8a. Collapsible sections (Trend) ─────────────────────────────────────
 * A one-point "trend" is a dot, so #s-trend collapses itself on both
 * dashboard pages when the selected range holds at most one week (see
 * js/collapse.js, overview.js, store.js). `.collapsible` is a reusable class,
 * not tied to #s-trend's id, so any other section could use the same trick
 * later.
 *
 * The "animate height" part uses the CSS grid-rows trick rather than
 * `max-height` (which needs a guessed-too-large value that makes the
 * animation timing lie) or `height: auto` (which cannot be transitioned at
 * all): a 1fr/0fr grid row animates to its EXACT content height either way.
 * `.collapsible-inner`'s `overflow: hidden` is what actually clips the
 * content while the row is shrinking — checked against this page's own
 * content: the charts are plain non-interactive SVG (no tooltips, nothing
 * focusable inside #s-trend), so clipping it while expanded costs nothing
 * here. If a future section using this class ever holds a focusable control
 * near its edge, restrict `overflow: hidden` to `.is-collapsed` only.
 *
 * `visibility` is delayed on the way OUT (see the transition's second value
 * below) so the section stays hittable/readable until the collapse finishes,
 * but flips to hidden immediately on the way IN — both together take the
 * section out of the tab order and accessibility tree the moment it is
 * actually invisible, not before and not after. `setCollapsed()` (collapse.js)
 * also sets `aria-hidden`/`inert` for browsers that don't act on `visibility`
 * for that purpose. `margin-top: 0` on `.is-collapsed` is what keeps the
 * section's own 34px `section` margin (in /styles.css) from leaving a visible
 * gap once its height is zero.
 *
 * The global `@media (prefers-reduced-motion: reduce) { * { transition: none
 * !important } }` block — now in /styles.css — turns both transitions off, so
 * a reduced-motion viewer gets an instant show/hide with no intermediate frame
 * — confirmed by reading the rule: with no transition, the collapsed
 * properties (including `visibility: hidden`) apply immediately. It is
 * `!important` on `*`, so it still wins from the earlier-loading site file.
 */
section.collapsible {
	display: grid;
	grid-template-rows: 1fr;
	transition:
		grid-template-rows 220ms ease,
		opacity 180ms ease,
		margin-top 220ms ease,
		visibility 0s linear 0s;
}
section.collapsible .collapsible-inner {
	min-height: 0;
	overflow: hidden;
}
section.collapsible.is-collapsed {
	grid-template-rows: 0fr;
	opacity: 0;
	margin-top: 0;
	visibility: hidden;
	transition:
		grid-template-rows 220ms ease,
		opacity 180ms ease,
		margin-top 220ms ease,
		visibility 0s linear 220ms;
}

/* The muted one-liner that stands in for #s-trend while it is collapsed.
   Same 34px gap a `section` gets, so the page reads the same whether the
   trend charts or this note occupies the slot. */
.trendnote {
	margin-top: 34px;
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--faint);
}

.legend {
	display: flex;
	gap: 16px;
	flex-wrap: wrap;
	margin-top: 8px;
	font-family: var(--mono);
	font-size: 10.5px;
	color: var(--muted);
}
.legend i {
	display: inline-block;
	width: 14px;
	height: 2px;
	vertical-align: middle;
	margin-right: 6px;
}

/* ── 9. Tables ─────────────────────────────────────────────────────────── */
/* The generic table look — `.tablewrap`, `table`, `th`/`td`, the name column,
   row and totals styling, `.na` and click-to-sort — is in /styles.css. What
   is left here is the dashboard's own columns, its traffic-light shading and
   the two table-adjacent widgets. */
td.dm,
th.dm {
	text-align: left;
	color: var(--muted);
	font-size: 11.5px;
}

/* Traffic-light cells. Applied by the render code, never hardcoded in markup. */
.cell-good {
	color: var(--good);
	background: var(--good-bg);
}
.cell-bad {
	color: var(--bad);
	background: var(--bad-bg);
}
.cell-warn {
	color: var(--warn);
	background: var(--warn-bg);
}

/* Collapsible table (details.tabletoggle): HR Overview's errors table
   (#aligndetails) and, since 2026-10-08, the admin page's settings History
   (#historydetails). It was built for the errors table — the one dropdown
   on HR Overview that hides a compliance list people can easily forget
   exists — so unlike the rest of the site's flat, sharp-edged chrome
   it is styled as a full-width bar — not small-caps label text — with an
   explicit "Show table / Hide table" cue, so the closed state reads as an
   invitation rather than a footnote. */
details.tabletoggle > summary {
	display: flex;
	align-items: center;
	gap: 9px;
	margin-top: 14px;
	padding: 12px 14px;
	background: var(--panel);
	border: 1px solid var(--rule);
	border-left: 4px solid var(--brand);
	border-radius: 2px;
	cursor: pointer;
	font-family: var(--sans);
	font-size: 14px;
	font-weight: 700;
	color: var(--ink);
	list-style: none;
	transition: background-color 0.15s ease, color 0.15s ease;
}
details.tabletoggle > summary::-webkit-details-marker {
	display: none;
}
details.tabletoggle > summary:hover {
	background: var(--rule-soft);
}
details.tabletoggle > summary:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: 2px;
}
/* CSS-drawn chevron (border corner rotated 45°) rather than a glyph, so its
   weight and colour track the summary's text colour exactly in both states. */
.aligntoggle-chev {
	flex: 0 0 auto;
	width: 7px;
	height: 7px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(-45deg);
	transition: transform 0.15s ease;
}
details.tabletoggle[open] > summary .aligntoggle-chev {
	transform: rotate(45deg);
}
.aligntoggle-label {
	/* Normal case, not the page's usual uppercase mono label treatment —
	   this bar already stands out by shape and colour, so the text itself
	   should read like a sentence, not a section eyebrow. */
	font: inherit;
	color: inherit;
}
.aligntoggle-state {
	margin-left: auto;
	flex: 0 0 auto;
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--muted);
}
details.tabletoggle[open] > summary .aligntoggle-state {
	color: inherit;
}
.aligntoggle-state .when-open {
	display: none;
}
details.tabletoggle[open] > summary .aligntoggle-state .when-closed {
	display: none;
}
details.tabletoggle[open] > summary .aligntoggle-state .when-open {
	display: inline;
}
/* Open state is deliberately a hard colour flip, not a shade of the closed
   bar — "this is expanded" has to survive a glance, not a close look. */
details.tabletoggle[open] > summary {
	background: var(--ink);
	border-color: var(--ink);
	border-left-color: var(--brand);
	color: var(--panel);
	border-radius: 2px 2px 0 0;
	margin-bottom: 0;
}
#aligncount {
	/* hr.js writes "(N)" here on every render, including "(0)" when nobody
	   is flagged, so the warning pill below is the nonzero default and the
	   [data-count="0"] rule overrides it to a neutral chip. var(--bad-bg)
	   stays a light chip against both the closed panel background and the
	   open state's dark one. */
	margin-left: 0;
	padding: 2px 7px;
	border-radius: 2px;
	background: var(--bad-bg);
	color: var(--bad);
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: normal;
	text-transform: none;
}
#historycount {
	/* Plain text ("· 12 versions"), not #aligncount's pill: a version count
	   is information, never a warning. Lighter than the bold label so the
	   label still leads. */
	font-weight: 400;
}
#aligncount[data-count="0"] {
	/* Nobody flagged is good news, not a warning — read as neutral instead
	   of red. Stays a light chip on the open state's dark bar too. */
	background: var(--rule-soft);
	color: var(--muted);
}

/* The little stacked bar next to gross profit: COGS + labor from the left,
   gross profit from the right, so the three always fill exactly 100%. */
.gpbar {
	display: inline-block;
	width: 200px;
	height: 9px;
	background: var(--rule-soft);
	vertical-align: middle;
	margin-left: 8px;
	position: relative;
}
.gpbar i {
	position: absolute;
	top: 0;
	bottom: 0;
	display: block;
}
.gpbar .c {
	background: var(--series-cogs);
	left: 0;
}
.gpbar .l {
	background: var(--series-labor);
}
.gpbar .g {
	background: var(--good);
	right: 0;
}

/* ── 10. Rankings ───────────────────────────────────────────────────────── */
.rk {
	display: inline-block;
	min-width: 16px;
	margin-left: 7px;
	padding: 1px 4px;
	text-align: center;
	font-family: var(--mono);
	font-size: 9.5px;
	font-weight: 600;
	background: var(--rule-soft);
	color: var(--faint);
}
.rk.t1 {
	background: var(--good-bg);
	color: var(--good);
}
.rk.t3 {
	background: var(--bad-bg);
	color: var(--bad);
}
td.rankcell {
	text-align: left;
	width: 64px;
}
.rankno {
	font-family: var(--mono);
	font-size: 16px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}
.move {
	font-family: var(--mono);
	font-size: 10px;
	margin-left: 6px;
	letter-spacing: 0.02em;
}
.move.u {
	color: var(--good);
}
.move.d {
	color: var(--bad);
}
.move.f {
	color: var(--faint);
}
/* The signed value-vs-goal sub-label on a relative lever's cell (labor,
   productivity — see rankings.js's relativeLeverCell / stores.js's
   relativeDmCell). Sits beside the raw value, ahead of the rank chip. */
.gap {
	font-family: var(--mono);
	font-size: 10px;
	margin-left: 6px;
	color: var(--muted);
}
.gap.good {
	color: var(--good);
}
.gap.bad {
	color: var(--bad);
}
.gap.none {
	color: var(--faint);
	font-style: italic;
}
/* "vs goal" under a relative lever's header (rankings and DM tables) —
   deliberately quieter than the column label itself. */
th .vsgoal {
	font-weight: 400;
	color: var(--faint);
	font-size: 9.5px;
	letter-spacing: normal;
	text-transform: none;
}
tr.podium td.rankcell {
	box-shadow: inset 3px 0 0 var(--brand);
}
tr.podium td.name {
	font-weight: 700;
}
/* The General Manager avatar on every rankings location row, and the DM
   avatar beside each DM name in the rankings and the by-DM table
   (rankings.js / stores.js via managers.js): a photo, or the initials circle
   when there is none or it fails to load, or for a location with nothing on
   file an empty circle (.gm-empty) so the names still line up. Inline-flex
   keeps it on the name's line and lets a long name wrap beside it at phone
   width rather than under it. The admin page's Store managers and District
   managers tables reuse the same classes at a larger size (§15). */
.namewithgm {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	/* An inline-flex box takes its baseline from its first item, and an <img>
	   has none — left at the default baseline alignment, the box's bottom
	   edge sits on the text baseline and the whole name rides high in the
	   row. Middle-aligning it centres it like the other cells' text. */
	vertical-align: middle;
	/* The 28px circle is taller than a line of 12.5px text; pulling it into
	   the cell padding keeps a podium row about as tall as the rest. The
	   margins are symmetric, so they shrink the line box without moving the
	   centre that vertical-align: middle chose. */
	margin: -5px 0;
}
.gm-photo,
.gm-initials {
	flex: none;
	width: 28px;
	height: 28px;
	border-radius: 50%;
	border: 1px solid var(--rule);
}
.gm-photo {
	display: block;
	object-fit: cover;
	background: var(--rule-soft);
}
.gm-initials {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: var(--rule-soft);
	color: var(--slate);
	font-family: var(--mono);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: 0.02em;
	line-height: 1;
	/* The name cell is underlined as a link; the circle is not text to read. */
	text-decoration: none;
}
/* Nothing on file: the same circle, emptier, so it reads as "no photo" rather
   than as a person. */
.gm-initials.gm-empty {
	background: transparent;
	border-color: var(--rule-soft);
}
/* An avatar-sized gap for a row that is not a person ("Unassigned", the
   Average row) — keeps its text in line with the names around it. */
.gm-spacer {
	flex: none;
	width: 28px;
	height: 28px;
}
.composite {
	font-family: var(--mono);
	font-size: 13px;
	font-weight: 600;
}
.rankmethod {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
	line-height: 1.6;
	background: var(--panel);
	border: 1px solid var(--rule);
	border-top: 0;
	padding: 11px 14px;
}
.rankmethod b {
	color: var(--slate);
	font-weight: 600;
}
/*
 * Flexbox, not CSS Grid, on purpose (2026-09-18). The card count here varies
 * 3-6 (gain/slip are conditional, and "Composite spread" needs 2+ ranked
 * locations), and a `grid-template-columns: repeat(auto-fit, minmax(...))`
 * grid picks its column COUNT once for the whole container: if any row fills
 * every column, a later incomplete row's cells do NOT stretch to fill it —
 * they just leave the empty tracks, and since `.movers`' background is the
 * rule colour showing through the 1px `gap` (the same divider trick as
 * `.rangepresets`, see the media-query comment below), that reads as a
 * ragged strip of stray rule colour rather than a clean short row. Flexbox
 * wrapping has no such fixed column count — items on ANY row, including a
 * trailing partial one, grow to fill exactly that row's width — so every
 * count from 3 to 6 lays out with no orphaned gap, at any viewport width,
 * with no extra per-count media queries.
 */
.movers {
	display: flex;
	flex-wrap: wrap;
	gap: 1px;
	background: var(--rule);
	border: 1px solid var(--rule);
	margin-top: 18px;
}
.mover {
	background: var(--panel);
	padding: 14px 16px;
	/* grow+shrink so a partial last row fills the width (see .movers above);
	   the 190px basis is small enough that all 6 possible cards still fit one
	   desktop row before wrapping, unlike the old 230px grid minimum. */
	flex: 1 1 190px;
}
.mover .k {
	font-family: var(--mono);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--faint);
}
.mover .v {
	font-size: 16px;
	font-weight: 700;
	margin: 5px 0 2px;
	letter-spacing: -0.01em;
}
.mover .d {
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--muted);
	/* "Composite spread"'s detail names two stores and can run longer than the
	   other cards' single-DM line; let it wrap instead of widening the card. */
	overflow-wrap: anywhere;
}

/* ── 13. Responsive ────────────────────────────────────────────────────── */
/* The dashboard-only half of the old single ≤640px block. The site-level
   selectors (`h1.title`, `.wrap`, `.topbar .inner`, `.brandblock`,
   `.footbrand`) have their own ≤640px block in /styles.css; nothing in
   either block sets a property the other one does, so splitting them
   changes nothing. The global reduced-motion block moved there too. */
@media (max-width: 640px) {
	.rail {
		grid-template-columns: 96px 1fr 78px;
		gap: 10px;
	}
	.kpi .v {
		font-size: 21px;
	}
	/* Once .rangepresets wraps onto two rows, a button's own width (fitted to
	   its label) leaves a short last row narrower than the row above it —
	   the 1px-gap-over-a-coloured-background trick that draws the dividers
	   would then show a ragged strip of that background to the right of the
	   short row. Growing each button to fill its own row closes that gap so
	   every row reaches the same right edge, on both dashboard pages. */
	.rangepresets button {
		flex: 1 1 auto;
	}
}

/* ── 14. Print export (HR Overview) ───────────────────────────────────── */
/*
 * "Export PDF" on the HR Overview does not generate a PDF itself — it opens
 * the browser's print dialog, which can already save to PDF. A client-side
 * PDF library was the other option and was ruled out: the CSP is
 * `script-src 'self'`, so a CDN-hosted library would have to be vendored
 * and self-hosted, and there is no build step in this repo to bundle one
 * in anyway. Print-to-PDF needs neither. hr.js adds `print-align` to
 * `<body>` for the duration of the print, and everything below is scoped
 * to that class so it never affects the screen view or any other page.
 *
 * What gets exported is exactly the rows on screen: the current sort (see
 * sort.js) and whichever location chips are selected, same as the errors
 * table itself — not the full unfiltered alignment list.
 */
.printonly {
	display: none;
}
@media print {
	body.print-align .topbar,
	body.print-align #banners,
	body.print-align .pickbar,
	body.print-align #s-turnover,
	body.print-align #hrkpis,
	body.print-align .alignactions,
	body.print-align details#aligndetails > summary,
	body.print-align footer,
	body.print-align #loading {
		display: none !important;
	}
	body.print-align .printonly {
		display: block;
		margin-bottom: 12px;
		font-family: var(--sans);
		font-size: 15px;
		font-weight: 700;
		color: #000;
	}
	/* The screen layout scrolls a fixed-width table sideways inside a bordered
	   box with a sticky header — none of that means anything on paper. */
	body.print-align .tablewrap {
		border: 0;
		overflow: visible;
	}
	body.print-align table {
		width: 100%;
		min-width: 0;
		color: #000;
		background: #fff;
	}
	body.print-align th {
		position: static;
		background: #fff;
	}
	body.print-align tr {
		break-inside: avoid;
	}
	/* cell-bad/cell-warn may print as plain grey once the browser drops
	   background colours by default — that is fine, the row still says what
	   is wrong in words (Grouping level, Title vs department, …); nothing
	   here depends on the colour alone. */
}

/* ── 15. Admin panel ────────────────────────────────────────────────────── */
/* Scoped to #admingate / #adminpanel so none of it touches any page that
   does not carry the admin gate — that is the Admin page (admin/index.html,
   js/admin.js) and, sharing the same ids and js/adminGate.js, the Location
   Report (locationreport/index.html, js/locationReport.js). Inputs reuse the
   lockbox's #pw look (border, mono font, #fafaf9 fill) at a smaller size —
   see the compact override for the per-store grid below, where twelve rows
   times seven metrics needs every column to stay narrow. */

/* The admin passphrase gate. Unlike #gate (the page's own lock screen, a
   full-viewport overlay), this sits inline inside #content, behind the
   ordinary dashboard password — so it is sized as a card in the page flow,
   not centered over the whole screen. */
#admingate {
	max-width: 420px;
	margin: 40px auto;
}
#admingate .lockbox {
	padding: 30px 26px 26px;
}
#admingate h1 {
	font-size: 17px;
}

.goalgrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 14px;
	margin-top: 16px;
}
.goalfield {
	display: block;
}
.goalfield .eyebrow {
	display: block;
	margin-bottom: 6px;
}
.goalfield input {
	width: 100%;
	padding: 8px 9px;
	font-family: var(--mono);
	font-size: 13px;
	border: 1px solid var(--rule);
	background: #fafaf9;
	color: var(--ink);
	border-radius: 0;
}
.goalfield input:focus {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
	background: #fff;
}

/* The company row (js/admin.js's buildStoreTable) — pinned to the top of
   #storegoalstable through every sort by tr.pinnedrow (see sort.js's
   applySort()), and tinted apart from the store rows so it reads as the
   fallback every blank store cell inherits from, not just another location. */
#storegoalstable tr.companyrow td {
	background: #eef1f6;
	border-top: 2px solid var(--slate);
	border-bottom: 2px solid var(--slate);
	font-weight: 600;
}
#storegoalstable tr.companyrow td.name {
	font-weight: 800;
}

/* The per-store grid. Same input look as .goalfield above, just compact —
   the table is wide enough already with twelve rows and a name/market
   column plus seven metric columns. */
#storegoalstable td {
	padding: 5px 6px;
}

#storegoalstable input {
	width: 74px;
	padding: 5px 6px;
	font-family: var(--mono);
	font-size: 11.5px;
	text-align: center;
	border: 1px solid var(--rule);
	background: #fafaf9;
	color: var(--ink);
	border-radius: 0;
}
#storegoalstable input:focus {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
	background: #fff;
}
#storegoalstable input::placeholder {
	color: var(--faint);
}


/* Store managers (js/admin.js's buildManagerTable). The 40px thumbnail is the
   same avatar the rankings draw at 28px (§10), so what is uploaded here is
   what the podium shows. The name input borrows the per-store grid's look. */
.gm-photo.gm-lg,
.gm-initials.gm-lg {
	width: 40px;
	height: 40px;
	font-size: 13px;
}
#managerstable td,
#dmmanagerstable td {
	padding: 5px 8px;
}
#managerstable td.gmthumb .gm-photo,
#managerstable td.gmthumb .gm-initials,
#dmmanagerstable td.gmthumb .gm-photo,
#dmmanagerstable td.gmthumb .gm-initials {
	margin: 0 auto;
}
#managerstable input.gminput {
	width: 220px;
	padding: 5px 7px;
	font-family: var(--mono);
	font-size: 12px;
	border: 1px solid var(--rule);
	background: #fafaf9;
	color: var(--ink);
	border-radius: 0;
}
#managerstable input.gminput:focus {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
	background: #fff;
}
#managerstable input.gminput::placeholder {
	color: var(--faint);
}
#managerstable td.gmactions,
#dmmanagerstable td.gmactions {
	text-align: left;
}
#managerstable td.gmactions .linkbtn + .linkbtn,
#dmmanagerstable td.gmactions .linkbtn + .linkbtn {
	margin-left: 12px;
}
#managerstable .linkbtn:disabled,
#dmmanagerstable .linkbtn:disabled {
	cursor: wait;
	color: var(--faint);
}
.gmstatus {
	margin-left: 12px;
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
}
.gmstatus.ok {
	color: var(--good);
}
.gmstatus.bad {
	color: var(--bad);
}
/* District managers' note ("Loading…", "list unavailable") is usually empty;
   an empty .histmsg still holds its min-height, which left a gap between
   the section heading and its table. */
#dmmanagersmsg:empty {
	display: none;
}

.adminactions {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 14px;
	margin-top: 22px;
}
.adminactions button {
	padding: 10px 18px;
	background: var(--ink);
	color: #fff;
	border: 0;
	font-family: var(--sans);
	font-size: 13px;
	font-weight: 700;
	cursor: pointer;
}
.adminactions button:hover {
	background: var(--slate);
}
.adminactions button:disabled {
	background: var(--faint);
	cursor: wait;
}
#adminsavemsg {
	font-family: var(--mono);
	font-size: 12px;
	color: var(--bad);
	min-height: 15px;
}
#adminsavemsg.ok {
	color: var(--good);
}
#adminupdated {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
	margin-left: auto;
}
/* The save note sits in the actions row; the basis lets it share the row with
   the button on a desktop and drop under it at phone width. */
#adminnote {
	flex: 1 1 260px;
	max-width: 420px;
	padding: 9px 10px;
	font-family: var(--mono);
	font-size: 12px;
	border: 1px solid var(--rule);
	background: #fafaf9;
	color: var(--ink);
	border-radius: 0;
}
#adminnote:focus {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
	background: #fff;
}
/* ".adminactions button" above would paint the conflict message's
   "Load current settings" as a dark action button; it is a text link. */
.adminactions button.linkbtn {
	padding: 0;
	margin-left: 8px;
	background: none;
	color: var(--muted);
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 400;
}
.adminactions button.linkbtn:hover {
	background: none;
	color: var(--ink);
}

/* Version history (js/admin.js's drawHistory). */
/* Neutral by default: it also shows the "Nothing saved yet." empty state.
   admin.js's setHistoryMsg() adds .bad only for a load failure. */
#historymsg {
	font-family: var(--mono);
	font-size: 12px;
	color: var(--muted);
	min-height: 15px;
}
#historymsg.bad {
	color: var(--bad);
}
/* History has no heading of its own (the details.tabletoggle bar says
   "History"), so its message line is the first thing after #adminform and
   carries the gap a section heading would. 20px + the line's 15px
   min-height + the bar's own 14px margin ≈ the 48px Data health gets
   (the table's 14px margin + `#adminpanel > h2.sec`'s 34px). */
#adminform + #historymsg {
	margin-top: 20px;
}
/* The sentence that used to be the History heading's hint, now inside the
   <details> between the dark open bar and the table. */
#historydetails .historyhint {
	margin: 10px 0 0;
}
#settingshistory td {
	vertical-align: top;
}
/* Free text and a bulleted list read badly centred like the numeric cells. */
#settingshistory td.note,
#settingshistory td.changes,
#settingshistory th.note,
#settingshistory th.changes {
	text-align: left;
}
#settingshistory td.changes {
	min-width: 240px;
}
.changelist {
	margin: 0;
	padding-left: 14px;
	list-style: disc;
	line-height: 1.35;
	font-size: 12px;
	color: var(--muted);
}
#settingshistory .current {
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
}
/* Data health and Workbook changes (js/admin.js). Same neutral-until-.bad
   message as #historymsg; two more sections share the class. */
.histmsg {
	font-family: var(--mono);
	font-size: 12px;
	color: var(--muted);
	min-height: 15px;
}
.histmsg.bad {
	color: var(--bad);
}
/* Every section after the form needs the gap #adminform + h2.sec gives History. */
#adminpanel > h2.sec {
	margin-top: 34px;
}
.healthline {
	margin: 0 0 10px;
	font-size: 13px;
}
.healthcheck {
	margin: 0 0 8px;
	padding: 8px 10px;
	background: var(--panel);
	border: 1px solid var(--rule);
	font-size: 13px;
}
.healthcheck .mark {
	font-family: var(--mono);
	font-weight: 600;
}
.healthcheck.ok .mark {
	color: var(--good);
}
.healthcheck.problem {
	border-left: 3px solid var(--bad);
	background: var(--bad-bg);
}
.healthcheck.problem .mark {
	color: var(--bad);
}
.healthcheck ul {
	margin: 6px 0 0;
	padding-left: 18px;
	list-style: disc;
	font-family: var(--mono);
	font-size: 12px;
	line-height: 1.4;
	overflow-wrap: anywhere;
}
.healthcheck details summary {
	margin-top: 6px;
	cursor: pointer;
	font-size: 12px;
	color: var(--muted);
}
.healthcheck details ul {
	margin-top: 4px;
}
.filterrow {
	display: flex;
	flex-wrap: wrap;
	gap: 6px 18px;
	margin: 0 0 8px;
	font-size: 13px;
}
.filterrow label {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	cursor: pointer;
}
#changestable td {
	vertical-align: top;
}
#changestable td.name,
#changestable td.change,
#changestable th.change {
	text-align: left;
}
#changestable td.change {
	white-space: nowrap;
}
.kindlabel {
	display: inline-block;
	padding: 1px 6px;
	border: 1px solid var(--rule);
	font-family: var(--mono);
	font-size: 11px;
	color: var(--muted);
}
.kindlabel.kind-changed {
	color: var(--warn);
	border-color: var(--warn);
}
.kindlabel.kind-cleared {
	color: var(--bad);
	border-color: var(--bad);
}
#adminsignout {
	margin-top: 26px;
}

/* General Manager photo editor (js/admin.js's crop dialog). A native
   <dialog> opened with showModal(), so the page behind is inert and Escape
   cancels. Sized to fit a phone with the site's 16px gutters. */
#photoeditor {
	width: min(420px, calc(100vw - 32px));
	max-height: calc(100dvh - 32px);
	overflow-y: auto;
	padding: 18px;
	border: 1px solid var(--rule);
	border-top: 4px solid var(--brand);
	border-radius: 2px;
	background: var(--panel);
	color: var(--ink);
}
#photoeditor::backdrop {
	background: var(--ink);
	opacity: 0.6;
}
#photoeditor h2.sec {
	margin-top: 0;
}
.photohint {
	margin: 8px 0 12px;
	font-size: 12.5px;
	color: var(--muted);
}
/* Square, as wide as the dialog allows. touch-action: none hands every
   touch to the pointer handlers (drag and pinch) instead of scrolling or
   zooming the page. The background shows only where the photo stops short
   of the canvas edge, i.e. outside the crop. */
#photocanvas {
	display: block;
	width: 100%;
	aspect-ratio: 1;
	background: var(--slate);
	touch-action: none;
	cursor: grab;
}
#photocanvas.dragging {
	cursor: grabbing;
}
#photocanvas:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: 2px;
}
.photozoom {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 12px;
}
.photozoom input {
	flex: 1;
	accent-color: var(--brand);
}
.photopreviews {
	display: flex;
	align-items: center;
	gap: 14px;
	margin-top: 12px;
}
/* The same circles the rankings (28px) and the Store managers table (40px)
   draw; the canvases hold the square photo, the radius makes it round. */
.photopreview {
	border-radius: 50%;
	background: var(--panel);
	box-shadow: 0 0 0 1px var(--rule);
}
#photopreview28 {
	width: 28px;
	height: 28px;
}
#photopreview40 {
	width: 40px;
	height: 40px;
}
.photoactions {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-top: 18px;
}
.photoactions .linkbtn {
	margin-right: auto;
}
.photobtn {
	padding: 9px 16px;
	border: 1px solid var(--ink);
	border-radius: 0;
	background: var(--ink);
	color: var(--panel);
	font-family: var(--sans);
	font-size: 13px;
	font-weight: 700;
	cursor: pointer;
}
.photobtn.secondary {
	background: var(--panel);
	color: var(--ink);
	border-color: var(--rule);
}
.photobtn:hover {
	background: var(--slate);
	border-color: var(--slate);
}
.photobtn.secondary:hover {
	background: var(--rule-soft);
	border-color: var(--rule);
}
.photobtn:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: 2px;
}
