:root { color-scheme: light; }  /* 2026-10-27 (Ali): never follow the phone's dark mode */
/* ---------------------------------------------------------------
   Admin panel — flat, quiet back-office visual language.
   White/light surfaces, hairline borders, one accent color used
   sparingly, sentence case everywhere, tables read like a
   spreadsheet rather than a consumer app.
------------------------------------------------------------------ */

:root {
  --bg: #FAFAF9;
  --surface: #FFFFFF;
  --border: #E3E2DE;
  --border-strong: #CFCDC7;
  --text: #1E211F;
  --text-muted: #6B6E69;
  --text-faint: #9A9C97;
  --accent: #1F6F5C;
  --accent-hover: #185A4A;
  --accent-tint: #EAF3F0;
  --danger: #B3432C;
  --danger-tint: #FBEEEA;
  --warn: #9A6B1E;
  --warn-tint: #FBF3E4;
  --radius: 6px;
  --shadow-modal: 0 12px 32px rgba(20, 22, 20, 0.16);
  font-variant-numeric: tabular-nums;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

button {
  font-family: inherit;
  font-size: 13px;
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.btn:hover { background: #F3F2EF; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-block { width: 100%; }
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-secondary { background: var(--surface); }
/* Handoff: the 3 product-creation action buttons stay a plain white/
   secondary look at rest -- this marks whichever one the person is
   actively working through (New single item / New multiple items /
   Add color) as a real, distinct visual state, reusing the same
   accent green already used for "completed"/active states elsewhere
   in this file (.status-completed) rather than inventing a new shade.
   Applied/cleared via setActiveProductActionButton in app.js. */
.btn-action-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-action-active:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-danger {
  background: var(--surface);
  color: var(--danger);
  border-color: var(--danger);
}
.btn-danger:hover { background: var(--danger-tint); }
.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.btn-ghost:hover { background: #F0EFEC; }
.btn-sm { padding: 4px 10px; font-size: 12px; }

/* ---------- login screen ---------- */
.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}
.login-card {
  width: 340px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.login-card h1 { font-size: 18px; margin: 0; }
.login-sub { margin: -8px 0 4px; color: var(--text-muted); font-size: 13px; }
.login-card label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--text-muted);
}
.login-card input {
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  background: var(--surface);
}
.login-card input:focus, button:focus-visible, a:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.form-error {
  background: var(--danger-tint);
  color: var(--danger);
  padding: 8px 10px;
  border-radius: var(--radius);
  font-size: 12px;
  margin: 0;
}
/* Real, reusable "this specific field has a problem" marker -- red
   border on the actual input/select/textarea, not just a generic
   message elsewhere on the page. Applied/cleared via the shared
   markFieldError/clearFieldErrors helpers (see app.js) so every form
   uses the same real mechanism rather than each screen inventing its
   own version. */
.field-error {
  border-color: var(--danger) !important;
  box-shadow: 0 0 0 1px var(--danger);
}
.login-config {
  margin-top: 4px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}
.login-config summary {
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
}
.login-config label { margin-top: 8px; }

/* ---------- app shell ---------- */
.app-shell {
  display: flex;
  min-height: 100vh;
}
.sidebar {
  width: 200px;
  flex-shrink: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}
.sidebar-brand {
  padding: 18px 20px;
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  border-bottom: 1px solid var(--border);
}
.sidebar-nav {
  list-style: none;
  margin: 0;
  padding: 8px;
  flex: 1;
  overflow-y: auto;
}
.sidebar-nav li { margin: 1px 0; }
.sidebar-nav a {
  display: block;
  padding: 8px 12px;
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 13px;
}
.sidebar-nav a:hover { background: #F3F2EF; text-decoration: none; color: var(--text); }
.sidebar-nav a.active {
  background: var(--accent-tint);
  color: var(--accent);
  font-weight: 600;
}
/* Grouped menu: a group header you can open/close, its tabs indented below it. */
.nav-group-head {
  display: flex; align-items: center; width: 100%; gap: 6px;
  padding: 8px 12px; margin-top: 6px;
  background: none; border: 0; border-radius: var(--radius); cursor: pointer;
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-muted); text-align: left;
}
.nav-group-head:hover { background: #F3F2EF; color: var(--text); }
.nav-group-title { flex: 1; }
.nav-chevron { width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform .15s; margin-right: 2px; }
.nav-group.open > .nav-group-head .nav-chevron { transform: rotate(45deg); }
.nav-group.has-active > .nav-group-head { color: var(--accent); }
.nav-sub { list-style: none; margin: 0; padding: 0 0 0 6px; display: none; }
.nav-group.open > .nav-sub { display: block; }
.nav-sub a { padding-left: 14px; }
.nav-group-badge { margin-left: 0; }
.nav-group.open > .nav-group-head .nav-group-badge { display: none; }

/* "Back" bar at the top-left of a form opened inside a page (e.g. Products). */
.subview-back { margin: 0 0 12px; }

.sidebar-footer {
  border-top: 1px solid var(--border);
  padding: 12px;
}
.sidebar-user {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  margin-bottom: 8px;
  color: var(--text-muted);
}
.role-pill {
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 11px;
  text-transform: lowercase;
}

.content {
  flex: 1;
  padding: 28px 36px 60px;
  /* Handoff -- real fluid width, confirmed directly with Ali: this
     used to cap at max-width: 1200px regardless of how wide the
     browser window actually was, wasting real screen space on wider
     monitors. Now genuinely full-width and responsive -- grows and
     shrinks with the window instead of stopping at a fixed cap. */
  width: 100%;
  min-width: 0; /* lets flex children (e.g. wide tables) shrink/scroll instead of overflowing the layout */
}

/* ---------- page header ---------- */
.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 14px;
  gap: 16px;
}
.page-header h1 { font-size: 18px; margin: 0 0 4px; }
.page-header .page-sub { color: var(--text-muted); font-size: 13px; margin: 0; }
.page-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* ---------- metric cards ---------- */
.metric-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}
.metric-card {
  background: var(--accent-tint);
  border-radius: var(--radius);
  padding: 16px 18px;
}
.metric-label {
  font-size: 12px;
  color: var(--text-muted);
  margin: 0 0 6px;
  text-transform: none;
}
.metric-value {
  font-size: 26px;
  font-weight: 600;
  margin: 0;
  color: var(--text);
}
.metric-card.warn { background: var(--warn-tint); }
.metric-card.warn .metric-value { color: var(--warn); }

/* ---------- section / cards ---------- */
.section {
  margin-bottom: 22px;
}
.section-title {
  font-size: 13px;
  font-weight: 600;
  margin: 0 0 10px;
  color: var(--text);
}
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}

