/* ===========================================================================
   SRK Sweets ERP — Global design system (additive layer)

   Single source of truth for the shared UI primitives that should look the same
   across the whole project:
     1. Form controls (inputs / selects / textareas)
     2. The calendar / date box  (native popup kept, box tokenised)
     3. The themed dropdown menu  (.global-dropdown, enhanced by dropdown-theme.js)
     4. Data tables

   This file is loaded LAST, so:
     - its :root tokens are available to every stylesheet, and
     - its component styles win on specificity ties (additive, no legacy CSS is
       removed — a screen with its own more-specific rules still wins).

   The token values are derived from the existing themed dropdown so nothing
   regresses. See DESIGN_SYSTEM.md for how to use these.
   =========================================================================== */

:root {
  /* ---- Form controls -------------------------------------------------- */
  --control-h: 42px;
  --control-radius: 8px;
  --control-border-color: #cbd5e1;
  --control-border: 1px solid var(--control-border-color);
  --control-bg: #ffffff;
  --control-color: var(--ink, #172333);
  --control-muted: var(--muted, #667485);
  --control-pad-y: 9px;
  --control-pad-x: 12px;
  --control-font-size: 14px;
  --control-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
  --control-focus-border: var(--brand, #aa3232);
  --control-focus-ring: 0 0 0 3px color-mix(in srgb, var(--brand, #aa3232) 16%, transparent);
  --control-disabled-bg: #f1f5f9;

  /* ---- Data tables ---------------------------------------------------- */
  --table-radius: 10px;
  --table-border-color: #e6ebf2;
  --table-header-bg: #f8fafc;
  --table-header-color: #475569;
  --table-row-border: #eef2f7;
  --table-row-hover: #f8fbff;
  --table-cell-pad-y: 10px;
  --table-cell-pad-x: 12px;
  --table-font-size: 13px;
}

/* ---------------------------------------------------------------------------
   1. Opt-in control classes — use these on any new field for the standard look
   --------------------------------------------------------------------------- */
.ui-field {
  display: grid;
  gap: 6px;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 700;
  color: #475569;
}
.ui-input,
.ui-select,
.ui-textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: var(--control-h);
  border: var(--control-border);
  border-radius: var(--control-radius);
  background: var(--control-bg);
  color: var(--control-color);
  padding: var(--control-pad-y) var(--control-pad-x);
  font-size: var(--control-font-size);
  font-weight: 500;
  line-height: 1.35;
  box-shadow: var(--control-shadow);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}
.ui-textarea { min-height: 84px; resize: vertical; }
.ui-input:focus,
.ui-select:focus,
.ui-textarea:focus {
  outline: 0;
  border-color: var(--control-focus-border);
  box-shadow: var(--control-focus-ring);
}
.ui-input[readonly],
.ui-input:disabled,
.ui-select:disabled,
.ui-textarea:disabled {
  background: var(--control-disabled-bg);
  color: var(--control-muted);
}

/* ---------------------------------------------------------------------------
   2. Calendar / date box  — native date & time inputs otherwise render with a
   harsh square browser border that clashes with every themed control. Give them
   the smooth, tokenised box everywhere (the popup itself stays the browser's
   native calendar). Low specificity: any screen with its own field styling wins.
   --------------------------------------------------------------------------- */
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"],
input[type="week"] {
  min-height: var(--control-h);
  border: var(--control-border);
  border-radius: var(--control-radius);
  background: var(--control-bg);
  color: var(--control-color);
  padding: var(--control-pad-y) var(--control-pad-x);
  box-sizing: border-box;
}
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator,
input[type="week"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.15s ease;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator,
input[type="time"]:hover::-webkit-calendar-picker-indicator {
  opacity: 1;
}

/* Fill naked text-family inputs that would otherwise fall back to the browser
   default border. Low specificity, so styled inputs keep their own look. */
input[type="text"],
input[type="search"],
input[type="tel"],
input[type="email"],
input[type="url"],
input[type="password"],
input:not([type]) {
  border-radius: var(--control-radius);
}

/* ---------------------------------------------------------------------------
   3. Themed dropdown menu — .global-dropdown (auto-applied to every <select> by
   dropdown-theme.js). It is already the token look; here we only pin its height
   and focus ring to the shared tokens so dropdowns match inputs exactly.
   --------------------------------------------------------------------------- */
.global-dropdown-button {
  min-height: var(--control-h);
  border-radius: var(--control-radius);
  border-color: var(--control-border-color);
}
.global-dropdown.open .global-dropdown-button,
.global-dropdown-button:focus-visible {
  border-color: var(--control-focus-border);
  box-shadow: var(--control-focus-ring);
}

/* ---------------------------------------------------------------------------
   4. Data tables — the shared table() builder already wraps every table in
   .smart-table-shell. Use .ui-table on any hand-written table for the same look.
   --------------------------------------------------------------------------- */
.ui-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--table-border-color);
  border-radius: var(--table-radius);
}
.ui-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--table-font-size);
  color: var(--control-color);
}
.ui-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--table-header-bg);
  color: var(--table-header-color);
  font-weight: 700;
  text-align: left;
  white-space: nowrap;
  padding: var(--table-cell-pad-y) var(--table-cell-pad-x);
  border-bottom: 1px solid var(--table-border-color);
}
.ui-table tbody td {
  padding: var(--table-cell-pad-y) var(--table-cell-pad-x);
  border-bottom: 1px solid var(--table-row-border);
  vertical-align: middle;
}
.ui-table tbody tr:last-child td { border-bottom: 0; }
.ui-table tbody tr:hover td { background: var(--table-row-hover); }
.ui-table .right,
.ui-table .num,
.ui-table td.right,
.ui-table th.right {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   5. Keyboard focus ring — ONE green ring for every focusable control,
   app-wide. Matches the POS billing counter's accent (#2e9968). The ring is
   an outline (2px, offset 2px) so it never shifts layout and is visually
   distinct from the transfer2 panels' flush 1px #2f8f5b borders: a control
   ring floats 2px off the control; a panel border hugs the panel.

   `!important` is deliberate: styles.css suppresses outlines in many places
   (global `input:focus { outline: 0 }` at :144, blue rings at :152/:26964,
   plus per-component `outline: none !important` sites restated below) and a
   fair fight on specificity is unwinnable from a class-level selector.
   `:focus-visible` keeps the ring keyboard-only for buttons/links; text
   inputs also match it on click, which is standard and desirable.
   --------------------------------------------------------------------------- */
:root {
  --focus-ring-color: #2e9968;
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
}
/* Night shell (#0f172a) gets a brighter green for contrast */
body[data-ui-mode="night"] {
  --focus-ring-color: #3fae7a;
}

/* POS ONLY (owner decision, July 2026). The green focus ring is no longer
   painted anywhere in this ERP shell — it lives exclusively in the React POS
   app (pos-react/src/styles.css, served at /pos), which is a separate
   document and therefore unaffected by this stylesheet.

   `.pos-page` on <body> (or any ancestor) is the escape hatch: if a POS
   screen is ever hosted inside this shell, the ring comes back for that
   subtree only. Everything else is explicitly outline-free, including the
   legacy blue/indigo rings that predate the green one — keyboard focus
   shows no box on ERP pages.

   Accessibility note: removing every visible focus indicator makes
   keyboard navigation hard to follow and fails WCAG 2.4.7. This is a
   deliberate product choice; revert by restoring the ring rules below the
   `.pos-page` guard. */
body:not(.pos-page) :is(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus,
body:not(.pos-page) :is(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus-visible,
body:not(.pos-page) .global-dropdown-button:focus,
body:not(.pos-page) .global-dropdown-button:focus-visible,
body:not(.pos-page) .global-datepicker-field:focus,
body:not(.pos-page) .global-datepicker-field:focus-visible {
  outline: none !important;
}

/* Kept for any POS surface rendered inside this shell (and as the single
   place the ring is defined if it is ever wanted app-wide again). */
.pos-page :is(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus-visible,
.pos-page .global-dropdown-button:focus-visible,
.pos-page .global-datepicker-field:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color) !important;
  outline-offset: var(--focus-ring-offset) !important;
  scroll-margin: 48px;
}

/* ONE green line, never two. The outline ring above is the whole focus
   indicator; earlier versions ALSO recolored each control's own border and
   painted a 3px halo, so a focused field showed a green border plus a green
   ring — the "double line" the owner reported. Those extra decorations are
   deliberately gone. Components keep their resting borders; the shared ring
   is what says "focused".
   Also suppress the transfer2 dialogs' legacy indigo focus glows so the
   green ring is the only focus mark there too. */
.transfer2-form .transfer2-itemsarea .transfer2-searchwrap input.transfer2-search[type="text"]:focus,
.transfer2-form .transfer2-qty:focus-within,
.transfer2-form .transfer2-note-field:focus-within,
.global-dropdown-button:focus-visible,
.ui-input:focus-visible,
.ui-select:focus-visible,
.ui-textarea:focus-visible {
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
   6. Full-screen report tabs (/report/<id>) — the SPA shell renders with no
   sidebar/topbar; the report + its compact top bar use the full width.
   Report cards in the catalog are real <a target=_blank> links now; keep them
   looking like the cards they replace.
   --------------------------------------------------------------------------- */
body.report-page-mode .sidebar,
body.report-page-mode .topbar,
body.report-page-mode .support-widget-root,
body.report-page-mode #notificationPanel,
body.report-page-mode #globalSearchPanel {
  display: none !important;
}
/* With the sidebar hidden the workspace becomes the shell's first grid item,
   so the sidebar column template must go too. !important because the shell's
   column rules come in several higher-specificity body:not()/collapsed
   variants; this mode is fixed at boot, nothing else may win. */
body.report-page-mode .app-shell {
  grid-template-columns: minmax(0, 1fr) !important;
}
body.report-page-mode .workspace {
  height: 100vh;
  min-height: 100vh;
}
body.report-page-mode #contentArea {
  padding: 16px 20px 24px;
}
body.report-page-mode .report-focus-shell {
  max-width: none;
}

a.report-card-option,
a.report-side-item {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
a.report-card-option:hover,
a.report-side-item:hover {
  text-decoration: none;
  color: inherit;
}

/* Full-screen order tabs (/order/…): the full-viewport order dialog covers
   everything, but hide the shell behind it for clean paint + fallback. */
body.order-page-mode .sidebar,
body.order-page-mode .topbar,
body.order-page-mode .support-widget-root {
  display: none !important;
}
body.order-page-mode .app-shell {
  grid-template-columns: minmax(0, 1fr) !important;
}
body.order-page-mode {
  overflow: hidden;
}
html:has(body.order-page-mode) {
  overflow: hidden;
}

/* ── §7 Transfer approval inside the record view ─────────────────────────────
   The destination approves a dispatched transfer in the transfer's own detail
   popup — there is no separate approve dialog. The form only exists in the DOM
   for the receiving store, so these rules are scoped to that state.
   Viewport maths divide by --ui-zoom: the shell applies `zoom` to <body>, so a
   raw 100dvh overflows the screen at the default 1.15 text size. */
/* The form state used to be a NARROW exception (760px, whole panel scrolls) -
   which is exactly the popup the owner screenshotted on 3 Aug asking for the
   1250x750 standard: the Approve button scrolled away under the footer. It now
   matches .record-view-dialog in styles.css - the receive form renders inside
   [data-record-view-body], so the standard "only the body scrolls" layout
   already contains it, and the title and actions stay put. Kept as a rule (not
   deleted) for the zoom-aware maths the base rule lacks. */
#recordViewDialog.has-record-view-form {
  width: min(1250px, calc((100vw - 24px) / var(--ui-zoom, 1)));
  height: min(750px, calc((100vh - 28px) / var(--ui-zoom, 1)));
  max-height: min(750px, calc((100vh - 28px) / var(--ui-zoom, 1)));
}
.record-view-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--ds-border);
}
.record-view-form-actions {
  justify-content: flex-end;
}
/* Told to whoever is NOT the receiving store, in place of the form. */
.record-view-await {
  margin: 14px 0 0;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--ds-border);
  background: var(--ds-surface-muted);
  color: var(--ds-text-muted);
  font-size: 13px;
}

/* ── §8 Add Production popup ─────────────────────────────────────────────────
   Production Planning ▸ Add Production opens as a 1000x750 popup instead of the
   docked full-page view it used to use, which swapped the whole screen out and
   read as the app reloading.

   Scoped :not(.transfer-as-page) so the legacy page-view rules in styles.css
   stay untouched, and sized with viewport units divided by --ui-zoom: the shell
   applies `zoom` to <body> (default 1.15), so a raw 100dvh overflows the
   screen. inset:0 + margin:auto is what actually centres a <dialog> once it has
   a definite width and height. */
#productionDialog:not(.transfer-as-page) {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(1000px, calc((100vw - 24px) / var(--ui-zoom, 1)));
  height: min(750px, calc((100dvh - 24px) / var(--ui-zoom, 1)));
  max-width: none;
  max-height: none;
  padding: 0;
  border-radius: 14px;
  overflow: hidden;
}
#productionDialog:not(.transfer-as-page) .dialog-panel {
  display: flex;
  flex-direction: column;
  height: 100%;
  margin: 0;
  overflow-y: auto;
}
/* The materials reference is the only part that should grow, so the action row
   stays reachable at the bottom instead of being pushed off.

   The base rule caps the materials list at 132px — about two and a half rows —
   which left it scrolling inside a small box while most of a 750px popup sat
   empty. In the popup the list fills the space it has instead, and only starts
   scrolling if a recipe genuinely has more materials than the popup can show. */
#productionDialog:not(.transfer-as-page) #productionRecipeMaterials {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#productionDialog:not(.transfer-as-page) .production-materials-ref {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#productionDialog:not(.transfer-as-page) .production-materials-ref-scroll {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}
#productionDialog:not(.transfer-as-page) .dialog-actions {
  margin-top: auto;
}

