/* ==========================================================================
   MKH - Shared components
   Visual language from the Claude Design project "MKH Accounts & Inventory".
   Requires tokens.css and base.css. Load before any screen stylesheet.

   Anything used by more than one screen lives here. Screen stylesheets hold
   layout for that screen only.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 36px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              filter var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn svg { width: 15px; height: 15px; }
.btn:disabled { opacity: 0.55; }

.btn--block { width: 100%; }
.btn--sm { height: 32px; padding: 0 12px; font-size: var(--fs-sm); }
.btn--lg { height: 46px; padding: 0 20px; font-size: var(--fs-lg); font-family: var(--font-display); font-weight: var(--fw-bold); }

/* Filled buttons are a vertical gradient with a lit top edge - flat fills
   read as unfinished next to the rest of this design. */
.btn--primary {
  background: var(--primary-fill);
  border-color: var(--indigo-700);
  color: #FFFFFF;
  box-shadow: var(--sh-glow), var(--edge);
}
.btn--primary:hover:not(:disabled)  { filter: brightness(1.10); }
.btn--primary:active:not(:disabled) { transform: translateY(1px); }

/* Green fill; the label is white, so this uses the darker end of the ramp. */
.btn--accent {
  background: var(--accent-fill);
  border-color: var(--green-800);
  color: #FFFFFF;
  box-shadow: 0 8px 22px -12px rgba(0, 166, 81, 0.7), var(--edge);
}
.btn--accent:hover:not(:disabled)  { filter: brightness(1.08); }
.btn--accent:active:not(:disabled) { transform: translateY(1px); }

.btn--ghost {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--txt-2);
}
.btn--ghost:hover:not(:disabled) {
  color: var(--txt);
  background: var(--surface-3);
  border-color: var(--line-strong);
}

.btn--subtle { background: var(--primary-soft); color: var(--primary-soft-fg); }
.btn--subtle:hover:not(:disabled) { border-color: var(--primary); }

.btn--danger { background: var(--bad); color: #FFFFFF; }
.btn--danger:hover:not(:disabled) { filter: brightness(1.08); }

/* A button is often an anchor: "Record a bill" navigates rather than submits,
   and it has to look identical to the one beside it that does submit.

   base.css styles every link on hover, and `a:hover` (0,1,1) out-specifies
   `.btn--primary` (0,1,0), so without this the white label is repainted to the
   link colour on an indigo fill - unreadable - and underlined. It bites only
   the filled variants: the ghost button already sets its hover colour through
   `:not(:disabled)`, which scores high enough to win on its own.

   Restated here rather than fixed per page, because the next anchor styled as a
   button would arrive broken too. */
a.btn:hover,
a.btn:focus { text-decoration: none; }

a.btn--primary:hover,
a.btn--accent:hover,
a.btn--danger:hover { color: #FFFFFF; }

a.btn--subtle:hover { color: var(--primary-soft-fg); }

.btn__spinner {
  display: none;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.35);
  border-top-color: #FFFFFF;
  border-radius: var(--r-full);
  animation: mkh-spin 0.7s linear infinite;
}
.btn.is-loading .btn__spinner { display: block; }

/* Square icon-only button. */
.icon-btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--txt-2);
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.icon-btn:hover { color: var(--txt); border-color: var(--line-strong); }
.icon-btn svg { width: 17px; height: 17px; }

/* Quiet variant for inside table rows, where a bordered button per row would
   turn the table into a grid of boxes. */
.icon-btn--quiet {
  width: 30px;
  height: 30px;
  background: none;
  border-color: transparent;
  color: var(--txt-3);
}
.icon-btn--quiet:hover { color: var(--txt); background: var(--surface-3); border-color: var(--line); }

/* --------------------------------------------------------------------------
   Form controls
   -------------------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 7px; margin-bottom: var(--sp-4); }

.field__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--txt-2);
  letter-spacing: 0.01em;
}

.field__control { position: relative; display: flex; align-items: center; }

.field__icon {
  position: absolute;
  left: 12px;
  display: grid;
  place-items: center;
  color: var(--txt-3);
  pointer-events: none;
  transition: color var(--dur-fast) var(--ease);
}
.field__icon svg { width: 16px; height: 16px; }

.input {
  width: 100%;
  height: 38px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--txt);
  font-size: var(--fs-base);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.input:hover:not(:disabled) { border-color: var(--line-strong); }

/* Focus lifts the field off the page background as well as ringing it. */
.input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
  background: var(--surface);
}
.field__control:focus-within .field__icon { color: var(--primary); }

.input:disabled { background: var(--surface-3); color: var(--txt-3); cursor: not-allowed; }

.input--lg { height: 42px; font-size: 14px; }

/* A textarea sizes from its own rows attribute. Without this the fixed 38px
   control height above applies to it as well and every multi-line field is one
   line tall with the rest scrolled out of sight: the declaration, the terms, an
   address. There is an .input--area modifier that opted out of it, but a field
   that is broken unless somebody remembers a second class is broken by default.

   Horizontal resizing is off because a dragged-out textarea escapes its drawer. */
textarea.input {
  height: auto;
  padding-top: 10px;
  padding-bottom: 10px;
  line-height: var(--lh-relaxed);
  resize: vertical;
}

/* A leading icon reserves room automatically. Uses ~ rather than + because a
   visually-hidden <label> often sits between the icon and the input. */
.field__icon ~ .input { padding-left: 38px; }
.input--password { padding-right: 44px; }

.field.is-invalid .input { border-color: var(--bad); }
.field.is-invalid .input:focus { box-shadow: var(--ring-danger); }
.field.is-invalid .field__icon { color: var(--bad); }

.field__error {
  display: none;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--bad-fg);
}
.field.is-invalid .field__error { display: flex; }

.field__hint { font-size: var(--fs-sm); color: var(--warn-fg); }
.field__hint[hidden] { display: none; }
.field__note { font-size: var(--fs-xs); color: var(--txt-3); line-height: 1.5; }

/* A note saying the value as it stands will be refused, before anybody presses
   save to find out. Amber rather than red: nothing has been refused yet. */
.field__note--warn { color: var(--warn-fg); }

/* A value the form shows but does not ask for: the place of supply, which is
   settled by the customer and the delivery address. Deliberately not a disabled
   input - a greyed-out box invites clicking and then refuses, where plain text
   reads as an answer. */
.field__static {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 4px;
  min-height: 34px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--txt-1);
}

/* A control that belongs to the field above but is not part of filling it in:
   a "put it back" button beside a note. Left-aligned with the input rather
   than stretched to its width, because it is an offer, not a step. */
.field__aside { display: flex; margin-top: 2px; }

/* Shown under a suggest box while something is typed that nothing on file
   matches. Reassurance, not a warning: entering a new value is allowed, and
   the note only says what will happen to it. */
.field__note--new {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  color: var(--accent-graphic);
}
.field__note--new svg { width: 13px; height: 13px; flex: none; }


.reveal {
  position: absolute;
  right: 5px;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--r-xs);
  color: var(--txt-3);
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.reveal:hover { color: var(--primary); background: var(--surface-3); }
.reveal svg { width: 16px; height: 16px; }

.field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-5);
}

/* The label is the target, not the 15px box inside it, and without a floor a
   single-line one is 22px tall: under the 24px minimum, let alone what a thumb
   wants. The box stays where it is, aligned to the first line of the text, so
   a label that wraps to two lines is unaffected. */
.checkbox {
  display: inline-flex;
  align-items: flex-start;
  gap: 9px;
  min-height: 34px;
  padding-block: 6px;
  font-size: var(--fs-md);
  color: var(--txt-2);
  cursor: pointer;
  user-select: none;
}
.checkbox input { width: 15px; height: 15px; margin-top: 2px; flex: none; cursor: pointer; }

.check { width: 15px; height: 15px; flex: none; cursor: pointer; }
.check:disabled { cursor: not-allowed; opacity: 0.35; }

/* A choice between a few options, each of which needs explaining. A radio and a
   sentence, rather than a dropdown, because the whole point is that the reader
   can compare them without opening anything. */
.choice-list { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-3); }

.choice {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.choice:hover { border-color: var(--line-strong); }
.choice.is-chosen { border-color: var(--primary); background: var(--primary-soft); }
.choice:focus-within { box-shadow: 0 0 0 3px var(--primary-ring); }
.choice input { margin-top: 3px; flex: none; }
.choice__text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.choice__name { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--txt); }
.choice__why { font-size: var(--fs-md); color: var(--txt-2); }

/* The worked example. Tabular so the figures in two options line up against
   each other, which is the comparison the reader is actually making. */
.choice__example {
  margin-top: 2px;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-sm);
  color: var(--txt-3);
}

/* The save row at the foot of a page of settings, as opposed to a drawer. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.form-actions__note { margin-right: auto; font-size: var(--fs-sm); color: var(--txt-3); }

/* A row of headline figures above a listing. Wraps rather than scrolls, so a
   phone stacks them instead of hiding the second one off the edge. */
.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.stat-card {
  flex: 1 1 200px;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}
/* Four short figures: two to a row on a phone, rather than four cards stacked
   the height of the screen before the list they sum up even starts. */
.stat-row--quad .stat-card { flex-basis: 140px; }

/* A figure that opens the list behind it. A button rather than a card with a
   click handler, so it is reachable by keyboard and reads as pressable to a
   screen reader; the text stays left aligned because a stat card is a label
   above a number, not a caption. */
.stat-card--button {
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.stat-card--button:hover { border-color: var(--primary); }

.stat-card--warn { border-color: var(--warn); background: var(--warn-bg); }
.stat-card--warn .stat-card__label,
.stat-card--warn .stat-card__meta { color: var(--warn-fg); }
.stat-card--warn .stat-card__value { color: var(--warn-fg); }

.stat-card__label {
  display: block;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--txt-3);
}
.stat-card__value {
  display: block;
  margin-top: 3px;
  font-size: 19px;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--txt);
}
.stat-card__meta { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--txt-3); }