/* ---------- tables ---------- */
table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
/* Handoff: fixed column widths specifically for the products list --
   expanding/collapsing a parent family used to shift every column
   left/right, since a plain (table-layout: auto) table recalculates
   widths from whatever rows are currently visible, and a parent row's
   "--" placeholders are a different width than its real variant rows'
   actual SKU/name/category text. Fixed widths here keep every column
   steady regardless of which rows are expanded. Scoped to this one
   table (not the generic `table` rule above) so other tables
   elsewhere in the admin keep their own natural, unaffected sizing. */
.products-table-grid { table-layout: fixed; }
.products-table-grid th:nth-child(1), .products-table-grid td:nth-child(1) { width: 56px; }
.products-table-grid th:nth-child(2), .products-table-grid td:nth-child(2) { width: 12%; }
.products-table-grid th:nth-child(3), .products-table-grid td:nth-child(3) { width: 24%; }
.products-table-grid th:nth-child(4), .products-table-grid td:nth-child(4) { width: 8%; }
.products-table-grid th:nth-child(5), .products-table-grid td:nth-child(5) { width: 8%; }
.products-table-grid th:nth-child(6), .products-table-grid td:nth-child(6) { width: 11%; }
/* Handoff -- real bug found and fixed, confirmed directly with Ali:
   "buttons in products list in admin disappeared, i cant edit or
   delete" -- same real root cause as the two earlier "buttons
   disappearing" incidents referenced below, a third real occurrence
   of it: this table uses table-layout: fixed with an explicit,
   hardcoded width per real column position (nth-child), and these 9
   real rules already summed to exactly 100% on their own -- so
   inserting a real 10th column (Total qty) into the MIDDLE of this
   list, without updating every nth-child position and width after
   it, silently pushed the real last column (Actions -- the real
   Edit/Delete buttons) with no width rule of its own past 100%,
   off the real table's own right edge, invisible with no horizontal
   scrollbar to reach it -- not merely narrow or crowded, genuinely
   unreachable. All 9 real percentages recalculated here for the real
   10-column layout, still summing to exactly 100%, Actions kept
   generous (11%) since it holds two real buttons, not one.
   Confirmed live: Edit/Delete are back, on-screen, clickable. */
