:root {
  /* Neutral slate chrome with a vibrant categorical palette (traditional
     dashboard look). Green is the primary data/action color. */
  --primary: #2b333b;      /* dark neutral slate (topbar / headings) */
  --primary-700: #20262c;
  --bg: #f4f5f7;
  --surface: #ffffff;
  --border: #e3e7eb;
  --text: #232a31;
  --muted: #6b7280;

  /* categorical / semantic colors */
  --green:  #1f9d57;  --green-soft:  #e6f5ec;
  --orange: #d9722e;  --orange-soft: #fbeadd;
  --amber:  #b8860b;  --amber-soft:  #f8f0d2;
  --red:    #d24b45;  --red-soft:    #fbe7e5;
  --blue:   #2f6fb0;  --blue-soft:   #e7f0f9;

  --accent: var(--green);          /* buttons, bars, charts */
  --accent-soft: var(--green-soft);

  --shadow: 0 1px 2px rgba(20, 28, 38, 0.05), 0 1px 3px rgba(20, 28, 38, 0.08);
  --radius: 12px;
}

* { box-sizing: border-box; }

/* The hidden attribute must ALWAYS win. Any rule that sets display (e.g.
   .nav-link { display:inline-flex }) silently overrides the UA's
   [hidden]{display:none} — which made "hidden" elements render (found live
   2026-07-12). Every show/hide in this app goes through the hidden
   attribute, so enforce it globally. */
[hidden] { display: none !important; }

/* Connection-status chips (dashboard header): dot color = feed health. */
.conn-status { display: flex; flex-wrap: wrap; gap: 0.45rem; margin: 0.2rem 0 0.6rem; }
.conn-chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.78rem; color: var(--muted);
  padding: 0.25rem 0.6rem; border: 1px solid var(--border);
  border-radius: 999px; background: var(--surface);
}
.conn-dot { width: 0.55rem; height: 0.55rem; border-radius: 50%; flex: none; }
.conn-dot.conn-ok   { background: var(--green); }
.conn-dot.conn-warn { background: var(--amber); }
.conn-dot.conn-bad  { background: var(--red); }
.conn-dot.conn-off  { background: var(--muted); }

/* First-run note shown while a tenant has no machines and no sales. */
.getting-started { border: 1px solid var(--border); border-radius: 10px; padding: 0.9rem 1rem; margin: 0.75rem 0; }
.getting-started h2 { font-size: 1rem; margin: 0 0 0.35rem; }
.getting-started p { margin: 0; font-size: 0.9rem; color: var(--muted); line-height: 1.5; }

/* ---------- Margins (warehouse page) ---------- */
.margin-row { cursor: pointer; }
.margin-row:hover td { background: var(--bg); }
/* The name wraps (table cells are nowrap by default): one long product name set
   the whole table's minimum width and pushed it into a sideways scroll on
   anything narrower than a full desktop window. */
.margin-item { font-weight: 600; white-space: normal; min-width: 9rem; }
.margin-nocost { color: var(--orange); font-size: 0.72rem; font-weight: 600; white-space: nowrap; }
.margin-profit { color: var(--green); font-weight: 650; }
.margin-neg { color: var(--red); font-weight: 650; }
.cost-editor {
  display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: end;
  padding: 0.6rem 0.4rem; background: var(--bg); border-radius: 8px;
}
.cost-editor .ef input { width: 7rem; }
.cost-tax-label { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.82rem; color: var(--muted); }
.cut-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0; }
.cut-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.cut-row input { width: 4.5rem; padding: 0.35rem 0.45rem; }
.cut-pct { color: var(--muted); }
.alias-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; width: 100%; }
.alias-chip {
  font-size: 0.75rem; color: var(--muted); background: var(--surface);
  border: 1px solid var(--border); border-radius: 999px; padding: 0.2rem 0.55rem;
  cursor: pointer;
}
.alias-chip:hover { color: var(--red); border-color: var(--red); }
.cost-editor select { padding: 0.45rem 0.5rem; max-width: 16rem; }

/* Warehouse collapsible sections: the summary row is the section header with
   an explicit triangle (custom ::before — flex summaries drop the native
   marker). Whole sections fold away; state persists via localStorage. */
.wh-section { margin: 0 0 1.1rem; }
.wh-section-head {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  cursor: pointer; list-style: none; padding: 0.15rem 0;
}
.wh-section-head::-webkit-details-marker { display: none; }
.wh-section-head::before {
  content: '▸'; color: var(--muted); font-size: 0.85em;
  transition: transform 0.15s; flex: none;
}
details[open] > .wh-section-head::before { transform: rotate(90deg); }
.wh-section-head h2 { margin: 0; font-size: 1.05rem; }

/* Margins totals row: bold summary line under the sortable body, hosting the
   range control (7D/30D/90D). */
.margins-total td { font-weight: 650; border-top: 2px solid var(--border); }
.margin-sub {
  margin: 0.7rem 0 0.25rem; font-size: 0.68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
}
.margins-total-label { margin-right: 0.6rem; }
/* .preset is drawn for the dark top bar (white text). Here it sits on a white
   card, where 7D and 90D were white on white and only the active one showed. */
.margins-range {
  display: inline-flex; gap: 0.25rem; vertical-align: middle;
  background: var(--bg); border: 1px solid var(--border);
}
.margins-range .preset { padding: 0.25rem 0.55rem; font-size: 0.78rem; color: var(--text); opacity: 1; }
.margins-range .preset:hover { background: var(--border); }
.margins-range .preset.active { background: var(--primary); color: #fff; }

/* Info icons: help text lives behind a tappable ⓘ instead of paragraphs on
   the page. The popover is a positioned card; tapping the icon toggles it,
   tapping anywhere else closes it (wired in warehouse.js). */
.info { position: relative; display: inline-flex; flex: none; }
.info-btn {
  width: 1.15rem; height: 1.15rem; padding: 0; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); font-size: 0.7rem; font-weight: 700;
  font-style: italic; font-family: Georgia, serif;
  line-height: 1; cursor: pointer;
}
.info-btn:hover { color: var(--primary); border-color: var(--primary); }
.info-pop {
  position: absolute; top: calc(100% + 0.4rem); left: 50%;
  transform: translateX(-50%);
  width: min(18rem, 78vw); padding: 0.6rem 0.75rem; z-index: 30;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  font-size: 0.8rem; font-weight: 400; line-height: 1.45;
  color: var(--text); text-transform: none; letter-spacing: normal;
  text-align: left; white-space: normal;
}
/* info.js nudges an opened popover back on-screen if the centered position
   would clip at a viewport edge (icons sit anywhere from margin to margin). */
.help-row { display: flex; align-items: center; gap: 0.45rem; }
.help-row > p, .help-row > .inv-summary { margin: 0; }

/* Keyboard focus: one consistent, visible ring on every interactive control. */
button:focus-visible, select:focus-visible, a:focus-visible, input:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Rapid taps on controls must never trigger iOS double-tap zoom (the refill
   stepper especially — counting stock is a burst of + / − taps). manipulation
   keeps scrolling and pinch-zoom but drops the double-tap gesture, which also
   removes the legacy tap delay. */
button, a, select, input, label, summary { touch-action: manipulation; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  /* The page never scrolls sideways — wide content (tables, charts) scrolls
     inside its own container. Guards against any stray overflow reading as a
     "shifted" page on phones. */
  overflow-x: hidden;
}

/* ---------- Top bar ---------- */
.topbar {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.9rem 1.5rem;
  background: linear-gradient(120deg, var(--primary), var(--primary-700));
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 10;
}

/* Wraps: on narrow screens the nav links flow onto their own row below the
   logo + title instead of overflowing off-screen. min-width: 0 lets the block
   shrink inside .topbar (flex items otherwise refuse to go below content
   width and push siblings — e.g. the Refresh button — off the viewport). */
.topbar-brand { display: flex; align-items: center; gap: 0.6rem 0.75rem; flex-wrap: wrap; min-width: 0; }

.brand-mark {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  background: #fff;
  object-fit: cover;
  display: block;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15);
}

