/* =========================
   Design Tokens (shared)
   ========================= */

:root {
  /* Core */
  --c-text: #333;
  --c-bg: #fff;

  /* Neutrals */
  --c-border: #777;
  --c-border-soft: #ddd;
  --c-muted: #666;
  /* Was #9a9a9a (~2.8:1 on white — below WCAG AA 4.5:1). Darkened to ~4.5:1
     for secondary text (timestamps/metadata). */
  --c-muted-2: #767676;

  /* Links */
  --c-link: #0066cc;

  /* Accent (neutral default; set per-view) */
  --c-accent: #111;

  /* ── Surfaces & semantic aliases ──
     These were referenced via var(--…) across templates but never defined,
     so their declarations were invalid (transparent backgrounds, non-mono
     text). Values match the inline fallbacks already used in those templates
     so the intended appearance is now applied everywhere, not just where a
     fallback happened to be typed. */
  --c-surface: #f5f5f5;
  --c-bg-alt: #f8f9fa;
  --c-bg-soft: #f9fafb;
  --c-primary: #2563eb;
  --c-danger: #c62828;
  --c-text-secondary: #555;
  --c-warning-bg: #fff8e1;
  --c-warning-border: #ffe082;
  --c-warning-text: #e65100;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* Radii */
  --r-sm: 0.25rem;
  --r-md: 0.5rem;
  --r-lg: 0.75rem;
  --r-xl: 1rem;

  /* Layout widths - responsive */
  --page-narrow: 32rem;      /* Mobile/forms: 512px */
  --page-wide: 64rem;        /* Tables/lists: 1024px - original */
  --page-max: 80rem;         /* Full-width views: 1280px */

  /* ── Subway Line Colors ──
     Task 3b (axe AA-contrast fixup, 2026-07-21): these vars back both the
     per-page <h1> accent (needs >=3:1 on white) AND, on any page whose
     .button--primary uses --c-accent, white button text (needs >=4.5:1 on
     white). Six shades were darkened (uniform RGB scaling — preserves hue
     and saturation exactly, only lightness drops) to clear 4.5:1 wherever a
     real .button--primary consumes that accent; the rest already cleared
     4.5:1 and are untouched. subway-red stays at its original shade: no
     .button--primary currently uses it (kickback_queue's white-text button
     is --btn-success, fixed separately below) and its original 4.05:1
     already clears the 3:1 h1 floor. No spec references these hex values by
     name; the full old/new ratio table lives in the commit message that
     introduced this block (git log -S subway-orange -- static/css/main.css). */
  --subway-orange:   #CA4F14;   /* Receiving — was #FF6319 (2.98:1 h1 / N/A btn), now 4.53:1 (button used in receive_batch.html) */
  --subway-blue:     #0139A6;   /* Delivery tracking — 9.83:1, unchanged */
  --subway-yellow:   #8F7306;   /* Requesting/Warehouses — was #FCCC0B (1.52:1), now 4.54:1 (button used in warehouse_management.html) */
  --subway-magenta:  #B934AD;   /* Outgoing/BOL — 5.05:1, unchanged */
  --subway-green:    #018938;   /* Completing — was #01933C (4.01:1), now 4.53:1 (button used in complete_delivery.html) */
  --subway-gray:     #757679;   /* Inventory views — was #A7A9AC (2.36:1), now 4.54:1 (button used in batch_detail.html) */
  --subway-brown:    #996634;   /* User management — 4.88:1, unchanged */
  --subway-purple:   #6E3667;   /* Invoicing — 8.80:1, unchanged */
  --subway-teal:     #2A7B9B;   /* Site management — 4.77:1, unchanged */
  --subway-red:      #EE352E;   /* Review/Approval — 4.05:1, unchanged (no white-text button consumes it; see note above) */
  --subway-seagreen: #2D8654;   /* Contractor portal — was #2E8B57 (4.25:1), now 4.52:1 (button used in contractor_portal.html) */
  --subway-lime:     #4B8430;   /* Approval queue — was #6CBE45 (2.31:1, axe-flagged h1 + .button--primary), now 4.52:1 */

  /* ── Header Icon System ── */
  --icon-size:       2rem;      /* 32px desktop */
  --icon-size-sm:    1.75rem;   /* 28px mobile */
  --icon-color:      #4A5568;   /* Slate */
  --icon-hover:      #2D3748;   /* Dark slate */
  --icon-touch-min:  2.75rem;   /* 44px WCAG minimum */

  /* ── Message / Alert Colors ── */
  --msg-success-bg:     #d4edda;
  --msg-success-border: #28a745;
  --msg-success-text:   #155724;
  --msg-error-bg:       #f8d7da;
  --msg-error-border:   #dc3545;
  --msg-error-text:     #721c24;
  --msg-warning-bg:     #fff3cd;
  --msg-warning-border: #ffc107;
  --msg-warning-text:   #856404;
  --msg-info-bg:        #d1ecf1;
  --msg-info-border:    #17a2b8;
  --msg-info-text:      #0c5460;

  /* ── Badge Colors ── */
  --badge-danger:  #e41e3f;    /* Notification count */
  --badge-bg:      #e9ecef;    /* Role badge background */
  --badge-text:    #495057;    /* Role badge text */

  /* ── Action Button Semantic Colors ──
     Task 3b (axe AA-contrast fixup, 2026-07-21): white-text button
     backgrounds must reach >=4.5:1 on white.
     - --btn-success was #28a745 (3.13:1, axe-flagged on kickback_queue's
       "Edit & Resubmit"), darkened to 4.52:1. --btn-success-hover darkened
       to match (kept darker than the new base so hover still reads as a
       press-down state, not a lighten) — was #218838 (4.52:1, incidentally
       equal to the new base), now #1B702E (6.17:1).
     - --btn-warning/-hover keep their YELLOW background (#ffc107) because
       the button text on this variant is dark (#212529, see
       .button--warning), not white — out of scope for the white-text
       requirement, left untouched.
     - --btn-danger (4.53:1) and --btn-danger-hover (5.61:1) already clear
       4.5:1 — left untouched, ratios recorded here as proof.
     - --btn-disabled (#A7A9AC, 2.36:1) intentionally untouched: disabled
       controls are exempt from WCAG 1.4.11 (inactive UI components), and
       axe does not flag :disabled elements for color-contrast. */
  --btn-success:         #218838;
  --btn-success-hover:   #1B702E;
  --btn-success-disabled:#94d3a2;
  --btn-warning:         #ffc107;
  --btn-warning-hover:   #e0a800;
  --btn-danger:          #dc3545;
  --btn-danger-hover:    #c82333;
  --btn-disabled:        #A7A9AC;
}

/* =========================
   Messages / Alerts
   ========================= */

.messages {
  text-align: center;
  margin-bottom: 1rem;
}

.message {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: var(--r-md);
  font-weight: 500;
  margin-bottom: 0.5rem;
}

.message--success {
  background-color: var(--msg-success-bg);
  border: 1px solid var(--msg-success-border);
  color: var(--msg-success-text);
}

.message--error {
  background-color: var(--msg-error-bg);
  border: 1px solid var(--msg-error-border);
  color: var(--msg-error-text);
}

.message--warning {
  background-color: var(--msg-warning-bg);
  border: 1px solid var(--msg-warning-border);
  color: var(--msg-warning-text);
}

.message--info {
  background-color: var(--msg-info-bg);
  border: 1px solid var(--msg-info-border);
  color: var(--msg-info-text);
}

/* Promo banner — the ONE component for site promos (issue #306).

   Layout template (the rule, so future banners don't improvise):
     [ text ······················· ] [action] [×]
     [ detail (optional second row, full width)  ]
   - Text: left-aligned, semibold, flex-1 — the message IS the banner.
     No icons: emoji render inconsistently across platforms and read
     informal; the category is conveyed by the thin left accent rule.
   - Action: one compact solid button in the brand accent, immediately
     left of the dismiss. One action max — more belongs on a page.
   - Dismiss: ALWAYS the last element, far right, muted ×, ≥32px hit
     target (matches the trailing-dismiss convention users expect).
   - Detail: revealed-on-demand second row (regular weight, muted);
     never shown by default.
   - Surface: neutral white + standard border. Color appears ONLY in the
     3px left rule (category) and the action button (brand) — no tinted
     washes.
   At most one promo shows at a time (push outranks passkey — see the
   apex-promo-resolved handshake in the partials). */
.promo-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.6rem 0.55rem 0.85rem;
  background: var(--c-surface, #fff);
  border: 1px solid var(--c-border);
  border-left: 3px solid var(--c-border);
  border-radius: var(--r-sm);
  margin-bottom: 0.75rem;
  font-size: 0.88rem;
  color: var(--c-text, #1f2937);
}

/* display:flex would otherwise beat the UA [hidden] rule (equal specificity,
   later source order), leaving a hidden banner stuck visible. */
.promo-banner[hidden] {
  display: none;
}

.promo-banner__text {
  /* Basis 0 keeps long copy from claiming a full row of its own. */
  flex: 1 1 0;
  min-width: 0;
  font-weight: 600;
}

.promo-banner__action {
  border: none;
  cursor: pointer;
  border-radius: var(--r-sm);
  padding: 0.35rem 0.75rem;
  font-weight: 600;
  font-size: 0.8rem;
  white-space: nowrap;
  color: #fff;
  background: var(--c-accent);
}

.promo-banner__dismiss {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.05rem;
  line-height: 1;
  min-width: 32px;
  min-height: 32px;
  padding: 0;
  color: var(--c-muted);
}

.promo-banner__dismiss:hover {
  color: var(--c-text, #1f2937);
}

.promo-banner__detail {
  flex-basis: 100%;
  font-size: 0.82rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--c-muted);
}

.promo-banner__detail--error {
  color: #991b1b;
}

/* Category = left rule only. */
.promo-banner--push {
  border-left-color: var(--c-accent);
}

.promo-banner--passkey {
  border-left-color: #16a34a;
}

.warning-banner {
  padding: 0.75rem 1rem;
  border-radius: var(--r-md);
  background-color: var(--msg-warning-bg);
  border: 1px solid var(--msg-warning-border);
  color: var(--msg-warning-text);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* =========================
   View Accent Overrides
   (NYC subway palette idea)
   ========================= */

/* Default (neutral) */
body {
  --c-accent: #111;
}

/* Receive Batch */
body#view-receive-batch {
  --c-accent: var(--subway-orange);
}

/* Upcoming Deliveries */
body#view-upcoming-deliveries {
  --c-accent: var(--subway-blue);
}

/* Batch Status */
body#view-batch-status {
  --c-accent: var(--subway-blue);
}

/* Request Delivery */
body#view-request-delivery {
  --c-accent: var(--subway-yellow);
}