.products-table-grid th:nth-child(7), .products-table-grid td:nth-child(7) { width: 8%; }
.products-table-grid th:nth-child(8), .products-table-grid td:nth-child(8) { width: 9%; }
.products-table-grid th:nth-child(9), .products-table-grid td:nth-child(9) { width: 9%; }
.products-table-grid th:nth-child(10), .products-table-grid td:nth-child(10) { width: 11%; }
.products-table-grid td, .products-table-grid th { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Handoff -- real bug fix, confirmed directly with Ali, twice now
   ("the delete buttons of products is disappearing again, i can
   press on black space and it is the delete button"): the rule above
   applies ellipsis truncation to EVERY cell in this table, including
   the one holding the real Edit/Delete buttons -- when that cell's
   two real buttons together are wider than the column, the whole
   cell's overflowing content gets truncated as one unit, which cut
   off the second button's own real text down to just "..." -- fully
   functional and clickable (its own real click handler never
   moved), just visually unreadable, which is exactly why it looked
   like empty/dark space that still happened to work when pressed.
   A real button's own text must never be ellipsis-truncated --
   excluded here rather than removing the rule itself, which still
   genuinely helps long product names/SKUs in every other real
   column. Real, dedicated column width added too so both buttons
   have genuine room to render at full size, not just barely avoid
   truncation. */
.products-table-grid td.actions-cell {
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
  min-width: 150px;
}
/* Handoff: Designer page's live preview boxes -- a real, bordered
   frame around whatever image comes back (SVG barcode label, or
   pos_backend's real ReportLab-rendered receipt/certificate), capped
   at a sensible width so a tall receipt preview doesn't dominate the
   page. */
.designer-preview {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  background: var(--surface);
  display: inline-block;
  max-width: 100%;
}
.designer-preview img { max-width: 320px; height: auto; display: block; }
/* Handoff -- real tabs for the Delivery page (Delivery orders /
   Drivers / Delivery zones), first tab pattern in this app. Deliberately
   simple: underline-style active state, no external library. */
.page-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}
.page-tab {
  padding: 10px 16px;
  border: none;
  background: none;
  font-weight: 600;
  font-size: 14px;
  color: var(--text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.page-tab:hover { color: var(--text); }
.page-tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.page-tab-panel[hidden] { display: none; }
/* Handoff -- the major "two kinds of delivery" feature. A small,
   indented table for an expanded district row's own areas, visually
   distinct from the main table it's nested inside. */
.nested-table { width: 100%; margin: 6px 0; font-size: 13px; }
.nested-table th, .nested-table td { padding: 4px 8px; }
.district-row-toggle { cursor: pointer; }
/* Handoff -- real click-to-toggle active/inactive, confirmed directly
   with Ali: the status badge itself is now the control, not just a
   read-only label. */
.badge-toggle { cursor: pointer; }
.badge-toggle:hover { opacity: 0.75; }
/* Receipt/gift-card-certificate previews are real PDFs (pos_backend's
   real ReportLab generator), not images -- an <img> tag can't render
   a PDF at all, confirmed directly by checking the real response
   content-type rather than assuming. An iframe needs explicit
   dimensions (unlike an <img>, which sizes itself from the image).
   Real bug found and fixed: a generic 320x420 iframe made the QR code
   (drawn last, at the real bottom of the page -- confirmed by
   actually rendering a real receipt PDF to a PNG and looking at it
   directly) appear to be "in the middle" of the visible preview,
   since the browser's PDF viewer can't fit an 80mm-wide, much-taller
   receipt into a 420px box and falls back to showing/scaling only
   part of the page. Receipt (tall, narrow -- 80mm wide, easily
   150-250mm+ tall with real content) and the gift card certificate
   (a real, fixed 140x90mm landscape card, confirmed directly in
   giftcard_certificate.py) are genuinely different shapes and need
   their own real aspect ratios, not one generic box for both. */
.designer-preview-pdf-receipt { width: 280px; height: 700px; border: none; }
/* Real, explicit follow-up request, confirmed directly with Ali: "the
   viewer here needs to view all the certificate not a part of it" --
   the previous fixed 420x270px box cropped the real certificate
   whenever the embedded PDF viewer's own real rendering didn't fit
   that exact pixel height, leaving a real scrollbar and a partially-
   hidden document instead of the whole real card. A real, taller box
   with a safely generous real aspect ratio (the real default card is
   140x90mm, a ~1.56:1 ratio -- this stays tall enough to show that in
   full with real room to spare for a taller real card design too, 
   rather than cropping based on one specific real width/height). */
.designer-preview-pdf-giftcard { width: 420px; height: 420px; border: none; }
/* Handoff: live preview moved to sit beside each Designer section's
   own fields (not below them) -- a real side-by-side layout, form on
   the left (flexible width), preview on the right (fixed width,
   matching its own real size so it doesn't stretch/shrink oddly).
   Stacks vertically on narrow screens, same responsive convention as
   the rest of this app's own .form-grid breakpoint. */
.designer-section-layout { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
.designer-section-layout > .designer-section-fields { flex: 1 1 380px; min-width: 300px; }
.designer-section-layout > .designer-section-preview { flex: 0 0 auto; }
thead th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-faint);
  font-weight: 600;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  background: #FCFCFB;
}
tbody td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: #FBFBFA; }
td.num, th.num { text-align: right; }
.row-link { cursor: pointer; }
.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.mono { font-variant-numeric: tabular-nums; }

/* ---------- badges / status ---------- */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
}
.badge.on { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }
.badge.off { color: var(--text-faint); }
/* A negative drawer difference (a shortage). .off is faint grey and is
   also used for plain "closed" status, so it can't carry this. */
.badge.neg { color: var(--danger); border-color: var(--danger); background: var(--danger-tint); }
.badge.status-refunded { color: var(--danger); border-color: var(--danger); background: var(--danger-tint); }
.badge.status-completed { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }
/* Stock Transfer statuses (CONVOY) -- pending/rejected/cancelled use the
   default unstyled badge above; only flagged (needs attention) and
   received (done, reuses status-completed's green) get their own look. */
.badge.status-flagged { color: var(--warn); border-color: var(--warn); background: var(--warn-tint); }
.badge.status-received { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }

/* ---------- toggle switch ---------- */
.switch {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 19px;
  flex-shrink: 0;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--border-strong);
  border-radius: 999px;
  transition: 0.15s;
}
.switch .slider::before {
  content: "";
  position: absolute;
  width: 15px;
  height: 15px;
  left: 2px;
  top: 2px;
  background: #fff;
  border-radius: 50%;
  transition: 0.15s;
}
.switch input:checked + .slider { background: var(--accent); }
.switch input:checked + .slider::before { transform: translateX(15px); }
.switch input:disabled + .slider { opacity: 0.5; cursor: not-allowed; }