.topbar h1 { font-size: 1.25rem; margin: 0; font-weight: 650; line-height: 1.15; }
/* color (not opacity) so children like the back pill can render full-white */
.brand-sub { margin: 0; font-size: 0.84rem; color: rgba(255, 255, 255, 0.75); }
/* Back button: the leading element of every subpage header (top-left, where
   thumbs expect it) — a big round arrow target, not a text link. */
.back-btn {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; /* 44px */
  border-radius: 50%;
  color: #fff; text-decoration: none;
  background: rgba(255, 255, 255, 0.16); border: 1px solid rgba(255, 255, 255, 0.35);
  font-size: 1.35rem; line-height: 1;
}
.back-btn:hover { background: rgba(255, 255, 255, 0.3); }
.back-btn:active { transform: scale(0.94); }
.topbar-brand a { display: inline-flex; }

/* topbar nav link + badge */
.nav-link {
  display: inline-flex; align-items: center; gap: 0.45rem;
  color: #fff; text-decoration: none; font-size: 0.95rem; font-weight: 550;
  padding: 0.55rem 0.95rem; border-radius: 9px;
  background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.2);
}
.topbar h1 + .brand-sub { margin-right: 0.5rem; }
/* extra breathing room between the title block and the first nav link */
.topbar-brand > div { margin-right: 0.4rem; min-width: 0; }
.nav-link:hover { background: rgba(255, 255, 255, 0.22); }
/* Sign out reads quieter than the destination pills but stays tappable. */
.nav-signout { background: none; border-color: rgba(255, 255, 255, 0.35); font-weight: 500; }
.nav-badge {
  background: var(--red); color: #fff; border-radius: 999px;
  font-size: 0.7rem; font-weight: 700; padding: 0.05rem 0.4rem;
  min-width: 1.1rem; text-align: center;
}

/* ---------- Refund requests ---------- */
/* Capped: refund cards are prose-heavy and unreadable at full laptop width. */
.refund-list { display: flex; flex-direction: column; gap: 1rem; max-width: 60rem; }
.refund-card { display: flex; flex-direction: column; gap: 0.45rem; }
.refund-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.refund-when { font-size: 0.82rem; color: var(--muted); }
.refund-amount { font-weight: 700; font-size: 1.1rem; color: var(--primary); display: inline-flex; align-items: center; gap: 0.5rem; }
.refund-method {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
  padding: 0.12rem 0.45rem; border-radius: 999px; background: var(--blue-soft); color: var(--blue);
}
.refund-who { font-weight: 650; font-size: 1rem; }
.refund-contact { font-size: 0.85rem; color: var(--muted); overflow-wrap: anywhere; }
.refund-fields { display: flex; flex-wrap: wrap; gap: 0.4rem 1.75rem; margin-top: 0.25rem; }
.refund-field { display: flex; flex-direction: column; }
.refund-field-label { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 600; }
.refund-field-value { font-size: 0.9rem; }
.refund-desc {
  margin: 0.4rem 0 0; font-size: 0.9rem; line-height: 1.5; color: var(--text);
  background: var(--bg); border-radius: 8px; padding: 0.6rem 0.8rem; white-space: pre-wrap; overflow-wrap: anywhere;
}
.refund-head-left { display: inline-flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }

/* status pill (semantic): pending=warning, refunded=positive, denied=urgent */
.refund-status {
  font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700;
  padding: 0.14rem 0.55rem; border-radius: 999px;
}
.status-pending  { background: var(--orange-soft); color: var(--orange); }
.status-refunded { background: var(--green-soft);  color: var(--green); }
.status-denied   { background: var(--red-soft);    color: var(--red); }