/* A quantity and its unit, which belong together. One element so the stacked
   table layout, which spreads a row's children apart, cannot separate them. */
.qty { white-space: nowrap; }
.qty__uom { margin-left: 4px; color: var(--txt-3); font-size: var(--fs-sm); }

/* Colour is the second signal on a movement, never the only one: the sign and
   the Movement column already say which way stock went, so this survives a
   greyscale print and a reader who cannot separate red from green. */
.qty--in  { color: var(--ok-fg); }
.qty--out { color: var(--bad-fg); }
.qty--negative { color: var(--bad-fg); font-weight: var(--fw-semibold); }

/* Two views of one thing, switched in place rather than on separate pages: what
   moved and what that leaves on the shelf are read together. */
.tabs {
  display: flex;
  gap: 2px;
  margin-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.tab {
  padding: 9px 16px;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: none;
  color: var(--txt-2);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.tab:hover { color: var(--txt); }
.tab.is-active { color: var(--primary); border-bottom-color: var(--primary); }
.tab:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-ring); border-radius: var(--r-sm); }

/* A button that reads as a link, for an action offered inside a sentence. */
.link-inline {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: var(--fw-semibold);
  text-decoration: underline;
  cursor: pointer;
}

/* A short read-only breakdown inside a form: a label on the left, a figure on
   the right, and a ruled total. For showing what a record already holds without
   offering an edit that belongs on another screen. */
.stat-list { list-style: none; margin: 0 0 var(--sp-3); padding: 0; }
.stat-list__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-md);
}
.stat-list__row:last-child { border-bottom: 0; }
.stat-list__row--total {
  margin-top: 2px;
  border-top: 1px solid var(--line);
  border-bottom: 0;
  font-weight: var(--fw-semibold);
}
.stat-list__label { color: var(--txt-2); }
.stat-list__value { color: var(--txt); text-align: right; white-space: nowrap; }
.stat-list__meta { margin-left: 6px; font-size: var(--fs-sm); color: var(--txt-3); }

/* A breakdown of the row above it: the areas inside a warehouse. Indented and
   set smaller, with no rule of its own above, so the parts read as belonging to
   their total rather than as more warehouses. */
.stat-list__row--sub {
  padding: 3px 0 3px var(--sp-4);
  border-bottom: 0;
  font-size: var(--fs-sm);
}
.stat-list__row--sub .stat-list__label { color: var(--txt-3); }
.stat-list__row--sub .stat-list__value { color: var(--txt-2); }
.stat-list__row:has(+ .stat-list__row--sub) { border-bottom: 0; }

/* The last part closes the group off from the next warehouse. */
.stat-list__row--sub:has(+ .stat-list__row:not(.stat-list__row--sub)) {
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line-soft);
}

/* Several checkboxes that are expected to be ticked together, one per line.
   .checkbox is inline-flex on its own, which would run them together. */
.checkbox-stack { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-4); }
.checkbox-stack .checkbox { display: flex; }

/* The quiet half of a checkbox label: what ticking it actually means. */
.checkbox__hint {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  font-style: normal;
  color: var(--txt-3);
}

.link-muted { font-size: var(--fs-md); font-weight: var(--fw-semibold); }

/* ---- Select -------------------------------------------------------------- */

.select-wrap { position: relative; display: inline-flex; align-items: center; }
.select-wrap--block { display: flex; width: 100%; }

.select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 36px;
  padding: 0 32px 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--txt-2);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.select:hover { border-color: var(--line-strong); }
.select:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
  background: var(--surface);
}

/* A real element rather than a background image, so it follows the theme. */
.select__chevron {
  position: absolute;
  right: 11px;
  width: 14px;
  height: 14px;
  color: var(--txt-3);
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Card
   -------------------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
}
/* Cards that hold a toolbar or table clip their children to the radius. */
.card--flush { overflow: hidden; }

/* A list opened from a flush card's toolbar has to be able to leave the card.

   The customer picker on the statement is the first searchable list to sit in
   one. Its list is positioned under the box and is up to 264px tall, and the
   card is only as tall as what is in it: before a customer is chosen that is
   a toolbar and one line, so everything after the third name was cut off at
   the card's bottom edge. It looks like something drawn on top of the list,
   and it is not: clipping is not stacking, and no z-index reaches past an
   ancestor that clips. The same thing, and the same answer, as the list in a
   table further down (.table-wrap:has(.combo.is-open)).

   While a list is open the card stops clipping. What the clip was for is the
   corners, so for that moment the first and last things in the card are
   rounded to the card's own inner radius instead; a table's foot has a fill,
   and squared off it would stand proud of the corner it sits in. */
.card--flush:has(> .toolbar .combo.is-open) { overflow: visible; }

.card--flush:has(> .toolbar .combo.is-open) > :first-child {
  border-top-left-radius: calc(var(--r-lg) - 1px);
  border-top-right-radius: calc(var(--r-lg) - 1px);
}

.card--flush:has(> .toolbar .combo.is-open) > :last-child {
  border-bottom-left-radius: calc(var(--r-lg) - 1px);
  border-bottom-right-radius: calc(var(--r-lg) - 1px);
}

.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: 16px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.card__title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.015em;
}
.card__body { padding: 18px; }

/* A head whose second line explains rather than acts. Side by side squeezes the
   title into a column two words wide; a settings card wants it read as a
   heading and a paragraph. */
.card__head--stacked { flex-direction: column; align-items: stretch; gap: 5px; }

/* Set on the sub itself rather than on the stacked head, which for a long time
   was the only kind there was. A head that also acts has a sub too, and without
   this it inherits the body size and colour and reads as loud as the title. */
.card__sub { font-size: var(--fs-md); color: var(--txt-2); }
.card__head--stacked .card__sub { max-width: 68ch; }

/* A head that explains on one side and acts on the other. The text takes what
   the buttons leave and is allowed to shrink past its longest word; the buttons
   keep their own width and wrap among themselves. */
.card__head--split .card__head-text { min-width: 0; flex: 1 1 auto; }
.card__head--split .card__head-actions {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  flex: none;
}

/* Where the card is narrow enough for its table to have stacked, the head has
   no room for two columns either: side by side the title ends up a couple of
   words wide and the buttons pile into a pillar beside it. */
@container content (max-width: 760px) {
  .card__head--split { flex-direction: column; align-items: stretch; }
}
.card__foot {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 13px 18px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* --------------------------------------------------------------------------
   Alerts
   -------------------------------------------------------------------------- */

.alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: var(--sp-4);
  padding: 11px 13px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-md);
  animation: mkh-rise 0.24s var(--ease);
}
.alert[hidden] { display: none; }
.alert svg { width: 17px; height: 17px; margin-top: 1px; }

.alert--danger  { background: var(--bad-bg);  border-color: var(--bad);  color: var(--bad-fg); }
.alert--success { background: var(--ok-bg);   border-color: var(--ok);   color: var(--ok-fg); }
.alert--warning { background: var(--warn-bg); border-color: var(--warn); color: var(--warn-fg); }
.alert--info    { background: var(--info-bg); border-color: var(--info); color: var(--info-fg); }

/* An alert that asks for a decision rather than only stating one: a short
   sentence, what it would move, and the button that moves it. The parts stack
   inside the alert so the button lands under the text it belongs to rather
   than beside an icon at the far left. */
.alert--action .alert__body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.alert__line { margin: 0; font-weight: var(--fw-medium); }
.alert__list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 3px; }
.alert__list li { font-size: var(--fs-sm); }
.alert--action .btn { align-self: flex-start; }

/* --------------------------------------------------------------------------
   Build note - scaffolding that should be deleted once real data lands
   -------------------------------------------------------------------------- */

.note {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--txt-2);
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.note svg { width: 17px; height: 17px; margin-top: 2px; color: var(--txt-3); }
.note code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-3);
  color: var(--primary-soft-fg);
  font-size: var(--fs-xs);
}

.cell-muted { color: var(--txt-3); white-space: nowrap; }

/* --------------------------------------------------------------------------
   Bulk action bar
   -------------------------------------------------------------------------- */

.bulkbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 12px 16px;
  background: var(--primary-soft);
  border-bottom: 1px solid var(--line);
  animation: mkh-fade 0.18s var(--ease);
}
.bulkbar[hidden] { display: none; }
.bulkbar__count { font-size: var(--fs-md); color: var(--primary-soft-fg); }
.bulkbar__count strong { font-family: var(--font-display); font-size: var(--fs-lg); }
.bulkbar__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-left: auto; }

/* --------------------------------------------------------------------------
   Badges

   Pills. Status badges carry a dot as well as a word, so the state survives
   both a greyscale print and a reader who cannot separate red from green.
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
}
/* A badge sitting inline after a name, rather than alone in a cell. */
.badge--inline { margin-left: 7px; vertical-align: middle; }

.badge__dot { width: 6px; height: 6px; flex: none; border-radius: var(--r-full); background: currentColor; }

.badge--paid    { background: var(--ok-bg);   color: var(--ok-fg); }
.badge--overdue { background: var(--bad-bg);  color: var(--bad-fg); }
.badge--low     { background: var(--warn-bg); color: var(--warn-fg); }
.badge--draft   { background: var(--info-bg); color: var(--info-fg); border-color: var(--sky-200); }
.badge--void    { background: var(--mute-bg); color: var(--mute-fg); }
/* A product that plays more than one part. Indigo rather than the accent green,
   which resolves to the same value as --ok-bg and would make this badge
   indistinguishable from "paid"; and deliberately not a warning colour, since
   playing several parts is a normal state rather than a problem. */
.badge--mixed   { background: var(--primary-soft); color: var(--primary-soft-fg); }