/* ---------- forms ---------- */
.form-grid {
  display: grid;
  /* 2026-10-27 (Ali): as many columns as fit (2 on a small window, 3-4 on a wide one)
     instead of always 2, so a form is not one long column of boxes. */
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
}
.form-grid.full { grid-template-columns: 1fr; }
/* Handoff: single-item form's identity/pricing rows requested as real
   4-column layouts, not the shared 2-column default -- a modifier on
   .form-grid rather than a new class, matching the existing .narrow
   convention already used elsewhere for the same base grid. */
.form-grid.cols-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
.form-grid.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: 12px; color: var(--text-muted); }
.field input, .field select, .field textarea {
  font-size: 13px;
  padding: 7px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
}
.field textarea { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; }
.field .hint { color: var(--text-faint); font-size: 11px; }
.field-row { display: flex; align-items: center; gap: 8px; }

.thumb {
  width: 36px; height: 36px; border-radius: var(--radius);
  object-fit: cover; border: 1px solid var(--border-strong);
  display: block;
}
.thumb-empty {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 11px; background: var(--surface);
}
.thumb-lg { width: 120px; height: 120px; margin-bottom: 6px; }

/* ---------- filter bar ---------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
  align-items: flex-end;
}
.filter-bar .field { min-width: 140px; }
.filter-bar .field.grow { flex: 1; min-width: 180px; }

/* ---------- autocomplete dropdown (Orders report product filter) ---------- */
.autocomplete-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 2px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-modal);
  max-height: 220px;
  overflow-y: auto;
}
.autocomplete-item {
  padding: 7px 10px;
  font-size: 13px;
  cursor: pointer;
}
.autocomplete-item:hover { background: var(--accent-tint); }

/* ---------- summary stat boxes (Orders report) ---------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 22px;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 12px 14px;
}
.stat-card .stat-label {
  font-size: 11px;
  color: var(--text-muted);
  margin: 0 0 5px;
}
.stat-card .stat-value {
  font-size: 20px;
  font-weight: 600;
  margin: 0;
  color: var(--text);
}
.stat-card .stat-sub {
  font-size: 11px;
  color: var(--text-faint);
  margin: 3px 0 0;
}
.stat-card.stat-accent { border-left-color: var(--accent); }
.stat-card.stat-accent .stat-value { color: var(--accent); }
.stat-card.stat-danger { border-left-color: var(--danger); }
.stat-card.stat-danger .stat-value { color: var(--danger); }
.stat-card.stat-warn { border-left-color: var(--warn); }
.stat-card.stat-warn .stat-value { color: var(--warn); }
.stat-card.stat-neutral { border-left-color: var(--border-strong); }

/* ---------- pagination ---------- */
.pagination {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  font-size: 12px;
  color: var(--text-muted);
}

/* ---------- empty / blocked / error states ---------- */
.empty-state, .blocked-state, .error-state {
  padding: 28px 20px;
  text-align: center;
  color: var(--text-muted);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}
