/* Component styles for shared/ui.html, ported from
 * ~/Projects/fuel-trading-system (app/static/css/app.css).
 *
 * Every value here comes from tokens.css. There are no colour literals in this
 * file and there should never be: it is the first stylesheet written after the
 * token layer landed, so it is the one that has no excuse.
 *
 * Loaded after styles.css so a converted page wins over the legacy rules
 * without either sheet having to know about the other.
 */

/* ── status badges ─────────────────────────────────────────────────────────
 * Scoped to `.badge.badge-*` on purpose. A bare `.badge-ok` already exists in
 * admin/dashboard.html as a local class, and a single-class selector here
 * would have reached inside it.
 */
.badge.badge-ok,
.badge.badge-info,
.badge.badge-warn,
.badge.badge-danger,
.badge.badge-neutral {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 3px var(--s-2);
  border: 1px solid transparent;
  /* A pill, and sentence case. The reference writes a state the way a sentence
     would — `Active`, `Loaded`, `Partially paid` — and reserves uppercase
     letter-spacing for section labels, where it means "this is a heading"
     rather than "this is shouting". */
  border-radius: var(--r-pill);
  font-size: var(--fs-11);
  font-weight: var(--fw-semibold);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}

/* The dot is not decoration: it is the second channel that keeps status
 * legible without colour (WCAG 2.2 §1.4.1). Never remove it. */
.badge > .dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: var(--r-pill);
  background: currentColor;
}

.badge.badge-ok      { background: var(--pos-50);  border-color: var(--pos-200);  color: var(--pos-700); }
.badge.badge-info    { background: var(--info-50); border-color: var(--info-200); color: var(--info-700); }
.badge.badge-warn    { background: var(--warn-50); border-color: var(--warn-200); color: var(--warn-700); }
.badge.badge-danger  { background: var(--neg-50);  border-color: var(--neg-200);  color: var(--neg-700); }
.badge.badge-neutral { background: var(--bg-sunken); border-color: var(--border); color: var(--text-muted); }

.badge.badge-sm { padding: 1px var(--s-1); font-size: var(--fs-11); }

/* ── document header ─────────────────────────────────────────────────────── */
.document-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-4) var(--s-6);
  margin-bottom: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
}
.document-type {
  color: var(--text-subtle);
  font-size: var(--fs-11);
  font-weight: var(--fw-semibold);
  letter-spacing: .07em;
  text-transform: uppercase;
}
.document-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-1);
}
/* The document number is the identity of the record, so it is set in the mono
 * face with tabular figures: two numbers that differ by one character have to
 * look different at a glance. */
.document-title-row h1 {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-22);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  color: var(--text);
}
/* A name, not a number: the product typeface, medium weight. */
.document-title-row h1.is-name {
  font-family: inherit;
  font-weight: var(--fw-medium);
  letter-spacing: -.01em;
}
.document-subtitle { margin: var(--s-2) 0 0; color: var(--text-muted); font-size: var(--fs-13); }
.document-subtitle a { color: var(--brand-600); text-decoration: none; }
.document-subtitle a:hover { text-decoration: underline; }

.document-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
  gap: var(--s-3) var(--s-6);
  align-items: start;
}
.document-fact { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.document-fact-label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  color: var(--text-subtle);
  font-size: var(--fs-11);
  text-transform: uppercase;
  letter-spacing: .05em;
}
.document-fact-value {
  color: var(--text);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.document-fact-value a { color: var(--brand-600); text-decoration: none; }
.document-fact-value a:hover { text-decoration: underline; }

/* A derived value must not look editable. The tint and the rule say "this was
 * computed"; document-fact-source says what from. */
.document-fact.is-calculated .document-fact-value {
  padding: 2px var(--s-2);
  border-left: 2px solid var(--fin-border);
  background: var(--fin-bg);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.document-fact-calc { color: var(--text-subtle); font-size: var(--fs-11); }
.document-fact-source { color: var(--text-subtle); font-size: var(--fs-11); }

/* ── action bar ──────────────────────────────────────────────────────────── */
.document-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-4);
  margin-bottom: var(--s-4);
}
.document-action-group { display: flex; align-items: center; gap: var(--s-2); }
.document-action-label {
  color: var(--text-subtle);
  font-size: var(--fs-11);
  text-transform: uppercase;
  letter-spacing: .05em;
}
.document-action-controls { display: flex; align-items: center; gap: var(--s-2); }
.document-action-controls > form { display: inline-flex; margin: 0; }
/* Destructive actions are pushed to the end of the bar and separated, so
 * "Cancel this document" is never adjacent to the primary action. */