/* Record Outgoing BOL */
body#view-create-outgoing-bol,
body#view-bol-viewer {
  --c-accent: var(--subway-magenta);
}

body#view-create-outgoing-bol .truck-contents h3,
body#view-create-outgoing-bol .truck-contents-header,
body#view-create-outgoing-bol .form-section-title {
  border-top: none;
  padding-top: 0;
}

body#view-create-outgoing-bol .print-button {
  margin: 1.5rem 0 0;
  background-color: var(--c-accent);
  color: #fff;
  border: none;
  cursor: pointer;
  padding: 0.8rem 1.5rem;
  font-weight: 600;
  border-radius: var(--r-md);
}

body#view-create-outgoing-bol .print-button:disabled {
  background-color: #A7A9AC;
  cursor: not-allowed;
  opacity: 0.7;
}

body#view-create-outgoing-bol .col-requested,
body#view-create-outgoing-bol .col-shipped,
body#view-create-outgoing-bol .col-remaining {
  text-align: center;
}

/* Complete Delivery */
body#view-complete-delivery {
  --c-accent: var(--subway-green);
}

/* View Batches / Inventory (neutral views) */
body#view-batches,
body#view-batch-detail {
  --c-accent: var(--subway-gray);
}

/* View Users / User Detail */
body#view-users,
body#view-user-detail {
  --c-accent: var(--subway-brown);
}

body#view-users .table-card {
  border: none;
  border-radius: 0;
}

body#view-users .email-link {
  text-decoration: underline;
  color: var(--c-accent);
}

body#view-users .users-table {
  margin-top: 0;
}

/* Create Invoice */
body#view-create-invoice {
  --c-accent: var(--subway-purple);
}

body#view-create-invoice .button.button--primary,
body#view-create-invoice .generate-btn {
  background-color: var(--c-accent);
  color: #fff;
  border: none;
  cursor: pointer;
  padding: 0.8rem 1.5rem;
  font-weight: 600;
  border-radius: var(--r-md);
}

body#view-create-invoice .context-info h3 {
  padding-top: 0;
}

/* #315 PR5 Task 8: was #A7A9AC (2.36:1 on white); var(--c-muted) is the
   same token .step's inactive-label color already uses, 5.74:1. */
body#view-create-invoice .step.completed .step-number {
  background-color: var(--c-muted);
  color: #fff;
}

body#view-create-invoice .line-items-table .text-center,
body#view-create-invoice .line-items-table .qty-cell,
body#view-create-invoice .line-items-table .days-cell {
  text-align: center;
}

body#view-create-invoice .line-items-table input[type="number"] {
  padding: 0.2rem;
}

body#view-create-invoice .line-note-display {
  white-space: nowrap;
}

body#view-create-invoice .details-panel h2 {
  padding-top: 0;
}

body#view-create-invoice .details-panel textarea {
  width: 100%;
}

/* Approval Queue (review workflow) */
body#view-approval-queue {
  --c-accent: var(--subway-lime);
}

/* Approval Queue mobile overrides removed 2026-07-20: the squeezed-table
   rules (nowrap identity, 120px ellipsis description) predate card mode and
   were truncating card content; the stats-bar rules died with the stats
   tiles (issue #315 walk feedback). */

/* Kickback Queue (review workflow) */
body#view-kickback-queue {
  --c-accent: var(--subway-red);
}

/* Contractor Portal */
body#view-contractor-portal {
  --c-accent: var(--subway-seagreen);
}

/* Site Management */
body#view-site-management {
  --c-accent: var(--subway-teal);
}

/* Warehouse Management */
body#view-warehouse-management {
  --c-accent: var(--subway-yellow);
}

body#view-site-management .contractor-meta {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
}

body#view-site-management .billing-info {
  border-top: none;
}

body#view-site-management .assign-dropdown {
  padding: 0.3rem 1.8rem 0.3rem 0.5rem;
}

body#view-site-management .edit-link {
  padding: 0 0.6rem;
  /* --subway-teal (#2A7B9B) is only 4.37:1 on the contractor card's
     --c-surface (#f5f5f5) background -- its documented 4.77:1 assumes a
     white page background, which this card isn't. Pre-existing gap
     surfaced by the PR8 admin-screens smoke test's axe scan (issue #315
     follow-on); darkened just for this placement (5.3:1 on #f5f5f5)
     rather than touching the shared --subway-teal token, which stays
     correct everywhere else it's used (add-btn background, dashboard
     tile, both white-on-teal). */
  color: #1f6d8c;
}

body#view-site-management .add-btn {
  background-color: var(--c-accent);
  color: #fff;
  border: none;
  padding: 0.8rem 1.5rem;
  font-weight: 600;
  border-radius: var(--r-md);
  cursor: pointer;
}

/* View Invoices / Invoice Preview */
body#view-invoices,
body#view-invoice-preview {
  --c-accent: #000;
}

/* Invoices List */
body#view-invoices .filter-search {
  width: 100%;
  max-width: none;
}

body#view-invoices .invoices-table-wrap {
  overflow-x: auto;
}

body#view-invoices .invoices-table {
  width: 100%;
  table-layout: auto;
}

body#view-invoices .invoice-link {
  text-decoration: underline;
  color: var(--c-accent);
}

/* Invoice Preview */
body#view-invoice-preview .invoice-actions {
  display: flex;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
}

body#view-invoice-preview .invoice-actions .button,
body#view-invoice-preview .invoice-actions a,
body#view-invoice-preview .invoice-actions button {
  background-color: var(--c-accent);
  color: #fff;
  border: none;
  padding: 0.8rem 1.5rem;
  font-weight: 600;
  border-radius: var(--r-md);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

/* Profile */
body#user-profile {
  --c-accent: #000;
}

/* Edit User / Add User */
body#view-create-user {
  --c-accent: var(--subway-brown);
}



/* =========================
   Base
   ========================= */

body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  color: var(--c-text);
  background-color: var(--c-bg);
}

/* =========================
   Layout & Page Structure
   ========================= */

.page {
  max-width: var(--page-narrow);
  margin: 0 auto;
  padding: 1.5rem 1rem 2rem;
}

.page--wide {
  max-width: var(--page-wide);
}

.page--max {
  max-width: var(--page-max);
}

/* Tablet+ : more breathing room */
@media (min-width: 768px) {
  .page {
    padding: 2rem 2.5rem 3rem;
  }

  /* Forms can be wider on tablet/desktop */
  .page:not(.page--wide):not(.page--max) {
    max-width: 40rem; /* 640px - better for forms */
  }
}

/* Desktop: generous padding */
@media (min-width: 1024px) {
  .page {
    padding: 2.5rem 3rem 4rem;
  }
}

/* Large desktop: even more space for wide pages */
@media (min-width: 1440px) {
  .page--wide,
  .page--max {
    padding-left: 4rem;
    padding-right: 4rem;
  }
}

/* Small screens: constrain wide pages */
@media (max-width: 560px) {
  .page--wide {
    max-width: 100%;
  }
}

/* =========================
   Header
   ========================= */

.header {
  position: relative;
  text-align: center;
  margin-bottom: 1.25rem;
}

.logo {
  max-width: 8rem;
  margin: 0 auto;
  display: block;
}

.logo-link {
  display: block;
  margin: 0 auto;
  width: fit-content;
}

/* Header actions row (bell + sign out) */
.header-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

/* Notification bell */
.notification-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--icon-color);
  text-decoration: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.notification-bell:hover {
  color: var(--icon-hover);
}

.notification-bell svg {
  width: var(--icon-size);
  height: var(--icon-size);
}

/* Badge count */
.notification-badge {
  position: absolute;
  bottom: 2px;
  right: -6px;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--badge-danger);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.25rem;
  text-align: center;
  text-decoration: none;
  pointer-events: none;
}

.notification-badge:empty {
  display: none;
}

/* Notification Dropdown */
.notification-bell-wrap {
  position: relative;
}

.notification-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  width: 320px;
  max-height: 400px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  z-index: 100;
}

.notification-dropdown-item {
  display: block;
  padding: 0.75rem 1rem;
  text-decoration: none;
  color: inherit;
  border-bottom: 1px solid var(--c-border);
}

.notification-dropdown-item:hover {
  background: #f5f5f5;
}

.notification-dropdown-item--unread {
  background: #e8f4fd;
}

.notification-dropdown-title {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0 0 0.15rem;
}

.notification-dropdown-message {
  font-size: 0.8rem;
  color: var(--c-muted);
  margin: 0 0 0.15rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.notification-dropdown-time {
  font-size: 0.75rem;
  color: #999;
  margin: 0;
}

.notification-dropdown-more {
  padding: 0.5rem 1rem;
  font-size: 0.8rem;
  color: var(--c-muted);
  border-bottom: 1px solid var(--c-border);
}

.notification-dropdown-viewall {
  display: block;
  padding: 0.75rem 1rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--c-accent);
  text-decoration: none;
  font-weight: 600;
}

.notification-dropdown-viewall:hover {
  background: #f5f5f5;
}

.notification-dropdown-empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--c-muted);
  font-size: 0.85rem;
}

@media (max-width: 640px) {
  .notification-dropdown {
    position: fixed;
    top: auto;
    right: 0.5rem;
    left: 0.5rem;
    width: auto;
  }
}

.sign-out {
  font-size: 0.9rem;
  text-decoration: underline;
  color: var(--c-link);
}

.user-menu {
  position: relative;
}

.user-menu-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--icon-color);
  font-size: 0.9rem;
  padding: 0.5rem;
  border-radius: var(--r-md);
  min-height: var(--icon-touch-min);
}

.user-menu-toggle:hover {
  color: var(--icon-hover);
  background: rgba(0, 0, 0, 0.05);
}

.user-menu-toggle svg {
  width: var(--icon-size);
  height: var(--icon-size);
}

@media (max-width: 640px) {
  .notification-bell svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
  }
  .user-menu-toggle svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
  }
}

.user-menu-name {
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu-dropdown {
  position: absolute;
  top: 100%;
  /* left-anchored: the user menu sits at the LEFT of .header-actions, so a
     right: 0 anchor hung the dropdown off the viewport edge (pre-#315 rule
     from #275; Matt caught the clip 2026-07-21). */
  left: 0;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  min-width: 140px;
  z-index: 100;
  overflow: hidden;
}

.user-menu-dropdown a,
.user-menu-dropdown button {
  display: block;
  width: 100%;
  padding: 0.6rem 1rem;
  font-size: 0.9rem;
  color: var(--c-text);
  text-decoration: none;
  text-align: left;
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
}

.user-menu-dropdown a:hover,
.user-menu-dropdown button:hover {
  background: #f5f5f5;
}

.user-menu-dropdown form {
  margin: 0;
}

.header h1 {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--c-accent);
  margin: 0;
}

.divider {
  height: 1px;
  background-color: var(--c-accent);
  margin: 1.25rem 0;
}