/* Uppercase micro-tag, for "System role" / "Custom" style labels. */
.tag {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: var(--r-full);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.tag--system { background: var(--accent-soft); color: var(--accent-soft-fg); }
.tag--custom { background: var(--primary-soft); color: var(--primary-soft-fg); }

/* For a tag that states a fact rather than flagging one: which address is the
   default, which way the tax splits. Coloured like the surface it sits on, so it
   does not compete with the figure it is labelling. */
.tag--quiet {
  background: var(--surface-3, var(--surface-2));
  color: var(--txt-3);
  border: 1px solid var(--line-soft);
}

/* A product's stock against the minimum and maximum set on it. Under the
   minimum is a thing to act on; over the maximum is worth knowing. */
.tag--low  { background: var(--warn-bg); color: var(--warn-fg); }
.tag--high { background: var(--info-bg); color: var(--info-fg); }

/* --------------------------------------------------------------------------
   Avatar - a rounded square, not a circle, matching the mark's own badge
   -------------------------------------------------------------------------- */

.avatar {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 10px;
  background: var(--grad-badge);
  color: #FFFFFF;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
  box-shadow: var(--sh-xs);
  user-select: none;
}
.avatar--sm { width: 30px; height: 30px; border-radius: 9px; font-size: var(--fs-2xs); }
.avatar--round { border-radius: var(--r-full); }

/* --------------------------------------------------------------------------
   Dropdown menu
   -------------------------------------------------------------------------- */

.menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: var(--z-menu);
  min-width: 218px;
  /* Bounded, or a menu holding a sentence rather than short items would
     shrink-to-fit the whole viewport width once it is placed. */
  max-width: min(320px, calc(100vw - 24px));
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-lg);
  animation: mkh-rise 0.16s var(--ease);
}
.menu[hidden] { display: none; }

/* Pinned to the viewport by mkh.placeMenu. The row menus sit inside a card
   that clips to its radius and a table wrapper that scrolls, so an absolutely
   positioned menu on the last row is cut off by both. */
.menu--fixed { position: fixed; top: auto; right: auto; }

.menu__label {
  padding: 7px 10px 5px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--txt-3);
}

.menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border-radius: var(--r-xs);
  font-size: var(--fs-md);
  color: var(--txt);
  text-align: left;
}
.menu__item:hover { background: var(--surface-2); text-decoration: none; }
.menu__item svg { width: 16px; height: 16px; color: var(--txt-3); }
.menu__item--danger { color: var(--bad); }
.menu__item--danger svg { color: currentColor; }
.menu__item--danger:hover { background: var(--bad-bg); }
.menu__item:disabled { color: var(--txt-3); cursor: not-allowed; }
.menu__item:disabled:hover { background: none; }

.menu__sep {
  height: 1px;
  margin: 5px 8px;
  background: linear-gradient(90deg, transparent, var(--line), transparent);
}

.menu__note {
  padding: 6px 10px 4px;
  font-size: var(--fs-xs);
  color: var(--txt-3);
}
.menu__note[hidden] { display: none; }

/* Row action menu.

   Fixed, and re-parented to <body> on init. Two things would otherwise break
   it: .table-wrap scrolls horizontally so it would clip the menu, and .content
   declares container-type, which makes it a containing block for fixed
   descendants and would shift the coordinates. */
.menu--row {
  position: fixed;
  top: 0;
  left: 0;
  right: auto;
  z-index: var(--z-drawer);
  min-width: 224px;
  max-width: min(280px, calc(100vw - 16px));
}

/* --------------------------------------------------------------------------
   Toolbar
   -------------------------------------------------------------------------- */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.toolbar__search { position: relative; display: flex; align-items: center; flex: 1; min-width: 190px; }
.toolbar__search .input { height: 36px; font-size: var(--fs-md); }
.toolbar__search .field__icon { left: 11px; }
/* Wraps rather than running past the card. The group still moves to a row of
   its own before anything inside it wraps, because a flex line breaks on the
   group's full width first. */
.toolbar__end { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }

/* A count with actions beside it, on the narrowest phones: the count takes a
   line and the actions share the next one evenly. Left to wrap on their own, a
   320px screen put the count and Export CSV on one line and Print alone under
   them, and before wrapping was allowed Print ran 13px past the card. */
@container content (max-width: 359px) {
  .toolbar__end:has(> .toolbar__count):has(> .btn) { flex: 1 1 100%; }
  .toolbar__end:has(> .toolbar__count):has(> .btn) > .toolbar__count { flex: 1 1 100%; }
  .toolbar__end:has(> .toolbar__count):has(> .btn) > .btn { flex: 1 1 0; }
}

/* A search box that shares its row with actions as well as filters, and needs a
   floor so the actions give way rather than the box. Stock on hand's Export CSV
   and Print took the room its placeholder had: at tablet width "Search product,
   code or warehouse…" was cut off mid-word. With the floor the actions wrap to a
   row of their own. Capped at the row, so a phone never scrolls sideways for it. */
.toolbar__search--roomy { min-width: min(290px, 100%); }
.toolbar__count { font-size: var(--fs-sm); color: var(--txt-3); white-space: nowrap; }

/* A date in a toolbar, or two that are one question.

   Kept together as one control with its word beside it. Left as bare form
   fields they carried the bottom margin a field has in a form, which sat them
   eight pixels above every other control in the row and made the toolbar
   seventeen taller than it needed to be; they had no label anybody could see;
   and on a phone the two halves of a period wrapped onto separate lines, as
   two unlabelled boxes. The same shape as .activity-filters__dates, which
   does this for the filtered lists. */
.toolbar__dates { display: flex; align-items: center; gap: 6px; min-width: 0; }
.toolbar__dates .input { width: auto; min-width: 0; height: 36px; flex: 1 1 0; font-size: var(--fs-md); }
.toolbar__dates-word { flex: none; font-size: var(--fs-sm); color: var(--txt-3); white-space: nowrap; }

/* On a phone the control takes the row, and the dates share it evenly. */
@container content (max-width: 560px) {
  .toolbar__dates { flex: 1 1 100%; }
}

/* Under a finger the search box and the dates are 42px like every other
   input: see the touch block under "Touch and forced colours". */

/* A dropdown is as wide as its longest option unless told otherwise, and one
   long state or vendor name was enough to take the search box's room: "Dadra
   and Nagar Haveli and Daman and Diu" made a box reading "All states" 317px
   wide and left the search too narrow for its own placeholder. Capped where
   the toolbar runs across the card. On a phone each control has a row of its
   own and keeps its width. The list still opens as wide as what is in it.

   Over 560 rather than from 561 up. The width is the room inside the page's
   padding and is seldom a whole number: at 560.5 the toolbar had stopped being
   a phone's and the cap had not begun. */
@container content (width > 560px) {
  .toolbar .select-wrap { max-width: 14rem; }
}

/* --------------------------------------------------------------------------
   Advanced filters
   -------------------------------------------------------------------------- */

.filters {
  padding: 16px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-soft);
  animation: mkh-fade 0.18s var(--ease);
}
.filters[hidden] { display: none; }
.filters__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
.filters__field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.filters__label { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--txt-2); }
.filters__field .select { background: var(--surface); }
.filters__foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); margin-top: 14px; }

.filterbtn__count {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: var(--primary);
  color: #FFFFFF;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  font-family: var(--font-mono);
}
.filterbtn__count[hidden] { display: none; }

/* Keep applied filters visible once the panel is collapsed - otherwise a
   filtered list looks like an empty one. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.chips[hidden] { display: none; }
.chips__label { font-size: var(--fs-sm); color: var(--txt-3); margin-right: var(--sp-1); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 5px 0 11px;
  border-radius: var(--r-full);
  background: var(--primary-soft);
  color: var(--primary-soft-fg);
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.chip__label { font-weight: var(--fw-semibold); }
.chip__x {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: var(--r-full);
  color: inherit;
  opacity: 0.7;
}
.chip__x:hover { opacity: 1; background: rgba(0, 0, 0, 0.10); }
.chip__x svg { width: 11px; height: 11px; }

/* --------------------------------------------------------------------------
   Table
   -------------------------------------------------------------------------- */

/* Wide tables scroll inside this, never pushing the page sideways.
   position:relative is load-bearing: a .sr-only child is absolutely
   positioned, and without a positioned ancestor it escapes this scroller and
   drags the document's scrollWidth out with it. */
.table-wrap { position: relative; overflow-x: auto; }

/* A dropdown opened inside a table has to be able to leave it. A box that
   scrolls sideways clips in both directions, so the list of areas under the
   last line of a credit note was cut off at the table's bottom edge, with a
   scrollbar where the options should have been; raising the list's z-index
   cannot help, because clipping is not stacking. While a list is open the
   wrapper stops being a scrolling box. It only ever scrolls a table too wide
   for its card, and nobody scrolls sideways in the middle of a choice. A capped
   table is left alone: it scrolls up and down on purpose, and letting its rows
   spill out over the page would be worse than a list that scrolls with them. */
.table-wrap:not(.table-wrap--tall):has(.combo.is-open) { overflow: visible; }

.table { width: 100%; font-size: var(--fs-base); }

.table thead tr { background: var(--surface-2); }
.table thead th {
  padding: 11px 14px;
  text-align: left;
  font-size: 10.5px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--txt-3);
  white-space: nowrap;
}

/* A heading sits over the same end of the column as the figures under it.

   The rule above is `.table thead th`, one class and two elements, and it beats
   a bare `.cell-amount` on specificity: every money and quantity column in the
   application was right-aligning its figures and labelling them from the left.
   The printed invoice hit this and fixed it locally; the screen tables never
   did. Twenty-five headings carry the class. */
.table thead th.cell-amount { text-align: right; }

.table tbody tr {
  border-top: 1px solid var(--line-soft);
  transition: background-color var(--dur-fast) var(--ease);
}
.table tbody tr:hover { background: var(--surface-2); }
.table tbody tr.is-selected { background: var(--primary-soft); }
.table tbody td { padding: 11px 14px; vertical-align: middle; }

