@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');

/* ============================================================================
   LogicLink — Expo design system
   Spec: VoltAgent/awesome-design-md › design-md/expo/DESIGN.md

   Principles enforced below:
   - "Pure black pill is the singular CTA treatment" — #000 is spent only on
     primary actions; everything else is white with a hairline.
   - Text-link blue (#0d74ce) is reserved for inline links, nothing else.
   - Chrome is white; the canvas behind cards is #fafafa. Cards carry a 1px
     #dcdee0 border and take a soft drop shadow only on hover.
   - Uppercase IS part of this system: Caption Uppercase is Inter 11px/600 with
     +0.88px tracking, used for table headers, eyebrow labels and badge pills.
   - Negative tracking applies to display sizes only; titles and body sit at 0.
   ========================================================================== */

:root {
  /* Primary */
  --primary: #000000;
  --primary-active: #1a1a1a;
  --on-primary: #ffffff;

  /* Text. Expo's Muted #999999 only reaches 2.85:1 on white, which is why the
     11px uppercase labels read as haze — --muted is darkened to clear AA. */
  --ink: #171717;           /* headings, body-strong */
  --body: #565b64;          /* secondary text — 6.83:1 */
  --muted: #6e747d;         /* labels, captions, table headers — 4.71:1 */
  --muted-soft: #a8adb5;    /* disabled, decorative */

  /* Surfaces — a real elevation ladder. Expo's #fafafa canvas sits 1.04:1 from a
     white card, so cards had no edge to stand on; the page drops to #edeff3 and
     white cards float above it, with #f4f5f7 for regions inset INSIDE a card. */
  --canvas: #ffffff;             /* cards, chrome */
  --canvas-page: #e5e9f0;        /* page background — 1.22:1 under a white card */
  --canvas-soft: #f0f2f6;        /* inset: table heads, filter bars, side panes */
  --surface-strong: #e2e6ec;     /* secondary fills, active nav, badges */
  --surface-dark: #171717;
  --surface-dark-elevated: #1a1a1a;

  /* Hairlines. --hairline-strong has to read against BOTH the white card it
     bounds and the darker canvas behind it, so it sits between the two. */
  --hairline: #dfe3ea;
  --hairline-soft: #eef1f5;
  --hairline-strong: #c8ced8;   /* card + input borders */

  /* Links */
  --link: #0d74ce;
  --link-2: #476cff;
  --link-bright: #47c2ff;

  /* Semantic. The spec ships Success #16a34a, Warning #ab6400 and a pale
     Error accent #eb8e90; the deeper error/warning text tones and the soft
     tint backgrounds are derived from those so pills stay readable. */
  --success: #16a34a;
  --success-deep: #0f7536;
  --success-soft: #d5efdf;
  --warning: #ab6400;
  --warning-deep: #7d4900;
  --warning-soft: #fbe8c8;
  --error: #eb8e90;         /* spec accent — borders/dots */
  --error-text: #a4262a;    /* derived, readable */
  --error-bright: #d13438;
  --error-soft: #fbdcdc;
  --info: #0d74ce;
  --info-deep: #0a5399;
  --info-soft: #d3e6fa;
  --preview: #8145b5;
  --preview-deep: #6b3597;
  --preview-soft: #ebdcf7;

  /* Atmospheric */
  --sky-light: #cfe7ff;
  --sky-mid: #a8c8e8;

  /* Radius */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;              /* buttons, inputs — the base UI radius */
  --r-lg: 12px;             /* cards */
  --r-xl: 16px;
  --r-full: 9999px;

  /* Shadow. Expo ships one hover drop; a resting shadow is added so cards lift
     off the canvas instead of relying on the border alone. */
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
  --shadow-soft: 0 4px 12px rgba(16, 24, 40, 0.08);

  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Caption Uppercase — Inter 11px/600, +0.88px. The system's label voice. */
.kpi-card .label,
.detail-grid .label,
.pane-title,
.store-picker-list .picker-group,
th {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.88px;
  text-transform: uppercase;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  background: var(--canvas-page);
  color: var(--body);
  display: flex;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Sidebar — main 4 layers. White chrome, hairline separation; the black is
   saved for CTAs, so the active row reads through ink text + a soft fill. */
.sidebar {
  width: 240px;
  background: var(--canvas);
  color: var(--body);
  border-right: 1px solid var(--hairline-strong);
  padding: 20px 0;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}
.brand {
  padding: 0 20px 18px;
  border-bottom: 1px solid var(--hairline);
  font-size: 22px;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.5px;
  color: var(--ink);
}
.brand a { color: inherit; text-decoration: none; }
.brand span { color: var(--muted); }
/* min-height:0 + overflow-y let the tab list scroll INSIDE the sidebar. Without it a layer with
   every section expanded simply overflowed the viewport (the sidebar itself doesn't scroll), which is
   what used to bury the account block at the bottom. */
.layer-list {
  list-style: none; margin-top: 12px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  scrollbar-width: thin; scrollbar-color: var(--hairline-strong) transparent;
}
.layer-list::-webkit-scrollbar { width: 8px; }
.layer-list::-webkit-scrollbar-thumb { background: var(--hairline-strong); border-radius: var(--r-xs); }
.layer-list::-webkit-scrollbar-track { background: transparent; }
.layer-item > .layer-row {
  margin: 0 8px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  color: var(--body);
  transition: background .15s, color .15s;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.layer-item > .layer-row:hover { background: var(--hairline-soft); color: var(--ink); }
.layer-item.active > .layer-row {
  background: var(--surface-strong);
  color: var(--ink);
  font-weight: 600;
}
/* Icons are inline SVG (LAYER_ICONS in js/sidebar.js) inheriting currentColor, so they
   pick up the row's hover/active colour automatically. The flex box keeps every glyph on
   the same 18px optical rail regardless of its individual path bounds. */
.layer-row .icon {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-right: 10px;
  color: var(--muted);
  transition: color .15s;
}
.layer-row .icon svg { width: 18px; height: 18px; display: block; }
.layer-item > .layer-row:hover .icon,
.layer-item.active > .layer-row .icon { color: var(--ink); }
.layer-row .left { display: flex; align-items: center; min-width: 0; }
.layer-row .chev {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px;
  color: var(--muted-soft);
  transition: transform .15s, color .15s;
}
.layer-row .chev svg { width: 14px; height: 14px; display: block; }
.layer-item > .layer-row:hover .chev { color: var(--muted); }
.layer-item.expanded .chev { transform: rotate(90deg); }

/* Nested sub-tab menu */
.sub-list {
  list-style: none;
  display: none;
  margin: 2px 8px 6px 22px;
  padding-left: 10px;
  border-left: 1px solid var(--hairline-strong);
}
.layer-item.expanded .sub-list { display: block; }
.sub-list li { padding: 0; }
.sub-list a {
  display: block;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  font-size: 14px;
  font-weight: 500;
  color: var(--body);
  text-decoration: none;
  transition: background .15s, color .15s;
}
.sub-list a:hover { background: var(--hairline-soft); color: var(--ink); }
.sub-list a.active { color: var(--ink); background: var(--surface-strong); font-weight: 600; }

/* Main */
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.topbar {
  background: var(--canvas);
  height: 64px;
  padding: 0 24px;
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
}
/* The page title truncates before the identity block does — min-width:0 is what lets a flex item
   shrink below its content so the ellipsis can actually kick in. */
.topbar h1 {
  font-size: 22px; line-height: 1.25; font-weight: 600; letter-spacing: -0.5px;
  color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar .left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar .user {
  background: var(--primary);
  color: var(--on-primary);
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  font-weight: 500;
  font-size: 13px;
  flex-shrink: 0;
}
/* Who's signed in, as text beside the avatar (initTopbar in js/auth.js). Right-aligned and allowed
   to ellipsize so a long email can never push the page title around. */
.tb-id {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  flex-shrink: 0;          /* the page title gives way first (see .topbar h1) */
  min-width: 0;
  max-width: 240px;
  line-height: 1.3;
  text-align: right;
}
.tb-id-name { font-size: 13px; font-weight: 600; color: var(--ink); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-id-email { font-size: 12px; color: var(--muted); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------------- Notification bell (topbar) ---------------- */
.notif-wrap { position: relative; margin-left: auto; flex-shrink: 0; }
.notif-bell {
  position: relative;
  width: 36px; height: 36px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  background: var(--canvas); cursor: pointer; font-size: 15px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink);
  transition: background .15s, border-color .15s, box-shadow .15s;
}
.notif-bell:hover { background: var(--canvas-soft); box-shadow: var(--shadow-soft); }
.notif-badge {
  position: absolute; top: -6px; right: -6px;
  min-width: 18px; height: 18px; padding: 0 4px; box-sizing: border-box;
  background: var(--error-bright); color: var(--on-primary);
  border: 2px solid var(--canvas);
  border-radius: var(--r-full); font-size: 10px; font-weight: 600;
  display: flex; align-items: center; justify-content: center;
}
/* The `display:flex` above overrides the UA `[hidden]{display:none}`, so hiding via the hidden
   attribute (renderBadge) needs this explicit rule or a 0-count badge stays on screen. */
.notif-badge[hidden] { display: none; }
.notif-panel {
  position: absolute; top: 46px; right: 0; z-index: 60;
  width: 360px; max-width: calc(100vw - 32px);
  background: var(--canvas); border: 1px solid var(--hairline-strong); border-radius: var(--r-lg);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.10);
  overflow: hidden;
}
.notif-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--hairline);
}
.notif-head strong { font-size: 16px; font-weight: 600; color: var(--ink); }
.notif-head-actions { display: flex; gap: 10px; }
.notif-head-actions button {
  background: none; border: none; color: var(--link); cursor: pointer;
  font-size: 13px; font-weight: 500; font-family: inherit; padding: 0;
}
.notif-head-actions button:hover { text-decoration: underline; }
.notif-list { max-height: 420px; overflow-y: auto; overflow-x: hidden; }
.notif-empty { padding: 28px 16px; text-align: center; color: var(--muted); font-size: 14px; }
.notif-row {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 12px 16px; border-bottom: 1px solid var(--hairline);
  cursor: pointer;
}
.notif-row:hover { background: var(--canvas-soft); }
.notif-row.unread { background: var(--hairline-soft); }
.notif-row.unread:hover { background: var(--surface-strong); }
.notif-row-main { flex: 1; min-width: 0; }
.notif-row-title { font-size: 14px; font-weight: 600; color: var(--ink); }
/* Free-text bodies (announcements) can run long, so the preview is clamped to 3 lines — the row
   then shows "Read full message" and opens the whole text in a dialog (js/notifications.js). */
.notif-row-body {
  font-size: 13px; color: var(--body); margin-top: 2px; line-height: 1.5;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden; white-space: pre-line; overflow-wrap: anywhere;
}
.notif-row-more { font-size: 12px; font-weight: 500; color: var(--link); margin-top: 3px; }
.notif-row-time { font-size: 12px; color: var(--muted); margin-top: 4px; }
.notif-row-x {
  background: none; border: none; color: var(--muted-soft); cursor: pointer;
  font-size: 17px; line-height: 1; padding: 0 2px; flex-shrink: 0;
}
.notif-row-x:hover { color: var(--error-bright); }
.hamburger {
  display: none;
  background: var(--canvas);
  border: 1px solid var(--hairline-strong);
  width: 36px; height: 36px;
  border-radius: var(--r-md);
  cursor: pointer;
  color: var(--ink);
  font-size: 16px;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.hamburger:hover { background: var(--canvas-soft); box-shadow: var(--shadow-soft); }
.sidebar-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(23, 23, 23, 0.4);
  z-index: 40;
}

/* Content area */
.content { padding: 24px; overflow-y: auto; flex: 1; background: var(--canvas-page); }

/* KPI cards */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}
/* Four identical white boxes read as one block, so each card takes a 3px accent
   on top. The cycle is positional (not semantic) — it exists to give the row
   rhythm and let the eye land on a specific figure. */
.kpi-card {
  position: relative;
  background: var(--canvas);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  padding: 20px 24px;
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: box-shadow .15s, transform .15s;
}
.kpi-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--info);
}
.kpi-card:nth-child(4n+2)::before { background: var(--success); }
.kpi-card:nth-child(4n+3)::before { background: var(--warning); }
.kpi-card:nth-child(4n+4)::before { background: var(--preview); }
.kpi-card:hover { box-shadow: var(--shadow-soft); transform: translateY(-1px); }
.kpi-card .label { color: var(--muted); }
.kpi-card .value {
  font-size: 28px; line-height: 1.2; font-weight: 600; letter-spacing: -0.84px;
  color: var(--ink); margin-top: 8px;
}
.kpi-card .delta { font-size: 13px; margin-top: 4px; }
.kpi-card .delta.up { color: var(--success); }
.kpi-card .delta.down { color: var(--error-text); }
/* A KPI that links to the list its number came from. It keeps the card's own hover lift and adds a
   quiet arrow in the label row — the arrow, not the cursor, is what tells you before you hover. */
