/* dot-components.css — the shared component library for the 2026-07 redesign.
   Loaded globally from _Layout. Every class here is a reusable primitive extracted from a
   claude.ai/design comp implementation; colors come EXCLUSIVELY from the tokens in dot-base.css.
   Typography: ds-* components INHERIT the global fonts (Space Grotesk body / Fraunces headings,
   dot-base.css) — comp font families are NOT adopted (Phil's call, 2026-07-04); a component may
   opt into a mono face for numerals where fixed width matters (e.g. the PIN digits).
   Conventions (docs/web-design-system.md):
     • ds-*    = shared primitives (this file). Add here only on SECOND use ("rule of two") —
                 or when a comp clearly establishes the pattern (card/field/buttons did).
     • <pg>-*  = page-specific classes, in that page's own dot-<page>.css.
     • No raw hex in page css when a token exists; new color roles become tokens first. */

/* ── Card ─────────────────────────────────────────────────────────────────── */

.ds-card {
  width: 100%;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: 16px;
  box-shadow: var(--card-shadow);
  overflow: hidden;
  height: fit-content;
}

.ds-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 24px 28px;
  border-bottom: 1px solid var(--card-line-soft);
}

.ds-card-title {
  margin: 0;
  font-family: "Fraunces", Georgia, serif;
  font-weight: 700;
  font-size: 23px;
  color: var(--ink);
  letter-spacing: -0.2px;
}

.ds-card-body {
  padding: 26px 28px 30px;
}

.ds-pill {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 5px 11px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  white-space: nowrap;
}

/* Sub-line under a ds-card-title — the card's one-sentence purpose. Same measure as the stock-take
   section sub (st-section-sub), promoted with the hero-page convergence (DEV-959 Phase 3a). */
.ds-card-sub {
  margin: 5px 0 0;
  font-size: 12.5px;
  color: var(--ink-soft);
  line-height: 1.5;
  max-width: 60ch;
}

/* A title (and optional sub) at the top of a padded body, with no header row: the form and detail
   cards of the warehouse family (DEV-959 Phase 3b). */
.ds-card-body > .ds-card-title { margin: 0 0 12px; }
.ds-card-body > .ds-card-title + .ds-card-sub { margin: -6px 0 18px; }

/* ── Page column + page header (DEV-959 Phase 3a) ─────────────────────────────
   The one page shell for a sidebar-section page: a centred 1320px measure holding a header
   (eyebrow · Fraunces h1 · lede, actions at the right) and then cards. It is the stock-take
   pattern (st-page / st-pagehead / st-eyebrow / st-title in dot-stocktake.css) promoted under
   ds-* names when the eleven hero-banner pages converged on it; the st-* classes are the same
   rules and repoint here when that family is next touched. Do not write another page header. */
.ds-page {
  min-width: 0;
  width: 100%;
  max-width: 1320px;
  /* Centre the measure inside the sidebar grid track — otherwise the column pins to the sidebar
     and all the slack piles up on the right. */
  margin-inline: auto;
  padding: 8px clamp(16px, 2vw, 32px) 56px;
  display: grid;
  gap: 22px;
  align-content: start;
}

.ds-pagehead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}

/* The text block fills the column; the actions slot keeps only its own width at the right. Without
   this the block shrank to its content and the lede sat in a narrow strip on wide screens while the
   cards below ran the full measure (Phil, 2026-09-08). */
.ds-pagehead > :first-child {
  flex: 1 1 0;
  min-width: 0;
}

.ds-eyebrow {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 700;
  margin-bottom: 7px;
}

.ds-eyebrow a { color: inherit; text-decoration: none; }
.ds-eyebrow a:hover { text-decoration: underline; }

.ds-title {
  margin: 0;
  font-family: "Fraunces", Georgia, serif;
  font-weight: 600;
  font-size: clamp(28px, 4vw, 40px);
  line-height: 0.98;
  letter-spacing: -0.5px;
  color: var(--ink);
}

/* A title that is itself a link (a delivery job's order number opens the sale) reads as the title;
   the underline arrives on hover, where it says "this goes somewhere" without shouting. */
.ds-title a { color: inherit; text-decoration: none; }
.ds-title a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* No measure cap: the lede spans the header block, the same width as the cards under it. */
.ds-pagehead .ds-lede {
  margin: 6px 0 0;
  max-width: none;
  font-size: 14px;
  line-height: 1.6;
}

