/* ===========================================================================
   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;
}

/* A tick column: a checkbox and nothing else, so it shrinks to the box.
   Without this it fell through to the text floor below and took 150px -
   the widest column on the screen, blank on every row that cannot be
   ticked (owner, 7 Sep 2026). tableColumnClassName() stamps col-tick. */
.table-wrap thead th.col-tick,
.table-wrap tbody td.col-tick {
  white-space: nowrap;
  width: 1%;
  min-width: 44px;
  text-align: center;
  padding-left: 8px;
  padding-right: 8px;
}

/* 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(.col-tick):not(.action-column),
.table-wrap tbody td:not(.col-numeric):not(.col-center):not(.col-compact):not(.col-tick):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; }
/* The single-class .qa-item loses font-size to a styles.css button rule, so
   the 19 button items rendered 12px while the one anchor item (POS Billing)
   kept the authored 14.5 - one menu, two type sizes. Doubled specificity so
   every item reads the same. */
.qa-panel .qa-item { font-size: 14.5px; }
.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; border: 0; background: none; box-shadow: none; padding: 0; margin: 0 0 12px; }
.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;
  /* Four tidy columns: checkbox, order + customer, date, amount. The date and
     amount are max-content so they pack to the right edge and line up down the
     list, instead of the date floating mid-row on a stretchy track. */
  grid-template-columns: 18px minmax(0, 1fr) max-content max-content;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  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-date { color: var(--muted); font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.b2b-invoice-order-amt { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; min-width: 84px; white-space: nowrap; }
.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;
}

/* Sales Order dialog (so2): one 36px control height, the same number its
   sibling two-pane redesign (transfer2) settled on. The form asked for 32px
   fields but the themed datepicker and dropdown cannot follow it down, so a
   customer order was typed across 30/32/39/41/42px controls in one pane.
   The note textarea keeps its 32px single-line grow-on-focus design, and the
   items-bar + Add Item stays compact; the Cancel / Save Order pair joins the
   fields at 36. */
#salesOrderDialog .so2-form input:not([type="checkbox"]):not([type="radio"]),
#salesOrderDialog .so2-form select,
#salesOrderDialog .so2-form .global-dropdown-button,
#salesOrderDialog .so2-form .global-datepicker-field {
  height: 36px;
  min-height: 36px;
}
#salesOrderDialog .so2-actions .primary-button,
#salesOrderDialog .so2-actions .ghost-button {
  min-height: 36px;
}

/* One Clear button. renderListControls screens show .search-clear-button -
   white, bordered, 84px minimum - while kitchenSearchControl screens rendered
   theirs as a bare ghost mini-action, so the same control in the same spot
   changed clothes between Recipe and, say, Transfers. Dress the kitchen one
   the same. */
body:not(.logged-out):not(.pos-fullscreen-mode) .kitchen-search-controls [data-clear-kitchen-search],
body:not(.logged-out):not(.pos-fullscreen-mode) .kitchen-search-controls [data-clear-product-search] {
  min-width: 84px;
  border: 1px solid #cbd8ea;
  border-radius: 9px;
  background: #ffffff;
  color: #1f3f68;
  font-size: 13px;
  font-weight: 800;
  box-shadow: 0 6px 14px rgba(15, 43, 75, 0.07);
}

/* Screen-header actions (Add Supplier, New Transfer, Add Expense...) sit in
   .module-toolbar / .compact-panel-head directly above a control row whose
   every control is 42px (--control-h in styles.css). The buttons' base is
   min-height 40, so each list screen opened with a 40px button over a 42px
   row - the same two-heights drift the toolbar block above retires. */
body:not(.logged-out):not(.pos-fullscreen-mode) .module-toolbar .primary-button,
body:not(.logged-out):not(.pos-fullscreen-mode) .module-toolbar .ghost-button,
body:not(.logged-out):not(.pos-fullscreen-mode) .compact-panel-head .primary-button,
body:not(.logged-out):not(.pos-fullscreen-mode) .compact-panel-head .ghost-button {
  min-height: 42px;
}

/* 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);
}

/* Transfer drafts (22 Aug 2026): Save draft row, 'Draft saved' note and the
   offered-back banner at the top of the items pane. Same amber as the
   supplier-bill draft banner in styles.css, scoped to the transfer dialog. */
/* Save draft sits in the items header, with Clear all - the bottom row is for
   submitting the transfer (owner, 22 Aug 2026). Styled like Clear all beside
   it rather than as a form button. */