a.kpi-card.is-link { display: block; text-decoration: none; color: inherit; }
.kpi-card .label { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.kpi-go { font-size: 13px; color: var(--muted-soft); transition: color .15s, transform .15s; }
a.kpi-card.is-link:hover .kpi-go { color: var(--link); transform: translateX(2px); }
a.kpi-card.is-link:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

/* ============================ Charts ============================
   Order pipeline bar (js/ui.js › orderPipeline). The data is the only loud thing
   here: the bar is thin, the chrome is hairlines, and every label wears a TEXT
   token rather than the colour of the mark beside it. See the comment block above
   orderPipeline() for why the stages take one stepped hue and the off-pipeline
   states take the app's status pills instead. */
.chart-card .chart-body { padding: 20px 24px 18px; }

/* The 2px gap IS the separator between segments — the card's white shows through it.
   Nothing gets a border: a stroke around a mark adds ink that isn't data. */
.pipe-bar {
  display: flex;
  gap: 2px;
  height: 24px;               /* mark cap — also the minimum comfortable hit height */
  width: 100%;
}
.pipe-seg {
  flex: 1 1 0;
  min-width: 4px;             /* a 1-order stage stays visible and clickable */
  border: 0;
  padding: 0;
  border-radius: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity .15s, outline-color .15s;
}
/* Square at the baseline (left), 4px rounded at the data end (right). */
.pipe-bar .pipe-seg:last-child { border-radius: 0 4px 4px 0; }
.pipe-seg:hover { opacity: .85; }
/* Selection ring — an interaction state, not a separator, so it sits OUTSIDE the mark. */
.pipe-seg.is-active { outline: 2px solid var(--ink); outline-offset: 2px; }
.pipe-seg-label {
  font-size: 11px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* Legend — always present, and the dependable hit target for a thin segment. */
.pipe-legend {
  display: flex; flex-wrap: wrap; gap: 4px 18px;
  margin-top: 14px;
}
.pipe-legend-item {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 0; padding: 4px 6px; margin: 0 -6px;
  border-radius: var(--r-sm);
  cursor: pointer; font-size: 12.5px; line-height: 1.4;
  color: var(--body);
}
.pipe-legend-item:hover { background: var(--canvas-soft); }
.pipe-legend-item.is-active { background: var(--surface-strong); color: var(--ink); }
.pipe-key { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.pipe-legend-label { color: var(--body); }
.pipe-legend-item.is-active .pipe-legend-label { font-weight: 600; color: var(--ink); }
.pipe-legend-value { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.pipe-legend-pct { color: var(--muted); font-variant-numeric: tabular-nums; }

.pipe-total { font-size: 12.5px; color: var(--muted); }
.pipe-none { font-size: 13px; color: var(--muted); padding: 4px 0 2px; }

/* Off-pipeline states. Separated by a hairline so they read as a footnote to the bar,
   not another segment of it. */
.pipe-exceptions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--hairline-soft);
}
.pipe-exceptions-label {
  font-size: 11px; font-weight: 600; letter-spacing: .88px; text-transform: uppercase;
  color: var(--muted); margin-right: 2px;
}
.pipe-exc { border: 0; cursor: pointer; font-family: inherit; }
.pipe-exc b { font-weight: 700; font-variant-numeric: tabular-nums; }
.pipe-exc:hover { filter: brightness(.96); }
.pipe-exc.is-active { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Ranked horizontal bars (js/ui.js › magnitudeBars). Label · track · value at the tip —
   no gridlines and no axis, because the value rides the bar it belongs to. */
.mbar-list { display: flex; flex-direction: column; gap: 6px; }
.mbar-row {
  display: grid;
  grid-template-columns: minmax(90px, 210px) 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 28px;            /* comfortable hit target — the bar itself is thinner */
  padding: 3px 6px;
  margin: 0 -6px;
  background: none; border: 0; border-radius: var(--r-sm);
  font-family: inherit; font-size: 13px; text-align: left;
  color: var(--body);
}
button.mbar-row { cursor: pointer; }
button.mbar-row:hover { background: var(--canvas-soft); }
.mbar-row.is-active { background: var(--surface-strong); }
.mbar-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--body);
}
.mbar-row.is-active .mbar-label { font-weight: 600; color: var(--ink); }
/* The track is layout only — deliberately NOT tinted. A filled-vs-unfilled track reads as a meter
   ("this warehouse is 30% full"), and on a WAREHOUSE chart that misreading is one a viewer would
   actually make. The remainder is just the gap to the largest warehouse, not free capacity, so
   there is nothing there to draw. */
.mbar-track { height: 14px; }
.mbar-fill {
  display: block; height: 100%;
  border-radius: 0 4px 4px 0;   /* square at the baseline, rounded at the data end */
  min-width: 2px;
}
.mbar-value {
  font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
  min-width: 2.5em; text-align: right;
}
/* Bars embedded in a card that already has its own heading (the stock detail's warehouse card).
   They sit above that card's table, separated by a hairline rather than a second card edge. */
.mbar-bare {
  padding: 14px 20px 16px;
  border-bottom: 1px solid var(--hairline-soft);
}
.mbar-bare .mbar-row { font-size: 12.5px; }

/* Monthly columns (js/ui.js › monthlyBars). Two grid rows: [gutter | columns] over [empty | month
   labels]. Both rows share the same flex settings, which is what keeps a label under its column. */
.tbar-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 10px;
}
/* The unit, written ONCE at the head of the y axis so the ticks below can stay bare numbers. */
.tbar-yunit {
  margin-bottom: 4px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px;
  color: var(--muted);
}
.tbar-y {
  display: flex; flex-direction: column; justify-content: space-between;
  text-align: right;
  font-size: 10.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Nudged half a line so each label sits ON its rule rather than under it. */
.tbar-y span:first-child { margin-top: -6px; }
.tbar-y span:last-child { margin-bottom: -6px; color: var(--muted-soft); }
.tbar-cols, .tbar-xs { display: flex; gap: 2px; }
.tbar-cols {
  position: relative;
  align-items: flex-end;
  border-bottom: 1px solid var(--hairline-strong);   /* zero — the line that matters */
  border-top: 1px dashed var(--hairline-soft);       /* the top of the scale */
}
/* Midpoint rule. Recessive on purpose: it exists to let you read a column's height, not to be seen,
   so it sits BEHIND the columns rather than ruled across them. */
.tbar-mid {
  position: absolute; left: 0; right: 0; top: 50%;
  border-top: 1px dashed var(--hairline-soft);
  z-index: 0;
}
.tbar-col {
  position: relative; z-index: 1;
  flex: 1 1 0; min-width: 0; height: 100%;
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0 2px;
}
.tbar-fill {
  width: 100%; max-width: 30px; min-height: 1px;
  border-radius: 3px 3px 0 0;   /* rounded at the data end, square on the baseline */
}
.tbar-col:hover .tbar-fill { opacity: .78; }
/* A column that drills into its month. The hit area is the whole column, not just the painted bar,
   so a thin month is as easy to hit as a tall one. */
a.tbar-col.is-link { text-decoration: none; cursor: pointer; }
a.tbar-col.is-link:hover { background: var(--canvas-soft); }
a.tbar-col.is-link:focus-visible { outline: 2px solid var(--link); outline-offset: -2px; }
.tbar-x {
  flex: 1 1 0; min-width: 0;
  padding-top: 6px;
  font-size: 10.5px; color: var(--muted); text-align: center;
  white-space: nowrap; overflow: hidden;
}
/* January prints its year on a second line so the axis is unambiguous without repeating it 12×. */
.tbar-x i { display: block; font-style: normal; font-size: 9.5px; color: var(--muted-soft); }
.tbar-note { margin-top: 12px; font-size: 11.5px; color: var(--muted); }

@media (max-width: 720px) {
  .chart-card .chart-body { padding: 16px; }
  .pipe-legend { gap: 4px 14px; }
  .mbar-row { grid-template-columns: minmax(72px, 110px) 1fr auto; gap: 8px; }
  .tbar-x { font-size: 9.5px; }
  .tbar-fill { max-width: 18px; }
}

/* Card */
.card {
  background: var(--canvas);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  margin-bottom: 24px;
  overflow: hidden;
  box-shadow: var(--shadow-card);
}
.card-header {
  padding: 16px 24px;
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.card-header h2 { font-size: 16px; line-height: 1.4; font-weight: 600; color: var(--ink); }
/* align-items matters here: the row also carries non-button items (the "View only" pill on
   the read-only pages). Left to stretch, a pill inflates to the 40px button height and its
   text sticks to the top of a tall rounded box instead of sitting in a small badge. */
.card-header .actions { display: flex; align-items: center; gap: 8px; }
.card-header .actions button {
  background: var(--primary);
  color: var(--on-primary);
  border: 1px solid var(--primary);
  height: 40px;
  padding: 0 18px;
  border-radius: var(--r-md);
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  font-family: inherit;
  transition: background .15s, border-color .15s;
}
.card-header .actions button:hover { background: var(--primary-active); border-color: var(--primary-active); }
/* A card header's default action is the black primary; secondary actions (Refresh, Export)
   sit beside it in the outline treatment. Needs this specificity to beat the rule above. */
.card-header .actions button.btn-secondary {
  background: var(--canvas); color: var(--ink); border-color: var(--hairline-strong);
}
.card-header .actions button.btn-secondary:hover {
  background: var(--canvas-soft); border-color: var(--hairline-strong); box-shadow: var(--shadow-soft);
}

.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* No min-width: the table should fit its container and only scroll when the content
   genuinely can't compress further (narrow screens), not by default. */
table { width: 100%; border-collapse: collapse; }
thead { background: var(--canvas-soft); }
/* Every column TITLE is centred; the data below keeps the reading convention — text from
   the left, numerals right-aligned by the cell that emits them, actions centred.
   vertical-align:middle is kept so a two-line cell doesn't ride high against its row. */
/* Cells WRAP by default. Forcing nowrap on every cell is what pushed wide lists (orders,
   stock) into horizontal scroll: one long distributor name or product summary set the
   column width for the whole table. Things that must never break mid-value — numerals,
   dates, status pills, action clusters — opt back into nowrap below. */
th, td {
  text-align: left;
  vertical-align: middle;
  padding: 12px;
  border-bottom: 1px solid var(--hairline);
  white-space: normal;
  overflow-wrap: anywhere;
}
th { white-space: nowrap; }                 /* titles are short; keep them on one line */
td.col-right, td.col-center { white-space: nowrap; }   /* numerals and action clusters */
td.col-nowrap { white-space: nowrap; }                 /* dates, codes — opt-in per column */
td .pill { white-space: nowrap; }
th { color: var(--muted); }        /* Caption Uppercase — see the block at the top */
/* Alignment classes apply to CELLS only — every column title stays left-aligned. */
td.col-left { text-align: left; }
td.col-right { text-align: right; }
td.col-center { text-align: center; }
td { font-size: 14px; color: var(--body); }
tbody tr:hover { background: var(--canvas-soft); }

/* ---------------- Table pagination (paginationBar in js/ui.js) ---------------- */
.pager {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 16px;
  border-top: 1px solid var(--hairline);
  background: var(--canvas-soft);
  font-size: 13px; color: var(--body);
}
.pager-info strong { color: var(--ink); font-weight: 600; }
.pager-nav { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.pager-page { color: var(--muted); white-space: nowrap; }
.pager-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  background: var(--canvas); color: var(--ink);
  font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer;
  transition: background .15s, border-color .15s;
}
.pager-btn:hover:not(:disabled) { background: var(--canvas); border-color: var(--muted); box-shadow: var(--shadow-soft); }
.pager-btn:disabled { color: var(--muted-soft); cursor: not-allowed; background: var(--surface-strong); border-color: var(--hairline); }
.pager-size {
  height: 32px; padding: 0 30px 0 10px;   /* right room for the native arrow (data-no-search) */
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  background: var(--canvas); color: var(--ink);
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.pager-size:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07); }
@media (max-width: 600px) {
  .pager { gap: 10px; }
  .pager-nav { margin-left: 0; }
  .pager-info { width: 100%; }
}

/* Status pills — Badge Pill shape (11px/600 uppercase, full radius) carrying
   the semantic tints so a status still reads at a glance. */
.pill {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--r-full);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.88px;
  text-transform: uppercase;
  background: var(--surface-strong);
  color: var(--ink);
}
.pill.ok { background: var(--success-soft); color: var(--success-deep); }
.pill.warn { background: var(--warning-soft); color: var(--warning-deep); }
.pill.bad { background: var(--error-soft); color: var(--error-text); }
.pill.info { background: var(--info-soft); color: var(--info-deep); }
.pill.muted { background: var(--surface-strong); color: var(--body); }
/* Keeps the pill shape but not the uppercasing — for pills carrying content the user typed
   (a role name), where forcing caps would misrepresent what they entered. */