/* The header's action slot: the view's one primary (ds-btn) and any ghost siblings, top-right. */
.ds-pagehead-actions {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  flex-wrap: wrap;
}

/* ── Text ─────────────────────────────────────────────────────────────────── */

.ds-lede {
  margin: 0 0 26px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink-soft);
}

.ds-lede strong {
  color: var(--ink);
  font-weight: 600;
}

.ds-microlabel {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 9px;
}

.ds-meta {
  font-size: 13px;
  color: var(--ink-soft);
  line-height: 1.55;
}

.ds-meta.dim {
  font-size: 12px;
  color: var(--muted);
}

.ds-hint {
  margin: 9px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

/* ── Fields ───────────────────────────────────────────────────────────────── */

.ds-fields {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}

.ds-field {
  flex: 1;
  min-width: 200px;
}

.ds-select-wrap {
  position: relative;
}

.ds-select-wrap::after {
  content: "▼";
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--muted);
  font-size: 11px;
}

.ds-field select,
.ds-field input,
.ds-field textarea {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  font: inherit;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  border-radius: 10px;
  padding: 12px 14px;
}

/* Promoted with the second multi-line form (Bug Reports New + the Report-an-issue dialog, DEV-755). */
.ds-field textarea {
  resize: vertical;
  min-height: 120px;
  line-height: 1.5;
}

.ds-field select {
  padding-right: 40px;
  cursor: pointer;
}

/* ── Buttons ──────────────────────────────────────────────────────────────── */

.ds-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

/* Primary action — repointed to the platform's orange gradient (design standard, 2026-07-30):
   the .btn recipe from dot-base.css, kept as its own class for the redesigned modules' geometry.
   One per view. The black-primary era briefly introduced by the stock take redesign is over —
   colour carries meaning: orange = the one affirmative action, ghost = secondary, red = danger. */
.ds-btn {
  font: inherit;
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--white);
  background: linear-gradient(180deg, var(--accent), var(--accent-dark));
  border: none;
  border-radius: 14px;
  padding: 14px 22px;
  cursor: pointer;
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.18);
}

.ds-btn:hover {
  /* Lighten both gradient stops on hover — same treatment as .btn.danger:hover. */
  background: linear-gradient(180deg, #d47b40, #a5541f);
}

/* A disabled primary must look disabled. It had no state of its own, so Manual Dispatch's Create
   button read as clickable while no route was chosen (DEV-1248), and every "Saving…" button looked
   live while its request was in flight. Faded, no hover, no pointer. Applies to the ghost too. */
.ds-btn:disabled,
.ds-btn-ghost:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.ds-btn:disabled:hover {
  background: linear-gradient(180deg, var(--accent), var(--accent-dark));
}

.ds-btn-ghost:disabled:hover {
  color: var(--ink-soft);
  border-color: var(--field-line);
}

.ds-btn.grow {
  flex: 1;
  min-width: 200px;
}

/* Compact primary — the affirmative button of an inline form row whose fields are the 38px page
   controls (Add Zone, Add Schedule, Save); the pair of .ds-btn-ghost.sm. Promoted with the hero-page
   convergence (DEV-959 Phase 3a). */
.ds-btn.sm {
  font-size: 13.5px;
  padding: 9px 16px;
  border-radius: 10px;
}

.ds-btn-ghost {
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--field-line);
  border-radius: 11px;
  padding: 14px 22px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}

.ds-btn-ghost:hover {
  color: var(--accent-red);
  border-color: var(--accent-red);
}

/* Either button rendered as a link (Clear, Back, Merge Duplicates, X-Read …) keeps the button geometry
   and loses the link underline — the role is the button's, not the anchor's (DEV-959 Phase 3d). */
a.ds-btn,
a.ds-btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* Compact ghost — table row actions (Alerting comp). */
.ds-btn-ghost.sm {
  font-size: 13px;
  padding: 7px 14px;
  border-radius: 8px;
}

/* Danger-tinted ghost — destructive row actions; quiet until hovered. */
.ds-btn-ghost.danger {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 30%, var(--card));
}

.ds-btn-ghost.danger:hover {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 55%, var(--card));
  background: color-mix(in srgb, var(--danger) 8%, var(--card));
}

/* ── Status panel (tinted info panel with state dot) ─────────────────────── */