/* ── §9 Table layout: overflow, column sizing, print ─────────────────────────

   Everything here is additive and lives in this file on purpose — styles.css is
   58,000 lines of legacy and is not where new work goes. Loaded last, so a
   plain class selector here beats an equally specific one there.

   The root problem: styles.css:5663 declares a BARE element rule

       table { width: 100%; border-collapse: collapse; white-space: nowrap; }

   white-space inherits, so every th and td in the ERP is nowrap on desktop.
   The only resets are inside @media (max-width: 640px) and (max-width: 900px),
   i.e. wrapping was designed for phones and never for the screens people
   actually use. Combined with the default table-layout: auto (fixed applies
   only once a user has resized a column, styles.css:4325), a single long value
   drags its whole column wider: measured on Item ▸ Products, one long product
   name took the Name column from 123px to 403px and the table from 1394px to
   1674px, pushing everything else off-screen. */

/* Text columns wrap. This is what stops the column from ballooning: with
   nowrap a cell's min-content width is the entire string, so the column can
   never be narrower than its longest value. overflow-wrap: anywhere also
   breaks a long unbroken token (a code, an email, a pasted reference) instead
   of letting it set the column width. */
.table-wrap tbody td,
.table-wrap thead th {
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: normal;
}

/* Compact columns keep their single line and shrink to their content.
   width: 1% is the standard trick: in auto table layout the browser treats it
   as "as small as possible", and nowrap keeps the floor at the content width.
   That hands the leftover space to the text columns, which is the point. */
.table-wrap thead th.col-numeric,
.table-wrap tbody td.col-numeric,
.table-wrap thead th.col-center,
.table-wrap tbody td.col-center,
.table-wrap thead th.col-compact,
.table-wrap tbody td.col-compact {
  white-space: nowrap;
  overflow-wrap: normal;
  width: 1%;
}

/* Dates, document numbers and codes: compact, and left-aligned — they read as
   labels, not quantities. tableColumnClassName() stamps col-compact on these. */
.table-wrap thead th.col-compact,
.table-wrap tbody td.col-compact {
  text-align: left;
}

/* Text columns need a floor. Once they are allowed to wrap, auto table layout
   squeezes them down to their longest single word — measured on Products, Name
   collapsed from 123px to 85px, narrower than the Status column beside it. A
   min-width keeps the readable share the compact columns just gave up. */
.table-wrap thead th:not(.col-numeric):not(.col-center):not(.col-compact):not(.action-column),
.table-wrap tbody td:not(.col-numeric):not(.col-center):not(.col-compact):not(.action-column) {
  min-width: 150px;
}

/* Once a table is table-layout: fixed — which happens for every user who has
   ever dragged a column divider, and for the handful of screens that hard-code
   it — a nowrap cell paints straight over its neighbour, because td defaults
   to overflow: visible. Clip it and show an ellipsis instead. */