.pill.as-is { text-transform: none; letter-spacing: 0; font-size: 12px; font-weight: 500; }

/* Inline help marker (the on-hand / on-reserve / available explainer on the stock
   pages). A stroked glyph rather than a filled grey disc with a letter in it — it sits
   beside uppercase column headers, so it has to read as punctuation, not as a badge.
   Focusable so the tooltip is reachable without a mouse. */
.info-tip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 5px;
  color: var(--muted-soft); cursor: help;
  vertical-align: -2px;
  transition: color .15s;
}
.info-tip svg { width: 14px; height: 14px; display: block; }
.info-tip:hover, .info-tip:focus-visible { color: var(--ink); outline: none; }

/* Per-row action cluster in a table's last column. nowrap is the point: these used to
   carry flex-wrap and broke 3-above-1-below whenever the column tightened. */
/* flex-end, not text-align — these are flex items, so text-align on the cell has no say. */
.row-actions { display: flex; gap: 4px; justify-content: flex-end; flex-wrap: nowrap; }
/* Square icon buttons for the mutating actions — five text buttons made this column so
   wide the whole table had to scroll sideways. Each carries title + aria-label, and the
   semantic tint still says what it does at a glance. */
.row-act.is-icon { width: 28px; padding: 0; justify-content: center; }
.row-act svg { width: 15px; height: 15px; display: block; }
.row-act {
  display: inline-flex; align-items: center;
  height: 28px; padding: 0 11px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  font-family: inherit; font-size: 12px; font-weight: 500; line-height: 1;
  white-space: nowrap; cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.row-act.act-in    { background: var(--success-soft); color: var(--success-deep); }
.row-act.act-in:hover    { background: #c4e8d3; }
.row-act.act-move  { background: var(--preview-soft); color: var(--preview-deep); }
.row-act.act-move:hover  { background: #e0cbf3; }
.row-act.act-out   { background: var(--error-soft);   color: var(--error-text); }
.row-act.act-out:hover   { background: #f7cccc; }
/* "Set Price" on an unpriced item — amber flags that the row still needs attention. */
.row-act.act-warn  { background: var(--warning-soft); color: var(--warning-deep); }
.row-act.act-warn:hover  { background: #f8dcae; }
/* Neutral action — e.g. rejecting a return, which just puts the order back, so it should
   not read as destructive the way the red variants do. */
.row-act.act-muted { background: var(--surface-strong); color: var(--body); }
.row-act.act-muted:hover { background: var(--hairline-strong); }
/* Forward step in a workflow — advancing a shipment to its next state. */
.row-act.act-info { background: var(--info-soft); color: var(--info-deep); }
.row-act.act-info:hover { background: #c2ddf7; }
/* View is the row's primary, but 20 solid-black buttons down a table would spend the
   one treatment reserved for real CTAs — so it reads as a bordered secondary. */
.row-act.act-view  { background: var(--canvas); color: var(--ink); border-color: var(--hairline-strong); }
.row-act.act-view:hover  { background: var(--canvas-soft); border-color: var(--muted); }
/* A caption riding alongside the buttons — the buyer pages' "2 days left" return-window
   countdown next to Return. Aligns with the 28px buttons rather than sitting on the baseline. */
.row-act-note {
  display: inline-flex; align-items: center; height: 28px;
  font-size: 11px; color: var(--muted); white-space: nowrap;
}

/* ---------------- Per-row quantity entry (order/request builders) ----------------
   The narrow number boxes in a "Request Qty" cell — one per packing level. Too small for the
   44px .form-row treatment, but they were the last inputs still carrying their own hardcoded
   border and grey, so they drifted from everything else. */
.qty-cell { display: flex; gap: 5px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
.qty-cell .qty-unit { font-size: 11px; color: var(--muted); }
.qty-box {
  width: 58px; height: 32px; padding: 0 8px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  text-align: right; font-family: inherit; font-size: 13px; color: var(--ink);
  background: var(--canvas);
  transition: border-color .15s, box-shadow .15s;
}
.qty-box:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07); }
.qty-box:disabled { background: var(--surface-strong); color: var(--muted); cursor: not-allowed; }
/* Asking for more than the seller has — the figure is still submittable-looking, so the box
   itself has to carry the warning. */
.qty-box.is-over { border-color: var(--error-bright); }
.qty-note { font-size: 11px; margin-top: 3px; }
.qty-note.is-over { color: var(--error-bright); }
.qty-note.is-muted { color: var(--body); }

.empty {
  text-align: center;
  padding: 64px 24px;
  color: var(--muted);
}
.empty h3 {
  font-size: 22px; line-height: 1.25; font-weight: 600; letter-spacing: -0.5px;
  margin-bottom: 8px; color: var(--ink);
}
.empty p { font-size: 14px; }

/* Modal */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(23, 23, 23, 0.45);
  display: none;
  align-items: center; justify-content: center;
  z-index: 100;
}
.modal-backdrop.open { display: flex; }
.modal {
  background: var(--canvas);
  width: 560px; max-width: 92vw;
  border-radius: var(--r-lg);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.14);
  overflow: hidden;
}
.modal-header {
  padding: 20px 24px;
  border-bottom: 1px solid var(--hairline);
  display: flex; justify-content: space-between; align-items: center;
}
.modal-header h2 { font-size: 22px; line-height: 1.25; font-weight: 600; letter-spacing: -0.5px; color: var(--ink); }
.modal-header .close {
  background: none; border: none; font-size: 22px; cursor: pointer;
  color: var(--muted); line-height: 1; font-family: inherit;
}
.modal-header .close:hover { color: var(--ink); }
.modal-body { padding: 24px; max-height: 70vh; overflow-y: auto; overflow-x: hidden; }
.modal-footer {
  padding: 16px 24px;
  border-top: 1px solid var(--hairline);
  display: flex; justify-content: flex-end; gap: 12px;
}

/* A LIST inside a dialog body (e.g. Roles → "N users").
   The body is padded 24px, which is right for prose and form rows but leaves a table
   floating in a box instead of reading like every other table in the app. These pull the
   bar and the table back out to the modal's own edges so a dialog list looks like a card
   list — a filter bar under the header, then rows — and reuse .filter-bar / table styling
   rather than restating colours inline. */
.dlg-bar { margin: -24px -24px 0; }                    /* a .filter-bar at the top of a body */
.dlg-list {
  margin: 0 -24px -24px;
  max-height: 340px; overflow: auto;
}
.dlg-list th, .dlg-list td { padding: 10px 24px; }
/* The head stays put while the rows scroll — a long list otherwise loses its column titles. */
.dlg-list thead th { position: sticky; top: 0; z-index: 1; background: var(--canvas-soft); }
.dlg-list tbody tr:last-child td { border-bottom: none; }
.dlg-empty { padding: 32px 24px; text-align: center; color: var(--muted); font-size: 14px; }
/* Muted body copy in a dialog (the "nothing here yet" line). */
.dlg-note { margin: 0; color: var(--body); font-size: 14px; line-height: 1.6; }

/* A button that reads as a link — used inside table cells to drill into a related record
   (the Roles list's user count). Styled here so cells don't carry inline colours. */
.link-btn {
  background: none; border: none; padding: 0;
  font-family: inherit; font-size: 13px;
  color: var(--link); cursor: pointer; text-decoration: underline;
}
.link-btn:hover { color: var(--info-deep); }

.form-row { margin-bottom: 16px; }
.form-row.two { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* Side-by-side fields line up on their INPUTS, not their labels. A longer label (e.g.
   "Email (also used for Driver App login)") wraps to two lines in a narrow column, which
   otherwise pushes its input below its neighbour's — the two boxes then sit at different
   heights. The grid already makes both cells equal height, so pinning each control to the
   bottom of its cell keeps the row even however the labels wrap. */
.form-row.two > div { display: flex; flex-direction: column; }
.form-row.two > div > input,
.form-row.two > div > select,
.form-row.two > div > textarea,
.form-row.two > div > .ssel { margin-top: auto; }
.form-row label {
  display: block; font-size: 14px; font-weight: 500;
  color: var(--ink);
  margin-bottom: 8px;
}
/* The required marker. Labels carry a plain trailing "*" in their markup; markRequiredIn
   (js/ui.js) wraps it in this span at runtime, in every dialog, present and future. */
.req { color: var(--error-bright); font-weight: 700; margin-left: 2px; }
/* Explanatory line under a field. Put it under the ROW, not inside a .form-row.two cell: the
   cells are equal height with their controls pinned to the bottom, so an extra element in ONE
   cell lifts that input clear of its neighbour's and the two boxes stop lining up. */
.form-hint { font-size: 12px; color: var(--muted); line-height: 1.5; margin: -8px 0 16px; }
.form-row > .form-hint { margin: 6px 0 0; }   /* single-column row: sits right under its control */
.form-row input, .form-row select, .form-row textarea {
  width: 100%;
  height: 44px;
  padding: 0 16px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-md);
  font-size: 14px;
  font-family: inherit;
  color: var(--ink);
  background: var(--canvas);
  transition: border-color .15s, box-shadow .15s;
}
/* A native select paints its own arrow at the right edge and reserves no space for it, so a
   long option label runs underneath. Most selects are swapped for the searchable combo, but
   any opted out with data-no-search stays native — and every one of them is native for the
   moment before enhanceSelectsIn runs. Needs this specificity to beat the shorthand above. */
.form-row select { padding-right: 38px; }
.form-row textarea { resize: vertical; min-height: 88px; height: auto; padding: 12px 16px; line-height: 1.5; }
.form-row input::placeholder, .form-row textarea::placeholder { color: var(--muted); }
.form-row input:focus, .form-row select:focus, .form-row textarea:focus {
  outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07);
}
/* Package builder level rows (js/stock-packages.js): name | contains-how-many | remove.
   These can't ride on .form-row.two — that grid lays each cell out as a COLUMN, which put the
   remove button under the input and left the two textfields at different heights. Own grid,
   inputs pinned to the bottom of their cell so the row stays even however the labels wrap. */
.pkg-tier-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 10px; align-items: end; margin-bottom: 10px; }
.pkg-tier-row > div { display: flex; flex-direction: column; min-width: 0; }
.pkg-tier-row > div > input { margin-top: auto; }
.form-row.pkg-tier-row label { font-size: 12px; font-weight: 500; color: var(--muted); margin-bottom: 6px; }
.pkg-tier-row input:disabled { background: var(--surface-strong); color: var(--muted); cursor: not-allowed; }
.pkg-tier-row .tDel { height: 44px; padding: 0 14px; font-size: 14px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 40px; padding: 0 18px; border-radius: var(--r-md);
  border: 1px solid transparent; cursor: pointer;
  font-size: 14px; font-weight: 500; line-height: 1; font-family: inherit;
  /* Anchors styled as buttons (e.g. "Go to sign in") would otherwise keep the UA underline. */
  text-decoration: none;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
.btn-primary { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.btn-primary:hover { background: var(--primary-active); border-color: var(--primary-active); }
.btn-primary:disabled { background: var(--surface-strong); border-color: var(--hairline-strong); color: var(--muted); cursor: not-allowed; }
.btn-secondary { background: var(--canvas); color: var(--ink); border-color: var(--hairline-strong); }
.btn-secondary:hover { background: var(--canvas-soft); box-shadow: var(--shadow-soft); }
/* Irreversible, destructive actions only — closing a company account. Black stays the
   ordinary primary; red is reserved for the point of no return. */
.btn-danger { background: var(--error-bright); color: #fff; border-color: var(--error-bright); }
.btn-danger:hover { background: var(--error-text); border-color: var(--error-text); }
.btn-danger:disabled { background: var(--surface-strong); border-color: var(--hairline); color: var(--muted); cursor: not-allowed; }

/* Danger zone card — a red hairline and heading instead of ad-hoc inline colours. */
.card.card-danger { border-color: var(--error); }
.card.card-danger .card-header h2 { color: var(--error-text); }
.card.card-danger .card-danger-body {
  padding: 16px 24px; display: flex; gap: 16px;
  align-items: center; justify-content: space-between; flex-wrap: wrap;
}
.card.card-danger .card-danger-body p { font-size: 13px; color: var(--body); margin: 6px 0 0; line-height: 1.6; }

/* My Settings — per-user preference cards (js/my-settings.js). A settings row is a checkbox with a
   two-line label (name + what it does), not a form field, so it gets its own spacing rather than
   .form-row's. Matches the danger-zone card's 16px/24px body padding. */
.settings-body { padding: 16px 24px 20px; }
.settings-addr {
  font-size: 13px; color: var(--body); margin: 0 0 16px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.settings-addr strong { color: var(--ink); font-weight: 600; }
.settings-toggle { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.settings-toggle input { margin: 2px 0 0; cursor: pointer; flex-shrink: 0; }
.settings-toggle input:disabled { cursor: not-allowed; }
.settings-toggle input:disabled ~ span { opacity: .6; }
.settings-toggle > span { display: flex; flex-direction: column; gap: 2px; }
.settings-toggle strong { font-size: 14px; font-weight: 600; color: var(--ink); }
.settings-toggle em { font-style: normal; font-size: 13px; color: var(--muted); line-height: 1.5; }
.settings-note { font-size: 13px; color: var(--muted); line-height: 1.6; margin: 14px 0 0; max-width: 620px; }
.settings-note strong { color: var(--ink); font-weight: 600; }
.settings-note.is-error { color: var(--error-text); }
/* An inline textual action inside a paragraph (send verification link) — a button for semantics and
   keyboard access, styled as the link it reads as. */
.btn-link {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--link); font-size: 13px; font-weight: 500; font-family: inherit;
}
.btn-link:hover { text-decoration: underline; }
.btn-link:disabled { color: var(--muted-soft); cursor: not-allowed; text-decoration: none; }
.form-error {
  background: var(--error-soft); color: var(--error-text);
  padding: 12px 16px; border-radius: var(--r-md);
  font-size: 14px; margin-bottom: 16px;
}

/* ---------------- Wide two-pane create/edit modal ----------------
   Business details on the left, a roomy coverage/area picker on the
   right. Shared by the distributor, outlet and retail-store modals. */
.modal.wide { width: 900px; max-width: 94vw; }
.modal.wide .modal-body { padding: 0; }
.modal.wide .modal-body > .form-error { margin: 20px 24px 0; }
.form-2pane { display: grid; grid-template-columns: 1.05fr 0.95fr; align-items: stretch; }
.form-2pane > .pane { padding: 24px; min-width: 0; }
.form-2pane > .pane-coverage { background: var(--canvas-soft); border-left: 1px solid var(--hairline); }
.pane-title {
  color: var(--muted);
  margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid var(--hairline);
}
.pane-title .req { color: var(--error-bright); }

/* Coverage / area picker (checkbox list with search + bulk actions). */
.cov { border: 1px solid var(--hairline-strong); border-radius: var(--r-lg); background: var(--canvas); }
.cov-head { display: flex; flex-direction: column; gap: 8px; padding: 12px; border-bottom: 1px solid var(--hairline); }
.cov-head input[type=text] { width: 100%; height: 36px; padding: 0 12px; border: 1px solid var(--hairline-strong); border-radius: var(--r-md); font-size: 14px; font-family: inherit; color: var(--ink); }
.cov-head input[type=text]:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07); }
.cov-bulk { display: flex; align-items: center; gap: 8px; }
.cov-bulk button { background: var(--canvas); color: var(--ink); border: 1px solid var(--hairline-strong); padding: 5px 12px; border-radius: var(--r-sm); cursor: pointer; font-size: 13px; font-family: inherit; }
.cov-bulk button:hover { background: var(--canvas-soft); }
.cov-count { margin-left: auto; color: var(--muted); font-size: 13px; font-weight: 500; }
.cov-list { max-height: 288px; overflow-y: auto; overflow-x: hidden; padding: 8px; display: flex; flex-direction: column; gap: 2px; }
.cov-list.grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2px 8px; }
.cov-list label { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 400; color: var(--body); padding: 7px 8px; border-radius: var(--r-sm); cursor: pointer; text-transform: none; letter-spacing: normal; margin: 0; }
.cov-list label:hover { background: var(--hairline-soft); }
.cov-list input[type=checkbox] { width: auto; height: auto; flex: none; accent-color: var(--primary); }
.cov-list .cov-district { color: var(--muted); }
.cov-list .cov-empty { color: var(--muted); font-size: 14px; padding: 12px 8px; grid-column: 1 / -1; }
.cov-hint { margin-top: 12px; color: var(--body); font-size: 13px; line-height: 1.5; }

@media (max-width: 720px) {
  .form-2pane { grid-template-columns: 1fr; }
  .form-2pane > .pane-coverage { background: var(--canvas); border-left: none; border-top: 1px solid var(--hairline); }
  .cov-list.grid { grid-template-columns: 1fr; }
}

/* Retail-store picker (salesman modal). Built to stay usable as the store
   count grows: a sticky search + bulk actions header over a scrolling list. */
.store-picker {
  border: 1px solid var(--hairline-strong); border-radius: var(--r-lg); overflow: hidden;
}
.store-picker-head {
  padding: 12px; border-bottom: 1px solid var(--hairline); background: var(--canvas-soft);
  display: flex; flex-direction: column; gap: 8px;
}
.store-picker-head .row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.store-picker-head input {
  width: 100%; height: 36px; padding: 0 12px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md); font-size: 14px;
  font-family: inherit; color: var(--ink); background: var(--canvas);
}
.store-picker-head input:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07); }
.store-picker-count { font-size: 13px; color: var(--muted); white-space: nowrap; }
.store-picker-bulk { display: flex; gap: 6px; }
.store-picker-bulk button {
  background: var(--canvas); color: var(--ink); border: 1px solid var(--hairline-strong);
  padding: 5px 12px; border-radius: var(--r-sm); cursor: pointer; font-size: 13px; font-family: inherit;
}
/* The header itself is --canvas-soft, so hovering to that tone would be invisible. */
.store-picker-bulk button:hover { background: var(--surface-strong); }
.store-picker-list {
  max-height: 420px; overflow-y: auto; overflow-x: hidden;
  padding: 8px; display: grid; grid-template-columns: 1fr; gap: 2px;
}
@media (min-width: 720px) {
  .store-picker-list { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
}
.store-picker-list label {
  display: flex; align-items: center; gap: 9px;
  padding: 8px; border-radius: var(--r-sm);
  font-weight: 400; cursor: pointer; font-size: 14px; color: var(--body);
}
.store-picker-list label:hover { background: var(--hairline-soft); }
.store-picker-list label input { width: auto; height: auto; margin: 0; flex: none; accent-color: var(--primary); }
.store-picker-list label.is-locked { color: var(--muted-soft); cursor: not-allowed; }
.store-picker-list label.is-locked:hover { background: transparent; }
.store-picker-list .store-other { color: var(--warning); font-size: 13px; }
.store-picker-empty { color: var(--muted); font-size: 14px; padding: 12px 8px; }
/* Group subheader inside the picker (used by the Roles permission picker). Spans
   the full grid width so it reads as a section divider above its checkboxes. */
.store-picker-list .picker-group {
  grid-column: 1 / -1;
  color: var(--muted); padding: 12px 8px 2px;
}
.store-picker-list .picker-group:first-child { padding-top: 4px; }
/* Disabled state — e.g. an admin role where individual permissions don't apply. */
.store-picker.is-disabled { opacity: 0.5; pointer-events: none; }

/* Radio / checkbox choice list inside a form row (e.g. the announcement audience picker).
   Same palette as the pickers above, but each option is its own selectable card. */
.choice-list { display: flex; flex-direction: column; gap: 8px; }
.choice-list label {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  font-size: 14px; font-weight: 400; color: var(--body);
  text-transform: none; letter-spacing: normal; margin: 0; cursor: pointer;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.choice-list label:hover { background: var(--canvas-soft); }
.choice-list label.is-on { border-color: var(--ink); background: var(--canvas); color: var(--ink); box-shadow: var(--shadow-soft); }
.choice-list input { width: auto; height: auto; flex: none; margin: 0; accent-color: var(--primary); }

/* "Who will get this" panel under a choice-list — spells the selected audience out by name. */
.aud-detail {
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md); background: var(--canvas-soft);
  padding: 12px 14px; margin-top: 8px;
}
/* One block per selected audience, so a combined send shows each group it reaches. */
.aud-section + .aud-section { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--hairline-strong); }
.aud-detail-head { font-size: 13px; font-weight: 600; color: var(--ink); }
.aud-detail-note { font-size: 12px; color: var(--muted); margin-top: 4px; line-height: 1.5; }
.aud-chips {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 8px; max-height: 116px; overflow-y: auto; overflow-x: hidden;
}
.aud-chip {
  background: var(--canvas); border: 1px solid var(--hairline-strong); border-radius: var(--r-full);
  padding: 3px 10px; font-size: 13px; color: var(--body);
}
.aud-chip.more { color: var(--muted); border-style: dashed; }
/* A recipient deleted since the send — kept in the list (it DID receive the announcement) but
   visibly retired, so the sender isn't left wondering who an unfamiliar id was. */