.ds-panel {
  border-radius: 12px;
  border: 1px solid var(--card-line-soft);
  background: var(--field-bg);
  padding: 18px 20px;
  margin-bottom: 26px;
}

.ds-panel.good {
  border-color: #dceee3;
  background: #f4faf6;
}

.ds-state {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 600;
}

.ds-state .ds-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}

.ds-state.on { color: var(--success-deep); }
.ds-state.on .ds-dot { background: var(--success); box-shadow: 0 0 0 3px rgba(46, 158, 107, 0.16); }
.ds-state.off { color: var(--ink-soft); font-weight: 400; font-size: 14px; }
.ds-state.off .ds-dot { background: var(--field-line); }

/* Pill form of the state indicator — tinted capsule for table cells (Alerting comp). */
.ds-state.pill {
  gap: 6px;
  font-size: 12.5px;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--card-line);
  background: var(--field-bg);
  white-space: nowrap;
}

.ds-state.pill .ds-dot { width: 6px; height: 6px; box-shadow: none; }

.ds-state.pill.on {
  border-color: color-mix(in srgb, var(--success) 28%, var(--card));
  background: color-mix(in srgb, var(--success) 10%, var(--card));
}

/* DEV-1205 — the two states a connection can be in besides on/off: broken (red — "Disconnected", act on it) and
   needs a look (amber — connected but incomplete). Pill form only, as that is where a state sits in a row. */
.ds-state.pill.bad {
  color: var(--danger);
  font-weight: 600;
  font-size: 12.5px;
  border-color: color-mix(in srgb, var(--danger) 30%, var(--card));
  background: color-mix(in srgb, var(--danger) 8%, var(--card));
}
.ds-state.pill.bad .ds-dot { background: var(--danger); }
.ds-state.pill.warn {
  color: var(--accent-dark);
  font-weight: 600;
  font-size: 12.5px;
  border-color: color-mix(in srgb, var(--accent) 32%, var(--card));
  background: color-mix(in srgb, var(--accent) 9%, var(--card));
}
.ds-state.pill.warn .ds-dot { background: var(--accent); }

/* ── Switch ───────────────────────────────────────────────────────────────────
   A pill toggle for a boolean setting (Till Options comp, 2026-08-10). Promoted
   straight to the shared file rather than living in the page: six settings on one
   page already use it, and every settings screen has booleans.

   It styles a REAL <input type="checkbox"> rather than a div with a click handler,
   which is the whole design of it: the form still posts the value with no JS, the
   control is reachable and operable by keyboard for free, and a screen reader is
   told it is a checkbox because it is one. The comp drew it as a div + onClick —
   that is fine in a mockup and would be a regression here, since the current page
   posts real checkboxes.

   Markup:
     <label class="ds-switch">
       <input type="checkbox" asp-for="X" />
       <span class="ds-switch-track"></span>
       <span class="ds-switch-body"> … label + hint … </span>
     </label>                                                                    */

.ds-switch {
  display: flex;
  gap: 14px;
  cursor: pointer;
}

/* Visually hidden, NOT display:none — a hidden input is unfocusable and unposted. */
.ds-switch > input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ds-switch-track {
  flex: none;
  position: relative;
  margin-top: 2px;
  width: 44px;
  height: 24px;
  border-radius: 999px;
  background: var(--field-line);
  transition: background 0.18s;
}

.ds-switch-track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 1px 2px rgba(23, 23, 26, 0.2);
  transition: left 0.18s;
}

.ds-switch > input:checked + .ds-switch-track { background: var(--accent-dark); }
.ds-switch > input:checked + .ds-switch-track::after { left: 23px; }

/* Focus lands on the invisible input, so the ring has to be drawn on the track. */
.ds-switch > input:focus-visible + .ds-switch-track {
  box-shadow: 0 0 0 3px var(--accent-ring);
}

.ds-switch > input:disabled + .ds-switch-track { opacity: 0.5; cursor: not-allowed; }

.ds-switch-title {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}

/* The parenthetical half of a title ("(block taking over another user's till)") — same line,
   lighter, so the setting's name stays scannable without losing the qualifier. */
.ds-switch-title .soft { font-weight: 500; color: var(--ink-soft); }

/* ── Row grid ─────────────────────────────────────────────────────────────────
   A header row plus zebra data rows sharing one column template, inside a card. Promoted from the
   Native Taxes province grid (ntx-) when Tax Profiles (DEV-499) became the second page to need it —
   the rule of two. Pages set the columns with --ds-rowgrid-cols on both the head and the rows;
   everything else (padding, rules, zebra) is shared here. */
