/* ─── INK & SIGNAL — InvoiceFlow Design System (Option B) ──────────────────
   Typefaces : Oswald (display / wordmark / titles) + JetBrains Mono (data,
               labels, buttons, badges) + Inter (body copy)
   Philosophy: near-black surfaces, a single red "signal" accent, and hollow
               uppercase status chips — an editorial/industrial dark theme
               inspired by the Levecke Sticker Master reference screenshot.
   ────────────────────────────────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap');

/* ─── Tokens ──────────────────────────────────────────────────────────────── */
:root {
  /* Palette */
  --paper:     #0B0B0D;   /* near-black — page background */
  --surface:   #131316;   /* card / modal surface */
  --ledger:    #000000;   /* pure black — sidebar / wordmark */
  --ledger-h:  #050505;   /* deeper black on hover */
  --accent:    #E8394B;   /* signal red — primary actions + brand rule */
  --accent-h:  #C22A3A;   /* darker red on hover */
  --ink:       #F2F1EE;   /* near-white body text */
  --ink-mid:   #A8A7A2;   /* medium prose / secondary text */
  /* UI/UX audit fix (Critical #4): was #6B6A66, ~3.6:1 against --paper —
     failed WCAG AA for small text (table headers, stat labels). Bumped for
     legibility; still clearly a step down from --ink-mid. */
  --ink-soft:  #8A897F;   /* de-emphasized labels */
  --border:    #26262B;   /* hairlines */
  --surface-elev: #17171B; /* modal/popover surface — one step up from --surface for real elevation */

  /* Semantic colors — hollow (transparent) chips, colored text + border */
  --amber:     #F5B942;
  --amber-bg:  transparent;
  --amber-br:  #F5B942;
  --red:       #E8394B;
  --red-bg:    transparent;
  --red-br:    #E8394B;
  /* UI/UX audit fix (Critical #3): 30 references to var(--danger) across 10
     view files (required-field asterisks, destructive-card borders) were
     resolving to nothing because this token never existed. Aliased to --red
     so every existing usage starts rendering correctly with zero view-file
     changes; new work should standardize on --danger going forward. */
  --danger:    #E8394B;
  --green:     #5FD98A;
  --green-bg:  transparent;
  --green-br:  #5FD98A;
  --blue:      #4FC3E8;
  --blue-bg:   transparent;
  --blue-br:   #4FC3E8;

  /* Shape */
  --radius:      3px;
  --radius-lg:   4px;
  --radius-pill: 999px;
  --shadow:      none;
  --shadow-1:    0 2px 8px rgba(0,0,0,.35);
  --shadow-2:    0 8px 24px rgba(0,0,0,.5);
  --shadow-lg:   0 20px 60px rgba(0,0,0,.6);

  /* Spacing scale — formalizes the ad hoc paddings scattered through inline
     styles across the app (design system §14). Not yet consumed everywhere;
     new/updated components should use these instead of one-off px values. */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;

  /* Type */
  --font-mono:    'JetBrains Mono', 'Courier New', monospace;
  --font-body:    'Inter', system-ui, sans-serif;
  --font-display: 'Oswald', 'JetBrains Mono', sans-serif; /* wordmark + page/section titles only */
}

/* ─── Motion preference ───────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ─── Reset ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.6;
  min-height: 100vh;
}

/* ─── Auth screens ────────────────────────────────────────────────────────── */
.auth-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
  background: var(--paper);
}
.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 44px 40px;
  width: 100%;
  max-width: 420px;
  box-shadow: var(--shadow);
}
.auth-logo {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 6px;
}
.auth-logo span { color: var(--ink-soft); }
.auth-sub        { color: var(--ink-mid); font-size: 13px; margin-bottom: 32px; }
.auth-card h2 {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: uppercase;
  margin-bottom: 24px;
}
.auth-switch     { text-align: center; margin-top: 20px; font-size: 13px; color: var(--ink-mid); }
.auth-switch a   { color: var(--accent); text-decoration: none; font-weight: 600; }
.auth-switch a:hover { text-decoration: underline; }
.demo-box {
  margin: 20px 0;
  padding: 14px 16px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-mid);
  line-height: 1.8;
}

/* ─── App shell ───────────────────────────────────────────────────────────── */
.app { display: grid; grid-template-columns: 232px 1fr; min-height: 100vh; }

/* ─── Sidebar ─────────────────────────────────────────────────────────────── */
.sidebar {
  background: var(--ledger);
  padding: 24px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
.sidebar-logo {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #FFFFFF;
  margin-bottom: 32px;
  padding: 0 10px;
  line-height: 1.4;
}
.sidebar-logo span { color: rgba(255,255,255,.38); }

.nav-section-label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(255,255,255,.28);
  padding: 16px 10px 4px;
}
.nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius);
  text-decoration: none;
  color: rgba(255,255,255,.6);
  font-size: 13px;
  font-weight: 500;
  border-left: 2px solid transparent;
  transition: background-color .15s, color .15s, border-color .15s;
}
.nav-link:hover  { background: rgba(255,255,255,.06); color: #FFFFFF; }
.nav-link.active { background: rgba(232,57,75,.1); color: #FFFFFF; border-left-color: var(--accent); }
.nav-link:focus-visible {
  outline: 2px solid rgba(255,255,255,.55);
  outline-offset: 1px;
}
.nav-link .icon { font-size: 14px; width: 20px; text-align: center; flex-shrink: 0; }
.nav-spacer     { flex: 1; }
.nav-user {
  padding: 12px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: var(--radius);
  margin-top: 8px;
}
.nav-user-name {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: #FFFFFF;
}
.nav-user-role {
  font-family: var(--font-mono);
  font-size: 10px;
  color: rgba(255,255,255,.4);
  text-transform: uppercase;
  letter-spacing: .08em;
  margin-top: 2px;
}

/* ─── Main content ────────────────────────────────────────────────────────── */
.main           { padding: 36px 40px; overflow-y: auto; }
.page-header    { margin-bottom: 28px; }
.page-header h1 {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: var(--ink);
}
.page-header p { color: var(--ink-mid); font-size: 13px; margin-top: 4px; }

/* ─── Stats row ───────────────────────────────────────────────────────────── */
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 28px; }
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.stat-value {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.stat-label {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--ink-soft);
  margin-top: 5px;
  text-transform: uppercase;
  letter-spacing: .1em;
}
.stat-card.accent .stat-value { color: var(--accent); }
.stat-card.green  .stat-value { color: var(--green);  }
.stat-card.red    .stat-value { color: var(--red);    }
.stat-card.amber  .stat-value { color: var(--amber);  }
.stat-card.blue   .stat-value { color: var(--blue);   }

/* Phase D — SaaS-borrowed "clickable KPI card" shortcut (§18/§20). Only the
   metrics with an exact, unambiguous filter target in the destination list
   use this; composite (e.g. "open" = 2 statuses) or currency-total metrics
   stay plain .stat-card rather than link somewhere that wouldn't actually
   match the number shown. */
a.stat-card-link {
  display: block;
  color: inherit;
  text-decoration: none;
  position: relative;
  transition: border-color .15s ease, transform .1s ease;
}
a.stat-card-link:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}
a.stat-card-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
a.stat-card-link::after {
  content: '→';
  position: absolute;
  top: 16px; right: 16px;
  color: var(--ink-soft);
  font-size: 13px;
  opacity: 0;
  transition: opacity .15s ease, transform .15s ease;
  transform: translateX(-3px);
}
a.stat-card-link:hover::after,
a.stat-card-link:focus-visible::after {
  opacity: 1;
  transform: translateX(0);
}