/* manage block */
.refund-manage { border-top: 1px solid var(--border); margin-top: 0.5rem; padding-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
.refund-status-btns { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.refund-status-btns .refund-field-label { margin-right: 0.25rem; }
.status-btn {
  font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer;
  padding: 0.3rem 0.75rem; border-radius: 7px;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
}
.status-btn:hover { border-color: var(--muted); color: var(--text); }
.status-btn:disabled { opacity: 0.6; cursor: default; }
.status-btn.status-pending.active  { background: var(--orange); border-color: var(--orange); color: #fff; }
.status-btn.status-refunded.active { background: var(--green);  border-color: var(--green);  color: #fff; }
.status-btn.status-denied.active   { background: var(--red);    border-color: var(--red);    color: #fff; }

/* Software requests: same cards as refunds. The status is the follow-up
   stage, always shown as a word: new = needs a reply (blue), contacted = in
   progress (orange), signed up = won (green), not interested = closed (grey). */
.status-new       { background: var(--blue-soft);   color: var(--blue); }
.status-contacted { background: var(--orange-soft); color: var(--orange); }
.status-signed_up { background: var(--green-soft);  color: var(--green); }
.status-declined  { background: var(--bg);          color: var(--muted); }
.status-btn.status-new.active       { background: var(--blue);   border-color: var(--blue);   color: #fff; }
.status-btn.status-contacted.active { background: var(--orange); border-color: var(--orange); color: #fff; }
.status-btn.status-signed_up.active { background: var(--green);  border-color: var(--green);  color: #fff; }
.status-btn.status-declined.active  { background: var(--muted);  border-color: var(--muted);  color: #fff; }
.sw-systems { margin: 0.3rem 0 1rem; font-size: 0.85rem; color: var(--muted); }

.refund-notes-label { margin-top: 0.15rem; }
.refund-notes {
  width: 100%; font: inherit; font-size: 0.88rem; resize: vertical;
  border: 1px solid var(--border); border-radius: 8px; padding: 0.5rem 0.65rem; color: var(--text);
}
.refund-notes:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.refund-actions { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.refund-actions .btn-primary { font-size: 0.82rem; }
.refund-msg { font-size: 0.8rem; color: var(--muted); }
.refund-msg.ok { color: var(--green); font-weight: 600; }
.refund-msg.err { color: var(--red); }
.refund-updated { font-size: 0.76rem; color: var(--muted); margin-left: auto; }

/* ---------- Inventory / refill (mobile-first) ---------- */
/* Machine status grid — every machine at a glance (replaces the one-at-a-time
   dropdown). Card = name + source + live status; the active one is outlined. */
.mach-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.5rem; margin: 0.2rem 0 1rem;
}
.mach-card {
  display: flex; flex-direction: column; gap: 0.3rem; align-items: flex-start;
  padding: 0.6rem 0.7rem; font: inherit; text-align: left; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow); color: var(--text);
}
.mach-card.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.mach-name { font-weight: 650; font-size: 0.92rem; overflow-wrap: anywhere; }
.mach-sub { display: flex; gap: 0.5rem; align-items: center; font-size: 0.75rem; }

/* Address of the selected machine. Sits directly under the grid so it reads as
   a property of the card you just tapped. The whole row is the tap target —
   this is used one-handed in a van, so it is sized as a thumb target, not a
   text link. */
.mach-addr { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.75rem 0 0; }
.mach-addr-link {
  display: flex; align-items: center; gap: 0.5rem; flex: 1 1 16rem; min-width: 0;
  padding: 0.6rem 0.75rem; border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text); text-decoration: none; font-size: 0.88rem;
}
.mach-addr-link:hover { border-color: var(--accent); }
.mach-addr-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mach-addr-pin { flex: none; font-size: 0.95em; line-height: 1; }
#mach-addr-text { overflow-wrap: anywhere; margin-right: auto; }
.mach-addr-go {
  flex: none; font-size: 0.72rem; font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--accent); white-space: nowrap;
}
.mach-addr-edit { flex: none; font-size: 0.8rem; }
.mach-addr-form { display: flex; flex-wrap: wrap; gap: 0.4rem; flex: 1 1 100%; }
.mach-addr-form input {
  flex: 1 1 14rem; min-width: 0; font: inherit; font-size: 0.88rem;
  padding: 0.55rem 0.7rem; border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text);
}
#mach-addr-msg:empty { display: none; }
#mach-addr-msg { margin-top: 0.15rem; }
.mach-tag { color: var(--muted); }
/* A machine that is selling but has no device_locations row yet: it shows up
   by serial so its slots can be entered before setup names it. */
.mach-tag-new { color: var(--orange); font-weight: 700; }
.mach-stat { font-weight: 700; white-space: nowrap; }
.mach-stat.good { color: var(--green); }
.mach-stat.bad { color: var(--red); }
.mach-stat.muted { color: var(--muted); font-weight: 600; }

.inv-main { padding-bottom: 5.5rem; } /* room for the sticky save bar */

.inv-summary {
  font-size: 0.92rem; font-weight: 600; color: var(--muted);
  padding: 0.5rem 0.2rem 0.8rem;
}
.inv-summary.has-short { color: var(--red); }

/* HAHA machines are API-synced: a notice banner + read-only slot cards. */
.inv-managed-note {
  font-size: 0.9rem; font-weight: 600; color: var(--primary);
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0.6rem 0.75rem; margin: 0.2rem 0 0.8rem;
}
/* Same banner, "new machine" variant (unnamed serial) — editable, not synced. */
.inv-new-note { color: var(--orange); }
.slot-card-ro .slot-head { gap: 0.55rem; }

.slot-list { display: grid; gap: 0.7rem; }

/* The INVENTORY page's slot list is ONE column, always (#slot-list, so the
   Refill Run and Warehouse lists that share .slot-list keep their grid). A
   machine's slots are scanned top-to-bottom for the things that are low —
   two columns make the eye zig-zag and push half the machine off the side of
   a phone. Capped so a wide monitor doesn't stretch a card into a line of
   text you have to track across; the controls above it share the cap so the
   column reads as one object. */
#slot-list { grid-template-columns: minmax(0, 1fr); max-width: 46rem; }
#slot-filter, #slot-empty, .btn-primary.inv-add { max-width: 46rem; }

.slot-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 0.85rem 0.9rem;
}
/* Severity as a left edge: a scan down the single column reads the stock level
   off one vertical line, without landing on any single field. Only the
   inventory page grades its cards. */
#slot-list .slot-card { border-left: 4px solid var(--sev-ink, var(--border)); }
/* Never counted has no level to show — it must not read as "fine". */
#slot-list .slot-card.sev-none { --sev-ink: var(--border); }
/* A stocked slot keeps a neutral edge. Color is reserved for what needs
   action: grading every row green would make a mostly-fine machine a wall of
   color with nothing standing out — which is the whole job of the edge. The
   pill still says "Stocked", so the state is never ambiguous. */
#slot-list .slot-card.sev-0 { border-left-color: var(--border); }
.slot-head { display: flex; align-items: center; gap: 0.55rem; }
/* The level pill sits at the right of the head row, before the ✎ toggle, and
   drops to its own line rather than squeezing a long product name off the
   card. Scoped: .slot-head is also the Refill Run and Warehouse card head. */
#slot-list .slot-head { flex-wrap: wrap; }
#slot-list .sev-pill { flex: none; margin-left: auto; }
#slot-list .sev-pill.sev-none { background: var(--bg); color: var(--muted); }
.slot-badge {
  flex: none; min-width: 2.2rem; text-align: center; font-weight: 700; font-size: 0.82rem;
  color: var(--primary); background: var(--bg); border-radius: 7px; padding: 0.25rem 0.4rem;
}
.slot-name { flex: 1; font-weight: 700; font-size: 1.05rem; line-height: 1.2; }
.slot-edit {
  flex: none; border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  cursor: pointer; font-size: 1.05rem; line-height: 1; padding: 0.45rem 0.55rem; border-radius: 8px;
}
.slot-edit.on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.slot-meta { color: var(--muted); font-size: 0.8rem; margin: 0.35rem 0 0; }
/* Last reported price. Ink rather than muted — it is a fact read off the
   machine, not a derived figure like the "should be" beside it — and tabular so
   a column of slots lines up. Never colored: a price is not a status. */
.slot-price { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.slot-prior { color: var(--orange); font-size: 0.78rem; font-weight: 600; margin-top: 0.2rem; }

/* Read-only (HAHA) cards: stock is a fact, not a field, so the count is the
   headline and takes the level's ink. The bar under it repeats what the
   glyph and the number already say — one hue per level, never a second scale
   to decode. Omitted entirely when the full level is unknown: a bar with no
   denominator would be a made-up proportion. */
#slot-list .slot-left { font-size: 1.05rem; margin-right: 0.15rem; }
#slot-list .slot-gauge {
  margin-top: 0.5rem; height: 0.4rem; border-radius: 999px;
  background: var(--bg); overflow: hidden;
}
#slot-list .slot-gauge-fill { display: block; height: 100%; border-radius: 999px; background: var(--sev-ink); }

.count-row { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.6rem; }
.count-label { font-size: 0.85rem; font-weight: 600; color: var(--muted); }

/* Big touch-friendly stepper */
.stepper { display: inline-flex; align-items: stretch; gap: 0; margin-left: auto; }
.stepper .step {
  width: 3rem; height: 3rem; font-size: 1.6rem; line-height: 1; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--primary); font-weight: 700;
  display: flex; align-items: center; justify-content: center; user-select: none;
}
.stepper .step-down { border-radius: 10px 0 0 10px; }
.stepper .step-up { border-radius: 0 10px 10px 0; }
.stepper .step:active { background: var(--accent-soft); }
.stepper .slot-count {
  width: 4rem; text-align: center; font-size: 1.3rem; font-weight: 700; font-variant-numeric: tabular-nums;
  border: 1px solid var(--border); border-left: 0; border-right: 0; background: var(--surface); color: var(--text);
}
.stepper .slot-count:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* hide native number spinners (we have our own buttons) */
.slot-count::-webkit-outer-spin-button, .slot-count::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.slot-count { -moz-appearance: textfield; }

.count-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 0.45rem; min-height: 1.1rem; }
/* Shrinkage only, and only when there is some — the head's level pill is the
   card's verdict on how full the slot is, so this line no longer carries a
   "✓ on track" / "new count" of its own. (.good / .muted went with them.) */
.slot-status { font-size: 0.85rem; font-weight: 700; }
.slot-status.bad { color: var(--red); }
.slot-refill { font-size: 0.8rem; color: var(--muted); }

.slot-edit-fields {
  display: none; /* shown only when .open is toggled by the per-card ✎ */
  flex-wrap: wrap; gap: 0.6rem; align-items: flex-end;
  margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px dashed var(--border);
}
.slot-edit-fields.open { display: flex; }
.ef { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.72rem; color: var(--muted); font-weight: 600; }
.ef-grow { flex: 1 1 9rem; }
.ef-label { text-transform: uppercase; letter-spacing: 0.03em; }
.ef input {
  font: inherit; font-size: 0.95rem; border: 1px solid var(--border); border-radius: 7px;
  padding: 0.5rem 0.55rem; color: var(--text); background: var(--surface);
}
.ef input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.edit-slot { width: 4.5rem; }
.edit-cap { width: 5.5rem; }
.edit-remove {
  border: 1px solid var(--border); background: var(--surface); color: var(--red);
  cursor: pointer; font-size: 0.85rem; font-weight: 600; padding: 0.5rem 0.7rem; border-radius: 8px;
}
.edit-remove:hover { background: var(--red-soft); border-color: var(--red); }

/* Sits ABOVE the slot list (see inventory.html), so the margin hangs below.
   Colors come from .btn-primary — the same solid green as Save, because this
   button kept getting lost: it shipped as .btn-secondary, which is styled for
   the dark slate .topbar (white on rgba(255,255,255,.14)) and measured ~1.05:1
   against the light page background, then as a soft-green tint that still read
   as background (both reported live 2026-07-30). Compound selector: plain
   .inv-add would lose the padding to .btn-primary, declared later in this
   file. */
.btn-primary.inv-add {
  margin: 0 0 0.9rem; width: 100%;
  padding: 0.7rem; font-size: 0.95rem; font-weight: 650;
}

/* Archived (past-stint) locations on the home page: collapsed by default. */
.loc-archive { margin-top: 1rem; border-top: 1px solid var(--border); padding-top: 0.6rem; }
.loc-archive > summary {
  cursor: pointer; list-style: none; font-size: 0.85rem; font-weight: 600; color: var(--muted);
  padding: 0.3rem 0;
}
.loc-archive > summary::-webkit-details-marker { display: none; }
.loc-archive > summary::before { content: "▸ "; }
.loc-archive[open] > summary::before { content: "▾ "; }
.loc-archive[open] > summary { color: var(--text); }
.archive-grid { margin-top: 0.75rem; max-height: none; }
/* Archived cards read as muted history until hovered. */
.archive-grid .loc-card { opacity: 0.72; }
.archive-grid .loc-card:hover { opacity: 1; }

/* Past-stint pointer on a location page whose machine has moved on. */
.moved-note {
  margin: 0 0 1rem; padding: 0.65rem 0.9rem; font-size: 0.88rem;
  background: var(--blue-soft); border: 1px solid var(--blue);
  border-radius: var(--radius); color: var(--text);
}
.moved-note a { color: var(--blue); font-weight: 700; }

/* Record-a-move panel (inventory page) */
.move-panel {
  margin: 1.25rem 0.25rem 0; padding: 0.8rem 1rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.move-panel > summary { cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--primary); list-style: none; }
.move-panel > summary::-webkit-details-marker { display: none; }
.move-current { margin: 0.6rem 0 0; font-size: 0.86rem; font-weight: 600; color: var(--text); }
.move-form { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: flex-end; margin-top: 0.6rem; }
.move-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: 0.7rem; }
.move-actions .btn-primary { padding: 0.55rem 1rem; }
.btn-plain {
  font: inherit; font-size: 0.86rem; font-weight: 600; cursor: pointer;
  padding: 0.5rem 0.85rem; border-radius: 7px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
.btn-plain:hover { border-color: var(--muted); }
.btn-plain:disabled { opacity: 0.55; cursor: default; }
.move-undo { color: var(--red); }
.move-undo:hover { border-color: var(--red); background: var(--red-soft); }
.move-help { margin: 0.6rem 0 0; }

/* Sticky save bar. The [hidden] rule matters: display:flex would otherwise
   beat the hidden attribute and the bar would show while empty. */
.save-bar[hidden] { display: none; }
.save-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; align-items: center; gap: 0.8rem;
  padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom, 0px));
  background: var(--surface); border-top: 1px solid var(--border);
  box-shadow: 0 -2px 10px rgba(20, 28, 38, 0.08);
}
.save-summary { flex: 1; font-size: 0.88rem; font-weight: 600; color: var(--muted); }
.save-summary.err { color: var(--red); }
.btn-lg { font-size: 1.05rem; padding: 0.7rem 1.4rem; }