.ds-rowgrid-head,
.ds-rowgrid-row {
  display: grid;
  grid-template-columns: var(--ds-rowgrid-cols, 1fr 1fr);
  gap: 16px;
  align-items: center;
  padding: 0 28px;
}

.ds-rowgrid-head {
  border-bottom: 2px solid var(--card-line);
}

.ds-rowgrid-head .ds-microlabel {
  margin: 0;
  padding: 13px 0;
}

.ds-rowgrid-row {
  border-bottom: 1px solid var(--card-line-soft);
}

.ds-rowgrid-row:nth-child(even) {
  background: var(--card-inset);
}

.ds-rowgrid-row:last-child {
  border-bottom: none;
}

/* ── Sortable column header (DEV-1009) ────────────────────────────────────────
   Rendered by Pages/Shared/_SortHeader.cshtml from a SortColumnViewModel. This layer styles the caret;
   it never DECIDES it. The glyph is text the partial emits from the same object that sets .is-sorted, so
   the two cannot fall out of step — which is exactly what went wrong in the rules this replaces
   (dot-warehouse.css keyed a ::after off .sort-asc/.sort-desc, Manual Dispatch's script set a data
   attribute, and every header read neutral while the table was sorted). A caret that lives only in CSS
   is a caret no test can see.

   Lives here rather than in dot-datagrid.css because that file, despite serving 34 list pages, is NOT
   linked by Orders — which ships its own comp in dot-orders.css. dot-components.css is global via
   _Layout.cshtml, and a clickable header is a ds-* primitive like the pager it sits above. */
/* A link on server-sorted tables, a <button type="button"> on client-sorted ones (DEV-1011). The
   button resets are what let one rule serve both — without them a header renders with the browser's
   default button chrome. */
.ds-sort-link {
  display: flex;
  align-items: center;
  gap: 6px;
  color: inherit;
  font: inherit;
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  width: 100%;
  text-align: inherit;
  cursor: pointer;
}

.ds-sort-link:hover {
  color: var(--ink);
}

.ds-sort-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Dim until sorted: every sortable header carries a caret so the affordance is discoverable, but only
   the active one earns full contrast. */
.ds-sort-caret {
  font-size: 0.72em;
  line-height: 1;
  opacity: 0.3;
}

.ds-sort.is-sorted {
  color: var(--ink);
}

.ds-sort.is-sorted .ds-sort-caret {
  opacity: 1;
  color: var(--accent);
}

/* Numeric columns right-align their cells; the header follows, caret outermost. Both spellings, because
   Orders uses col-num and the shared grid uses data-grid-col-num. */
.ds-sort.col-num .ds-sort-link,
.ds-sort.data-grid-col-num .ds-sort-link {
  justify-content: flex-end;
}

/* ── Per-column filters (ds-filter, DEV-1039) ────────────────────────────────
   A second row inside <thead>, under the sortable headers, so each control sits beneath the column it
   narrows. With eight or ten columns a separate labelled strip above the grid stops being readable —
   you end up matching box to column by counting.

   --ds-filter-top is the one real constraint: `thead th` is position:sticky, so this row needs its own
   offset or it sticks on top of the header above it. Pages whose header row is a different height
   override the variable rather than the rule. (DEV-1011 recorded that this placement could not work on
   a wide table because the boxes scroll out of view inside overflow-x; that was wrong — the column
   scrolls away too, and a filter travelling with its own column is correct.) */
.ds-filter-row th {
  position: sticky;
  top: var(--ds-filter-top, 42px);
  z-index: 1;
  padding: 6px 22px 10px;
  background: #ece4d9;
  border-bottom: 2px solid var(--line-strong);
}

/* Not every table that wants column filters has a sticky header. `.data-grid-table` sticks its headings
   at top:0, which is what the offset above is measured from; `.pc-table` (Admin ▸ Print Jobs) does not.
   On those, a sticky filter row detaches from its own headings and floats over the rows as you scroll —
   worse than not sticking at all, because the boxes end up labelled by whatever row is behind them.

   A modifier rather than a page-local override, so the condition is stated once where the rule is and
   the next page to adopt filters can read it (DEV-1039). */
.ds-filter-row--static th {
  position: static;
}