/* ─── Dashboard grid ──────────────────────────────────────────────────────── */
/* Phase D — UI/UX audit fix (Critical, §6/§15): the chart+activity row (and
   the mirrored inventory-widgets row) previously used a raw inline
   `style="display:grid;grid-template-columns:1fr 1.3fr"` with no media
   query, so it never collapsed at any width — the two most-visited-after-
   the-KPIs panels on the busiest page in the app just got crushed side by
   side on phones. Chart stays first in DOM order, so stacking below the
   mobile breakpoint puts the chart above Recent Activity, not after it. */
.dashboard-grid {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: 20px;
  align-items: start;
}
.dashboard-grid.mt { margin-top: 20px; }

/* ─── Cards & tables ─────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
  color:#fff;
}
.card-header {
  padding: 14px 22px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--paper);
}
.card-header h3 {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--ink-soft);
}
/* ─── Inline Form Card (Phase H — inline-style migration) ───────────────────
   The lightweight "add/edit in place" card used across the simple CRUD list
   pages (banks, bank accounts, departments, locations, users, vendors,
   items, inventory adjust/transfer) — hidden by default, shown by toggling
   element.style.display directly from JS (an inline style set by JS always
   wins over this class's display:none, so the existing show/hide JS needs
   no changes). Was a byte-identical style="" repeated 8-10x per file;
   extracted here instead of duplicated per view. Distinct from .card-header
   above — that one's for static section headers (mono/uppercase label,
   var(--paper) background); this is a plain title + Cancel button row. */
.crud-form-card { display: none; margin-bottom: 16px; }
.crud-form-card-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.crud-delete-card {
  display: none;
  margin-bottom: 16px;
  border: 2px solid var(--danger);
}

.table-wrap { overflow-x: auto; }
table       { width: 100%; border-collapse: collapse; }
th {
  padding: 10px 16px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: .1em;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  background: var(--paper);
  user-select: none;
}
th[onclick]           { cursor: pointer; }
th[onclick]:hover     { color: var(--accent); }
th[onclick]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  vertical-align: middle;
}
tr:last-child td { border-bottom: none; }
/* Ink & Signal alternating rows */
tbody tr:nth-child(even) td { background: #101012; }
tbody tr:hover td { background: rgba(232,57,75,.06) !important; }

/* ─── Forms ──────────────────────────────────────────────────────────────── */
.form-group { margin-bottom: 18px; }
label {
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .09em;
  margin-bottom: 6px;
  color: var(--ink-mid);
}
input[type=text],
input[type=email],
input[type=password],
input[type=number],
input[type=date],
input[type=file],
input[type=search],
select,
textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  transition: border-color .15s, box-shadow .15s;
  outline: none;
  font-family: var(--font-body);
}
input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(232,57,75,.18);
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(232,57,75,.18);
}
input[type=file] { padding: 8px; cursor: pointer; font-family: var(--font-mono); font-size: 11px; }
textarea         { resize: vertical; min-height: 80px; }
/* Phase E fix — UI/UX audit-class bug (same family as Critical #1-3 in
   style.ink-signal.css's :root): input[type=search] was never in the shared
   selector above, so every search box across the app (PO/Invoices/Checks
   list filters, etc.) was relying on the "form-control" class alone, which
   this design system never defines — those inputs were rendering with
   unstyled browser/Bootstrap-default chrome instead of the dark Ink &
   Signal look. -webkit-appearance reset needed separately since search
   inputs get native OS chrome (rounded corners, cancel button) that
   ignores border-radius/padding otherwise. */
input[type=search] { -webkit-appearance: none; appearance: none; }
/* Fase 12 fix: native calendar/clock icon renders near-black by default,
   which disappears against this theme's dark input surfaces. */
input[type=date]::-webkit-calendar-picker-indicator,
input[type=datetime-local]::-webkit-calendar-picker-indicator,
input[type=time]::-webkit-calendar-picker-indicator {
  filter: invert(1);
  cursor: pointer;
}
.form-hint       { font-size: 12px; color: var(--ink-soft); margin-top: 4px; }
.form-hint a     { color: var(--accent); text-decoration: none; }
.form-hint a:hover { text-decoration: underline; }

/* Phase C — error state, tied to aria-invalid/aria-describedby per §18.
   Markup: `<input aria-invalid="true" aria-describedby="qty-error">` +
   `<p class="field-error" id="qty-error">…</p>`. */
input[aria-invalid="true"],
select[aria-invalid="true"],
textarea[aria-invalid="true"] {
  border-color: var(--red);
}
input[aria-invalid="true"]:focus,
select[aria-invalid="true"]:focus,
textarea[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px rgba(232,57,75,.18);
}
.field-error {
  font-size: 12px;
  color: var(--red);
  margin-top: 4px;
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Phase C — formalizes the existing "readonly price field with an unlock
   button" pattern into a reusable variant. Markup:
   `<div class="input-unlock"><input readonly ...><button type="button"
   class="input-unlock-btn" aria-label="Unlock field">🔓</button></div>` */
.input-unlock { position: relative; }
.input-unlock input { padding-right: 40px; }
.input-unlock input[readonly] { background: var(--paper); color: var(--ink-mid); cursor: default; }
.input-unlock-btn {
  position: absolute;
  right: 6px; top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 14px;
  padding: 6px;
  line-height: 1;
}
.input-unlock-btn:hover { color: var(--accent); }
.input-unlock-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: var(--radius); }