.table .col-check { width: 44px; padding-left: 16px; padding-right: 0; }
/* --------------------------------------------------------------------------
   Picking rows
   --------------------------------------------------------------------------
   A tick column for the printed pack that goes to the auditor. Narrow, because
   the box is the whole column, and left-aligned so the ticks line up down the
   edge of the table where they can be run down quickly. */
.table .col-pick { width: 48px; padding-left: 9px; padding-right: 0; }

/* The label is the tap target, not the box inside it. A 16px checkbox is well
   under what a finger can be asked to hit, and in a column this narrow a near
   miss lands on nothing at all, so the label carries a minimum of its own and
   the box sits in the middle of it. */
.pick {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  min-width: 34px;
  min-height: 34px;
}

.pick input { width: 16px; height: 16px; margin: 0; cursor: pointer; accent-color: var(--accent); }

/* A ticked row is tinted rather than outlined: an outline on one row of a long
   table reads as a focus ring somebody has lost track of. */
.table tbody tr.is-picked { background: var(--accent-soft); }

/* A row that belongs to the row above it: the areas a returned line's goods go
   into or come from. No rule between the two and no padding above, so it reads
   as the bottom of that line rather than as the next one. */
.table tbody tr.table__sub { border-top: 0; }
.table tbody td.cell-areas { padding-top: 0; }

.table .col-actions { width: 96px; padding-right: 16px; }
.table__actions { display: flex; align-items: center; gap: 5px; justify-content: flex-end; }

.table .num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.table__empty td {
  padding: 46px 20px;
  text-align: center;
  color: var(--txt-3);
  font-size: var(--fs-base);
}

/* A figure that is also the way in to what is behind it: the stock on hand,
   which opens the warehouses it is spread across.

   Deliberately quiet. It sits in a column of plain figures and has to read as
   one of them first and a control second, or a table of twenty rows becomes
   twenty buttons. The pin is what says it can be pressed; the count beside it
   says whether pressing is worth it, so a product held in one place answers the
   question without being opened at all.

   The figure comes last so it keeps the column's alignment: its digits line up
   with the plain figures above and below, and the count and pin hang off the
   left. The pin between them is not decoration - with the count next to the
   figure, "2" beside "1,215" reads as "21,215". */
.stock-where {
  display: inline-flex;
  align-items: center;

  /* The figure has to end where the plain figures end. This button is a child of
     .person__text, a column flex container, so it is stretched to the full width
     of the cell however little is in it; left to pack from the start, the number
     drifted left by however wide that row's pin and count happened to be, while
     the unit underneath stayed right-aligned. Every row with a breakdown sat a
     few pixels out of the column, each by a different amount. */
  justify-content: flex-end;

  gap: 4px;

  /* The padding is the finger and the negative margin gives the row its height
     back: a line of text is about 19px tall, which is under the 24px a thumb
     needs, and growing every stock row by 8px to buy that would be paid for on
     every screen by everyone. */
  padding: 5px 0;
  margin: -5px 0;

  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.stock-where__pin {
  width: 11px;
  height: 11px;
  flex: none;
  color: var(--txt-3);
  transition: color var(--dur-fast) var(--ease);
}

/* Not a badge: a badge would out-weigh the figure it belongs to. Just the
   number, small, in the colour the unit underneath already uses. */
.stock-where__count {
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--txt-3);
  transition: color var(--dur-fast) var(--ease);
}

.stock-where:hover .stock-where__pin,
.stock-where:hover .stock-where__count { color: var(--accent-soft-fg); }
.stock-where:hover .cell-mono { text-decoration: underline; text-underline-offset: 2px; }

/* The global :focus-visible rule in base.css already draws the indicator; this
   only opens the offset a little so the ring clears the pin. */
.stock-where:focus-visible { outline-offset: 3px; }

/* Identity cell - avatar plus name and secondary line. */
.person { display: flex; align-items: center; gap: 11px; min-width: 0; }
/* The children are spans, so the stack has to be declared. */
.person__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.person__name {
  font-weight: var(--fw-semibold);
  color: var(--txt);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.person__meta {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--txt-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A name that opens something about its row, such as a salesperson's
   statement. Reads as the name it is and answers the pointer the way a link
   does. The padding buys a 25px target and the margin gives the row its height
   back, the same trade the stock figure makes. */
/* A document number that is a link gets the same finger-sized target. */
a.person__name {
  padding-block: 3px;
  margin-block: -3px;
}

.person__open {
  padding: 3px 0;
  margin: -3px 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: var(--fw-semibold);
  text-align: left;
  cursor: pointer;
}
.person__open:hover { color: var(--primary-hover); text-decoration: underline; text-underline-offset: 2px; }
.person__open:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--r-xs); }

/* Audit cell - who last changed the row, and when.
   Secondary information, so it is quieter than the name beside it and it is
   the first column to go when the table runs out of room. */
.audit { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.audit__who {
  font-size: var(--fs-sm);
  color: var(--txt-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.audit__when { font-size: var(--fs-xs); color: var(--txt-3); white-space: nowrap; }

/* The pair as a link to the record's history, for somebody allowed to read the
   audit log. The same box as the plain pair, so no listing moves: only the
   hover says it goes anywhere, and it is the name that lights up. */
.audit--link,
.audit--link:hover { text-decoration: none; }
.audit--link:hover .audit__who,
.audit--link:focus-visible .audit__who {
  color: var(--primary-hover);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.audit--link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--r-xs); }

/* The server renders UTC and app.js rewrites it on the reader's clock. The
   dotted underline marks it as carrying a tooltip with the time zone. */
/* A timestamp on the company clock. The dotted underline only appears where
   there is a tooltip worth reading, which is the audit column. */
.stamp { border-bottom: 1px dotted transparent; }
.audit__when .stamp { cursor: help; }
.audit__when .stamp:hover { border-bottom-color: var(--line-strong); }

/* Keyed to the content container rather than the viewport: the nav rail takes
   about 178px, so the same window is a different amount of room depending on
   whether it is open. Below this the table is already tight and the audit
   column is the least important thing in the row, so it goes rather than
   forcing a sideways scroll. The information is still on the record. */
/* Dropped while the layout is still a table and the columns are competing for
   width. A stacked row has a line to spare and brings it back; see the stacked
   table section below. */
@container content (max-width: 1000px) {
  .table .col-audit { display: none; }
}

@supports not (container-type: inline-size) {
  @media (max-width: 1180px) {
    .table .col-audit { display: none; }
  }
}

/* An identity column carrying a name, a code and sometimes a second name. Left
   to itself it sizes to the longest legal name in the list and pushes a wide
   table sideways, so it is capped and the text ellipsises instead. */
.table .col-party { max-width: 240px; }

/* Columns a wide table can do without before it starts scrolling. The value is
   still on the row when it stacks, still searchable, and still on the edit
   panel; it is only the at-a-glance column that goes. */
@container content (max-width: 1150px) {
  .table .col-secondary { display: none; }
}

@supports not (container-type: inline-size) {
  @media (max-width: 1330px) {
    .table .col-secondary { display: none; }
  }
}

/* --------------------------------------------------------------------------
   Stacked table

   Below the card breakpoint a six-column table is the wrong object: dropping
   columns loses information and sideways scrolling hides it. Each row becomes
   a labelled block instead, which keeps every field visible without a bespoke
   card layout per module. The Users screen predates this and has its own
   hand-built cards, which carry an avatar this cannot.

   The header cells supply the labels through data-label on each td, so the
   label and the column it names cannot drift apart.
   -------------------------------------------------------------------------- */

@container content (max-width: 760px) {
  .table--stack thead { display: none; }

  /* The table has to stop being a table. Left as display:table it keeps
     shrink-to-fit sizing from its cell contents and grows past the wrapper,
     which is the sideways scroll this layout exists to remove. */
  .table--stack,
  .table--stack tbody { display: block; width: 100%; }

  /* Long values wrap here rather than being clipped or ellipsised: there is a
     whole line for them, and a half-shown customer name is worse than two
     lines. */
  .table--stack tbody .person__name,
  .table--stack tbody .person__meta,
  .table--stack tbody .audit__who,
  .table--stack tbody .audit__when {
    white-space: normal;
    overflow: visible;
    overflow-wrap: anywhere;
  }

  .table--stack tbody tr {
    display: block;
    padding: 12px 16px 14px;
    position: relative;
  }

  .table--stack tbody td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    padding: 3px 0;
    text-align: right;
  }

  .table--stack tbody td::before {
    content: attr(data-label);
    flex: none;
    font-size: 10.5px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--txt-3);
    text-align: left;
  }

  /* The first cell is the row's identity: it reads as a heading, not as one
     more labelled field. */
  .table--stack tbody td:first-child {
    display: block;
    padding: 0 34px 6px 0;
    text-align: left;
  }
  .table--stack tbody td:first-child::before { content: none; }

  /* The row menu goes to the top right corner, level with that heading. */
  .table--stack tbody td:last-child {
    position: absolute;
    top: 10px;
    right: 12px;
    padding: 0;
  }
  .table--stack tbody td:last-child::before { content: none; }

  /* A stacked table whose last column is data rather than a row menu.
     The rule above pins the last cell to the top right corner because that is
     where the actions belong, and a table without an actions column gets its
     final field thrown over the row's heading instead - which is what happened
     to the amount box on a receipt, landing it on top of the bill number and
     losing its label with it. */
  .table--stack.table--stack-all tbody td:last-child {
    position: static;
    display: flex;
    padding: 3px 0;
  }
  .table--stack.table--stack-all tbody td:last-child::before { content: attr(data-label); }

  /* The row under a line is one cell and a form, not a labelled figure: laid
     out as a block, without the label the rules above would give it. */
  .table--stack tbody tr.table__sub { padding-top: 0; }
  .table--stack.table--stack-all tbody td.cell-areas {
    display: block;
    padding: 0;
    text-align: left;
  }
  .table--stack.table--stack-all tbody td.cell-areas::before { content: none; }

  /* The tick goes to the top left corner, opposite the row menu, so the row
     keeps its heading. Without this the checkbox cell would be the first child
     and would take the heading treatment itself, pushing the customer name down
     among the labelled fields where it reads as one detail out of eight. */
  /* Offset by the padding the label now carries, so the box itself stays in the
     corner it was in while the area around it that answers a tap grows. */
  .table--stack tbody td.col-pick {
    position: absolute;
    top: 2px;
    left: 3px;
    display: block;
    width: auto;
    padding: 0;
  }
  .table--stack tbody td.col-pick::before { content: none; }

  .table--pick tbody tr { padding-left: 42px; }

  /* Which makes the cell after it the row's identity. */
  .table--pick tbody td.col-pick + td {
    display: block;
    padding: 0 34px 6px 0;
    text-align: left;
  }
  .table--pick tbody td.col-pick + td::before { content: none; }

  /* An address or a description can be long; let it wrap under its label. */
  .table--stack tbody td.cell-wrap { display: block; text-align: left; }
  .table--stack tbody td.cell-wrap::before { display: block; margin-bottom: 2px; }

  /* The columns a narrow table drops come back here. A stack has a line to
     spare for each of them, so nothing about the row is hidden on a phone -
     which is the whole point of stacking rather than scrolling sideways. */
  .table--stack tbody td.col-audit,
  .table--stack tbody td.col-secondary { display: flex; }

  /* The cap that keeps the identity column from widening a table is pointless
     once the row is a block, and it would truncate the name for no reason. */
  .table--stack tbody td.col-party { max-width: none; }
}