.aud-chip.gone { color: var(--muted); background: var(--surface-strong); border-style: dashed; text-decoration: line-through; }

.banner {
  padding: 14px 16px; border-radius: var(--r-md);
  margin-bottom: 16px; font-size: 14px;
}
.banner.error { background: var(--error-soft); color: var(--error-text); }
.banner.info { background: #ddeeff; color: #0a5ca4; }
.banner.warn { background: var(--warning-soft); color: var(--warning-deep); }
.loading {
  padding: 40px; text-align: center; color: var(--muted); font-size: 14px;
}
/* Spinner for the blocking "working…" dialog (uiBusy in js/ui.js). */
.ui-busy-spin {
  flex: none; width: 22px; height: 22px; border-radius: var(--r-full);
  border: 2.5px solid var(--hairline-strong); border-top-color: var(--primary);
  animation: ui-busy-spin 0.8s linear infinite;
}
@keyframes ui-busy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ui-busy-spin { animation-duration: 2.4s; } }

/* Detail rows */
.detail-grid {
  display: grid;
  grid-template-columns: 150px 1fr;
  row-gap: 12px;
  column-gap: 16px;
  font-size: 14px;
}
.detail-grid .label { color: var(--muted); padding-top: 3px; }
.detail-grid .value { color: var(--ink); word-break: break-word; }
.detail-grid .value.muted { color: var(--muted); }