/* Phase C — Switch, recommended in §18 for the Active/Inactive toggles that
   currently use a plain checkbox. Markup: `<label class="switch"><input
   type="checkbox" class="switch-input"><span class="switch-track"></span>
   <span class="switch-label">Active</span></label>`. */
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.switch-input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.switch-track {
  position: relative;
  width: 34px; height: 19px;
  background: var(--border);
  border-radius: var(--radius-pill);
  transition: background-color .15s ease;
  flex-shrink: 0;
}
.switch-track::before {
  content: '';
  position: absolute;
  left: 2px; top: 2px;
  width: 15px; height: 15px;
  background: #fff;
  border-radius: 50%;
  transition: transform .15s ease;
}
.switch-input:checked ~ .switch-track { background: var(--accent); }
.switch-input:checked ~ .switch-track::before { transform: translateX(15px); }
.switch-input:focus-visible ~ .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch-label { font-size: 13px; color: var(--ink); }

.two-col         { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

/* ─── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  cursor: pointer;
  border: 1px solid transparent;
  text-decoration: none;
  transition: background-color .15s, border-color .15s, color .15s;
  line-height: 1;
  white-space: nowrap;
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-primary        { background: var(--accent);   color: #fff; border-color: var(--accent); }
.btn-primary:hover  { background: var(--accent-h); border-color: var(--accent-h); }
.btn-outline        { background: transparent; color: var(--ink); border-color: #590D0D; }
.btn-outline:hover  { border-color: var(--accent); color: var(--accent); background: rgba(232,57,75,.08); }
.btn-danger         { background: var(--red-bg); color: var(--red); border-color: var(--red-br); }
.btn-danger:hover   { background: rgba(232,57,75,.12); }
.btn-green          { background: var(--green-bg); color: var(--green); border-color: var(--green-br); }
.btn-green:hover    { background: rgba(95,217,138,.12); }
.btn-red            { background: var(--red-bg);  color: var(--red);   border-color: var(--red-br); }
.btn-red:hover      { background: rgba(232,57,75,.12); }
.btn-sm             { padding: 5px 10px; font-size: 10px; }
/* Phase C — quiet/tertiary action, for Minimal-influenced low-emphasis buttons
   (e.g. "Cancel" next to a primary submit) that shouldn't compete visually. */
.btn-ghost          { background: transparent; color: var(--ink-mid); border-color: transparent; }
.btn-ghost:hover    { color: var(--accent); background: rgba(232,57,75,.06); }
/* Phase C — loading state. Markup: add aria-busy="true" and keep the button's
   existing label text; the spinner replaces it visually while staying out of
   the accessible name so screen readers still announce the button's purpose. */
.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  position: relative;
}
.btn[aria-busy="true"]::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 14px; height: 14px;
  margin: -7px 0 0 -7px;
  border: 2px solid rgba(255,255,255,.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: btn-spin .6s linear infinite;
}
.btn-outline[aria-busy="true"]::after,
.btn-ghost[aria-busy="true"]::after {
  border-color: rgba(232,57,75,.25);
  border-top-color: var(--accent);
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* ─── Badges (hollow / outlined uppercase chips) ────────────────────────── */
.badge {
  display: inline-block;
  padding: 2px 7px;
  border-radius: var(--radius);   /* square, not pills */
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  border: 1px solid;
}
.badge-pending  { background: var(--amber-bg); color: var(--amber); border-color: var(--amber-br); }
.badge-approved { background: var(--green-bg); color: var(--green); border-color: var(--green-br); }
.badge-declined { background: var(--red-bg);   color: var(--red);   border-color: var(--red-br);   }
.badge-paid     { background: var(--blue-bg);  color: var(--blue);  border-color: var(--blue-br);  }

/* ─── Role badges ────────────────────────────────────────────────────────── */
.badge-role-admin  { background: transparent; color: #B98CFF; border-color: #B98CFF; border-radius: var(--radius); font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; border-width: 1px; border-style: solid; padding: 2px 7px; display: inline-block; }
.badge-role-ap-mgr { background: var(--blue-bg);  color: var(--blue);  border-color: var(--blue-br);  border-radius: var(--radius); font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; border-width: 1px; border-style: solid; padding: 2px 7px; display: inline-block; }
.badge-role-ap     { background: var(--green-bg); color: var(--green); border-color: var(--green-br); border-radius: var(--radius); font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; border-width: 1px; border-style: solid; padding: 2px 7px; display: inline-block; }
.badge-role-user   { background: transparent; color: var(--ink-mid); border-color: var(--border);    border-radius: var(--radius); font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; border-width: 1px; border-style: solid; padding: 2px 7px; display: inline-block; }

/* ─── Alert messages ─────────────────────────────────────────────────────── */
.alert {
  padding: 11px 16px;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  margin-bottom: 24px;
  border: 1px solid;
}
.alert-success { background: var(--green-bg); color: var(--green); border-color: var(--green-br); }
.alert-error   { background: var(--red-bg);   color: var(--red);   border-color: var(--red-br);   }
/* Phase C — rounds out the alert semantic set to match badges (§18); no
   existing view currently emits these two, so this is additive only. */
.alert-warning { background: var(--amber-bg); color: var(--amber); border-color: var(--amber-br); }
.alert-info    { background: var(--blue-bg);  color: var(--blue);  border-color: var(--blue-br);  }

/* ─── Upload zone ────────────────────────────────────────────────────────── */
.upload-zone {
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: 40px;
  text-align: center;
  cursor: pointer;
  transition: border-color .15s, background-color .15s;
  background: var(--paper);
}
.upload-zone:hover,
.upload-zone.drag {
  border-color: var(--accent);
  background: rgba(232,57,75,.05);
}
.upload-zone:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.upload-zone .uz-icon { font-size: 28px; margin-bottom: 10px; }
.upload-zone p        { color: var(--ink-mid); font-size: 13px; }
.upload-zone small    { color: var(--ink-soft); font-size: 11px; font-family: var(--font-mono); letter-spacing: .04em; }

/* ─── Filter tabs ────────────────────────────────────────────────────────── */
.filter-tabs { display: flex; gap: 0; }
.filter-tab {
  padding: 10px 16px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-soft);
  transition: color .15s;
  border-bottom: 2px solid transparent;
}
.filter-tab:hover  { color: var(--accent); }
.filter-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.filter-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ─── Filter bar ──────────────────────────────────────────────────────────── */
/* Phase C — generalizes the per-page ad hoc search/filter row (§18) into one
   shared component. Already wraps via flex-wrap; this just formalizes the
   spacing/width rules instead of leaving them as one-off page CSS. */
.filter-bar {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.filter-bar .form-group { margin-bottom: 0; }
.filter-bar-simple .form-group  { min-width: 220px; flex: 1 1 220px; }
.filter-bar-advanced .form-group { min-width: 160px; flex: 0 1 160px; }
.filter-bar-actions { display: flex; gap: 8px; margin-left: auto; }

/* ─── Search / combobox primitive ────────────────────────────────────────── */
/* Phase C — generalizes today's per-instance item/vendor picker markup
   (see .bom-picker-* in app.css, kept as-is for now) into one shared
   combobox primitive per the ARIA Authoring Practices combobox pattern.
   Markup:
     <div class="combobox">
       <input class="combobox-input" role="combobox" aria-expanded="false"
              aria-controls="xyz-list" aria-autocomplete="list">
       <ul class="combobox-list" id="xyz-list" role="listbox"></ul>
     </div>
   Options: <li class="combobox-option" role="option" id="xyz-opt-1">. */
.combobox { position: relative; }
.combobox-list {
  display: none;
  position: absolute;
  z-index: 30;
  top: 100%;
  left: 0; right: 0;
  margin-top: 4px;
  list-style: none;
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  max-height: 280px;
  overflow-y: auto;
}
.combobox-input[aria-expanded="true"] ~ .combobox-list,
.combobox-list.open { display: block; }
.combobox-option {
  padding: 9px 12px;
  font-size: 13px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.combobox-option:last-child { border-bottom: none; }
.combobox-option:hover,
.combobox-option[aria-selected="true"] { background: rgba(232,57,75,.08); }
.combobox-empty,
.combobox-loading { padding: 12px; font-size: 12px; color: var(--ink-soft); text-align: center; }

/* ─── Confirmation dialog — inline idiom ─────────────────────────────────── */
/* Phase C — the two-idiom consolidation from §13/§18: this "inline card" is
   for same-page CRUD confirmations (e.g. "Delete this line item?" appearing
   in place); the existing .modal is the other idiom, for
   interrupting/irreversible actions. Rolling individual instances of the
   third idiom (native confirm()) onto one of these two happens per-module
   in Phase E. */
.confirm-inline {
  border: 1px solid var(--red-br);
  background: var(--red-bg);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
  margin-top: 10px;
}
.confirm-inline p { font-size: 13px; color: var(--ink); margin-bottom: 10px; }
.confirm-inline-actions { display: flex; gap: 8px; }

/* ─── Modal ──────────────────────────────────────────────────────────────── */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.75);
  z-index: 100;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.modal-overlay.open { display: flex; }
.modal {
  display: block; /* Bootstrap 5's .modal sets display:none; this app's custom modal reuses the class name */
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 32px;
  width: 100%;
  max-width: 480px;
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border);
}
.modal h3 {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  text-transform: uppercase;
  margin-bottom: 20px;
  letter-spacing: .01em;
}
.modal-actions { display: flex; gap: 10px; margin-top: 24px; justify-content: flex-end; }
.modal-wide    { max-width: 860px; }

/* ─── Empty state ────────────────────────────────────────────────────────── */
.empty      { padding: 60px 20px; text-align: center; color: var(--ink-soft); }
.empty-icon { font-size: 32px; margin-bottom: 12px; }
.empty p    { font-family: var(--font-mono); font-size: 12px; letter-spacing: .04em; }
/* Phase C — optional action slot, e.g. `<div class="empty">...<button class="btn
   btn-outline btn-sm empty-action">Reset filters</button></div>`. */
.empty-action { margin-top: 16px; }

/* ─── Misc helpers ───────────────────────────────────────────────────────── */
.file-name  { display: flex; align-items: center; gap: 6px; }
.amount-col {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* UI/UX audit fix (Critical #2): was `color: #fff` — solid white, used
   app-wide (161 occurrences / 36 view files) as the "de-emphasized/secondary
   text" utility, so secondary text was rendering BRIGHTER than primary text
   almost everywhere. Now actually muted. */
.text-soft { color: var(--ink-mid); font-size: 12px; }

/* ─── Preview link ───────────────────────────────────────────────────────── */
.preview-link       { color: var(--accent); text-decoration: none; font-weight: 500; }
.preview-link:hover { text-decoration: underline; }
.preview-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* ─── Form section title ─────────────────────────────────────────────────── */
.form-section-title {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--ink-soft);
  margin: 28px 0 14px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

/* ─── Required asterisk ──────────────────────────────────────────────────── */
.required { color: var(--red); margin-left: 2px; }

/* ─── Due date colour coding ─────────────────────────────────────────────── */
.due-date        { font-family: var(--font-mono); font-size: 12px; font-weight: 500; white-space: nowrap; }
.due-date small  { font-weight: 400; font-size: 10px; opacity: .75; display: block; margin-top: 1px; }
.due-black { color: var(--ink); }
.due-yellow { color: var(--amber); background: rgba(245,185,66,.08); padding: 2px 6px; border-radius: var(--radius); }
.due-red    { color: var(--red);   background: rgba(232,57,75,.1);   padding: 2px 6px; border-radius: var(--radius); }

/* ─── Sort icons ─────────────────────────────────────────────────────────── */
.sort-icon { opacity: .35; margin-left: 4px; }
th.sort-asc  .sort-icon,
th.sort-desc .sort-icon { opacity: 1; color: var(--accent); }
/* Phase C — mirrors the rule above for the ARIA-correct markup (§18: sortable
   columns need aria-sort, not just a visual class). Both selectors are kept
   so existing sortable tables keep working unchanged until Phase E adds
   aria-sort to their <th> markup. */
th[aria-sort="ascending"]  .sort-icon,
th[aria-sort="descending"] .sort-icon { opacity: 1; color: var(--accent); }

/* Phase C — expandable table rows (e.g. Receipts sub-rows). Toggle button
   lives in the row's first cell; the detail row is hidden until .open.
   The rotating chevron lives on a nested .icon span rather than the button
   itself, so this works both icon-only and with a label next to it (e.g.
   "▶ 3 items" — rotating the whole button would tip the text sideways too). */
.row-expand-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 5px;
  cursor: pointer;
  color: var(--ink-mid);
  font-size: 12px;
  font-weight: 600;
  padding: 3px 9px;
  transition: color .15s ease, border-color .15s ease;
}
.row-expand-toggle .icon { display: inline-block; font-size: 10px; transition: transform .15s ease; }
.row-expand-toggle:hover { color: var(--accent); border-color: var(--accent); }
.row-expand-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.row-expand-toggle[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.row-expand-toggle[aria-expanded="true"] .icon { transform: rotate(90deg); }
tr.row-detail { display: none; }
tr.row-detail.open { display: table-row; }
tr.row-detail td { background: var(--paper); padding: 14px 16px 14px 40px; }

/* ─── Paid stat card ──────────────────────────────────────────────────────── */
.stat-card.blue .stat-value { color: var(--blue); }

/* ─── Inline bar chart ───────────────────────────────────────────────────── */
.bar-chart { display: flex; align-items: flex-end; gap: 12px; height: 160px; }

/* ─── Two/three column layout helpers ───────────────────────────────────── */
.three-col { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }

/* ─── Signal rule ─────────────────────────────────────────────────────────
   Signature element: a 3px red rule across the top of the topbar, echoing
   the reference screenshot's red bar under the site header. app.css loads
   after this file but does not set border-top on .topbar, so this survives
   the cascade untouched. ────────────────────────────────────────────────── */
.topbar { border-top: 3px solid var(--accent); }

/* ─── Item picker (Purchase Order line-item search combobox) ────────────────
   Phase 1 of the Item Selector redesign — see
   ITEM_SELECTOR_REDESIGN_ANALYSIS.md. Markup/behavior in js/item-picker.js.
   ────────────────────────────────────────────────────────────────────────── */
.item-picker-cell { position: relative; }
.item-picker-input.is-invalid { border-color: var(--red); box-shadow: 0 0 0 3px rgba(232,57,75,.18); }

.item-picker-list {
  display: none;
  position: absolute;
  z-index: 40;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  max-height: 280px;
  overflow-y: auto;
}

.item-picker-option {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.item-picker-option:last-child { border-bottom: none; }
.item-picker-option:hover,
.item-picker-option.active { background: rgba(232,57,75,.1); }

.ip-opt-main { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.ip-opt-number { font-family: var(--font-mono); font-weight: 600; color: var(--ink); white-space: nowrap; }
.ip-opt-desc   { color: var(--ink-mid); font-size: 13px; }
.ip-opt-meta   { margin-top: 2px; color: var(--ink-soft); font-size: 11px; font-family: var(--font-mono); letter-spacing: .02em; }

.item-picker-list mark {
  background: rgba(232,57,75,.28);
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
}

.item-picker-empty { padding: 12px; color: var(--ink-soft); font-size: 13px; }

/* quick-item-search.js (Inventory Adjust/Transfer/filter fields) — reuses
   .item-picker-input/.item-picker-list/.item-picker-option/etc. above, but
   with its own cell wrapper (not .item-picker-cell) since it has no search
   button appended and shouldn't inherit that 34px right padding. */
.quick-item-cell { position: relative; }

/* Advanced-search entry point — appended into every cell by item-picker.js
   itself (see attach()), not part of the view markup. */
.item-picker-cell .item-picker-input { padding-right: 34px; }
.item-picker-search-btn {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 30px;
  border: none;
  border-radius: 0 var(--radius) var(--radius) 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.item-picker-search-btn:hover,
.item-picker-search-btn:focus-visible { color: var(--accent); background: rgba(232,57,75,.08); }
.item-picker-search-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ─── Vendor picker (PO header Vendor field search combobox) ────────────────
   Same UX as the Item picker above, applied to the single Vendor field
   (see the user's request to extend the redesign there) — kept in its own
   class namespace rather than reusing .item-picker-* so the two components
   stay fully independent (different data source: item-picker.js searches
   the server per keystroke since the catalog is ~8,800 rows; vendor-picker.js
   filters a small in-memory list — see VendorModel::getAllActiveForPicker()).
   Markup/behavior in js/vendor-picker.js. ─────────────────────────────────── */
.vendor-picker-cell { position: relative; }
.vendor-picker-input.is-invalid { border-color: var(--red); box-shadow: 0 0 0 3px rgba(232,57,75,.18); }

.vendor-picker-list {
  display: none;
  position: absolute;
  z-index: 40;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  max-height: 280px;
  overflow-y: auto;
}

.vendor-picker-option {
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.vendor-picker-option:last-child { border-bottom: none; }
.vendor-picker-option:hover,
.vendor-picker-option.active { background: rgba(232,57,75,.1); }

.vp-opt-main { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.vp-opt-number { font-family: var(--font-mono); font-weight: 600; color: var(--ink); white-space: nowrap; }
.vp-opt-name   { color: var(--ink-mid); font-size: 13px; }
.vp-opt-meta   { margin-top: 2px; color: var(--ink-soft); font-size: 11px; font-family: var(--font-mono); letter-spacing: .02em; }

.vendor-picker-list mark {
  background: rgba(232,57,75,.28);
  color: inherit;
  border-radius: 2px;
  padding: 0 1px;
}

.vendor-picker-empty { padding: 12px; color: var(--ink-soft); font-size: 13px; }

/* Advanced-search entry point — appended into the cell by vendor-picker.js
   itself (see attach()), not part of the view markup. */
.vendor-picker-cell .vendor-picker-input { padding-right: 34px; }
.vendor-picker-search-btn {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 30px;
  border: none;
  border-radius: 0 var(--radius) var(--radius) 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.vendor-picker-search-btn:hover,
.vendor-picker-search-btn:focus-visible { color: var(--accent); background: rgba(232,57,75,.08); }
.vendor-picker-search-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ─── Advanced Search modal shell (Phase 2 — F2 key or the 🔍 button) ───────
   Shared by both js/item-picker.js's #itemAdvSearchModal and
   js/vendor-picker.js's #vendorAdvSearchModal (each a single instance
   injected into <body>, tagged with the .adv-search-modal class below) —
   reuses .modal / .modal-overlay / .modal-wide from the base modal styles
   above for the chrome, these rules are just the search-specific bits.
   ────────────────────────────────────────────────────────────────────────── */
.adv-search-modal .table-wrap { max-height: 50vh; overflow-y: auto; margin-bottom: 4px; }
.adv-filters { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 16px; }
.adv-search-modal th[data-sort] { cursor: pointer; user-select: none; white-space: nowrap; }
.adv-search-modal th[data-sort]:hover { color: var(--accent); }
.adv-row { cursor: pointer; }
.adv-row:hover { background: rgba(232,57,75,.06); }
.adv-pagination { display: flex; align-items: center; justify-content: center; gap: 4px; padding-top: 8px; }
.adv-page-indicator { margin: 0 10px; color: var(--ink-mid); font-size: 12px; }

/* ─── Table pagination (Prev / numbered window / Next) ──────────────────── */
.win-pagination { display: flex; align-items: center; justify-content: center; gap: 4px; flex-wrap: wrap; }
.win-ellipsis { color: var(--ink-soft); font-family: var(--font-mono); font-size: 12px; padding: 0 4px; user-select: none; }
.win-count { margin-left: 10px; color: var(--ink-soft); font-size: 11px; font-family: var(--font-mono); }
.win-pagination span.btn[aria-disabled="true"] { opacity: .35; cursor: not-allowed; }

/* ─── Receipt Unit Price Control (Phases 2-3) ───────────────────────────────
   js/receipt-price-control.js, on po_receipts/create.php. Unit Price starts
   readonly for everyone; users holding override_receipt_price additionally
   get the ✎ unlock button. #priceChangeConfirmModal / #priceChangeReviewModal /
   #priceHistoryPanelModal are single instances injected into <body> on first
   use — same lazy-modal pattern as .adv-search-modal above, reusing .modal /
   .modal-overlay / .modal-wide for the chrome. See
   RECEIPT_PRICE_CONTROL_ANALYSIS.md §4/§9/§16.
   ────────────────────────────────────────────────────────────────────────── */
.price-field-wrap { display: flex; align-items: center; gap: 6px; position: relative; }
.price-field-wrap .price-input { flex: 1 1 auto; min-width: 0; }
.price-input:read-only:not(.price-unlocked) { background: rgba(255,255,255,.03); color: var(--ink-mid); cursor: default; }
.price-input.price-modified { background: rgba(245,185,66,.12); border-color: var(--amber); box-shadow: 0 0 0 1px rgba(245,185,66,.35) inset; }
.price-edit-btn,
.price-history-btn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 13px;
}
.price-edit-btn:hover,
.price-edit-btn:focus-visible,
.price-history-btn:hover,
.price-history-btn:focus-visible { color: var(--accent); border-color: var(--accent); background: rgba(232,57,75,.08); }
.price-changed-badge { display: none; flex: 0 0 auto; color: var(--amber); font-size: 14px; }
.price-field-wrap.has-change .price-changed-badge { display: inline-flex; }

/* Price History Panel body (#phpBody, built by js/receipt-price-control.js) */
.php-last-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.php-stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; text-align: center; }
.php-stat { padding: 10px 6px; border: 1px solid var(--border); border-radius: var(--radius); }
.php-stat .value { font-size: 16px; font-weight: 700; color: var(--ink); }
.php-stat .label { font-size: 11px; color: var(--ink-soft); margin-top: 2px; }
#priceChangeReviewModal .table-wrap { max-height: 50vh; overflow-y: auto; margin-bottom: 4px; }
#priceChangeReviewModal input[type=text] { padding: 6px 8px; font-size: 12px; }

/* ─── Toast ───────────────────────────────────────────────────────────────── */
/* Phase C — new component (§18): non-blocking, corner-anchored confirmation
   for actions that today only get a full page reload + top flash banner.
   JS: window.AppToast.show(message, type, duration) — see js/app.js §7. */
.toast-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 320px;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  background: var(--surface-elev);
  border: 1px solid var(--border);
  border-left: 3px solid var(--ink-mid);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  font-size: 13px;
  color: var(--ink);
  animation: toast-in .2s ease;
}
.toast.toast-out { animation: toast-out .18s ease forwards; }
.toast-success { border-left-color: var(--green); }
.toast-error   { border-left-color: var(--red); }
.toast-warning { border-left-color: var(--amber); }
.toast-info    { border-left-color: var(--blue); }
.toast-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 13px;
  margin-left: auto;
  padding: 0 0 0 8px;
  line-height: 1.4;
}
.toast-close:hover { color: var(--ink); }
@keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out { from { opacity: 1; } to { opacity: 0; transform: translateY(4px); } }

/* ─── Skeleton ────────────────────────────────────────────────────────────── */
/* Phase C — new component (§18), for the handful of async panels (e.g. the
   Price History Panel, notification list) that currently show nothing while
   loading. Respects the prefers-reduced-motion block at the top of this
   file, which zeroes out animation-duration app-wide. */
.skeleton {
  background: linear-gradient(90deg, var(--surface) 25%, var(--surface-elev) 37%, var(--surface) 63%);
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
  border-radius: var(--radius);
}
.skeleton-text  { height: 12px; margin-bottom: 8px; width: 100%; }
.skeleton-text:last-child { margin-bottom: 0; width: 70%; }
.skeleton-block { height: 60px; width: 100%; }
.skeleton-circle { width: 32px; height: 32px; border-radius: 50%; }
@keyframes skeleton-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}

/* ─── Touch targets ───────────────────────────────────────────────────────── */
/* Phase C — §18: buttons need a minimum 44px touch height in any context
   reachable from a touch viewport. Scoped to pointer:coarse rather than a
   width breakpoint so it also covers touchscreen laptops/desktops. */
@media (pointer: coarse) {
  .btn, .bottom-tab, .hamburger, .topbar-btn, .input-unlock-btn, .row-expand-toggle {
    min-height: 44px;
  }
  .btn, .hamburger, .topbar-btn { min-width: 44px; }
}

/* ─── Skip link ───────────────────────────────────────────────────────────── */
/* Visually hidden until focused (keyboard/screen-reader users only); jumps
   straight to <main id="mainContent">, ahead of the sidebar/topbar. */
.skip-link {
  position: absolute;
  top: -48px;
  left: 12px;
  z-index: 1000;
  background: var(--accent);
  color: #fff;
  padding: 10px 16px;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: top .15s ease;
}
.skip-link:focus { top: 12px; }

/* ─── Off-canvas scrim (mobile drawer backdrop) ──────────────────────────── */
/* Present in the DOM at every width but inert/invisible above the mobile
   tier; only does anything once body.drawer-open is toggled by app.js. */
.sidebar-scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.5);
  z-index: 90;
  opacity: 0;
  transition: opacity .2s ease;
}

/* ─── Mobile bottom tab bar ───────────────────────────────────────────────── */
/* Hidden above the mobile tier — see the ≤767px query below for the shown
   state. Mirrors .nav-link's active styling with the accent color. */
.bottom-tabs {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 95;
  background: var(--ledger);
  border-top: 1px solid rgba(255,255,255,.1);
  padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
}
.bottom-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 1 1 0;
  background: none;
  border: none;
  color: rgba(255,255,255,.55);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  text-decoration: none;
  padding: 6px 2px;
  cursor: pointer;
}
.bottom-tab .icon   { font-size: 17px; line-height: 1; }
.bottom-tab.active  { color: #FFFFFF; }
.bottom-tab:focus-visible { outline: 2px solid rgba(255,255,255,.55); outline-offset: -2px; border-radius: var(--radius); }

/* ─── Responsive ─────────────────────────────────────────────────────────── */
/* Three tiers:
     desktop ≥1024px — full sidebar with labels (default styles above)
     tablet  768–1023px — icon-only sidebar rail, labels hidden (tooltip via title=)
     mobile  ≤767px — off-canvas drawer sidebar + bottom tab bar, replaces the
                       old single 700px breakpoint that just hid the sidebar
                       outright with no way to reopen it. */
@media (max-width: 1023px) {
  .app     { grid-template-columns: 72px 1fr; }
  .sidebar { padding: 24px 10px; }
  .sidebar-logo { text-align: center; padding: 0; }
  .sidebar-logo span { display: none; }
  .nav-section-label { display: none; }
  .nav-link { justify-content: center; padding: 10px 6px; }
  .nav-link .label { display: none; }
  .nav-user { display: none; }
}

/* ─── Mobile Card Table (expand/collapse) ────────────────────────────────────
   Generalized from the pattern first built for items/index.php. Opt-in per
   table (class="mobile-card-table" on the <table>, class="row-card" on each
   <tr>, and a dedicated first <td class="mobile-cell"> per row holding a
   .mobile-summary + collapsible .mobile-details) — column-priority hiding
   (.col-priority-low, above) stays the default for simpler tables; this is
   for tables where hiding columns still leaves too much to read from a
   horizontally-scrolled row. Toggle behavior is delegated globally in
   js/app.js §9, so no per-view script is needed. These two base rules hide
   the mobile-only markup at any width by default; the @media blocks below
   swap that per breakpoint. */
.mobile-summary,
.mobile-details { display: none; }

@media (max-width: 767px) {
  .app { grid-template-columns: 1fr; }

  .hamburger { display: inline-flex; }

  .sidebar {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: 240px;
    padding: 24px 14px;
    transform: translateX(-100%);
    transition: transform .2s ease;
    z-index: 100;
    box-shadow: var(--shadow-lg);
  }
  .sidebar-logo { text-align: left; padding: 0 10px; }
  .sidebar-logo span { display: inline; }
  .nav-section-label { display: block; }
  .nav-link { justify-content: flex-start; padding: 8px 10px; }
  .nav-link .label { display: inline; }
  .nav-user { display: block; }

  body.drawer-open .sidebar      { transform: translateX(0); }
  body.drawer-open .sidebar-scrim { display: block; opacity: 1; }

  .main       { padding: 20px 16px 84px; }
  .two-col    { grid-template-columns: 1fr; }
  .three-col  { grid-template-columns: 1fr; }
  .dashboard-grid { grid-template-columns: 1fr; }

  /* Phase D — verified at 320px per §15's viewport matrix: two 150px tiles
     plus a 12px gap (312px) already just fits 320, but leaves only 8px of
     breathing room against the .main padding below, so this nudges the
     minimum down slightly for a less cramped 2-per-row layout on the
     narrowest supported phones. */
  .stats-row  { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }

  .bottom-tabs { display: flex; }

  /* Phase E — §15's "column-priority hiding": on simple list tables (PO,
     Invoices, Checks, Items…), the lower-priority columns (called out per
     table via .col-priority-low on both the <th> and its matching <td>)
     drop out below mobile width instead of forcing horizontal scroll to
     read the columns that actually matter at a glance (number/name/amount/
     status). Table stays a real <table> — .table-wrap's existing
     overflow-x:auto remains the floor for any table that's still too wide
     after this. Also compacts every table's padding/font-size generally,
     per the reported issue that rows needed horizontal scroll to be
     legible on phone widths even before counting hidden columns. */
  .table-wrap table       { font-size: 12px; }
  .table-wrap th          { padding: 8px 10px; }
  .table-wrap td          { padding: 9px 10px; }
  .col-priority-low       { display: none; }

  /* Phase E — §15's "complex multi-line form" pattern: on phone widths,
     line-item entry tables (PO/Invoice line items — opt in with
     .line-items-table) switch from a horizontally-scrolling table row to
     one-item-at-a-time stacked fields, so editing Qty/Price never requires
     scrolling right. Reported directly against PO create's Line Items. */
  .line-items-table thead { display: none; }
  .line-items-table,
  .line-items-table tbody,
  .line-items-table tr,
  .line-items-table td {
    display: block;
    width: 100% !important;
  }
  .line-items-table { min-width: 0 !important; }
  .line-items-table tr.item-row {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    padding: 12px;
    margin-bottom: 12px;
  }
  .line-items-table td {
    padding: 6px 0;
    border-bottom: none;
  }
  .line-items-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--ink-soft);
    margin-bottom: 4px;
  }
  /* !important: some line-items-table instances set a fixed px width inline
     on the input itself (e.g. invoices/create.php's Qty This Invoice field,
     90px to fit its old table column) — that inline style otherwise beats
     any external rule and would leave a tiny 90px input sitting under a
     full-width label on the stacked mobile card. */
  .line-items-table input,
  .line-items-table select,
  .line-items-table .item-picker-cell { width: 100% !important; }
  .line-items-table td:last-child { padding-top: 4px; }
  .line-items-table td:last-child .remove-item { width: 100%; }

  /* Receivers "Unit Price" cell wraps the input with edit/history icon
     buttons in a flex row (.price-field-wrap, see po_receipts/create.php) —
     forcing the input itself to width:100% (the generic rule above) would
     push those buttons out past the card edge. Let flex handle the sizing
     instead, same as it already does on desktop. */
  .line-items-table .price-field-wrap { width: 100%; }
  .line-items-table .price-field-wrap input { width: auto !important; flex: 1 1 auto; }

  /* Phase F — full-screen search sheets: the Advanced Item/Vendor Search
     modal (js/item-picker.js, js/vendor-picker.js) is a centered 860px
     dialog with the results table capped at 50vh on desktop; below mobile
     width that left a cramped, double-scrolling box. Here it expands to
     fill the viewport so results get real room, with the header/filters/
     pagination/actions pinned and only the table itself scrolling. */
  .modal-overlay.adv-search-modal { padding: 0; align-items: stretch; }
  .adv-search-modal .modal.modal-wide {
    max-width: 100%;
    width: 100%;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
    padding: 16px;
    display: flex;
    flex-direction: column;
  }
  .adv-search-modal .modal > h3,
  .adv-search-modal .adv-filters,
  .adv-search-modal .adv-pagination,
  .adv-search-modal .modal-actions { flex: 0 0 auto; }
  .adv-search-modal .table-wrap { flex: 1 1 auto; max-height: none; }

  /* Mobile Card Table — see the component comment above .col-priority-low's
     media block for the opt-in markup this expects. */
  .mobile-card-table { border-collapse: separate; border-spacing: 0; }
  .mobile-card-table thead { display: none; }
  .mobile-card-table,
  .mobile-card-table tbody,
  .mobile-card-table tr,
  .mobile-card-table td { display: block; width: 100%; }
  .mobile-card-table tbody { display: grid; gap: 12px; }

  .mobile-card-table tr.row-card {
    position: relative;
    overflow: hidden;
    margin: 0;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow);
  }
  /* Hide the desktop cells — their data is represented by the mobile
     summary/details below instead. */
  .mobile-card-table tr.row-card > td { display: none; }
  .mobile-card-table tr.row-card > td.mobile-cell { display: block; border: 0; padding: 0; }

  /* purchase_orders/show.php's Receipts table nests a desktop-only expand/
     collapse sub-row (.row-expand-toggle / .row-detail, unrelated to this
     component) inside a .mobile-card-table. Its own toggle button lives in
     a regular (non-.mobile-cell) <td>, so it's already hidden on mobile by
     the rule above — but the sub-row itself is a sibling <tr>, not a <td>,
     so without this it would be forced open by the generic
     ".mobile-card-table tr { display:block }" rule above, regardless of its
     default-closed ".row-detail" state. The card's own mobile-details panel
     (opt-in content, not this sub-row) covers the same info on mobile. */
  .mobile-card-table tr.row-detail { display: none; }
  .mobile-card-table tr.row-detail.open { display: block; }

  .mobile-summary {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px 12px;
    align-items: start;
    padding: 14px;
    cursor: pointer;
    user-select: none;
  }
  .mobile-summary-main { min-width: 0; }
  .mobile-title {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    font-weight: 700;
    font-size: 14px;
    line-height: 1.3;
    overflow-wrap: anywhere;
  }
  .mobile-subtitle {
    margin-top: 3px;
    color: var(--ink-mid);
    font-size: 13px;
    line-height: 1.4;
    overflow-wrap: anywhere;
  }
  .mobile-summary-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 7px;
    text-align: right;
  }
  .mobile-chevron {
    display: inline-flex;
    width: 26px;
    height: 26px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    border-radius: 50%;
    color: var(--ink-mid);
    font-size: 14px;
    line-height: 1;
    transition: transform .18s ease, background .18s ease;
  }
  .row-card.is-expanded .mobile-chevron { transform: rotate(180deg); }

  .mobile-metrics {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    margin-top: 2px;
  }
  .mobile-metric {
    min-width: 0;
    padding: 8px 9px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: rgba(127,127,127,.035);
  }
  .mobile-metric-label {
    display: block;
    margin-bottom: 2px;
    color: var(--ink-soft);
    font-size: 10px;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: .03em;
  }
  .mobile-metric-value {
    display: block;
    min-width: 0;
    color: var(--ink);
    font-size: 13px;
    line-height: 1.3;
    overflow-wrap: anywhere;
  }

  .mobile-details { padding: 0 14px 14px; border-top: 1px solid var(--border); }
  .row-card.is-expanded .mobile-details { display: block; }
  .mobile-detail-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
  .mobile-detail {
    display: grid;
    grid-template-columns: 42% minmax(0, 58%);
    gap: 10px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    line-height: 1.35;
  }
  .mobile-detail:last-child { border-bottom: 0; }
  .mobile-detail-label { color: var(--ink-soft); font-weight: 500; }
  .mobile-detail-value { min-width: 0; color: var(--ink); overflow-wrap: anywhere; text-align: right; }
  /* Secondary line within a detail value — e.g. items' Purchasing/
     Manufacturing UOM ratio under the base Unit of Measure. */
  .mobile-detail-value .detail-sub { margin-top: 2px; color: var(--ink-soft); font-size: 10px; }

  .mobile-details-actions { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; margin-top: 12px; }
  .mobile-details-actions .btn { flex: 1 1 auto; min-width: 90px; }
  /* Opt-in for icon-only buttons (e.g. a 📈 history button) that shouldn't
     stretch to the same minimum width as labeled actions. */
  .mobile-details-actions .btn-compact { flex: 0 0 auto; min-width: 42px; }

  /* Prevent the outer wrapper/table-wrap from clipping a card's shadow or
     forcing horizontal scroll now that rows are stacked cards, not a wide
     table. Opt in by giving the wrapper div class="mobile-card-wrap". */
  .mobile-card-wrap,
  .mobile-card-wrap > .table-wrap { overflow: visible; }
}

@media (min-width: 768px) {
  .mobile-cell { display: none !important; }
}
