:root {
  --navy-950: #0b1524;
  --navy-900: #101d33;
  --navy-800: #16283f;
  --accent: #2e8b74;      /* scan-green — used sparingly for action/success */
  --accent-dark: #22685a;
  --danger: #c0392b;
  --warn: #b8860b;
  --surface: #f5f7f9;
  --border: #dfe4ea;
}

* { box-sizing: border-box; }

body {
  background: var(--surface);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  overscroll-behavior-y: contain;
}

/* ---------- Navbar ---------- */
.app-navbar {
  background: var(--navy-950);
  min-height: 56px;
}
.app-navbar .navbar-brand { letter-spacing: 0.2px; }

/* ---------- Shell layout ---------- */
.app-shell {
  display: flex;
  min-height: calc(100vh - 56px);
}

.app-sidebar {
  width: 230px;
  flex-shrink: 0;
  background: var(--navy-900);
  color: #cfd8e3;
  position: sticky;
  top: 56px;
  height: calc(100vh - 56px);
  overflow-y: auto;
}
.app-sidebar .nav-link {
  color: #cfd8e3;
  padding: 0.65rem 0.9rem;
  border-radius: 8px;
  font-size: 0.95rem;
  margin-bottom: 2px;
}
.app-sidebar .nav-link i { width: 20px; display: inline-block; }
.app-sidebar .nav-link:hover { background: var(--navy-800); color: #fff; }
.app-sidebar .nav-link.active { background: var(--accent); color: #fff; font-weight: 600; }
.app-sidebar .nav-link.disabled { color: #5b6b80; cursor: default; }
.app-sidebar .nav-section-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #5b6b80;
  padding: 1rem 0.9rem 0.3rem;
}

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 56px 0 0 0;
  background: rgba(0,0,0,0.45);
  z-index: 1030;
}

.app-main {
  flex: 1;
  min-width: 0;
  padding: 1.25rem;
}

/* ---------- Mobile: sidebar becomes a slide-in drawer ---------- */
@media (max-width: 991.98px) {
  .app-sidebar {
    position: fixed;
    left: -240px;
    top: 56px;
    z-index: 1040;
    width: 230px;
    transition: left 0.2s ease;
    box-shadow: 4px 0 12px rgba(0,0,0,0.15);
  }
  .app-sidebar.open { left: 0; }
  .sidebar-backdrop.open { display: block; }
  .app-main { padding: 0.85rem; }
}

/* ---------- Cards / tables ---------- */
.card { border: 1px solid var(--border); border-radius: 10px; }
.table thead th {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #5b6b80;
  border-bottom-width: 1px;
  white-space: nowrap;
}
.table td { vertical-align: middle; }

.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-accent:hover { background: var(--accent-dark); border-color: var(--accent-dark); color: #fff; }

/* ---------- Mobile-friendly big controls ---------- */
@media (max-width: 575.98px) {
  .btn { min-height: 44px; }
  .form-control, .form-select { min-height: 44px; }
}

/* ---------- Search bar ---------- */
.search-bar .form-control:focus, .search-bar .form-select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 0.2rem rgba(46,139,116,0.15);
}

/* ---------- Status badges ---------- */
.badge { font-weight: 500; font-size: 0.75rem; }

/* ---------- Toast feedback ---------- */
.toast-container { z-index: 1080; }

/* ---------- Empty state ---------- */
.empty-state {
  text-align: center;
  padding: 3rem 1rem;
  color: #8b98a8;
}
.empty-state i { font-size: 2.5rem; opacity: 0.5; }

/* ---------- Login page ---------- */
.login-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, var(--navy-950), var(--navy-800));
  padding: 1rem;
}
.login-card {
  width: 100%;
  max-width: 380px;
  border-radius: 14px;
  border: none;
  box-shadow: 0 20px 50px rgba(0,0,0,0.35);
}

/* ---------------- Scanning screen ---------------- */
.scan-header-card {
  background: var(--navy-950);
  color: #fff;
  border-radius: 12px;
  padding: 1.25rem 1.5rem;
}
.scan-counter {
  font-size: 2.75rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
}
.scan-counter .scan-required { color: #8b98a8; font-weight: 600; }
.scan-progress {
  height: 10px;
  border-radius: 6px;
  background: rgba(255,255,255,0.12);
  overflow: hidden;
  margin-top: 0.75rem;
}
.scan-progress-bar {
  height: 100%;
  background: var(--accent);
  transition: width 0.25s ease;
}

.scan-hidden-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  height: 1px;
  width: 1px;
}
.scan-visible-input {
  font-size: 1.25rem;
  padding: 0.9rem 1rem;
  text-align: center;
  letter-spacing: 0.02em;
  border: 2px dashed var(--border);
}
.scan-visible-input:focus {
  border-color: var(--accent);
  border-style: solid;
}