/* Desktop header improvements */
@media (min-width: 768px) {
  .header {
    margin-bottom: 1.5rem;
  }

  .logo {
    max-width: 10rem;
  }

  .header h1 {
    font-size: 1.75rem;
  }

  .divider {
    height: 2px;
    margin: 1.5rem 0;
  }
}

@media (min-width: 1024px) {
  .header {
    margin-bottom: 2rem;
  }

  .header h1 {
    font-size: 2rem;
  }

  .divider {
    margin: 1.75rem 0;
  }
}

/* =========================
   Form
   ========================= */

.form {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.form-group-row {
  display: flex;
  flex-direction: row;
  gap: 1rem;
  width: 100%;
}

.form-group-row > .form-group {
  flex: 1 1 0;
  min-width: 0;
}

label {
  font-size: 0.95rem;
  font-weight: 600;
}

input,
select,
textarea {
  width: 100%;
  max-width: 100%;
  padding: 0.75rem;
  font-size: 1rem;
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  box-sizing: border-box;
}

/* Strip native Safari/iOS form control styling */
input,
select,
textarea,
button,
input[type="submit"],
input[type="button"],
input[type="reset"] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

/* Restore native appearance for checkboxes and radios */
input[type="checkbox"],
input[type="radio"] {
  appearance: auto;
  -webkit-appearance: auto;
  -moz-appearance: auto;
  width: auto;
}

select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23333' d='M1.41 0L6 4.58 10.59 0 12 1.41l-6 6-6-6z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 12px 8px;
  padding-right: 2.25rem;
  background-color: #fff;
}

/* Flatpickr inputs */
.flatpickr-input,
.flatpickr-alt-input,
input.flatpickr-input {
  width: 100% !important;
  max-width: 100% !important;
  box-sizing: border-box !important;
}

textarea {
  min-height: 6rem;
  resize: vertical;
}

input::placeholder,
textarea::placeholder {
  color: #aaa;
}

/* Desktop form improvements */
@media (min-width: 768px) {
  .form {
    gap: 1.5rem;
  }

  .form-group-row {
    gap: 1.5rem;
  }

  label {
    font-size: 1rem;
  }

  input,
  select,
  textarea {
    padding: 0.85rem 1rem;
  }
}

@media (min-width: 1024px) {
  .form {
    gap: 1.75rem;
  }

  .form-group-row {
    gap: 2rem;
  }
}

/* =========================
   Toolbar / Buttons
   ========================= */

/* ── Toolbar ──
   Filter/search/export bars. No primary concept, no reordering —
   .actions is for priority-ordered record actions, .toolbar is for
   controls. Stacks full-width below 768px. */
.toolbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  margin: 0.75rem 0 1rem;
}

@media (min-width: 768px) {
  .toolbar {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }

  .toolbar--end {
    justify-content: flex-end;
  }
}

/* ── Base Button ── */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.6rem 1.1rem;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.3;
  white-space: normal;
  text-align: center;
  text-decoration: none;
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  color: var(--c-text);
  background: transparent;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}

/* ── Variants ── */
.button--primary {
  background-color: var(--c-accent);
  border-color: var(--c-accent);
  color: #fff;
}

.button--primary:hover {
  background-image: linear-gradient(rgba(0,0,0,0.12), rgba(0,0,0,0.12));
}

.button--success {
  background-color: var(--btn-success);
  border-color: var(--btn-success);
  color: #fff;
}

.button--success:hover {
  background-color: var(--btn-success-hover);
  border-color: var(--btn-success-hover);
}

.button--warning {
  background-color: var(--btn-warning);
  border-color: var(--btn-warning);
  color: #212529;
}

.button--warning:hover {
  background-color: var(--btn-warning-hover);
  border-color: var(--btn-warning-hover);
}

.button--danger {
  background-color: var(--btn-danger);
  border-color: var(--btn-danger);
  color: #fff;
}

.button--danger:hover {
  background-color: var(--btn-danger-hover);
  border-color: var(--btn-danger-hover);
}

.button--secondary {
  background: transparent;
  border: 1px solid var(--c-border);
  color: var(--c-text);
}

.button--secondary:hover {
  background-color: #f5f5f5;
}

/* ── Focus (keyboard navigation) ── */
.button:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* ── Disabled States ── */
.button--primary:disabled,
.button--success:disabled,
.button--warning:disabled,
.button--danger:disabled {
  background-color: var(--btn-disabled);
  border-color: var(--btn-disabled);
  color: #fff;
  cursor: not-allowed;
  opacity: 0.7;
}

.button--secondary:disabled {
  color: var(--btn-disabled);
  border-color: var(--c-border-soft);
  cursor: not-allowed;
  opacity: 0.7;
}

/* ── Sizes ── */
/* 36px row-action size — the one exception to the 44px touch-target
   floor (table/row actions only; issue #315). */
.button--sm {
  min-height: 36px;
  padding: 0.35rem 0.7rem;
  font-size: 0.85rem;
}

/* ── Full-width variant ── */
.button--full {
  width: 100%;
}

/* ── Link-button ──
   Inline text action (toggles, expanders). Constant --c-link, NOT the
   per-section --c-accent — the same control must not change colour
   between pages. The hit area is enlarged by the ::after overlay below
   rather than by padding, so the text metrics stay put. Usable with or
   without the .button base class, so it carries its own focus ring.

   AMENDED 2026-07-20 (PR 2, the freeze gate). This used to add "so inline
   flow is undisturbed" — no longer true, and the correction is the point:
   the overlay now reserves matching vertical margin, because costing zero
   layout space is exactly what let it steal taps from the control above.
   `line-height` and `margin` below are load-bearing for that overlay — read
   the arithmetic at ::after before changing either. */
.button--link {
  position: relative;
  min-height: 0;
  /* 0.8rem × 1.25 = 16px line box. Pinned rather than inherited so the
     overlay arithmetic below is deterministic at every call site. */
  line-height: 1.25;
  /* (44 − 16) / 2 = 14px. Reserves exactly the space the 44px overlay
     occupies, so the overlay never escapes this element's margin box. */
  margin: 14px 0;
  padding: 0;
  border: none;
  background: none;
  font-size: 0.8rem;
  color: var(--c-link);
  text-decoration: underline;
  cursor: pointer;
}

/* The visible control is one line of 0.8rem text (16px line box) — far under
   the 44px touch floor, closed with an overlay rather than padding so the
   text metrics stay put. Vertical only: these sit inline among other text,
   and a horizontal expansion would swallow taps meant for their neighbours.

   AMENDED 2026-07-20 (PR 2, the freeze gate). The overlay used to cost no
   layout space at all, and that was the bug: `.button--link` is
   `position: relative`, so the overlay paints in the positioned layer above
   in-flow siblings and captured their taps. It overhangs the 16px line box
   by (44 − 16) / 2 = 14px in each direction, while three of the four
   batch_detail call sites sit only 8px (`margin-top: 0.5rem`) below a
   control — a button row at :1820, a <select> at :2407 and :2459 — leaving
   the bottom ~6px of each of those dead to touch.

   THE TRADE CHOSEN: pay the layout space rather than shrink the target. The
   14px vertical margin on `.button--link` above makes the element reserve
   exactly the 44px it claims, so the overlay is flush with its own margin
   box and cannot reach a sibling. Shrinking the overlay to the ~24px that
   fit in the old zero-margin box was rejected — the 44px floor is the whole
   reason the overlay exists, and a 24px target fails it silently.

   ARITHMETIC (all four call sites, margins do not collapse — the element is
   inline-level, and at :2407/:2459 it is a flex item):
     border box       = 16px (line box; padding 0, border none)
     overlay          = 44px, `top: 50%` + `translateY(-50%)`
     overlay top      = border-box top + 8 − 22 = border-box top − 14px
     margin box top   = border-box top − 14px   → flush, no overhang
   Same by symmetry at the bottom. Changing `line-height`, `font-size` or
   `margin` above without redoing this breaks containment. */
.button--link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* Matches .button:focus-visible. Declared separately because .button--link is
   valid standalone (it resets min-height/padding, so it does not require the
   .button base) — without this, a keyboard user gets no focus indicator on
   those call sites. The ring tracks the visible text, not the 44px overlay. */
.button--link:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* ── Modal close ── */
.modal-close {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: none;
  background: none;
  font-size: 1.25rem;
  color: var(--c-muted);
  cursor: pointer;
  /* Modal headers are flex rows (title + close); without this a
     two-line title on mobile can shrink the close button below its
     44px target. */
  flex-shrink: 0;
}

.modal-close:hover {
  border-radius: var(--r-sm);
  background: #f0f0f0;
  color: #333;
}

/* Call sites use .modal-close without the .button base, so it carries its
   own ring (same reasoning as .button--link). */
.modal-close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* ── Action cluster ──
   Priority-ordered actions on a record. DOM order is ALWAYS priority
   order, so THE FIRST CHILD IS THE PRIMARY — no marker class. Mobile:
   the primary spans the full width and secondaries pair in a 2-column
   grid; an unpaired last secondary lands on an EVEN child index and
   spans. Desktop: row-reverse renders the primary rightmost while
   screen-reader order stays priority-first.

   AMENDED 2026-07-20 (PR 2, the freeze gate). This previously required an
   explicit .actions__primary marker. That broke on role-gated clusters:
   batch_detail's toolbar has three permission flags, so which button is
   primary varies at runtime, and expressing that needed a chain of
   {% if %}-gated marker classes — which a static guard reads as three
   primaries. Deriving the primary positionally makes every flag
   combination correct by construction and deletes that whole class of
   bug.

   The trade accepted, stated honestly: what is true by definition is that
   the FIRST CHILD IS STYLED AS THE PRIMARY. It is NOT true that the first
   child IS the highest-priority action — that is now an authoring
   convention with no marker to check it against. Under the retired marker,
   putting Cancel first failed a guard; now it silently renders Cancel
   full-width, desktop-rightmost, in the primary's suite. The residual
   static coverage is ActionsPrimaryGuardTest, which asserts no cluster
   leads with a .button--secondary — a secondary as the cluster primary is
   always wrong and is statically checkable. Ordering between two
   non-secondary actions is not, and rests on review. */
.actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-top: 1rem;
}

.actions .button {
  width: 100%;
}

.actions > :first-child {
  grid-column: 1 / -1;
}

/* A form-wrapped primary (submit actions need their own form) needs no rule
   of its own: `.actions .button` above is a descendant selector and already
   matches it. A `.actions > :first-child > .button` child rule was removed
   2026-07-20 — it added nothing on mobile and, sitting outside the media
   query at specificity (0,3,0), it outranked the (0,2,0)
   `.actions .button { width: auto }` below, pinning form-wrapped primaries
   to 100% on desktop. */