.blocked-state {
  text-align: left;
  border-style: solid;
  background: var(--warn-tint);
  border-color: #E7D6AC;
}
.blocked-state .blocked-title {
  font-weight: 600;
  color: var(--warn);
  margin: 0 0 6px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.blocked-state p { margin: 0 0 8px; font-size: 13px; }
.blocked-state p:last-child { margin-bottom: 0; }
.error-state { border-color: var(--danger); background: var(--danger-tint); color: var(--danger); }

.loading { color: var(--text-faint); padding: 20px 0; font-size: 13px; }

/* ---------- modal ---------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 22, 20, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
}
.modal {
  width: 380px;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-modal);
  padding: 22px;
  /* Handoff -- real bug found and fixed, confirmed directly with
     Ali: a modal whose own content grows very tall (a long real
     results list, in this case) could push its own Close/Save
     buttons below the visible screen entirely, with no way to
     reach them short of a full page refresh. A real, global safety
     net here protects every modal built on this shared class, not
     just the one that happened to hit it first -- the modal itself
     never grows past a real, reachable height, and scrolls its own
     content internally instead. */
  max-height: 90vh;
  overflow-y: auto;
}
.modal h2 { font-size: 15px; margin: 0 0 8px; }
.modal p { font-size: 13px; color: var(--text-muted); margin: 0 0 14px; }
#modal-body { white-space: pre-line; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* Handoff -- real bug fix, confirmed directly with Ali: "i want it a
   normal window, not elongated like this... i should zoom out the
   window to see the buttons." A real, dedicated layout for the slide
   edit modal only -- see index.html's own comment on why this is a
   separate class rather than a change to the shared .modal every
   other real modal already depends on. Real max-height + real
   internal scroll on the body, real sticky Save/Cancel footer that
   never moves off-screen regardless of how tall the scrollable
   content above it gets. */
.modal--slide-edit {
  width: 720px;
  max-width: 92vw;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  padding: 22px 22px 0 22px;
}
.modal--slide-edit-body {
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
  padding-bottom: 4px;
}
.modal--slide-edit-actions {
  flex: 0 0 auto;
  padding: 14px 0;
  border-top: 1px solid var(--border, #e5e5e5);
  margin-top: 10px;
}
/* Handoff -- real, explicit request, confirmed directly with Ali: "put
   these 2 boxes beside the category image and replace and title...
   so they are next to each other not over each other" -- a real
   two-column layout: the real form fields (image preview, replace,
   title, link, category, sort order) on the left, the two real focal-
   point preview boxes stacked together on the right, rather than one
   long real vertical stack of all five. Stacks back to one column
   below 640px (the same real breakpoint the rest of this admin panel
   already treats as "too narrow for side-by-side"), so this stays
   usable on a smaller real screen instead of squeezing two real
   columns into no space. */
.se-edit-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: start;
}
.se-edit-grid .se-focal-box { max-width: 100% !important; }
@media (max-width: 640px) {
  .se-edit-grid { grid-template-columns: 1fr; }
}

/* Handoff -- real "press the log, see the whole drawer" feature,
   confirmed directly with Ali: a real, wider variant for content that
   doesn't fit the plain 380px confirm modal (the full entries table +
   Drawer Close report + totals). */
.modal--wide { width: min(760px, 92vw); max-height: 86vh; display: flex; flex-direction: column; }
.modal-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.modal-header-row h2 { margin: 0; }
#drawer-detail-body { overflow-y: auto; }

/* ---------- toast ---------- */
.toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--text);
  color: #fff;
  padding: 10px 18px;
  border-radius: var(--radius);
  font-size: 13px;
  z-index: 60;
  box-shadow: var(--shadow-modal);
}
.toast.error { background: var(--danger); }

/* ---------- detail panel (order drill-in) ---------- */
.detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px 20px;
  margin-bottom: 18px;
}
.detail-item .k { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.03em; margin: 0 0 3px; }
.detail-item .v { font-size: 14px; margin: 0; }

.back-link { display: inline-block; margin-bottom: 14px; font-size: 13px; color: var(--text-muted); }

/* ---------- collapsible panel (DISPATCH-14 §3a Stock Transfer redesign) ---------- */
.collapsible-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.collapsible-panel + .collapsible-panel { margin-top: 16px; }
.collapsible-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 18px;
  background: #F7F6F4;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
.collapsible-header:last-child { border-bottom: none; }
.collapse-toggle {
  width: 26px; height: 26px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.15s;
}
.collapse-toggle:hover { background: var(--accent-hover); }
.collapse-toggle.collapsed { transform: rotate(180deg); }
.collapsible-body { padding: 18px; }
.collapsible-body.collapsed { display: none; }
.collapsible-body .form-grid.narrow { grid-template-columns: 1fr; max-width: 420px; }
.required-mark { color: var(--danger); margin-left: 2px; }