.inv-help { color: var(--muted); font-size: 0.82rem; line-height: 1.5; margin: 1rem 0.25rem 0; max-width: 70ch; }

/* Wider screens: cards in a responsive grid — the Refill Run pick list, which
   is scanned as a set. The inventory page opts out above (#slot-list): its
   column is read in order, not scanned. */
@media (min-width: 720px) {
  .slot-list { grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); }
}

.range { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }

.presets { display: inline-flex; gap: 0.25rem; background: rgba(255,255,255,0.1); padding: 0.2rem; border-radius: 9px; }
.preset {
  border: none;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 0.8rem;
  padding: 0.3rem 0.6rem;
  border-radius: 7px;
  cursor: pointer;
  opacity: 0.85;
}
.preset:hover { opacity: 1; background: rgba(255,255,255,0.12); }
.preset.active { background: #fff; color: var(--primary); opacity: 1; font-weight: 600; }

/* Wraps: the location page adds Apply + Refresh to this row, which must fold
   onto a second line on phones rather than clip at the viewport edge. */
.range-dates { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; font-size: 0.82rem; }
.range-dates label { display: flex; gap: 0.35rem; align-items: center; opacity: 0.9; }
.range-dates input {
  border: 1px solid rgba(255,255,255,0.25);
  background: rgba(255,255,255,0.95);
  color: var(--text);
  border-radius: 7px;
  padding: 0.35rem 0.45rem;
  font: inherit;
  font-size: 0.82rem;
}
/* Enlarges a leading/trailing glyph icon (↻ ← → +) inside a button or link so
   it's easier to see and tap, without scaling the label text alongside it. */
.icon { font-size: 1.4em; line-height: 1; vertical-align: -0.08em; }

.btn-primary {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 7px;
  padding: 0.5rem 1rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-secondary {
  background: rgba(255,255,255,0.14);
  color: #fff;
  border: 1px solid rgba(255,255,255,0.25);
  border-radius: 7px;
  padding: 0.5rem 0.9rem;
  font: inherit;
  cursor: pointer;
}
.btn-secondary:hover { background: rgba(255,255,255,0.24); }
.btn-secondary:disabled { opacity: 0.55; cursor: default; }

/* ---------- Layout ---------- */
main { max-width: 1440px; margin: 0 auto; padding: 1.5rem clamp(1.5rem, 2.5vw, 2.75rem); }

.status { color: var(--muted); padding: 2rem 0.25rem; font-size: 0.95rem; }
.status.error { color: #b42318; }

/* Caption + source toggle share one row so filters sit in a single line
   above the data, not stacked. */
.toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem 1rem; flex-wrap: wrap; margin-bottom: 1rem; min-height: 1.8rem;
}
/* Range caption: the active date window as a chip, with quiet secondary meta
   ("updated …", "N active days") stacked below it in smaller text. */
.range-caption { margin: 0; display: inline-flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.cap-range {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.9rem; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums;
  background: var(--surface); border: 1px solid var(--border);
  padding: 0.28rem 0.7rem; border-radius: 999px;
}
.cap-range .cap-ico { font-size: 0.9em; line-height: 1; }
.cap-sub { color: var(--muted); font-size: 0.72rem; padding-left: 0.2rem; }

/* ---------- Source filter ---------- */
.source-filter { display: flex; align-items: center; gap: 0.4rem; }
.source-label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 600; margin-right: 0.15rem; }
.src-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 0.82rem;
  padding: 0.3rem 0.8rem;
  border-radius: 7px;
  cursor: pointer;
}
.src-btn:hover { border-color: var(--accent); color: var(--text); }
.src-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); font-weight: 600; }

/* Source badge on machine/location cards */
.src-badge {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  white-space: nowrap;
}
.src-badge.seedlive { background: var(--blue-soft); color: var(--blue); }
.src-badge.haha { background: var(--orange-soft); color: var(--orange); }

/* ---------- Silent-machine alert ---------- */
/* Real state, so status color is earned — always paired with the ⚠ + text. */
.alert-strip {
  background: var(--red-soft);
  border: 1px solid var(--red);
  border-radius: var(--radius);
  padding: 0.8rem 1rem;
  margin-bottom: 1.25rem;
}
.alert-head { font-weight: 700; color: var(--red); font-size: 0.92rem; }
.alert-row { font-size: 0.88rem; color: var(--text); margin-top: 0.35rem; }
.alert-row a { color: var(--red); font-weight: 600; }

/* Pending settlement is money in limbo, not a broken feed — amber, so it
   never competes with the red health strip above it for attention. */
.pending-strip { background: var(--orange-soft); border-color: var(--orange); }
.pending-strip .alert-head { color: var(--orange); }
.pending-note { font-size: 0.8rem; color: var(--muted); margin-top: 0.5rem; line-height: 1.45; }

/* ---------- Refill run (mobile-first) ---------- */
.run-card.excluded { opacity: 0.45; }
.run-card .slot-head { align-items: center; }
.run-include { width: 1.35rem; height: 1.35rem; flex: none; accent-color: var(--accent); }
.run-low { flex: none; margin-left: auto; font-size: 0.72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--orange); background: var(--orange-soft);
  padding: 0.15rem 0.5rem; border-radius: 999px; }
.run-low.none { color: var(--muted); background: var(--bg); }
.run-items { display: flex; flex-direction: column; margin-top: 0.5rem; }
.run-item { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0.1rem;
  border-top: 1px solid var(--border); cursor: pointer; }