.actions > :not(:first-child):last-child:nth-child(even) {
  grid-column: 1 / -1;
}

@media (min-width: 768px) {
  .actions {
    display: flex;
    flex-direction: row-reverse;
    justify-content: flex-start;
    gap: 0.75rem;
  }

  .actions .button {
    width: auto;
  }
}

/* A cluster used as a modal footer restores the padding and separator the
   deleted `.modal-footer` used to supply. `.modal-overlay .modal` is
   deliberately `padding: 0` (sticky-header fix, see that rule), so each
   region — header, body, footer — pads itself; `.actions` alone supplies
   only `margin-top`, which left these buttons flush to the card edge with
   no rule above them.

   Scoped to `footer.actions`, not a bare descendant selector, for two
   reasons found by reading the markup: two of the four footers sit inside
   the modal's <form> rather than directly under `.modal`, so a child
   combinator misses them; and the request-delivery modal nests a policy
   popup whose own `<div class="actions">` a bare descendant selector would
   wrongly pad. `<footer class="actions">` matches exactly the four real
   footers.

   `.modal-backdrop` modals (the _confirm_submit_modal.html include, the
   photo-delete confirm) are NOT covered and need no rule: they keep the
   global `.modal { padding: 1.5rem }`, so their clusters are already
   inset, and they carry no header to separate from. */
.modal-overlay .modal footer.actions {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--c-border);
  margin-top: 0;
}

/* ── Tabs ──
   Switches which set of records is listed. Underline, no fill — tabs
   must read lighter than actions. Scrolls horizontally on mobile (no
   overflow affordance — a right-edge mask fade was tried and dropped;
   mask-image is positioned against the element box, not the scroll
   content, so it never cleared after scrolling fully right). */
.tabs {
  display: flex;
  gap: 1.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 2px solid var(--c-border-soft);
  overflow-x: auto;
  scrollbar-width: none;
}

/* Overflow affordance (issue #315, amended at the PR 2 freeze gate). The
   scrollbar is hidden, so a clean cut between tabs reads as "no more tabs" —
   these fades signal hidden content per side. The classes are toggled by the
   tabsScroll Alpine component in base.html on scroll; gating by class is what
   the earlier CSS-only mask lacked (it stayed faded at scroll-end, which is
   why PR 2's review removed it). mask-image is pinned to the element box, so
   the fade sits at the visible edge regardless of scroll position — exactly
   right once something else decides WHEN it shows. */
.tabs--more-right {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}

.tabs--more-left {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - 24px), transparent);
}

.tabs--more-left.tabs--more-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}

.tabs::-webkit-scrollbar {
  display: none;
}

.tabs__tab {
  /* Buttons are first-class consumers of this primitive, not just <a>s.
     The global `appearance: none` reset above does not strip Chrome's UA
     button background-color/border, so reset them explicitly here.
     background/border must precede border-bottom or the shorthand reset
     wipes out the underline; font: inherit must precede font-size/
     font-weight for the same reason. (Discovered #315 PR 8 preview walk;
     billing_setup shipped with this leak since PR 5.) */
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 44px;
  padding: 0.5rem 0.125rem;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--c-muted);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.tabs__tab:hover {
  color: var(--c-text);
}

.tabs__tab.active {
  border-bottom-color: var(--c-accent);
  color: var(--c-text);
  font-weight: 600;
}

.tabs__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.25rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: var(--badge-bg);
  color: var(--badge-text);
  font-size: 0.7rem;
  font-weight: 700;
}

/* ── Segmented filter ──
   Narrows the set the tabs already chose (Active | Inactive | All).
   ~32px and lighter than .tabs — the nesting order must stay visible. */
.segmented {
  display: inline-flex;
  overflow: hidden;
  border: 1px solid var(--c-border-soft);
  border-radius: var(--r-md);
}

.segmented__btn {
  display: inline-flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 0.3rem 0.75rem;
  border-right: 1px solid var(--c-border-soft);
  background: var(--c-bg);
  color: var(--c-text);
  font-size: 0.8rem;
  text-decoration: none;
  cursor: pointer;
}

.segmented__btn:last-child {
  border-right: none;
}

.segmented__btn:hover {
  background: #f5f5f5;
}

.segmented__btn.active {
  background: var(--badge-bg);
  color: var(--badge-text);
  font-weight: 600;
}

/* =========================
   Filter-toolbar component (issue #315, PR 3)
   =========================
   The five Tier 1 list pages — plus contractor_portal, a SIXTH carrier
   found in review — held this block as byte-identical page <style> copies
   (verified by hash). Eight of the nine rules are hoisted here once
   (.filter-actions is not: the shared shell uses the .toolbar primitive for
   that row); each page's adoption commit deletes its local copy —
   contractor_portal's dies with PR 4. Two rules need care:
   - A bare .filter-row global also exists (align-items: stretch; currently
     below this block — specificity decides, not order). site_management
     uses it un-nested and is unaffected by the scoped rule.
     contractor_portal DOES nest .filter-row inside .filter-section, so the
     scoped center rule matches it — harmlessly, because its local copy
     already sets center; PR 4 can delete that copy without a visual change.
   - .filter-toggle also exists globally (cursor only); the page copy adds
     white-space, merged below under the same scoping. */
.filter-section {
  margin-bottom: 1.25rem;
}

.filter-section .filter-row {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

/* Below 768px the nowrap row crushed the search input to ~31px beside the
   buttons — the audit's original View Batches complaint, faithfully
   preserved by the byte-identical page copies this component replaced.
   Search takes its own full-width row; the buttons wrap beneath at 44px.
   (Found on the PR 3 preview walk.) */
@media (max-width: 767px) {
  .filter-section .filter-row {
    flex-wrap: wrap;
  }

  .filter-section .filter-search {
    flex: 1 1 100%;
  }
}

.filter-search {
  flex: 1;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--c-border);
  border-radius: 6px;
}

.filter-section .filter-toggle {
  white-space: nowrap;
}

.filter-panel {
  margin-top: 1rem;
  padding: 1rem;
  background: #f9f9f9;
  border-radius: 6px;
  border: 1px solid var(--c-border);
}

.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1rem;
}

.filter-item label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.filter-item select {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid var(--c-border);
  border-radius: 4px;
}

/* =========================
   Card-mode list tables (issue #315, PR 3)
   =========================
   Tier 1 list pages render ONE markup tree: a real <table> that desktop
   shows as-is and mobile restructures into cards purely with CSS. Opt in
   with .list-table--cards on the <table>. Every <td> must carry
   data-label="..." (guarded by CardTableLabelGuardTest) except cells
   opting out via .card-cell--bare (identity cells whose content needs no
   caption). Card-only content (footers, action rows) lives INSIDE an
   existing td, marked .card-only; desktop-only cells are hidden in card
   mode with .desktop-only. Column sorting lives in the sr-only thead (PR4
   Task 3: sr-only clip, not display:none, so headers stay in the
   accessibility tree) and is deliberately unavailable to sighted mobile
   users (design: mobile is lookup, not triage) — see the thead rule below
   for how a focused sort button un-clips instead of trapping keyboard
   focus on an invisible control. */

.card-only {
  display: none;
}

/* Shared filter extras (#315): the date-range filter hides rows by class so
   it composes with each page's own inline display toggling — !important so
   an out-of-range row stays hidden even when the page JS shows it.
   [data-filter-extras-table] mirrors the JS opt-in (PR 4): contractor_portal
   keeps its own bespoke card CSS rather than adopting .list-table--cards, so
   the visibility rule needs the same explicit second selector or the
   date-range control would render but silently do nothing on that table. */
.list-table--cards tr.date-filtered,
[data-filter-extras-table] tr.date-filtered {
  display: none !important;
}

/* The sort select is mobile-only — desktop keeps its column-header sort. */
@media (min-width: 768px) {
  .filter-item--sort {
    display: none;
  }
}

@media (max-width: 767px) {
  .list-table--cards {
    border-collapse: separate;
  }

  /* Visually hide the header row but keep it in the accessibility tree —
     ported from the contractor_portal.html card dialect (issue #315 PR4
     Task 2): the data-label ::before captions above are CSS-generated
     content, which screen readers don't announce reliably, so the real
     <th> text has to stay reachable instead of being display:none'd away. */
  .list-table--cards thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* The column-sort controls are real <button>s inside those <th>s
     (th[data-sort] .sort-header-btn, issue #258) — CSS has no way to pull
     an element out of the tab order (that's tabindex, an HTML/JS concern),
     so clipping the thead alone would leave sighted keyboard users tabbing
     onto an invisible 1px control with no visible focus target. Rather than
     that trap, :focus-within un-clips the whole header row the moment a
     sort button receives focus, so it becomes visible right when a keyboard
     user lands on it (and re-clips on blur). Screen-reader users already
     get the header text regardless, since it was never removed from the
     accessibility tree. */
  .list-table--cards thead:focus-within {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip: auto;
    white-space: normal;
  }

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

  .list-table--cards tbody tr {
    /* position: relative anchors the absolutely-placed status badge below. */
    display: block;
    position: relative;
    margin-top: 0.75rem;
    margin-bottom: 0.75rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--c-border-soft);
    border-radius: var(--r-md);
    background: var(--c-bg);
  }

  .list-table--cards tbody td {
    display: block;
    padding: 0.15rem 0;
    border: none;
    width: auto;
  }

  /* Field caption from the markup's own data-label, inline with the value
     (2026-07-20 density pass: a block caption cost a full line per field,
     capping scan density below 2 cards per screen). */
  .list-table--cards tbody td[data-label]::before {
    content: attr(data-label) ": ";
    display: inline;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--c-muted);
    margin-right: 0.25rem;
  }

  /* Identity/status cells render without a caption. */
  .list-table--cards tbody td.card-cell--bare::before {
    content: none;
  }

  /* Status badge joins the identity line, top-right — it is the card's
     second fact and was buried below the description. The identity cell
     reserves the corridor so long batch/PO identities wrap under, not
     behind, the badge. */
  .list-table--cards tbody td.card-cell--bare > .status-badge {
    position: absolute;
    top: 0.75rem;
    right: 1rem;
  }

  /* The identity cell is the first *rendered* cell: first-child on most
     pages, but aws_portal leads with a desktop-only Batch column, so the
     adjacent-sibling form covers identity cells behind hidden leaders. */
  .list-table--cards tbody td.card-cell--bare:first-child,
  .list-table--cards tbody td.desktop-only:first-child + td.card-cell--bare {
    padding-right: 9rem;
  }

  .list-table--cards .desktop-only {
    display: none;
  }

  /* revert, not block: span.card-only (inline prefixes/suffixes) must flow
     with their line; div.card-only (footers, action rows) stays block. */
  .list-table--cards .card-only {
    display: revert;
  }

  /* PO/batch identity segments wrap between, never inside, each other. */
  .list-table--cards .po-pair {
    white-space: nowrap;
  }

  .list-table--cards .card-footer {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--c-border-soft);
    font-size: 0.8rem;
    color: var(--c-muted);
  }
}