/* ---------------- Loading skeletons (listSkeleton / blockSkeleton in js/ui.js) ----------------
   A page waiting on its first Firestore read draws the SHAPE of what's coming — KPI tiles, a
   filter bar, table rows — instead of the word "Loading". The layout then doesn't jump when the
   data lands, because the placeholder is built from the same .kpi-row / .card / table markup.
   Every placeholder bar is a .skel: a tinted block with a highlight sweeping across it. */
.skel {
  display: inline-block;
  vertical-align: middle;
  background-color: var(--surface-strong);
  background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  border-radius: var(--r-xs);
  animation: skel-sweep 1.4s ease-in-out infinite;
}
@keyframes skel-sweep {
  from { background-position: 150% 0; }
  to   { background-position: -150% 0; }
}
/* The sweep is decoration, not information — respect a reduced-motion preference and let the
   flat tinted blocks carry the "still loading" message on their own. */
@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; }
}
/* On the soft-grey surfaces (table heads, filter bars) the default fill is only a shade off the
   background and the bars all but vanish — step them up to the border tone there. */
thead .skel, .filter-bar .skel { background-color: var(--hairline-strong); }
.skel-title { width: 160px; height: 16px; }
.skel-btn   { width: 96px;  height: 40px; border-radius: var(--r-md); }
.skel-input { width: 260px; height: 40px; border-radius: var(--r-md); }
/* Skeleton cells keep the real table's row rhythm — without this the bars sit at the top of a
   cell whose height comes from vertical-align, and the rows read as uneven. */