.document-action-group.is-destructive {
  margin-left: auto;
  padding-left: var(--s-4);
  border-left: 1px solid var(--border);
}

/* ── two-column document layout ──────────────────────────────────────────── */
.document-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(250px, 300px);
  align-items: start;
  gap: var(--s-4);
}
.document-main, .factbox-pane { min-width: 0; }
.factbox-pane {
  position: sticky;
  top: calc(var(--topbar-h) + var(--s-4));
  display: grid;
  gap: var(--s-3);
}
@media (max-width: 1200px) {
  .document-layout { grid-template-columns: minmax(0, 1fr); }
  .factbox-pane { position: static; }
}

/* ── FastTab ─────────────────────────────────────────────────────────────── */
.fasttab {
  margin-bottom: var(--s-3);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-surface);
}
.fasttab > summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s-3);
  min-height: 46px;
  padding: var(--s-3) var(--s-4);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.fasttab > summary::-webkit-details-marker { display: none; }
.fasttab > summary:hover { background: var(--bg-hover); }
.fasttab > summary:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.fasttab-title { font-size: var(--fs-13); font-weight: var(--fw-semibold); color: var(--text); }
.fasttab-hint { color: var(--text-subtle); font-size: var(--fs-11); }
.fasttab-chevron { color: var(--text-subtle); transition: transform var(--dur-fast) var(--ease); }
.fasttab[open] .fasttab-chevron { transform: rotate(180deg); }
.fasttab-body { padding: var(--s-4); border-top: 1px solid var(--border); }
.fasttab-body.flush { padding: 0; }

/* ── FactBox ─────────────────────────────────────────────────────────────── */
.factbox {
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-surface);
}
.factbox-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border);
  background: var(--bg-sunken);
}
.factbox-head h2 {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-muted);
}
.factbox-hint { color: var(--text-subtle); font-size: var(--fs-11); }
.factbox-body { padding: var(--s-3) var(--s-4); }
.factbox-empty { margin: 0; color: var(--text-subtle); font-size: var(--fs-12); }
.factbox-foot-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  margin-top: var(--s-3);
  color: var(--brand-600);
  font-size: var(--fs-12);
  text-decoration: none;
}
.factbox-foot-link:hover { text-decoration: underline; }

/* ── related documents ───────────────────────────────────────────────────── */
.related-list { display: grid; gap: var(--s-1); }
.related-link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2);
  border-radius: var(--r-sm);
  color: var(--text);
  text-decoration: none;
}
.related-link:hover { background: var(--bg-hover); }
.related-link-copy { display: flex; flex-direction: column; min-width: 0; }
.related-link-copy strong { font-size: var(--fs-13); font-weight: var(--fw-medium); }
.related-link-copy small { color: var(--text-subtle); font-size: var(--fs-11); }
.related-chevron { color: var(--text-subtle); }

/* ── attachments ─────────────────────────────────────────────────────────── */
.attachment-upload { display: grid; gap: var(--s-2); margin-bottom: var(--s-3); }
.attachment-upload input[type="text"] {
  width: 100%;
  min-height: var(--control-h);
  padding: 0 var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg-surface);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-12);
}
.attachment-picker {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: var(--fs-12);
  cursor: pointer;
}
.attachment-picker:hover { border-color: var(--brand-600); color: var(--brand-600); }
.attachment-list { display: grid; gap: var(--s-2); }
.attachment-item { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-2); align-items: start; }
.attachment-copy { display: flex; flex-direction: column; min-width: 0; }
.attachment-copy strong { font-size: var(--fs-12); font-weight: var(--fw-medium); }
.attachment-copy small { color: var(--text-subtle); font-size: var(--fs-11); overflow-wrap: anywhere; }

/* ── audit timeline ──────────────────────────────────────────────────────── */
.factbox-timeline { display: grid; gap: var(--s-3); margin: 0; padding: 0; list-style: none; }
.factbox-timeline > li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-3); align-items: start; }
.timeline-dot {
  width: 7px;
  height: 7px;
  margin-top: 5px;
  border-radius: var(--r-pill);
  background: var(--brand-400);
}
.timeline-copy { display: flex; flex-direction: column; min-width: 0; }
.timeline-copy strong { font-size: var(--fs-12); font-weight: var(--fw-medium); }
.timeline-copy small { color: var(--text-subtle); font-size: var(--fs-11); }