.filter-toggle,
button[type="button"] {
  cursor: pointer;
}

.filter-row {
  display: flex;
  gap: 0.75rem;
  align-items: stretch;
}

.remove-photo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.required-note {
  font-size: 0.85rem;
  margin: 0;
}

/* Upload */
.upload-button {
  display: block;
  width: 100%;
  padding: 0.75rem;
  font-size: 0.95rem;
  font-weight: 600;
  text-align: center;
  background-color: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  cursor: pointer;
}

/* Form Help Text */
.form-help {
  font-size: 0.85rem;
  color: var(--c-muted);
  margin-top: 0.25rem;
}

.approval-required-hint {
  /* #b45309 (amber-700), not #d97706 (amber-600): the lighter shade only
     hits 3.18:1 on white, below WCAG's 4.5:1 floor for this small
     (0.85rem/13.6px) text -- axe caught it on the Receive Batch page e2e
     smoke (#315 PR6 Task 9). #b45309 matches this template's own
     .label-incomplete-hint warning color and clears 5.02:1. */
  color: #b45309;
  font-weight: bold;
}

.form-help--approval {
  color: #b45309;
  font-style: italic;
}

/* Form Section Title */
.form-section-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--c-accent);
  margin: 0 0 0.75rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--c-border-soft);
}

/* (Button sizes now defined in the button system above) */

/* =========================
   Table (Users)
   ========================= */

.table-card {
  border-top: 1px solid var(--c-border);
}

.users-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.75rem;
}

.users-table th,
.users-table td {
  text-align: left;
  padding: 0.85rem 0.5rem;
  border-bottom: 1px solid var(--c-border);
  font-size: 0.95rem;
  vertical-align: middle;
}

.users-table th {
  font-weight: 700;
}

.email-link {
  color: var(--c-link);
  text-decoration: underline;
}

.col-last-active {
  white-space: nowrap;
}

/* Desktop table improvements */
@media (min-width: 768px) {
  .users-table th,
  .users-table td {
    padding: 1rem 0.75rem;
    font-size: 1rem;
  }
}

@media (min-width: 1024px) {
  .users-table th,
  .users-table td {
    padding: 1.1rem 1rem;
  }
}

/* Responsive column hiding */
@media (max-width: 900px) {
  .col-last-active {
    display: none;
  }
}
@media (max-width: 720px) {
  .col-active {
    display: none;
  }
}
@media (max-width: 560px) {
  .col-admin {
    display: none;
  }
}

/* =========================
   Modal
   ========================= */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-backdrop.active {
  display: flex;
}

.modal {
  background: #fff;
  width: 100%;
  max-width: 28rem;
  padding: 1.5rem;
  border-radius: var(--r-xl);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}

.modal--wide {
  max-width: 40rem;
}

.modal h2 {
  margin-top: 0;
  margin-bottom: 0.5rem;
}

.modal-blurb {
  font-size: 0.95rem;
  margin-bottom: 1.25rem;
  color: var(--c-text);
}

.modal label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.25rem;
}

.modal input[type="email"] {
  width: 100%;
  padding: 0.6rem 0.7rem;
  font-size: 0.95rem;
  border-radius: var(--r-md);
  border: 1px solid var(--c-border);
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* =========================
   Cards / Layout Helpers
   ========================= */

.card {
  border-top: 1px solid var(--c-border);
  padding: 1rem 0;
}

.card-title {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
}

.card-row {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.card-field .label {
  font-weight: 700;
  font-size: 0.85rem;
  margin-bottom: 0.25rem;
}

.card-field .value {
  font-size: 0.95rem;
}

.grid-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
}

@media (max-width: 720px) {
  .grid-two {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
}

/* =========================
   Chips
   ========================= */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.chip {
  display: inline-block;
  padding: 0.35rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--c-border);
  font-size: 0.9rem;
}

.chip--subtle {
  background: #f5f5f5;
}

/* =========================
   Accordion
   ========================= */

.accordion {
  border-top: 1px solid var(--c-border);
}

.accordion:last-child {
  border-bottom: 1px solid var(--c-border);
}

.accordion-header {
  width: 100%;
  background: transparent;
  border: none;
  padding: 0.9rem 0;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  text-align: left;
  cursor: pointer;
  font-size: 0.95rem;
}

.accordion-icon {
  font-weight: 800;
  width: 1.5rem;
  text-align: center;
}

.accordion-body {
  padding: 0 0 1rem 0;
}

.muted {
  color: #444;
  font-size: 0.92rem;
}

.activity-list {
  margin: 0.6rem 0 0 1.2rem;
}

/* =========================
   Form Grid / Toggles (modal layouts)
   ========================= */

.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem 2rem;
}

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

.toggle-row {
  display: flex;
  gap: 1.5rem;
  align-items: flex-end;
  flex-wrap: wrap;
}

.modal-section {
  margin-top: 1.25rem;
}

.section-title {
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem 1rem;
}

.checkbox-grid--small {
  grid-template-columns: repeat(5, minmax(2rem, 1fr));
}

@media (max-width: 560px) {
  .checkbox-grid {
    grid-template-columns: 1fr;
  }
  .checkbox-grid--small {
    grid-template-columns: repeat(3, minmax(2rem, 1fr));
  }
}

.checkbox {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

/* =========================
   Edit User modal spacing
   ========================= */

.form-basics {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
}

.basic-block {
  min-width: 16rem;
}

.basic-block--right {
  min-width: auto;
}

.checkbox--inline {
  margin-top: 0.25rem;
}

.modal-section--spaced {
  padding-top: 1rem;
  margin-top: 1rem;
  border-top: 1px solid #e0e0e0;
}

@media (max-width: 720px) {
  .form-basics {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  .basic-block {
    min-width: 100%;
  }
}

/* =========================
   Recent Activity (muted)
   ========================= */

.activity-muted {
  margin-top: 1.25rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--c-border-soft);
  color: var(--c-muted);
}

.activity-toggle {
  background: transparent;
  border: none;
  padding: 0;
  color: #555;
  text-decoration: underline;
  cursor: pointer;
  font-size: 0.95rem;
}

.activity-panel {
  margin-top: 0.6rem;
  background: #f6f6f6;
  border: 1px solid var(--c-border-soft);
  border-radius: 0.6rem;
  padding: 0.5rem 0.75rem;
}

.activity-list-simple {
  font-size: 0.82rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.activity-list-simple li {
  display: grid;
  grid-template-columns: 13.5rem 1fr;
  column-gap: 0.4rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--c-border-soft);
}

.activity-list-simple li:last-child {
  border-bottom: none;
}

.activity-ts {
  font-weight: 500;
  color: #555;
  white-space: nowrap;
}

.activity-text {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    "Liberation Mono", "Courier New", monospace;
  font-size: 0.8rem;
  color: var(--c-text);
}

@media (max-width: 720px) {
  .activity-list-simple li {
    grid-template-columns: 1fr;
  }

  .activity-ts {
    margin-bottom: 0.15rem;
    font-size: 0.75rem;
  }
}

/* =========================
   User Summary
   ========================= */

.user-summary {
  border-top: 1px solid var(--c-border);
  padding: 1rem 0 1.25rem;
}

.user-identity {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2rem;
  flex-wrap: wrap;
}

.user-primary {
  min-width: 16rem;
}

.user-name {
  font-size: 1.15rem;
  font-weight: 700;
  margin-bottom: 0.15rem;
}

.user-email {
  font-size: 0.95rem;
}

.user-meta {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
}

.meta-item {
  font-size: 0.85rem;
}

.meta-label {
  display: block;
  font-weight: 700;
  margin-bottom: 0.15rem;
}

.meta-value {
  white-space: nowrap;
}

@media (max-width: 720px) {
  .user-identity {
    flex-direction: column;
    gap: 0.75rem;
  }

  .user-meta {
    gap: 1.25rem;
  }
}

/* =========================
   View: Receive Batch
   ========================= */

body#view-receive-batch .form-group-row {
    display: flex;
    gap: 1rem;
    width: 100%;
}

body#view-receive-batch .form-group-row > .form-group {
    flex: 1 1 0;
    min-width: 0;
}

body#view-receive-batch .form-group input,
body#view-receive-batch .form-group select,
body#view-receive-batch .form-group textarea {
    width: 100%;
}

body#view-receive-batch .add-item-button,
body#view-receive-batch [x-show] .button {
  border: 1px solid var(--c-border);
  background: #F8F9FA;
  cursor: pointer;
}

/* The [x-show] .button ghosting above predates the #315 primitives and its
   id specificity beats .button--primary — the stale-draft banner's Continue
   rendered white-on-grey (invisible). Restore the primitive for primaries
   only; everything else the legacy rule touches stays as-is (receive-form
   cleanup is parked for the vendor slice). */
body#view-receive-batch [x-show] .button--primary {
  border-color: var(--c-accent);
  background-color: var(--c-accent);
}

body#view-receive-batch .form-group-row {
  padding-bottom: 1rem;
}

body#view-receive-batch .form-group-row > * {
  padding-bottom: 0;
}

body#view-receive-batch .form-section:first-of-type {
  margin-top: 0;
}

/* 3-up row behavior: stack only on truly tiny screens */
@media (max-width: 359px) {
    body#view-receive-batch .form-group-row--three {
        flex-direction: column;
    }
}


/* =========================
   View: Batch Status
   (scoped + clean)
   ========================= */

body#view-batch-status .batch-title {
  margin-top: 0.5rem;
}

body#view-batch-status .batch-id {
  font-weight: 700;
}

body#view-batch-status .status-timeline {
  max-width: 50rem;
  margin: 0 auto;
}

body#view-batch-status .status-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

body#view-batch-status .status-item {
  display: grid;
  grid-template-columns: 3.25rem 1fr auto;
  column-gap: 0.9rem;
  align-items: start;
  padding: 0.85rem 0;
}

body#view-batch-status .status-marker {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

body#view-batch-status .status-dot {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 999px;
  display: grid;
  place-items: center;
  background: var(--c-accent);
  color: #fff;
  position: relative;
  z-index: 2;
}

body#view-batch-status .status-line {
  position: absolute;
  top: 2rem;
  bottom: -2rem;
  width: 0.22rem;
  background: var(--c-accent);
  border-radius: 999px;
}

body#view-batch-status .status-item:last-child .status-line {
  display: none;
}

body#view-batch-status .status-check {
  font-size: 1.05rem;
  font-weight: 800;
  line-height: 1;
}

body#view-batch-status .status-name {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--c-accent);
  margin-bottom: 0.15rem;
}