.scan-feedback {
  min-height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.1rem;
  border-radius: 8px;
  padding: 0.5rem 1rem;
  transition: transform 0.15s ease;
}
.scan-flash-success { background: #e6f4ef; color: var(--accent-dark); }
.scan-flash-error { background: #fbe9e7; color: var(--danger); }
.scan-flash-active { transform: scale(1.02); }

.scan-recent-item {
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.scan-recent-item:last-child { border-bottom: none; }

.offline-banner {
  background: var(--warn);
  color: #fff;
  padding: 0.5rem 1rem;
  border-radius: 8px;
  font-weight: 600;
  font-size: 0.85rem;
  text-align: center;
}

/* ---------------- Camera scanning ----------------
   The viewfinder is deliberately SHORT AND WIDE. A barcode is a wide, short
   thing, and a tall or square window invites the user to hold the phone too
   far back -- at which point the bars are too few pixels apart to decode.
   The dark mask outside the window is not decoration: only the bright band in
   the middle is actually sent to the decoder, so the frame is an honest
   picture of what the software is looking at. */

.scan-stage {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  background: #000;
  line-height: 0;
}

.camera-video {
  width: 100%;
  /* Shorter than the old 320px: the decoded band is in the middle, and a tall
     picture only encourages aiming from further away. */
  height: 240px;
  object-fit: cover;
  background: #000;
  display: block;
}

.scan-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* The window itself: 86% wide, 30% tall -- the same proportions the decoder
   crops to in camera-scan.js (SCAN_ROI). Change one without the other and the
   box stops telling the truth. */
.scan-frame-window {
  position: absolute;
  left: 7%;
  right: 7%;
  top: 35%;
  height: 30%;
  border-radius: 6px;
  /* Everything outside the window dimmed, in one property, with no extra
     elements to keep aligned. */
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55);
}

.scan-corner {
  position: absolute;
  width: 18px;
  height: 18px;
  border: 3px solid var(--accent, #2e8b74);
}
.scan-corner.tl { top: -2px; left: -2px;  border-right: 0; border-bottom: 0; border-radius: 6px 0 0 0; }
.scan-corner.tr { top: -2px; right: -2px; border-left: 0;  border-bottom: 0; border-radius: 0 6px 0 0; }
.scan-corner.bl { bottom: -2px; left: -2px;  border-right: 0; border-top: 0; border-radius: 0 0 0 6px; }
.scan-corner.br { bottom: -2px; right: -2px; border-left: 0;  border-top: 0; border-radius: 0 0 6px 0; }

.scan-laser {
  position: absolute;
  left: 6px;
  right: 6px;
  top: 50%;
  height: 2px;
  background: rgba(255, 60, 60, .85);
  box-shadow: 0 0 6px rgba(255, 60, 60, .9);
  animation: scanSweep 1.8s ease-in-out infinite;
}

@keyframes scanSweep {
  0%, 100% { transform: translateY(-14px); opacity: .35; }
  50%      { transform: translateY(14px);  opacity: 1; }
}

/* A phone held still with a moving line on screen is doing something; without
   it people assume the camera has frozen. Respect the system setting though. */
@media (prefers-reduced-motion: reduce) {
  .scan-laser { animation: none; }
}

.scan-frame-hint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 8px;
  text-align: center;
  color: #fff;
  font-size: .75rem;
  line-height: 1.2;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}

/* Torch. Sits inside the picture, thumb-reachable, and lights up when on. */
.scan-torch {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  border-radius: 50%;
  width: 38px;
  height: 38px;
  padding: 0;
  line-height: 1;
  opacity: .85;
}
.scan-torch.active {
  background: #ffc107;
  border-color: #ffc107;
  color: #212529;
  opacity: 1;
}

.scan-diag {
  white-space: pre-wrap;
  font-size: .72rem;
  line-height: 1.35;
  max-height: 220px;
  overflow: auto;
}

/* ---------------- Print (dispatch slip) ---------------- */
@media print {
  .app-navbar, .app-sidebar, .sidebar-backdrop, .no-print { display: none !important; }
  .app-main { padding: 0 !important; }
  body { background: #fff !important; }
}

/* ---------------------------------------------------------------
   PRINTED DOCUMENTS (9.30): the Proforma Invoice and the Dispatch Sheet.

   PREVIEW = PRINT = PDF. The document sits on a sheet exactly 190mm wide:
   an A4 page (210mm) less 10mm margins each side, which is what @page gives
   the printer below. So the screen, the paper and the PDF (a picture of this
   same sheet, assets/js/doc-pdf.js) all lay the document out at one width.

   It used to print differently from the screen because an A4 page is
   narrower than the phone breakpoint: every table on the page turned into
   stacked mobile tiles on paper. Tables inside .doc-paper are left alone by
   assets/js/responsive-tables.js.
   --------------------------------------------------------------- */
.doc-frame { overflow-x: auto; background: #eef0f3; border-radius: .5rem; padding: 16px; }
.doc-page  { width: 210mm; margin: 0 auto; background: #fff; padding: 10mm;
             box-shadow: 0 1px 6px rgba(0,0,0,.12); }
.doc-paper { width: 190mm; background: #fff; color: #000; }
.doc-paper table { width: 100%; }

@media print {
  @page { size: A4 portrait; margin: 10mm; }
  .doc-frame { background: none; padding: 0; overflow: visible; border-radius: 0; }
  .doc-page  { width: auto; padding: 0; box-shadow: none; }
  .doc-paper th, .doc-paper td, .doc-paper th * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* The card a sheet sits in on screen is not part of the paper. */
  .doc-editable, .doc-editable > .card-body { border: 0 !important; padding: 0 !important; box-shadow: none !important; }
}


/* ---------------------------------------------------------------
   Searchable selectors (supplier / dealer / product / brand)

   The menu is rendered into #ssMenuLayer on <body> and positioned with
   position:fixed, so no table cell, card or modal can clip it. Its size and
   place are set in JS against visualViewport, which is the only measurement
   that is correct while a phone keyboard is up.
   --------------------------------------------------------------- */
.ss-wrap { position: relative; width: 100%; }

.ss-input { padding-right: 2rem; }

.ss-clear {
  position: absolute;
  right: .5rem;
  top: 50%;
  transform: translateY(-50%);
  border: 0;
  background: none;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--muted, #6c757d);
  padding: 0 .15rem;
  cursor: pointer;
}
.ss-clear:hover { color: var(--danger, #dc3545); }

#ssMenuLayer { position: absolute; top: 0; left: 0; }

.ss-menu {
  position: fixed;
  z-index: 2000;               /* above modals (1055) and their backdrop */
  overflow-y: auto;
  background: var(--card-bg, #fff);
  border: 1px solid var(--border, #dee2e6);
  border-radius: .5rem;
  box-shadow: 0 .5rem 1.25rem rgba(0,0,0,.22);
  -webkit-overflow-scrolling: touch;
}

.ss-item {
  padding: .55rem .75rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border, #eee);
  line-height: 1.25;
}
.ss-item:last-child { border-bottom: 0; }
.ss-item.active,
.ss-item:hover { background: var(--accent, #0d6efd); color: #fff; }
.ss-item.active .ss-sub,
.ss-item:hover .ss-sub { color: rgba(255,255,255,.75); }

.ss-label { display: block; font-weight: 500; }
/* 9.29: model on the left, stock on the right, on one line. */
.ss-line { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.ss-line .ss-label { font-weight: 700; min-width: 0; overflow-wrap: anywhere; }
.ss-badge { flex-shrink: 0; font-weight: 700; font-size: .85rem; white-space: nowrap; color: #198754; }
.ss-badge-zero { color: #dc3545; }
.ss-item.active .ss-badge,
.ss-item:hover .ss-badge { color: #fff; }
.ss-sub {
  display: block;
  font-size: .78rem;
  color: var(--muted, #6c757d);
}

.ss-empty, .ss-more {
  padding: .6rem .75rem;
  font-size: .85rem;
  color: var(--muted, #6c757d);
  text-align: center;
}

/* Comfortable tap targets on a phone. */
@media (max-width: 767.98px) {
  .ss-item { padding: .7rem .75rem; }
}

/* ---------------------------------------------------------------
   Item rows on Purchase Inward / Dispatch / Preparation.

   One line per product on a tablet and up, exactly as specified. Below
   576px a single line genuinely cannot hold a product search box, a
   quantity and a rate -- the product box came out 62px wide, showing the
   word "Type" and nothing else. So each item becomes one bordered block:
   the product on its own full-width row, quantity and rate side by side
   beneath it. It still reads as one item, and nothing is shrunk.
   --------------------------------------------------------------- */
.item-add-btn {
  width: 2.25rem; height: 2.25rem;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.15rem; line-height: 1; padding: 0;
}

.item-table th { white-space: nowrap; }
.item-table td { vertical-align: middle; }
.item-table .col-qty  { width: 7.5rem; }
.item-table .col-rate { width: 9.5rem; }
.item-table .col-del  { width: 3rem; }

@media (max-width: 575.98px) {
  .item-table thead { display: none; }
  .item-table, .item-table tbody, .item-table td { display: block; width: 100%; }

  /* flex, not inline-block: two inline-blocks at 50% each are pushed onto
     separate lines by the whitespace between the cells. */
  .item-table tr {
    display: flex;
    flex-wrap: wrap;
    gap: 0 .6rem;
    border: 1px solid var(--border, #dee2e6);
    border-radius: .5rem;
    padding: .6rem .6rem .2rem;
    margin-bottom: .6rem;
    position: relative;
  }
  .item-table td { border: 0; padding: 0 0 .5rem; }
  .item-table td.col-product { flex: 0 0 100%; padding-right: 2.25rem; }
  /* quantity and rate share the row beneath the product */
  .item-table td.col-qty,
  .item-table td.col-rate { flex: 1 1 0; min-width: 0; width: auto; }
  .item-table td.col-del {
    position: absolute;
    top: .35rem; right: .35rem;
    width: auto; padding: 0;
  }
  /* the label each field lost when the header row was hidden */
  .item-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--muted, #6c757d);
    margin-bottom: .15rem;
  }
}

/* ---------------------------------------------------------------
   Dashboard cards are links: every number opens its report.
   --------------------------------------------------------------- */
.dash-card { color: inherit; transition: box-shadow .15s ease, transform .15s ease; }
.dash-card:hover {
  box-shadow: 0 .35rem .75rem rgba(0,0,0,.10);
  transform: translateY(-1px);
  border-color: var(--accent);
}
.dash-card:active { transform: none; }

/* Stock Summary identity: three columns on a tablet and up; on a phone the
   category and model fold into the brand cell so nothing is lost. */
.stock-identity .stock-inline { display: block; font-size: .8rem; font-weight: 400; }

/* The menu button was 25px wide — too narrow to hit reliably with a thumb.
   44px is the usual minimum tap target. */
#sidebarToggle { min-width: 44px; }

/* ==========================================================
   COMPANY COLOUR
   ----------------------------------------------------------
   The palette itself is set as :root custom properties by
   includes/header.php from the active company's theme, so every
   rule that already used --navy-950 / --accent follows along and
   nothing here needs a per-company variant.

   The classes below are the parts that exist ONLY to make the
   current company unmistakable.
   ========================================================== */

/* The dot in the switcher, and beside each company in its menu. */
.company-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
  box-shadow: 0 0 0 2px rgba(255,255,255,0.25);
}
.company-chip { display: inline-flex; align-items: center; gap: 0.45rem; }

/* A colour swatch in the company form, so the choice is made by looking
   rather than by reading a colour's name. */
.theme-swatches { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.theme-swatch {
  border: 2px solid transparent;
  border-radius: 8px;
  padding: 4px;
  cursor: pointer;
  background: none;
  line-height: 0;
}
.theme-swatch.selected { border-color: #212529; }
.theme-swatch span {
  display: block;
  width: 34px;
  height: 22px;
  border-radius: 5px;
}


/* ---------------- Recently Scanned ----------------
   Sits under the scanner on a phone, so it must stay compact and must never
   push the scan box off the screen. Newest row first, and the whole list
   scrolls inside itself rather than growing the page. */

.recent-scan-wrap {
  max-height: 320px;
  overflow-y: auto;
}

.recent-scan-table {
  font-size: .85rem;
}

.recent-scan-table td,
.recent-scan-table th {
  padding: .35rem .5rem;
  vertical-align: middle;
}

/* The newest row, briefly highlighted, so a fast scanner can see its last
   read land without reading the whole list. */
@keyframes recentFlash {
  from { background: rgba(46, 139, 116, .35); }
  to   { background: transparent; }
}
.recent-scan-new {
  animation: recentFlash 1.2s ease-out 1;
}

.recent-remove {
  --bs-btn-padding-y: .05rem;
  --bs-btn-padding-x: .35rem;
  --bs-btn-font-size: .75rem;
}


/* ---------------- Stock Summary figure cards ----------------
   Four numbers, each a way in to the serial numbers behind it. They have to
   look pressable, because a figure that opens something and does not say so
   is a figure nobody clicks. */

.stock-figure-card {
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}

.stock-figure-card:hover,
.stock-figure-card:focus-visible {
  transform: translateY(-2px);
  border-color: var(--accent, #2e8b74);
  box-shadow: 0 .4rem .9rem rgba(0, 0, 0, .10);
}

.stock-figure-card .display-6 {
  line-height: 1.1;
  /* A six-figure stock count must not wrap onto two lines on a phone. */
  font-size: clamp(1.6rem, 7vw, 2.4rem);
}

@media (prefers-reduced-motion: reduce) {
  .stock-figure-card { transition: none; }
  .stock-figure-card:hover { transform: none; }
}

/* ==========================================================================
   9.8 — LISTS: TIGHTER ROWS, A STICKY SELECTION BAR, AND NO SIDEWAYS SCROLL
   ========================================================================== */

/* ---- Row spacing --------------------------------------------------------
   The default cell padding wastes a third of a phone screen on white space.
   Six rows should fit where four did, without the text getting smaller. */
.table > :not(caption) > * > * {
  padding-top: .4rem;
  padding-bottom: .4rem;
}

.table-sm > :not(caption) > * > * {
  padding-top: .3rem;
  padding-bottom: .3rem;
}

/* The two injected columns are narrow and must never grow. */
.cell-compact {
  width: 1%;
  white-space: nowrap;
}

/* ---- The selection bar --------------------------------------------------
   FIXED ACROSS THE BOTTOM of the screen, one line, on every screen size.

   It used to sit in the page above the table, and appearing pushed the whole
   list down by its own height — the row you had just tapped moved, and the
   next one slid under your finger. Fixed, it takes no space in the layout at
   all, so ticking a row moves nothing; and it is always reachable, however far
   down a long list you have scrolled.

   On desktop it starts after the side menu rather than covering it. On a phone
   the menu is a drawer, so it spans the full width. */
.selection-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1035;                 /* over the page, under a modal (1055) */
  background: #fff;
  border-top: 1px solid var(--bs-border-color, #dee2e6);
  box-shadow: 0 -.25rem .75rem rgba(0, 0, 0, .12);
  padding: .5rem .85rem;
  /* Clear of the home indicator on an iPhone. */
  padding-bottom: calc(.5rem + env(safe-area-inset-bottom, 0px));
}

.selection-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  /* ONE LINE. The count truncates before the buttons are allowed to wrap —
     losing a word of "3 product records selected" costs nothing, losing the
     Delete button costs the whole interaction. */
  flex-wrap: nowrap;
  max-width: 1320px;
  margin: 0 auto;
}

.selection-bar-count {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.selection-bar-actions {
  display: flex;
  gap: .5rem;
  flex-shrink: 0;
}

/* The page gets a foot deep enough for the bar to sit over, so the last row of
   a list is still readable while it is showing. */
body.has-selection-bar .app-main {
  padding-bottom: 5rem;
}

@media (min-width: 992px) {
  /* Start after the side menu instead of covering it. */
  .selection-bar {
    left: 230px;
  }
}

/* ---- The "Show Deleted" toggle -----------------------------------------
   The icon and the word sit on a shared centre line. Left to the browser they
   align on the text baseline, which drops the icon a pixel or two and makes
   the button look badly made. */
.btn-check + .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
}

.btn-check + .btn i {
  line-height: 1;
}

/* ---- Pagination ---------------------------------------------------------
   Record numbers on the left, page numbers and chevrons on the right. */
.pagination-controls {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.page-arrow,
.page-number {
  --bs-btn-padding-y: .15rem;
  --bs-btn-padding-x: .5rem;
  --bs-btn-font-size: .8rem;
  min-width: 2rem;
}

.page-arrow i {
  line-height: 1;
  vertical-align: middle;
}

.page-gap {
  color: #adb5bd;
  padding: 0 .15rem;
  font-size: .8rem;
}

.pagination-count {
  white-space: nowrap;
}

/* ==========================================================================
   NO HORIZONTAL SCROLLING
   --------------------------------------------------------------------------
   Below the phone breakpoint every table becomes a list of TILES — one tile
   per row, laid out the way somebody reads a record rather than the way a
   spreadsheet stores one:

       [tick] [#3]  MASTERCOOL 30                          [edit]
              SYMPHONY · COOLER · Rs.8,000

   A headline, and everything else on one quiet line underneath. Far more
   legible than four rows of LABEL / value, and three or four records fit on a
   phone screen instead of one and a half.

   Which cell becomes the headline, and which are dropped for being empty, is
   decided per row in assets/js/responsive-tables.js — see the note at the top
   of that file for why that cannot be done in CSS.
   ========================================================================== */

@media (max-width: 767.98px) {

  /* The wrapper stops being a scroller. This is the actual fix; everything
     below is what makes the content fit once it can no longer slide.
     NOT overflow-x:hidden on html/body: with overflow-y visible the browser
     promotes overflow-y to auto, body becomes a scroll container, and the
     sticky top bar stops sticking. Tiling removes the overflow at source, so
     there is nothing left to hide. */
  .table-responsive {
    overflow-x: visible;
  }

  .table-stack,
  .table-stack tbody {
    display: block;
    width: 100%;
  }

  /* The headings live in the tiles now (as tooltips), so the header row goes. */
  .table-stack thead {
    display: none;
  }

  /* The tile. A wrapping flex row: the tick box, the row number, the headline
     and the buttons share the first line; everything else flows onto the
     second. */
  .table-stack tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: .4rem;
    row-gap: 0;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: .375rem;
    margin-bottom: .25rem;
    padding: .25rem .5rem;
    background: #fff;
  }

  .table-stack tbody tr.table-danger {
    background: var(--bs-danger-bg-subtle, #f8d7da);
  }

  /* Striping and hover assume rows are rows, not tiles. */
  .table-stack.table-hover > tbody > tr:hover > *,
  .table-stack.table-striped > tbody > tr > * {
    background: transparent;
  }

  .table-stack td {
    display: inline;
    width: auto;
    border: 0 !important;
    padding: 0 !important;
    text-align: left !important;
    white-space: normal;
    overflow-wrap: break-word;
  }

  /* Badges in a tile are decoration, not headings. */
  .table-stack td .badge {
    font-size: .65rem;
    padding: .2em .45em;
    vertical-align: middle;
  }

  /* The label is a tooltip on the cell, not text in front of it. */
  .table-stack td::before {
    content: none;
  }

  /* Nothing to show. Dropped rather than rendered as a dash: a dash lines up
     under a heading in a table and means something; in a run-on line it is
     just noise. */
  .table-stack td.cell-empty {
    display: none;
  }

  /* The tick box and the row number lead the first line. */
  .table-stack td.cell-compact {
    flex: 0 0 auto;
    order: 0;
    display: inline-flex;
    align-items: center;
    color: #6c757d;
    font-size: .75rem;
  }

  /* A tick box scaled to the line it sits on. */
  .table-stack td.cell-compact .form-check-input {
    width: 1rem;
    height: 1rem;
    margin: 0;
  }

  /* The headline: the one thing the eye should land on per tile.
     0 1 auto, NOT 1 1 auto — it takes the width of its own text and shrinks
     only if it has to. The growing item on this line has to be the actions
     cell; see the note there. */
  .table-stack td.cell-title {
    flex: 0 1 auto;
    order: 1;
    min-width: 0;
    font-weight: 600;
    font-size: .875rem;
    line-height: 1.25;
    color: var(--bs-body-color, #212529);
  }

  /* The buttons finish the first line, hard right. flex-grow pushes them to
     the edge. */
  .table-stack td.cell-actions {
    flex: 1 1 auto;
    order: 2;
    min-width: 0;
    text-align: right !important;
    /* Its own line box, rather than the table's 1rem one, which added a few
       pixels of nothing to every tile. */
    font-size: .8rem;
    line-height: 1;
  }

  /* A FORCED LINE BREAK between the headline and the detail line.
     Neither margin-left:auto nor flex-grow can do this. Flexbox decides which
     items share a line from their CONTENT widths, and only then hands out the
     spare space — so a short detail like "COOLER" would still slot in beside
     the buttons, stranding the rest of the detail on a line of its own and
     leaving the pencil marooned in the middle of the tile.
     A pseudo-element on the row IS a flex item, and one that demands the full
     width has to start a new line. Zero height, so it costs nothing. */
  .table-stack tbody tr::after {
    content: "";
    flex: 0 0 100%;
    height: 0;
    order: 3;
  }

  /* THE BUTTONS SET THE HEIGHT OF THE WHOLE TILE, so they are sized to the
     text beside them rather than towering over it.
     
     This overrides the app's 44px minimum tap target — deliberately, and only
     for the small icon buttons inside a list row. 44px is the right size for
     a button somebody has to find and press (Add Product, Delete, Complete);
     applied to an edit icon repeated on every row it forced every tile to be
     at least 55px tall, which is what made a list of six products fill the
     screen. 32px is still a comfortable target for a secondary action sitting
     in its own corner of the tile, and it is not shrunk further than that. */
  .table-stack td.cell-actions .btn {
    --bs-btn-padding-y: .1rem;
    --bs-btn-padding-x: .45rem;
    --bs-btn-font-size: .8rem;
    --bs-btn-line-height: 1.1;
    min-height: 32px;
    min-width: 32px;
  }

  .table-stack td.cell-actions .btn i {
    line-height: 1;
    vertical-align: middle;
  }

  /* The detail line: everything else, quiet, running on together.
     order 4 — behind the forced break above. */
  .table-stack td.cell-meta {
    flex: 0 0 auto;
    order: 4;
    font-size: .75rem;
    line-height: 1.25;
    color: #6c757d;
  }

  /* A middot between the details, never before the first of them. */
  .table-stack td.cell-meta ~ td.cell-meta::after {
    content: none;
  }
  .table-stack td.cell-meta + td.cell-meta {
    padding-left: .1rem !important;
  }
  .table-stack td.cell-meta + td.cell-meta::before {
    content: "·";
    display: inline;
    margin-right: .45rem;
    color: #adb5bd;
  }

  /* Full-width cells — empty states, error messages, the loading row. */
  .table-stack td[colspan] {
    flex: 0 0 100%;
    order: 5;
    display: block;
    text-align: center !important;
    padding: .5rem 0 !important;
  }

  /* Nothing inside a cell may reintroduce the overflow tiling just removed. */
  .table-stack td > * {
    max-width: 100%;
  }

  .table-stack td.text-nowrap {
    white-space: normal;
  }

  .table-stack .scan-progress {
    min-width: 0;
  }

  /* A progress bar or badge sitting in the detail line should not tower over
     the text beside it. */
  .table-stack td.cell-meta .badge {
    font-size: .7rem;
    vertical-align: baseline;
  }

  /* Filter bars and button groups wrap instead of overflowing. */
  .btn-group {
    flex-wrap: wrap;
  }
}

/* ==========================================================================
   9.8 — THE TOP BAR ON A PHONE
   --------------------------------------------------------------------------
   Everything on ONE line, on the narrowest phone we support. The company chip
   is the only piece allowed to give way: a long name is truncated with an
   ellipsis rather than wrapping the bar onto a second row. The full name stays
   readable in the button's tooltip and in the menu it opens, so nothing is
   lost — and the menu button and the scan icon never move, because a control
   that has shifted is a control nobody finds.
   ========================================================================== */

.company-chip-wrap {
  /* Shrinkable, and allowed to shrink below its content width — without
     min-width:0 a flex item refuses to go narrower than its text, which is
     exactly what pushes the bar onto two lines.

     NO overflow:hidden HERE. This element is the Bootstrap .dropdown, so the
     company menu is rendered INSIDE it — hiding the overflow clipped the menu
     out of existence and the company could not be switched at all. The name is
     truncated on .company-chip-name below, which has no menu in it. */
  min-width: 0;
  flex: 0 1 auto;
}

.company-chip {
  max-width: 100%;
}

.company-chip-name {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

@media (max-width: 575.98px) {
  /* On a small phone the brand is short (IMS), so the company name can have
     most of what is left. */
  .company-chip-name {
    max-width: 40vw;
  }

  .app-navbar .navbar-brand {
    font-size: 1rem;
  }

  .app-navbar .company-chip {
    --bs-btn-padding-x: .45rem;
    font-size: .8rem;
  }
}


/* ---- The confirmation box ------------------------------------------------
   A small box on EVERY screen size.

   Bootstrap's .modal-sm caps the width at 300px, but that cap is declared
   inside a min-width:576px media query — so on a phone it did nothing and a
   one-line yes/no question filled the whole display. This cap has no
   breakpoint, and stays comfortably inside the narrowest phone. */
.modal-confirm {
  max-width: 320px;
  width: calc(100% - 2rem);
  margin-left: auto;
  margin-right: auto;
}

.modal-confirm .modal-content {
  border-radius: .75rem;
}


/* A menu item that belongs to the one above it — Purchase Report under
   Purchase Inward, Dealer Report under Dealer Dispatch, Returns History under
   Returns. Indented and a shade quieter, so the pairing is visible without
   needing a heading of its own. */
.app-sidebar .nav-link.nav-sub {
  padding-left: 2rem;
  font-size: 0.88rem;
}

.app-sidebar .nav-link.nav-sub i {
  opacity: .75;
}


/* ---- Rows per page ------------------------------------------------------
   Sits where the record count used to, on the left of the pagination bar.

   The count said "1 record", which answers a question nobody had; this
   answers the one they did — how much of the list do I want in front of me.
   "All" is one of the choices, and the server caps what that can fetch. */
.pagination-size {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  white-space: nowrap;
}

.pagination-size .form-select {
  width: auto;
  min-width: 4.5rem;
  padding-top: .15rem;
  padding-bottom: .15rem;
  font-size: .8rem;
}

/* ---- A row that opens something ----------------------------------------
   Purchase Inward and Dealer Dispatch no longer carry a "Continue Scanning"
   button at the right-hand end: the whole row is the button. That has to LOOK
   tappable, or it is just a table that mysteriously navigates.

   The tick box inside it stops the click itself (see txn-select.js), so
   selecting a row never opens it. */
.row-link {
  cursor: pointer;
}

.row-link:focus-visible {
  outline: 2px solid var(--bs-primary, #0d6efd);
  outline-offset: -2px;
}

/* The tick box keeps the ordinary arrow: it does something different from the
   rest of the row, and the cursor is the only warning of that. */
.row-link .form-check-input {
  cursor: pointer;
}


/* ---- The scan screen, two columns --------------------------------------
   Scanning on the left, the order on the right, on anything wider than a
   phone. The cards inside keep their own bottom margin; the LAST one in each
   column does not, or the row's gutter is doubled underneath it. */
.scan-layout > [class*="col-"] > :last-child {
  margin-bottom: 0 !important;
}

/* A Bootstrap row stretches its columns to equal height. The order panel
   should be as tall as its own contents, not as tall as the scanner beside
   it, so it is pinned to the top of the column rather than filled out. */
@media (min-width: 1200px) {
  .scan-layout > [class*="col-"] {
    align-self: flex-start;
  }
}


/* ---- A stock figure you can open ---------------------------------------
   On the drill-down, Dispatched and Returned narrow the movements below to
   the documents that produced them. They are real <button>s, so they are
   reachable from the keyboard; these rules stop them looking like form
   buttons and make the one in force obvious. */
.stock-figure-btn {
  background: #fff;
  cursor: pointer;
  box-shadow: var(--bs-box-shadow-sm, 0 .125rem .25rem rgba(0,0,0,.075));
  border: 1px solid var(--bs-border-color, #dee2e6) !important;
  border-radius: var(--bs-border-radius, .375rem);
}

.stock-figure-btn:hover {
  border-color: var(--bs-primary, #0d6efd) !important;
}

.stock-figure-btn.active {
  border-color: var(--bs-primary, #0d6efd) !important;
  box-shadow: 0 0 0 .15rem rgba(13, 110, 253, .2);
}


/* ---- A stock figure that opens its documents ---------------------------
   On the Stock Summary the Inward, Dispatched and Returned numbers are real
   <button>s — reachable from the keyboard, and obviously pressable — but they
   sit in a table of figures and must not look like form buttons. */
.figure-link {
  text-decoration: underline;
  text-underline-offset: .15em;
  vertical-align: baseline;
}


/* ---- The notifications offer -------------------------------------------
   A bar at the foot of the screen on a phone, above the home indicator. It is
   an offer with two answers, not a dialog: nothing is blocked behind it and
   whatever the user was doing is still reachable. */
.push-offer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1040;
  background: #0f2537;
  color: #fff;
  padding: .75rem .85rem;
  padding-bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -.25rem .75rem rgba(0, 0, 0, .25);
}

.push-offer-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  max-width: 1320px;
  margin: 0 auto;
}

.push-offer-text {
  min-width: 0;
}

.push-offer-text .small {
  opacity: .75;
}

.push-offer-actions {
  display: flex;
  gap: .5rem;
  flex-shrink: 0;
}


/* ---------------------------------------------------------------
   ROWS THAT OPEN IN PLACE (9.54)

   A report row whose detail belongs to the row itself -- the serial
   numbers on a dispatch line -- opens underneath it rather than on a
   screen of its own. Leaving the screen to read four serial numbers
   loses the search, the period and your place in a long list.
   --------------------------------------------------------------- */
tr.row-expandable { cursor: pointer; }
tr.row-expandable:hover { background: rgba(0, 0, 0, .035); }
tr.row-expandable .row-caret { transition: transform .15s ease-out; display: inline-block; }
tr.row-expandable.row-open .row-caret { transform: rotate(180deg); }
tr.row-expandable:focus-visible { outline: 2px solid var(--bs-primary, #0d6efd); outline-offset: -2px; }

tr.row-detail > td { background: #f8f9fa; border-top: 0; }
.serial-panel { padding: .25rem .25rem .5rem; }
.serial-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: .25rem .75rem;
}
.serial-grid code { font-size: .82rem; color: #b3306b; }

@media print {
  tr.row-detail { display: table-row !important; }
  tr.row-detail > td { background: none; }
  .row-caret { display: none !important; }
}

/* The Stock Summary's figure panel uses the same row-detail styling as the
   dispatch history, so a panel that opens under a row looks the same wherever
   it is. (9.55) */
tr.stock-detail-row > td { background: #f8f9fa; border-top: 0; }
tr.row-open > td { background: rgba(13, 110, 253, .06); }

/* ---------------------------------------------------------------
   9.85: DRAGGING A ROW INTO PLACE.

   touch-action:none on the handle is what makes this work on a tablet:
   without it the browser takes the finger for scrolling and the row never
   moves. The handle is the only part that grabs -- the rest of the row
   still scrolls and still opens the dispatch.
   --------------------------------------------------------------- */
.drag-grip {
  cursor: grab;
  touch-action: none;
  line-height: 1;
}
.drag-grip:active { cursor: grabbing; }
tr.dragging {
  opacity: .55;
  background: var(--surface, #f5f7f9);
}