/* ── empty state ─────────────────────────────────────────────────────────── */
.empty {
  display: grid;
  justify-items: center;
  gap: var(--s-2);
  padding: var(--s-10) var(--s-4);
  text-align: center;
  color: var(--text-muted);
}
.empty-ico { font-size: 28px; color: var(--text-subtle); }
.empty h3 { margin: 0; font-size: var(--fs-14); font-weight: var(--fw-semibold); color: var(--text); }
.empty p { margin: 0; font-size: var(--fs-13); max-width: 44ch; }
.empty-sm { padding: var(--s-5) var(--s-3); }
.empty-sm .empty-ico { font-size: 20px; }

/* ── responsive record cards ───────────────────────────────────────────────
 * A data table cannot be made to work on a 375px screen. Horizontal scroll
 * inside .table-responsive looks like it works and does not: the status and
 * action columns sit off-screen with nothing indicating they are there, which
 * is how a phone user concludes a quotation has no status.
 *
 * So every list carries a second rendering of the same rows — a card per
 * record, showing the two or three fields that identify it plus its status.
 * It is authored per table, not generated: which fields identify a record is
 * an editorial judgement, and a mechanical row-to-card transform just moves
 * all six columns somewhere else.
 *
 * The two never show at once, so there is no duplicate content for a screen
 * reader to read twice.
 */
.as-cards { display: none; }

@media (max-width: 768px) {
  /* Hide a table ONLY where a card fallback actually follows it. The first
     version of this rule hid every table inside a .card-body, which on a page
     with no fallback — a worksheet, a two-column summary — left an empty card
     and simply lost the content on a phone.
     Three selectors because the table may be bare, wrapped by ux.js in .dgrid,
     or wrapped in Bootstrap's .table-responsive; in each case the fallback is
     the next sibling of whichever element is outermost. */
  table.table:has(+ .as-cards),
  .dgrid:has(+ .as-cards),
  .table-responsive:has(+ .as-cards) { display: none; }

  /* The toolbar needs no rule of its own: ux.js builds it inside .dgrid, so
     hiding that wrapper takes the filter and column picker with the table
     they control — which is right, since a filter that narrows rows nobody
     can see is worse than no filter. */

  .as-cards { display: grid; gap: var(--s-2); padding: var(--s-3); }
}

.rec-card {
  display: block;
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-surface);
  color: inherit;
  text-decoration: none;
}
a.rec-card:hover { border-color: var(--brand-300); background: var(--bg-hover); }
a.rec-card:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.rec-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}
.rec-card-title {
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  color: var(--text);
  overflow-wrap: anywhere;
}
.rec-card-sub { margin-top: 2px; color: var(--text-muted); font-size: var(--fs-12); }

/* Money and quantity keep tabular figures and right alignment on a phone for
 * the same reason they have them in the table: a column of amounts is read by
 * comparing digit positions. */
.rec-card-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-top: var(--s-2);
  padding-top: var(--s-2);
  border-top: 1px solid var(--border);
}
.rec-card-label { color: var(--text-subtle); font-size: var(--fs-11); text-transform: uppercase; letter-spacing: .05em; }
.rec-card-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-13);
  font-weight: var(--fw-semibold);
  color: var(--text);
  text-align: right;
}

/* ── filter forms on a phone ───────────────────────────────────────────────
 * A date range is two <input type="date"> in a flex row. Flex items default to
 * min-width:auto, so they refuse to shrink below the intrinsic width the
 * browser gives a date control — about 150px each. In a half-width column on a
 * 375px screen that is 301px of content in 155px of space, and since nothing
 * clips it, the whole PAGE scrolls sideways. Measured at 113px of overflow on
 * /admin/coupons.
 *
 * Two changes, both scoped to controls inside a form so no other .d-flex in
 * the product starts wrapping: let the pair wrap onto a second line, and let
 * each control shrink. Wrapping is preferred to squeezing — a date input
 * narrower than its own placeholder is not usable.
 */
@media (max-width: 768px) {
  form .d-flex:has(> input[type="date"]),
  form .d-flex:has(> input[type="time"]) { flex-wrap: wrap; }

  form input[type="date"],
  form input[type="time"],
  form input[type="number"] { min-width: 0; }

  /* Same reasoning for the row itself: a grid column must be allowed to be
     narrower than its contents' intrinsic width, or it pushes the page. */
  .card-body.row > [class*="col-"] { min-width: 0; }
}