body#view-batch-status .status-ts {
  color: var(--c-muted-2);
  font-size: 0.82rem;
  line-height: 1.25;
}

body#view-batch-status .status-detail-link {
  color: var(--c-accent);
  text-decoration: underline;
  font-weight: 700;
  font-size: 0.92rem;
  white-space: nowrap;
}

body#view-batch-status .status-action-spacer {
  display: inline-block;
  width: 6.2rem;
  height: 1rem;
}

/* states */
body#view-batch-status .status-item--pending .status-dot,
body#view-batch-status .status-item--pending .status-line {
  background: #bdbdbd;
}

body#view-batch-status .status-name--muted {
  color: #b0b0b0;
  font-weight: 700;
}

body#view-batch-status .status-item--active .status-check {
  display: none;
}

body#view-batch-status .status-active-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: #fff;
}

/* active transitions to gray line afterward */
body#view-batch-status .status-item--active .status-line {
  background: #bdbdbd;
}

body#view-batch-status .status-modal-body {
  border-top: 1px solid #e6e6e6;
  padding-top: 0.9rem;
  margin-top: 0.75rem;
  line-height: 1.45;
}

@media (max-width: 720px) {
  body#view-batch-status .status-item {
    grid-template-columns: 3rem 1fr auto;
    column-gap: 0.8rem;
  }

  body#view-batch-status .status-dot {
    width: 2.25rem;
    height: 2.25rem;
  }

  body#view-batch-status .status-name {
    font-size: 1.02rem;
  }

  body#view-batch-status .status-detail-link {
    font-size: 0.9rem;
  }
}

/* =========================
   Filters
   ========================= */

body#view-upcoming-deliveries .deliveries-filters {
  margin: 0 0 1rem;
}



body#view-upcoming-deliveries .filter-item,
body#view-upcoming-deliveries .filter-item--search {
  width: 100%;
  min-width: 0;
  margin: 0 0 0.85rem 0;
}

body#view-upcoming-deliveries .filter-item select,
body#view-upcoming-deliveries .filter-item input {
  width: 100%;
}

/* =========================
   Table
   ========================= */

body#view-upcoming-deliveries .deliveries-card {
  margin-top: 0.5rem;
}

body#view-upcoming-deliveries .deliveries-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0;
}

body#view-upcoming-deliveries .deliveries-table th,
body#view-upcoming-deliveries .deliveries-table td {
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--c-border);
  padding: 0.85rem 0.35rem;
  font-size: 0.9rem;
}

/* Column behavior */
body#view-upcoming-deliveries .col-date {
  width: 1%;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

body#view-upcoming-deliveries .col-action {
  width: 1%;
  white-space: nowrap;
}

body#view-upcoming-deliveries .col-status {
  color: var(--c-muted);
  white-space: normal;
  text-align: center;
}

/* =========================
   Batch link + phase
   ========================= */

body#view-upcoming-deliveries .batch-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}

body#view-upcoming-deliveries .batch-link:hover {
  color: var(--c-accent);
}

body#view-upcoming-deliveries .batch-phase {
  color: var(--c-muted);
  margin-left: 0.35rem;
}

/* =========================
   Items (single type)
   ========================= */

body#view-upcoming-deliveries .items-single {
  color: var(--c-muted);
  font-weight: 600;
  white-space: nowrap;
}

body#view-upcoming-deliveries .items-single strong {
  font-weight: 800;
}

/* =========================
   Buttons
   ========================= */

body#view-upcoming-deliveries .button--compact {
  padding: 0.42rem 0.8rem;
  font-size: 0.85rem;
  border-radius: 999px;
}

/* =========================
   Responsive Adjustments
   ========================= */

/* Hide Action column on small screens */
@media (max-width: 520px) {
  body#view-upcoming-deliveries th.col-action,
  body#view-upcoming-deliveries td.col-action {
    display: none;
  }

  body#view-upcoming-deliveries .deliveries-table th,
  body#view-upcoming-deliveries .deliveries-table td {
    padding: 0.85rem 0.3rem;
    font-size: 0.88rem;
  }
}

/* =========================
   View: Complete Delivery
   ========================= */

/* If you want a slightly larger notes box ONLY on this view */
body#view-complete-delivery textarea {
  min-height: 8rem;
}

/* =========================
   View: Request Delivery
   ========================= */

/* Delivery Address should be a larger box (desktop mockup style) */
body#view-request-delivery textarea {
  min-height: 8rem;
}

/* =========================
   View: Create Outgoing BOL
   ========================= */

/* =========================
   View: Upcoming Deliveries
   ========================= */

/* Basic layout hooks (only if you don't already have equivalents) */
body#view-upcoming-deliveries .page {
  max-width: 980px;
  margin: 0 auto;
  padding: 1rem;
}

body#view-upcoming-deliveries .header {
  position: relative;
  text-align: center;
  padding-top: 0.25rem;
}

body#view-upcoming-deliveries .sign-out {
  position: absolute;
  top: 0;
  right: 0;
  text-decoration: underline;
}


body#view-upcoming-deliveries .page-title {
  margin: 0.25rem 0 0.75rem;
  color: var(--c-accent);
}

body#view-upcoming-deliveries .page-rule {
  border: 0;
  border-top: 2px solid var(--c-accent);
  margin: 0.75rem 0 1.25rem;
}

/* Filters */
body#view-upcoming-deliveries .deliveries-filters {
  margin-bottom: 1.25rem;
}

body#view-upcoming-deliveries .filter-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 700;
}

body#view-upcoming-deliveries .filter-search {
  width: 100%;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--c-border);
  border-radius: 6px;
}

/* Table container */
body#view-upcoming-deliveries .deliveries-table-wrap {
  border-top: 1px solid var(--c-border);
}

/* Table */
body#view-upcoming-deliveries .deliveries-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed; /* enables predictable truncation */
  margin-top: 0;
}

body#view-upcoming-deliveries .deliveries-table th,
body#view-upcoming-deliveries .deliveries-table td {
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--c-border);
  padding: 0.85rem 0.55rem; /* column spacing */
  font-size: clamp(0.78rem, 2.2vw, 0.92rem);
  line-height: 1.25;

  /* allow our intentional 2-line stack */
  white-space: normal;
  overflow: hidden;
}

/* Column widths (stacked mobile-friendly) */
body#view-upcoming-deliveries th.col-batch,
body#view-upcoming-deliveries td.col-batch { width: 30%; }

body#view-upcoming-deliveries th.col-location,
body#view-upcoming-deliveries td.col-location { width: 34%; }

body#view-upcoming-deliveries th.col-status,
body#view-upcoming-deliveries td.col-status { width: 20%; }

body#view-upcoming-deliveries th.col-action,
body#view-upcoming-deliveries td.col-action {
  width: 16%;
  text-align: right;
}

/* Headings */
/* Status heading must NOT be muted */
body#view-upcoming-deliveries th.col-status {
  color: inherit;
}

/* Stacked cell lines: never wrap, always ellipsis */
body#view-upcoming-deliveries .cell-primary,
body#view-upcoming-deliveries .cell-sub {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

body#view-upcoming-deliveries .cell-sub {
  color: var(--c-muted);
  font-size: 0.86em;
  margin-top: 0.12rem;
}

/* Batch link */
body#view-upcoming-deliveries .batch-link {
  color: inherit;
  font-weight: 600;
}

body#view-upcoming-deliveries .batch-link:hover,
body#view-upcoming-deliveries .batch-link:focus-visible {
  text-decoration: underline;
}

/* Muted rows (delivered/etc) */
body#view-upcoming-deliveries tr.row--muted td {
  color: #b0b0b0;
}

body#view-upcoming-deliveries tr.row--muted td .cell-sub {
  color: #b0b0b0;
}

/* Compact buttons for this view */
body#view-upcoming-deliveries .button--compact {
  padding: 0.42rem 0.8rem;
  font-size: 0.82rem;
  border-radius: 999px;
  white-space: nowrap;
  min-width: 6rem;
  text-align: center;
}

/* Filter toggle fixed width */
body#view-upcoming-deliveries .filter-toggle {
  min-width: 8rem;
  text-align: center;
  cursor: pointer;
}

/* Reduce expand-cell and child-indent */
body#view-upcoming-deliveries .expand-cell {
  width: 2rem;
  padding: 0.25rem;
}

body#view-upcoming-deliveries .child-indent {
  width: 1.5rem;
}

/* Multi-item batches each on own line */
body#view-upcoming-deliveries .item-list .item {
  display: block;
}

/* If Action is too tight, drop it */
@media (max-width: 520px) {
  body#view-upcoming-deliveries th.col-action,
  body#view-upcoming-deliveries td.col-action {
    display: none;
  }

  body#view-upcoming-deliveries th.col-batch,
  body#view-upcoming-deliveries td.col-batch { width: 34%; }

  body#view-upcoming-deliveries th.col-location,
  body#view-upcoming-deliveries td.col-location { width: 40%; }

  body#view-upcoming-deliveries th.col-status,
  body#view-upcoming-deliveries td.col-status { width: 26%; }
}

/* Mobile overrides for upcoming deliveries */
@media (max-width: 768px) {
  body#view-upcoming-deliveries .deliveries-table {
    min-width: unset;
  }
  /* NOTE: .expand-cell must stay visible — it is the first column of the
     grid card layout. Hiding it (old table-era rule) shifted every row one
     grid column left, so the status chip rendered on top of the batch
     number (2026-07-18 mobile inspection finding). */
  body#view-upcoming-deliveries .deliveries-table th:first-child,
  body#view-upcoming-deliveries .child-indent {
    display: none;
  }
  body#view-upcoming-deliveries .action-buttons {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
}

/* ==========================================================================
   Item quantity tables (2026-07-18 redesign)
   Description-anchored rows: each item leads with its equipment description
   (unit type as a tag beneath) so same-type lines are distinguishable, and
   every table closes with a Total footer. Used by batch detail Contents,
   delivery-request item tables, the request-delivery modal and the batch
   status page. Display-only component — it renders existing quantities.
   ========================================================================== */

.iqt-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.iqt-table th,
.iqt-table td {
  padding: 0.45rem 0.5rem;
  border-bottom: 1px solid var(--c-border-soft);
  text-align: right;
  vertical-align: top;
}

.iqt-table th:first-child,
.iqt-table td:first-child {
  text-align: left;
  width: 55%;
}

.iqt-table thead tr {
  border-bottom: 2px solid var(--c-border);
}