.skel-row td { height: 45px; }
/* Announced to a screen reader, invisible on screen: the skeleton itself says nothing, so the
   status text is what tells a non-visual user the page is still fetching. */
.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;
}

/* Filter bar */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 12px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--hairline);
  background: var(--canvas-soft);
  align-items: center;
}
.filter-bar input, .filter-bar select {
  height: 40px;
  padding: 0 14px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-md);
  font-size: 14px;
  background: var(--canvas);
  color: var(--ink);
  font-family: inherit;
  transition: border-color .15s, box-shadow .15s;
}
.filter-bar select { padding-right: 34px; }   /* room for the native arrow — see .form-row select */
.filter-bar input { min-width: 220px; flex: 1; max-width: 320px; }
.filter-bar input::placeholder { color: var(--muted); }
.filter-bar input:focus, .filter-bar select:focus {
  outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(23,23,23,.07);
}
/* Buttons living in a filter/bulk bar (e.g. the user list's bulk actions) match the height
   and treatment of the inputs beside them. Without this they fell through to the browser's
   native button styling, which is the one place the design system wasn't reaching. */
.filter-bar button:not(.clear):not(.ssel-btn) {
  height: 40px; padding: 0 14px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-md);
  background: var(--canvas); color: var(--ink);
  font-family: inherit; font-size: 14px; font-weight: 500; line-height: 1;
  cursor: pointer;
  transition: background .15s, border-color .15s, box-shadow .15s;
}
.filter-bar button:not(.clear):not(.ssel-btn):hover:not(:disabled) { background: var(--canvas-soft); box-shadow: var(--shadow-soft); }
.filter-bar button:not(.clear):not(.ssel-btn):disabled {
  background: var(--surface-strong); color: var(--muted);
  border-color: var(--hairline); cursor: not-allowed;
}
.filter-bar button.is-primary:not(.ssel-btn):not(:disabled) { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.filter-bar button.is-primary:not(.ssel-btn):not(:disabled):hover { background: var(--primary-active); border-color: var(--primary-active); }
.filter-bar button.is-warn:not(.ssel-btn):not(:disabled) { background: var(--warning-soft); color: var(--warning-deep); border-color: transparent; }
.filter-bar button.is-warn:not(.ssel-btn):not(:disabled):hover { background: #f8dcae; }

.filter-bar .clear {
  background: transparent; border: none;
  color: var(--link); cursor: pointer;
  font-size: 14px; font-weight: 500; font-family: inherit;
  padding: 6px 10px;
}
.filter-bar .clear:hover { text-decoration: underline; }
.filter-bar .count { margin-left: auto; font-size: 13px; color: var(--muted); }

/* Landing page tiles */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}
.tile {
  display: block;
  background: var(--canvas);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  padding: 24px;
  text-decoration: none;
  color: inherit;
  transition: box-shadow .15s, transform .15s;
}
.tile:hover { box-shadow: var(--shadow-soft); transform: translateY(-2px); }
.tile .tile-icon { font-size: 24px; margin-bottom: 12px; }
.tile .tile-title { font-weight: 600; font-size: 16px; line-height: 1.4; color: var(--ink); margin-bottom: 4px; }
.tile .tile-desc { font-size: 14px; color: var(--body); line-height: 1.5; }

/* Responsive */
@media (max-width: 900px) {
  .sidebar {
    position: fixed;
    top: 0; bottom: 0; left: 0;
    transform: translateX(-100%);
    transition: transform .25s ease;
    z-index: 50;
    box-shadow: 0 8px 32px rgba(0,0,0,.10);
  }
  body.sidebar-open .sidebar { transform: translateX(0); }
  body.sidebar-open .sidebar-overlay { display: block; }
  .hamburger { display: inline-flex; }
  .topbar { padding: 0 16px; }
  .topbar h1 { font-size: 18px; letter-spacing: 0; }
  /* The sidebar is behind the hamburger here, so the topbar is the ONLY place the identity shows —
     keep it, just give it less room. */
  .tb-id { max-width: 150px; }
  .content { padding: 16px; }
  .card-header { padding: 16px; flex-wrap: wrap; gap: 10px; }
  .card-header .actions { display: flex; flex-wrap: wrap; gap: 8px; }
  .filter-bar { padding: 12px 16px; gap: 8px; }
  .filter-bar input { min-width: 0; width: 100%; max-width: none; flex: 1 1 100%; }
  .filter-bar select { flex: 1 1 40%; }
  .filter-bar .count { width: 100%; margin-left: 0; text-align: right; }
  th, td { padding: 12px 16px; }
}
@media (max-width: 480px) {
  .topbar h1 { font-size: 16px; }
  /* Too narrow for two columns of text plus the title: drop the name line and keep the email, which
     is the part that identifies the login. The avatar keeps its tooltip either way. */
  .tb-id { max-width: 118px; }
  .tb-id-name { display: none; }
  .tb-id-email { font-size: 11px; }
  .kpi-card .value { font-size: 22px; letter-spacing: -0.5px; }
  .modal { width: 100%; max-width: 100%; border-radius: 0; height: 100vh; display: flex; flex-direction: column; }
  .modal.wide { width: 100%; max-width: 100%; }
  .modal-body { flex: 1; }
  .form-row.two { grid-template-columns: 1fr; }
  .detail-grid { grid-template-columns: 1fr; row-gap: 4px; }
  .detail-grid .label { padding-top: 10px; }
}

/* ---------------- Login page ---------------- */
.auth-body { background: var(--canvas-page); }
.auth-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  min-height: 100vh;
}
.auth-card {
  background: var(--canvas);
  width: 100%;
  max-width: 400px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-xl);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
  padding: 32px;
}
.auth-brand {
  font-size: 28px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: -0.84px;
  color: var(--ink);
  text-align: center;
}
.auth-brand span { color: var(--muted); }
.auth-sub {
  text-align: center;
  color: var(--body);
  font-size: 14px;
  margin: 8px 0 24px;
}
/* Helper line under an auth-form field (e.g. what picking a business category does). */
.auth-hint { margin: 8px 0 0; font-size: 13px; line-height: 1.4; color: var(--muted); }
.auth-alt { text-align: center; margin-top: 20px; font-size: 14px; color: var(--body); }
.auth-alt a { color: var(--link); text-decoration: none; font-weight: 500; }
.auth-alt a:hover { text-decoration: underline; }
.auth-alt-muted { margin-top: 8px; }
.auth-alt-muted a { color: var(--muted); font-weight: 400; }
/* Privacy-policy consent on login / signup (js/policy-consent.js). Every way in — email form,
   Google, Apple — is blocked until it is ticked; .auth-social-guard covers the provider buttons
   (rendered by Google/Apple, so their clicks can't be intercepted) while it is unticked.
   Google's reCAPTCHA v3 badge is left visible: without the branding text Google's terms
   require the badge, so never hide .grecaptcha-badge. */