.run-done { width: 1.35rem; height: 1.35rem; flex: none; accent-color: var(--accent); }
.run-item-name { flex: 1; font-size: 0.95rem; font-weight: 600; overflow-wrap: anywhere; }
.run-have { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }
.run-qty { flex: none; font-size: 0.9rem; font-weight: 700; color: var(--accent);
  background: var(--accent-soft); padding: 0.2rem 0.6rem; border-radius: 999px; white-space: nowrap; }
.run-qty.check { color: var(--orange); background: var(--orange-soft); }
.run-item.done .run-item-name, .run-item.done .run-qty { text-decoration: line-through; opacity: 0.55; }

.run-item-name .run-wh {
  display: block; font-weight: 500; font-size: 0.74rem; color: var(--muted);
}
.run-item-name .run-wh.short { color: var(--orange); font-weight: 700; }

/* ---------- Warehouse ----------
   ONE product list: stock count and unit economics in the same row, one
   editor behind it. (Stock and Margins used to be separate sections, so every
   product appeared twice with two add forms and three editors.) */

/* Toolbar above the list: search, then the worklist chips. */
.wh-toolbar { display: flex; flex-direction: column; gap: 0.55rem; margin: 0.2rem 0 0.8rem; }
.wh-search {
  width: 100%; max-width: 24rem;
  font: inherit; font-size: 0.95rem; border: 1px solid var(--border); border-radius: 8px;
  padding: 0.5rem 0.6rem; color: var(--text); background: var(--surface);
}
.wh-search:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }

/* Filter chips. Each one is a worklist you can empty, so the count is the
   payload — tabular so chips don't jitter as numbers change width. Sized as
   thumb targets: this page is worked one-handed. Mirrors .sev-chip on the
   Inventory page. */
.wh-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.wh-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-height: 2.4rem; padding: 0.4rem 0.7rem;
  font: inherit; font-size: 0.82rem; font-weight: 600;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.wh-chip-n { color: var(--text); font-variant-numeric: tabular-nums; }