.iqt-table thead th {
  font-weight: 600;
  color: var(--c-muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-bottom: none;
}

.iqt-desc {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 600;
  line-height: 1.35;
}

.iqt-type {
  display: block;
  font-size: 0.78rem;
  color: var(--c-muted);
  margin-top: 0.1rem;
}

/* Name-leading item lines: "NAME — description" (issue #315 PR 6 Task 6,
   apps/inventory/templatetags/item_line.py). .item-line-desc overrides
   .iqt-desc's inherited font-weight:600 where the two nest, so only the
   name reads bold. */
.item-line-name {
  font-weight: 600;
}
.item-line-desc {
  color: var(--c-muted);
  font-weight: 400;
}

/* Header label pair: desktop shows the full word. */
.iqt-table .th-full {
  display: inline;
}
.iqt-table .th-short {
  display: none;
}

.iqt-table tfoot .iqt-total td,
.iqt-table tfoot .iqt-total th {
  font-weight: 700;
  border-bottom: none;
  border-top: 2px solid var(--c-border);
}

@media (max-width: 640px) {
  .iqt-table {
    /* Slightly smaller than body copy: descriptions run long and the
       numbers matter more than font size here (Matt, 2026-07-19). */
    font-size: 0.82rem;
    /* Fixed layout: auto tables grow past width:100% when the unbreakable
       DELIVERED/REMAINING headers exceed their share, bleeding out of
       padded cards (batch-status finding, 2026-07-19). */
    table-layout: fixed;
  }
  .iqt-table th,
  .iqt-table td {
    padding: 0.35rem 0.3rem;
  }
  .iqt-table th:first-child,
  .iqt-table td:first-child {
    width: 46%;
  }
  .iqt-table thead th {
    font-size: 0.72rem;
    white-space: normal;
  }
  /* Dual header labels: full words desktop, short forms on phones —
     mid-word breaking ("AVAILA-BLE") read as smooshed (Matt, 2026-07-19). */
  .iqt-table .th-short {
    display: inline;
  }
  .iqt-table .th-full {
    display: none;
  }
  /* Narrow columns push the distinguishing suffix ("(4) 32' UNITS") onto a
     third line — two-line clamping made same-prefix items identical again. */
  .iqt-desc {
    -webkit-line-clamp: 3;
  }
}

/* =========================
   View: View Batches
   ========================= */

/* Layout hooks (only on this page) */
body#view-batches .page {
  margin: 0 auto;
  padding: 1rem;
}

body#view-batches .header {
  position: relative;
  text-align: center;
  padding-top: 0.25rem;
}

body#view-batches .sign-out {
  position: absolute;
  top: 0;
  right: 0;
  text-decoration: underline;
}


body#view-batches .page-title {
  margin: 0.25rem 0 0.75rem;
  color: var(--c-accent);
}

body#view-batches .page-rule {
  border: 0;
  border-top: 2px solid var(--c-accent);
  margin: 0.75rem 0 1.25rem;
}

/* Filters */
body#view-batches .batches-filters {
  margin-bottom: 1.25rem;
}

body#view-batches .filter-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 700;
}

body#view-batches .filter-search {
  width: 100%;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--c-border);
  border-radius: 6px;
}

/* Table wrap */
body#view-batches .batches-table-wrap {
  border-top: 1px solid var(--c-border);
}

/* Table (unstacked cells) */
body#view-batches .batches-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed; /* predictable truncation */
}

body#view-batches .batches-table th,
body#view-batches .batches-table td {
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--c-border);
  padding: 0.85rem 0.55rem; /* spacing between columns */
  font-size: clamp(0.78rem, 2.1vw, 0.92rem);
  line-height: 1.25;
}

/* Hard no-wrap/ellipsis is the DESKTOP truncation contract (with
   table-layout: fixed). Desktop-only since 2026-07-20: the id selector
   outguns the card block at any width, and it was clipping the paired
   count—description lines on mobile cards. Cards wrap instead. */
@media (min-width: 768px) {
  body#view-batches .batches-table th,
  body#view-batches .batches-table td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

body#view-batches .batches-table th {
  font-weight: 700;
}

/* Column widths (desktop/tablet baseline) */
body#view-batches th.col-batch,
body#view-batches td.col-batch { width: 22%; }

body#view-batches th.col-po,
body#view-batches td.col-po { width: 22%; }

body#view-batches th.col-status,
body#view-batches td.col-status { width: 22%; }

body#view-batches th.col-received,
body#view-batches td.col-received { width: 17%; }

body#view-batches th.col-delivered,
body#view-batches td.col-delivered { width: 17%; }

/* Batch link */
body#view-batches .batch-link {
  color: inherit;
  font-weight: 800;
  text-decoration: none;
}

body#view-batches .batch-link:hover,
body#view-batches .batch-link:focus-visible {
  text-decoration: underline;
}

/* Optional muted row */
body#view-batches tr.row--muted td {
  color: #b0b0b0;
}

/* At small resolutions: both dates fall off */
@media (max-width: 560px) {
  body#view-batches th.col-received,
  body#view-batches td.col-received,
  body#view-batches th.col-delivered,
  body#view-batches td.col-delivered {
    display: none;
  }

  /* Rebalance remaining columns */
  body#view-batches th.col-batch,
  body#view-batches td.col-batch { width: 34%; }

  body#view-batches th.col-po,
  body#view-batches td.col-po { width: 33%; }

  body#view-batches th.col-status,
  body#view-batches td.col-status { width: 33%; }
}


body#view-batches .batch-link {
  text-decoration: underline;
}

/* =========================
   View: Batch Detail (Single Batch)
   ========================= */

/* Title formatting */
body#view-batch-detail .batch-page-title {
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--c-accent);
  margin: 0;
}

body#view-batch-detail .batch-page-id {
  font-weight: 800;
}

body#view-batch-detail .batch-page-status {
  font-weight: 700;
}

/* Sections */
body#view-batch-detail .batch-section {
  border-top: 1px solid var(--c-border);
  padding: 1rem 0 1.25rem;
}

body#view-batch-detail .batch-section:first-of-type {
  margin-top: 0.25rem;
}

body#view-batch-detail .batch-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.65rem;
}

body#view-batch-detail .batch-section-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
}

body#view-batch-detail .batch-section-edit {
  color: var(--c-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: 600;
}

body#view-batch-detail .batch-section-spacer {
  width: 2rem;
}

/* Key/Value grid */
body#view-batch-detail .batch-kv-grid {
  display: grid;
  gap: 2rem;
}

body#view-batch-detail .batch-kv-grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

body#view-batch-detail .batch-kv-grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

body#view-batch-detail .batch-kv {
  min-width: 0;
}

body#view-batch-detail .batch-k {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--c-muted);
  margin-bottom: 0.35rem;
}

body#view-batch-detail .batch-v {
  font-size: 1.1rem;
  font-weight: 500;
}

/* Contents grid */
body#view-batch-detail .batch-contents-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1.4fr;
  gap: 2rem;
}

body#view-batch-detail .batch-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

body#view-batch-detail .batch-list li {
  font-size: 1.1rem;
  padding: 0.15rem 0;
}

/* Events */
body#view-batch-detail .batch-events {
  display: grid;
  gap: 0.7rem;
  padding-top: 0.25rem;
}

body#view-batch-detail .batch-event {
  display: grid;
  grid-template-columns: 1.6fr 1fr auto;
  gap: 1rem;
  align-items: center;
  padding: 0.25rem 0;
}

body#view-batch-detail .batch-event-name {
  font-size: 1.05rem;
  font-weight: 500;
}

body#view-batch-detail .batch-event-date {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-size: 1.05rem;
}

body#view-batch-detail .batch-event-link {
  color: var(--c-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: 500;
  white-space: nowrap;
}

body#view-batch-detail .batch-event--muted {
  color: #b0b0b0;
}

body#view-batch-detail .batch-event--muted .batch-event-link {
  color: #b0b0b0;
}

/* Responsive */
@media (max-width: 980px) {
  body#view-batch-detail .batch-kv-grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
  }

  body#view-batch-detail .batch-kv-grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
  }

  body#view-batch-detail .batch-contents-grid {
    grid-template-columns: 1fr;
    gap: 1.25rem;
  }
}

@media (max-width: 560px) {
  body#view-batch-detail .batch-kv-grid--3 {
    grid-template-columns: 1fr;
  }

  body#view-batch-detail .batch-event {
    grid-template-columns: 1fr;
    gap: 0.25rem;
    align-items: start;
  }

  body#view-batch-detail .batch-event-link {
    justify-self: start;
  }
}

body#view-batch-detail .approval-actions h3 {
  padding-top: 0;
}

body#view-batch-detail .info-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

body#view-batch-detail .save-changes-button,
body#view-batch-detail .request-delivery-button {
  background-color: #FCCC0A;
  color: #000;
  border-color: #FCCC0A;
}

/* =========================
   View: Notifications
   ========================= */

body#view-notifications .page {
  max-width: 40rem;
}

.notif-page {
  padding-top: 0.25rem;
}

.notif-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.notif-title {
  font-size: 1.35rem;
  font-weight: 700;
  margin: 0;
}

.notif-header-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.notif-btn-mark-all {
  background: none;
  border: 1px solid var(--c-link);
  color: var(--c-link);
  padding: 0.3rem 0.7rem;
  border-radius: var(--r-sm);
  font-size: 0.8rem;
  cursor: pointer;
}

.notif-btn-mark-all:hover {
  background: var(--c-link);
  color: #fff;
}

.notif-unread-count {
  font-size: 0.85rem;
  color: var(--c-muted);
}

/* Filters */
.notif-filters {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.notif-filter {
  padding: 0.3rem 0.85rem;
  border-radius: 999px;
  font-size: 0.85rem;
  text-decoration: none;
  background: #e4e6eb;
  color: var(--c-text);
}

.notif-filter:hover {
  background: #d0d2d6;
}

.notif-filter--active {
  background: var(--c-link);
  color: #fff;
}

/* Notification list */
.notif-list {
  border: 1px solid var(--c-border-soft);
  border-radius: var(--r-md);
  overflow: hidden;
}

.notif-item {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--c-border-soft);
}

.notif-item:last-child {
  border-bottom: none;
}

.notif-item--unread {
  background: #eef3ff;
}

.notif-item-body {
  flex: 1;
  min-width: 0;
}

.notif-item-top {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

.notif-item-title {
  font-weight: 600;
  font-size: 0.95rem;
}

.notif-new-badge {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: var(--r-sm);
  background: #dbeafe;
  color: var(--c-link);
  font-size: 0.7rem;
  font-weight: 600;
}

.notif-item-message {
  margin: 0.15rem 0;
  font-size: 0.88rem;
  color: var(--c-muted);
}

.notif-item-time {
  font-size: 0.75rem;
  color: var(--c-muted-2);
}

.notif-item-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: 0.75rem;
  flex-shrink: 0;
}

.notif-link {
  font-size: 0.85rem;
  color: var(--c-link);
  text-decoration: none;
}

.notif-link:hover {
  text-decoration: underline;
}