.transfer2-form .transfer2-mainhead-right .transfer2-draftbtn {
  border: 1px solid var(--ds-line, #d7dbe3);
  background: var(--ds-surface, #fff);
  color: var(--ds-ink, #1c2033);
  font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 5px 12px; border-radius: 8px; cursor: pointer; white-space: nowrap;
}
.transfer2-form .transfer2-mainhead-right .transfer2-draftbtn:hover { background: var(--ds-canvas, #eef0f3); }
.transfer2-form .transfer2-draft-note { font-size: 12px; color: var(--ds-text-muted); line-height: 1.25; text-align: right; max-width: 190px; }
.transfer2-form .transfer2-draft-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 10px; padding: 10px 14px;
  border: 1px solid #f59e0b; border-radius: 10px;
  background: #fffbeb; color: #92400e; font-size: 13px; font-weight: 600;
}
.transfer2-form .transfer2-draft-banner-actions { display: inline-flex; gap: 8px; flex: none; }

/* == Bakery label sticker (Central Kitchen > Labels preview) ================
   Mirrors what the Citizen CL-E321 prints via labeltool: shop name, product,
   Rs. price, QR + code number, MFD, EXP, FSSAI. Print CSS for the browser
   popup lives in printProductLabel(); these rules are the on-screen preview. */
.bakery-label {
  display: flex; flex-direction: column; gap: 2px;
  padding: 5px 7px;
  background: #fff; color: #111;
  font-family: Arial, "Segoe UI", sans-serif;
}
.bakery-label-head { text-align: center; font-size: 10px; font-weight: 700; line-height: 1.15; white-space: nowrap; overflow: hidden; }
.bakery-label-product { text-align: center; font-size: 13px; font-weight: 800; line-height: 1.1; overflow-wrap: anywhere; }
.bakery-label-body { display: flex; justify-content: space-between; align-items: flex-start; gap: 6px; }
/* min-width:0 is load-bearing. A flex item defaults to min-width:auto, so this
   column refused to shrink below its longest line ("Best Before - N Days from
   the MFD") and shoved the QR 12px past the sticker edge - the preview showed
   an overflowing QR on a label that prints perfectly (owner, 4 Sep 2026).
   Print was never affected: the ZPL places the QR at a fixed ^FO inside the
   400-dot label. This only ever mismatched the on-screen mock. */
.bakery-label-left { display: grid; gap: 2px; min-width: 0; }
.bakery-label-price { font-size: 22px; font-weight: 800; line-height: 1.1; }
.bakery-label-date { font-size: 10px; font-weight: 700; white-space: nowrap; }
/* "Best Before - N Days from the MFD" - a note under the MFD date, smaller and
   lighter than it, like the printed sticker. */
.bakery-label-bestbefore { font-size: 8px; font-weight: 600; line-height: 1.15; white-space: nowrap; }
.bakery-label-qr { flex: 0 0 auto; text-align: center; }
.bakery-label-qr .dynamic-qr-svg { display: block; width: 44px; height: 44px; margin: 0 auto; }
.bakery-label-qr span { font-size: 7px; letter-spacing: .3px; }
.bakery-label-fssai { margin-top: auto; text-align: center; font-size: 7px; font-weight: 600; white-space: nowrap; overflow: hidden; }

/* The barcode label design (default): black header, NET WT / RATE / TOTAL
   cells, USE BY + FSSAI, Code 128 of the weighted code. */
.bar-label { display: flex; flex-direction: column; gap: 2px; padding: 0 0 3px; background: #fff; color: #111; font-family: Arial, "Segoe UI", sans-serif; }
.bar-label-head { display: flex; justify-content: space-between; gap: 6px; background: #111; color: #fff; font-size: 9px; font-weight: 700; padding: 3px 6px; }
/* font-size is set per label from the name's length, mirroring the printer's
   own shrink-to-fit; the ellipsis only covers the last pixel or two, since the
   screen face is heavier than the printer's. */
.bar-label-product { font-size: 17px; font-weight: 800; line-height: 1.05; padding: 0 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar-label-cells { display: grid; grid-template-columns: 1fr 1fr 1fr; border-top: 2px solid #111; border-bottom: 2px solid #111; }
.bar-label-cell { padding: 2px 6px; border-right: 2px solid #111; }
.bar-label-cell span { display: block; font-size: 8px; font-weight: 700; }
.bar-label-cell strong { font-size: 13px; font-weight: 800; white-space: nowrap; }
.bar-label-cell.total { background: #111; color: #fff; border-right: 0; }
.bar-label-use { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; padding: 1px 6px; font-size: 9px; font-weight: 700; white-space: nowrap; overflow: hidden; }
.bar-label-use span:last-child { font-size: 7px; font-weight: 600; }
.bar-label-barcode { height: 24px; padding: 0 10px; }
.bar-label-barcode .product-label-barcode-svg { display: block; width: 100%; height: 100%; fill: #111; }
.bar-label-code { text-align: center; font-size: 8px; font-weight: 600; letter-spacing: 2px; }

/* Tick-mark picker for which design prints. */
.label-design-picker { display: flex; gap: 10px; justify-content: center; }
.label-design-option {
  border: 1px solid var(--ds-line, #d7dbe3); border-radius: 999px;
  background: var(--ds-surface, #fff); color: var(--ds-text-muted, #5a6272);
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 7px 16px; cursor: pointer;
}
.label-design-option.active { background: #111827; border-color: #111827; color: #fff; }

/* Labels page: search + filters in one row on top, the label itself below. */
.label-page { display: grid; gap: 14px; }
.label-page-controls { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.label-page-controls label {
  display: grid; gap: 4px;
  font-size: 12.5px; font-weight: 600; color: var(--ds-text-muted, #5a6272);
}
.label-page-print-controls { display: contents; }
/* The product box was spanning the whole row; it only ever holds one name. */
.label-page-search { flex: 0 1 340px; min-width: 240px; max-width: 380px; }
.label-page-controls label { position: relative; }
/* Each caption is one flex line, so the star can never wrap onto its own row
   (a bare text node and an inline element become SEPARATE grid rows inside the
   grid label - that is what scattered the stars before). */
.label-caption { display: flex; align-items: center; gap: 3px; white-space: nowrap; min-height: 16px; }
/* Required fields carry a red star, and their label turns amber while empty so
   an unfilled box is visible without reading the whole row. */
.label-req { color: #dc2626; font-weight: 700; font-style: normal; }
.label-page-controls label.is-empty .label-caption { color: #b45309; }
.label-page-controls label.is-empty input { border-color: #fbbf24; background: #fffbeb; }
/* UOM comes from Product Master and is display-only. */
.label-uom-box input {
  width: 86px; text-align: center; font-weight: 700; letter-spacing: .3px;
  background: #f3f4f6; color: #374151; cursor: default;
}
/* Product search suggestions: same look as the transfer item search, floated
   under the box. Structural class names are shared with the transfer combobox
   so the common keyboard/highlight code drives both. */
.label-search-wrap { position: relative; display: block; }
/* Compact by design: on a laptop or tablet the panel must show several results
   without covering the label preview, so rows are tight and the height is
   capped against the viewport as well as in pixels. */
.label-search-panel {
  position: absolute; top: calc(100% + 3px); left: 0; right: 0; z-index: 30;
  background: #fff; border: 1px solid var(--ds-line, #d7dbe3); border-radius: 8px;
  box-shadow: 0 8px 20px rgba(15, 23, 42, .12);
  max-height: min(210px, 40vh); overflow: auto; padding: 3px;
}
.label-search-panel[hidden] { display: none; }
.label-search-panel .transfer2-dd-empty { padding: 7px 9px; font-size: 11.5px; font-weight: 500; color: var(--ds-text-muted, #5a6272); }
.label-search-panel .transfer2-dd-option {
  display: grid; width: 100%; grid-template-columns: 18px minmax(0, 1fr) auto;
  align-items: center; border: none; background: transparent; text-align: left;
  padding: 4px 7px; border-radius: 6px; cursor: pointer; font: inherit; color: #0f172a;
}
.label-search-panel .transfer2-dd-option:hover,
.label-search-panel .transfer2-dd-option.active { background: #f5f7fb; }
.label-search-panel .transfer2-dd-plus { color: #94a3b8; font-size: 11px; }
.label-search-panel .transfer2-dd-main { min-width: 0; display: grid; }
.label-search-panel .transfer2-dd-name { font-size: 12px; font-weight: 600; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.label-search-panel .transfer2-dd-meta { font-size: 10px; line-height: 1.2; color: #8592a3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
.label-search-panel .transfer2-dd-price { text-align: right; font-size: 11px; color: #475569; font-variant-numeric: tabular-nums; white-space: nowrap; padding-left: 8px; }
.label-page-controls input, .label-page-controls select {
  height: 40px; padding: 6px 12px;
  border: 1px solid var(--ds-line, #d7dbe3); border-radius: 8px;
  background: var(--ds-surface, #fff); color: var(--ds-ink, #1c2033);
  font: inherit; font-size: 14px;
}
.label-page-search input { width: 100%; }
.label-page-controls input[name="count"] { width: 92px; }
.label-page-controls input[name="weightGrams"] { width: 118px; }
.label-page-preview { display: grid; gap: 10px; justify-items: center; padding: 4px 0; }
.label-page-preview .product-label-preview { width: 100%; padding: 28px 12px; }
.label-page-preview .product-label-sticker { zoom: 1.8; }
.label-page-expiry { font-size: 13px; font-weight: 600; color: var(--ds-text-muted, #5a6272); }
.label-page-actions { justify-content: center; }
/* "Change shelf life" sits under the expiry line; saving writes to Product Master. */
.label-shelf-life-edit-toggle { text-align: center; }
.label-shelf-life-edit-toggle > summary {
  cursor: pointer; display: inline-block;
  font-size: 12.5px; font-weight: 600; text-decoration: underline;
  color: var(--ds-text-muted, #5a6272);
}
.label-shelf-life-edit-toggle[open] > summary { margin-bottom: 6px; }
.label-shelf-life-edit-toggle .product-label-shelf-life-edit { justify-content: center; }

/* Saved label weight — reuses the shelf-life editor's layout (owner, 26 Aug 2026). */
.label-weight-edit-toggle { text-align: center; margin-top: 6px; }
.label-weight-edit-toggle > summary {
  cursor: pointer; display: inline-block;
  font-size: 12.5px; font-weight: 600; text-decoration: underline;
  color: var(--ds-text-muted, #5a6272);
}
.label-weight-edit-toggle[open] > summary { margin-bottom: 6px; }
.label-weight-edit-toggle .product-label-weight-edit { justify-content: center; }
.label-weight-edit-hint {
  display: block; margin-top: 4px;
  font-size: 11.5px; color: var(--ds-text-muted, #5a6272);
}

/* Settings > Developer: download rows (label printer toolkit, etc.). */
.developer-download-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-top: 8px;
}
.developer-download-info { display: flex; flex-direction: column; gap: 2px; min-width: 240px; flex: 1; }
.developer-download-row .primary-button { white-space: nowrap; text-decoration: none; }
.developer-download-info code {
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 0.9em;
}

:root {
  /* ---- Table Panel (.claude/rules/table-panel.md §2) -------------------
     The toolbar that sits ABOVE a table: search, filters, selection bar,
     meta row, footer. One panel for every list and report screen; these are
     the only numbers it may use. Never hard-code them in a screen.
     --------------------------------------------------------------------- */
  --panel-pad-y: 10px;
  --panel-pad-x: 12px;
  --panel-h: calc(var(--control-h) + (var(--panel-pad-y) * 2));
  --panel-gap: 8px;
  --panel-group-gap: 12px;
  --panel-bg: #ffffff;
  --panel-border: var(--table-border-color);
  --search-w: 260px;
  --filter-min-w: 132px;
  --filter-max-w: 200px;
  --size-w: 88px;
  --icon-btn-w: var(--control-h);
  --pop-w: 230px;
  --pop-shadow: 0 10px 30px rgba(15, 23, 42, 0.12);
  --meta-h: 38px;
  --meta-bg: #fcfdfe;
  --flag-bg: #fffbeb;
  --flag-border: #fde68a;
  --flag-text: #92400e;
  --selbar-bg: #eff6ff;
  --table-row-h: 40px;
  --totals-bg: #f8fafc;
  --totals-border: 2px solid #cbd5e1;
  --actions-w: 96px;
  --link: #1d4ed8;
  --link-hover: #1e40af;
  --drawer-w: 440px;
  --control-h-touch: 44px;
  --mobile-bp: 720px;
  /* Not in the §2 table but required by §7 (the scrolling body needs a cap)
     and §9 (negatives render red). Kept here so no screen invents its own. */
  --table-max-h: 56vh;
  --neg: #b91c1c;
}

/* ---------------------------------------------------------------------------
   5. Table Panel — the one toolbar above every list and report table.

   See .claude/rules/table-panel.md. The panel WRAPS the existing
   .smart-table-shell that table() already produces; it does not replace it, so
   sorting, pagination, column show/hide, column resize and the CSV/Excel/PDF
   exports all keep working exactly as before.

   Anatomy (§3), top to bottom:
     .ui-table-toolbar   search -> filters -> Clear all -> | -> Rows, columns,
                         export, print, primary action        (height --panel-h)
     .ui-print-head      print only: report name, active filters, count, date
     .ui-table-selbar    replaces the toolbar in place when rows are selected
     .ui-table-meta      "Showing 1-10 of 51", filtered flag, compact pager
     .smart-table-shell  existing helper output, untouched
     .ui-table-footer    active sort, hidden-column note, full pager

   z-index scale is fixed (§7): popovers 40, toolbar 30, meta 20, sticky header
   cells 11, thead/tfoot 10, sticky action cells 1. Nothing else may use 10-40.
   --------------------------------------------------------------------------- */
.ui-table-panel {
  background: var(--panel-bg);
  border: 1px solid var(--table-border-color);
  border-radius: var(--control-radius);
  /* Popovers must escape the panel; only the scroll area below clips. */
  overflow: visible;
  min-width: 0;
}

/* ---- toolbar ---- */
.ui-table-panel .ui-table-toolbar {
  position: relative;
  z-index: 30;
  min-height: var(--panel-h);
  padding: var(--panel-pad-y) var(--panel-pad-x);
  display: flex;
  align-items: center;
  gap: var(--panel-group-gap);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--panel-border);
}
.ui-table-panel .ui-table-toolbar[hidden] { display: none; }

.ui-table-search {
  position: relative;
  flex: 0 0 auto;
}
.ui-table-search input {
  height: var(--control-h);
  width: var(--search-w);
  max-width: 100%;
  box-sizing: border-box;
  border: var(--control-border);
  border-radius: var(--control-radius);
  background: var(--control-bg);
  color: var(--control-color);
  padding: var(--control-pad-y) 30px var(--control-pad-y) 34px;
  font: inherit;
  font-size: var(--control-font-size);
  outline: none;
}
.ui-table-search input:focus {
  border-color: var(--control-focus-border);
  box-shadow: var(--control-focus-ring);
}
.ui-table-search .ico {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  opacity: 0.45;
  pointer-events: none;
}
.ui-table-search .clr {
  display: block;
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--control-muted);
  font-size: 16px;
  line-height: 1;
  padding: 2px 4px;
}

/* Filters wrap to a second line; the toolbar never scrolls sideways (§3). */
.ui-table-filters {
  display: flex;
  align-items: center;
  gap: var(--panel-gap);
  flex-wrap: wrap;
  min-width: 0;
}
/* Plain <select> elements, so dropdown-theme.js enhances them into
   .global-dropdown like every other select in the app. These sizes therefore
   have to land on the RENDERED control as well as the native one. */
.ui-table-filters select.ui-filter,
.ui-table-actions select.ui-size {
  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: inherit;
  font-size: var(--control-font-size);
  outline: none;
  cursor: pointer;
  text-overflow: ellipsis;
}
.ui-table-filters .ui-filter-field,
.ui-table-actions .ui-size-field {
  display: inline-flex;
  align-items: center;
  min-width: 0;
}
.ui-table-filters .ui-filter-field {
  min-width: var(--filter-min-w);
  max-width: var(--filter-max-w);
  flex: 0 1 var(--filter-max-w);
}
.ui-table-actions .ui-size-field {
  width: var(--size-w);
  min-width: var(--size-w);
}
.ui-table-filters .ui-filter-field > .global-dropdown,
.ui-table-actions .ui-size-field > .global-dropdown {
  width: 100%;
  min-width: 0;
}
.ui-table-filters .ui-filter-field .global-dropdown-button,
.ui-table-actions .ui-size-field .global-dropdown-button,
.ui-table-panel .global-datepicker-field {
  min-height: var(--control-h);
  height: var(--control-h);
}
.ui-table-filters .ui-filter-field.active .global-dropdown-button,
.ui-table-filters select.ui-filter.active {
  border-color: var(--control-focus-border);
  background-color: #f5f9ff;
}
.ui-table-panel input.ui-filter[type="date"] {
  height: var(--control-h);
  min-width: 150px;
  box-sizing: border-box;
  border: var(--control-border);
  border-radius: var(--control-radius);
  padding: var(--control-pad-y) var(--control-pad-x);
  font: inherit;
  font-size: var(--control-font-size);
}

/* [hidden] must beat the display these two are given below — an attribute's
   UA display:none loses to any explicit rule. */
.ui-table-search .clr[hidden],
.ui-clear[hidden] { display: none; }

.ui-clear {
  height: var(--control-h);
  display: inline-block;
  border: 0;
  background: none;
  color: var(--control-focus-border);
  font: inherit;
  font-size: var(--control-font-size);
  cursor: pointer;
  padding: 0 6px;
}

.ui-table-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--panel-gap);
  flex-wrap: wrap;
}
.ui-table-actions .lbl {
  color: var(--control-muted);
  font-size: 13px;
}

.ui-table-panel .ui-btn {
  height: var(--control-h);
  border: var(--control-border);
  background: var(--control-bg);
  border-radius: var(--control-radius);
  padding: 0 var(--control-pad-x);
  font: inherit;
  font-size: var(--control-font-size);
  cursor: pointer;
  color: #334155;
  white-space: nowrap;
}
.ui-table-panel .ui-btn.primary {
  background: var(--brand, #aa3232);
  border-color: var(--brand, #aa3232);
  color: #ffffff;
}
.ui-table-panel .ui-btn:focus-visible,
.ui-table-panel .ui-icon-btn:focus-visible,
.ui-table-panel .ui-clear:focus-visible,
.ui-table-panel .ui-flag:focus-visible,
.ui-table-panel .ui-pager button:focus-visible,
.ui-table-panel .ui-link:focus-visible {
  outline: 2px solid var(--control-focus-border);
  outline-offset: 2px;
}

/* Columns / export / print are square icon buttons: three text labels would eat
   the width the filters need (§3). */
.ui-table-panel .ui-icon-btn {
  width: var(--icon-btn-w);
  height: var(--control-h);
  padding: 0;
  position: relative;
  border: var(--control-border);
  background: var(--control-bg);
  border-radius: var(--control-radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #475569;
}
.ui-table-panel .ui-icon-btn:hover {
  background: var(--table-header-bg);
  color: #0f172a;
}
.ui-table-panel .ui-icon-btn.on {
  border-color: var(--control-focus-border);
  color: var(--control-focus-border);
  background: #f5f9ff;
}
.ui-table-panel .ui-icon-btn svg {
  width: 18px;
  height: 18px;
}
.ui-table-panel .ui-icon-btn .dot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--control-focus-border);
}

/* ---- popovers ---- */
.ui-pop-wrap {
  position: relative;
  display: inline-flex;
}
.ui-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: var(--pop-w);
  background: #ffffff;
  border: 1px solid var(--control-border-color);
  border-radius: var(--control-radius);
  box-shadow: var(--pop-shadow);
  padding: 6px;
  display: none;
  max-height: 340px;
  overflow: auto;
  text-align: left;
}
.ui-pop.on { display: block; }
.ui-pop.ui-pop-export { min-width: 190px; }
.ui-pop .hd {
  font-size: 12px;
  color: var(--control-muted);
  padding: 6px 8px 4px;
}
.ui-pop label {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 500;
  color: #334155;
}
.ui-pop label:hover { background: var(--table-header-bg); }
.ui-pop label.locked { opacity: 0.5; cursor: not-allowed; }
.ui-pop label .fit-note {
  color: #94a3b8;
  font-style: normal;
}
.ui-pop .mi {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px;
  border: 0;
  background: none;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: #334155;
}
.ui-pop .mi:hover { background: var(--table-header-bg); }
.ui-pop .mi svg { width: 16px; height: 16px; opacity: 0.6; flex: 0 0 16px; }
.ui-pop .ft {
  border-top: 1px solid var(--table-row-border);
  margin-top: 4px;
  padding-top: 4px;
}
.ui-pop .ft .mi { color: var(--control-focus-border); }

/* ---- selection bar: replaces the toolbar at the same height (§3) ---- */
.ui-table-selbar {
  min-height: var(--panel-h);
  padding: var(--panel-pad-y) var(--panel-pad-x);
  background: var(--selbar-bg);
  border-bottom: 1px solid var(--panel-border);
  display: none;
  align-items: center;
  gap: var(--panel-gap);
  flex-wrap: wrap;
}
.ui-table-selbar.on { display: flex; }
.ui-table-selbar .n { font-weight: 600; margin-right: 4px; }
.ui-table-selbar .sp { margin-left: auto; }

/* ---- meta row ---- */
.ui-table-meta {
  position: relative;
  z-index: 20;
  min-height: var(--meta-h);
  padding: 4px var(--panel-pad-x);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: var(--meta-bg);
  border-bottom: 1px solid var(--panel-border);
  color: var(--control-muted);
  font-size: 13px;
}
.ui-table-meta b { color: #0f172a; font-weight: 600; }
.ui-table-meta .right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
}
/* Amber, not red: a filter is a normal state, not an error (§5). */
.ui-flag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--flag-bg);
  border: 1px solid var(--flag-border);
  color: var(--flag-text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.ui-flag:hover { background: #fef3c7; }
.ui-flag .x { opacity: 0.55; font-size: 13px; line-height: 1; }

/* ---- footer ---- */
.ui-table-footer {
  min-height: var(--panel-h);
  padding: var(--panel-pad-y) var(--panel-pad-x);
  display: flex;
  align-items: center;
  gap: var(--panel-group-gap);
  flex-wrap: wrap;
  border-top: 1px solid var(--panel-border);
  color: var(--control-muted);
  font-size: 13px;
}
.ui-table-footer .right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--panel-gap);
}

/* ---- pagers (meta row: compact; footer: full) ---- */
.ui-table-panel .ui-pager {
  display: flex;
  gap: 4px;
  align-items: center;
}
.ui-table-panel .ui-pager button {
  height: 34px;
  min-width: 34px;
  border: var(--control-border);
  background: var(--control-bg);
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  color: #334155;
}
.ui-table-panel .ui-pager button[disabled] { opacity: 0.4; cursor: default; }
.ui-table-panel .ui-pager.compact button {
  height: 28px;
  min-width: 28px;
  font-size: 12px;
  border-radius: 6px;
  padding: 0 7px;
}

/* ---------------------------------------------------------------------------
   The table inside the panel. The shell markup is unchanged, so these rules
   target the existing .table-wrap / table / th / td that table() already emits.
   --------------------------------------------------------------------------- */
.ui-table-panel > .smart-table-shell { min-width: 0; }
/* The shell's own control bar stays in the DOM — the existing pagination code
   finds it as a sibling of .table-wrap and keeps its range text and page-size
   select in sync — but the panel is what the user sees, so it is not shown. */
.ui-table-panel > .smart-table-shell > .table-pagination { display: none; }
/* The panel already draws the border and radius, so the wrap must not draw a
   second one. */
.ui-table-panel > .smart-table-shell > .table-wrap {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  /* §7: scroll the BODY, not the page, so the toolbar, meta row and footer
     never move and the header and totals stay on screen. */
  max-height: var(--table-max-h);
  overflow: auto;
}
/* border-collapse: collapse drops borders on sticky cells while scrolling in
   several browsers, so the panel separates them (§7). */
.ui-table-panel > .smart-table-shell > .table-wrap > table {
  border-collapse: separate;
  border-spacing: 0;
  /* The table asks for the width its content needs and only then fills the
     wrap. Without this it is pinned to width:100% and can never be "too wide",
     so the width fitter in §11 would have nothing to measure. */
  width: auto;
  min-width: 100%;
}
/* One line per row, ellipsis past --cell-max-w. A cell that wraps hides the
   fact that the table does not fit; a cell that runs on forever makes one long
   value dictate the whole layout. The full value is still in the detail
   drawer, the export and the print. */
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th,
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td,
.ui-table-panel > .smart-table-shell > .table-wrap > table > tfoot > tr > td {
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column,
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th.action-column {
  max-width: none;
  overflow: visible;
}
/* The empty state spans the table and must not be clipped to one line. */
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td[colspan] {
  white-space: normal;
  max-width: none;
  overflow: visible;
}
/* !important on the z-indexes only: styles.css already sticks every table head
   with `body:not(...):not(...):not(...):not(...) .table-wrap thead th`, which
   outranks any class-scoped selector here, and §7 fixes the scale (thead 10,
   sticky header cells 11, sticky action cells 1) rather than leaving it to
   whichever rule happens to win. */
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th {
  position: sticky;
  top: 0;
  z-index: 10 !important;
  background: var(--table-header-bg);
  border-bottom: 1px solid var(--table-border-color);
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td {
  height: var(--table-row-h);
  border-bottom: 1px solid var(--table-row-border);
  background: #ffffff;
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr:hover > td {
  background: var(--table-row-hover);
}
/* Sticky cells need their own background - a transparent one shows the rows
   sliding underneath (§7). Set it on the cell, never the row. */
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th.action-column,
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column {
  position: sticky;
  right: 0;
  width: var(--actions-w);
  box-shadow: -1px 0 0 var(--table-row-border);
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th.action-column { z-index: 11 !important; }
/* The shared table reserves 220px for up to five inline icons. The panel caps
   a row at three buttons (§8), so it only needs --actions-w. */
.ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th.action-column,
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column {
  min-width: var(--actions-w);
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column {
  z-index: 1 !important;
  background: #ffffff;
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr:hover > td.action-column {
  background: var(--table-row-hover);
}
/* Totals stick to the bottom of the scroll area (§6). */
.ui-table-panel > .smart-table-shell > .table-wrap > table > tfoot > tr > td {
  position: sticky;
  bottom: 0;
  z-index: 10 !important;
  background: var(--totals-bg);
  border-top: var(--totals-border);
  border-bottom: 0;
  font-weight: 600;
  color: #0f172a;
}
.ui-table-panel > .smart-table-shell > .table-wrap > table > tfoot > tr > td.table-totals-label {
  color: var(--control-muted);
  font-weight: 500;
}

/* Numbers line up vertically, negatives read as brackets in red (§9). */
.ui-table-panel .col-numeric { font-variant-numeric: tabular-nums; }
.ui-table-panel .neg { color: var(--neg); }

/* ---- reference links: exactly one column per table is blue (§8) ---- */
.ui-table-panel .ui-link {
  all: unset;
  color: var(--link);
  cursor: pointer;
  font: inherit;
  border-bottom: 1px solid transparent;
  display: inline;
}
.ui-table-panel .ui-link:hover {
  color: var(--link-hover);
  border-bottom-color: currentColor;
}
.ui-table-panel .ui-link:focus-visible { border-radius: 3px; }

/* ---- empty states (§5) ---- */
.ui-table-panel .ui-empty {
  min-height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--control-muted);
  text-align: center;
}
.ui-table-panel .ui-empty strong { color: #0f172a; font-weight: 600; }

/* ---- auto-hidden to fit the width (§11) ------------------------------------
   Kept in the DOM with display:none, NOT with the hidden attribute the column
   picker uses. That distinction is load-bearing: the export reads
   header.hidden / .table-column-hidden, so a fit-hidden column is still
   exported, and the print block below puts it back on paper. A narrow laptop
   must never silently produce a narrower CSV.
   --------------------------------------------------------------------------- */
.ui-table-panel .ui-fit-hidden { display: none; }

/* ---- detail drawer (§8) ---- */
.ui-drawer-scrim {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.32);
  z-index: 90;
}
.ui-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(var(--drawer-w), 100%);
  background: #ffffff;
  z-index: 91;
  box-shadow: -12px 0 40px rgba(15, 23, 42, 0.18);
  display: flex;
  flex-direction: column;
}
.ui-drawer .hdr {
  padding: 14px 16px;
  border-bottom: 1px solid var(--table-border-color);
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.ui-drawer .kind {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--control-muted);
}
.ui-drawer h3 { margin: 2px 0 0; font-size: 17px; }
.ui-drawer .close {
  margin-left: auto;
  width: 32px;
  height: 32px;
  border: 0;
  background: none;
  border-radius: 6px;
  cursor: pointer;
  color: var(--control-muted);
  font-size: 19px;
  line-height: 1;
}
.ui-drawer .close:hover { background: var(--table-header-bg); color: #0f172a; }
.ui-drawer .bd { padding: 4px 16px 16px; overflow: auto; flex: 1; }
.ui-drawer .dl { display: grid; grid-template-columns: 150px 1fr; margin: 0; }
.ui-drawer .dl dt {
  padding: 9px 0;
  color: var(--control-muted);
  font-size: 13px;
  border-bottom: 1px solid var(--table-row-border);
}
.ui-drawer .dl dd {
  margin: 0;
  padding: 9px 0;
  text-align: right;
  border-bottom: 1px solid var(--table-row-border);
  font-variant-numeric: tabular-nums;
}
.ui-drawer .ftr {
  padding: 12px 16px;
  border-top: 1px solid var(--table-border-color);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.ui-drawer .ui-btn {
  height: var(--control-h);
  border: var(--control-border);
  background: var(--control-bg);
  border-radius: var(--control-radius);
  padding: 0 var(--control-pad-x);
  font: inherit;
  font-size: var(--control-font-size);
  cursor: pointer;
  color: #334155;
}
.ui-drawer .ui-btn.primary {
  margin-left: auto;
  background: var(--brand, #aa3232);
  border-color: var(--brand, #aa3232);
  color: #ffffff;
}
.ui-drawer .ui-link {
  all: unset;
  color: var(--link);
  cursor: pointer;
  font: inherit;
  border-bottom: 1px solid transparent;
  display: inline;
}
.ui-drawer .ui-link:hover { color: var(--link-hover); border-bottom-color: currentColor; }
.ui-drawer .ui-link:focus-visible { outline: 2px solid var(--control-focus-border); outline-offset: 2px; }

/* ---------------------------------------------------------------------------
   Below --mobile-bp the table becomes cards (§11). Same component, same
   config - there is no separate mobile screen.
   --------------------------------------------------------------------------- */
@media (max-width: 720px) {
  .ui-table-panel { --control-h: var(--control-h-touch); }
  .ui-table-search { flex: 1 1 100%; }
  .ui-table-search input { width: 100%; }
  .ui-table-panel .ui-table-actions { margin-left: 0; }
  /* A nested horizontal scroll area on a phone is a trap - let the page scroll. */
  .ui-table-panel > .smart-table-shell > .table-wrap {
    max-height: none;
    overflow: visible;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table { min-width: 0; }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > thead { display: none; }
  .ui-table-panel > .smart-table-shell > .table-wrap > table,
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody,
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr,
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td {
    display: block;
    width: 100%;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr {
    border: 1px solid var(--table-border-color);
    border-radius: var(--control-radius);
    margin: 8px 0;
    padding: 4px 2px;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td {
    border: 0;
    height: auto;
    display: flex;
    justify-content: space-between;
    gap: 14px;
    padding: 6px 10px;
    white-space: normal;
    max-width: none;
    overflow: visible;
    text-align: right;
  }
  /* data-label is already stamped on every cell by responsiveTableRow(), so
     cards need no separate markup. */
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td::before {
    content: attr(data-label);
    color: var(--control-muted);
    font-size: 12px;
    flex: 0 0 auto;
    text-align: left;
  }
  /* pri 1 is the card title, pri 2 the body, pri 3 is dropped. */
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td[data-pri="3"] { display: none; }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column {
    position: static;
    box-shadow: none;
    justify-content: flex-end;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr > td.action-column::before { content: ""; }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tfoot > tr > td { position: static; }
  .ui-table-panel .ui-pager button { height: var(--control-h-touch); }
}

/* ---------------------------------------------------------------------------
   Print (§5). One table, one print stylesheet - never a separate print view.
   --------------------------------------------------------------------------- */
.ui-print-head { display: none; }
@media print {
  .ui-table-panel .ui-table-toolbar,
  .ui-table-panel .ui-table-selbar,
  .ui-table-panel .ui-table-meta,
  .ui-table-panel .ui-table-footer,
  .ui-table-panel .table-pagination,
  .ui-table-panel .action-column,
  .ui-drawer-scrim,
  .ui-drawer {
    display: none !important;
  }
  /* Blue costs ink and means nothing on paper. */
  .ui-table-panel .ui-link {
    color: #0f172a !important;
    border-bottom: 0 !important;
  }
  /* Paper is wider than a 14-inch laptop: put the fitted-out columns back. */
  .ui-table-panel .ui-fit-hidden { display: table-cell !important; }
  .ui-table-panel col.ui-fit-hidden { display: table-column !important; }
  .ui-table-panel {
    border: 0;
    border-radius: 0;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap {
    overflow: visible;
    max-height: none;
    border: 0;
    box-shadow: none;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > thead { display: table-header-group; }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > thead > tr > th,
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tfoot > tr > td {
    position: static;
    background: #f1f5f9 !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
  .ui-table-panel > .smart-table-shell > .table-wrap > table > tbody > tr { page-break-inside: avoid; }
  .ui-table-panel .table-sort-icon { display: none !important; }
  .ui-table-panel .table-column-resizer { display: none !important; }
  .ui-print-head {
    display: block;
    padding: 0 0 12px;
    border-bottom: 2px solid #0f172a;
    margin-bottom: 10px;
  }
  .ui-print-head .t { font-size: 16px; font-weight: 700; }
  .ui-print-head .m { font-size: 11px; color: #334155; margin-top: 3px; }
  @page { margin: 14mm 10mm; }
}

/* ── Owner alert recipients ───────────────────────────────────────────────
   The list under Integrations > WhatsApp > Owner alert mobile, showing every
   number an automatic owner alert will reach. It rides on .settings-note for
   its frame, so only the list and the missing-mobile warning are set here. */
.owner-alert-recipients ul {
  margin: 4px 0 6px;
  padding-left: 16px;
  display: grid;
  gap: 2px;
}

.owner-alert-recipients li {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.35;
}

/* A named colour, not the inherited muted grey: an Owner who cannot be
   alerted is a gap in the oversight, not a footnote. */
.owner-alert-recipients .owner-alert-recipients-gap {
  color: #b45309;
  font-weight: 650;
}
/* =========================================================================
   Owner / Planner / Supervisor dashboard (redesign v2, 30 Aug 2026)
   A self-contained "island" that matches the approved mockup rather than
   inheriting the generic ERP theme: warm ground, deep-navy brand, Manrope
   numerals, colour-coded outlets, amber Pay Later, soft mint calendar tiles.
   All colours are scoped --od-* tokens on .owner-dash, with a night variant,
   so the look is consistent regardless of the active brand theme.
   ====================================================================== */
.owner-dash {
  --od-ground: #f4f2ec;
  --od-surface: #ffffff;
  --od-surface-2: #faf9f5;
  --od-ink: #16182f;
  --od-brand: #14173d;
  --od-muted: #6f7288;
  --od-faint: #9a9cae;
  --od-line: #e7e3d9;
  --od-line-soft: #f0ede5;
  --od-accent: #343a86;
  --od-accent-soft: #e9eaf6;
  --od-paylater: #b9631a;
  --od-mint: #e7efe8;
  --od-mint-2: #d6e7d8;
  --od-mint-ink: #24693b;
  --od-shadow: 0 1px 2px rgba(20,23,61,.05), 0 10px 26px -16px rgba(20,23,61,.28);
  --od-font: "Manrope", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Fluid, not capped: the dashboard uses whatever width the screen gives
     (owner, 30 Aug 2026 - a 1180px cap left a large monitor half empty).
     Small screens are handled by the media queries below, unchanged. */
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 18px;
  background: var(--od-ground);
  border: 1px solid var(--od-line);
  border-radius: 20px;
  padding: clamp(16px, 2.4vw, 26px);
  color: var(--od-ink);
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
}
body[data-ui-mode="night"] .owner-dash {
  --od-ground: #14162b;
  --od-surface: #1b1e38;
  --od-surface-2: #191c33;
  --od-ink: #f1f1f7;
  --od-brand: #eef0ff;
  --od-muted: #a4a7c0;
  --od-faint: #787b98;
  --od-line: #2b2e4f;
  --od-line-soft: #242745;
  --od-accent: #8f97ee;
  --od-accent-soft: #262a55;
  --od-paylater: #e79a53;
  --od-mint: #1d3027;
  --od-mint-2: #274233;
  --od-mint-ink: #82d8a2;
  --od-shadow: 0 1px 2px rgba(0,0,0,.32), 0 12px 28px -16px rgba(0,0,0,.6);
}

/* ---- hero ---- */
.owner-dash-hero {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 18px 28px;
  flex-wrap: wrap;
  padding: 2px 2px 0;
}
.owner-dash-hero-lead { min-width: 0; }
.owner-dash-eyebrow {
  font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--od-muted); font-weight: 800;
}
.owner-dash-amount {
  font-family: var(--od-font);
  font-size: clamp(32px, 5vw, 50px);
  font-weight: 800; letter-spacing: -0.03em; line-height: 1.02;
  color: var(--od-ink); margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.owner-dash-sub {
  color: var(--od-muted); font-size: 14px; font-weight: 600;
  margin-top: 6px; font-variant-numeric: tabular-nums;
}
.owner-dash-hero-controls { flex: none; }

/* ---- segmented period toggle ---- */
.owner-dash-period { display: flex; gap: 8px; align-items: center; }
.owner-dash-seg {
  display: inline-flex; gap: 2px;
  background: var(--od-surface-2); border: 1px solid var(--od-line);
  border-radius: 11px; padding: 3px;
}
.owner-dash-seg button {
  border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--od-muted);
  padding: 7px 13px; border-radius: 8px; line-height: 1; white-space: nowrap;
}
.owner-dash-seg button:hover { color: var(--od-ink); }
.owner-dash-seg button.on {
  background: var(--od-brand); color: #ffffff; font-weight: 700;
  box-shadow: 0 1px 2px rgba(20,23,61,.18);
}
body[data-ui-mode="night"] .owner-dash-seg button.on { color: #14162b; }
.owner-dash-more { position: relative; }
.owner-dash-more-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--od-surface); border: 1px solid var(--od-line);
  border-radius: 10px; padding: 8px 12px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--od-muted);
}
.owner-dash-more-btn svg { width: 14px; height: 14px; }
.owner-dash-more-btn.on { color: var(--od-ink); border-color: var(--od-accent); background: var(--od-accent-soft); }
.owner-dash-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  background: var(--od-surface); border: 1px solid var(--od-line);
  border-radius: 12px; box-shadow: var(--od-shadow); padding: 6px;
  min-width: 220px; display: flex; flex-direction: column; gap: 2px;
}
.owner-dash-menu > button {
  border: 0; background: none; text-align: left; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 600; color: var(--od-ink);
  padding: 8px 10px; border-radius: 8px;
}
.owner-dash-menu > button:hover { background: var(--od-surface-2); }
.owner-dash-menu > button.active { background: var(--od-accent-soft); color: var(--od-accent); }
.owner-dash-menu .dashboard-custom-dates { display: none; gap: 8px; padding: 6px 4px 2px; }
.owner-dash-menu .dashboard-custom-dates.active { display: flex; }
.owner-dash-menu .dashboard-custom-dates label { font-size: 11px; font-weight: 700; color: var(--od-muted); display: grid; gap: 3px; }
.owner-dash-apply {
  margin-top: 4px; border: 0; background: var(--od-accent); color: #fff;
  font: inherit; font-size: 13px; font-weight: 700; padding: 8px 10px;
  border-radius: 8px; cursor: pointer;
}
body[data-ui-mode="night"] .owner-dash-apply { color: #14162b; }

/* ---- outlet cards ---- */
.owner-outlet-cards {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px;
}
.owner-outlet-card {
  --card-accent: #5b6472;
  background: var(--od-surface); border: 1px solid var(--od-line);
  border-radius: 16px; padding: 16px;
  display: flex; flex-direction: column; gap: 12px; box-shadow: var(--od-shadow);
}
.owner-outlet-card.accent-ck { --card-accent: #5b6472; }
.owner-outlet-card.accent-iskcon { --card-accent: #1f9d55; }
.owner-outlet-card.accent-rtc { --card-accent: #e2711d; }
.owner-outlet-card.accent-vrc { --card-accent: #7a55d0; }
.owner-outlet-card.accent-default { --card-accent: var(--od-accent); }
.owner-outlet-card.is-kitchen { background: var(--od-surface-2); }
.owner-outlet-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.owner-outlet-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--card-accent); flex: none; }
.owner-outlet-name {
  font-size: 12.5px; font-weight: 800; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--od-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.owner-outlet-amount {
  font-family: var(--od-font); font-size: 25px; font-weight: 800; letter-spacing: -0.02em;
  color: var(--od-ink); font-variant-numeric: tabular-nums; line-height: 1;
}
.owner-outlet-bills { font-size: 12.5px; color: var(--od-faint); margin-top: -6px; font-variant-numeric: tabular-nums; }
.owner-outlet-metrics { display: flex; flex-direction: column; gap: 7px; padding-top: 11px; border-top: 1px solid var(--od-line-soft); }
.owner-outlet-row { display: flex; justify-content: space-between; align-items: baseline; font-size: 12.5px; }
.owner-outlet-row span { color: var(--od-muted); font-weight: 600; }
.owner-outlet-row b { font-family: var(--od-font); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--od-ink); }
.owner-outlet-row.pay b { color: var(--od-paylater); }
.owner-outlet-row.dis b { color: var(--od-muted); }

/* ---- banners / empty ---- */
.owner-dash-banner {
  background: #fff4e5; border: 1px solid #f2d5a8; color: #8a5312;
  border-radius: 12px; padding: 10px 14px; font-size: 13px; font-weight: 600;
}
body[data-ui-mode="night"] .owner-dash-banner { background: #33230f; border-color: #5a3f18; color: #e6a24f; }
.owner-dash-empty {
  background: var(--od-surface); border: 1px dashed var(--od-line);
  border-radius: 14px; padding: 20px; text-align: center;
  color: var(--od-muted); font-size: 13.5px; font-weight: 500;
}

/* ---- calendar + orders split (reference design: calendar card left, a
        branch-filtered orders panel right) ---- */
.owner-cal-split {
  display: grid; grid-template-columns: 1.5fr 1fr; gap: 14px; align-items: start;
}
.owner-cal-card, .owner-cal-orders-panel {
  background: var(--od-surface); border: 1px solid var(--od-line); border-radius: 16px;
  box-shadow: var(--od-shadow); padding: 16px; min-width: 0;
}
.owner-cal-card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.owner-cal-card-title strong { display: block; font-family: var(--od-font); font-size: 16px; font-weight: 800; color: var(--od-ink); }
.owner-cal-card-title small { color: var(--od-muted); font-size: 12px; }
.owner-dash .owner-cal-nav { display: flex; align-items: center; gap: 10px; }
.owner-dash .owner-cal-nav span { color: var(--od-ink); font-weight: 700; font-size: 13.5px; white-space: nowrap; }
.owner-dash .owner-cal-nav button {
  width: 28px; height: 28px; border: 1px solid var(--od-line); background: var(--od-surface-2);
  color: var(--od-muted); border-radius: 8px; cursor: pointer; font-size: 15px; line-height: 1;
}
/* Neutralise the legacy month-nav pill (uppercase, max-width ellipsis) so the
   label reads plainly like the reference. */
.owner-dash .owner-cal-nav span {
  background: none; border: 0; padding: 0; max-width: none;
  text-transform: none; letter-spacing: 0; overflow: visible; text-overflow: clip;
}

/* calendar grid: day number small at top, count big below, like the reference */
.owner-dash .od-cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
.owner-dash .od-cal > b { text-align: center; font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--od-faint); font-weight: 700; padding-bottom: 2px; }
.owner-dash .od-day {
  position: relative; display: flex; flex-direction: column; align-items: flex-start;
  min-height: 64px; padding: 7px 9px; margin: 0;
  background: var(--od-surface-2); border: 1px solid transparent; border-radius: 10px;
  cursor: pointer; text-align: left; transition: transform 0.12s ease;
}
.owner-dash .od-day:hover { transform: translateY(-1px); }
.owner-dash .od-day.blank { background: transparent; cursor: default; }
.owner-dash .od-day > * { position: static; }
/* Neutralise legacy day-cell number/badge chrome (circle backgrounds, fixed
   sizes) so only the reference look applies - including on the selected day. */
.owner-dash .od-day .od-dn,
.owner-dash .od-day .od-cnt {
  background: none; border: 0; border-radius: 0; width: auto; height: auto;
  min-width: 0; min-height: 0; box-shadow: none; display: block;
}
.owner-dash .od-day.selected .od-cnt, .owner-dash .od-day.is-selected .od-cnt { background: none; color: var(--od-mint-ink); }
.owner-dash .od-day.selected .od-dn, .owner-dash .od-day.is-selected .od-dn { color: var(--od-accent); }
.owner-dash .od-day .od-dn { font-size: 11.5px; font-weight: 600; color: var(--od-muted); line-height: 1; }
.owner-dash .od-day .od-cnt { margin-top: auto; font-family: var(--od-font); font-size: 17px; font-weight: 800; color: var(--od-mint-ink); line-height: 1; }
.owner-dash .od-day .od-lbl { font-size: 8.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--od-mint-ink); opacity: 0.7; line-height: 1.4; }
/* ONE colour, graded by volume: plain = no orders (and holidays get no colour
   of their own - they live in the tooltip); light green = quiet day, darker =
   busier, relative to the month's busiest day. */
.owner-dash .od-day.g1 { background: #e9f2ea; }
.owner-dash .od-day.g2 { background: #d3e7d7; }
.owner-dash .od-day.g3 { background: #b9dac0; }
body[data-ui-mode="night"] .owner-dash .od-day.g1 { background: #1c2f26; }
body[data-ui-mode="night"] .owner-dash .od-day.g2 { background: #25402f; }
body[data-ui-mode="night"] .owner-dash .od-day.g3 { background: #2f5039; }
.owner-dash .od-day.selected, .owner-dash .od-day.is-selected { border-color: var(--od-accent); box-shadow: 0 0 0 1px var(--od-accent) inset; }
.owner-dash .od-day.today .od-dn { color: var(--od-accent); font-weight: 800; }

/* orders panel: branch chips + the selected day's orders. min-height keeps the
   panel from bouncing when a 1-order day follows an 8-order day. */
.owner-cal-orders-panel { display: flex; flex-direction: column; gap: 12px; min-height: 340px; }
.owner-cal-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.owner-cal-chips button {
  border: 1px solid var(--od-line); background: var(--od-surface-2); color: var(--od-muted);
  font: inherit; font-size: 12px; font-weight: 700; padding: 6px 12px; border-radius: 999px; cursor: pointer;
}
.owner-cal-chips button.on { background: var(--od-brand); border-color: var(--od-brand); color: #ffffff; }
body[data-ui-mode="night"] .owner-cal-chips button.on { color: #14162b; }
.owner-cal-orders-card { display: none; flex-direction: column; gap: 10px; min-width: 0; }
.owner-cal-orders-card.default { display: flex; }
.owner-cal-orders-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; border-bottom: 1px solid var(--od-line-soft); padding-bottom: 10px; }
.owner-cal-orders-title strong { display: block; font-family: var(--od-font); font-size: 16px; font-weight: 800; color: var(--od-ink); margin-top: 2px; }
.owner-cal-orders-tot { text-align: right; }
.owner-cal-orders-tot b { display: block; font-family: var(--od-font); font-size: 14px; font-weight: 800; color: var(--od-accent); }
.owner-cal-orders-tot span { font-size: 12px; color: var(--od-muted); font-variant-numeric: tabular-nums; }
.owner-cal-orders-list { display: flex; flex-direction: column; max-height: 430px; overflow-y: auto; }
.owner-cal-order-row { display: flex; gap: 10px; padding: 10px 2px; border-bottom: 1px solid var(--od-line-soft); align-items: flex-start; }
.owner-cal-order-row:last-child { border-bottom: 0; }
.ocor-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 5px; flex: none; background: #5b6472; }
.ocor-dot.accent-iskcon { background: #1f9d55; }
.ocor-dot.accent-rtc { background: #e2711d; }
.ocor-dot.accent-vrc { background: #7a55d0; }
.ocor-dot.accent-default { background: var(--od-faint); }
.ocor-mid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ocor-mid strong { font-size: 13.5px; font-weight: 700; color: var(--od-ink); letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ocor-mid small { font-size: 12px; color: var(--od-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ocor-mid em { font-style: normal; font-size: 11px; color: var(--od-faint); }
.ocor-right { text-align: right; flex: none; display: flex; flex-direction: column; gap: 2px; }
.ocor-right b { font-family: var(--od-font); font-size: 13.5px; font-weight: 800; color: var(--od-ink); font-variant-numeric: tabular-nums; }
.ocor-right small { font-size: 11.5px; color: var(--od-faint); font-variant-numeric: tabular-nums; }
.ocor-right em { font-style: normal; font-size: 11px; color: var(--od-muted); font-weight: 600; }
.owner-cal-orders-empty { color: var(--od-muted); font-size: 13px; padding: 16px 4px; text-align: center; border: 1px dashed var(--od-line); border-radius: 10px; }
.owner-cal-orders-more { font-size: 12px; color: var(--od-accent); font-weight: 700; padding: 8px 2px 0; }

/* ---- Planner / Supervisor ---- */
.planner-dash, .supervisor-dash { gap: 14px; }
.supervisor-dash-head { padding: 2px 4px; }
.supervisor-dash-head .owner-dash-eyebrow { display: block; }
.supervisor-dash-outlet {
  font-family: var(--od-font); font-size: clamp(22px, 4vw, 30px); font-weight: 800;
  letter-spacing: -0.02em; color: var(--od-ink); margin-top: 4px;
}

/* ---- responsive ---- */
@media (max-width: 900px) {
  .owner-outlet-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .owner-dash-hero { flex-direction: column; align-items: stretch; gap: 14px; }
  .owner-dash-hero-controls { align-self: flex-start; width: 100%; }
  .owner-dash-period { flex-wrap: wrap; }
  .owner-dash-seg { flex: 1 1 auto; }
  .owner-dash-seg button { flex: 1 1 auto; text-align: center; }
  .owner-cal-split { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
  .owner-dash { padding: 14px; border-radius: 16px; }
  .owner-outlet-cards { grid-template-columns: 1fr; }
}
/* Small phones: tighter calendar cells, drop the "orders" micro-label. */
@media (max-width: 620px) {
  .owner-dash .od-cal { gap: 3px; }
  .owner-dash .od-day { min-height: 46px; padding: 5px 6px; border-radius: 8px; }
  .owner-dash .od-day .od-cnt { font-size: 14px; }
  .owner-dash .od-day .od-lbl { display: none; }
}

/* ---- Topbar on a phone: title and profile on one row, search under them ----
   (owner, 30 Aug 2026: "in mobile version i see search bar becomes very big
   and profiles comes down".)

   Two rules in styles.css fight the responsive layout, and both do it with
   !important, which is why the existing @media blocks looked correct and
   changed nothing:

     1. The "High-visibility header search box override" (styles.css ~44635)
        pins the search group to `width/min-width/max-width: 560px !important`
        with no media query, AFTER the @media (max-width: 1100px) block that
        correctly relaxes it to `width: 100%; min-width: 0`. !important beats a
        normal declaration whatever the order, so the search box stayed 560px
        wide on a 375px screen - the "very big" search bar.

     2. That same block sets `order: 1 !important`, which beat the ordering in
        design-system's own 1100px block. The search therefore claimed the
        first row, the action cluster was pushed to a row of its own, and its
        contents overflowed the viewport - measured on a 375px screen, the bell
        sat at x=423 and the avatar at x=469, i.e. both off the right edge.
        That is the "profiles comes down".

   So these have to carry !important too: it is the only way to outrank an
   existing !important, and matching the same
   body:not(.logged-out):not(.pos-fullscreen-mode) prefix is what wins on
   specificity. Order is set on all three children so the row cannot be decided
   by DOM order - the bug above was exactly two children tying on order and the
   earlier one winning.

   Desktop is untouched: everything here is inside a max-width query, and the
   three-column centred header above still applies from 1101px up. */
@media (max-width: 1100px) {
  /* max-width and the min-width:0 on every child are load-bearing, not
     belt-and-braces. Without them this block blew the header out to 1016px
     inside an 885px viewport at tablet size: the header is itself a grid item
     of the app shell, grid items default to min-width:auto, and the search row
     spanning both columns pushed its min-content width up into the shell's
     column so the whole workspace grew with it. Nothing scrolled - the
     workspace was clipped, so the right-hand controls just vanished.
     Measured: 609px baseline, 1016px without these lines, 609px with them. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px 12px;
    min-width: 0;
    max-width: 100%;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > * {
    min-width: 0;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > *:first-child {
    order: 1;
    grid-column: 1;
  }
  /* Beside the title, not below it - this is the half the owner noticed. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar > .topbar-actions {
    order: 2;
    grid-column: 2;
    justify-self: end;
  }
  /* Its own full-width row underneath, sized by the screen rather than by the
     560px the override insists on. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .global-search-group {
    order: 3 !important;
    grid-column: 1 / -1 !important;
    flex: 1 1 auto !important;
    width: 100% !important;
    min-width: 0 !important;
    /* Capped, not stretched (owner, 30 Aug 2026: "can you fix this search bar
       strech in small screen"). Dropping the 560px floor let the box shrink on
       a phone, but on a narrow DESKTOP window it then ran the full width of
       the header - a 900px-wide search box for a shop name. 560px is the same
       width it uses on a full desktop, so the control is one size everywhere
       it has the room, and only narrower when the screen truly is. Left
       aligned so it sits under the title rather than drifting to the middle. */
    max-width: 560px !important;
    justify-self: start !important;
  }
}

/* Phone proper. The action cluster has to share one row with the title, so it
   is reduced to what a thumb actually needs: the New button keeps its icon but
   drops its label, and the gaps tighten. Without the flex clamp the New button
   grew to 275px of a 375px screen and shoved the rest out of view. */
@media (max-width: 640px) {
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar {
    gap: 8px 8px;
  }
  /* Never break mid-word. The action cluster takes its width first, so the
     title gets whatever is left - about 100px on a 375px screen - and without
     this "Dashboard" came out stacked as "Dash / boar / d". It shortens with an
     ellipsis instead, which is legible; a broken word is not. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar h1 {
    font-size: 18px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    word-break: normal;
    overflow-wrap: normal;
  }
  /* Trimmed so the title still fits beside them: five controls at 34-36px with
     4px gaps come to ~188px of the 303px content width, leaving ~100px for the
     title - just enough for "Dashboard" at 19px. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar-actions {
    gap: 4px;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar-actions > * {
    flex: 0 0 auto;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .qa-new-button {
    flex: 0 0 auto;
    width: 34px;
    min-width: 34px;
    height: 34px;
    padding: 0;
  }
  /* The label goes, the plus stays. Hidden rather than removed so the button
     keeps its accessible name for screen readers. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .qa-new-button span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
  }
  /* 34px square, matching the New button beside them. Still above the 32px
     floor a thumb needs, and the row has to hold five controls plus a title.

     !important and an explicit `flex` on both axes because width alone loses
     twice over here: the bell carries `flex: 0 0 38px`, and a flex-basis beats
     a width outright, while the What's-new and Help buttons are sized 38px by
     a rule earlier in this same file. Measured after the change rather than
     assumed - at 38px the row came to 239px of a 303px content width and the
     title was left showing "Das...". */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .top-icon-button,
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .top-icon-button.whatsnew-button,
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .top-icon-button.help-button {
    flex: 0 0 34px !important;
    width: 34px !important;
    min-width: 34px !important;
    height: 34px !important;
  }
  /* The bell needs naming separately - it carries .alert-button and is pinned
     at 38px by a rule the shared .top-icon-button selector above does not
     outrank. Measured, not assumed: the other three took 34px and this one
     stayed 38. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .top-icon-button.alert-button {
    flex: 0 0 34px !important;
    width: 34px !important;
    min-width: 34px !important;
    height: 34px !important;
  }
  /* What's new is dropped on a phone. Five controls plus a title do not fit
     across 303px of content: with all of them the title had 67px against the
     97px "Dashboard" needs, so it rendered as "Dash...". This is the one
     control that loses nothing - it opens the release-notes popup, which is
     reading material, not an action, and it is still there on tablet and
     desktop. Call support, notifications and the account badge all stay. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .whatsnew-button {
    display: none !important;
  }
  /* The avatar is the one round control; kept a shade larger so it still reads
     as the account button rather than a fourth icon. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar #sessionBadge {
    padding-left: 4px !important;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar #sessionBadge > * {
    width: 36px !important;
    height: 36px !important;
    min-width: 36px !important;
  }
  /* The search keeps a real tap target even on the narrowest phone. */
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .global-search-group .global-search-shell {
    min-width: 0 !important;
  }
  body:not(.logged-out):not(.pos-fullscreen-mode) .topbar .global-search-submit {
    flex: 0 0 auto;
  }
}

/* ---- Owner dashboard: no 1040px floor on a phone (owner, 30 Aug 2026) ------
   The owner dashboard was clipped on mobile - the location cards, the date
   chips and the calendar's Fri/Sat columns all ran off the right edge with no
   way to reach them.

   Cause: the app shell's content column is pinned to a 1040px MINIMUM, so the
   whole layout is 1128px wide (88px rail + 1040px) whatever the screen is.
   styles.css already tries to relax that twice - once generically below 1128px
   (styles.css ~490) and once for this dashboard below 900px (~42574) - but a
   THIRD rule undoes both:

     @media (max-width: 1366px) {
       body.sidebar-overlay-mode .app-shell:has(.owner-stitch-dashboard) {
         grid-template-columns: 88px minmax(1040px, 1fr);
       }
     }

   On a 375px screen all three queries match, and that one wins on specificity:
   body.sidebar-overlay-mode .app-shell:has(...) outranks both
   .app-shell:has(...) and body.sidebar-overlay-mode .app-shell. So the two
   narrower fixes never applied and the floor stayed at 1040px. Measured on the
   real page: shell columns resolved to "88px minmax(1040px, 1fr)" and the
   workspace was 1040px wide inside a 375px viewport, clipped rather than
   scrollable.

   This restates the intent below 1128px - the width at which the generic rule
   already says the page is guaranteed to overflow - at matching specificity
   and later in the cascade, so it wins without !important. The 1040px floor is
   left alone from 1129px up, which is the laptop range it was written for. */
@media (max-width: 1128px) {
  body.sidebar-overlay-mode .app-shell:has(.owner-stitch-dashboard),
  body.sidebar-overlay-mode.sidebar-expanded .app-shell:has(.owner-stitch-dashboard) {
    grid-template-columns: 88px minmax(0, 1fr);
    overflow-x: hidden;
  }
}

/* The panels inside it have to be able to shrink too. A grid item defaults to
   min-width:auto, so a wide child (the calendar's seven columns, a long money
   figure) would otherwise hold the column open at its min-content width and
   re-create the overflow one level down. */
@media (max-width: 1128px) {
  body.sidebar-overlay-mode .owner-stitch-dashboard,
  body.sidebar-overlay-mode .owner-dashboard-partition-box,
  body.sidebar-overlay-mode .owner-dashboard-partition,
  body.sidebar-overlay-mode .owner-builder-panel {
    min-width: 0;
    max-width: 100%;
  }
}


/* ── Toast tones ────────────────────────────────────────────────────────────

   showToast (app-workflows.js) now picks a tone: ok / warn / err, with the
   old neutral grey kept as "info" so an unclassified message looks exactly
   as it always did. The base .toast rule lives in styles.css; these deepen
   only the ground - dark enough that the base rule's white text passes
   contrast on all three. The err toast carries a dismiss button instead of
   a timer: a failure should not disappear before it has been read. */
.toast { display: flex; align-items: flex-start; gap: 10px; }
.toast-symbol { flex: 0 0 auto; font-weight: 700; line-height: 1.45; }
.toast-text { min-width: 0; overflow-wrap: anywhere; }
.toast-ok { background: #166534; }
.toast-warn { background: var(--ds-warning, #8a5300); }
.toast-err { background: #991b1b; }
.toast-dismiss {
  margin-left: 4px;
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: #ffffff;
  opacity: 0.75;
  font-size: 14px;
  line-height: 1.45;
  cursor: pointer;
  padding: 0 2px;
}
.toast-dismiss:hover,
.toast-dismiss:focus-visible { opacity: 1; }

/* A small, quiet spinner chip while the report-history loop is still paging
   the business history in (reportHistoryLoadingNotice, app-workflows.js). It
   USED to be a full-width amber paragraph, which read as a warning; the owner
   found it confusing (4 Sep 2026), so it is now an unobtrusive "Updating…"
   pill with a spinner - present enough to say the figures are still filling,
   quiet enough to ignore. */
.history-loading-notice {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 10px;
  padding: 4px 11px 4px 9px;
  border: 1px solid var(--control-line, #e2e8f0);
  border-radius: 999px;
  background: var(--surface, #fff);
  color: #64748b;
  font-size: 12px;
  font-weight: 600;
}
.history-loading-notice::before {
  content: "";
  width: 12px;
  height: 12px;
  flex: none;
  border-radius: 50%;
  border: 2px solid #cbd5e1;
  border-top-color: #64748b;
  animation: history-loading-spin 0.7s linear infinite;
}
@keyframes history-loading-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .history-loading-notice::before { animation-duration: 2.4s; }
}
/* Toolbar: tabs left, filters right, BOTH wrapping - three selects plus a
   button must never push past the card edge (owner, 2 Sep 2026: "filters are
   overflowing"). The select width beats the app-wide bare-select 260px rule. */
.pending-delivery-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 14px; margin-bottom: 12px; }
.pending-delivery-tabs { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pending-delivery-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.pending-delivery-filter { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: #475569; font-weight: 600; white-space: nowrap; }
.pending-delivery-filter select { width: 150px; max-width: 150px; min-height: 34px; }
/* The packing queue reads as item blocks: a rule above each item's first row. */
tr.pending-delivery-queue-first td { border-top: 2px solid #cbd5e1; }
.pending-delivery-search { width: 210px; min-height: 34px; padding: 0 10px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 12.5px; }
/* The app-wide search-clear enhancer wraps every type=search input in a
   .search-clear-field sized width:100% - which shoved this toolbar's search
   onto its own full-width row and the filters onto a second line (owner
   screenshot, 2 Sep 2026). Scoped: keep the standard Clear button, compact. */
.pending-delivery-filters .search-clear-field { width: auto; flex: 0 1 auto; grid-template-columns: auto auto; gap: 6px; }
.pending-delivery-filters .search-clear-field > input { width: 210px; }
.pending-delivery-filters .search-clear-button { min-width: 0; height: 34px; padding: 0 10px; }

/* ── Box order (walking-customer gift box, owner 2 Sep 2026) ─────────────────
   The strip sits between the item rows and the items footer; visible only
   while at least one row is ticked as a box component. The per-row "Box" tick
   lives inside the Item cell so the so2 grid columns stay untouched. */
.so-box-panel {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border: 1px dashed var(--border-strong, #c9a227);
  border-radius: 8px;
  margin-top: 6px;
  background: var(--surface-2, rgba(201, 162, 39, 0.06));
  font-size: 13px;
}
.so-box-panel[hidden] { display: none; }
.so-box-panel b { font-variant-numeric: tabular-nums; }
.so-box-panel label { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; }
.so-box-panel input#salesOrderBoxCount { width: 64px; min-height: 32px; text-align: center; }
/* The Box tick rides INSIDE the item field's right edge (owner, 2 Sep 2026:
   "instead of tick box on down can we make it on side") so the row keeps one
   line and every field stays aligned. The input pads right so a long product
   name never runs under the tick. */
.so-line-item-wrap { position: relative; display: block; }
.so-line-item-wrap > input.so-line-item { width: 100%; padding-right: 58px; }
.so-line-box-flag {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex !important;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--text-muted, #8a7a4a);
  margin: 0;
  cursor: pointer;
}
/* A crisp native tick, same as .pos-stock-check on the stock screens. The
   global input rules paint text-field chrome (background, border, padding,
   radius) onto every <input>, which wrapped the little square in a pale halo -
   strip all of it and let accent-color draw the box. */
.so-line-box-flag input.so-line-box {
  appearance: auto;
  -webkit-appearance: checkbox;
  width: 16px;
  height: 16px;
  min-height: 16px;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  accent-color: #1d4ed8;
  cursor: pointer;
}
/* Inside the so2 order grid the legacy "> label input" rule (styles.css,
   specificity 0,3,1) paints text-field chrome - 32px height, border, white
   fill, display:block - onto EVERY input under the label, the Box tick
   included, and outranks the reset above (0,2,1). Owner screenshot, 3 Sep
   2026, B2B order: the tick drew as a tall white slab across the "B" of Box.
   The item field's 58px right padding lost the same way. Scoped at (0,3,2). */
.so2-linelist .sales-order-line > label input.so-line-box {
  width: 16px;
  height: 16px;
  min-height: 16px;
  padding: 0;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  display: inline-block;
  line-height: normal;
}
/* (0,4,2): the dialog-wide
   `.dialog-panel input:not([type=checkbox]):not([type=radio]):not([type=file])`
   rule counts each :not() argument, so it sits at (0,4,1) and beat the 58px
   at (0,3,2) - measured 12px on the live dialog, 3 Sep 2026. */
.dialog-panel .so2-linelist .sales-order-line > label input.so-line-item { padding-right: 58px; }
.so2-linelist .sales-order-line > label .so-line-box-flag { font-size: 11px; line-height: 1; }

/* A Box row types its AMOUNT and the rate follows (owner, 3 Sep 2026). The
   read-only figure and the input share the cell; the row's is-box-row class
   picks which one shows. Estimations share the template but never tick a
   box, so the input stays hidden there. */
.sales-order-line .so-line-amount { display: none; }
.so2-linelist .sales-order-line .purchase-order-line-amount input.so-line-amount {
  display: none;
  width: 100%;
  height: 32px;
  min-height: 0;
  padding: 0 8px;
  border: 1px solid #dbe2ea;
  border-radius: 7px;
  font-size: 13px;
  font-weight: 700;
  text-align: right;
  color: #0f172a;
  background: #fff;
  box-sizing: border-box;
  font-variant-numeric: tabular-nums;
}
.so2-linelist .sales-order-line.is-box-row .purchase-order-line-amount input.so-line-amount { display: block; }
.so2-linelist .sales-order-line.is-box-row .purchase-order-line-amount strong { display: none; }
/* The legacy cell is a grid with a 4px gap, and its hidden "Amount" label
   text (font-size 0) still counts as a grid row - so the input landed 4px
   further down and centred 2px below Qty/Unit/Rate (owner screenshot, 3 Sep
   2026). A plain block with line-height 0 gives the label no height at all. */
.so2-linelist .sales-order-line.is-box-row .purchase-order-line-amount { display: block; line-height: 0; }

/* Settings ▸ Sales ▸ "POS menu items" tick grid (owner, 2 Sep 2026). */
.pos-menu-toggle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 6px 16px;
  margin: 4px 0 10px;
}
.pos-menu-toggle {
  display: flex !important;
  flex-direction: row !important;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.pos-menu-toggle input[type="checkbox"] {
  appearance: auto;
  -webkit-appearance: checkbox;
  width: 16px;
  height: 16px;
  min-height: 16px;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  box-shadow: none;
  accent-color: #1d4ed8;
  flex: 0 0 auto;
}

/* ── Branch (identification) label preview ────────────────────────────────
   Mirrors the "ident" ZPL in labeltool/print-label.ps1: branch reversed out of
   a solid bar, item name over up to two lines beneath it, weight last. This
   sticker is read by whoever unpacks the goods, not by a customer, so it
   carries no barcode, QR, price, MFD or FSSAI - the three fields get the whole
   50x25mm and are set as large as they fit (owner, 4 Sep 2026, replacing a
   BarTender layout). The print is the authority; this only has to look like
   it. */
/* Flex column, and the LINES share what is left under the bar. Item, weight and
   date are each optional, so a sticker can carry three lines, one, or none -
   and the ZPL re-spaces to match, centring a lone line rather than leaving a
   hole. Giving each line an equal share of the space reproduces that without
   the preview needing to know how many there are. */
.ident-label { display: flex; flex-direction: column; gap: 0; padding: 0 0 6px; }
/* The branch BLOCK - one line, centred in the band the print gives it, and
   free to be taller than it is wide. flex: none so it keeps its own height and
   the item lines below share what is left, the same order the ZPL places them
   in. overflow: visible because the line is squeezed by a transform and a
   clipped box would cut the untransformed text. */
.ident-label .ident-label-branchblock {
  display: flex; flex-direction: column; justify-content: center;
  flex: none; padding: 2px 0; overflow: visible;
}
/* The vertical stretch is applied per label from the markup; this only fixes
   where it stretches from, so the name grows evenly about its own middle. */
.ident-label .ident-label-branchblock .ident-label-branch { transform-origin: center; }
.ident-label .ident-label-branch {
  /* Black letters on white paper (owner, 10 Sep 2026). Was #fff on a #111
     bar, mirroring a ^GB block the ZPL no longer draws. The print is the
     authority; this only has to look like it. */
  background: #fff; color: #000;
  /* Size comes from the markup, fitted the way print-label.ps1 fits it, so
     the branch grows and shrinks on screen exactly as it does on the sticker. */
  text-align: center; letter-spacing: 0.03em;
  line-height: 1.35; padding: 3px 6px;
  /* No ellipsis. The printer fits the branch to the sticker and never cuts
     it, so a preview that cuts it is telling the user something false;
     fitBranchLabelPreview shrinks it to fit instead. nowrap stays - the ZPL
     sets the branch on ONE line (^FB…,1,…), so wrapping here would be just as
     untrue as clipping. */
  white-space: nowrap; overflow: visible;
  /* The ZPL overprints the field one dot right and one dot down to thicken
     the strokes, because ^A0 has no bold. 900 plus a hairline stroke is the
     closest the screen gets to that; the stroke is what keeps it heavy at the
     small preview size, where a weight alone barely reads. */
  font-weight: 900;
  -webkit-text-stroke: 0.4px #000;
}
.ident-label .ident-label-line {
  flex: 1; display: flex; align-items: center; justify-content: center;
  text-align: center; font-weight: 800; line-height: 1.08;
  padding: 0 6px; overflow-wrap: anywhere;
}

/* Pay Later WhatsApp delivery chip on Sales > Invoices (5 Sep 2026). */
.paylater-wa-chip { display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; vertical-align: middle; background: #f2f4f7; color: #344054; }
.paylater-wa-chip.sending { background: #fff7e6; color: #b54708; }
.paylater-wa-chip.sent { background: #ecfdf3; color: #067647; }
.paylater-wa-chip.failed { background: #fef3f2; color: #b42318; }

/* ---- Delivery Board (Central Kitchen > Delivery Board) --------------------
   The owner's reference design, kitchen-display.html (6 Sep 2026): a dark
   1920x1080 screen, one column per branch, big white item rows with an
   age-coloured bar, a sent/total footer per branch, a live clock top-right
   and an Upcoming Orders panel. Colours and sizes are the design's own; the
   only additions are the amber run labels inside a column and the counters
   in the top bar's empty middle. Fonts fall back from the design's Archivo /
   JetBrains Mono to the faces the app already ships. */
/* The viewport is whatever box the board is given - the whole TV, or the
   frame on the ERP page. Inside it the stage is the design's fixed
   1920x1080, scaled by --dboard-scale (set by applyDeliveryBoardFit) and
   centred, so the picture is identical on every screen. Below 1100px the
   fitter flips the viewport to "stacked" and the stage becomes a plain
   scrolling column. */
.dboard-viewport {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: #0B0E10;
}

.dboard-viewport[data-layout="stage"] .dboard-stage {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1920px;
  height: 1080px;
  transform: translate(-50%, -50%) scale(var(--dboard-scale, 1));
  transform-origin: center center;
}

.dboard-viewport[data-layout="stage"] .dboard {
  width: 1920px;
  height: 1080px;
}

.dboard-viewport[data-layout="stacked"] {
  overflow: auto;
}

.dboard-viewport[data-layout="stacked"] .dboard-stage {
  position: static;
  width: 100%;
  height: auto;
  transform: none;
}

.dboard-viewport[data-layout="stacked"] .dboard {
  height: auto;
}

.dboard {
  --dboard-mono: ui-monospace, "JetBrains Mono", "IBM Plex Mono", Consolas, monospace;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  background: #0B0E10;
  color: #F4F6F8;
  font-family: Archivo, Inter, "IBM Plex Sans", system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
}

/* The text-size zoom is switched off for this screen: the stage scale does
   the sizing, and with zoom in play the two would fight. The 100vh division
   the other display modes need is therefore undone here too. */
body.kds-display-mode.desktop-delivery-mode:not(.logged-out),
body.kds-display-mode.android-delivery-mode:not(.logged-out) {
  zoom: 1 !important;
}

body.kds-display-mode.desktop-delivery-mode .workspace,
body.kds-display-mode.android-delivery-mode .workspace {
  height: 100vh;
  padding: 0;
  background: #0B0E10;
}

body.kds-display-mode.desktop-delivery-mode .kitchen-shell,
body.kds-display-mode.android-delivery-mode .kitchen-shell {
  height: 100%;
  gap: 0;
}

.dboard-top {
  flex: 0 0 76px;
  display: flex;
  align-items: stretch;
  gap: 2px;
  background: #14181C;
  border-bottom: 2px solid #23292F;
}

.dboard-brand {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 28px;
  background: #191E23;
}

.dboard-brand strong {
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.5px;
  line-height: 1;
  white-space: nowrap;
}

.dboard-brand span {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  font-family: var(--dboard-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.5px;
  color: #8A959F;
  white-space: nowrap;
}

.dboard-pulse {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #22C55E;
  animation: dboard-pulse 2s infinite;
}

@keyframes dboard-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.dboard-counters {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 2px;
}

.dboard-counter {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 120px;
  padding: 0 18px;
}

.dboard-counter strong {
  font-size: 32px;
  font-weight: 800;
  line-height: 1;
  color: var(--dboard-accent, #F4F6F8);
}

.dboard-counter span {
  margin-top: 6px;
  font-family: var(--dboard-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 1.2px;
  color: #79848E;
}

.dboard-controls {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
}

.dboard-controls button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid #2A3138;
  border-radius: 10px;
  background: #191E23;
  color: #9AA5AF;
  cursor: pointer;
}

.dboard-controls button svg {
  width: 20px;
  height: 20px;
}

.dboard-controls button[data-logout] {
  color: #F87171;
}

.dboard-clock {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  padding: 0 28px;
  background: #191E23;
}

.dboard-clock strong {
  font-size: 38px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -1px;
  white-space: nowrap;
}

.dboard-clock span {
  margin-top: 6px;
  font-family: var(--dboard-mono);
  font-size: 14px;
  color: #8A959F;
  white-space: nowrap;
}

.dboard-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(var(--dboard-columns, 1), minmax(0, 1fr)) 400px;
  gap: 2px;
  background: #23292F;
}

.dboard-branch {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: #0F1317;
}

/* Compact (owner, 7 Sep 2026: "so we can show more items"): the branch header
   is the name alone, on a short band. */
.dboard-branch-head {
  flex: 0 0 auto;
  padding: 8px 16px 6px;
  border-top: 5px solid var(--dboard-accent, #38BDF8);
  border-bottom: 2px solid #1D2328;
}

.dboard-branch-head strong {
  display: block;
  overflow: hidden;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.3px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Rows flow down, then into a second column of the same width when the
   branch has more than fit - the design's column wrap, done in CSS grid. The
   container's height is definite (a stretched flex child of a fixed-height
   board), which is what lets auto-fill count the rows. */
.dboard-rows {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  padding: 12px;
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(auto-fill, 44px);
  grid-auto-columns: minmax(0, 1fr);
  gap: 5px;
  align-content: start;
}

.dboard-run {
  display: flex;
  align-items: center;
  padding: 0 6px;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 1.2px;
  /* Plain white since 12 Sep 2026. This heading was amber, which put a third
     colour on a board whose only colours now MEAN something - green for a
     delivery still to come, red for one whose time has gone. The heading says
     "3RD DELIVERY" in words, so it never needed a colour of its own. */
  color: #F4F6F8;
  white-space: nowrap;
}

.dboard-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 5px 12px;
  border-radius: 8px;
  background: #FFFFFF;
}

.dboard-row i {
  flex: 0 0 auto;
  width: 8px;
  height: var(--dboard-bar-height, 26px);
  border-radius: 3px;
}

.dboard-row span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: var(--dboard-row-font, 22px);
  font-weight: 800;
  letter-spacing: 0.2px;
  text-transform: uppercase;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: #101418;
}

/* The LATE / PART / APPROVAL badge and its three colour rules lived here
   until 8 Sep 2026. Nothing draws it now: on the TV it took the width the
   item name needed and truncated "KAJU KAARAM" to "KAJU KAAR...". The
   coloured bar on the left still carries the urgency, and the stage still
   rides on the row for the counters. */

.dboard-row.is-sent { background: #132018; }
.dboard-row.is-sent span { color: #5E7A68; text-decoration: line-through; }
.dboard-row.is-approval { background: #1A2026; }
.dboard-row.is-approval span { color: #C9D1D8; }

.dboard-branch-foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  border-top: 2px solid #1D2328;
}

.dboard-count {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.dboard-count strong { font-size: 24px; font-weight: 800; line-height: 1; color: #22C55E; }
.dboard-count em { font-size: 16px; font-weight: 700; font-style: normal; line-height: 1; color: #5F6A73; }
.dboard-count span { font-size: 18px; font-weight: 800; line-height: 1; color: #9AA5AF; }

.dboard-bar {
  flex: 1;
  min-width: 0;
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: #232A31;
}

.dboard-bar div {
  height: 100%;
  border-radius: 5px;
  background: #16A34A;
}

.dboard-clear {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 14px;
  padding: 20px 22px;
  border-radius: 12px;
  border: 1px dashed #2A3A31;
  background: #131A17;
}

.dboard-clear b { font-size: 24px; font-weight: 800; color: #22C55E; }
.dboard-clear span { font-size: 21px; font-weight: 700; color: #7E8C86; }
.dboard-clear-small { margin: 0; }
.dboard-clear-small span { font-size: 17px; }

.dboard-upcoming {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  background: #12171C;
}

.dboard-upcoming > header {
  flex: 0 0 auto;
  padding: 16px 20px 14px;
  border-top: 5px solid #F59E0B;
  border-bottom: 2px solid #1D2328;
}

.dboard-upcoming > header strong {
  display: block;
  font-size: 24px;
  font-weight: 800;
  letter-spacing: 0.4px;
  white-space: nowrap;
}

.dboard-upcoming > header span {
  display: block;
  margin-top: 6px;
  font-size: 16px;
  font-weight: 700;
  color: #79848E;
  white-space: nowrap;
}

.dboard-upcoming-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.dboard-day {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.dboard-day-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 6px 2px;
}

.dboard-day-head span { font-size: 17px; font-weight: 800; letter-spacing: 1.2px; color: #8B959D; white-space: nowrap; }
.dboard-day-head.is-today span { color: #F59E0B; }
.dboard-day-head code { font-family: var(--dboard-mono); font-size: 14px; font-weight: 500; color: #6D777F; white-space: nowrap; }

.dboard-order {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: 9px;
  background: #1A2026;
}

.dboard-order > div { flex: 1; min-width: 0; }
.dboard-order strong { display: block; overflow: hidden; font-size: 20px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; color: #ECF1F5; }
.dboard-order span { display: flex; align-items: center; gap: 8px; margin-top: 3px; overflow: hidden; font-size: 16px; font-weight: 600; color: #8B959D; white-space: nowrap; }
.dboard-order span b { flex: 0 0 auto; padding: 1px 8px; border-radius: 6px; font-family: var(--dboard-mono); font-size: 13px; font-weight: 700; color: #0B0E10; background: #6E7A84; }
.dboard-order > code { flex: 0 0 auto; font-family: var(--dboard-mono); font-size: 15px; font-weight: 700; color: #79848E; white-space: nowrap; }

/* Density: branch headers shrink as branches are added, so six stores still
   read; row text is sized per branch by applyDeliveryBoardFit(). */
.dboard[data-branches="4"] .dboard-branch-head strong { font-size: 24px; }
.dboard[data-branches="5"] .dboard-branch-head strong { font-size: 22px; }
.dboard[data-branches="6"] .dboard-branch-head strong,
.dboard[data-branches="7"] .dboard-branch-head strong,
.dboard[data-branches="8"] .dboard-branch-head strong { font-size: 20px; }
.dboard[data-branches="6"] .dboard-rows,
.dboard[data-branches="7"] .dboard-rows,
.dboard[data-branches="8"] .dboard-rows { padding: 8px; }
.dboard[data-branches="6"] .dboard-grid,
.dboard[data-branches="7"] .dboard-grid,
.dboard[data-branches="8"] .dboard-grid { grid-template-columns: repeat(var(--dboard-columns, 1), minmax(0, 1fr)) 320px; }

/* A new request or line flashes the top bar amber, with the KDS tone. */
body.kds-new-ticket-alert .dboard-top {
  animation: dboard-flash 0.7s ease-in-out 3;
}

@keyframes dboard-flash {
  50% { background: #7A4B00; }
}

/* Inside the ERP page: the same screen scaled into a fixed-height frame, no
   TV controls. */
.dboard-frame {
  position: relative;
  height: 78vh;
  min-height: 560px;
  border-radius: 12px;
  overflow: hidden;
}

.dboard-embedded .dboard-controls {
  display: none;
}

.delivery-board-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

/* Narrow screens (a tablet in portrait): the fitter flips the viewport to
   "stacked" - the columns stack, every branch grows to its own rows, and the
   board scrolls as a whole. */
.dboard-viewport[data-layout="stacked"] .dboard-top { flex: 0 0 auto; flex-wrap: wrap; }
.dboard-viewport[data-layout="stacked"] .dboard-counters { order: 3; flex: 1 0 100%; justify-content: flex-start; }
.dboard-viewport[data-layout="stacked"] .dboard-grid { flex: none; grid-template-columns: 1fr !important; grid-auto-rows: auto; }
.dboard-viewport[data-layout="stacked"] .dboard-rows {
  flex: none;
  overflow: visible;
  grid-auto-flow: row;
  grid-template-rows: none !important;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: 44px;
}
.dboard-viewport[data-layout="stacked"] .dboard-upcoming-body { overflow: visible; }

/* ---- Display shells on a tablet (KDS, Logistics, Delivery Board) ----------
   styles.css re-grids .app-shell for 761-1366px viewports when the sidebar
   auto-collapses (body.sidebar-overlay-mode ... .app-shell { display: grid;
   grid-template-columns: 88px / 252px ... }). In a display mode the sidebar is
   display:none, so the workspace - the next grid item - lands in the SIDEBAR'S
   column and the whole board renders 252px wide down the left edge (owner's
   tablet, 6 Sep 2026). The shell is one block in every display mode, whatever
   the viewport; !important because the overlay rules sit later in styles.css
   at equal or higher specificity. */
body.kds-display-mode .app-shell {
  display: block !important;
  grid-template-columns: none !important;
  overflow: hidden;
}

body.kds-display-mode .workspace {
  width: 100%;
  max-width: none;
  min-width: 0;
  margin: 0;
  /* Text size is a zoom on <body>, so 100vh inside it is 100vh x zoom. Divide
     it back out or a Large text setting pushes the board's bottom edge off
     the screen. */
  height: calc(100vh / var(--ui-zoom, 1));
}

body.kds-display-mode .kds-station-display {
  height: calc((100vh - 16px) / var(--ui-zoom, 1));
}


/* ---- Category Planning (owner, 6 Sep 2026) ---------------------------------
   Central Kitchen ▸ Category Planning. Read-only, Go-button page: a filter
   bar, a card header with the rows and columns controls at the far right, and
   a grouped table (category header rows, item rows, per-outlet breakdown rows)
   that the shell's sort/paging would scatter - so it is hand-rendered here. */
.category-planning-card > h2 { display: none; }
.cp-head { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 12px; }
.cp-head h2 { margin: 0; color: var(--ink); font-size: 20px; line-height: 1.2; }
.cp-lead { margin: 4px 0 0; color: var(--muted); font-size: 12.5px; }
.cp-head-controls { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.cp-rows { display: inline-flex; align-items: center; gap: 6px; color: #475569; font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.cp-rows select { width: auto; min-width: 66px; max-width: 92px; min-height: 34px; }
.cp-columns, .cp-multi { position: relative; }
.cp-columns > summary, .cp-multi > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; color: #334155; font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
.cp-columns > summary svg { width: 14px; height: 14px; }
.cp-columns > summary::-webkit-details-marker, .cp-multi > summary::-webkit-details-marker { display: none; }
.cp-columns[open] > summary, .cp-multi[open] > summary { border-color: #94a3b8; }
.cp-columns-menu, .cp-multi-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 120; min-width: 210px; max-height: 300px; overflow: auto;
  display: grid; gap: 2px; padding: 8px; border: 1px solid var(--line); border-radius: 10px; background: #fff;
  box-shadow: 0 18px 36px rgba(15, 23, 42, 0.18);
}

/* An open menu escapes the card. `.panel` clips its children (position:
   relative + overflow: hidden, styles.css), and these menus are absolutely
   positioned INSIDE it - so before Go, when the card is only as tall as the
   filter bar, the menu was cut off at the card's bottom edge and Branch showed
   just its first option (owner, 7 Sep 2026). After Go the card was tall enough
   and the same menu looked fine, which is what made it read as a data bug.

   Lifted only WHILE a menu is open, so the card still clips a wide table the
   rest of the time. :has() carries the state, so nothing has to be toggled in
   JS - and both menus are covered: the Branch/Category/Status/Delivery picks
   and the columns picker in the card head. */
.category-planning-card:has(.cp-multi[open]),
.category-planning-card:has(.cp-columns[open]),
.category-planning-card:has(.cp-multi[open]) .cp-filters,
.category-planning-card:has(.cp-columns[open]) .cp-head {
  overflow: visible;
}
.cp-columns-menu { left: auto; right: 0; }
.cp-multi-opt, .cp-columns-opt { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 6px; color: #0f172a; font-size: 12.5px; font-weight: 500; cursor: pointer; }
.cp-multi-opt:hover, .cp-columns-opt:hover { background: #f1f5f9; }
.cp-multi-opt input, .cp-columns-opt input { width: 15px; height: 15px; min-height: 15px; margin: 0; }
.cp-multi-all { border: 0; border-radius: 6px; padding: 6px 8px; background: #eef2f7; color: #1e3a8a; font: inherit; font-size: 12px; font-weight: 700; text-align: left; cursor: pointer; }
.cp-multi-all[disabled] { opacity: 0.55; cursor: default; }
.cp-multi-label { color: #64748b; font-weight: 600; }
.cp-multi-value { max-width: 220px; overflow: hidden; text-overflow: ellipsis; color: #0f172a; }
.cp-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; margin-bottom: 12px; padding: 12px;
  border: 1px solid var(--line); border-radius: 10px; background: linear-gradient(180deg, #ffffff, #f8fbff);
}
.cp-field { display: grid; gap: 5px; color: var(--muted); font-size: 12px; font-weight: 600; }
.cp-field input { min-height: 34px; width: 150px; }
.cp-go { min-width: 84px; }
.cp-go[data-stale="1"] { background: #f59e0b; border-color: #f59e0b; color: #3d2f00; }
/* From / To window (owner, 8 Sep 2026): a date and a time side by side under
   one label, so the two rows read as the range they are. */
.cp-range-label { display: block; }
.cp-range-inputs { display: flex; gap: 6px; }
.cp-range-inputs input[type="date"] { width: 148px; }
.cp-range-inputs input[type="time"] { width: 104px; }
/* The Date preset dropdown. A bare "select" rule in the legacy sheet gives
   every select 260px, and dropdown-theme.js copies the select's classes onto
   the wrapper it builds, so both need the width. */
.cp-filters .cp-field select,
.cp-filters .cp-field .global-dropdown { width: 178px; min-width: 0; }
.cp-filters .cp-field .global-dropdown-button { min-height: 34px; width: 100%; }
.cp-empty-note { display: inline-block; margin-top: 4px; color: var(--muted); font-size: 12px; }
.cp-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 14px; margin: 0 0 10px; }
.cp-summary { color: #475569; font-size: 12.5px; }
.cp-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cp-actions .mini-action svg { width: 14px; height: 14px; margin-right: 4px; }
.cp-table { width: 100%; border-collapse: collapse; }
tr.cp-group td { background: #eef2f7; border-top: 2px solid #cbd5e1; font-weight: 700; cursor: pointer; }
tr.cp-group.is-open td { background: #e2e8f0; }
tr.cp-item { cursor: pointer; }
tr.cp-item.is-open td { background: #fbfcfe; }
.cp-caret { display: inline-block; width: 14px; margin-right: 4px; color: var(--muted); font-size: 11px; }
tr.cp-breakdown > td { padding: 0 12px 12px 34px !important; background: #fbfcfe; }
.cp-sub { width: auto; min-width: 60%; border: 1px solid var(--line); border-radius: 8px; background: #fff; font-size: 12.5px; }
.cp-sub th, .cp-sub td { padding: 6px 10px; }
.cp-sub thead th { background: #f8fafc; color: #475569; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; }
.cp-tray { margin-left: 6px; color: #b45309; font-size: 11.5px; font-weight: 600; }
tr.cp-grand td { background: #f8fafc; border-top: 2px solid #94a3b8; font-weight: 700; }
/* The figures sit in a span: styles.css weights every table td and colours
   every td strong at (0,5,3)+, so the cell rules above never reach the text. */
.cp-figure { font-weight: 700; }
.cp-table td.cp-outlets { white-space: normal; overflow-wrap: anywhere; }
.cp-grand-qty, .cp-grand-qty .cp-figure { color: #1e3a8a; }
.cp-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 16px; margin-top: 10px; color: #475569; font-size: 12.5px; }
.cp-pager-nav { display: inline-flex; align-items: center; gap: 8px; }
.cp-empty { padding: 28px 12px; color: var(--muted); text-align: center; }
@media (max-width: 720px) {
  .cp-head-controls { margin-left: 0; }
  .cp-field input { width: 100%; }
  .cp-sub { min-width: 100%; }
}

/* Sales order ▸ Discount (owner, 7 Sep 2026). Sits in the summary card's own
   row rhythm: the ₹/% picker is a native select kept narrow, the value box
   takes the rest and is right-aligned like every other money figure there. */
.so2-discfield { display: inline-flex; align-items: center; gap: 6px; justify-self: end; }
.so2-discfield select {
  width: auto; min-width: 52px; min-height: 32px; padding: 0 6px;
  border: 1px solid var(--line, #d8dee8); border-radius: 8px; background: #fff;
  font: inherit; font-size: 13px; font-weight: 700; color: #0f172a;
}
.so2-discfield input {
  width: 92px; min-height: 32px; padding: 0 8px; text-align: right;
  border: 1px solid var(--line, #d8dee8); border-radius: 8px; font: inherit; font-size: 13px; font-weight: 700;
}
.so2-sumrow-discinput { align-items: center; }

/* Pending Deliveries keeps ALL its columns on a narrow window (owner, 10 Sep
   2026: "in pending deliveries the columns is hiding").

   styles.css:56670 is a legacy mobile rule - at <=900px it hides
   `th/td:nth-child(n+5):not(:last-child)` on EVERY body.inventory-view table.
   On this screen that silently drops Outlet, Pending, Tray, Requested at,
   Delivery and Delivery date, leaving only Request, Item, Requested qty and
   Status. The columns are the packing sheet, so losing them is not a
   simplification, it is losing the sheet.

   Worse, it hides them behind the column picker's back: the picker only ever
   reports the columns IT hid (`hidden` + .table-column-hidden), so it still
   read "11/11 shown" over a table showing five. Nothing on screen said why.

   .table-wrap already carries overflow-x: auto, so putting the columns back
   makes the table scroll sideways - the same thing it does on a desktop that
   is too narrow, and the behaviour the picker's count already describes.

   Specificity note: this must out-rank that rule (0,7,2), so it repeats the
   whole legacy prefix and adds the table id. A shorter selector loses, even
   from this file, which loads last. Scoped to this ONE table on purpose - the
   same legacy rule governs every other inventory screen, and Stock
   Verification already fights it its own way at styles.css:57818. */
@media (max-width: 900px) {
  body.inventory-view:not(.pos-fullscreen-mode):not(.kds-display-mode):not(.log-fullscreen-mode)
    .table-wrap[data-table-id="pending-delivery-lines"] th:nth-child(n+5):not(:last-child),
  body.inventory-view:not(.pos-fullscreen-mode):not(.kds-display-mode):not(.log-fullscreen-mode)
    .table-wrap[data-table-id="pending-delivery-lines"] td:nth-child(n+5):not(:last-child) {
    display: table-cell;
  }
  /* The picker's own hiding must still win - it is the one the count reports. */
  body.inventory-view
    .table-wrap[data-table-id="pending-delivery-lines"] th.table-column-hidden,
  body.inventory-view
    .table-wrap[data-table-id="pending-delivery-lines"] td.table-column-hidden,
  body.inventory-view
    .table-wrap[data-table-id="pending-delivery-lines"] th[hidden],
  body.inventory-view
    .table-wrap[data-table-id="pending-delivery-lines"] td[hidden] {
    display: none;
  }
}

/* Pending Deliveries: the Date control lines up with the filters beside it
   (owner, 11 Sep 2026: "in pending deliveries arrange the date properly").

   Every other control in this bar is .pending-delivery-filter - inline-flex,
   caption to the LEFT of the box, box 150px, all sharing one baseline. The Date
   control is the SHARED listDateFilterControl, built for the label-ABOVE
   toolbars (.inventory-table-controls) that every other screen uses: its field
   is .inventory-search-field, display:grid with flex 1 1 420px. Dropped into
   this bar it came out 63px tall against its neighbours' 41, sat 11px higher
   than all of them, and its box was 101px wide instead of 150. Measured at
   1440px, the ten items in the bar landed on five different top edges.

   Only the PRESENTATION changes here, and only inside this bar. The control
   keeps the same markup, the same data-list-date-* handlers and the same shared
   state as Stock Requests and Transfers, and those screens are untouched -
   there, label-above is right, because it matches their siblings.

   Specificity: .inventory-search-field is (0,1,0) and the group's own
   datepicker width rule is (0,3,0), so each override below carries one class
   more than the rule it has to beat. design-system.css loads last, so nothing
   here needs !important. The width rules deliberately target the THEMED
   wrappers as well as the native control: dropdown-theme.js and
   calendar-theme.js re-parent the real element and shrink it to 1x1px, so a
   width set only on select/input lands on an invisible node. */
.pending-delivery-filters .list-date-group {
  align-items: center;
  gap: 8px 12px;
  flex: 0 1 auto;
}
.pending-delivery-filters .list-date-preset-field,
.pending-delivery-filters .list-date-custom {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 1 auto;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: #475569;
  white-space: nowrap;
}
.pending-delivery-filters .list-date-preset-field select,
.pending-delivery-filters .list-date-preset-field .global-dropdown,
.pending-delivery-filters .list-date-custom input,
.pending-delivery-filters .list-date-group .list-date-custom .global-datepicker,
.pending-delivery-filters .list-date-group .list-date-custom .global-datepicker-field {
  width: 150px;
  max-width: 150px;
  min-height: 34px;
}
/* Below 900px the shared rule gives the group its own full-width band, which
   is right in a label-above toolbar and wrong here - the neighbours simply
   wrap, so this wraps with them. From / To may still stack inside the group
   when three 150px boxes will not fit. */
@media (max-width: 900px) {
  .pending-delivery-filters .list-date-group {
    flex: 0 1 auto;
    flex-wrap: wrap;
  }
}

/* Add Payment at the wide size (owner, 12 Sep 2026: "increase this size of add
   payment like stock transfer"). The dialog carries .wide-dialog, the same
   980px the other big dialogs use; these rules are what stop that width
   reading as an empty box.

   THREE columns, not two. The form is six short fields and a note - at 520px a
   pair per row was right, and at 980 the same pairs stretched into two enormous
   boxes with half the dialog blank. Three across fills the row at a width a
   figure or a date actually wants to be.

   Below 900px it falls back to the single column every dialog uses on a narrow
   window, so nothing about the phone layout changes. */
.wide-dialog #supplierPaymentForm .supplier-payment-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
}
/* The note is the one field that wants the room: a UPI reference or a bank
   transfer id is long, and it sits in the space Status leaves. */
.wide-dialog #supplierPaymentForm .supplier-payment-ref { grid-column: span 2; }
/* The pending figure is a cell in the first row now, not a full-width band, so
   it needs its own box rather than the band's stretch. */
.wide-dialog #supplierPaymentForm .supplier-payment-pending {
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  min-height: var(--control-h, 42px);
}
/* The bill list is what genuinely wanted the width: two columns of bills
   instead of one long scroll. */
.wide-dialog #supplierPaymentForm .supplier-payment-bill-list {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px;
}
@media (max-width: 900px) {
  .wide-dialog #supplierPaymentForm .supplier-payment-grid { grid-template-columns: minmax(0, 1fr); }
  .wide-dialog #supplierPaymentForm .supplier-payment-ref { grid-column: auto; }
  .wide-dialog #supplierPaymentForm .supplier-payment-bill-list { grid-template-columns: minmax(0, 1fr); }
}

/* ...and a height to go with the width (owner, 12 Sep 2026: "increase the
   height too because its looking like widthhh"). At 980 x 475 the card read as
   a letterbox; Add Transfer is 1000 x 660, which is the shape he is comparing
   it to.

   The height is NOT a min-height with a hole in it. The panel becomes a flex
   column, the rows space out through a larger gap, and the actions are pushed
   to the bottom edge with margin-top: auto - so whatever slack is left lands in
   ONE place, under the form and above the buttons, instead of opening a gap
   between two fields.

   min(620px, 100dvh - 48px) so a short laptop screen shrinks it rather than
   pushing the buttons off the bottom; the form scrolls inside itself if the
   bill list is long. */
.wide-dialog #supplierPaymentForm {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 26px;
  min-height: min(620px, calc(100dvh - 48px));
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
}
.wide-dialog #supplierPaymentForm .dialog-actions { margin-top: auto; }
/* NOT shrinkable. A flex child shrinks by default, and on a 640px-tall screen
   that squeezed the bill list from its own 210px down to 23px - a scroller two
   lines high, with 40 bills inside it. It keeps its natural height and the FORM
   scrolls instead when the two together do not fit. */
.wide-dialog #supplierPaymentForm .supplier-payment-bills { flex: 0 0 auto; }
.wide-dialog #supplierPaymentForm > * { flex: 0 0 auto; }
@media (max-width: 900px) {
  .wide-dialog #supplierPaymentForm {
    min-height: 0;
    gap: 16px;
    padding: 22px;
  }
}

/* Remaining amount on Add Payment: red when the payment is more than the
   balance (owner, 12 Sep 2026). It is not an error state - the dialog still
   saves, and a deliberate overpayment is a real thing a supplier account
   carries - so it colours the figure and does not block the button. */
.wide-dialog #supplierPaymentForm [name="remaining"] { font-weight: 700; }
.wide-dialog #supplierPaymentForm [name="remaining"].is-overpaid { color: #b42318; }

/* Display screen login switch on the staff form (14 Sep 2026): the checkbox
   and its words on one line, the explanation underneath like every other
   field-help on the form. */
.display-login-field .display-login-check { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; }
.display-login-field .display-login-check input[type="checkbox"] { width: 18px; height: 18px; min-height: 18px; margin: 0; }

/* Branch access on the staff form (15 Sep 2026): a select for the one-branch
   roles, tick boxes for the roles that take them, a fixed line for the rest.
   The role dropdown swaps them with .hidden, which needs these rules - the
   select's wrapper is a label, and `.settings-form label` is display:grid,
   which beats the bare utility. The checkbox needs its own min-height for
   the same reason as the display-login one above. */
.settings-form .employee-branch-one-field.hidden,
.settings-form .employee-branch-ticks-field.hidden,
.settings-form .employee-branch-note-field.hidden { display: none; }
.settings-form .employee-branch-ticks-field,
.settings-form .employee-branch-note-field { display: grid; gap: 6px; align-content: start; }
.settings-form .employee-branch-title { color: var(--muted); font-size: 12px; font-weight: 600; }
.settings-form .employee-branch-ticks { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; min-height: 42px; }
.settings-form .employee-branch-ticks .employee-branch-tick { display: inline-flex; align-items: center; gap: 8px; color: var(--ds-ink, #14213d); font-size: 13px; font-weight: 600; }
.settings-form .employee-branch-tick input[type="checkbox"] { width: 18px; height: 18px; min-height: 18px; margin: 0; }
.settings-form .employee-branch-note-field strong { display: flex; align-items: center; min-height: 42px; color: var(--ds-ink, #14213d); font-size: 13px; }