/* --------------------------------------------------------------------------
   Pagination
   -------------------------------------------------------------------------- */

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 13px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
}
.pagination__info { font-size: var(--fs-sm); color: var(--txt-3); }
.pagination__info strong { color: var(--txt-2); }
.pagination__pages { display: flex; align-items: center; gap: 6px; }

.page-btn {
  display: grid;
  place-items: center;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--surface);
  color: var(--txt-3);
  font-size: var(--fs-sm);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.page-btn svg { width: 14px; height: 14px; }
.page-btn:hover:not(:disabled) { color: var(--txt); border-color: var(--line-strong); }
.page-btn[aria-current="page"] {
  background: var(--primary-fill);
  border-color: var(--indigo-700);
  color: #FFFFFF;
  font-weight: var(--fw-bold);
}
.page-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* --------------------------------------------------------------------------
   Slide-over panel
   Its own scrim, independent of the nav drawer in layout.css - that one only
   exists below 900px, whereas this must work at every width.
   -------------------------------------------------------------------------- */

.panel-scrim {
  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-scrim);
  background: rgba(9, 10, 26, 0.55);
  backdrop-filter: blur(2px);
  animation: mkh-fade 0.2s var(--ease);
}
/* Shown while a slide-over is open. The panel says so itself, by carrying
   is-open on the drawer this scrim sits directly in front of, so there is one
   piece of state and no attribute for a page to remember to set: the app has
   twenty-six of these panels and nothing ever set one. Matched through the
   sibling rather than on the body, because a register that offers two panels
   renders two scrims, and both would darken the page while only one of them
   closed anything.

   Until this, every scrim rendered at display:none. Clicking away from a panel
   did nothing, because the click landed on an element that was not there, and
   the page went on scrolling behind an open panel. The drawer scrolls its own
   body, so holding the page still costs nothing. */
.panel-scrim:has(+ .drawer.is-open) { display: block; }
body:has(.drawer.is-open) { overflow: hidden; }

/* Kept for a browser without :has(), where the panel behaves as it always has:
   no scrim, and closed by its own button or by Escape. */
body[data-panel="open"] .panel-scrim { display: block; }
body[data-panel="open"] { overflow: hidden; }

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-drawer);
  display: flex;
  flex-direction: column;
  width: min(440px, 100%);
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: var(--sh-lg);
  transform: translateX(100%);
  transition: transform var(--dur-slow) var(--ease);
}
/* Which drawer is showing is per-drawer, not per-page: a screen can hold more
   than one. The body attribute only drives the shared scrim and scroll lock. */
.drawer.is-open { transform: translateX(0); }

/* For a form long enough that the extra width buys a second column of short
   fields and saves a lot of scrolling. Still capped by 100% on a phone. */
.drawer--wide { width: min(560px, 100%); }

/* For a panel holding a table rather than a form. A party's activity has five
   columns and a pager under them, and at 560px the figures start wrapping
   inside their own cells. Still capped by 100%, so on a phone it is the whole
   screen like every other drawer. */
.drawer--full { width: min(880px, 100%); }

.drawer__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex: none;
  padding: 18px;
  border-bottom: 1px solid var(--line);
}
.drawer__title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.drawer__sub { margin-top: 4px; font-size: var(--fs-sm); color: var(--txt-3); }

.drawer__body { flex: 1; min-height: 0; overflow-y: auto; padding: 18px; }

/* Two short fields side by side inside a panel. auto-fit with a floor rather
   than a fixed two-column grid, so on a narrow phone the pair stacks instead
   of squeezing both into half a screen.

   The floor is set by the widest thing that goes in one of these: a 15
   character GSTIN in the monospace face. Below that it would be typed into a
   box that cannot show what was typed. */
.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 12px;
}

/* A group of fields under a heading, for a form long enough that one flat run
   of labels stops being readable. */
.field-group { margin: 0 0 var(--sp-2); }
.field-group__title {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: var(--sp-2) 0 var(--sp-4);
  font-size: 10.5px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--txt-3);
}
.field-group__title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-soft);
}
.field-group:first-child .field-group__title { margin-top: 0; }

/* Fields that only exist because a switch above them is on. Indented and ruled
   so they read as belonging to that switch rather than as the next thing on the
   form. */
.field-group__inset {
  margin-top: var(--sp-3);
  padding-left: var(--sp-4);
  border-left: 2px solid var(--line-soft);
}

/* One repeated block in a list of them: a customer's delivery address, one of
   the company's registered addresses. Boxed so the eye can tell where one ends
   and the next begins, which a run of bare fields cannot say. */
/* The areas of a warehouse: one line each, because a building of racks can
   have dozens and a card per area would be a scroll per rack. The name takes
   the width; the active tick and the remove button keep their own. */
.area-list {
  display: grid;
  gap: 8px;
  margin: var(--sp-3) 0 var(--sp-2);
  padding: 0;
  list-style: none;
}
.area-row { display: flex; align-items: center; gap: 10px; }
.area-row .input { flex: 1 1 auto; min-width: 0; }
.area-row__active { flex: none; }
.area-row.is-off .input { color: var(--txt-3); }

/* Where one document line's stock goes, in a warehouse divided into areas.
   Tucked under the line rather than given a column: most companies never
   divide a warehouse, and the ones that do put most lines in one place, so the
   usual case is one picker and the split is there when a delivery spills over.

   The picker is held to a width an area name needs. Stretched across the whole
   line it would read as a second product field. */
.area-split { display: grid; gap: 6px; }

.area-split__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--txt-2);
  letter-spacing: 0.01em;
}

.area-split__one {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}
.area-split__one .area-split__pick { flex: 0 1 260px; min-width: 0; }

.area-split__rows {
  display: grid;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The same widths as the line above it where there is room: the area where
   the product is, the quantity under the quantity. */
.area-split__row {
  display: grid;
  grid-template-columns: minmax(0, 260px) 130px 34px;
  align-items: center;
  gap: var(--sp-3);
}
.area-split__remove { justify-self: end; }

.area-split__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
}

.area-split__tally {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--txt-3);
}
.area-split__tally.is-off { color: var(--warn-fg); font-weight: var(--fw-semibold); }

.area-split__short {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--warn-fg);
}

/* A phone: the picker takes what is left beside the quantity, and a lone
   picker takes the row with its split button under it. */
@container content (max-width: 560px) {
  .area-split__one .area-split__pick { flex: 1 1 100%; }
  .area-split__row { grid-template-columns: minmax(0, 1fr) 96px 34px; gap: var(--sp-2); }
}

@supports not (container-type: inline-size) {
  @media (max-width: 620px) {
    .area-split__one .area-split__pick { flex: 1 1 100%; }
    .area-split__row { grid-template-columns: minmax(0, 1fr) 96px 34px; gap: var(--sp-2); }
  }
}

.stack-row {
  margin-bottom: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

/* Wraps, because the head can carry a title, a Default tag and two buttons, and
   at 375px those do not fit on one line. */
.stack-row__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}

.stack-row__title {
  margin: 0;
  margin-right: auto;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--txt-3);
}

/* Said in the place the thing would be, rather than in a banner at the top. */
.empty-note {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--txt-3);
}

.icon-btn--sm { width: 26px; height: 26px; }
.icon-btn--sm svg { width: 14px; height: 14px; }

.drawer__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 9px;
  flex: none;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
}
.drawer__foot .btn { height: 38px; }

/* --------------------------------------------------------------------------
   Switch
   -------------------------------------------------------------------------- */

/* The whole label is the target, and without a floor it is only as tall as the
   22px track it contains, which is under what a thumb can reliably find. The
   track keeps its size; the label grows around it. */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 34px;
  cursor: pointer;
}
.switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.switch__track {
  position: relative;
  width: 40px;
  height: 22px;
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  background: var(--surface-3);
  transition: background-color var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease);
}
.switch__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: var(--r-full);
  background: #FFFFFF;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: left var(--dur-base) var(--ease);
}
.switch input:checked + .switch__track { background: var(--accent-fill); border-color: var(--green-800); }
.switch input:checked + .switch__track::after { left: 20px; }
.switch input:focus-visible + .switch__track { box-shadow: 0 0 0 3px var(--primary-ring); }