/* ── the tables that keep their table ──────────────────────────────────────
 * Five tables have no card fallback on purpose (three worksheets, a cross-tab,
 * and one whose rows are built by script). They still must not push the page
 * sideways — the rule is that wide content scrolls inside its own box, not
 * that the body scrolls. Measured: the dispatch board's allocation grid is
 * 648px wide and was taking the whole document with it.
 *
 * Where a fallback DOES exist the table is display:none by the rule above, so
 * this costs nothing there.
 */
@media (max-width: 768px) {
  .card-body:has(> table.table) { overflow-x: auto; }
}

/* ── page toolbars ─────────────────────────────────────────────────────────
 * A row of four buttons above a list is 447px wide and will not wrap on its
 * own: .btn-group is explicitly nowrap, and .d-flex defaults to it. Both push
 * the page on a phone. Wrapping is the only sane answer — the alternative is a
 * horizontally scrolling strip of primary actions, which people do not find.
 */
@media (max-width: 768px) {
  /* The header row has to wrap FIRST. Wrapping the button group on its own
     while it is still sitting beside a heading just stacks the buttons into a
     narrow column on top of the title — which is what the first version of
     this rule did. Once the row wraps, the group gets the full width and its
     four buttons fall into two sensible rows. */
  .d-flex.justify-content-between { flex-wrap: wrap; }
  .btn-group,
  .d-flex.gap-2 { flex-wrap: wrap; }
}

/* A figure inside a FactBox: label left, value right, tabular so a column of
   them lines up on the decimal the way the tables do. */
.factbox-figure {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-1) 0;
}
.factbox-figure + .factbox-figure { border-top: 1px solid var(--border); }
.factbox-figure-label { color: var(--text-muted); font-size: var(--fs-12); }
.factbox-figure-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

/* The Lines FastTab holds a full-bleed table, so its body must not pad it —
   the table draws its own edges. */
.fasttab-lines { margin: calc(-1 * var(--s-4)); }
.fasttab-lines > .as-cards { padding: var(--s-3); }

/* ── table cells ───────────────────────────────────────────────────────────
 * The second line under a cell's main value — a reference number under a
 * name, a date under a document number. Reference: .cell-sub. */
.cell-sub {
  display: block;
  margin-top: 4px;
  font-size: var(--fs-12);
  color: var(--text-subtle);
}

/* ── the way into a record ─────────────────────────────────────────────────
 * Reference: .text-link and the chevron that ends a row. The name in the
 * first cell is a link, and the row's last cell is a quiet chevron rather
 * than an "Open" button — the button repeated the same word on every row and
 * competed with the real actions beside it. The chevron keeps an accessible
 * name; only the visible word goes. */
.text-link {
  color: var(--brand-700);
  font-weight: var(--fw-medium);
  text-decoration: none;
}
.text-link:hover { color: var(--brand-800); text-decoration: underline; }
.row-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-sm);
  color: var(--text-subtle);
  text-decoration: none;
}
.row-go:hover { background: var(--bg-hover); color: var(--brand-700); }
.row-go:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 2px; }

/* ── error page ────────────────────────────────────────────────────────────
 * The status code is set large and quiet, a label rather than an alarm. */
.error-page { max-width: 560px; margin: var(--s-8) auto; }
.error-code {
  font-size: 56px;
  font-weight: var(--fw-semibold);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--text-subtle);
  margin-bottom: var(--s-3);
  font-variant-numeric: tabular-nums;
}

/* ════════════════════════════════════════════════════════════════════════
 * Page styles lifted out of templates (UI audit, September 2026).
 * Each block was a <style> element carrying its own copy of the palette —
 * the Day End worksheet and the station dashboard had the same six colours
 * typed twice — so none of it followed the token switch or the colour mode.
 * Every value now resolves through tokens.css.
 * ════════════════════════════════════════════════════════════════════════ */

/* ── Day End worksheet (was inline in wetstock/recon.html) ─── */
.ws-header{position:sticky;top:56px;z-index:5}