.ds-filter {
  width: 100%;
  min-width: 80px;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 400;
  /* The header row is uppercase and letter-spaced; a control that inherited that would be unreadable
     at the size people type into. */
  text-transform: none;
  letter-spacing: normal;
  padding: 5px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 7px;
  background: var(--white);
  color: var(--ink);
  outline: none;
}

.ds-filter:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring, rgba(201, 111, 52, 0.15));
}

/* A date range is two inputs in one cell; they stack rather than shrink, because two date controls
   side by side inside a table column are narrower than the date format they have to show. */
.ds-filter-range {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ── Active-filter chips (ds-chip, DEV-1039) ─────────────────────────────────
   One chip per filter that is set, each droppable on its own. A single "Clear" link is the wrong
   control once a list has several: the thing people want is "drop just the date", and with filters
   stacked in a header row the reader may have scrolled past, which ones are on stops being obvious
   from the controls alone. Promoted out of Requisitions, where this shipped page-local in DEV-1010. */
.ds-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 22px 16px;
}

.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--card-line, #ece4d9);
  border-radius: 999px;
  background: var(--card-inset, #f5efe3);
  font-size: 0.8rem;
  white-space: nowrap;
}

.ds-chip-label { color: var(--muted, var(--ink-soft)); font-weight: 600; }
.ds-chip-value { color: var(--ink); }

.ds-chip-x {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 1rem;
  line-height: 1;
  padding: 0 2px;
}

.ds-chip-x:hover,
.ds-chip-x:focus-visible { color: var(--accent-red, #e4322b); }

.ds-chips-clear {
  font-size: 0.82rem;
  color: var(--accent-dark);
  text-decoration: none;
}
.ds-chips-clear:hover { text-decoration: underline; }

/* ── Pager (DEV-959 Phase 2) ──────────────────────────────────────────────────
   The one list pager, rendered by Pages/Shared/_Pager.cshtml from a PagerViewModel. It replaced the
   customers-/orders-/data-grid-pagination, zones-pager, till-pager and msg-pager variants. The default is a
   footer row inside a panel (border-top + the panel inset); .ds-pager--bare drops both for use above a table,
   inside a settings card, or outside any panel. */
.ds-pager {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 16px;
  padding: 14px 26px;
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.ds-pager--bare {
  padding: 10px 0;
  border-top: none;
}

.ds-pager-range {
  font-variant-numeric: tabular-nums;
}

.ds-pager-size {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ds-pager-size select {
  min-height: 32px;
  padding: 0 28px 0 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}

/* Prev / pages / Next / go-to sit together at the right; the range label stays left. */
.ds-pager-nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}

.ds-pager-pages {
  display: flex;
  align-items: center;
  gap: 4px;
}

.ds-pager-page,
.ds-pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.ds-pager-page:hover,
.ds-pager-btn:hover {
  background: rgba(20, 32, 51, 0.06);
}

/* The current page is marked in ink, not the action orange — it is a position, not a call to act. */
.ds-pager-page.is-current {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
  font-weight: 700;
  pointer-events: none;
}

.ds-pager-btn.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.ds-pager-ellipsis {
  min-width: 24px;
  text-align: center;
  user-select: none;
}

.ds-pager-goto {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
}

.ds-pager-goto-input {
  width: 58px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  text-align: center;
}

/* ── Flash banner ──────────────────────────────────────────────────────────
   The result of the thing you just did, at the top of a page — "Saved", "Access
   denied", "mysqldump was not found".

   It lives HERE, in the globally-loaded file, because it is used by 31 pages.
   It used to be defined only in dot-location-management.css and
   dot-user-management.css — two byte-identical copies in PAGE-specific
   stylesheets — so on the other 29 pages the class resolved to nothing and an
   error message rendered as ordinary body text. That is how the S3 "Access
   denied" on Backup & Restore came to look like a sentence rather than a
   failure (DEV-1067).

   The `warning` variant is new. It was referenced by markup and defined by
   neither copy, so it had never had a colour anywhere. */

.flash-banner {
  padding: 12px 18px;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 500;
  /* A left rule as well as a wash: the tint alone is easy to miss on a busy
     page, and it is the whole reason this element exists. */
  border-left: 3px solid transparent;
}

.flash-banner.success {
  background: color-mix(in srgb, var(--success) 12%, transparent);
  border-left-color: var(--success);
  color: var(--success-deep);
}

.flash-banner.warning {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-left-color: var(--accent);
  color: var(--accent-dark);
}

.flash-banner.danger {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-left-color: var(--danger);
  color: var(--danger);
}

/* A banner carrying <code> — mysqldump, Backup:MysqldumpPath — should not have
   the code stand out MORE than the message around it. */
.flash-banner code {
  background: rgba(20, 32, 51, 0.08);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.92em;
}

/* ===========================================================================
   Row selection + bulk action bar (DEV-1083)
   ---------------------------------------------------------------------------
   Promoted out of dot-customers.css on its second use. Customers had a select
   column and a sticky "merge selected" bar; Admin ▸ Users now wants the same
   shape for Disable / Enable / Sign out, and Inventory is the obvious third.
   Copying the block a second time is exactly what the design system forbids,
   so it lives here and both pages point at it.

   The bar is sticky to the BOTTOM rather than floating above the table: the
   selection is made by reading down the rows, so the action belongs where the
   reader's eye ends up, and it stays reachable on a long page without
   covering the row being considered.
   =========================================================================== */

/* Checkbox column. Narrow, centred, and not a click target for the row itself —
   the page script honours [data-no-row-click] on the cell. */
.ds-select-col { width: 40px; text-align: center; cursor: default; }
.ds-select-col input,
.ds-row-select { width: 16px; height: 16px; cursor: pointer; }

.ds-bulk-bar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 26px;
  background: var(--ink);
  color: var(--white);
  border-top: 2px solid var(--accent);
}

/* Both forms, because the two callers hide it differently and display:flex
   above beats either one unaided: Users toggles the [hidden] attribute, the
   Customers merge bar toggles a .hidden class. Without these the bar sits
   there empty on every page load. */
.ds-bulk-bar[hidden],
.ds-bulk-bar.hidden { display: none; }

.ds-bulk-bar-count   { font-weight: 600; }
.ds-bulk-bar-spacer  { flex: 1; }
.ds-bulk-bar .btn    { white-space: nowrap; }

/* A quiet "clear selection" affordance on the dark bar. */
.ds-bulk-bar-clear {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.8);
  text-decoration: underline;
  cursor: pointer;
  font-size: 0.85rem;
}

/* Actions sit at the right-hand end, after the count. */
.ds-bulk-bar-actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}