/* A switch standing in for a field's control, so it lines up with the inputs
   beside it rather than sitting tight under the label. */
.switch--field { min-height: 38px; }
.switch__label { font-size: var(--fs-md); color: var(--txt-2); }

/* --------------------------------------------------------------------------
   Tabs
   -------------------------------------------------------------------------- */

/* The line under the strip is drawn inside it rather than as its border. The
   strip scrolls sideways on a phone, and a scrolling box clips at its padding
   edge: a tab pulled down one pixel to lay its underline over a border stuck
   out of that edge, and every tab strip grew a vertical scrollbar a pixel
   deep. Drawn inside, the underline covers the line without leaving the box. */
.tabs {
  display: flex;
  gap: var(--sp-1);
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--line);
  overflow-x: auto;
  overflow-y: hidden;
}
.tab {
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: 0;
  border-bottom: 2px solid transparent;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--txt-2);
  white-space: nowrap;
}
.tab:hover { color: var(--txt); text-decoration: none; }
.tab[aria-selected="true"] { color: var(--primary); border-bottom-color: var(--primary); }

/* --------------------------------------------------------------------------
   Empty state
   -------------------------------------------------------------------------- */

.empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 46px 20px; }
.empty__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: var(--sp-4);
  border-radius: var(--r-md);
  background: var(--primary-soft);
  color: var(--primary-soft-fg);
}
.empty__icon svg { width: 24px; height: 24px; }
.empty__title { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: var(--fw-bold); margin-bottom: var(--sp-2); }
.empty__text { max-width: 46ch; font-size: var(--fs-md); color: var(--txt-2); margin-bottom: var(--sp-4); }

/* --------------------------------------------------------------------------
   Toast
   -------------------------------------------------------------------------- */

.toast {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border: 1px solid var(--ok);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--sh-lg);
  font-size: var(--fs-md);
  color: var(--txt);
  animation: mkh-slide-r 0.24s var(--ease);
}
.toast[hidden] { display: none; }
.toast__icon {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--ok-bg);
  color: var(--ok-fg);
}
.toast__icon svg { width: 13px; height: 13px; }

/* --------------------------------------------------------------------------
   Touch and forced colours
   -------------------------------------------------------------------------- */

/* 16px stops iOS Safari zooming the page when a field takes focus. */
@media (max-width: 860px) {
  .input { font-size: 16px; }
  .select { font-size: 16px; }
}

@media (pointer: coarse) {
  .input { height: 42px; }
  /* The toolbar's search box and date boxes are set to 36px by rules more
     specific than the one above, so they are named here as well. Without this
     they stayed 36px beside 42px dropdowns on every list and every report. The
     search icon is centred by the flex row it sits in, so it follows the box. */
  .toolbar__search .input { height: 42px; }
  .toolbar__dates .input { height: 42px; }
  .select { height: 42px; }
  .btn { height: 42px; }
  .btn--sm { height: 38px; }
  /* A side panel's Cancel and Save are set to 38px by a rule more specific
     than the one above, so under a finger they stayed smaller than every
     other button in the app until they were named here too. */
  .drawer__foot .btn { height: 42px; }
  .reveal { width: 38px; height: 38px; }
  .icon-btn { width: 42px; height: 42px; }
  .icon-btn--quiet { width: 38px; height: 38px; }
  /* Row and permission checkboxes are hit directly, with no label wrapping
     them to widen the target. */
  .check, .checkbox input { width: 20px; height: 20px; }
  .link-muted { padding-block: var(--sp-2); }
}

@media (forced-colors: active) {
  .input, .btn, .alert, .card, .menu, .badge { border: 1px solid currentColor; }
}

/* When the filters or chips close out a card, the divider has nothing below
   it to separate from. */
.filters:last-child,
.chips:last-child { border-bottom: 0; }

/* Invisible catcher that dismisses an open row menu on the next click, without
   a global JS listener fighting Blazor over the DOM. */
.menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-menu) - 1);
}

/* ==========================================================================
   Reconnect modal

   Shown when the Blazor circuit drops. Blazor owns the state: it puts
   .components-reconnect-show / -retrying / -failed / -paused / -resume-failed
   on the dialog and fills #components-seconds-to-next-attempt with the
   countdown. Every state is present in the markup at once, so the rules below
   are what decide which one reaches the screen.
   ========================================================================== */

/* Everything is hidden until a state asks for it. No element carries its own
   `display` outside these rules, or it would leak into the wrong state. */
.reconnect__state,
.reconnect__icon,
.reconnect__actions,
.components-rejoining-animation,
#components-reconnect-button,
#components-resume-button,
#components-reload-button { display: none; }

/* Copy blocks.

   `retrying` is added ON TOP of `show`, not instead of it - Blazor only ever
   clears the whole set at once - so the first-attempt copy has to opt out of
   it explicitly or both messages appear together. Every other state is
   exclusive already. */
#components-reconnect-modal.components-reconnect-show:not(.components-reconnect-retrying) .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible {
  display: block;
}

/* The activity bars belong to the two "still trying" states only. */
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation {
  display: flex;
}

/* A static icon replaces the animation once Blazor has stopped trying. */
#components-reconnect-modal.components-reconnect-failed .reconnect__icon--warn,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect__icon--warn,
#components-reconnect-modal.components-reconnect-paused .reconnect__icon--pause {
  display: grid;
}

#components-reconnect-modal.components-reconnect-failed #components-reconnect-button,
#components-reconnect-modal.components-reconnect-failed #components-reload-button,
#components-reconnect-modal.components-reconnect-paused #components-resume-button,
#components-reconnect-modal.components-reconnect-resume-failed #components-resume-button,
#components-reconnect-modal.components-reconnect-resume-failed #components-reload-button {
  display: inline-flex;
}

/* The two "still trying" states offer no buttons, so the row itself goes too -
   an empty flex row would still contribute the card gap above it. */
#components-reconnect-modal.components-reconnect-failed .reconnect__actions,
#components-reconnect-modal.components-reconnect-paused .reconnect__actions,
#components-reconnect-modal.components-reconnect-resume-failed .reconnect__actions {
  display: flex;
}

/* ---- Dialog -------------------------------------------------------------- */

#components-reconnect-modal {
  margin: auto;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  color: var(--txt);
}
#components-reconnect-modal::backdrop {
  background: rgba(9, 10, 26, 0.62);
  backdrop-filter: blur(3px);
  animation: mkh-fade 0.2s var(--ease);
}

.reconnect__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-4);
  width: min(420px, calc(100vw - 32px));
  padding: 28px 26px 24px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
  animation: mkh-rise 0.26s var(--ease-out);
}

/* ---- Activity indicator -------------------------------------------------- */

/* Three bars rising and falling out of phase. A determinate progress bar would
   be a lie here: nobody knows how long the server will take to come back. */
.reconnect__pulse { align-items: flex-end; gap: 5px; height: 34px; }
.reconnect__pulse div {
  width: 6px;
  height: 12px;
  border-radius: var(--r-full);
  background: var(--primary-fill);
  animation: mkh-reconnect-pulse 1.05s ease-in-out infinite;
}
.reconnect__pulse div:nth-child(2) { animation-delay: 0.16s; }
.reconnect__pulse div:nth-child(3) { animation-delay: 0.32s; }

@keyframes mkh-reconnect-pulse {
  0%, 100% { height: 12px; opacity: 0.45; }
  50%      { height: 30px; opacity: 1; }
}

.reconnect__icon {
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: var(--r-full);
}
.reconnect__icon svg { width: 24px; height: 24px; }
.reconnect__icon--warn  { background: var(--bad-bg);  color: var(--bad-fg); }
.reconnect__icon--pause { background: var(--warn-bg); color: var(--warn-fg); }

/* ---- Copy ---------------------------------------------------------------- */

.reconnect__title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.reconnect__text {
  margin-top: 6px;
  font-size: var(--fs-md);
  line-height: 1.55;
  color: var(--txt-2);
  max-width: 34ch;
  margin-inline: auto;
}

/* ---- Countdown ----------------------------------------------------------- */

.reconnect__countdown {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 14px;
  min-height: 46px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

/* Tabular figures and a fixed minimum width, so the number does not jiggle the
   label sideways as it counts 10, 9, 8. */
.reconnect__seconds {
  min-width: 2ch;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 24px;
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--primary);
}
.reconnect__seconds-label { font-size: var(--fs-sm); color: var(--txt-3); text-align: left; }

/* Blazor holds the countdown at 0 for the two seconds an attempt takes. A
   frozen "0 seconds to the next attempt" reads as a stuck timer, so the JS
   flags that window and the box says what is actually happening. */
.reconnect__now { display: none; font-size: var(--fs-md); color: var(--txt-2); }
#components-reconnect-modal.is-attempting .reconnect__seconds,
#components-reconnect-modal.is-attempting .reconnect__seconds-label { display: none; }
#components-reconnect-modal.is-attempting .reconnect__now { display: inline; }

/* Attempt N of M. Says the retrying will not go on forever, which the bare
   countdown does not. */
.reconnect__attempt {
  margin-top: 8px;
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--txt-3);
}
.reconnect__attempt span { font-variant-numeric: tabular-nums; color: var(--txt-2); }

.reconnect__actions {
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .reconnect__pulse div { animation: none; height: 20px; opacity: 0.8; }
}

/* ==========================================================================
   Searchable select

   A text input that filters a list. Used where a plain <select> stops being
   usable - the state list is thirty-seven long and people know the name they
   want, so they should be able to type it.
   ========================================================================== */

.combo { position: relative; }

/* The chevron sits inside the control, so the text must not run under it, and
   the cross beside it takes another 30px once there is something to clear. */