.table-wrap tbody td.col-numeric,
.table-wrap tbody td.col-center,
.table-wrap tbody td.col-compact {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Cells whose content is meant to escape the box keep doing so: action buttons,
   and the absolutely-positioned stock peek panel. tableHeaderIsAction() only
   matches the exact header "action", so a screen headed "Actions" gets no
   action-column class — the :has() arms below catch those by content instead. */
.table-wrap tbody td.action-column,
.table-wrap tbody td.action-cell,
.table-wrap tbody td:has(> .row-actions),
.table-wrap tbody td:has(> .table-action-row),
.table-wrap tbody td:has(> .module-actions),
.table-wrap tbody td:has(> .mini-action),
.table-wrap tbody td:has(.stock-line-peek) {
  overflow: visible;
  white-space: nowrap;
}

/* ── Printing a list screen ──────────────────────────────────────────────────
   styles.css has exactly one @media print block (31602) and it is entirely
   about thermal receipts and the dedicated #reportPrintArea: it only hides the
   app when body carries .thermal-print-mode or .report-print-mode. Pressing
   Ctrl+P on an ordinary list screen therefore printed the whole application —
   sidebar, toolbars, search boxes — with the table cropped to whatever was
   visible inside its overflow container, because a scroll box prints only its
   visible slice. This block covers that case and leaves the two existing
   print modes untouched. */
@media print {
  body:not(.thermal-print-mode):not(.report-print-mode) {
    background: #fff;
  }

  /* Chrome that has no meaning on paper. */
  body:not(.thermal-print-mode):not(.report-print-mode) .app-sidebar,
  body:not(.thermal-print-mode):not(.report-print-mode) .sidebar,
  body:not(.thermal-print-mode):not(.report-print-mode) .app-topbar,
  body:not(.thermal-print-mode):not(.report-print-mode) .topbar,
  body:not(.thermal-print-mode):not(.report-print-mode) .module-toolbar,
  body:not(.thermal-print-mode):not(.report-print-mode) .search-box,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-controls,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-pagination,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-column-resizer,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-sort-icon,
  body:not(.thermal-print-mode):not(.report-print-mode) td.action-column,
  body:not(.thermal-print-mode):not(.report-print-mode) th.action-column {
    display: none !important;
  }

  /* Let the table out of its scroll box, or only the visible slice prints. */
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap {
    overflow: visible !important;
    border: 0;
    box-shadow: none;
  }
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap table {
    min-width: 0 !important;
    width: 100% !important;
    table-layout: auto !important;
  }

  /* Text wraps on paper. Ellipsis is a screen affordance — on a sheet there is
     no way to reveal what was clipped, so the clipping has to come off. */
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap td,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap th {
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: normal !important;
    width: auto !important;
    min-width: 0 !important;
  }

  /* Repeat the column headers on every sheet. This is the browser default for
     a real <table>, but only holds if nothing overrides display on thead. */
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap thead {
    display: table-header-group;
  }
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap tfoot {
    display: table-footer-group;
  }
  /* A row split across a page break is a misread number. */
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap tr {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* Ruled, so the grid still reads once the screen's tints are gone. */
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap td,
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap th {
    border: 1px solid #999;
  }
  body:not(.thermal-print-mode):not(.report-print-mode) .table-wrap thead th {
    border-bottom: 2px solid #333;
    background: #f0f0f0 !important;
    color: #000 !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/* ── §10 Dialog heading spans the whole form ─────────────────────────────────
   .dialog-panel is a grid, and the heading block is its first cell. In a
   two-column dialog that pushes the heading into the left column and drops the
   SECOND field directly beneath it, top-left — where the eye expects the first
   field. In Add Customer that put Contact, a digits-only box, exactly where
   people start typing the customer's name.

   Spanning the heading puts the real first field back at top-left. */
/* Target the heading block by what it contains, not by position: a close
   button is injected as the form's first child at runtime, so :first-child
   matches that instead and the rule silently does nothing. */
.dialog-panel > div:has(> h2) {
  grid-column: 1 / -1;
}

/* A brief flash when a field rejects what was typed, so the correction is
   visible as well as spoken. */
@keyframes rp-input-reject {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  75% { transform: translateX(3px); }
}
.input-rejected {
  animation: rp-input-reject .18s ease-in-out 2;
  border-color: #c02626 !important;
}
@media (prefers-reduced-motion: reduce) {
  .input-rejected { animation: none; }
}

/* Product Master filters moved INLINE into the toolbar (owner, 4 Aug
   2026): the Filters popover is gone; Product Type and Category sit as
   labelled dropdowns between Search and Rows. No grid override is
   needed - the toolbar is display:flex !important (styles.css "Standard
   list-control panel"), and the flex basis rules there size the new
   labels like every other control.

   Each active-filter chip carries its own remove button. The chip text
   lives in its own span so the base chip's ellipsis truncation keeps
   working inside the flexbox - and so a long value can never push the
   remove button outside the chip's max-width, clipping it invisible. */
.product-active-filters b { display: inline-flex; align-items: center; gap: 6px; }
.product-active-filters b span { color: inherit; font-size: inherit; font-weight: inherit; text-transform: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.product-active-filters b button { flex: 0 0 auto; border: 0; background: none; padding: 0; margin: 0; color: var(--ds-text-muted); font-size: 14px; line-height: 1; cursor: pointer; }
.product-active-filters b button:hover { color: var(--ds-danger); }

/* ── Product Master inline editing ────────────────────────────────────────
   The edit row's inputs fill their cells; the error and hint lines read as
   annotations under the row, not as data. */
.product-inline-edit-row .product-inline-input { width: 100%; min-width: 0; box-sizing: border-box; }
.product-inline-edit-row td { background: color-mix(in srgb, var(--ds-accent, #2a3390) 6%, transparent); }
.product-inline-error-row td { color: #b3261e; font-weight: 600; font-size: 12px; padding-top: 4px; }
.product-inline-hint-row td { color: var(--muted, #7a8194); font-size: 12px; padding-top: 4px; }

/* ── Stock Verification history ───────────────────────────────────────────
   Signed differences: green = excess, red = short. The record-popup pair is
   !important because styles.css forces the items table's LAST column emerald
   (#recordViewDialog .record-view-items td:last-child) - equal specificity,
   so loading later here wins; the history-card pair needs no !important. */
#recordViewDialog .record-view-items td.rv-pos { color: #0a5c27 !important; font-weight: 800 !important; }
#recordViewDialog .record-view-items td.rv-neg { color: #b3261e !important; font-weight: 800 !important; }
#recordViewDialog .record-view-items td.rv-total { font-weight: 800 !important; border-top: 2px solid #cdd3de; color: var(--ink, #172333) !important; }
#recordViewDialog .record-view-items td.rv-total.rv-pos { color: #0a5c27 !important; }
#recordViewDialog .record-view-items td.rv-total.rv-neg { color: #b3261e !important; }
/* !important: the Tailwind table refresh layer colours every td through a
   higher-specificity chain, which silently flattened these to slate. */
.sv-history td.rv-pos { color: #0a5c27 !important; font-weight: 700; }
.sv-history td.rv-neg { color: #b3261e !important; font-weight: 700; }
/* A voided register is a tombstone: readable, plainly struck, actions live. */
.sv-history tr.sv-voided-row td { opacity: 0.55; }
.sv-history tr.sv-voided-row td:first-child,
.sv-history tr.sv-voided-row td.action-cell { opacity: 1; }
/* The void tombstone (who / when / why) lives in the note cell and must stay
   readable, so that one cell escapes the strike-through. */
.sv-history tr.sv-voided-row td:not(:first-child):not(.action-cell):not(.sv-void-cell) { text-decoration: line-through; text-decoration-color: rgba(90, 101, 116, 0.5); }
.sv-history tr.sv-voided-row td.sv-void-cell { opacity: 1; }
.sv-history .sv-void-info { font-size: 12px; color: var(--ui-text-muted, #5d6672); }
/* The item filter is a text input inside a select-styled label; give it the
   standard control height (the 42px min-height touch rule already applies). */
.sv-history .sv-history-item input {
  height: var(--control-h);
  box-sizing: border-box;
  padding: 0 10px;
  border: 1px solid var(--line, #d9e1ea);
  border-radius: 8px;
  min-width: 190px;
  font: inherit;
}

/* ── §11 Assorted product lines ───────────────────────────────────────────
   The component grid inside the Add Assorted dialog. Its own rules
   because a dialog table is not a list screen: no toolbar, no pagination, and
   the unit column is read-only text rather than a field. */

/* The standard record-popup size (owner, 3 Aug 2026), same skeleton as
   .record-view-dialog in styles.css: the panel is a column, only the middle
   scrolls, the title and Save/Cancel stay put. */
#assortedProductDialog {
  width: min(1250px, calc(100vw - 28px));
  height: min(750px, calc(100vh - 28px));
  max-height: min(750px, calc(100vh - 28px));
}
#assortedProductDialog .dialog-panel {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
#assortedProductDialog .dialog-grid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
#assortedProductDialog .dialog-actions { flex: 0 0 auto; }
.assorted-lines {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 10px;
}
.assorted-lines th {
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted, #5a6574);
  padding: 4px 8px 6px;
  border-bottom: 1px solid var(--line, #d9e1ea);
}
.assorted-lines td {
  padding: 4px 8px;
  vertical-align: middle;
  border-bottom: 1px solid var(--line-soft, #eef2f6);
}
/* The bare element rule at styles.css:1576 gives every select a 260px width;
   these need to share the row instead. */
.assorted-lines td select,
.assorted-lines td input,
.assorted-lines td .global-dropdown {
  width: 100%;
  min-width: 0;
}
.assorted-lines td:nth-child(2) { width: 190px; }
/* Read-only, and filled from the product master - the quantity is always in
   the component's own unit, so this must not look editable.

   vertical-align is restated here rather than inherited from the td rule
   above, because this cell holds bare text next to a 42px-tall input (the
   global touch-target min-height at styles.css:32110) and any disagreement
   shows up immediately as the unit sitting off the input's centre line. */
.assorted-lines .assorted-comp-unit {
  width: 64px;
  font-weight: 600;
  font-size: 13px;
  color: var(--muted, #5a6574);
  vertical-align: middle;
  white-space: nowrap;
}
.assorted-lines td:last-child { width: 44px; text-align: right; }

/* ── Searchable component picker ──────────────────────────────────────────
   A native <select> cannot be searched and the product master runs to
   hundreds of sweets, so the first cell is a combobox: a text input that
   filters, over a hidden input holding the committed id. */
.assorted-lines .assorted-comp-pick {
  position: relative;
}
.assorted-lines .assorted-comp-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% - 2px);
  left: 8px;
  right: 8px;
  max-height: 232px;
  overflow-y: auto;
  background: var(--surface, #fff);
  border: 1px solid var(--line, #d9e1ea);
  border-radius: 8px;
  box-shadow: 0 10px 26px rgba(15, 30, 55, .16);
  padding: 4px;
}
/* [hidden] loses to any explicit display, and the rules above give this a
   box - so state it. (CLAUDE.md section 7: verify pixels, not attributes.) */
.assorted-lines .assorted-comp-menu[hidden] { display: none; }
.assorted-lines .assorted-comp-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 34px;
  padding: 6px 10px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font: inherit;
  font-size: 13px;
  color: var(--text, #1f2a37);
  text-align: left;
  cursor: pointer;
}
.assorted-lines .assorted-comp-option:hover,
.assorted-lines .assorted-comp-option.is-active {
  background: var(--accent-soft, #eef4ff);
}
.assorted-lines .assorted-comp-option-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.assorted-lines .assorted-comp-option-unit {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .03em;
  color: var(--muted, #5a6574);
}
.assorted-lines .assorted-comp-empty {
  padding: 10px;
  font-size: 13px;
  color: var(--muted, #5a6574);
}

/* ── Reports Access by Role ───────────────────────────────────────────────
   The settings layer sets `display: grid` on EVERY `.settings-form label`
   (styles.css:11977) and a 42px min-height on inputs, which between them
   stacked each checkbox above its own text and stretched the rows to a full
   screen of scrolling. Each selector below is deliberately deep enough to
   outrank those two, and the checkbox size is restated because the global
   touch-target rule has no checkbox exclusion. */
.settings-form.reports-access-form {
  display: block;
}

.reports-access-form .inline-select {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.reports-access-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.settings-form .reports-access-group {
  margin: 0;
  padding: 10px 12px 8px;
  border: 1px solid var(--ui-border, #d5dae0);
  border-radius: 10px;
  background: var(--ui-surface, #fff);
}

.settings-form .reports-access-group legend {
  padding: 0 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ui-muted, #64748b);
}

/* The row itself: checkbox and text side by side on ONE line. */
.settings-form .reports-access-group .reports-access-option {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 0;
  margin: 0;
  padding: 5px 4px;
  border-radius: 7px;
  color: var(--ui-text, #16191d);
  font-size: 12.5px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
}

.settings-form .reports-access-group .reports-access-option:hover {
  background: var(--ui-hover, rgba(15, 23, 42, 0.04));
}

.settings-form .reports-access-group .reports-access-option input[type="checkbox"] {
  width: 15px;
  height: 15px;
  min-height: 15px;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--ui-primary, #1d4ed8);
  cursor: pointer;
}

.settings-form .reports-access-group .reports-access-option span {
  line-height: 1.3;
}

@media (max-width: 620px) {
  .reports-access-groups { grid-template-columns: 1fr; }
}

.reports-access-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--ui-border, #d5dae0);
}

.reports-access-count {
  font-size: 12.5px;
  color: var(--ui-muted, #64748b);
}

.reports-access-count strong { color: var(--ui-text, #16191d); }

/* Bulk buttons sit together; Save is pushed to the far end. */
.reports-access-bulk { display: inline-flex; gap: 6px; }
.reports-access-actions .primary-button { margin-left: auto; }

/* ── Menu Access: summary bar, expand/collapse all, per-group bulk ────────
   The groups already folded and counted themselves; what was missing was a
   way to see the whole picture at once and to switch a group without ticking
   every line. */
.role-owner-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
  padding: 8px 10px;
  border: 1px solid var(--ui-border, #d5dae0);
  border-radius: 9px;
  background: var(--ui-surface-muted, #f6f8fa);
}

.role-owner-summary {
  font-size: 12.5px;
  color: var(--ui-muted, #64748b);
}

.role-owner-summary strong {
  font-size: 15px;
  color: var(--ui-primary, #1d4ed8);
}

.role-owner-toolbar-actions {
  display: inline-flex;
  gap: 6px;
  margin-left: auto;
}

/* Bulk row inside an opened group. */
.role-owner-branch-actions {
  display: flex;
  gap: 6px;
  padding: 2px 0 8px;
  border-bottom: 1px dashed var(--ui-border, #d5dae0);
  margin-bottom: 6px;
}

.role-owner-branch-actions .mini-action {
  font-size: 11.5px;
  padding: 3px 9px;
}

/* A group with nothing switched on reads as off even while folded. */
.role-owner-group.has-none-on .role-owner-group-count {
  opacity: 0.6;
}

/* == Stock Verification dialog (sv2) =======================================
   The ERP's "New Register" dialog, wearing the SAME skin as the POS handover
   modal at /pos - palette values copied verbatim from pos-react/src/styles.css
   (page #f0eee9, ink #1c1c1a/#55544f/#8b8a84, borders #ede7dd/#e7e6e2/#d9d2c5,
   accent green #2e9968/#1f7a5c, gold pill #fbf1dc/#9a6a10, thead #1c1c1a on
   #f4f2ee). The owner compared the two side by side and they must be
   indistinguishable (31 Jul 2026). Both are 1250x750, viewport-capped. */
#stockRegisterDialog.sv2 {
  width: min(1250px, calc(100vw - 24px));
  max-width: min(1250px, calc(100vw - 24px));
  height: min(750px, calc(100vh - 24px));
  max-height: min(750px, calc(100vh - 24px));
  border: 1px solid #ede7dd;
  border-radius: 16px;
  padding: 0;
  background: #fcfcfb;
}
#stockRegisterDialog.sv2 .dialog-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  max-height: none;
  box-sizing: border-box;
  padding: 18px 22px;
  overflow: hidden;
  background: #fcfcfb;
  color: #1c1c1a;
}
#stockRegisterDialog.sv2 .sv2-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
#stockRegisterDialog.sv2 .sv2-head h2 { margin: 0; color: #1c1c1a; }
#stockRegisterDialog.sv2 .eyebrow {
  color: #8b8a84;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-size: 10.5px;
  font-weight: 700;
}
.sv2-pills { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sv2-pill {
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 11.5px;
  font-weight: 700;
  background: #f6f6f4;
  color: #55544f;
  white-space: nowrap;
}
.sv2-pill.warn { background: #fbf1dc; color: #9a6a10; }
/* The ERP's .settings-note is a gold-bordered cream callout - the loudest
   colour clash against the POS design. Flattened to the quiet context line
   the POS header uses. */
#stockRegisterDialog.sv2 .sv2-context {
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  display: block;
}
#stockRegisterDialog.sv2 .sv2-context strong { color: #1c1c1a; font-size: 13px; display: block; }
#stockRegisterDialog.sv2 .sv2-context span { color: #8b8a84; font-size: 12px; }

/* ---- Location picker in the context line (owner, 5 Aug 2026) -------------
   The line is one sentence - "ISKCON . Balu . 215 items . 05 Aug 2026" - so
   the picker has to read as part of it rather than as a form control: no box,
   the sentence's own type, and an affordance that appears only on hover and
   focus. dropdown-theme.js re-parents the select into a generated wrapper and
   COPIES its class list onto it, so these rules dress the visible control;
   the native select itself is the 1x1px node inside. */
#stockRegisterDialog.sv2 .sv2-context strong #stockRegisterSummaryRest {
  /* Without this the generic `.sv2-context span` rule above drops the rest of
     the sentence to 12px grey, mid-sentence. */
  color: inherit; font-size: inherit; font-weight: inherit;
}
/* The generated wrapper is itself a SPAN, so `.sv2-context span` above claims
   it at (1,2,1) and every descendant then inherits 12px grey - the button and
   the label included. Resetting it here at (1,3,0) is what actually fixes the
   type; styling only the button leaves it inheriting from a grey parent. */
#stockRegisterDialog.sv2 .sv2-context .global-dropdown {
  width: auto; vertical-align: baseline;
  color: inherit; font-size: inherit;
}
#stockRegisterDialog.sv2 .sv2-context .global-dropdown-button {
  width: auto; min-height: 0; padding: 1px 4px 1px 6px; gap: 3px;
  border: 1px solid transparent; border-radius: 6px; background: transparent;
  font: inherit; font-weight: 700; color: inherit;
}
/* The themed control puts its label in a SPAN, which the `.sv2-context span`
   rule above matches at (1,2,1) and drops to 12px grey - so the location would
   render lighter and smaller than the sentence it belongs to. (1,3,0) here
   takes it back. Setting it on the button alone does nothing: the span has its
   own matching rule and never inherits. */
#stockRegisterDialog.sv2 .sv2-context .global-dropdown-value {
  color: inherit; font-size: inherit; font-weight: 700;
}
#stockRegisterDialog.sv2 .sv2-context .global-dropdown-button:hover,
#stockRegisterDialog.sv2 .sv2-context .global-dropdown.open .global-dropdown-button {
  border-color: rgba(28, 28, 26, 0.16); background: rgba(28, 28, 26, 0.05);
}
/* Pinned to one counter: no caret, no hover, no pointer. It has to read as
   exactly the plain text it replaced, or a cashier keeps trying to change it. */
#stockRegisterDialog.sv2 .sv2-context .global-dropdown.is-disabled .global-dropdown-button {
  border-color: transparent; background: transparent; cursor: default; padding-left: 0;
}
#stockRegisterDialog.sv2 .sv2-context .global-dropdown.is-disabled .global-dropdown-icon { display: none; }
.sv2-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sv2-search { flex: 1 1 320px; min-width: 220px; }
#stockRegisterDialog.sv2 .sv2-search input {
  width: 100%;
  height: 38px;
  box-sizing: border-box;
  border: 1px solid #d9d2c5;
  border-radius: 10px;
  padding: 0 13px;
  font-size: 13px;
  color: #1c1c1a;
  background: #ffffff;
}
#stockRegisterDialog.sv2 .sv2-search input:focus {
  border-color: #2e9968;
  box-shadow: 0 0 0 3px #e7f0eb;
  outline: none;
}
#stockRegisterDialog.sv2 .sv2-tools .ghost-button {
  border: 1px solid #d9d2c5;
  background: #ffffff;
  color: #55544f;
  border-radius: 8px;
  font-size: 11.5px;
  font-weight: 700;
}
#stockRegisterDialog.sv2 .sv2-tools [data-stock-register-fill-system] {
  color: #16624a;
  border-color: #2e9968;
}
.sv2-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.sv2-chip {
  border: 1px solid #d9d2c5;
  border-radius: 999px;
  padding: 7px 14px;
  background: #ffffff;
  font-size: 12.5px;
  font-weight: 700;
  color: #55544f;
  cursor: pointer;
  line-height: 1;
}
.sv2-chip:hover { border-color: #8b8a84; color: #1c1c1a; }
.sv2-chip-active, .sv2-chip-active:hover { background: #1c1c1a; color: #f0eee9; border-color: #1c1c1a; }
#stockRegisterDialog.sv2 .stock-register-scroll {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  overflow: auto;
  border: 1px solid #ede7dd;
  border-radius: 12px;
  background: #ffffff;
}
#stockRegisterDialog.sv2 .stock-register-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: #1c1c1a;
  color: #f4f2ee;
  text-transform: uppercase;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  border: none;
}
#stockRegisterDialog.sv2 .stock-register-table td {
  border-bottom: 1px solid #e7e6e2;
  vertical-align: middle;
}
#stockRegisterDialog.sv2 .stock-register-table td strong {
  font-size: 13.5px;
  font-weight: 600;
  color: #1c1c1a;
  display: block;
}
#stockRegisterDialog.sv2 .stock-register-table td strong + span {
  font-size: 11px;
  color: #8b8a84;
}
#stockRegisterDialog.sv2 .stock-register-table input[data-stock-register-count] {
  height: 36px;
  border: 1px solid #d9d2c5;
  border-radius: 9px;
  background: #fcfcfb;
  font-size: 13.5px;
  font-weight: 700;
  color: #1c1c1a;
  text-align: right;
  padding: 0 10px;
}
#stockRegisterDialog.sv2 .stock-register-table input[data-stock-register-count]:focus {
  border-color: #2e9968;
  box-shadow: 0 0 0 3px #e7f0eb;
  outline: none;
}
/* Variance as the POS pill, not bare coloured text. */
#stockRegisterDialog.sv2 .stock-register-variance { color: #8b8a84; }
#stockRegisterDialog.sv2 .stock-register-variance.ok,
#stockRegisterDialog.sv2 .stock-register-variance.warn {
  font-size: 12px;
  font-weight: 700;
}
#stockRegisterDialog.sv2 .stock-register-variance.ok { color: #16624a; }
#stockRegisterDialog.sv2 .stock-register-variance.warn { color: #9a6a10; }
#stockRegisterDialog.sv2 .stock-register-table tr[data-stock-register-search-hidden="1"] { display: none; }
#stockRegisterDialog.sv2 textarea[name="note"] {
  border: 1px solid #d9d2c5;
  border-radius: 10px;
  background: #fcfcfb;
  color: #1c1c1a;
  font-size: 12.5px;
}
/* !important: the ERP's .primary-button paints an indigo GRADIENT with
   !important, which no specificity can outrank - measured live: without this
   the button computed backgroundImage linear-gradient(rgb(42,51,144)...) and
   the green never showed. */
#stockRegisterDialog.sv2 #submitStockRegister {
  background: #2e9968 !important;
  border-color: #1f7a5c !important;
  color: #ffffff !important;
}
#stockRegisterDialog.sv2 #submitStockRegister:hover { background: #1f7a5c !important; }

/* Sidebar skeleton while the first post-login bootstrap is in flight - holds
   the menu space so nothing role-inappropriate flashes (see renderNav). */
.nav-skeleton { display: grid; gap: 10px; padding: 12px 14px; }
.nav-skeleton-item {
  height: 40px;
  border-radius: 10px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.08));
  background-size: 200% 100%;
  animation: nav-skeleton-shimmer 1.2s ease-in-out infinite;
}
body:not(.owner-nav-navy) .nav-skeleton-item {
  background: linear-gradient(90deg, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.05));
  background-size: 200% 100%;
}
@keyframes nav-skeleton-shimmer {
  0% { background-position: 180% 0; }
  100% { background-position: -20% 0; }
}

/* Split variance totals in the Stock Verification dialog footer - profit and
   loss shown separately, never netted (owner, 1 Aug 2026). */
.sv2-totals { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.sv2-total {
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
}
.sv2-total.over { background: #e7f0eb; color: #16624a; }
.sv2-total.short { background: #fbf1dc; color: #9a6a10; }

/* Stock Verification toolbar - one aligned row (owner, 1 Aug 2026). Every
   control sits at the shared --control-h; the search stretches; New Register
   pins to the right edge the way Filters/Refresh do on the Stock screen. */
.sv-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin: -2px 0 10px;
}
.sv-toolbar .sv-search {
  flex: 1 1 220px;
  min-width: 180px;
  height: var(--control-h) !important;
  min-height: var(--control-h);
  box-sizing: border-box;
}
.sv-toolbar .sv-search input { height: 100%; }
.sv-toolbar .sv-toggle,
.sv-toolbar .product-add-icon {
  height: var(--control-h) !important;
  min-height: var(--control-h) !important;
  align-self: flex-end;
}
.sv-toolbar .product-add-icon { margin-left: auto; }
.sv-toolbar .compact-inline-select {
  width: auto;
  min-width: 0;
  flex: 0 0 auto;
  border: 0;
  padding: 0;
  background: transparent;
}
.sv-toolbar .compact-inline-select select,
.sv-toolbar .compact-inline-select .global-dropdown {
  width: 180px;
  min-width: 180px;
  min-height: var(--control-h);
}
@media (max-width: 980px) {
  .sv-toolbar .product-add-icon { margin-left: 0; }
  .sv-toolbar .compact-inline-select select,
  .sv-toolbar .compact-inline-select .global-dropdown {
    width: min(220px, calc(100vw - 48px));
    min-width: 0;
  }
}

/* Reports Access follows the Menu Access role - the chip states which role is
   being edited instead of a second, independently-set dropdown. */
.reports-access-rolebar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.reports-access-rolechip {
  background: #101418;
  color: #ffffff;
  border-radius: 999px;
  padding: 6px 16px;
  font-size: 13px;
  font-weight: 700;
}
.reports-access-rolehint { color: var(--muted, #667485); font-size: 12px; }

/* Stock bulk Edit Mode - the Qty column as a keyboard-walkable input rail. */
.stock-bulk-input {
  width: 96px !important;
  min-width: 96px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Session recovery dialog - sits above every other layer, because the whole
   point is that the work behind it stays untouched while it is answered. */
.session-recovery-dialog {
  border: none;
  border-radius: 14px;
  padding: 0;
  max-width: 430px;
  width: calc(100vw - 32px);
}
.session-recovery-dialog::backdrop {
  background: rgba(15, 18, 28, 0.55);
}
.session-recovery-dialog .dialog-panel {
  display: grid;
  gap: 14px;
  padding: 22px;
}

/* Sortable Item/Quantity headers in the transfer and stock-request item lists.
   The button has to look exactly like the plain header it replaced until it is
   hovered, or the list head reads as a row of controls. */
.transfer2-sort {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.transfer2-sort:hover { color: var(--accent, #3f3d9e); }
.transfer2-sort.is-sorted { color: var(--accent, #3f3d9e); font-weight: 700; }
.transfer2-sort-caret { font-size: 10px; line-height: 1; }
/* == Section 12: Record Wastage / Expiry dialog - two-pane fast entry =======

   A counter records ten items in one sitting. Today the pending-lines box sits
   BETWEEN the Add button and the Reason/Note fields, so every Add grows the
   middle of the form and pushes everything below it down; #wastageForm itself
   is the scroll container (styles.css:55016), so after four items the Post
   button has walked off the bottom.

   So the entry fields and the list stop sharing a vertical axis. The entry
   column is fixed width and never changes height; the ledger grows and scrolls
   inside itself; the action row is its own grid row of the FORM, which makes it
   structurally incapable of being scrolled away at any viewport size.

   Every selector here out-specifies the styles.css rule it replaces, so this
   block does not depend on being loaded last. The size and two-pane rules are
   held behind (min-width: 641px) on purpose: below that, styles.css:30277+
   already makes dialogs full-screen with sticky actions, and styles.css:30292
   uses !important, which nothing can beat cleanly. Leave that alone.
   ========================================================================== */

/* The two-pane wastage layout (entry card on the left, ledger on the right)
   was replaced by the Add Expense grid on 3 Aug 2026. Its ~400 lines of
   layout lived HERE and, loading after the new rules, kept re-imposing the
   old geometry - the reported "details overflowing into other menus". The
   grid's own rules are at the foot of this file, scoped #wastageDialog. */

/* ---- Release notes & support call ---------------------------------------
   Added 2 Aug 2026. The "What's new" card sits at the foot of every role's
   dashboard (renderOverview appends it once, so a new role cannot forget it)
   and the release history reuses the standard dialog shell. */
/* Tightened so eight releases clear the dialog without scrolling: 90px was
   still spilling, and 8 notes x ~11px of padding is exactly that. */
.release-note { padding: 5px 0 7px; border-bottom: 1px solid var(--ds-line); }
.release-note:last-child { border-bottom: 0; }
.release-note-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px; }
.release-note-head strong { font-size: 14px; color: var(--ink, #16324f); }
.release-note-meta { font-size: 11.5px; font-weight: 600; color: var(--muted, #667485); white-space: nowrap; }
.release-note-list { margin: 4px 0 0; padding-left: 16px; display: grid; gap: 2px; }
.release-note-list li { font-size: 12.5px; line-height: 1.5; color: var(--muted, #4a5a6e); }
.release-note-more { margin: 6px 0 0; font-size: 11.5px; font-weight: 600; color: var(--muted, #667485); }
.release-card-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; }
.release-card-count { font-size: 11.5px; color: var(--muted, #667485); }
/* Size the DIALOG, not the panel inside it. Setting only the panel left it
   640px wide inside a 520px dialog, which overflowed by 120px - that was the
   horizontal scrollbar and the clipped left edge (reported 2 Aug 2026). */
#releaseNotesDialog {
  width: min(1100px, calc(100vw - 24px));
  max-width: calc(100vw - 24px);
  height: auto;
  max-height: min(900px, calc(100vh - 24px));
}
#releaseNotesDialog .dialog-panel {
  width: 100%;
  max-width: 100%;
  max-height: min(900px, calc(100vh - 24px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* flex:1 + min-height:0 so the list takes the room the dialog has instead of
   a fixed 480px that scrolled while the dialog had space to spare. */
#releaseNotesDialog .release-notes-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* Two columns once there is room: eight releases read in one glance instead of
   being scrolled past. A note never splits across the column break. */
@media (min-width: 900px) {
  #releaseNotesDialog .release-notes-body { columns: 2; column-gap: 26px; }
  #releaseNotesDialog .release-note { break-inside: avoid; page-break-inside: avoid; }
}

/* The number is the point of this dialog, so it is the biggest thing in it and
   it is a real tel: link - a phone dials it, a counter PC shows it plainly. */
.support-call-dialog .dialog-panel { width: min(420px, calc(100vw - 32px)); text-align: center; }
.support-call-number { display: block; margin: 6px 0 2px; font-size: 30px; font-weight: 800; letter-spacing: 0.02em; color: var(--ds-accent); text-decoration: none; font-variant-numeric: tabular-nums; }
.support-call-number:hover, .support-call-number:focus-visible { text-decoration: underline; }
.support-call-note { margin: 0; font-size: 12px; color: var(--muted, #667485); }

/* The What's new button and its unread dot. A dot rather than a count: it sits
   beside Alerts, and two number badges competing would make both easier to
   ignore - this one only says "something changed since you last looked". */
.top-icon-button.whatsnew-button { position: relative; }
.whatsnew-dot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: 50%; background: var(--ds-accent); box-shadow: 0 0 0 2px var(--surface, #fff); }
.whatsnew-dot[hidden] { display: none; }

/* ---- Expense entry: an editable grid (2 Aug 2026) ------------------------
   Rewritten from the two-pane "type on the left, press + Add item" shape. The
   owner asked for the rows themselves to be editable, with a category per row
   and rows added in batches - keying a shop run, not filing one item at a time.

   The dialog is a DEFINITE-height flex column. The old version left the form's
   grid rows at auto height inside a 750px box, so short content left a wide
   empty band under the buttons (owner: "lot of space at down"). Now the rows
   pane is the only flexible child, so that slack becomes usable grid instead. */
#expenseDialog { width: min(1250px, calc(100vw - 24px)); height: min(750px, calc(100vh - 24px)); max-width: 100%; }
#expenseDialog #addExpenseForm { display: flex; flex-direction: column; gap: 14px; height: 100%; min-height: 0; }
#expenseDialog #addExpenseForm :is(input, select, textarea, .global-dropdown, .global-datepicker) { width: 100%; min-width: 0; max-width: 100%; }
#expenseDialog .expense-head { flex: 0 0 auto; }
#expenseDialog .expense-top-grid { flex: 0 0 auto; grid-template-columns: repeat(4, minmax(0, 1fr)); }
#expenseDialog .expense-top-grid > label { min-width: 0; }

/* The grid pane: header, column strip, then the scrolling rows. */
#expenseDialog .expense-grid-pane {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  border: 1px solid var(--ds-line);
  border-radius: 12px;
  background: var(--ds-tint);
  overflow: hidden;
}
#expenseDialog .expense-grid-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; padding: 11px 15px 9px; }
#expenseDialog .expense-grid-head h3 { margin: 0; font-size: 13px; display: flex; align-items: baseline; gap: 8px; }
#expenseDialog .expense-list-count { font-size: 11.5px; font-weight: 600; color: var(--muted, #667485); }
#expenseDialog .expense-grid-tools { display: flex; align-items: center; gap: 6px; }
#expenseDialog .expense-grid-tools[hidden] { display: none; }
#expenseDialog .expense-grid-tools-label { font-size: 11px; font-weight: 600; color: var(--muted, #667485); margin-right: 2px; }
#expenseDialog .expense-grid-tools .mini-action { min-width: 42px; min-height: 28px; padding: 0 9px; font-weight: 700; font-variant-numeric: tabular-nums; }
#expenseDialog .expense-grid-tools .link-button { background: none; border: 0; padding: 0 2px; font-size: 12px; font-weight: 700; color: var(--ds-accent); cursor: pointer; }

/* One grid definition for the head strip and every row, so a column can never
   drift out of line with its heading. */
#expenseDialog .expense-grid-cols,
#expenseDialog .expense-grid-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1.9fr) minmax(0, 1fr) 76px 116px 32px;
  align-items: center;
  gap: 10px;
  padding: 0 15px;
}
#expenseDialog .expense-grid-cols { padding-bottom: 6px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted, #667485); border-bottom: 1px solid var(--ds-line); }
#expenseDialog .expense-grid-rows { overflow-y: auto; align-content: start; padding: 4px 0 6px; }
#expenseDialog .expense-grid-row { min-height: 44px; }
#expenseDialog .expense-row-no { font-size: 11.5px; color: var(--muted, #8794a5); font-variant-numeric: tabular-nums; }
/* Rows are dense by design: 10 of them have to be readable at once, so the
   inputs are shorter than the app's standard 42px control. */
#expenseDialog .expense-grid-row :is(input, .global-dropdown-button) { min-height: 34px; height: 34px; font-size: 12.5px; }
#expenseDialog .expense-grid-row .expense-row-amount,
#expenseDialog .expense-grid-row .expense-row-qty { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
#expenseDialog .expense-grid-row > button {
  width: 26px; height: 26px; display: grid; place-items: center;
  border: 1px solid var(--ds-line); border-radius: 6px; background: #fff;
  color: var(--muted, #667485); font-size: 15px; line-height: 1; cursor: pointer;
}
#expenseDialog .expense-grid-row > button:hover { border-color: var(--ds-danger); color: var(--ds-danger); }

/* Footer: what each category came to, the total, and the buttons. */
#expenseDialog .expense-foot { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 14px 22px; padding-top: 12px; border-top: 2px solid var(--ds-ink); }
#expenseDialog .expense-cat-totals { display: flex; flex-wrap: wrap; gap: 18px; margin-right: auto; }
#expenseDialog .expense-cat-total { display: grid; gap: 1px; }
#expenseDialog .expense-cat-total small { font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted, #8794a5); }
#expenseDialog .expense-cat-total strong { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--muted, #8794a5); }
#expenseDialog .expense-cat-total.is-live strong { color: var(--ds-ink); }
#expenseDialog .expense-total-figure { display: grid; gap: 1px; text-align: right; }
#expenseDialog .expense-total-label { font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted, #667485); }
#expenseDialog .expense-total-value { font-size: 24px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.1; }
#expenseDialog .expense-foot .dialog-actions { margin: 0; }

@media (max-width: 900px) {
  #expenseDialog .expense-top-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Qty is the first thing to go: most shop-run rows are one of a thing. */
  #expenseDialog .expense-grid-cols,
  #expenseDialog .expense-grid-row { grid-template-columns: 26px minmax(0, 1.6fr) minmax(0, 1fr) 100px 30px; }
  #expenseDialog .expense-grid-cols > span:nth-child(4),
  #expenseDialog .expense-grid-row .expense-row-qty { display: none; }
}

/* The reason box inside the delete confirmation, and the marker a deleted
   expense leaves in the register in place of its action buttons. */
.expense-delete-reason { display: grid; gap: 4px; margin: 4px 0 2px; text-align: left; font-size: 12px; }
.expense-void-note { font-size: 11.5px; color: var(--muted, #667485); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; max-width: 220px; }

/* ---- Transfer / request line: the item code -----------------------------
   The code was plain grey text at the front of "CODE - per unit", so it was
   the first thing the column's ellipsis removed. Its own element now, with
   flex-shrink:0 so the unit gives way first, and enough contrast to read at a
   glance (owner, 2 Aug 2026: "item number should be visible after adding"). */
.transfer2-form .transfer2-row-meta { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.transfer2-form .transfer2-row-code {
  flex: 0 0 auto;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-2, #46586f);
  font-variant-numeric: tabular-nums;
}
.transfer2-form .transfer2-row-unit {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Topbar: search centred, actions as icons (2 Aug 2026) --------------
   The header was two columns - title, then everything else crowded to the
   right. Three now, so the search sits in the middle instead of being shoved
   about by whatever shares the action cluster. */
/* The selector has to carry body:not(.logged-out):not(.pos-fullscreen-mode)
   because styles.css has SEVENTEEN competing .topbar grid rules and the ones
   that actually win are written that way - (0,3,1) against a bare .topbar's
   (0,1,0). A plain .topbar rule here loses silently and looks like the change
   did nothing. Matching the specificity and loading later is what wins. */
body:not(.logged-out):not(.pos-fullscreen-mode) .topbar {
  /* Equal side columns are what actually centres the middle one. With the
     title column at 1fr and the actions column sized to its content, the
     search sat off-centre by however much the two differed. */
  grid-template-columns: minmax(200px, 1fr) minmax(0, auto) minmax(200px, 1fr);
}
/* styles.css gives .global-search-group order:1, which threw it past the
   action cluster and put it LAST - the search ended up right of the icons
   instead of between the title and them. All three get an explicit order so
   the row reads title, search, actions regardless of what else sets one. */
body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > *:first-child { order: 1; }
body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > .global-search-group {
  order: 2;
  justify-self: center;
  width: 100%;
  max-width: 560px;
}
body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > .topbar-actions { order: 3; justify-self: end; }
.topbar-actions { align-items: center; }

/* What's new and Help are icon-only now and sit with the bell, so all three
   are the same square button. The dot rides on the What's new one. */
.top-icon-button.whatsnew-button,
.top-icon-button.help-button {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  font-size: 0;
}
.top-icon-button.whatsnew-button svg,
.top-icon-button.help-button svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (max-width: 1100px) {
  /* Back to two rows before the search gets squeezed into uselessness. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar { grid-template-columns: minmax(0, 1fr) auto; }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > .global-search-group { grid-column: 1 / -1; justify-self: stretch; max-width: none; order: 3; }
}

/* ---- Transfer / request items: Item code as its own column (2 Aug 2026) ---
   The head and the rows share one grid rule in styles.css, so both are widened
   together here - a code column added to only one of them would misalign every
   row under the header. Specificity matches the original
   (.transfer2-form .transfer2-row) and design-system.css loads later, so this
   wins without !important. */
.transfer2-form .transfer2-list-head,
.transfer2-form .transfer2-row {
  grid-template-columns: 30px 92px minmax(0, 1fr) 92px 96px 102px 34px;
}
.transfer2-form .transfer2-row-codecell {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-2, #46586f);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-right: 8px;
}
.transfer2-form .transfer2-row-code-empty { color: var(--muted, #9aa6b5); font-weight: 500; }

/* Narrow screens: the code matters more than the money, so the price and
   total give way before it does. */
@media (max-width: 720px) {
  .transfer2-form .transfer2-list-head,
  .transfer2-form .transfer2-row { grid-template-columns: 26px 78px minmax(0, 1fr) 78px 34px; }
  .transfer2-form .transfer2-col-price,
  .transfer2-form .transfer2-col-total,
  .transfer2-form .transfer2-row-price,
  .transfer2-form .transfer2-row-total { display: none; }
}

/* ---- List date filter: Today by default, Custom reveals From/To (2 Aug 2026)
   Only the GROUPING is set here. The three controls already carry
   .inventory-sort-field, which styles.css sizes at 190px (min 140, max 220) and
   re-sizes to 44% under 900px - both with !important on a
   body:not(.logged-out):not(.pos-fullscreen-mode) chain, so any width written
   here would lose at every breakpoint. Wrapping them in one flex item is what
   this needs anyway: the toolbar can then wrap around the three but never
   between them, so a "To" box can't drift onto the next line beside Status and
   read as if it belonged to Status. */
.list-date-group {
  display: flex;
  /* nowrap on a real screen: the three keep one line and the toolbar wraps
     around them. Allowed to shrink and the group splits internally instead,
     which is the same visual break the wrapper exists to prevent. */
  flex-wrap: nowrap;
  gap: 10px;
  align-items: flex-end;
  flex: 0 0 auto;
}
/* Under 900px styles.css forces these controls to 44% !important, two per row,
   so a single line is not achievable - give the group its own full-width band
   instead, where the three still read as one block. */
@media (max-width: 900px) {
  .list-date-group {
    flex: 1 1 100%;
    flex-wrap: wrap;
  }
}
/* calendar-theme.js re-parents the input into a .global-datepicker span and
   shrinks the native control to 1x1px, so the VISIBLE control is the wrapper -
   letting it fill the label keeps From and To the same width as the preset. */
.list-date-group .list-date-custom .global-datepicker,
.list-date-group .list-date-custom .global-datepicker-field { width: 100%; }

/* ---- Transfer / request item search: results float, list stays put -------
   The suggestions were `position: static` and sat in the flow BETWEEN the
   column header and the rows (styles.css:21270), so every keystroke shoved the
   items already added further down the panel, and both lists rendered as
   near-identical rows under one header. The owner's words (2 Aug 2026): "cannot
   identify the added item vs search item ... its pushing the existing item to
   below".

   Now it is what it always looked like it should be: a dropdown floating over
   the list. The rows underneath never move, and the panel reads as a separate
   surface - its own card, shadow and caption - rather than more table rows. */
.transfer2-form .transfer2-listwrap { position: relative; }
.transfer2-form .transfer2-dropdown {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  max-height: min(320px, 52vh);
  padding: 0 6px 6px;
  background: #fff;
  border: 1px solid var(--ds-line);
  border-radius: 12px;
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);
}
/* Says what the floating panel is, and stays put while the results scroll. */
.transfer2-form .transfer2-dropdown::before {
  /* Literal characters, not \2014-style escapes: a CSS hex escape swallows any
     following hex digit, so "\2014 click" loses its c to the escape. */
  content: "Search results — click to add";
  position: sticky;
  top: 0;
  z-index: 1;
  display: block;
  padding: 9px 8px 7px;
  margin: 0 -6px 2px;
  background: #fff;
  border-bottom: 1px solid var(--ds-line-soft);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted, #7a8194);
}
.transfer2-form .transfer2-dd-option { padding-left: 6px; padding-right: 6px; }
.transfer2-form .transfer2-dd-added {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  /* Accent, not green: the whole already-added state is blue now (3 Aug). */
  background: color-mix(in srgb, var(--ds-accent) 12%, transparent);
  color: var(--ds-accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  vertical-align: 1px;
}


/* =========================================================================
   ITEM PICKER - shared "search and add items" layout        (2 Aug 2026)
   Used by Stock Transfer, Stock Transfer Request and the Production raw
   material table. One definition here, opted into per screen by adding the
   classes - a fourth screen with an item picker reuses it as-is.
   ========================================================================= */

/* ---- Tokens -------------------------------------------------------------
   The --ds-* family was already referenced throughout this file but was
   NEVER DEFINED anywhere (0 declarations in design-system.css, styles.css or
   tailwind.css), so every `var(--ds-line, #e6edf5)` silently used its literal
   fallback - and the same token carried three different fallbacks in three
   places. Defining them here retires those literals and makes the values
   editable in one spot. The chosen values are the fallbacks already in use,
   so nothing shifts except the three --ds-line sites that disagreed. */
:root {
  /* Surfaces */
  --ds-surface: #ffffff;
  --ds-surface-muted: #f4f7fb;
  --ds-tint: #f8fbff;
  /* Lines */
  --ds-line: #e6edf5;
  --ds-line-soft: #eef3f8;
  --ds-border: #d9e2ef;
  /* Text - both pass WCAG AA on every surface and tint defined here */
  --ds-ink: #14213d;          /* 14.6:1 on white */
  --ds-text-muted: #55627a;   /* 6.4:1 on white, 6.0:1 on the accent tint */
  /* Status. Measured from the app's own primary button, which renders
     rgb(42,51,144) - NOT var(--brand), which is a stale #aa3232 red that no
     visible control uses any more. 8.9:1 on white. */
  --ds-accent: #2a3390;
  --ds-success: #1f9254;
  --ds-danger: #d94b4b;
  --ds-warning: #8a5300;      /* 5.9:1 on --ds-warning-tint - AA at any size */
  --ds-warning-tint: #fff6e5;
  --ds-warning-line: #e0a24a;
  /* Row washes. color-mix keeps these tied to the accent token instead of
     freezing a second hard-coded green next to it. */
  --ds-accent-tint: color-mix(in srgb, var(--ds-accent) 7%, transparent);
  --ds-accent-tint-strong: color-mix(in srgb, var(--ds-accent) 13%, transparent);
  --ds-stripe: color-mix(in srgb, var(--ds-ink) 3%, transparent);
  --ds-row-hover: color-mix(in srgb, var(--ds-accent) 10%, transparent);
  /* Shape and rhythm */
  --ds-radius: 12px;
  --ds-radius-sm: 8px;
  --ds-gap: 10px;
  --ds-pad: 14px;
}

/* ---- 1. Search zone ------------------------------------------------------
   Tinted block with its own label and a hard bottom edge, so "where I search"
   and "what I have chosen" are two surfaces rather than one flat white sheet. */
.transfer2-form .transfer2-itemsarea .item-picker-search,
.item-picker-search {
  background: var(--ds-surface-muted);
  border: 1px solid var(--ds-line);
  border-radius: var(--ds-radius) var(--ds-radius) 0 0;
  border-bottom: 2px solid var(--ds-border);
  padding: 10px var(--ds-pad) 12px;
  display: grid;
  gap: 6px;
}
.item-picker-search[hidden] { display: none; }
.item-picker-search-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ds-text-muted);
}
/* The input sits ON the tint, so it needs to stay white to read as a field. */
.item-picker-search .transfer2-search {
  background: var(--ds-surface);
  border-radius: var(--ds-radius-sm);
}

/* ---- 2. Added-items zone ------------------------------------------------
   A raised white surface butted directly under the search zone. */
.transfer2-form .transfer2-itemsarea .item-picker-added,
.item-picker-added {
  background: var(--ds-surface);
  border: 1px solid var(--ds-line);
  border-top: none;
  border-radius: 0 0 var(--ds-radius) var(--ds-radius);
  box-shadow: 0 1px 3px rgba(20, 33, 61, 0.05);
}
.item-picker-added-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 10px var(--ds-pad) 8px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ds-text-muted);
  border-bottom: 1px solid var(--ds-line-soft);
}
.item-picker-added-count {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ds-text-muted);
}

/* ---- 3. Row states ------------------------------------------------------
   `> *` so one rule serves both the grid rows (div) used by transfer and
   request and the <tr> rows used by the production materials table. */
.item-picker-rows > * { transition: background-color 120ms ease; }
.item-picker-rows > *:nth-child(even) { background: var(--ds-stripe); }
/* Added rows read as "selected": accent wash plus a left accent edge. inset
   box-shadow rather than border-left so it costs no layout width and cannot
   push a grid column out of line with its header.
   Deepened 3 Aug 2026 - counter staff could not tell a filled row from the
   search list at a glance ("they want more bit dark colours which are already
   added"). The wash went from 7/13% to 15/20% and the name and code take the
   accent ink, so a row with its quantity in reads unmistakably blue. */
.item-picker-rows > .item-picker-row {
  background: color-mix(in srgb, var(--ds-accent) 15%, transparent);
  box-shadow: inset 4px 0 0 0 var(--ds-accent);
}
.item-picker-rows > .item-picker-row:nth-child(even) {
  background: color-mix(in srgb, var(--ds-accent) 20%, transparent);
}
/* :not(.is-incomplete) so a row still WAITING for its quantity keeps neutral
   ink on the amber wash - "blue" is the filled state, exactly as asked. */
.item-picker-rows > .item-picker-row:not(.is-incomplete) :is(.transfer2-row-item, .transfer2-row-codecell) {
  color: var(--ds-accent);
}
.item-picker-rows > *:hover { background: var(--ds-row-hover); }
/* --ds-row-hover is a 10% wash - LIGHTER than an added row's base now, so
   without this override hovering an added row would fade it. */
.item-picker-rows > .item-picker-row:hover {
  background: color-mix(in srgb, var(--ds-accent) 24%, transparent);
}

/* ---- 4. Incomplete rows -------------------------------------------------
   No quantity yet, so the line would save as zero. Wins over the accent wash
   because it is the state the user still has to act on. */
.item-picker-rows > .is-incomplete,
.item-picker-rows > .is-incomplete:nth-child(even) {
  background: var(--ds-warning-tint);
  box-shadow: inset 3px 0 0 0 var(--ds-warning-line);
}
.item-picker-rows > .is-incomplete:hover {
  background: color-mix(in srgb, var(--ds-warning-line) 22%, transparent);
}
/* The class is repeated to raise specificity. styles.css styles these inputs
   through `.dialog-panel input:not([type="checkbox"]):not([type="radio"])
   :not([type="file"])`, and those three :not()s count, putting it at (0,4,1) -
   above a single-class version of this rule. Doubling both classes reaches
   (0,6,1), so the warning outline lands without !important. */
.item-picker-rows > .is-incomplete.is-incomplete :is(input.transfer2-qty-input.transfer2-qty-input, input.production-required-input.production-required-input) {
  border-color: var(--ds-warning-line);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--ds-warning-line) 30%, transparent);
}
/* Focus must still win over the warning outline, or the field the user is
   typing into looks the same as the ones they have not reached yet. */
.item-picker-rows > .is-incomplete.is-incomplete :is(input.transfer2-qty-input.transfer2-qty-input, input.production-required-input.production-required-input):focus-visible {
  border-color: var(--ds-accent);
  box-shadow: none;
}
/* A table row cannot take an inset shadow reliably across engines, so the
   accent edge goes on its first cell instead. */
.item-picker-rows > tr { box-shadow: none; }
.item-picker-rows > .item-picker-row > td:first-child { box-shadow: inset 4px 0 0 0 var(--ds-accent); }
.item-picker-rows > .is-incomplete > td:first-child { box-shadow: inset 3px 0 0 0 var(--ds-warning-line); }

/* ---- 5. Search results --------------------------------------------------
   An item already on the list is filled dark blue, badged, and not addable
   again. It used to be DIMMED instead (opacity 0.62, muted name) - which read
   as "less important" rather than "already yours", and staff kept scanning
   past it (3 Aug 2026). Now it matches the added rows on the right: same
   accent wash, same left edge, so "blue = already on the list" is one rule
   everywhere in the dialog. */
.item-picker-result.is-added {
  cursor: default;
  background: color-mix(in srgb, var(--ds-accent) 15%, transparent);
  box-shadow: inset 4px 0 0 0 var(--ds-accent);
}
.item-picker-result.is-added:hover,
.item-picker-result.is-added.active {
  background: color-mix(in srgb, var(--ds-accent) 22%, transparent);
}
.item-picker-result.is-added .transfer2-dd-plus { color: var(--ds-accent); font-weight: 800; }
.item-picker-result.is-added .transfer2-dd-name { color: var(--ds-accent); font-weight: 700; }


/* ── Boot gate, branded ───────────────────────────────────────────────────
   The loading state of the backend gate used to be three dots in a circle
   with nothing saying whose app this is; the error state already carried the
   plaque. Both wear it now, matching the reports page's boot screen. */
.backend-gate-loader--brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
.backend-gate-boot-logo {
  width: 190px;
  height: auto;
}
.backend-gate-boot-text {
  white-space: nowrap;   /* the first wording wrapped one word per line in a narrow flex column */
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--rp-muted, #5a6574);
}


/* ── Production batches: the Date column breathes ─────────────────────────
   Owner, 3 Aug 2026. Once the entry time moved in under the date the column
   was left too tight - "02 Aug 2026" flirting with a wrap and the time
   squeezed. Reserve real width and forbid wrapping on both lines. Scoped by
   the table id so no other first column widens. */
[data-table-id="kitchen-production-batches"] th:first-child,
[data-table-id="kitchen-production-batches"] td:first-child {
  /* Fits "02 Aug 2026 06:11 pm" on one line at the table's own font size.
     Was 122px when the time sat on a second line under the date. */
  min-width: 172px;
  white-space: nowrap;
}


/* ── Wastage grid: Add Expense's clothes, its own columns ────────────────
   The dialog reuses the expense-* class names for head, tools, rows and foot;
   those rules are scoped #expenseDialog, so the structural set is restated
   here for #wastageDialog with a 9-column template (code, item, reason, qty,
   unit, rate, amount). One grid definition for the head strip and every row,
   so a column can never drift out of line with its heading. */
#wastageDialog { width: min(1250px, calc(100vw - 24px)); height: min(750px, calc(100vh - 24px)); max-width: 100%; }
#wastageDialog .expense-head h2 { margin: 0; }
/* The panel is the scroll container, and ONLY the rows scroll inside it: the
   old sheet let #wastageForm scroll as a whole, which is how the grid rode up
   over the screen behind it. Everything else is fixed height. */
#wastageDialog .dialog-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  padding: 18px 20px 16px;
}
#wastageDialog .expense-head,
#wastageDialog .wastage-top-grid,
#wastageDialog .wastage-note-field,
#wastageDialog .expense-foot { flex: 0 0 auto; }
#wastageDialog .expense-grid-pane { flex: 1 1 auto; }
#wastageDialog .wastage-top-grid { grid-template-columns: 200px 260px; }
#wastageDialog .expense-grid-pane { display: flex; flex-direction: column; min-height: 0; }
#wastageDialog .expense-grid-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
#wastageDialog .expense-grid-head h3 { margin: 0; font-size: 14px; }
#wastageDialog .expense-list-count { font-size: 11.5px; font-weight: 600; color: var(--muted, #667485); margin-left: 6px; }
#wastageDialog .expense-grid-tools { display: flex; align-items: center; gap: 6px; }
#wastageDialog .expense-grid-tools-label { font-size: 11px; font-weight: 600; color: var(--muted, #667485); margin-right: 2px; }
#wastageDialog .expense-grid-tools .mini-action { min-width: 42px; min-height: 28px; padding: 0 9px; font-weight: 700; font-variant-numeric: tabular-nums; }
#wastageDialog .expense-grid-tools .link-button { background: none; border: 0; padding: 0 2px; font-size: 12px; font-weight: 700; color: var(--ds-accent); cursor: pointer; }
#wastageDialog .expense-grid-cols,
#wastageDialog .expense-grid-row {
  display: grid;
  grid-template-columns: 28px 104px minmax(0, 1.5fr) 148px 64px 68px 84px 92px 30px;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
}
#wastageDialog .expense-grid-cols { padding-bottom: 6px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted, #667485); border-bottom: 1px solid var(--ds-line); }
#wastageDialog .expense-grid-rows { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; align-content: start; padding: 4px 0 6px; }
#wastageDialog .expense-grid-row { min-height: 44px; }
/* Grid children default to min-width:auto, so an input's intrinsic width can
   push the whole row wider than its track list - that is a horizontal
   scrollbar on a dialog that should never have one. */
#wastageDialog .expense-grid-row > * { min-width: 0; }
#wastageDialog .expense-grid-row input { width: 100%; }
#wastageDialog .expense-row-no { font-size: 11.5px; color: var(--muted, #8794a5); font-variant-numeric: tabular-nums; }
#wastageDialog .expense-grid-row :is(input, select, .global-dropdown-button) { min-height: 34px; height: 34px; font-size: 12.5px; }
#wastageDialog .expense-grid-row .expense-row-qty { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }
#wastageDialog .wastage-row-amount { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 12.5px; }
#wastageDialog .expense-grid-row > button {
  width: 26px; height: 26px; display: grid; place-items: center;
  border: 1px solid var(--ds-line); border-radius: 6px; background: #fff;
  color: var(--muted, #667485); font-size: 15px; line-height: 1; cursor: pointer;
}
#wastageDialog .expense-grid-row > button:hover { border-color: var(--ds-danger); color: var(--ds-danger); }
#wastageDialog .wastage-note-field { display: grid; gap: 4px; font-size: 12.5px; }
#wastageDialog .expense-foot { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 14px 22px; padding-top: 12px; border-top: 2px solid var(--ds-ink); }
#wastageDialog .expense-total-figure { display: grid; gap: 1px; text-align: right; margin-right: auto; }
#wastageDialog .expense-total-label { font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted, #667485); }
#wastageDialog .expense-total-value { font-size: 24px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.1; }
#wastageDialog .expense-foot .dialog-actions { margin: 0; }
@media (max-width: 900px) {
  #wastageDialog .wastage-top-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #wastageDialog .expense-grid-cols,
  #wastageDialog .expense-grid-row { grid-template-columns: 24px 80px minmax(0, 1.4fr) 120px 52px 56px 70px 80px 28px; }
}

/* ── Quick-create panel ("+ New", owner mock quickactioncreatepanel_1.html,
   4 Aug 2026, second revision) ────────────────────────────────────────────
   A centred dark modal: search rail on top (type to jump, Enter opens the
   first match, ESC closes), five group cards with an oversized watermark
   icon each. The mock's own dark palette is kept deliberately - it is a
   single-theme design - only the font comes from the app. The topbar
   button stays in the ERP's navy. */
.qa-new-button { display: inline-flex; align-items: center; gap: 7px; padding: 0 16px 0 12px; height: 38px; border: 0; border-radius: 10px; background: var(--ds-accent); color: #fff; font: 600 13.5px/1 inherit; font-family: inherit; cursor: pointer; white-space: nowrap; }
.qa-new-button:hover { background: color-mix(in srgb, var(--ds-accent) 86%, #000); }
.qa-new-button svg { width: 16px; height: 16px; }

.qa-overlay { position: fixed; inset: 0; z-index: 1200; }
.qa-overlay[hidden] { display: none; }
.qa-backdrop { position: absolute; inset: 0; background: color-mix(in srgb, var(--ds-ink) 32%, transparent); backdrop-filter: blur(3px); }

/* 1250x750 and anchored NEAR THE TOP (owner, 5 Aug 2026): dead-centring
   pushed the panel low on a laptop, so it read as sitting at the bottom of
   the screen. Same 1250x750 the record-view dialogs use, so the app has one
   large-dialog footprint; both dimensions stay capped to the viewport, so a
   small screen shrinks the panel instead of overflowing it. */
.qa-panel { position: absolute; top: 6vh; left: 50%; transform: translateX(-50%); width: min(1250px, calc(100vw - 48px)); height: min(750px, calc(100vh - 12vh)); max-height: calc(100vh - 64px); overflow: auto; background: var(--ds-surface-muted); border: 1px solid var(--ds-line); border-radius: 20px; padding: 14px; box-shadow: 0 40px 90px -30px color-mix(in srgb, var(--ds-ink) 38%, transparent); animation: qaIn 0.18s ease-out; }
@keyframes qaIn { from { opacity: 0; transform: translateX(-50%) translateY(-10px); } }
@media (prefers-reduced-motion: reduce) { .qa-panel { animation: none; } }

.qa-rail { display: flex; align-items: center; gap: 12px; padding: 8px 10px 18px; }
.qa-search { flex: 1; display: flex; align-items: center; gap: 11px; padding: 12px 16px; border-radius: 12px; background: var(--ds-surface); border: 1px solid var(--ds-border); }
.qa-search svg { width: 18px; height: 18px; flex: none; fill: none; stroke: var(--ds-text-muted); stroke-width: 1.9; stroke-linecap: round; }
.qa-search input { flex: 1; border: 0; background: none; outline: none; color: var(--ds-ink); font: 500 15px/1.2 inherit; font-family: inherit; min-height: 0; height: auto; }
.qa-search input::placeholder { color: var(--ds-text-muted); }
.qa-kbd { font-size: 11px; font-weight: 600; letter-spacing: 0.06em; color: var(--ds-text-muted); border: 1px solid var(--ds-border); border-radius: 5px; padding: 4px 6px; }
.qa-x { width: 42px; height: 42px; flex: none; display: grid; place-items: center; border: 1px solid var(--ds-border); border-radius: 12px; background: var(--ds-surface); color: var(--ds-text-muted); cursor: pointer; }
.qa-x:hover { background: var(--ds-accent-tint); color: var(--ds-accent); border-color: var(--ds-accent); }
.qa-x svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

.qa-groups { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
.qa-group { position: relative; overflow: hidden; background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 14px; padding: 16px 14px 14px; display: flex; flex-direction: column; gap: 2px; }
.qa-group:hover { border-color: var(--ds-accent); background: var(--ds-tint); }
.qa-mark { position: absolute; right: -14px; top: -16px; width: 104px; height: 104px; fill: none; stroke: var(--ds-ink); stroke-width: 1.1; stroke-linecap: round; stroke-linejoin: round; opacity: 0.07; pointer-events: none; }
.qa-group:hover .qa-mark { opacity: 0.14; stroke: var(--ds-accent); }

.qa-label { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.qa-label svg { width: 19px; height: 19px; flex: none; fill: none; stroke: var(--ds-accent); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.qa-label span { font-size: 11.5px; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: var(--ds-ink); }

.qa-item { position: relative; display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 8px 9px; border: 0; border-radius: 9px; background: none; text-align: left; font-size: 14.5px; font-weight: 500; font-family: inherit; color: var(--ds-ink); text-decoration: none; cursor: pointer; }
.qa-item::after { content: "↵"; opacity: 0; font-size: 12px; color: var(--ds-accent); }
.qa-item:hover { background: var(--ds-accent-tint); color: var(--ds-accent); }
.qa-item:hover::after { opacity: 1; }

@media (max-width: 1100px) {
  /* The fixed 1250x750 box must not leak into the narrow layout - it fills
     the screen with a margin instead. */
  .qa-panel { top: 16px; left: 16px; right: 16px; width: auto; height: auto; transform: none; max-height: calc(100vh - 32px); }
  @keyframes qaIn { from { opacity: 0; transform: translateY(-10px); } }
  .qa-groups { grid-template-columns: repeat(2, 1fr); }
  .qa-kbd { display: none; }
}
@media (max-width: 640px) {
  .qa-groups { grid-template-columns: 1fr; }
  .qa-new-button span { display: none; }
  .qa-new-button { padding: 0 11px; }
}

/* Outlets > Close Register (owner, 5 Aug 2026): the pos-cashier-board grid
   put the filter panel in a 381px column, stacking Search / Date / Location
   vertically. This board stacks its sections full-width instead, so the
   search, Date and Location sit in ONE row like Stock Requests. */
.close-register-board { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
.close-register-board .kitchen-search-controls { display: flex; flex-wrap: wrap; align-items: end; gap: 10px 14px; }
.close-register-board .kitchen-search-controls .inventory-search-field { flex: 0 1 340px; }
.close-register-board .kitchen-search-controls .inventory-sort-field { flex: 0 0 200px; }
.close-register-board .kitchen-search-controls .inventory-search-meta { margin-left: auto; }

/* Stock verification close guard (owner, 5 Aug 2026): a scrim inside the
   dialog holding the save / stay / discard choice, so a stray Esc can no
   longer throw away minutes of counting. */
.sv-close-guard { position: absolute; inset: 0; z-index: 40; display: grid; place-items: center; background: color-mix(in srgb, var(--ds-ink) 38%, transparent); border-radius: inherit; }
.sv-close-guard-card { display: grid; gap: 8px; max-width: 430px; margin: 0 16px; padding: 20px 22px; background: var(--ds-surface); border-radius: 12px; box-shadow: 0 24px 60px -24px color-mix(in srgb, var(--ds-ink) 55%, transparent); }
.sv-close-guard-card strong { font-size: 16px; color: var(--ds-ink); }
.sv-close-guard-card span { font-size: 13px; color: var(--ds-text-muted); }
.sv-close-guard-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.sv-close-guard-actions .sv-guard-danger { color: var(--ds-danger); }
.sv-close-guard-actions .sv-guard-danger:hover { border-color: var(--ds-danger); }

/* Cash variance tone (owner, 5 Aug 2026): red when the drawer is short,
   green when it tallies or is over. !important because the table and the
   record popup both carry colour rules of their own that would otherwise
   win on specificity. */
.cash-var-short { color: #b3261e !important; font-weight: 800; }
.cash-var-ok { color: #0a5c27 !important; font-weight: 800; }


/* ── Edit Mode price list ────────────────────────────────────────────────────
   The popup behind the Product Master "Edit Mode" button. Scrolls rather than
   growing, because the list is whatever the filters left showing - which can
   be all 523 products. */
#productBulkPriceDialog .product-bulk-list {
  max-height: 60vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px 0;
}
#productBulkPriceDialog .product-bulk-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 6px 8px;
  border-radius: 8px;
}
#productBulkPriceDialog .product-bulk-row:nth-child(odd) { background: rgba(15, 23, 42, 0.03); }
#productBulkPriceDialog .product-bulk-name {
  display: flex;
  flex-direction: column;
  font-size: 13px;
  min-width: 0;
}
#productBulkPriceDialog .product-bulk-code {
  font-size: 11px;
  opacity: 0.65;
}
#productBulkPriceDialog .product-bulk-row input { width: 120px; flex: 0 0 auto; text-align: right; }

/* Spacing goes on the HEAD, not on the input. The global search enhancer wraps
   this input in a grid alongside its Clear button, and a margin on the input
   alone pushed it 4px below the button - same height, visibly misaligned. A
   gap on the container spaces the row without offsetting anything inside it. */
#productBulkPriceDialog .dialog-head { display: flex; flex-direction: column; gap: 8px; }
#productBulkPriceDialog .product-bulk-search { width: 100%; margin-top: 0; }
#productBulkPriceDialog .product-bulk-empty { padding: 18px 8px; text-align: center; opacity: 0.7; font-size: 13px; }

/* The Clear button the global search enhancer adds beside this search box came
   out 38px against the input's 42px. The input is not 42 by choice - a
   blanket min-height on form controls (styles.css) sets it - so BOTH height
   and min-height are given here: min-height wins over height, and setting only
   height would look like it did nothing. Scoped to this dialog rather than
   fixed globally, because other screens size that button against their own
   toolbars. */
#productBulkPriceDialog .search-clear-field .search-clear-button {
  height: 42px;
  min-height: 42px;
}

/* Landing cost block in the Product Master dialog.
   It is a bordered section rather than loose fields because it is OPTIONAL and
   sits below the required ones: a bare checkbox floating between the price row
   and the Save button read as something left behind rather than a feature.
   Boxing it also makes the fields it reveals obviously belong to the toggle. */
#addProductForm .product-landing-card {
  border: 1px solid var(--border-soft, #e2e8f0);
  border-radius: 10px;
  background: var(--surface-muted, #f8fafc);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The label would otherwise stack its control ABOVE its text like every other
   field in this form, so the row direction is set explicitly. */
#addProductForm .product-landing-toggle {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  margin: 0;
  cursor: pointer;
}

/* The checkbox needs BOTH height and min-height: the blanket min-height on form
   controls in styles.css has no checkbox exclusion, and min-height beats
   height, so setting height alone looks like it did nothing. */
#addProductForm .product-landing-toggle input[type="checkbox"] {
  width: 18px;
  height: 18px;
  min-height: 18px;
  flex: 0 0 auto;
  margin: 2px 0 0;
  cursor: pointer;
}

#addProductForm .product-landing-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

#addProductForm .product-landing-toggle-text strong {
  font-weight: 600;
}

#addProductForm .product-landing-toggle-text small,
#addProductForm .product-landing-note {
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-muted, #64748b);
  margin: 0;
}

/* Its own grid, not .dialog-grid: that one is display:contents, which would
   drop these fields out of the card and back into the dialog's own columns. */
#addProductForm .product-landing-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

@media (max-width: 640px) {
  #addProductForm .product-landing-grid { grid-template-columns: 1fr; }
}

/* A recipe-sourced cost is filled by the server; readOnly (not disabled) so it
   still submits and still reads at normal contrast, just visibly not yours. */
#addProductForm .input-readonly {
  background: var(--surface-muted, #f1f5f9);
  cursor: default;
}

#productLandingCostNote:empty {
  display: none;
}

/* Record view footer: POS Print sits on the LEFT, away from PDF Print and
   Close on the right (owner, 6 Aug 2026). The two send the same record to
   different machines - a receipt printer and a document printer - so putting a
   gap between them is what stops the wrong one being hit in a hurry.
   margin-right:auto rather than a wrapper, so the existing right-hand group is
   untouched. */
#recordViewDialog .record-view-pos-print {
  margin-right: auto;
}

/* [hidden] alone is not enough on a flex row: an explicit display would beat
   the attribute, and this button is hidden for every record view except a
   stock transfer. */
#recordViewDialog .record-view-pos-print[hidden] {
  display: none;
}

/* The shared "Rows" control (listPageSizeControl) carries Product Master's
   .inventory-page-size-field styling. Dropped into the Stock Verification
   filter bar it stood taller than the Item / Date / Outlet fields beside it,
   because those are .inline-select.compact-inline-select. Matched to its
   neighbours HERE rather than forking the control, so Product Master keeps
   the look it already had and both screens still share one component. */
.sv-history-toolbar .inventory-page-size-field {
  display: grid;
  gap: 5px;
  min-width: 0;
  color: var(--muted);
  font-size: 12px;
  /* No font-weight here on purpose: the control keeps the 800 it already has.
     Checked on Product Master itself rather than guessed - its "Rows" label
     renders at 800 against 600 for Search product / Product Type / Category,
     so "Rows" being the bolder one IS the look being copied. Forcing it to
     match its neighbours here would have made this screen differ from the one
     it was asked to resemble. */
}

.sv-history-toolbar .inventory-page-size-field select {
  min-height: 34px;
  padding-block: 6px;
}


/* CRM contacts: keep the one control row ON one row (owner, 7 Aug 2026 -
   "not required two panels").

   This bar carries seven things where most screens carry three: search, three
   filters, Rows, the count and Add Contact. At the shared field widths it
   needs ~1300px of content column, so it broke onto a second line on anything
   under a 1600px screen.

   THE SELECTOR IS LONG ON PURPOSE. The widths come from
   `body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls
   .inventory-search-field` in styles.css, which scores (0,4,1) - the :not()
   parts each count their inner class. A sane `.crm-shell .inventory-table-
   controls > .inventory-search-field` is (0,3,0) and silently loses, even
   though design-system.css loads last. Measured, not assumed: the rule was in
   the CSSOM and the computed flex-basis was still the 300px from styles.css.
   Same body prefix here, so it wins on specificity rather than by luck.

   The bases are floors, not fixed widths - every field still grows to fill a
   wide screen; only the wrap point moves. Scoped to .crm-shell so no other
   list screen's controls change. */
body:not(.logged-out):not(.pos-fullscreen-mode) .crm-shell .inventory-table-controls .inventory-search-field {
  flex: 1 1 190px;
  min-width: 150px;
}

/* 150/148, not 142: "Walking customer" measures 100px in this control's own
   font, and a 142px button leaves 102px of room once padding and the caret are
   taken out. Two pixels is not a margin - one font-rendering difference and the
   longest option in the list is clipped. Measured on the themed dropdown
   button, which is what is actually visible; the native <select> is 1x1px. */
body:not(.logged-out):not(.pos-fullscreen-mode) .crm-shell .inventory-table-controls .inventory-sort-field {
  flex: 0 1 150px;
  min-width: 148px;
  max-width: 200px;
}

/* ---------------------------------------------------------------------------
   Primary actions inside the kitchen toolbar (owner, 8 Aug 2026)

   Recipe master used to carry two strips: a compactPanelHead holding
   Consumption and Add Recipe, and the search toolbar under it. The buttons now
   sit in the toolbar's own tail, which is why this exists at all.

   align-self: end matters. The toolbar is `align-items: end` over fields that
   are label-above-control, so a button with no label would otherwise be
   dragged up to the label's line and float above the search box. The 40px
   floor is the same one .kitchen-search-controls .mini-action already sets, so
   Clear, the funnel and these buttons share one control height.
--------------------------------------------------------------------------- */
.kitchen-search-controls .kitchen-control-actions {
  display: flex;
  align-items: center;
  align-self: end;
  gap: 8px;
}

/* The search box must stop growing once the toolbar also carries buttons.

   styles.css gives it `flex: 1 1 300px` under
   `body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls
   .inventory-search-field` - specificity (0,4,1), because each :not() counts
   its inner class. That grow:1 is why the box measured 460px, not 300: it
   claims every spare pixel on the row, so the buttons had none and wrapped to
   a line of their own at 1366, a very ordinary laptop width.

   Killing the grow alone was NOT enough - measured, it still wrapped at 1366,
   by seven pixels. Flexbox decides to wrap from each item's HYPOTHETICAL main
   size (basis/min-width), before grow or shrink or auto margins are resolved,
   so the 300px basis is what has to come down. It drops to 200 and KEEPS
   grow:1, which means the box is small enough that the line always fits and
   then immediately grows back into whatever slack is left - roughly 290px at
   1366 and far wider at 1920. Shrink cannot help here: with flex-wrap:wrap a
   line that does not fit wraps rather than shrinking.

   The same body prefix is repeated deliberately -
   a sane `.kitchen-search-controls > .inventory-search-field` is (0,3,0) and
   loses, even though design-system.css loads last. Scoped to
   .has-control-actions so the six other screens using this toolbar are
   untouched. */
body:not(.logged-out):not(.pos-fullscreen-mode) .kitchen-search-controls.has-control-actions > .inventory-search-field {
  flex: 1 1 200px;
  min-width: 200px;
}

/* Something has to claim the slack, or the buttons sit against the last
   filter instead of hard right where they have always been.

   Normally that job belongs to .kitchen-filter-options, which styles.css gives
   `margin-left: auto` - and while the funnel is present the actions simply
   ride along at the end of the group it pushes right, measured good at 1366
   and 1920. Recipe master has no funnel any more, so there was nothing to
   claim it and Add Recipe ended up 10px after the Category filter.

   :not(.has-filter-options) is the whole point: exactly one auto margin on the
   row, whichever element is there to carry it. Give BOTH of them one and
   flexbox splits the free space between them - at 1920 that stranded the
   record count 185px from its buttons, which is how this was measured wrong
   the first time. */
.kitchen-search-controls:not(.has-filter-options) .kitchen-control-actions {
  margin-left: auto;
}

/* Below the width where the row can no longer hold everything, the buttons
   wrap - and without this they land at the LEFT of the new line, directly
   under the search box, which reads as a mistake because that is not where
   they have ever been.

   Why a measured breakpoint and not simply `margin-left: auto` always: tried
   that first and measured it. On a wide screen the funnel ALREADY carries
   margin-left:auto, so two auto margins split the free space between them and
   at 1920 the record count ended up stranded 185px from the buttons it belongs
   beside. The bar's own comment in styles.css warns about exactly that.

   1289px is where this toolbar actually breaks, measured, not guessed: one row
   at 1290, 1300, 1320, 1366 and 1920; wraps at 1280, because by then the
   search box and the category filter have both been squeezed to their floors
   and there is nothing left to give. Should the content ever change that
   threshold, the worst case is buttons at the left of a wrapped line - what
   happens today - not the 185px split. */
@media (max-width: 1289px) {
  .kitchen-search-controls .kitchen-control-actions { margin-left: auto; }
}

.kitchen-search-controls .kitchen-control-actions > .ghost-button,
.kitchen-search-controls .kitchen-control-actions > .primary-button {
  min-height: 40px;
  /* Never let the pair be the thing that wraps to its own line: the count and
     Clear sit to their left and read as one group with them. */
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Per-user Menu access panel (owner, 8 Aug 2026: "its very big box now")

   The two radios were rendering as 291x42px circles stacked above their own
   labels, which is CLAUDE.md section 7's touch-target trap almost word for
   word. Two rules in styles.css hit them:

     .settings-form input, .settings-form select, ...   width: 100%
     .settings-form input, .settings-form select, ...   min-height: 42px

   Neither excludes radios or checkboxes - they were written for text fields -
   and `.settings-form label { display: grid }` then put the ballooned control
   on its own row above the words. Measured, not guessed: the radio came back
   291px wide and 42px tall inside a 213px-tall fieldset holding two options.

   Both overrides are scoped through .settings-form so they score (0,2,1)
   against those rules' (0,1,1) and win on specificity rather than on load
   order, which is the failure mode design-system.css keeps hitting.

   The options are cards rather than bare radios so the whole row is a click
   target - they are a either/or choice, and a 16px circle is a mean thing to
   aim at. :has() does the selected state; where it is unsupported the cards
   simply stay unhighlighted and the radio still shows which is picked.
--------------------------------------------------------------------------- */
.settings-form .user-access-mode {
  /* The staff form is a two-column grid of 316px fields. Without this the
     panel takes ONE of them, which is what made it a tall narrow box: the two
     options had 283px to share and could only stack. Spanning both columns
     gives them 644px and they sit side by side, halving the height. The two
     editors below already span - this brings the panel into line with them. */
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
  margin: 6px 0 2px;
  padding: 14px 16px 16px;
  border: 1px solid var(--ui-border, #d5dae0);
  border-radius: 10px;
  background: var(--ui-surface, #fff);
}

.settings-form .user-access-mode > legend {
  padding: 0 6px;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.01em;
}

/* The note explains the whole panel, so it spans both cards rather than
   sitting inside one of them. */
.settings-form .user-access-mode > .settings-note {
  grid-column: 1 / -1;
  margin: 0;
}

.settings-form .user-access-mode > label {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 10px;
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--ui-border, #d5dae0);
  border-radius: 8px;
  cursor: pointer;
}

.settings-form .user-access-mode > label:has(input:checked) {
  border-color: var(--ui-accent, #1f6feb);
  background: var(--ui-surface-muted, #f6f8fa);
}

/* Beats `.settings-form input { width: 100%; min-height: 42px }` - see above. */
.settings-form .user-access-mode input[type="radio"] {
  width: 16px;
  height: 16px;
  min-height: 0;
  margin: 3px 0 0;
  flex: none;
  accent-color: var(--ui-accent, #1f6feb);
}

.settings-form .user-access-mode > label > span {
  display: grid;
  gap: 3px;
}

.settings-form .user-access-mode strong { font-size: 13.5px; }

.settings-form .user-access-mode small {
  color: var(--ui-text-muted, #5d6672);
  line-height: 1.45;
}

/* ---------------------------------------------------------------------------
   Record view: single-column statement (owner, 8 Aug 2026)

   For a record that is an arithmetic chain rather than a set of fields. The
   close register's nine money lines were being laid out three per row by
   .record-view-grid, which put Total sales top-right, scattered the five
   deductions across two rows and landed Cash amount on the right again - every
   figure correct, the sum impossible to follow.

   One column, label left and figure right, so it subtracts straight down the
   page like the 80mm slip it mirrors. Capped in width because a money column
   stretched across 1900px is no easier to read than a scattered one.
--------------------------------------------------------------------------- */
.record-view-statement {
  max-width: 460px;
  margin: 4px 0 2px;
  display: grid;
  gap: 2px;
}

.record-view-statement-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  padding: 7px 2px;
}

/* Tabular figures so the rupee amounts line up digit under digit - the one
   thing a reader checking a subtraction actually needs. */
.record-view-statement-line > span:last-child {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.record-view-statement-line.is-strong { font-weight: 700; }

/* The rule goes ABOVE the emphasised lines, closing the group of figures that
   feeds them, exactly as the slip prints it. */
.record-view-statement-line.has-rule {
  border-top: 1px solid var(--ui-border, #d5dae0);
  margin-top: 4px;
  padding-top: 10px;
}

.record-view-statement-line:first-child.has-rule {
  border-top: 0;
  margin-top: 0;
  padding-top: 7px;
}

/* ---------- Date / Time column pair ---------- */
/* Emitted by displayDateTimeCells() in app-core.js, on the lists that give
   Time its own column: POS stock request, stock transfer and close register.
   Neither wraps - a wrapped date reads as two numbers and a wrapped time is
   unreadable - and the widths are just enough for the fixed-length strings.

   tabular-nums on the time so the colons line up down the column, which is
   the whole reason for splitting it out. */
.ui-table-panel .col-date,
.table-wrap .col-date {
  white-space: nowrap;
  width: 105px;
  text-align: left;
}
.ui-table-panel .col-time,
.table-wrap .col-time {
  white-space: nowrap;
  width: 75px;
  text-align: left;
  font-variant-numeric: tabular-nums;
}

/* ── Supplier bills register: stop the long columns wrapping to nothing ─────
   Reported 12 Aug 2026: a near-zero-width unlabelled column between Date and
   Total, its text stacking one character per line, rows ~300px tall and the
   Action column pushed off screen.

   It is not a header/body mismatch. Measured on the rendered table: 12 <th>,
   12 <td>, 12 totals cells, every one stamped with the same original index
   (data-table-column-index on the header, data-orig-col on the body), and each
   value sitting under its own heading. Nothing is rendered into an unintended
   cell.

   The cause is that this table has no saved column widths, so it lays out with
   table-layout:auto and the browser is free to give a column almost nothing.
   Columns 4 "Added" and 5 "Taxable / Tax" were the ONLY content columns
   without nowrap - Date and Due carry .col-compact, the money columns carry
   .col-numeric, and both of those already set it - and they hold the longest
   strings on the row ("12 Aug 2026, 13:45" and "Rs.11,350 / Rs.0"). They sit
   exactly where the reported blank column appears.

   Keyed on the ORIGINAL column index, not nth-child, so this survives a column
   reorder. Scoped to this one table id - the same header words appear on other
   screens and must not be dragged along. */
   INDEX SHIFT, 12 Aug 2026: an "Item" column was inserted at position 2, so
   every column from Date onwards moved one to the right. These selectors key
   on the original index, which is exactly the number that changed - Added went
   4 -> 5, Taxable / Tax 5 -> 6, Date 2 -> 3, Due 3 -> 4, Total 6 -> 7 and
   Balance 8 -> 9. Left alone they would have quietly styled the wrong columns
   rather than failing visibly. */
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="5"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="5"],
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="6"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="6"] {
  white-space: nowrap;
  min-width: 130px;
}

/* The new Item column. It WRAPS - an item name is prose and a long one should
   run to two lines rather than being clipped - but the minimum stops the
   browser squeezing it to the character-per-line width that started all of
   this. */
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="2"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="2"] {
  min-width: 150px;
}

/* Date, Total and Balance already say nowrap; the minimum is what stops the
   browser squeezing them under pressure from the wider columns. Deliberately
   NOT applied to Supplier or Action: the supplier name is the one thing that
   should be free to take the slack, and the action column has its own
   minimum (tableActionColumnMinWidthPx). */
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="3"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="3"],
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="4"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="4"] {
  white-space: nowrap;
  min-width: 96px;
}
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="7"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="7"],
[data-column-table="list-purchase-supplier-invoices"] th[data-table-column-index="9"],
[data-column-table="list-purchase-supplier-invoices"] td[data-orig-col="9"] {
  white-space: nowrap;
  min-width: 92px;
}

/* Advance / paid on the sales order summary card: the inline refusal when the
   amount is negative or above the order total. Sits directly under the input,
   so the message and the field it refuses are read together. */
.so2-adverror {
  margin: 2px 0 6px;
  color: #b42318;
  font-size: 11.5px;
  line-height: 1.35;
  text-align: right;
}
.so2-sumrow-adv input.is-invalid {
  border-color: #b42318;
  background: #fff5f4;
}

/* Supplier payment: the bill picker is multi-select, so it needs a header row
   for Select all / Clear and a line saying how many are ticked.

   The box is sized here because styles.css:18330 only ever sized the radio
   this list used to use, and the app-wide touch-target rule (styles.css:32110)
   sets min-height:42px on form inputs with no checkbox exclusion - height
   alone loses to it, so min-height has to be set too. */
.supplier-payment-bill input[type="checkbox"] {
  margin: 0;
  width: 16px;
  height: 16px;
  min-height: 16px;
  flex: 0 0 auto;
  accent-color: var(--brand);
}
.supplier-payment-bills-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
/* Deliberately NOT resized. A shrunk ghost button here would be one more
   one-off control height, and it loses anyway: .ghost-button carries
   min-height:40px, which beats a plain height, and the app-wide
   body:not(.logged-out) :is(... .ghost-button ...) font-size rule outranks a
   two-class selector. These stay the standard button. */
.supplier-payment-bill-actions { display: flex; gap: 6px; }
.supplier-payment-bill-count {
  margin: 6px 0 0;
  font-size: 11.5px;
  color: #475569;
  font-variant-numeric: tabular-nums;
}

/* Category ▸ In POS billing. The box is sized here because the app-wide
   touch-target rule (styles.css:32110) sets min-height:42px on form inputs
   with no checkbox exclusion - height alone loses to it. */
.category-pos-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink);
}
.category-pos-toggle input[type="checkbox"] {
  margin: 0;
  width: 16px;
  height: 16px;
  min-height: 16px;
  flex: 0 0 auto;
  accent-color: var(--brand);
}
.category-pos-flag {
  font-size: 12.5px;
  color: var(--muted);
}
.category-pos-flag.is-off { color: #b42318; }

/* Raise B2B invoice: a wide working dialog (owner, 16 Aug 2026, 1250x750).

   The WIDTH belongs on the <dialog>, not on .dialog-panel inside it. The base
   rule is `dialog { width: min(520px, calc(100vw - 28px)) }` (styles.css:23807),
   so sizing only the panel made a 1250px panel inside a 520px box: the content
   overflowed to the left, the labels were clipped and the whole thing grew a
   horizontal scrollbar. Same shape as .wide-dialog, which is how every other
   wide dialog here does it.

   calc(100vw - 28px), matching the base rule, so it still fits a small laptop -
   a modal wider than the viewport cannot be scrolled to its own buttons. */
#b2bInvoiceRaiseDialog {
  width: min(1250px, calc(100vw - 28px));
}
/* Height only. display stays GRID - .dialog-panel is a grid and the dialog-grid
   inside it depends on that; forcing flex here collapsed the field layout. */
#b2bInvoiceRaiseDialog .dialog-panel {
  min-height: min(750px, calc(100dvh - 40px));
  max-height: calc(100dvh - 28px);
  overflow-y: auto;
  align-content: start;
}

.b2b-invoice-orders-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.b2b-invoice-orders-actions { display: flex; gap: 6px; }
/* The list scrolls, the dialog does not: the Raise button must stay put. */
.b2b-invoice-order-list {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--table-border-color, #e2e8f0);
  border-radius: 10px;
  padding: 6px;
  display: grid;
  gap: 4px;
  background: #fff;
}
.b2b-invoice-order {
  display: grid;
  grid-template-columns: 18px minmax(0, 1.4fr) minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
}
.b2b-invoice-order:hover { border-color: #b9c6d8; }
.b2b-invoice-order.is-selected { background: #eef6ff; border-color: var(--brand); }
/* Sized here because the app-wide touch-target rule sets min-height:42px on
   form inputs with no checkbox exclusion - height alone loses to it. */
.b2b-invoice-order input[type="checkbox"] {
  margin: 0; width: 16px; height: 16px; min-height: 16px;
  flex: 0 0 auto; accent-color: var(--brand);
}
.b2b-invoice-order-main { display: grid; gap: 2px; min-width: 0; }
.b2b-invoice-order-main strong { font-size: 13px; }
.b2b-invoice-order-main small { color: var(--muted); font-size: 11.5px; }
.b2b-invoice-order-amt { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.b2b-invoice-order-count { margin: 6px 0 0; font-size: 11.5px; color: #475569; font-variant-numeric: tabular-nums; }
.b2b-invoice-order-empty { padding: 10px; color: var(--muted); font-size: 12.5px; }

/* Supplier bill items: a heading over the figure it names (owner, 16 Aug 2026,
   "columns are zig zag").

   ROOT CAUSE, an off-by-one in styles.css:19308:

     th:nth-child(2), th:nth-child(3), th.right { text-align: right }

   The columns are 1 Code, 2 NAME, 3 Qty, 4 Rate, 5 Tax, 6 Total. That rule
   meant "the numeric ones" but indexed one column to the left: it right-aligned
   the NAME heading - which sits over a left-reading text box - and missed Rate
   entirely. So Name's label ran away from its field while Rate's label sat
   left above a right-aligned number, and the eye zig-zagged down the row.

   Name back to the left, Qty and Rate to the right over their figures. Tax
   stays left: its cell holds a dropdown, which reads from the left. */
.pbill-main .purchase-product-table th:nth-child(2) { text-align: left; }
.pbill-main .purchase-product-table th:nth-child(3),
.pbill-main .purchase-product-table th:nth-child(4) { text-align: right; }
/* The Total CELL is right-aligned but an <input> does not inherit that, so the
   figure sat left under a right-aligned heading. */
.pbill-main .purchase-product-table .purchase-product-line-amount input { text-align: right; }

/* ---------------------------------------------------------------------------
   ONE toolbar shape for the list screens (user, 18 Aug 2026: "each page is
   in different size and different shapes - can we improve it?").

   Product Master, Stock, Stock Transfer and Stock Requests are rendered by
   THREE different builders (hand-rolled markup, kitchenSearchControl,
   renderListControls) that emit the same field classes but wear different
   container modifiers - and styles.css sizes each modifier differently,
   which is exactly the inconsistency the client sees. This block normalises
   the four list-screen variants to one geometry. Deliberately NOT touching
   .sv-history-toolbar or .crm-shell - both carry measured, more specific
   fits of their own above.

   The body:not() prefix is SPECIFICITY, not decoration: the styles.css rules
   being overridden score (0,4,1) the same way - see the CRM note above. */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 12px;
  align-items: end;
}
/* The search: ONE target width (400px) on every screen, shrinking only
   when its row is genuinely crowded - a growing basis re-created the very
   drift being removed (measured: 314 / 371 / 399px across four screens
   from identical rules, because each row's leftover space differed). */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .inventory-search-field {
  flex: 0 1 400px;
  width: 400px;
  min-width: 220px;
}
/* Dropdown filters: one width band, so Status on Transfers, Category on
   Stock and Product Type on Product Master all read as the same control. */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .inventory-sort-field {
  flex: 0 1 176px;
  min-width: 150px;
  max-width: 220px;
}
/* Every visible control on one 40px line: the input, the themed dropdown
   BUTTON (the native select is the 1x1px hidden node - styles land on the
   wrapper button, see the dropdown-theme re-parenting trap), and the
   toolbar's own buttons. */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .inventory-search-input-wrap input,
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .global-dropdown-button,
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) select,
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .ghost-button,
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .primary-button {
  min-height: 40px;
}
/* One label voice: Product Master's "Rows" rendered at weight 800 against
   600 for every neighbour - the exact kind of drift being retired here. */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) > label,
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .inventory-page-size-field {
  font-size: 12px;
  font-weight: 600;
}
/* The tail (count, Clear, screen actions) always sits at the right edge. */
body:not(.logged-out):not(.pos-fullscreen-mode) .inventory-table-controls:is(.smart-table-controls, .stock-ledger-controls, .product-filter-controls, .kitchen-search-controls) .list-control-tail {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Hint under a Product master field (Sold in multiples of, 22 Aug 2026). */
.dialog-panel label .field-hint {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  line-height: 1.35;
  font-weight: 400;
  color: #6b737d;
}

/* Sale multiples, live (22 Aug 2026): a quantity off the item's multiple (or a
   fractional piece) in a transfer / stock-request row. Same doubled-class
   specificity trick as .is-incomplete above; unlike the warning outline this
   one stays through focus, so the person typing sees it. The multiple itself is
   NOT written on the row - the owner found it noise beside the item; the note
   under the list names it when a quantity is wrong. */
.item-picker-rows > .is-offstep.is-offstep input.transfer2-qty-input.transfer2-qty-input,
.item-picker-rows > .is-offstep.is-offstep input.transfer2-qty-input.transfer2-qty-input:focus-visible {
  border-color: #c02626;
  box-shadow: 0 0 0 2px rgba(192, 38, 38, 0.22);
}