.auth-consent {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 20px 0 0; padding: 12px 14px;
  border: 1px solid var(--hairline-strong); border-radius: var(--r-sm);
  background: var(--canvas-soft);
  font-size: 13px; line-height: 1.5; color: var(--body);
  cursor: pointer;
}
.auth-consent input { width: 16px; height: 16px; margin: 1px 0 0; flex: none; accent-color: var(--link); cursor: pointer; }
.auth-consent a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; font-weight: 500; }
.auth-consent.consent-missing { border-color: var(--error-bright); background: var(--error-soft); }
.auth-social { position: relative; }
.auth-social-guard { position: absolute; inset: 0; z-index: 2; cursor: not-allowed; }
.auth-social.locked .gsi-wrap, .auth-social.locked .apple-wrap { opacity: 0.45; }
.auth-resend { text-align: center; margin: -4px 0 16px; }
.auth-linkbtn {
  background: none; border: none;
  color: var(--link);
  font-weight: 500; font-size: 14px;
  cursor: pointer; font-family: inherit;
  padding: 4px 2px;
}
.auth-linkbtn:hover:not(:disabled) { text-decoration: underline; }
.auth-linkbtn:disabled { color: var(--muted); cursor: default; }
.auth-forgot { text-align: right; margin-top: 8px; }
.auth-forgot .auth-linkbtn { font-size: 13px; font-weight: 400; }