.combo__input { padding-right: 38px; }
.combo.has-value .combo__input { padding-right: 68px; }

/* Sized to fill the box it sits in rather than to fit the chevron. At 28px it
   was the smallest thing on any of these screens and the one most often
   reached for on a phone, where the list it opens is the whole point of the
   control. The input is 38px tall and reserves 38px of padding on the right,
   so 34px square is as large as it goes without covering the text. */
.combo__toggle {
  position: absolute;
  top: 50%;
  right: 3px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--txt-3);
  cursor: pointer;
}
.combo__toggle svg { width: 16px; height: 16px; }
.combo__toggle:hover { color: var(--txt); background: var(--surface-2); }
.combo.is-open .combo__toggle svg { transform: rotate(180deg); }

/* Quieter than the chevron and a size smaller: emptying a field is the rarer
   act of the two, and this sits next to the one people reach for all day. */
.combo__clear {
  position: absolute;
  top: 50%;

  /* Clear of the chevron rather than three pixels under it: the chevron is
     drawn after this and would take that sliver of the hit area with it. */
  right: 37px;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--txt-3);
  cursor: pointer;
}
.combo__clear svg { width: 14px; height: 14px; }
.combo__clear:hover { color: var(--danger); background: var(--surface-2); }
.combo__clear:focus-visible { color: var(--danger); background: var(--surface-2); }

.combo__list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: var(--z-menu);

  /* Bounded and scrollable: thirty-seven options must not push the drawer's
     own buttons off the screen. */
  max-height: 264px;
  overflow-y: auto;

  margin: 0;
  padding: 5px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--sh-lg);
  animation: mkh-rise 0.14s var(--ease);
}

.combo__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  color: var(--txt);
  cursor: pointer;
}

/* Highlight follows the keyboard as well as the mouse, so arrowing down and
   hovering look the same and there is only ever one active row. */
.combo__option.is-active { background: var(--surface-2); }
.combo__option.is-selected { color: var(--primary); font-weight: var(--fw-semibold); }
.combo__option.is-selected.is-active { background: var(--primary-soft); }

.combo__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo__meta {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--txt-3);
}
.combo__option.is-selected .combo__meta { color: var(--primary); }

.combo__empty { padding: 12px 10px; font-size: var(--fs-sm); color: var(--txt-3); }

/* Sits under the list and over everything else, so the next click anywhere
   dismisses the list instead of landing on whatever is behind it. */
.combo__backdrop {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-menu) - 1);
}

/* ---- Table and field variants used by the master screens ----------------- */

/* Codes, phone numbers and GSTINs: fixed-width so a column of them lines up
   and a transposed digit is visible rather than camouflaged. */
.cell-mono { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--txt-2); }

/* A name with a tag beside it, on the top line of a stacked cell. The tag is
   kept out of the name's own ellipsis: a long number should be cut before the
   two letters that say what kind of sale it was. */
.cell-pair { display: flex; align-items: center; gap: 7px; min-width: 0; }
.cell-pair .tag { flex: none; }

/* Money right-aligns and uses tabular figures, so the decimal points stack. */
.cell-amount {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A negative balance is money held on the customer's behalf, not money owed.
   Colour alone would not say so, which is why the figure keeps its minus. */
.cell-amount.is-credit { color: var(--accent-soft-fg); }

/* Less than nothing on the shelf: something went out that never came in. The
   minus sign is what says so; the colour only makes it findable in a long
   list. */
.cell-mono.is-short { color: var(--bad-fg); font-weight: var(--fw-semibold); }

.input--mono { font-family: var(--font-mono); }

/* --------------------------------------------------------------------------
   A rate that has usual answers without being limited to them
   --------------------------------------------------------------------------
   A box that takes anything, with the common answers offered underneath as
   one-tap suggestions. A dropdown was tried first and could only ever hand
   back one of its own options, which is wrong for a GST rate: the slabs cover
   almost every bill, and the ones they do not cover still have to be
   enterable. */

/* Inside the control rather than after it, so the unit travels with the figure
   when the field is narrow. Not clickable: the target is the box behind it. */
.rate__unit {
  position: absolute;
  right: 12px;
  color: var(--txt-3);
  font-size: var(--fs-sm);
  pointer-events: none;
}
.rate__input { padding-right: 28px; }

.rate__slabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

/* 32px tall and 52 wide at the minimum: these are meant to be tapped on a
   phone, which is the case the old dropdown served worst. */
.rate__slab {
  min-width: 52px;
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface);
  color: var(--txt-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition:
    border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}
.rate__slab:hover { border-color: var(--line-strong); color: var(--txt); }
.rate__slab:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Marks what the box holds rather than what was last clicked, so typing 18 by
   hand lights the 18 and typing 7.5 lights none of them. */
.rate__slab.is-on {
  border-color: var(--primary);
  background: var(--primary-soft);
  color: var(--primary-soft-fg);
}

/* --------------------------------------------------------------------------
   A commission: a share of the goods or a fixed sum
   --------------------------------------------------------------------------
   The two kinds side by side as one control, then the figure. They wrap onto
   two lines on a narrow drawer rather than squeezing the box to nothing. */
.commission {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
}

.commission__kinds {
  display: inline-flex;
  flex: none;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface-2);
}

/* 30px inside a 3px track, so the whole target is 36px tall on a phone. */
.commission__kind {
  min-height: 30px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--txt-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.commission__kind:hover:not(:disabled):not(.is-on) { color: var(--txt); }
.commission__kind:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.commission__kind:disabled { cursor: default; }

/* Filled, the way the primary button is, so the chosen kind is the loudest
   thing in the track in both themes. A lifted surface was tried first and in
   the dark theme it came out darker than the track, with dimmer text than the
   option beside it: the unchosen kind read as the chosen one. */
.commission__kind.is-on {
  background: var(--primary-fill);
  color: #FFFFFF;
  box-shadow: var(--sh-xs);
}

/* Takes the rest of the row beside the two kinds, and the whole row once they
   wrap above it, so it lines up with the other boxes in the form. */
.commission__value { flex: 1 1 120px; min-width: 110px; }

/* Inside the box, like the unit on a GST rate: after a percentage, before
   rupees, which is where each is written. */
.commission__unit {
  position: absolute;
  right: 12px;
  color: var(--txt-3);
  font-size: var(--fs-sm);
  pointer-events: none;
}
.commission__unit--lead { right: auto; left: 12px; }
.commission__input { padding-right: 30px; }
.commission__input--lead { padding-right: 12px; padding-left: 28px; }

/* A textarea that should be roomy whatever its rows say. The height, padding
   and resizing now come from the textarea.input rule above; this only sets the
   floor. Element-qualified so it keeps the same specificity as that rule and
   still wins, being later. */
textarea.input--area { min-height: 92px; }

/* A role that may view a record but act on none of it still gets a menu, and
   an empty box tells the user nothing. The items are hidden by AuthorizeView,
   so whether any survive is only knowable once rendered - hence :has(). */
.menu:not(:has(.menu__item, .menu__note))::after {
  content: "No actions available for your role.";
  display: block;
  padding: 7px 10px 9px;
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: var(--txt-3);
}

/* ==========================================================================
   Confirmation dialog

   Sits in front of anything irreversible. Deliberately a modal rather than an
   inline "are you sure" on the row: the point is to interrupt, and something
   that can be dismissed by carrying on clicking does not interrupt.
   ========================================================================== */

.confirm {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-modal) + 10);
  display: grid;
  place-items: center;
  padding: 20px;
}

.confirm__scrim {
  position: absolute;
  inset: 0;
  background: rgba(9, 10, 26, 0.62);
  backdrop-filter: blur(3px);
  animation: mkh-fade 0.16s var(--ease);
}

.confirm__box {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  width: min(420px, 100%);
  padding: 26px 26px 20px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
  animation: mkh-rise 0.18s var(--ease-out);
}

.confirm__icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-bottom: 6px;
  border-radius: var(--r-full);
  background: var(--info-bg);
  color: var(--info-fg);
}
.confirm__icon svg { width: 22px; height: 22px; }
.confirm__icon--danger { background: var(--bad-bg); color: var(--bad-fg); }

.confirm__title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  color: var(--txt);
}

.confirm__body { font-size: var(--fs-md); line-height: 1.55; color: var(--txt-2); }

/* The consequence, when there is one worth stating separately from the name. */
.confirm__detail {
  margin-top: 4px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--txt-3);
}

.confirm__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: 14px;
}

/* A dialog that asks for something as well as confirming it: the day a cheque
   cleared, what a bounce is charged. The box centres its words; the fields
   under them read from the left, as a form does, and take the box's width. */
.confirm__form {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
  text-align: left;
}
.confirm__form .field { margin: 0; }

/* Such a dialog can be taller than a small phone. Centred while it fits, it
   starts at the top and the page behind it scrolls once it does not, rather
   than losing its buttons off both ends. The scrim stays put meanwhile. */
.confirm--form { overflow-y: auto; align-items: start; }
.confirm--form .confirm__box { margin-block: auto; }
.confirm--form .confirm__scrim { position: fixed; }

/* --------------------------------------------------------------------------
   Where a customer's cheque has got to
   --------------------------------------------------------------------------
   Said in words with a dot before them, on the line under a receipt, rather
   than as a pill: a pill is a row's main state and this is a detail of how it
   was paid. The colour is the dot's; the words stay legible in either theme. */
.chq-state { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.chq-state::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background: currentColor;
}
.chq-state--clearing { color: var(--warn-fg); }
.chq-state--cleared { color: var(--ok-fg); }
.chq-state--bounced { color: var(--bad-fg); }

/* --------------------------------------------------------------------------
   Where a party's account stands
   --------------------------------------------------------------------------
   Four figures across the head of the activity panel, above the tabs rather
   than inside them: they are the same four facts whichever tab is open, and
   having to change tab to see a balance would be an odd way to read one.

   auto-fit with a floor rather than four fixed tracks, so the row becomes two
   and then one as the panel narrows, instead of squeezing a rupee figure into
   a track it cannot hold. */