/* ── Party detail: a business or a vendor (DEV-1052, promoted DEV-1145) ───────
   The detail-and-edit page for something we deal with rather than somebody
   who works here: two columns, the edit form on the left and a sticky aside
   (the contacts under it) on the right, with the party's own reach details
   and its address cards. Written for Clients ▸ Businesses and promoted to
   ds-* the moment Purchasing ▸ Vendors needed the same page — the rule in
   docs/web-design-system.md is promote on second use, never copy a block. */

/* The two columns; same proportions as the contact detail page (dot-customers.css)
   so moving between the three does not move the furniture. */
.ds-detail-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: 24px;
  align-items: start;
}
.ds-detail-aside {
  position: sticky;
  top: 20px;
}
.ds-detail-form > .ds-card + .ds-card { margin-top: 20px; }

/* The row of pills under the page title. */
.ds-head-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}

/* A field that earns twice the width of its neighbours — the party's name. */
.ds-field--wide { flex: 2 1 320px; }

/* A count as a pill rather than a bare number, so "0 contacts" reads as a
   deliberate fact rather than a missing value. The party with nobody under
   it is the one somebody needs to look at, so it is dimmed rather than hidden. */
.ds-count-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--card-line);
  background: var(--field-bg);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.ds-count-pill.is-empty {
  color: var(--muted);
  font-weight: 500;
}

/* A state that is not an error — inactive, closed, retired. Muted, not red. */
.ds-pill--muted {
  color: var(--muted);
  border-color: var(--line);
}

/* The party's own phone / email / fax / website: each type stacks its stored
   values and one blank row, so adding a second number needs no script at all. */
.ds-reach-group { margin-bottom: 18px; }
.ds-reach-group:last-child { margin-bottom: 0; }

.ds-reach-values {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
}
.ds-reach-input {
  width: 100%;
  font: inherit;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  border-radius: 10px;
  padding: 12px 14px;
}
.ds-reach-input:disabled {
  color: var(--ink-soft);
  cursor: default;
}

/* One address card (_LinkedAddressRow.cshtml). Its own block rather than a
   copy of .cust-addr: that card carries the DEV-900 reserve fields and the
   :has() rules that reveal them, none of which a party address has. */