/* KPI strip — the position, before any detail */
.ws-kpis{display:grid;gap:12px;grid-template-columns:repeat(6,1fr)}
@media (max-width:1199px){.ws-kpis{grid-template-columns:repeat(3,1fr)}}
@media (max-width:575px){.ws-kpis{grid-template-columns:repeat(2,1fr)}}
.ws-kpi{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:10px 12px}
.ws-kpi-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);font-weight:600}
.ws-kpi-value{font-size:22px;font-weight:700;line-height:1.2;font-variant-numeric:tabular-nums;
              color:var(--text);margin-top:2px}
.ws-kpi-note{font-size:11px;color:var(--text-muted);line-height:1.35;margin-top:2px;
             font-variant-numeric:tabular-nums}
.ws-kpi.is-good{border-color:var(--pos-200);background:var(--pos-50)}
.ws-kpi.is-warn{border-color:var(--warn-200);background:var(--warn-50)}
.ws-kpi.is-bad{border-color:var(--neg-200);background:var(--neg-50)}
.ws-kpi.is-bad .ws-kpi-value{color:var(--neg-700)}
.ws-kpi.is-warn .ws-kpi-value{color:var(--warn-700)}
.ws-kpi.is-good .ws-kpi-value{color:var(--pos-700)}

/* Record cards */
.ws-cards{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
.ws-card{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:12px;
         display:flex;flex-direction:column;gap:10px}
.ws-card-top{display:flex;gap:10px;align-items:flex-start}
.ws-card-icon{width:32px;height:32px;border-radius:9px;background:var(--brand-50);color:var(--brand-600);
              display:inline-flex;align-items:center;justify-content:center;flex:none}
.ws-card-title{font-weight:700;font-size: var(--fs-13);line-height:1.25}
.ws-card-stat{font-size:12px;color:var(--text-muted);margin-top:2px;font-variant-numeric:tabular-nums}
.ws-card-actions{display:flex;gap:6px;margin-top:auto}
.ws-card-actions .btn{flex:1}
.ws-card.has-exc{border-color:var(--warn-200);background:var(--warn-50)}

/* Exceptions list */
.ws-exc-list{list-style:none;padding:0;margin:0}
.ws-exc-list li{padding:6px 8px;border-radius:8px;font-size:13px;display:flex;gap:8px;
                align-items:flex-start}
.ws-exc-list li.actionable{cursor:pointer}
.ws-exc-list li.actionable:hover{background:var(--bg-hover)}
.ws-exc-sev{flex:none;margin-top:1px}

/* Activity rail */
.ws-activity{list-style:none;padding:0;margin:0;font-size: var(--fs-12)}
.ws-activity li{display:flex;gap:8px;padding:4px 0;align-items:flex-start}
.ws-act-dot{width:8px;height:8px;border-radius:50%;flex:none;margin-top:6px}

/* Row lists inside modals */
.ws-rowlist{border:1px solid var(--bg-sunken);border-radius:10px;overflow:hidden}
.ws-rowrec{display:flex;gap:10px;align-items:center;padding:8px 10px;border-bottom:1px solid var(--bg-sunken);
           font-size:13px}
.ws-rowrec:last-child{border-bottom:none}
.ws-rowrec:hover{background:var(--bg-app)}
.ws-rowrec .ws-rowmain{flex:1;min-width:0}
.ws-rowrec .ws-rowsub{font-size: var(--fs-11);color:var(--text-muted)}
.ws-rowamt{font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.ws-empty{padding:14px;text-align:center;color:var(--text-muted);font-size: var(--fs-12)}

/* Records table */
.ws-search{position:relative;min-width:180px;max-width:340px}
.ws-search i{position:absolute;left:9px;top:50%;transform:translateY(-50%);
             color:var(--text-subtle);font-size:13px;pointer-events:none}
.ws-search input{padding-left:28px}
.ws-records th{font-size:11px;text-transform:uppercase;letter-spacing:.02em;
               color:var(--text-muted);font-weight:700;border-bottom:1px solid var(--border)}
.ws-records tbody td{border-bottom:1px solid var(--bg-sunken)}
.ws-records tbody tr:hover{background:var(--bg-app)}
.ws-sort{background:none;border:0;padding:0;font:inherit;color:inherit;cursor:pointer;
         display:inline-flex;align-items:center;gap:4px}
.ws-sort i{font-size:10px;opacity:.45}
.ws-sort:hover i{opacity:.9}
.ws-sort:focus-visible{outline:2px solid var(--brand-600);outline-offset:2px;border-radius:4px}
th[aria-sort] .ws-sort i{opacity:1;color:var(--brand-600)}

/* Status pills — a word and an icon, never colour alone */
.ws-pill{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;
         font-size:11px;font-weight:600;border:1px solid transparent;white-space:nowrap}
.ws-pill-ok{background:var(--pos-50);color:var(--pos-700);border-color:var(--pos-200)}
.ws-pill-warn{background:var(--warn-50);color:var(--warn-700);border-color:var(--warn-200)}

/* Empty / loading / error states */
.ws-state{padding:26px 16px;text-align:center;color:var(--text-muted);font-size:13px}
.ws-state i{font-size:22px;display:block;margin-bottom:6px;opacity:.5}
.ws-state-error{color:var(--neg-700)}
.ws-state-error i{opacity:.8}

/* Interaction targets stay comfortably tappable */
.ws-card-actions .btn,.ws-records .btn{min-height:32px}
.ws-kpi-sm{font-size:17px}
.ws-kpi-status{line-height:1.4}

/* Read-only summary tables — the Day End Report as it is signed off */
.ws-ro{font-variant-numeric:tabular-nums}
.ws-ro thead th{background:var(--bg-hover);color:var(--text);font-size:11px;font-weight:700;
                text-transform:uppercase;letter-spacing:.02em;white-space:nowrap;
                border-bottom:1px solid var(--border)}
.ws-ro tbody td{border-bottom:1px solid var(--bg-sunken)}
.ws-ro tfoot td{border-top:2px solid var(--border);background:var(--bg-hover)}
.ws-ro tbody tr:hover{background:var(--bg-app)}
/* Counted vs scanned: four figures that have to be read together, so they sit
   in one row rather than being buried in the table below them. */
.ws-poskpis{display:grid;gap:10px;grid-template-columns:repeat(4,1fr)}
@media (max-width:767px){.ws-poskpis{grid-template-columns:repeat(2,1fr)}}
.ws-poskpi{background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:9px 11px}
.ws-poskpi-l{font-size: var(--fs-11);text-transform:uppercase;letter-spacing:.04em;
             color:var(--text-muted);font-weight:600}
.ws-poskpi-v{font-size:20px;font-weight:700;line-height:1.2;color:var(--text);
             margin-top:2px;font-variant-numeric:tabular-nums}
.ws-poskpi-n{font-size:11px;color:var(--text-muted);margin-top:2px}
.ws-poskpi.is-off{border-color:var(--warn-200);background:var(--warn-50)}
.ws-poskpi.is-off .ws-poskpi-v{color:var(--warn-700)}

/* Grids inside modals keep the spreadsheet feel */
.ws-table th{position:sticky;top:0;background:var(--bg-surface);z-index:1}
.ws-table th.calc, .ws-table td.calc{background:var(--bg-hover);color:var(--text)}
.ws-in{width:86px;border:1px solid var(--border-strong);border-radius:6px;padding:3px 6px;font-size:13px;background:var(--warn-50)}
.ws-in:focus{outline:2px solid var(--brand-600);background:var(--bg-surface)}
.ws-in.num{text-align:right;font-variant-numeric:tabular-nums}
.ws-in.is-invalid{border-color:var(--neg-600);background:var(--neg-50)}
textarea.ws-in{width:100%;background:var(--warn-50)}
td.calc.num,[data-sum]{font-variant-numeric:tabular-nums}
.ws-exc{background:var(--warn-50) !important}
.ws-exc-bad{background:var(--neg-100) !important}
fieldset[disabled] .ws-in{background:var(--bg-app)}

@media print{
  .topnav,nav,.btn,.dropdown,.page-actions,.modal{display:none !important}
  .ws-header{position:static}
  .ws-kpi,.ws-card{break-inside:avoid}
}

/* ── Station dashboard KPIs (was inline in admin/station_dashboard.html) ─── */
.sd-kpis{display:grid;gap:12px;grid-template-columns:repeat(6,1fr)}
@media (max-width:1199px){.sd-kpis{grid-template-columns:repeat(3,1fr)}}
@media (max-width:575px){.sd-kpis{grid-template-columns:repeat(2,1fr)}}
.sd-kpi{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:10px 12px}
.sd-kpi-l{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);
          font-weight:600}
.sd-kpi-v{font-size:22px;font-weight:700;line-height:1.2;color:var(--text);margin-top:2px;
          font-variant-numeric:tabular-nums}
.sd-kpi-n{font-size:11px;color:var(--text-muted);margin-top:2px}
.sd-kpi.is-warn{border-color:var(--warn-200);background:var(--warn-50)}

/* ── Day End review queue (was inline in admin/wetstock_hub.html) ─── */
.ws-row-high{background:var(--neg-50)}
.ws-row-med{background:var(--warn-50)}
.ws-pill{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;
         font-size:11px;font-weight:600;border:1px solid transparent;white-space:nowrap}
.ws-pill-ok{background:var(--pos-50);color:var(--pos-700);border-color:var(--pos-200)}
.ws-pill-warn{background:var(--warn-50);color:var(--warn-700);border-color:var(--warn-200)}
.ws-pill-bad{background:var(--neg-50);color:var(--neg-700);border-color:var(--neg-200)}

/* ── Role picker (was inline in admin/_team_fields.html) ─── */
.role-radio input{position:absolute;opacity:0;pointer-events:none;}
.role-radio .role-card{
  border:1.5px solid var(--line);border-radius:10px;padding:10px 12px;cursor:pointer;
  background:var(--bg-surface);
  transition:border-color .12s, box-shadow .12s, background .12s;
}
.role-radio .role-card .check-mark{opacity:0;transition:opacity .12s;}
.role-radio input:checked + .role-card{
  border-color:var(--accent);background:var(--brand-50);
  box-shadow:0 0 0 3px var(--brand-100);
}
.role-radio input:checked + .role-card .check-mark{opacity:1;color:var(--accent)!important;}
.role-radio:hover .role-card{border-color:var(--border-strong);}

/* ── Staff portal doors (was inline in staff_portals.html) ─── */
/* Icons are pinned to the card's own colour: styles.css paints `.nav-item i`
   and friends with the dark sidebar chrome, and a directly-matched rule beats
   an inherited one however specific the ancestor selector is. */
.portal-door{
  display:flex; align-items:flex-start; gap:14px;
  padding:16px 18px; background:var(--bg-surface); border:1px solid var(--border);
  border-radius:14px; text-decoration:none; color:inherit;
  transition:border-color 120ms, box-shadow 120ms, transform 120ms;
}
.portal-door:hover{
  border-color:var(--brand-600); box-shadow:var(--shadow-md);
  transform:translateY(-1px); text-decoration:none; color:inherit;
}
.portal-door:focus-visible{outline:2px solid var(--brand-600); outline-offset:2px}
.portal-door-icon{
  flex:0 0 auto; width:40px; height:40px; border-radius:10px;
  display:flex; align-items:center; justify-content:center;
  background:var(--brand-50);
}
.portal-door-icon i{color:var(--brand-600); font-size:19px; width:auto}
.portal-door-body{display:flex; flex-direction:column; gap:3px; min-width:0}
.portal-door-title{font-weight:700; color:var(--text); font-size:15px}
.portal-door-sub{font-size: var(--fs-12); color:var(--text-muted); line-height:1.45}
.portal-door-go{margin-left:auto; align-self:center; color:var(--text-subtle); font-size:14px}
.portal-door:hover .portal-door-go{color:var(--brand-600)}
@media (prefers-reduced-motion:reduce){
  .portal-door{transition:none}
  .portal-door:hover{transform:none}
}

/* ── busy submit ───────────────────────────────────────────────────────────
 * The submitting button keeps its width and label and gains a spinner, so
 * the row does not jump while the POST runs. */
.btn.is-busy { position: relative; pointer-events: none; opacity: .85; }
.btn-spinner {
  display: inline-block;
  width: .85em;
  height: .85em;
  margin-right: .5em;
  vertical-align: -.1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn-spinner { animation: none; border-right-color: currentColor; opacity: .5; } }

/* ── segmented control ─────────────────────────────────────────────────────
 * A choice among a few views or filters — Summary/Detail, Pending/Accepted,
 * Value/Litres. Seven pages drew this as a button group with the active
 * segment painted as a primary button, so a report opened with three calls
 * to action on screen and the real one was lost among them. One quiet
 * control now: the selected segment is filled, the others are plain. */
.seg {
  display: inline-flex;
  max-width: 100%;
  overflow-x: auto;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--bg-surface);
}
.seg > a, .seg > button {
  flex: none;
  padding: var(--s-1) var(--s-3);
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
  line-height: 1.5;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.seg > a + a, .seg > button + button, .seg > a + button, .seg > button + a { border-left: 1px solid var(--border); }
.seg > a:hover, .seg > button:hover { background: var(--bg-hover); color: var(--text); }
.seg > .is-on, .seg > [aria-current] {
  background: var(--brand-600);
  color: var(--text-invert);
  font-weight: var(--fw-semibold);
}
.seg > .is-on .badge, .seg > [aria-current] .badge { background: rgba(255,255,255,.2); border-color: transparent; color: inherit; }
.seg > a:focus-visible, .seg > button:focus-visible { outline: 2px solid var(--brand-600); outline-offset: -2px; }
.seg-sm > a, .seg-sm > button { padding: 2px var(--s-2); font-size: var(--fs-12); }

/* ── filter bar ────────────────────────────────────────────────────────────
 * The GET form that narrows a list or a report. Thirty-two pages drew it six
 * ways — a card with a row inside, a bare row, an input group with or
 * without an icon, a flex row — and named its button four ways. One surface
 * now: a quiet sunken panel with the fields on a baseline and "Apply
 * filters" at the end. The row grid inside is unchanged. */
.filter-bar {
  padding: var(--s-3) var(--s-4);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.filter-bar > .card-body { padding: 0 !important; }
.filter-bar .form-label { margin-bottom: 2px; color: var(--text-muted); font-size: var(--fs-12); }
.filter-bar .form-control, .filter-bar .form-select { background: var(--bg-surface); }
/* The bare-row variant keeps its own gutters: the row's negative margins are
   what line its columns up with the card under it. */
.filter-bar-flat { margin-left: 0 !important; margin-right: 0 !important; }

/* ── proof-of-payment preview ──────────────────────────────────────────────
 * The uploaded slip or PDF, framed like the panels around it. */
.proof-frame { width: 100%; height: min(640px, 70vh); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-surface); }

/* ── KPI comparison chip ───────────────────────────────────────────────────
 * "+12% vs last month" under a figure. Coloured by whether the move is good
 * for this figure, with an arrow and the sign so it reads without colour. */
.kpi-delta {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-top: var(--s-1);
  padding: 1px var(--s-2) 1px 4px;
  border-radius: var(--r-sm);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  width: fit-content;
}
.kpi-delta .bi { font-size: 14px; line-height: 1; }
.kpi-delta-label { font-weight: var(--fw-regular); color: var(--text-subtle); margin-left: 2px; }
.kpi-delta.is-good { background: var(--pos-50); color: var(--pos-700); }
.kpi-delta.is-bad  { background: var(--neg-50); color: var(--neg-700); }
.kpi-delta.is-flat { background: var(--bg-sunken); color: var(--text-muted); }
.kpi .chart { margin-top: var(--s-2); }

/* A KPI with a coloured accent stripe when it carries a state: the office
   dashboard's action counters. The stripe is the status colour, and the
   figure's own text and label say the same thing. */
.kpi.is-warn { box-shadow: inset 3px 0 0 var(--warn-600); }
.kpi.is-good { box-shadow: inset 3px 0 0 var(--pos-600); }
.kpi.is-bad  { box-shadow: inset 3px 0 0 var(--neg-600); }
.kpi.is-info { box-shadow: inset 3px 0 0 var(--brand-500); }


/* A six-figure row (the station day at a glance): six across on a wide
   screen, three, then two, rather than auto-fit's uneven last row. */
/* The skin's own .kpi-row rule (auto-fit) ties on specificity and loads
   later, so this one names the skin too. */
.kpi-row.kpi-row-6, [data-portal="office"] .kpi-row.kpi-row-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 1399px) { .kpi-row.kpi-row-6, [data-portal="office"] .kpi-row.kpi-row-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 575px)  { .kpi-row.kpi-row-6, [data-portal="office"] .kpi-row.kpi-row-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ── Split-bar legend ──────────────────────────────────────────────────────
 * HTML, not SVG, so the type does not shrink with the drawing on a narrow
 * card. Label, value and share on every row: identity is never colour alone. */
.chart-legend {
  list-style: none;
  margin: var(--s-2) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  font-size: var(--fs-12);
  color: var(--text);
}
.chart-legend li { display: inline-flex; align-items: center; gap: var(--s-1); white-space: nowrap; }
.chart-legend-swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.chart-legend-label { margin-right: 2px; }
a.chart-legend-label { color: inherit; text-decoration: none; }
a.chart-legend-label:hover { text-decoration: underline; }
.chart-legend-value { font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); }
.chart-legend-share { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.chart-legend-share::before { content: "·"; margin-right: var(--s-1); }