/* Google sign-in button + "or" divider (login + signup cards). */
.btn-google {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  height: 44px; padding: 0 16px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-md);
  background: var(--canvas);
  color: var(--ink);
  font-size: 14px; font-weight: 500; font-family: inherit;
  cursor: pointer;
  transition: background .15s, box-shadow .15s;
}
.btn-google:hover:not(:disabled) { background: var(--canvas-soft); box-shadow: var(--shadow-soft); }
.btn-google:disabled { color: var(--muted); cursor: default; }
.btn-google .g-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: var(--r-full);
  background: var(--canvas); color: #4285F4;
  font-weight: 600; font-size: 14px;
  border: 1px solid var(--hairline-strong);
}
.gsi-wrap { display: flex; justify-content: center; min-height: 44px; }
.apple-wrap { display: flex; justify-content: center; margin-top: 10px; }
/* Apple-brand-compliant "Continue with Apple" button (black fill, white glyph). */
.apple-signin-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; height: 44px; padding: 0 12px;
  background: #000; color: #fff;
  border: 1px solid #000; border-radius: var(--r-md);
  font-family: inherit; font-size: 14px; font-weight: 500;
  cursor: pointer; transition: background .15s ease;
}
.apple-signin-btn:hover { background: var(--primary-active); }
.apple-signin-btn:disabled { opacity: .6; cursor: default; }
.apple-signin-btn svg { width: 16px; height: 16px; flex: none; }
.auth-divider {
  display: flex; align-items: center; gap: 12px;
  color: var(--muted); font-size: 13px;
  margin: 20px 0;
}
.auth-divider::before, .auth-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--hairline-strong);
}

/* ---------------- Searchable select (js/searchable-select.js) ----------------
   The combo button replaces every native <select>, so it has to match the height of
   whatever input sits beside it — 44px in a form row, 40px in a filter bar. The base
   rules live in the injected stylesheet; these context overrides win on specificity. */
.form-row .ssel-btn { min-height: 44px; padding: 10px 30px 10px 16px; border-radius: var(--r-md); }
.filter-bar .ssel-btn { min-height: 40px; padding: 8px 28px 8px 14px; border-radius: var(--r-md); }
.cov-head .ssel-btn, .store-picker-head .ssel-btn { min-height: 36px; border-radius: var(--r-md); }

/* ---------------- Sidebar identity + topbar logout ---------------- */
/* Signed-in identity, under the brand at the TOP of the sidebar (renderSidebarAccount in
   js/sidebar.js). Log out is no longer in the sidebar — it sits in the topbar beside the bell
   (.tb-logout below). */
.sidebar-account {
  flex: none;
  padding: 16px 20px;
  border-bottom: 1px solid var(--hairline);
}
.sb-user { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.sb-idcol { min-width: 0; display: flex; flex-direction: column; }
.sb-org-name {
  font-size: 14px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-org-parent {
  font-size: 12px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sb-avatar {
  background: var(--primary);
  color: var(--on-primary);
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  font-weight: 500; font-size: 13px;
  flex-shrink: 0;
}
.sb-email {
  font-size: 13px; color: var(--body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Log out, in the topbar beside the notification bell (renderTopbarLogout in js/sidebar.js; the
   platform console's header uses the same class). It's the one exit in the chrome: tinted red with
   a red border so it's unmistakable at a glance, going solid red on hover/focus. Same 36px height
   as the bell so the two sit as a pair. */
.tb-logout {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  flex-shrink: 0;
  margin-left: auto;         /* the bell carries the push when it's there — see the next rule */
  background: var(--error-soft);
  border: 1px solid var(--error);
  color: var(--error-text);
  height: 36px;
  padding: 0 12px;
  border-radius: var(--r-md);
  font-size: 13px; font-weight: 600; font-family: inherit; white-space: nowrap;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
}
.notif-wrap ~ .tb-logout { margin-left: 0; }
.tb-logout svg { width: 16px; height: 16px; flex: none; }
.tb-logout:hover {
  background: var(--error-bright); border-color: var(--error-bright);
  color: #fff; box-shadow: var(--shadow-soft);
}
.tb-logout:focus-visible { outline: 2px solid var(--error-bright); outline-offset: 2px; }
/* Phones: icon only, square like the bell, so the identity block keeps its room. */
@media (max-width: 480px) {
  .tb-logout { width: 36px; padding: 0; }
  .tb-logout span { display: none; }
}

/* ============================================================
   Dashboard (js/dashboard.js)
   ============================================================ */

/* Two charts abreast on a wide screen, stacked below it. The cards keep their own bottom margin,
   so the grid only has to supply the column gap. */
.dash-2col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 16px;
}
.dash-2col > .card { min-width: 0; }   /* let a card shrink instead of forcing the grid wider */

/* A row of things to act on. Each is a link to the record, so the whole row is the hit target and
   the reason it's here is spelled out rather than implied by a colour. */
.dash-todo { display: flex; flex-direction: column; }
/* Expanded ("+N more"), the list can hold every out-of-stock product. It scrolls inside the card
   so the blocks below it stay where the reader left them instead of being pushed off-screen. */
.dash-todo.is-open { max-height: 60vh; overflow-y: auto; }
.dash-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center; gap: 12px;
  padding: 11px 24px;
  border-bottom: 1px solid var(--hairline-soft);
  color: var(--body); text-decoration: none;
  font-size: 13px;
}
.dash-item:last-child { border-bottom: 0; }
.dash-item:hover { background: var(--canvas-soft); }
.dash-item .dash-what { color: var(--ink); font-weight: 600; }
.dash-item .dash-sub { color: var(--muted); font-size: 11.5px; margin-top: 2px; }
.dash-item .dash-when { color: var(--muted); font-size: 11.5px; white-space: nowrap; }
/* The middle column is the only one allowed to run out of room. */
.dash-item .dash-mid { min-width: 0; }
.dash-item .dash-what, .dash-item .dash-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A dot instead of a pill for the activity feed: a feed of pills is a wall of colour, and here the
   status is already written out beside it. */
.dash-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--muted-soft); }
.dash-dot.ok { background: var(--success); }
.dash-dot.warn { background: var(--warning); }
.dash-dot.bad { background: var(--error-bright); }
.dash-dot.info { background: var(--info); }

.dash-empty { padding: 20px 24px; font-size: 13px; color: var(--muted); }

@media (max-width: 900px) {
  .dash-2col { grid-template-columns: 1fr; }
}