.notif-btn-read {
  background: none;
  border: none;
  color: var(--c-muted-2);
  cursor: pointer;
  padding: 0.25rem;
  border-radius: var(--r-sm);
  display: inline-flex;
}

.notif-btn-read:hover {
  color: #28a745;
  background: #e8f5e9;
}

.notif-empty {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--c-muted);
}

/* =========================
   View: Landing (Home)
   ========================= */

body#view-landing {
  --c-accent: #111; /* keep neutral for header/divider */
}

/* Layout - responsive */
body#view-landing .page {
  max-width: 40rem; /* mobile-first */
}

body#view-landing .landing {
  display: grid;
  gap: 1rem;
  padding-top: 0.25rem;
}

/* Tiles */
body#view-landing .landing-tile {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 4.25rem;
  padding: 1.25rem 1.5rem;

  border-radius: var(--r-md);
  border: 1px solid rgba(0, 0, 0, 0.28);

  color: #fff;
  text-decoration: none;
  font-weight: 700;
  font-size: 1.35rem;
  line-height: 1.1;

  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

body#view-landing .landing-tile:hover,
body#view-landing .landing-tile:focus-visible {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  outline: none;
}

/* Tile Colors (subway palette) */
body#view-landing .landing-tile--receive    { background: var(--subway-orange); }
body#view-landing .landing-tile--drafts     { background: #C06000; }          /* Amber — draft/in-progress */
body#view-landing .landing-tile--upcoming   { background: var(--subway-blue); }
body#view-landing .landing-tile--bol        { background: var(--subway-magenta); }
body#view-landing .landing-tile--complete   { background: var(--subway-green); }
body#view-landing .landing-tile--batches    { background: var(--subway-gray); }
body#view-landing .landing-tile--approval   { background: var(--subway-lime); }
body#view-landing .landing-tile--kickback   { background: var(--subway-red); }
body#view-landing .landing-tile--invoice    { background: var(--subway-purple); }
body#view-landing .landing-tile--sites      { background: var(--subway-teal); }
body#view-landing .landing-tile--users      { background: var(--subway-brown); }
body#view-landing .landing-tile--database   { background: var(--subway-blue); }
body#view-landing .landing-tile--warehouses { background: var(--subway-yellow); }
body#view-landing .landing-tile--contractor { background: var(--subway-seagreen); }
body#view-landing .landing-tile--invoices    { background: #000; }
body#view-landing .landing-tile--rates      { background: var(--subway-teal); }  /* Reused (Site Management precedent — same-page reuse already exists for --subway-blue) */

/* Tile badge (approval queue count — positioned like notification badge) */
body#view-landing .landing-tile { position: relative; }
body#view-landing .tile-badge {
  position: absolute;
  top: -0.45rem;
  right: -0.45rem;
  min-width: 1.4rem;
  height: 1.4rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: #1a472a;
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.4rem;
  text-align: center;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
body#view-landing .tile-badge--danger {
  background: #dc3545;
}

/* =========================
   Crane Badge (global)
   ========================= */

.crane-badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  background-color: #f8d7da;
  color: #721c24;
  margin-left: 0.35rem;
  vertical-align: middle;
}


/* ==========================================================================
   Mobile Responsive Tables & Touch Targets
   ========================================================================== */

/* At 640px: remove fixed min-width, hide lower-priority columns */
@media (max-width: 640px) {
  .batches-table {
    min-width: 0 !important;
    table-layout: auto !important;
  }

  /* Legacy narrow-table column squeeze. Card-mode tables
     (.list-table--cards) manage their own column visibility via
     data-label/desktop-only and must be exempt — without the :not() this
     rule's 0-2-0 specificity beats the card block's display:block and
     silently hides Phase on cards below 640px (found in PR 3 review).
     Non-card .batches-table pages (contractor_portal until PR 4) keep the
     squeeze. */
  .batches-table:not(.list-table--cards) .col-phase,
  .batches-table:not(.list-table--cards) .col-count,
  .batches-table:not(.list-table--cards) .col-shipped {
    display: none;
  }

  .batches-table th,
  .batches-table td {
    padding: 0.55rem 0.35rem;
    font-size: 0.85rem;
  }
}

/* At 480px: tighter spacing */
@media (max-width: 480px) {
  .batches-table th,
  .batches-table td {
    padding: 0.4rem 0.25rem;
    font-size: 0.82rem;
  }
}

/* Stats bar reflow rules removed 2026-07-21 (issue #315 PR 4): the last
   template referencing .stats-bar/.stat-item was dashboard.html's contractor
   stats bar, and that block is gone now too (walk feedback rationale). */

/* Touch targets for non-.button submit buttons.
   Excludes .button--sm: table/list row actions are the one deliberate
   exception to the 44px floor (issue #315). Without the :not(), this
   selector's specificity (0,1,1) outranks .button--sm's (0,1,0) and the
   36px row size silently never applies to submit buttons — which was half
   of them. Decision: Matt, 2026-07-20. */
button[type="submit"]:not(.button--sm) {
  min-height: 44px;
}

/* Breadcrumbs */
.breadcrumb {
  padding: 0 0 1.5rem;
  font-size: 0.85rem;
  color: var(--c-muted);
}

/* Task 3b (axe AA-contrast fixup, 2026-07-21): breadcrumb links used to
   tint with the page's --c-accent, which failed color-contrast (as low as
   2.31:1 on approval_queue) and link-in-text-block (no non-color distinction
   from the surrounding #666666 breadcrumb text) at every accent shade the
   link ever took on. Fixed shared-CSS-level rather than per page: one fixed
   link color (var(--c-link), #0066cc, 5.57:1 on white) plus a permanent
   underline — the underline alone satisfies link-in-text-block regardless
   of the (low, 1.03:1) contrast against the adjacent muted text. */
.breadcrumb a {
  color: var(--c-link);
  text-decoration: underline;
}

.breadcrumb .separator {
  margin: 0 0.4rem;
  color: var(--c-muted);
}

/* =========================
   Dashboard Role Indicator
   ========================= */

.dashboard-role-indicator {
  text-align: center;
  margin-bottom: 1.5rem;
  color: var(--c-muted);
  font-size: 0.95rem;
}

.dashboard-role-indicator p {
  margin: 0 0 0.25rem;
}

.role-badge {
  display: inline-block;
  padding: 0.2rem 0.75rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  background: #e9ecef;
  color: #495057;
}

/* Landing Section Headers (Admin tile grouping) */
.landing-section-header {
    grid-column: 1 / -1;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--c-muted);
    margin: 1rem 0 0.25rem;
    padding-bottom: 0.25rem;
    border-bottom: 1px solid var(--c-border);
}

.landing-section-header:first-of-type {
    margin-top: 0;
    border-top: none;
}

/* Recent submissions strip (Dashboard, warehouse roles) */
.recent-submissions {
    max-width: 900px;
    margin: 1.5rem auto 0;
}

.recent-submissions-summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.recent-submissions-summary::-webkit-details-marker {
    display: none;
}

/* Firefox et al. use ::marker, not the WebKit pseudo — suppress both so
   only the custom ::after caret renders. */
.recent-submissions-summary::marker {
    content: '';
}

.recent-submissions-summary::after {
    content: '\25B8';
    margin-left: 0.4rem;
    color: var(--c-muted);
    font-size: 0.7rem;
}

.recent-submissions details[open] .recent-submissions-summary::after,
details[open] > .recent-submissions-summary::after {
    content: '\25BE';
}

.recent-submissions-list {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
    border: 1px solid var(--c-border);
    border-radius: var(--r-sm);
    background: var(--c-surface, #fff);
    overflow: hidden;
}

.recent-submissions-list li + li {
    border-top: 1px solid var(--c-border-soft, var(--c-border));
}

.recent-submissions-list a {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0.85rem;
    text-decoration: none;
    color: inherit;
}

.recent-submissions-list a:hover {
    background: #f5f5f5;
}

.recent-batch-name {
    flex: 1;
    min-width: 0;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Contractor Stats Bar (Dashboard) removed 2026-07-21 (issue #315 PR 4,
   walk feedback rationale) — the markup that used .contractor-stats-bar and
   .stat-card is gone from dashboard.html. */

/* =========================
   Reduced-motion (WCAG 2.3.3 / respects OS preference)
   Users who ask for reduced motion get near-instant animations and
   transitions — including the continuous upload spinner and item slide-ins.
   ========================= */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Sortable table headers (issue #258): the real <button> inside th gives
   native keyboard activation while the th keeps its columnheader role
   (required for aria-sort). The button visually disappears into the th. */
th[data-sort] .sort-header-btn {
    all: unset;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    cursor: pointer;
    font: inherit;
    color: inherit;
}

th[data-sort] .sort-header-btn:focus-visible {
    outline: 2px solid var(--c-primary, #AC3F07);
    outline-offset: 2px;
    border-radius: 2px;
}

/* ---------------------------------------------------------------------------
   Status badge (issue #264): single source of truth. Previously copy-pasted
   into 12 templates with 4 different sizes; add new statuses HERE only. */
.status-badge {
    display: inline-block;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

.status-badge--pending,
.status-badge--pending_verification,
.status-badge--flagged,
.status-badge--rescheduled,
.status-badge--on_site { background-color: #fff3cd; color: #856404; }

.status-badge--approved,
.status-badge--in_warehouse,
.status-badge--delivered,
.status-badge--fulfilled,
.status-badge--paid,
.status-badge--active { background-color: #d4edda; color: #155724; }

.status-badge--rejected,
.status-badge--kickback,
.status-badge--missing_info,
.status-badge--cancelled,
.status-badge--inactive { background-color: #f8d7da; color: #721c24; }

.status-badge--requested,
.status-badge--sent { background-color: #cce5ff; color: #004085; }

.status-badge--confirmed { background-color: #b8daff; color: #004085; }

.status-badge--in_transit,
.status-badge--delivering,
.status-badge--fulfilling { background-color: #d1ecf1; color: #0c5460; }

.status-badge--partially_delivered { background-color: #bee5eb; color: #0c5460; }

.status-badge--generated { background-color: #e9ecef; color: #495057; }

/* PR 7 review finding 4: a voided invoice stays visible on the Unpaid
   tab (excluded only from its totals, apps/inventory/views.py::
   invoice_list) -- muted so it reads as "not a real unpaid receivable"
   at a glance, distinct from the neutral .status-badge--generated. */
.status-badge--voided { background-color: #e9ecef; color: #6c757d; }

/* Empty state (issue #264): canonical list/table empty placeholder. */
.empty-state {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--c-muted);
}

/* Form focus + error affordances (issues #264/#259). */
.form-control:focus-visible {
    outline: 2px solid var(--c-primary, #AC3F07);
    outline-offset: 1px;
}

.form-control[aria-invalid="true"] {
    border-color: #c62828;
}