.account {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin-bottom: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--line);
  overflow: hidden;
}

.account__figure {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 11px 13px;
  background: var(--surface);
}

.account__label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--txt-3);
}

.account__value { font-size: 15px; font-weight: var(--fw-semibold); color: var(--txt); }

.account__meta { font-size: var(--fs-xs); color: var(--txt-3); }

/* The figure the other three are read against. */
.account__figure--sum .account__value { color: var(--primary); }

/* Only when something actually is late; a nought here should not be amber. */
.account__figure--late { background: var(--warn-bg); }
.account__figure--late .account__label,
.account__figure--late .account__meta { color: var(--warn-fg); opacity: 0.85; }
.account__figure--late .account__value { color: var(--warn-fg); }

.account__late { color: var(--warn); }

/* Why the four figures above do not add up. Said only when there is an advance
   to explain, so it is never a line of boilerplate. */
.account__note {
  margin: calc(var(--sp-3) * -1 + 2px) 0 var(--sp-3);
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: var(--fs-sm);
  color: var(--txt-2);
}
.account__note strong { color: var(--txt); font-weight: var(--fw-semibold); }

/* --------------------------------------------------------------------------
   Filtering a paged list
   -------------------------------------------------------------------------- */

.activity-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-3) 0;
}
.activity-filters .toolbar__search { flex: 1 1 200px; min-width: 0; }

/* The two dates stay together as one control when the row wraps: "from" and
   "to" on separate lines are two questions rather than one range. */
/* The pair has to be allowed to shrink, not only to grow. Two date inputs each
   ask for about 172px on a phone, and a flex item defaults to min-width: auto,
   which is its content: the row then measures 368px inside a 343px panel and
   runs off the right edge. min-width: 0 is what lets them give way. */
.activity-filters__dates {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 240px;
  min-width: 0;
}
.activity-filters__dates .input { width: auto; min-width: 0; flex: 1 1 0; }
.activity-filters__to { flex: none; font-size: var(--fs-sm); color: var(--txt-3); }

/* --------------------------------------------------------------------------
   Pager
   --------------------------------------------------------------------------
   The count reads as a sentence about the whole filtered set rather than about
   the ten rows above it, because the total beside it is for the set too. */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.pager__count { margin: 0; font-size: var(--fs-sm); color: var(--txt-3); }
.pager__count strong { color: var(--txt); font-weight: var(--fw-semibold); }

.pager__moves { display: flex; align-items: center; gap: var(--sp-2); }
.pager__where { font-size: var(--fs-sm); color: var(--txt-3); white-space: nowrap; }

.pager .btn svg { width: 15px; height: 15px; }

/* --------------------------------------------------------------------------
   A list the browser only draws the visible part of
   --------------------------------------------------------------------------
   Virtualize needs a box with a bottom to it. .table-wrap declares
   overflow-x: auto, and the spec turns the other axis into auto with it, so the
   wrapper is already a scroll container - it simply has no height to scroll
   within, and every row would be drawn to fill it. Capping the height is what
   turns it into a window.

   The cap is in vh rather than px so a tall screen shows more rows for the same
   scrolling, and it is generous enough that a short list never scrolls at all. */
/* A ceiling and no floor: a list of three rows is three rows tall.

   There was a floor here, and it belonged to Virtualize, which decided how many
   rows to fetch from the height of this box and deadlocked without one - the box
   starts at header height, it works out that no rows are visible, fetches none,
   and the box never grows. Keyset scrolling replaced Virtualize and reads its
   next window from a sentinel row that only exists while there is one, so
   nothing measures this box any more. The floor outlived its reason and was
   showing as 121px of empty card under any short list. */
.table-wrap--tall {
  max-height: min(52vh, 520px);
  overflow-y: auto;
}

/* A capped table stops mid-row by design, so whatever follows it has to be
   held off. Flush against a totals block, the clipped half of a row reads as
   the totals overlapping the table rather than as more rows to scroll to. */
.table-wrap--tall + .doc-total,
.table-wrap--tall + .form-actions {
  margin-top: 1rem;
  border-top: 1px solid var(--line);
  padding-top: 1rem;
}

/* The head stays put while the rows move under it. Without this the column
   names scroll away and the figures below lose their labels. */
.table-wrap--tall .table thead th {
  position: sticky;
  top: 0;
  z-index: 1;

  /* The background has to be on the cell, not on the row, and this is the whole
     bug that was reported: a sticky th is lifted into its own paint layer and
     the tr it belongs to is left behind, so the row's background scrolls away
     while the cell stays. The column names then sit over the rows passing under
     them, both fully legible and on top of each other.

     The separating edge is an inset shadow rather than a border because the
     table collapses its borders, and a collapsed border belongs to the table's
     own border grid rather than to the cell: it stays with the grid and scrolls
     out of the box with it. */
  background: var(--surface-2);
  box-shadow: inset 0 -1px var(--line);
}

/* A row whose window has not come back yet. Held to the height of a real one so
   the list does not jump under the scrollbar as it fills. */
.row-waiting td { padding: 11px 14px; }
.row-waiting__bar {
  display: block;
  height: 11px;
  max-width: 220px;
  border-radius: var(--r-full);
  background: var(--line);
  animation: mkh-waiting 1.2s ease-in-out infinite;
}

@keyframes mkh-waiting {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 0.9; }
}

/* What a filtered list comes to, under the list rather than inside it: the
   figure is for everything the filter matches, and one that added up the rows
   on screen would change as somebody scrolled. */
.list-total {
  margin: 0;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--txt-3);
}
.list-total strong { color: var(--txt); font-weight: var(--fw-semibold); }

/* The four figures while they are still being added up. Each of them sweeps a
   party's whole ledger, so on a long history they arrive a moment after the
   rows do; dimming them says "not yet" without moving anything, which is what
   a spinner in their place would do. */
.account--working { opacity: 0.45; }
.account--working .account__value { color: var(--txt-3); }

/* --------------------------------------------------------------------------
   The selection bar
   --------------------------------------------------------------------------
   Appears under the toolbar once something is ticked, and says what would be
   printed before the button that prints it. On a register read a window at a
   time the count is the important part: "40 invoices selected" when the header
   tick was meant to take 300 is the kind of thing nobody notices until the
   pack is already at the auditor. */
.picked {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  padding: 10px 16px;
  background: var(--accent-soft);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.picked__count { font-size: var(--fs-sm); color: var(--txt-1); }
.picked__count strong { font-variant-numeric: tabular-nums; }

/* The qualifier reads as an aside, because it explains the count rather than
   adding to it. */
.picked__note { color: var(--txt-3); }
.picked__note::before { content: " · "; }

.picked__warn { font-size: var(--fs-sm); color: var(--warn-fg); flex: 1 1 240px; }
.picked__actions { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }

/* A link cannot be disabled the way a button can, so it is taken out of the
   tab order and stopped from being clicked rather than merely dimmed. */
.btn.is-disabled { opacity: 0.5; pointer-events: none; }

@container content (max-width: 560px) {
  .picked { align-items: flex-start; }
  .picked__actions { margin-left: 0; width: 100%; justify-content: flex-end; }
}

/* --------------------------------------------------------------------------
   Cover panel

   What a sales order would set aside, line by line: what comes off the shelf,
   what is held to make the rest, and what is nowhere to be found. Read while
   the order is still being typed, so it is laid out to be scanned rather than
   studied: one block per line, a badge saying how it stands, and the figures
   down the right where the eye can run over them.
   -------------------------------------------------------------------------- */

.cover {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

.cover__line {
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.cover__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

.cover__product { display: flex; flex-direction: column; min-width: 0; }

.cover__cap {
  margin: 12px 0 5px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--txt-3);
}

/* What is missing is the one part of this panel somebody has to act on. */
.cover__cap--short { color: var(--warn-fg); }

.cover__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.cover__list li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
}

.cover__what { font-weight: var(--fw-semibold); }

/* Which finished article a component is being held for, said on the component
   itself: two levels down a bill, nothing else on the row would say. */
.cover__for { margin-left: 6px; font-weight: var(--fw-regular); color: var(--txt-3); }

/* The second way to answer a shortage, said under the first: quieter than a
   caption because it is an alternative rather than another list. */
.cover__cap--aside {
  margin-top: 6px;
  font-size: var(--fs-sm);
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--fw-regular);
  color: var(--txt-3);
}

.cover__where { color: var(--txt-2); }

/* Which orders spoke for a product, under its name on the reserved list. The
   quantity is the answer and the orders are the reason, so they read as a
   second line rather than competing with the figure on the right. */
.reserved__orders {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  color: var(--txt-3);
}

/* The cover panel in one sentence, above the detail it summarises: somebody
   about to save an order wants the shape of it, not five lots and their racks. */
.cover__summary {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--txt-2);
}

/* Pushed to the right so the quantities line up down the panel. margin-left
   auto rather than a grid, because the rows carry different numbers of parts. */
.cover__qty { margin-left: auto; white-space: nowrap; }

.cover__list--short .cover__qty { color: var(--warn-fg); font-weight: var(--fw-semibold); }

/* A combo box that cannot be typed into, for a document nothing may change on.
   The input carries the disabled attribute; this is what stops the box looking
   live while it is not. */
.combo.is-disabled { opacity: 0.6; }
.combo.is-disabled .combo__toggle { pointer-events: none; }

/* A form that opens inside a panel, above the list it adds to: set apart by a
   rule and a tint rather than by a card, which inside a drawer reads as a
   second panel sitting on the first. */
.panel-form {
  margin-bottom: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.panel-form .field:last-of-type { margin-bottom: 0; }
.panel-form .form-actions { margin-top: var(--sp-3); padding: 0; border: 0; }