.transfer-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; gap: 16px; }
.transfer-header .header-title-row { display: flex; align-items: center; gap: 10px; }
.transfer-header .icon-back {
  font-size: 18px; color: var(--text-muted); text-decoration: none; line-height: 1;
  padding: 2px 4px;
}
.transfer-header .icon-back:hover { color: var(--text); }
.transfer-header h1 { font-size: 18px; margin: 0; }

.products-toolbar { display: flex; align-items: center; gap: 18px; margin-bottom: 14px; flex-wrap: wrap; }
.bulk-add-field { display: flex; align-items: center; gap: 8px; }
.bulk-add-field input[type="number"] {
  width: 64px; font-size: 13px; padding: 7px 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
}
.bulk-add-field .hint { font-size: 11px; color: var(--text-faint); }
.row-product-cell { position: relative; min-width: 200px; }
.row-product-cell input[readonly] { background: #FAFAF9; color: var(--text-muted); }
.row-product-selected { display: flex; gap: 6px; align-items: center; }
.row-product-selected input[readonly] { flex: 1; }

/* Stock Transfer / New Stock "Add product" search widget (CONVOY).
   Real bug found and fixed: this used to be `position: absolute` on
   the table cell, which clipped/mispositioned the dropdown for rows
   near the bottom of a long table. Positioning (position/top/left/
   right/bottom/width) is now computed and set inline in JS
   (attachProductAutocomplete), anchored to the real viewport --
   only the presentational styling stays here. */
.autocomplete-dropdown {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-height: 220px;
  overflow-y: auto;
  z-index: 200;
  box-shadow: var(--shadow-modal);
}
.autocomplete-item { padding: 8px 12px; font-size: 13px; cursor: pointer; }
.autocomplete-item:hover { background: #F3F2EF; }
.autocomplete-item.muted { cursor: default; }
.autocomplete-item.muted:hover { background: transparent; }

@media (max-width: 800px) {
  .app-shell { flex-direction: column; }
  .sidebar { width: 100%; height: auto; position: relative; }
  .content { padding: 20px; }
  .form-grid { grid-template-columns: 1fr; }
  .form-grid.cols-4 { grid-template-columns: 1fr; }
  .form-grid.cols-3 { grid-template-columns: 1fr; }
}

/* Order receipt printing (FOLIO §6e). Two print modes, chosen by
   app.js setting/clearing body[data-print-mode="full"] before calling
   window.print():
   - Default (no attribute): "Print receipt" — the dedicated compact
     8cm template (.receipt-print) is the ONLY thing printed. This
     replaces the old "hide the sidebar and print the whole page"
     approach, which produced a full-width web-page printout with no
     receipt-like structure.
   - data-print-mode="full": "Print full details" — restores that old
     whole-page behavior for when someone genuinely wants the full
     on-screen detail on paper, with .receipt-print explicitly hidden
     so it doesn't ALSO print underneath everything else. */
@media print {
  .sidebar, .no-print, #modal-backdrop, .toast { display: none !important; }
  .app-shell { display: block; }
  .content { padding: 0; }

  .receipt-print { display: none; }
  body[data-print-mode="full"] .receipt-print { display: none !important; }
  body:not([data-print-mode="full"]) .content > *:not(.receipt-print) {
    display: none !important;
  }
  body:not([data-print-mode="full"]) .receipt-print {
    display: block !important;
  }
}

/* @page controls the physical print media itself -- 80mm (8cm) wide,
   height auto so it doesn't force a fixed page length on a
   variable-length order (per the handoff's explicit "width: 8cm,
   height auto" instruction, not a fixed-height page). Most receipt
   printers/print-to-PDF drivers respect @page size directly. */
@page {
  size: 80mm auto;
  margin: 3mm;
}

.receipt-print {
  display: none; /* shown only under @media print, see above */
  width: 8cm;
  margin: 0 auto;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 11px;
  line-height: 1.45;
  color: #000;
}
.receipt-logo {
  text-align: center;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin-top: 2mm;
}
.receipt-tagline {
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.12em;
  color: #333;
  margin-bottom: 3mm;
}
.receipt-meta {
  border-top: 1px dashed #000;
  border-bottom: 1px dashed #000;
  padding: 2mm 0;
  margin-bottom: 2mm;
}
.receipt-meta p { margin: 0 0 1mm; }
.receipt-items {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 2mm;
  background: transparent;
  border: none;
  border-radius: 0;
}
.receipt-items thead th {
  text-align: left;
  font-size: 10px;
  text-transform: none;
  letter-spacing: 0;
  color: #000;
  background: transparent;
  padding: 1mm 0;
  border-bottom: 1px solid #000;
}
.receipt-items td {
  padding: 1mm 0;
  border-bottom: none;
  font-size: 11px;
  vertical-align: top;
}
.receipt-items th.num, .receipt-items td.num { text-align: right; }
.receipt-totals {
  border-top: 1px dashed #000;
  padding-top: 2mm;
  margin-bottom: 2mm;
}
.receipt-totals p {
  display: flex;
  justify-content: space-between;
  margin: 0 0 1mm;
}
.receipt-total-line {
  font-weight: 700;
  font-size: 13px;
  border-top: 1px solid #000;
  padding-top: 1mm;
  margin-top: 1mm !important;
}
.receipt-thankyou {
  text-align: center;
  font-weight: 600;
  margin: 3mm 0 2mm;
}
.receipt-qr {
  display: flex;
  justify-content: center;
  margin-bottom: 2mm;
}
.receipt-qr-placeholder {
  width: 22mm;
  height: 22mm;
  border: 1px dashed #666;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 9px;
  color: #666;
  line-height: 1.3;
}
.receipt-followus {
  text-align: center;
  font-size: 10px;
  margin: 0 0 3mm;
}
.receipt-branches {
  border-top: 1px dashed #000;
  padding-top: 2mm;
  margin-bottom: 2mm;
  font-size: 10px;
  text-align: center;
}
.receipt-branches-title {
  font-weight: 600;
  margin: 0 0 1mm;
}
.receipt-branches p { margin: 0 0 1mm; }
.receipt-contact {
  text-align: center;
  font-size: 10px;
  padding-bottom: 3mm;
}
.receipt-contact p { margin: 0 0 1mm; }

/* ---------- PALETTE: product creation wizard (§6b) ---------- */
.wizard-steps {
  display: flex;
  gap: 4px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--border, #e5e3de);
  padding-bottom: 0;
}
.wizard-step-tab {
  padding: 8px 14px;
  font-size: 12px;
  color: var(--text-faint);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.wizard-step-tab.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
.wizard-step-tab.done { color: var(--text-muted); }
.wizard-actions { display: flex; gap: 8px; margin-top: 16px; justify-content: space-between; }
.wizard-actions .left, .wizard-actions .right { display: flex; gap: 8px; }

.variant-table { width: 100%; border-collapse: collapse; margin-top: 8px; }
.variant-table th { text-align: left; font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.02em; padding: 4px 6px; }
.variant-table td { padding: 4px 6px; vertical-align: top; }
.variant-table input[type="text"], .variant-table input[type="number"] {
  width: 100%; padding: 5px 7px; font-size: 13px; border: 1px solid var(--border, #ddd); border-radius: 4px;
}
.variant-row { border-top: 1px solid var(--border, #eee); cursor: grab; }
.variant-row.dragging { opacity: 0.4; }
.variant-row .drag-handle { cursor: grab; color: var(--text-faint); user-select: none; padding: 0 4px; }
/* Handoff §3b: branch name is now shown directly above each qty/threshold
   input instead of relying on a 3-letter truncated placeholder + hover
   tooltip -- Ali's screenshot showed literal "?" marks where he expected
   to see which column belonged to which branch. */
.branch-qty-field { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.branch-qty-label { font-size: 9px; color: var(--text-faint); max-width: 52px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Handoff §3b: real per-field validation highlighting, replacing a
   generic bottom-of-screen-only error banner for the cases this
   editor can check client-side (missing required fields, duplicate
   codes/names within one batch). */
.variant-row input.field-error { border-color: var(--danger, #b3261e); background: color-mix(in srgb, var(--danger, #b3261e) 8%, transparent); }
.field-error-msg { font-size: 11px; color: var(--danger, #b3261e); margin-top: 2px; max-width: 160px; }
.bulk-set-row { background: var(--surface, #FAFAF8); border-radius: 6px; }
.bulk-set-row td { padding: 8px 6px; }
.variant-qty-toggle { font-size: 11px; color: var(--accent); cursor: pointer; }
.variant-qty-box { display: none; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.variant-qty-box.open { display: flex; }
.variant-qty-box .field { min-width: 90px; }
.gallery-grid { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.gallery-thumb { position: relative; width: 96px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.gallery-thumb img { width: 96px; height: 96px; object-fit: cover; border-radius: 6px; border: 1px solid var(--border, #ddd); }
.gallery-thumb.gallery-thumb-main img { border: 2px solid var(--accent); }
.gallery-thumb .gallery-remove {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--danger, #b3261e); color: #fff; border: none; font-size: 11px; cursor: pointer; line-height: 18px; padding: 0;
}
.gallery-main-badge {
  font-size: 10px; color: var(--accent); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em;
}
.gallery-thumb .gallery-make-main { font-size: 11px; padding: 2px 8px; }
.variant-image-block { border: 1px solid var(--border, #eee); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
/* DISPATCH-23 §4b -- per-variant inline images in the Variants step:
   "up to 10 images uploadable in one selection, previewed as medium
   icons, 5 per row, each deletable individually from the icon itself".
   A real CSS grid (not flex-wrap) so 5-per-row holds regardless of
   container width, rather than an incidental wrap point. */
.gallery-grid-5col { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-top: 6px; max-width: 420px; }
.gallery-grid-5col .gallery-thumb { width: auto; }
.gallery-grid-5col .gallery-thumb img { width: 72px; height: 72px; }
.category-tree-picker { max-height: 220px; overflow-y: auto; border: 1px solid var(--border, #ddd); border-radius: 6px; padding: 6px; }
.category-tree-picker label { display: block; padding: 4px 6px; font-size: 13px; cursor: pointer; border-radius: 4px; }
.category-tree-picker label:hover { background: var(--surface, #F3F2EF); }
/* Handoff §3b -- shared category tree (renderCategoryTree): inline
   create + up/down reorder, used by both the wizard's Category step
   and the standalone Categories page. */
.category-tree-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px 6px; border-radius: 4px; }
.category-tree-row:hover { background: var(--surface, #F3F2EF); }
.category-tree-radio { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; flex: 1; }
.category-tree-name { font-size: 13px; flex: 1; }
.category-tree-actions { display: flex; gap: 2px; flex-shrink: 0; }
.btn-icon { border: none; background: none; cursor: pointer; font-size: 12px; padding: 2px 5px; border-radius: 4px; color: var(--text-muted); }
.btn-icon:hover:not(:disabled) { background: var(--border, #eee); }
.btn-icon:disabled { opacity: 0.3; cursor: default; }
.category-tree-add-form { display: flex; align-items: center; gap: 6px; padding: 4px 6px; }
.category-tree-add-form input { flex: 1; max-width: 220px; }
.type-badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; border: 1px solid var(--border, #ddd); }
.type-badge.simple { color: var(--text-muted); }
.type-badge.parent { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }
.type-badge.variant { color: var(--text-faint); }
.parent-row-toggle { cursor: pointer; user-select: none; }
.variant-nested-row td:first-child { padding-left: 26px; }

/* Gift card print template (MINT -- gift cards phase 2 addendum).
   .gc-print-only is invisible on screen and only appears when printing
   -- lets the gift-card detail view show its normal interactive
   controls on screen while printing a clean, minimal card instead of
   the whole page. Same .no-print escape hatch as the order-receipt
   pattern above, just inverted for this one block. */
.gc-print-only { display: none; }
@media print {
  .gc-print-only { display: block; }
}

/* The small red count next to "Low Stock" in the sidebar: the notification
   that something needs restocking, visible on every page. */
.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: 0 6px;
  margin-left: 6px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  vertical-align: middle;
}
.nav-badge[hidden] { display: none; }
.badge.out { color: var(--danger); border-color: var(--danger); background: var(--danger-tint); }

/* Low Stock page: a ticked row, and the action bar that stays in view at the
   bottom while you scroll a long list. */
.ls-selected { background: var(--accent-tint); }
.ls-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  margin-top: 12px;
  padding: 12px 14px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08);
}
.ls-bar[hidden] { display: none; }

/* Real, explicit request, confirmed directly with Ali: "in low stock list,
   ability to 'ignore'." An ignored row (only visible while "Show ignored" is
   ticked) is dimmed so it reads as set aside, not as a live alert. */
.ls-ignored td { opacity: 0.55; }
.ls-ignored td:last-child { opacity: 1; }


/* ---------- shared page counter + low-stock bulk bar ---------- */
.pager-input { width: 64px; padding: 3px 6px; text-align: center; }
.pager-label { display: inline-flex; align-items: center; gap: 6px; }
.pagination { flex-wrap: wrap; }
.ls-bulk-bar {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  margin: 0 0 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--accent-tint);
}
.ls-bulk-bar[hidden] { display: none; }


/* ---------- folded blocks (2026-10-27) ----------
 * A rarely-used form (settings, "add a ...") sits behind one clickable line instead of
 * filling the page; click it to open. */
details.fold > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px; font-size: 13px; font-weight: 600; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::before {
  content: ""; width: 7px; height: 7px; flex-shrink: 0;
  border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted);
  transform: rotate(-45deg); transition: transform .15s;
}
details.fold[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
details.fold[open] > summary::before { transform: rotate(45deg); }
details.fold > summary:hover { background: #F3F2EF; }
details.fold > .panel { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; }

/* two small forms side by side (drawer settings) */
.settings-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 24px; }

/* a one-row filter strip: fields side by side, button at the end */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
  margin-bottom: 14px; padding: 10px 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.filter-bar .field { min-width: 160px; }