.ds-addr {
  border: 1px solid var(--card-line);
  border-radius: 12px;
  background: var(--field-bg);
  padding: 16px 18px;
  margin-bottom: 14px;
}
.ds-addr:last-child { margin-bottom: 0; }

.ds-addr-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.ds-addr-fields {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.ds-addr-uses {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-top: 12px;
}
.ds-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.88rem;
  color: var(--ink);
}

/* Who belongs to this party: the roster in the aside. */
.ds-roster-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.ds-roster-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--card-line-soft);
}
.ds-roster-row:last-child { border-bottom: none; }

.ds-roster-who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ds-roster-name {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.ds-roster-name:hover { text-decoration: underline; }

.ds-roster-meta {
  font-size: 0.82rem;
  color: var(--ink-soft);
  text-decoration: none;
}

/* The per-row Add/Remove buttons each sit in their own form (nesting forms is
   invalid HTML), so the margin a form would otherwise contribute is removed. */
.ds-roster-action { margin: 0; flex: none; }

.ds-roster-pager { margin-top: 12px; }

/* The add-a-contact picker and the new-contact form, separated from the roster they add to. */
.ds-roster-add {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--card-line-soft);
}
.ds-roster-search {
  display: flex;
  gap: 8px;
  margin: 6px 0 12px;
}
.ds-roster-search input[type="search"] {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  border-radius: 10px;
  padding: 10px 12px;
}
.ds-roster-list--candidates { margin-top: 4px; }

/* A compact inline form in the aside — a new contact's name, phone and email. */
.ds-roster-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 6px 0 0;
}
.ds-roster-form input {
  width: 100%;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  border-radius: 10px;
  padding: 10px 12px;
}
.ds-roster-form .ds-roster-form-actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Narrow screens: the aside drops under the form, the address grid folds. */
@media (max-width: 1200px) {
  .ds-detail-layout { grid-template-columns: 1fr; }
  .ds-detail-aside  { position: static; }
}
@media (max-width: 900px) {
  .ds-addr-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .ds-addr-fields { grid-template-columns: 1fr; }
  .ds-roster-form { grid-template-columns: 1fr; }
}

/* ── Cancelled requisition on a dispatch (ds-cancelled, DEV-1180) ────────────────
   A requisition cancelled while linked to a dispatch stays listed for the record: greyed out, at the
   bottom of its list, with a Cancelled badge (the shared .status-chip.danger) and NO inputs. Used by
   the Picker, Shipping and Operations dispatch-detail pages, so it lives here rather than in any one
   page's css. Colors are tokens only. */

.ds-cancelled {
  opacity: 0.72;
  background: var(--card-inset);
}

/* The latest "Cancelled by {name}: {reason}" line (or "No cancel details recorded"). */
.ds-cancelled-note {
  font-size: 13px;
  color: var(--ink-soft);
}

/* "Cancelled · return item to bin" - old data where units were picked before the cancel. */
.ds-cancelled-return {
  font-size: 13px;
  font-weight: 700;
  color: var(--danger);
}

/* The small "· 1 cancelled" tag on counts and queue cards. */
.ds-cancelled-count {
  color: var(--ink-soft);
  font-weight: 400;
}

/* ── Scan field with a clear (X) button (ds-scan-field / ds-scan-clear, DEV-1152) ─────────
   The handheld scan and quantity fields (Picker, Shipping, Receiving, Stow, Stock take count) wrap the input
   and a Shared/_ScanClearButton in .ds-scan-field. The button is 44px square so a thumb can hit it, sits inside
   the field's right edge, and shows only while the field has a value: :placeholder-shown is false exactly then,
   so no script has to chase every place a page sets .value. A field therefore needs a placeholder.
   wwwroot/js/scan-fields.js does the clearing. Colors are tokens only. */

.ds-scan-field {
  position: relative;
  display: block;
  min-width: 0;
}

/* Room for the button, so a long barcode never runs underneath it. */
.ds-scan-field > input { padding-right: 52px; }

.ds-scan-clear {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.ds-scan-clear:hover,
.ds-scan-clear:focus-visible { background: var(--card-inset); color: var(--ink); }

/* Nothing typed or scanned: nothing to clear. */
.ds-scan-field:has(> input:placeholder-shown) > .ds-scan-clear { display: none; }