.wh-chip:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
.wh-chip.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.wh-chip.active .wh-chip-n { color: #fff; }
/* An empty worklist is still worth SEEING ("nothing is negative") — it just
   isn't a filter you can enter. */
.wh-chip:disabled { opacity: 0.45; cursor: default; }

/* On-hand cell. Color is real state here (a negative count is a miscount that
   costs money), so it earns ink beyond the default. */
.wh-cell-count { font-weight: 650; }
.wh-cell-count.low { color: var(--orange); }
.wh-cell-count.neg { color: var(--red); font-weight: 700; }
.wh-cell-none { color: var(--muted); }
.margin-row.open td { background: var(--bg); }

/* The one editor: stock actions, cost basis, name — each labelled group on its
   own line so a phone never puts two unrelated controls side by side. */
/* white-space: table cells are nowrap, and the editor inherited it — its one
   long calculator line stretched the whole table wider than its card. */
.wh-editor { display: flex; flex-direction: column; gap: 0.7rem; padding: 0.3rem 0 0.6rem; white-space: normal; }
.wh-edit-group { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.wh-edit-label {
  flex: 0 0 100%; font-size: 0.68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
}
.wh-edit-group input[type="number"], .wh-edit-group .edit-name {
  font: inherit; font-size: 0.95rem; border: 1px solid var(--border); border-radius: 7px;
  padding: 0.5rem 0.55rem; color: var(--text); background: var(--surface); width: 8rem;
}
.wh-edit-group .edit-name { flex: 1 1 12rem; width: auto; }
.wh-edit-group input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.wh-edit-hint { margin: 0; font-size: 0.82rem; color: var(--muted); line-height: 1.5; }
/* A negative count explains itself where it's fixed. Orange, not red: nothing
   is wrong with the product, the baseline was just never set. */
.wh-edit-warn { margin: 0; font-size: 0.85rem; color: var(--orange); font-weight: 600; line-height: 1.5; }
/* The live cost calculator under the cost fields — full width of the group. */
.wh-edit-note {
  flex: 0 0 100%; margin: 0.1rem 0 0; font-size: 0.82rem; color: var(--muted);
  line-height: 1.5; font-variant-numeric: tabular-nums;
}
.wh-under { color: var(--orange); font-weight: 600; }
.wh-edit-actions {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
  border-top: 1px dashed var(--border); padding-top: 0.7rem;
}
.wh-qty {
  width: 5.2rem; font: inherit; font-size: 1rem; text-align: center;
  border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.4rem;
  background: var(--surface); color: var(--text);
}
.wh-qty:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.wh-remove { margin-left: auto; color: var(--red); }
.wh-remove:hover { border-color: var(--red); background: var(--red-soft); }

/* One add form (there used to be two, with different fields, creating the
   same thing). Name is the only required field. */
.wh-add {
  display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: flex-end;
  margin: 0.4rem 0 0.9rem; padding: 0.85rem 0.9rem;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
}
.wh-add input {
  font: inherit; font-size: 0.95rem; border: 1px solid var(--border); border-radius: 7px;
  padding: 0.5rem 0.55rem; color: var(--text); background: var(--surface);
}
.wh-add input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
#add-qty, #add-cost, #add-per { width: 6rem; }

/* The attention tile's state lives in its context line, not in a colored
   headline number (see the stat-tile rule above). */
.kpi-sub.warn { color: var(--orange); font-weight: 600; }

/* Sticky van-totals bar (reuses .save-bar shell) */
.van-totals { flex: 1; }
.van-totals summary { cursor: pointer; font-weight: 700; font-size: 0.95rem; color: var(--primary); list-style: none; }
.van-totals summary::-webkit-details-marker { display: none; }
.van-totals summary::before { content: "▸ "; color: var(--muted); }
.van-totals[open] summary::before { content: "▾ "; }
.totals-list { max-height: 40vh; overflow-y: auto; margin-top: 0.5rem; }
.totals-row { display: flex; justify-content: space-between; gap: 1rem; padding: 0.3rem 0.1rem;
  font-size: 0.92rem; border-top: 1px solid var(--border); }
.totals-n { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- KPI cards ---------- */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}
/* Stat tiles: label / value / context line. Values wear ink, not series or
   status color — a big green number implies "good" that the data may not
   support; color is reserved for real state (Low/Out badges etc.). */
.kpi {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.kpi-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-weight: 600;
}
.kpi-value { font-size: 1.85rem; font-weight: 700; color: var(--text); letter-spacing: -0.01em; line-height: 1.1; }
.kpi-sub { font-size: 0.8rem; color: var(--muted); min-height: 1.05em; font-variant-numeric: tabular-nums; }

/* ---------- Panels ---------- */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.15rem 1.25rem;
  margin-bottom: 1.25rem;
  box-shadow: var(--shadow);
}
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.panel h2 { font-size: 0.98rem; margin: 0; color: var(--primary); font-weight: 650; }
.panel-note { font-size: 0.78rem; color: var(--muted); }

/* ---------- Low inventory widget ---------- */
.panel-link { text-decoration: none; color: var(--accent); font-weight: 600; }
.panel-link:hover { text-decoration: underline; }
/* ---------- Severity scale (Low Inventory + the Inventory page) ----------
   ONE ordinal status scale shared with the Inventory page, so "worse" looks
   the same wherever it appears: warning → serious → critical, stepping one
   direction in intensity (the app's own semantic heat, amber → orange → red).

   Two rules it must not break:
   1. Color never carries the meaning alone. Every level ships a distinct
      GLYPH SHAPE (hollow triangle → filled triangle → cross) plus a word, so
      the level survives colorblindness, greyscale printing and forced-colors.
   2. The pill labels are small text and must clear 4.5:1 on their own tint.
      The shipped --orange/--red on their -soft backgrounds measured 2.80 and
      3.66, so each ink here is a darkened step of the same hue (measured
      ratios noted per level). Tints are the app's existing -soft tokens. */
.sev-1 { --sev-ink: #7d5b07; --sev-tint: var(--amber-soft); }   /* warning  5.45:1 */
.sev-2 { --sev-ink: #9a4c19; --sev-tint: var(--orange-soft); }  /* serious  5.24:1 */
.sev-3 { --sev-ink: #a5322d; --sev-tint: var(--red-soft); }     /* critical 5.71:1 */
/* Level 0 — "nothing wrong here". Only the Inventory page has it: the
   dashboard's Low panel contains problems by construction, but a machine's
   full planogram is mostly fine, and a list where every row is graded needs a
   name for fine. Same darkened-ink treatment as the rest (5.44:1 on its own
   tint) so the four levels read as one family. */
.sev-0 { --sev-ink: #17703f; --sev-tint: var(--green-soft); }   /* stocked  5.44:1 */

.sev-pill {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700;
  padding: 0.14rem 0.5rem; border-radius: 999px;
  background: var(--sev-tint); color: var(--sev-ink);
}
/* The glyph is the redundant channel — keep it legible, not decorative. */
.sev-pill .sev-glyph { font-size: 1.05em; line-height: 1; }
/* The one number that says how bad it is, in the level's ink. */
.sev-num { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--sev-ink); }
.low-empty { color: var(--muted); font-size: 0.88rem; margin: 0; }

/* ---------- Low Inventory: severity chips ----------
   Replaced the machine <select>. A dropdown hides its own contents, so you had
   to open it to learn whether anything was OUT; these say it standing still —
   the same reason the Refill page's machine picker stopped being a select.
   Sized as thumb targets: this panel is read one-handed at the machine. */
.sev-filter { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.7rem; }
.sev-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  min-height: 2.4rem; padding: 0.4rem 0.7rem;
  font: inherit; font-size: 0.82rem; font-weight: 600;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.sev-chip .sev-glyph { color: var(--sev-ink); font-size: 0.95em; line-height: 1; }
/* The count is the chip's payload — ink, tabular so the chips don't jitter as
   numbers change width across re-renders. */
.sev-chip-n { color: var(--text); font-variant-numeric: tabular-nums; }
.sev-chip:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
/* Selected: the level's own tint, so the chip and the rows it filters to read
   as the same state. */
.sev-chip.active { background: var(--sev-tint); border-color: var(--sev-ink); color: var(--text); }
/* "All" is not a level and has no tint — it wears the app's accent. */
.sev-chip-all.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.sev-chip-all.active .sev-chip-n { color: #fff; }
/* A level with nothing in it is still worth SEEING ("nothing is out") — it just
   isn't a filter you can enter. */
.sev-chip:disabled { opacity: 0.45; cursor: default; }
/* "Not counted" is missing data, not a severity — no ink, no glyph. */
.sev-chip.sev-none.active { background: var(--bg); border-color: var(--muted); color: var(--text); }

/* ---------- Low Inventory: one collapsed row per machine ----------
   The card grid showed about two machines and put the rest below a scroll, so
   the panel couldn't answer "how bad is it overall". Collapsed rows fit every
   machine at once; the strip carries the shape of each machine's problem and
   the row expands to the slot detail (a <details>, so it works without JS).
   Row order = urgency; slot order inside = what to load first. */
.restock-groups { max-height: 26rem; overflow-y: auto; display: grid; gap: 0.35rem; }
.restock-group { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
/* The whole summary row is the tap target — a caret alone is a 12px target on
   a phone. Explicit triangle: flex summaries drop the native marker. */
.rg-head {
  display: flex; align-items: center; gap: 0.5rem;
  min-height: 2.75rem; padding: 0.4rem 0.7rem;
  background: var(--bg); cursor: pointer; list-style: none;
}
.rg-head::-webkit-details-marker { display: none; }
.rg-head::before {
  content: '▸'; color: var(--muted); font-size: 0.85em;
  transition: transform 0.15s; flex: none;
}
details[open] > .rg-head::before { transform: rotate(90deg); }
details[open] > .rg-head { border-bottom: 1px solid var(--border); }
.rg-head:hover { background: var(--border); }
.rg-name {
  font-weight: 650; font-size: 0.9rem; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1 1 auto; min-width: 4rem;
}
/* Last serviced sits at the far end whatever the name's length. */
.rg-visit { font-size: 0.72rem; color: var(--muted); white-space: nowrap; flex: none; }

/* The strip: one cell per low slot, worst first, fixed cell width so a row's
   LENGTH is that machine's low-slot count and lengths compare down the panel.
   Redundant by construction — the tally beside it says the same numbers, so
   the strip is aria-hidden and colorblind readers lose nothing. */
.sev-strip { display: flex; gap: 2px; flex: none; align-items: center; }
.sev-cell { width: 7px; height: 1.1rem; border-radius: 2px; background: var(--sev-ink); }
.sev-more { font-size: 0.68rem; color: var(--muted); margin-left: 0.15rem; }
/* The strip's text channel: glyph shape + count, in the level's ink. */
.sev-tally { display: inline-flex; gap: 0.45rem; flex: none; font-size: 0.78rem; font-weight: 700; }
.sev-tally-item {
  display: inline-flex; align-items: center; gap: 0.15rem;
  color: var(--sev-ink); font-variant-numeric: tabular-nums;
}
.sev-tally-item .sev-glyph { font-size: 0.95em; line-height: 1; }

/* An opened machine scrolls inside its own window, so a 19-slot machine can't
   push every other machine's summary out of the panel — the second half of the
   fix above (see restockGroup). */
.rg-body {
  padding: 0.5rem 0.7rem 0.6rem; background: var(--surface);
  max-height: 14rem; overflow-y: auto;
}
.rg-link {
  display: inline-block; margin-top: 0.5rem;
  font-size: 0.78rem; font-weight: 600; color: var(--accent); text-decoration: none;
}
.rg-link:hover { text-decoration: underline; }

/* Label that only assistive tech reads — the status WORD next to a count whose
   sighted channel is the glyph + ink. Not display:none, which drops it from
   the accessibility tree along with the screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- Low Inventory: the slot rows inside an expanded machine ---------- */
.rc-slots { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; }
/* Five tracks: glyph · item (flexes) · slot id · gauge · count. The gauge is a
   fixed track so bars in one card share a baseline and can be compared. */
.rc-slot {
  display: grid;
  grid-template-columns: 0.9rem minmax(0, 1fr) auto 3.25rem 1.75rem;
  align-items: center; gap: 0.4rem;
  font-size: 0.84rem;
  padding: 0.22rem 0.3rem; border-radius: 6px;
}
.rc-slot:hover { background: var(--sev-tint); }
.rc-glyph { color: var(--sev-ink); font-size: 0.85rem; line-height: 1; text-align: center; }
.rc-item { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-slotid {
  font-size: 0.72rem; color: var(--muted); font-variant-numeric: tabular-nums;
  background: var(--bg); border-radius: 4px; padding: 0.05rem 0.3rem;
}
/* Gauge: length = units left ÷ slot capacity, in the row's severity ink. It is
   redundant with the glyph and the number beside it — never the only channel.
   Slots with no capacity recorded get an empty dashed track instead of a bar
   scaled to a denominator that doesn't exist. */
.rc-gauge {
  position: relative; height: 0.4rem; border-radius: 3px;
  background: var(--bg); box-shadow: inset 0 0 0 1px var(--border);
}
.rc-gauge-unknown {
  background: repeating-linear-gradient(
    135deg, transparent 0 3px, var(--border) 3px 4px);
}
.rc-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: 3px; background: var(--sev-ink);
}
.rc-left { text-align: right; font-size: 0.85rem; }

/* ---------- Location grid (hero) ---------- */
.location-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.85rem;
  /* Cap at roughly 8 cards (more per row on wider screens) and scroll the rest. */
  max-height: 23rem;
  overflow-y: auto;
  padding-right: 0.25rem;
}
.loc-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem 1rem;
  background: #fcfdfe;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
a.loc-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
a.loc-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.loc-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.loc-name { font-weight: 600; font-size: 0.92rem; color: var(--text); overflow-wrap: anywhere; }
.loc-revenue { font-weight: 700; font-size: 1.05rem; color: var(--primary); white-space: nowrap; }
/* One hue: bar length carries relative revenue (magnitude), status colors are
   NOT used for rank — real problems belong to Low Inventory. */
.loc-bar { height: 6px; border-radius: 99px; background: var(--accent-soft); overflow: hidden; }
.loc-bar-fill { height: 100%; background: var(--accent); border-radius: 99px; width: 0; transition: width 0.4s ease; }
.loc-meta { display: flex; justify-content: space-between; gap: 0.5rem; font-size: 0.78rem; color: var(--muted); }
.loc-meta strong { color: var(--text); font-weight: 600; }
/* Street address under the figures — quieter than the units/txns line, since
   it's orientation rather than a number you read. Wraps rather than truncating:
   a half-shown address is no use when you're trying to find the place. */
.loc-addr {
  font-size: 0.72rem; color: var(--muted); opacity: 0.85;
  line-height: 1.4; overflow-wrap: anywhere;
}
.loc-share { font-variant-numeric: tabular-nums; }

/* ---------- Charts ---------- */
.trend { width: 100%; position: relative; }
.trend svg { width: 100%; display: block; overflow: visible; }
.trend-area { fill: var(--accent-soft); }
.trend-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* Second chart series (profit): the accessible yellow — pure yellow is
   unreadable on white. */
.trend-line2 { fill: none; stroke: var(--amber); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* Running average: dashed and in a third hue, so it reads as a smoothing of
   the revenue line rather than a series of its own. */
.trend-line3 {
  fill: none; stroke: var(--blue); stroke-width: 2; stroke-dasharray: 5 4;
  stroke-linejoin: round; stroke-linecap: round;
}
.chart-legend { display: flex; gap: 1rem; justify-content: flex-end; font-size: 0.78rem; color: var(--muted); padding-bottom: 0.25rem; }
.chart-legend .lg { display: inline-flex; align-items: center; gap: 0.35rem; }
.chart-legend .lg::before { content: ''; width: 1rem; height: 3px; border-radius: 2px; background: var(--accent); }
.chart-legend .lg-extra::before { background: var(--amber); }
.chart-legend .lg-avg::before { background: var(--blue); }
.chart-tip-extra { color: var(--amber); }
/* The average's row in the tooltip. A lighter tint of the dashed line's blue:
   --blue itself is 2.45:1 on the tooltip's dark background, this is 6.6:1. */
.chart-tip-avg { color: #8fbfee; }
.trend-empty { color: var(--muted); font-size: 0.85rem; padding: 1rem 0; }

/* axes + hover */
.chart-grid { stroke: var(--border); stroke-width: 1; }
/* Sunday markers on the daily trend. Dashed and border-colored so they read as
   chart furniture, never as a data series or a status — the line and the
   profit line stay the only things carrying meaning in ink. */
.chart-sunday { stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 4; }
.chart-axis { fill: var(--muted); font-size: 11px; }
.chart-axis-name { fill: var(--muted); font-size: 11px; font-weight: 600; }
.chart-guide { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-hoverdot { fill: var(--accent); stroke: #fff; stroke-width: 1.5; }
.chart-tip {
  position: absolute;
  transform: translate(-50%, -135%);
  background: var(--primary);
  color: #fff;
  border-radius: 6px;
  padding: 0.28rem 0.5rem;
  font-size: 0.74rem;
  line-height: 1.25;
  pointer-events: none;
  white-space: nowrap;
  box-shadow: var(--shadow);
  z-index: 5;
}
.chart-tip-date { opacity: 0.78; font-size: 0.66rem; }
.chart-tip-val { font-weight: 700; }

/* ---------- Tables ---------- */
.table-wrap { overflow-x: auto; }
/* Cap long tables (e.g. Profit by Location) at ~8 rows and scroll the rest;
   the sticky header below stays pinned while scrolling. */
.table-scroll { max-height: 22rem; overflow-y: auto; }
table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
th {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border-bottom: 2px solid var(--border);
  color: var(--muted);
  font-weight: 600;
  white-space: nowrap;
  position: sticky;
  top: 0;
  background: var(--surface); /* opaque so scrolled rows don't show through */
  z-index: 1;
}
td { padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); white-space: nowrap; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
tbody tr:last-child td { border-bottom: none; }
/* Location sales log / bought together: product lists wrap inside their cell
   (td is nowrap) so a long order doesn't push the table into a sideways
   scroll on a phone. */
.cell-wrap { white-space: normal; display: inline-block; min-width: 9rem; }
.sale-when > span { display: block; }
.sale-time { font-size: 0.8em; color: var(--muted); }
.pay-mark { font-size: 1.05rem; line-height: 1; }
/* A text label, not a status color: a refunded sale is information, not an alarm. */
.refund-tag {
  font-size: 0.72rem; font-weight: 600; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 0 0.4rem;
  white-space: nowrap;
}
/* Single-day sales log (home page). Time and Location are their own columns
   on a wide screen; five columns will not fit a phone, so there they drop out
   and one line under the item name carries both instead. */
.cell-link { color: inherit; text-decoration: none; font-weight: 600; }
.sale-sub { display: none; font-size: 0.8em; color: var(--muted); }
.sale-sub a { color: inherit; text-decoration: none; }
.cell-link:hover, .sale-sub a:hover { text-decoration: underline; }
tbody tr:hover { background: var(--bg); }

/* Sortable headers: the whole header is one button (padding moves off the th
   so the hit target fills the cell); the arrow appears only on the active
   column, direction = current order. */
th.sortable { padding: 0; }
.th-btn {
  display: flex; align-items: center; gap: 0.3rem; width: 100%;
  padding: 0.6rem 0.75rem; margin: 0;
  font: inherit; font-weight: 600; color: inherit; text-align: inherit;
  background: none; border: none; cursor: pointer; white-space: nowrap;
}
th.num .th-btn { justify-content: flex-end; }
.th-btn:hover { color: var(--text); }
.sort-ind { font-size: 0.62rem; line-height: 1; min-width: 0.75em; color: var(--accent); }

@media (max-width: 560px) {
  .topbar { padding: 0.75rem 1rem; }
  main { padding: 1rem; }
  .range { width: 100%; }
  .kpi-value { font-size: 1.45rem; }
  /* Header on phones: slightly smaller title/logo/back, and the title block
     claims the rest of its row so the nav pills wrap onto their own line
     together (instead of one pill clipping at the edge). */
  .topbar { gap: 0.6rem 1rem; }
  .topbar h1 { font-size: 1.1rem; }
  .brand-mark { width: 40px; height: 40px; }
  .back-btn { width: 2.5rem; height: 2.5rem; font-size: 1.2rem; }
  .topbar-brand > div { flex: 1 1 auto; }
  .nav-link { font-size: 0.88rem; padding: 0.45rem 0.7rem; }
  /* Slightly tighter tables on phones so fewer columns fall into the
     horizontal scroll. */
  table { font-size: 0.88rem; }
  td, th, .th-btn { padding: 0.5rem 0.6rem; }
  /* Sales log: four columns have to share ~310px; the product list is the one
     that can wrap. */
  .cell-wrap { min-width: 6rem; }
  /* Tables led by a name wrap the name instead of pushing the figures off
     the card. Measured at 390px: Profit by Location was 430px wide in a
     316px card and a location's Products table 398px, with Profit, Margin
     and Revenue cut off at the edge. */
  #table-items td:first-child { white-space: normal; overflow-wrap: anywhere; }
  /* Profit by Location still needed ~330px for its four headers alone, so
     on a phone the location name takes its own line and Sales / Profit /
     Margin sit in three aligned columns under it. The header row follows
     the same grid, so each figure stays under its (sortable) label. */
  #table-locprofit, #table-locprofit thead, #table-locprofit tbody { display: block; }
  #table-locprofit tr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  #table-locprofit th:first-child, #table-locprofit td:first-child {
    grid-column: 1 / -1; white-space: normal; overflow-wrap: anywhere;
  }
  #table-locprofit td:first-child { font-weight: 600; padding-bottom: 0.1rem; border-bottom: none; }
  #table-locprofit td:not(:first-child) { padding-top: 0.1rem; }
  #table-locprofit th:first-child { border-bottom: none; }
  #table-day th:nth-child(-n+2), #table-day td:nth-child(-n+2) { display: none; }
  .sale-sub { display: block; }
  /* This panel is read one-handed at the machine. Give the item name back the
     width the gauge borrowed, and keep every row a thumb-sized target. */
  .rc-slot { grid-template-columns: 0.9rem minmax(0, 1fr) auto 2.5rem 1.6rem; gap: 0.35rem; }
  /* Taller rows: these are the lines a thumb scrolls past mid-refill. */
  .rc-slot { padding: 0.3rem; }
  .sev-chip { min-height: 2.75rem; padding: 0.5rem 0.8rem; font-size: 0.86rem; }
  .wh-chip { min-height: 2.75rem; padding: 0.5rem 0.8rem; font-size: 0.86rem; }
  /* Name, tally and visit can't share a phone's width with a 14-cell strip, so
     the strip drops to its own line rather than being clipped (a clipped strip
     would understate the machine's slot count — the one thing its length
     means). Still one summary, so still one tap target. */
  .rg-head { min-height: 3rem; gap: 0.35rem 0.4rem; padding: 0.5rem 0.6rem; flex-wrap: wrap; }
  .rg-name { font-size: 0.88rem; }
  .sev-strip { order: 5; flex-basis: 100%; }
  /* Flex wraps a line BEFORE it shrinks anything, so a long machine name at its
     content width pushed "37d ago" onto its own line while a short one didn't —
     the label landed somewhere different machine to machine. A small basis lets
     name, tally and visit be placed together and the name then grows into
     whatever is left (ellipsising if it must). */
  .rg-name { flex-basis: 4rem; }
}

/* The narrowest phones (360px and below). The sales log's four columns were
   311px in a 286px card here; a little less padding and a smaller minimum
   for the wrapping item column let every table fit. */
@media (max-width: 380px) {
  td, th, .th-btn { padding: 0.5rem 0.4rem; }
  .cell-wrap { min-width: 4.5rem; }
}

/* ---------- Warehouse product list on a narrow screen ----------
   Its own breakpoint, wider than the phone one above: measured, the
   eight-column table needs a 950px window before it stops scrolling sideways,
   so a landscape phone, a tablet or a narrow laptop window gets the block
   layout too. */
@media (max-width: 1000px) {
  /* Warehouse product list. Eight columns side by side were 781px wide in a
     316px card: a phone showed the product names and nothing else, with every
     number a sideways scroll away. Here each row is a block instead — name and
     count on top, the other six figures labelled underneath in three aligned
     columns — so all of it reads without scrolling sideways, and no column is
     dropped. Same DOM as the desktop table; only the layout changes. */
  #sec-products .table-wrap { overflow-x: visible; }
  #margins-table { display: flex; flex-direction: column; }
  #margins-table thead, #margins-table tbody, #margins-table tfoot { display: block; }
  /* Totals first: they carry the 7D/30D/90D control, which would otherwise sit
     under every product on a long list. */
  #margins-table tfoot { order: -1; }
  #margins-table td {
    display: block; padding: 0; border: none; min-width: 0;
    white-space: normal; text-align: left; font-size: 0.84rem;
  }
  #margins-table .margin-row, #margins-table .margins-total {
    display: grid; grid-template-columns: 0.78fr 1.22fr 1fr;
    grid-template-areas: "name name stock" "sold revenue portion" "margin profit cost";
    gap: 0.2rem 0.45rem; padding: 0.7rem 0.15rem; border-bottom: 1px solid var(--border);
  }
  #margins-table .margins-total { border-bottom-width: 2px; }
  #margins-table .margin-row > td:nth-child(1), #margins-table .margins-total > td:nth-child(1) {
    grid-area: name; font-size: 0.95rem; overflow-wrap: anywhere;
  }
  #margins-table .margin-row > td:nth-child(2), #margins-table .margins-total > td:nth-child(2) {
    grid-area: stock; text-align: right; font-size: 1.05rem; white-space: nowrap;
  }
  #margins-table .margin-row > td:nth-child(2)::after, #margins-table .margins-total > td:nth-child(2)::after {
    content: ' on hand'; font-size: 0.7rem; font-weight: 500; color: var(--muted);
  }
  /* An untracked product has no count to label. */
  #margins-table .margin-row > td.wh-cell-none::after { content: none; }
  #margins-table td:nth-child(3) { grid-area: cost; }
  #margins-table td:nth-child(4) { grid-area: sold; }
  #margins-table td:nth-child(5) { grid-area: revenue; }
  #margins-table td:nth-child(6) { grid-area: portion; }
  #margins-table td:nth-child(7) { grid-area: profit; }
  #margins-table td:nth-child(8) { grid-area: margin; }
  #margins-table td[data-label]::before {
    content: attr(data-label) ' '; font-size: 0.7rem; font-weight: 500; color: var(--muted);
  }
  #margins-table .margin-row.open { background: var(--bg); }
  /* The editor row: a plain full-width block, so its controls wrap inside the
     card instead of running off the edge of a wide table. */
  #margins-table .wh-editor-row { display: block; border-bottom: 1px solid var(--border); }
  #margins-table .wh-editor-row > td { padding: 0.5rem 0.15rem 0.9rem; font-size: 0.9rem; }
  /* The header row has no columns to head here; it stays as the sort control.
     Outlined buttons, four to a row: as bare words in a row the eight headers
     ran together and read as one scrunched line. Four share a row evenly, but
     a button never gets narrower than its own text, so the sort arrow always
     has room. */
  #margins-table thead tr {
    display: flex; flex-wrap: wrap; gap: 0.35rem;
    padding: 0.65rem 0 0.75rem; border-bottom: 2px solid var(--border);
  }
  #margins-table thead tr::before {
    content: 'Sort by'; flex: 0 0 100%; font-size: 0.68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
  }
  #margins-table th { display: block; flex: 1 1 22%; position: static; border: none; padding: 0; }
  #margins-table .th-btn {
    width: 100%; min-height: 2.5rem; padding: 0.3rem 0.3rem; justify-content: center;
    font-size: 0.78rem;
    border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
  }
  /* No arrow, no reserved room for one: the label centres in its button. */
  #margins-table .th-btn { gap: 0; }
  #margins-table .sort-ind { min-width: 0; }
  #margins-table .sort-ind:not(:empty) { margin-left: 0.25rem; }
  #margins-table th[aria-sort="ascending"] .th-btn, #margins-table th[aria-sort="descending"] .th-btn {
    background: var(--primary); border-color: var(--primary); color: #fff;
  }
  #margins-table th:not([aria-sort="none"]) .sort-ind { color: #fff; }
  .margins-range .preset { padding: 0.45rem 0.8rem; font-size: 0.86rem; }
}

/* ---------- Sign-in pages (/login, /login/verify) ---------- */
.login-main { max-width: 26rem; margin: 2.5rem auto; padding: 0 1rem; }
.login-card { padding: 1.5rem 1.25rem; }
.login-card h2 { margin: 0 0 0.4rem; font-size: 1.3rem; }
.login-lead { margin: 0 0 1.1rem; color: var(--muted); line-height: 1.5; }
.login-label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.35rem; }
.login-input {
  width: 100%; font: inherit; font-size: 1rem; padding: 0.7rem 0.75rem;
  border: 1px solid var(--border); border-radius: 8px; color: var(--text); background: var(--surface);
}
.login-input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.login-btn { width: 100%; margin-top: 0.9rem; padding: 0.75rem 1rem; font-size: 1rem; }
.login-msg { margin: 0.9rem 0 0; line-height: 1.5; color: var(--muted); }
.login-msg:empty { display: none; }
.login-msg.ok { color: var(--text); }
.login-msg.err { color: var(--red); }
.login-alt { margin: 1rem 0 0; font-size: 0.9rem; }
.login-alt a { color: var(--blue); font-weight: 600; }
