/* ==========================================================================
   Agri Warehouse — Design System v3
   A thin, opinionated layer on top of Bootstrap 5. Bootstrap still provides
   the grid, form controls, and JS components (dropdowns, offcanvas, modals,
   collapse); this file redraws its visual defaults so the app reads as a
   considered product rather than a recolored admin template.

   Ground rules this version enforces everywhere:
     - Full viewport width. No centered, width-capped content box.
     - Indigo is the ONLY brand color (buttons, links, active nav, focus
       rings). Green / orange / red are reserved strictly for meaning
       (success, warning/outstanding, error/overdue) — never decoration.
       Most icons and borders are neutral gray by default; color is added
       only where it communicates a status.
     - Cards are white with a hairline border AND a subtle shadow — shadow
       carries the separation, the border just keeps edges crisp on white.
     - No zebra striping. Rows are separated by hairlines + generous height,
       distinguished on hover only.
     - Form fields are filled by default (no boxed outline until focus).
     - Big, bold numerals against small, quiet labels — typography carries
       hierarchy instead of chrome.

   Structure:
     1. Tokens
     2. Base / typography
     3. Layout shell
     4. Sidebar + topbar
     5. Buttons
     6. Cards + KPI cards
     7. Tables
     8. Filter bar / toolbar
     9. Forms
     10. Badges / status pills
     11. Alerts / flash
     12. Empty & loading states
     13. Breadcrumb
     14. Modals
     15. Utilities
     16. Compact screens
     17. Mobile (app-like, ≤991.98px — tables/nav/modals; ≤767.98px — density)
     18. Bottom navigation
     19. Mobile list row (scannable list pattern)
     20. Filter sheet (mobile filters bottom-sheet)
     21. Combo select (mobile searchable picker)
     22. Kisani dynamic rows (trucks & broker payments)
   ========================================================================== */

/* ---------- 1. Tokens ---------- */
:root {
  --gray-25:  #fbfbfc;
  --gray-50:  #f7f8fa;
  --gray-100: #f1f2f5;
  --gray-200: #e5e7eb;
  --gray-300: #d7dae0;
  --gray-400: #b0b5bf;
  --gray-500: #8a8f9a;
  --gray-600: #62687a;
  --gray-700: #454b5c;
  --gray-800: #262b3a;
  --gray-900: #14161f;

  /* Accent — Indigo, the single brand color. Used for primary buttons,
     links, active nav, focus rings. Nothing else in the app uses this
     color; it never stands in for a status. */
  --accent-50:  #eef2ff;
  --accent-100: #e0e7ff;
  --accent-500: #4f46e5;
  --accent-600: #4338ca;
  --accent-700: #3730a3;

  --success: #16a34a;
  --success-soft: #eaf7ef;
  --warning: #d97706;
  --warning-soft: #fdf3e5;
  --danger:  #dc2626;
  --danger-soft: #fbeaea;
  --info:    #2563eb;
  --info-soft: #eaf0fd;

  /* Status color taxonomy — color is applied ONLY where it communicates
     meaning, never as decoration:
       Positive / success / stock-in / completed -> --success (green)
       Warning / outstanding amounts              -> --warning (orange)
       Error / overdue / alerts                    -> --danger  (red)
       Everything else (including plain magnitudes
       like stock totals or transaction amounts)   -> neutral gray
     --info (blue) is defined for possible future informational use but is
     not currently applied anywhere, to keep the palette to brand + 3
     statuses + gray. */

  /* Semantic (dark-mode ready: redefine these under [data-theme="dark"] later) */
  --bg-page: var(--gray-50);
  --bg-surface: #ffffff;
  --bg-field: var(--gray-100);
  --border-default: var(--gray-200);
  --border-subtle: var(--gray-100);
  --text-primary: var(--gray-900);
  --text-secondary: var(--gray-600);
  --text-muted: var(--gray-500);
  --text-on-accent: #ffffff;

  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-pill: 999px;

  /* Shadow — elevation only, never used on static/flat surfaces */
  --shadow-sm: 0 2px 8px rgba(15, 23, 42, 0.06);
  --shadow-md: 0 8px 24px rgba(15, 23, 42, 0.10);
  --shadow-lg: 0 20px 48px rgba(15, 23, 42, 0.16);

  --space-card: 1.5rem;
  --transition-fast: 120ms ease;

  /* Backdrop dim behind modals/offcanvas. Declared on :root, not just
     `.modal` (where section 14 also sets it) — .modal-backdrop and
     .offcanvas-backdrop are siblings Bootstrap appends near the end of
     <body>, not descendants of .modal/.offcanvas, so a custom property
     scoped only to those components never reaches them: opacity fell back
     to its initial value (1, fully opaque) and every modal/offcanvas in
     the app — including the sidebar drawer — has been showing a solid
     black backdrop instead of a 35% dim. Found while testing the new
     mobile filter sheet, but it was pre-existing everywhere. */
  --bs-backdrop-opacity: 0.35;

  --sidebar-w: 264px;
  --sidebar-w-collapsed: 76px;
  --topbar-h: 60px;

  --content-max: 1920px;
}

/* ---------- 2. Base / typography ---------- */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');

html {
  -webkit-text-size-adjust: 100%;
  /* App-like touch feel: no rubber-band pull triggering pull-to-refresh
     mid-form. This has to live on <html>, not <body> — <html> is the
     element that's actually scrolling here (see the overflow-x rule
     below), and applying overscroll-behavior to an element that isn't
     itself a scroll container is what real Android Chrome builds have
     been observed to mishandle, up to swallowing the scroll gesture
     entirely instead of merely containing the overscroll bounce. */
  overscroll-behavior-y: contain;
}

/* Belt-and-braces against page-level horizontal scroll on small screens —
   any stray fixed-width element blowing out the viewport gets clipped here
   instead of adding a sideways scrollbar to the whole app. Inner scrollers
   (.table-responsive, code blocks) opt back in explicitly with overflow-x:
   auto, so this never hides content that's meant to scroll. */
html, body { max-width: 100%; overflow-x: hidden; }

body {
  background-color: var(--bg-page);
  color: var(--text-primary);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 0.9375rem;
  line-height: 1.5;
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
  /* No gray flash on tap (we draw our own :active states), and the
     standard 300ms tap delay gone from anything a thumb hits. Pinch-zoom
     stays available (viewport has no user-scalable=no) — this only
     removes the double-tap-to-zoom gesture that fires accidental zooms on
     fast repeated taps, which is what actually hurts one-handed use. */
  -webkit-tap-highlight-color: transparent;
}

a, button, .btn, input, select, textarea, .sidebar-link, .aw-bottom-nav__item {
  touch-action: manipulation;
}

/* Respect the OS-level "reduce motion" setting (Phase 6) — collapses every
   CSS transition/animation in the app (sidebar slide, offcanvas/modal
   slide-in, dropdown fade, bottom-nav color transitions, progress bars)
   to near-instant instead of trying to enumerate and override each one
   individually. Bootstrap's own JS-driven slide/fade transitions are CSS
   transitions under the hood, so this covers those too. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

h1, h2, h3, h4, h5, h6 { font-weight: 700; letter-spacing: -0.02em; color: var(--text-primary); }

.page-title { font-size: 1.625rem; font-weight: 800; letter-spacing: -0.03em; color: var(--text-primary); margin: 0; }
.page-subtitle { font-size: 0.875rem; color: var(--text-secondary); margin: 0.2rem 0 0; }
.section-title { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin: 0 0 0.75rem; }

.text-secondary-aw { color: var(--text-secondary) !important; }
.text-muted-aw { color: var(--text-muted) !important; }
.text-warning-aw { color: var(--warning) !important; }

.num, .tabular { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* Typography utility scale — use these instead of ad-hoc inline font-size
   styles so every "small text" in the app is one of exactly two sizes. */
.text-sm { font-size: 0.8125rem !important; }
.text-xs { font-size: 0.75rem !important; }

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

::selection { background: var(--accent-100); color: var(--accent-700); }

/* Keyboard focus — visible ring on anything focusable, consistent with the
   form-field focus treatment. :focus-visible only, so mouse/touch users
   never see it flash. */
:focus-visible { outline: 3px solid var(--accent-100); outline-offset: 1px; border-radius: var(--r-sm); }
.form-control:focus-visible, .form-select:focus-visible { outline: none; } /* fields already show their own focus ring */

/* Custom scrollbar — small premium detail, WebKit/Chromium + Firefox */
* { scrollbar-width: thin; scrollbar-color: var(--gray-300) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--gray-300); border-radius: var(--r-pill); }
*::-webkit-scrollbar-thumb:hover { background: var(--gray-400); }

/* Turbo Drive's built-in navigation progress bar — the one bit of "loading
   state" that needs zero custom JS, just a color that matches the brand. */
.turbo-progress-bar { background-color: var(--accent-500); height: 2px; }

/* ---------- 3. Layout shell ---------- */
.app-main {
  max-width: var(--content-max);
  min-width: 0;
  width: 100%;
  /* Do not grow to leftover viewport height — that gives every child a
     definite containing block, and Bootstrap cards (flex column +
     flex-grow body) turn into tall empty boxes on every page. */
  flex: 0 0 auto;
  padding: 1.75rem 2rem 4rem;
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* ---------- 4. Sidebar + topbar ---------- */
.brand-mark {
  width: 28px; height: 28px;
  border-radius: 8px;
  display: block;
  flex-shrink: 0;
}

.aw-shell { display: flex; min-height: 100vh; min-width: 0; width: 100%; align-items: flex-start; }
.aw-content-wrap {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: stretch;
  overflow: visible;
}

.aw-sidebar.offcanvas.offcanvas-lg {
  --bs-offcanvas-width: var(--sidebar-w);
  border-right: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  flex-shrink: 0;
}

/* Bootstrap's `.offcanvas-lg` responsive reset only cancels the mobile
   drawer styles (position/visibility/transform/width) that it applies
   BELOW the lg breakpoint — those declarations live inside that same
   sub-breakpoint block, so nothing re-establishes a visible, sized, flex
   column above it. The base `.offcanvas` class stays `position: fixed;
   visibility: hidden; transform: translateX(-100%)` at every width, so
   without this override the nav is off-canvas even on desktop. Sticky +
   viewport height keeps it in view while the main column scrolls. */
@media (min-width: 992px) {
  .aw-sidebar.offcanvas.offcanvas-lg.offcanvas-start {
    position: sticky !important;
    top: 0;
    left: auto !important;
    bottom: auto !important;
    z-index: auto !important;
    display: flex !important;
    flex-direction: column !important;
    visibility: visible !important;
    transform: none !important;
    align-self: flex-start;
    flex: 0 0 auto;
    width: var(--sidebar-w) !important;
    height: 100vh !important;
    height: 100dvh !important;
    max-height: 100vh !important;
    max-height: 100dvh !important;
    max-width: none !important;
    background-color: var(--bg-surface) !important;
    background: var(--bg-surface) !important;
    border-right: 1px solid var(--border-subtle) !important;
    transition: width var(--transition-fast);
    overflow: hidden;
  }
}

.aw-sidebar__brand {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 1rem 1.1rem;
  border-bottom: 1px solid var(--border-subtle);
  height: var(--topbar-h);
}
.aw-sidebar__brand .brand-name { font-weight: 800; font-size: 0.95rem; white-space: nowrap; }

.aw-sidebar__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 0.75rem; }

.sidebar-section-title {
  font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); padding: 1.1rem 0.6rem 0.4rem; white-space: nowrap;
}
.sidebar-section-title:first-child { padding-top: 0.25rem; }

.sidebar-link {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--r-sm);
  color: var(--text-secondary);
  font-size: 0.875rem; font-weight: 500;
  margin-bottom: 0.125rem;
  transition: background-color var(--transition-fast), color var(--transition-fast);
  white-space: nowrap;
  cursor: pointer;
  /* Masters / Reports toggles are <button>s so Turbo doesn't treat #hash
     clicks as navigation (which kept the submenu stuck open). */
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  font-family: inherit;
}
.sidebar-link:hover { background: var(--gray-100); color: var(--text-primary); }
.sidebar-link.active { background: var(--accent-50); color: var(--accent-600); font-weight: 600; }
.sidebar-link.disabled { color: var(--gray-300); pointer-events: none; }
.sidebar-link .bi, .sidebar-link .ph { font-size: 1.05rem; width: 20px; text-align: center; flex-shrink: 0; }
.sidebar-link .sidebar-chevron { margin-left: auto; font-size: 0.7rem; transition: transform var(--transition-fast); flex-shrink: 0; }
.sidebar-link[aria-expanded="true"] .sidebar-chevron { transform: rotate(90deg); }

.sidebar-submenu { padding-left: 1.9rem; display: none; }
.sidebar-submenu.is-open { display: block; }
.sidebar-submenu .sidebar-link { font-size: 0.8125rem; padding: 0.4rem 0.65rem; }

.aw-sidebar__footer { border-top: 1px solid var(--border-subtle); padding: 0.75rem; flex-shrink: 0; }
.sidebar-collapse-toggle {
  width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.5rem; border-radius: var(--r-sm); color: var(--text-muted);
  border: none; background: var(--bg-field); font-size: 0.8rem;
}
.sidebar-collapse-toggle:hover { background: var(--gray-200); color: var(--text-primary); }

@media (min-width: 992px) {
  html.sidebar-collapsed .aw-sidebar.offcanvas.offcanvas-lg.offcanvas-start {
    --bs-offcanvas-width: var(--sidebar-w-collapsed);
    width: var(--sidebar-w-collapsed) !important;
    min-width: var(--sidebar-w-collapsed) !important;
    flex: 0 0 var(--sidebar-w-collapsed);
  }
  html.sidebar-collapsed .aw-sidebar .sidebar-label,
  html.sidebar-collapsed .aw-sidebar .brand-name,
  html.sidebar-collapsed .aw-sidebar .sidebar-section-title,
  html.sidebar-collapsed .aw-sidebar .sidebar-chevron { display: none !important; }
  html.sidebar-collapsed .aw-sidebar .sidebar-link { justify-content: center; padding: 0.6rem; }
  html.sidebar-collapsed .aw-sidebar .sidebar-submenu { display: none !important; }
  html.sidebar-collapsed .aw-sidebar__brand { justify-content: center; padding: 0 0.5rem; }
  html.sidebar-collapsed .aw-sidebar__brand a { flex-grow: 0 !important; }
  html.sidebar-collapsed .aw-sidebar .sidebar-collapse-toggle { padding: 0.6rem; }
}

.aw-topbar {
  position: sticky; top: 0; z-index: 1020;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-subtle);
  /* PWA standalone on a notched phone draws right up to the status bar —
     pad the inset into the topbar's own height rather than the page, so
     the notch never overlaps the hamburger/avatar. env() resolves to 0 on
     everything else, so this is a no-op on desktop and non-notched phones. */
  padding: env(safe-area-inset-top, 0px) 1.5rem 0;
  height: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  overflow: visible;
}
.aw-topbar .form-select:focus { box-shadow: none; }

.aw-user-chip { font-size: 0.8125rem; color: var(--text-secondary); }
.aw-avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--accent-50); color: var(--accent-700);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 0.75rem;
  flex-shrink: 0;
}

/* ---------- 5. Buttons ---------- */
.btn {
  font-weight: 600;
  font-size: 0.875rem;
  border-radius: var(--r-sm);
  padding: 0.5rem 1rem;
  transition: all var(--transition-fast);
  border: 1px solid transparent;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn:active { transform: translateY(0.5px); }
.btn:disabled, .btn.disabled { opacity: 0.55; box-shadow: none; }
.btn-lg { padding: 0.7rem 1.3rem; font-size: 0.9375rem; border-radius: var(--r-md); }
.btn-sm { padding: 0.32rem 0.7rem; font-size: 0.8125rem; }

.btn-primary {
  background-color: var(--accent-500);
  border-color: var(--accent-500);
  color: #fff;
}
.btn-primary:hover, .btn-primary:focus {
  background-color: var(--accent-600);
  border-color: var(--accent-600);
  box-shadow: var(--shadow-sm);
}
.btn-primary:active { background-color: var(--accent-700); border-color: var(--accent-700); }
.btn-primary:disabled { background-color: var(--accent-500); border-color: var(--accent-500); }

/* Secondary = white surface + a visible gray border. Not ghost/transparent —
   a clean, always-visible outline reads as an intentional button rather
   than a stray link, which is what actually separates a considered SaaS UI
   from a Bootstrap default; ghost-everywhere just looks unfinished. */
.btn-outline-secondary {
  color: var(--text-secondary);
  background: var(--bg-surface);
  border-color: var(--border-default);
}
.btn-outline-secondary:hover {
  background: var(--gray-50);
  border-color: var(--gray-300);
  color: var(--text-primary);
}

.btn-outline-danger { color: var(--danger); background: transparent; border-color: transparent; }
.btn-outline-danger:hover { background: var(--danger-soft); border-color: transparent; color: var(--danger); }

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

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-secondary); }
.btn-ghost:hover { background: var(--gray-100); color: var(--text-primary); }

.btn-group .btn { border-radius: var(--r-sm); }

/* ---------- 6. Cards + KPI cards ---------- */
.card {
  --bs-card-height: auto;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  /* Hug content on every page. Bootstrap's .card is a column flex whose
     .card-body has flex-grow, so a stretched card becomes a tall empty box. */
  height: auto;
  flex-grow: 0;
}
.card-header { background: transparent; border-bottom: 1px solid var(--border-subtle); padding: 0.8rem var(--space-card); }
/* Clickable cards (e.g. report launchers) lift slightly so it's obvious
   they're tappable, without any color noise. */
a.card { transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast); }
a.card:hover { border-color: var(--gray-300); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.card-body { padding: 1.15rem var(--space-card); flex: 0 0 auto; }
.card-body > .row { align-content: start; }
.card-footer { background: transparent; border-top: 1px solid var(--border-subtle); }

/* Packed field layout for show-page Details cards. Explicit column counts
   so a handful of short labels never stretch across the full content width
   (the old 3-col Bootstrap grid left a nearly empty last row). */
.detail-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.85rem 1.25rem;
  align-content: start;
}
@media (min-width: 768px) {
  .detail-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 992px) {
  .detail-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.detail-grid__span { grid-column: 1 / -1; }

/* Title + status/actions on one row — Bill & Payment, empty Activity, etc. */
.card-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.65rem 1rem;
  padding: 0.8rem var(--space-card);
}

.kpi-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.25rem 1.375rem;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.kpi-card__top { display: flex; align-items: center; justify-content: space-between; }
.kpi-card__icon {
  width: 36px; height: 36px;
  border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.05rem;
  color: var(--text-secondary);
  background: var(--gray-100);
  flex-shrink: 0;
}
.kpi-card__icon.tone-success  { color: var(--success);  background: var(--success-soft); }
.kpi-card__icon.tone-warning  { color: var(--warning);  background: var(--warning-soft); }
.kpi-card__icon.tone-danger   { color: var(--danger);   background: var(--danger-soft); }
.kpi-card__icon.tone-info     { color: var(--info);     background: var(--info-soft); }
.kpi-card__icon.tone-secondary { color: var(--text-secondary); background: var(--gray-100); }
.kpi-card__label {
  font-size: 0.75rem; font-weight: 600; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.045em;
}
.kpi-card__value {
  font-size: 1.875rem; font-weight: 800; letter-spacing: -0.03em;
  color: var(--text-primary); line-height: 1.1;
}
.kpi-card__trend { font-size: 0.75rem; font-weight: 700; display: inline-flex; align-items: center; gap: 0.2rem; }
.kpi-card__trend.up { color: var(--success); }
.kpi-card__trend.down { color: var(--danger); }
.kpi-card__trend.flat { color: var(--text-muted); }
.kpi-card__support { font-size: 0.8125rem; color: var(--text-muted); }

/* ---------- 7. Tables ---------- */
.aw-table-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  /* Visible so the Columns dropdown can extend past a short card;
     table-responsive still clips the grid for sticky headers + radius. */
  overflow: visible;
  min-width: 0;
  max-width: 100%;
  height: auto;
  flex-grow: 0;
}

/* The table itself is the vertical scrollport, so thead can stick at
   top: 0 without covering the first row. (A page-level `top: var(--topbar-h)`
   offset was sliding the header down over the first cells, because
   overflow on this wrapper makes it — not the viewport — the sticky
   containing block.) */
.table-responsive {
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  overflow: auto;
  max-height: calc(100dvh - var(--topbar-h) - 14rem);
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}

table.aw-table { margin: 0; width: 100%; border-collapse: separate; border-spacing: 0; }
table.aw-table thead th {
  position: sticky;
  top: 0;
  z-index: 5;
  /* Same indigo as .btn-primary's resting state (section 5) — not the
     darker :active shade — so the header reads as the same brand color as
     the buttons instead of a separate, heavier dark tone. */
  background: var(--accent-500);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.8rem 1.25rem;
  border-bottom: 1px solid var(--accent-500);
  white-space: nowrap;
}
table.aw-table tbody td {
  padding: 0.95rem 1.25rem;
  font-size: 0.875rem;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}
table.aw-table tbody tr:last-child td { border-bottom: none; }
table.aw-table:has(tfoot) tbody tr:last-child td { border-bottom: 1px solid var(--border-subtle); }
table.aw-table tbody tr { transition: background-color var(--transition-fast); }
table.aw-table tbody tr:hover { background: var(--gray-50); }
table.aw-table tfoot td {
  padding: 0.95rem 1.25rem;
  font-size: 0.875rem;
  color: var(--text-primary);
  background: var(--gray-50);
  border-top: 1px solid var(--border-subtle);
  vertical-align: middle;
}
table.aw-table td.text-end, table.aw-table th.text-end { text-align: right; }
table.aw-table .cell-primary { font-weight: 600; color: var(--text-primary); }
table.aw-table .cell-muted { color: var(--text-muted); }

.aw-table-footer { padding: 0.85rem 1.25rem; border-top: 1px solid var(--border-subtle); }

/* Pagination (pagy_bootstrap_nav) — Bootstrap's default is its own blue and
   heavy borders; restyle to quiet gray chips with an accent current page. */
.pagination { gap: 0.25rem; margin: 0; }
.pagination .page-link {
  border: none;
  border-radius: var(--r-sm);
  color: var(--text-secondary);
  background: transparent;
  font-size: 0.8438rem;
  font-weight: 600;
  min-width: 2.25rem;
  padding: 0.45rem 0.6rem;
  text-align: center;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}
.pagination .page-link:hover { background: var(--gray-100); color: var(--text-primary); box-shadow: none; }
.pagination .page-link:focus { box-shadow: 0 0 0 3px var(--accent-50); }
.pagination .page-item.active .page-link { background: var(--accent-500); color: #fff; }
.pagination .page-item.disabled .page-link { color: var(--gray-300); background: transparent; }

/* ---------- 8. Filter bar / toolbar ---------- */
.aw-table-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border-subtle);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  position: relative;
  z-index: 6;
}
/* The z-index above makes .aw-table-toolbar a stacking context, which
   traps anything nested inside it (including the mobile filter sheet's
   offcanvas — see application.css section 20 / stock_entries & transactions
   index views) to that local z-index of 6 — no matter how high the nested
   element's OWN z-index is, it can't escape to compete against page-level
   fixed elements like the bottom nav (1030) or install banner (1035).
   Lift the trap only while the sheet is actually open, above every fixed
   chrome element in the app, rather than raising it unconditionally (which
   would wrongly float the toolbar over the bottom nav on every scroll). */
.aw-table-toolbar:has(.offcanvas.show) { z-index: 1046; }
.aw-table-toolbar .search-wrap { position: relative; max-width: 320px; flex: 1 1 260px; }
.aw-table-toolbar .search-wrap .bi,
.aw-table-toolbar .search-wrap .ph { position: absolute; left: 0.8rem; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 0.875rem; pointer-events: none; }
.aw-table-toolbar .search-wrap input { padding-left: 2.2rem; }
.aw-table-toolbar .toolbar-actions { display: flex; gap: 0.4rem; align-items: center; }
.aw-table-toolbar .toolbar-actions .btn { color: var(--text-secondary); }

/* Generate Bill selection bar (Purchase/Sale list) — display:flex lives
   here rather than on a Bootstrap ".d-flex" utility class so the JS only
   ever has to toggle ".d-none" on/off; mixing two "!important" display
   utilities on one element is a specificity trap (whichever the vendor
   stylesheet declares last wins, regardless of source order in the class
   attribute), so this sidesteps it entirely. */
.bill-picker-bar { display: flex; }

/* ---------- 9. Forms ---------- */
.form-label { font-size: 0.8125rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.35rem; }

/* Label row with an inline "+" that opens a quick-add modal (Party, Broker
   on Purchase/Sale). The button is not a .btn so the mobile 44px min-height
   doesn't blow the label line apart. */
.field-label-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.35rem; }
.field-label-row .form-label { margin-bottom: 0; }
.field-add {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; padding: 0; flex-shrink: 0;
  border: none; border-radius: var(--r-pill);
  background: var(--accent-50); color: var(--accent-600);
  line-height: 1;
}
.field-add:hover { background: var(--accent-100); color: var(--accent-700); }
.field-add i { font-size: 0.8rem; font-weight: 700; }

/* Filled-by-default fields: no visible box until focused. This is the
   single change that most separates "Bootstrap form" from "SaaS form". */
.form-control, .form-select {
  background-color: var(--bg-field);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  padding: 0.6rem 0.8rem;
  font-size: 0.9rem;
  color: var(--text-primary);
  transition: background-color var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-control-lg, .form-select-lg { padding: 0.75rem 0.95rem; font-size: 0.9375rem; border-radius: var(--r-md); }
.form-control:focus, .form-select:focus {
  background-color: var(--bg-surface);
  border-color: var(--accent-500);
  box-shadow: 0 0 0 3px var(--accent-50);
}
.form-control:disabled, .form-control[readonly] { background-color: var(--bg-field); opacity: 0.7; }
.form-text { font-size: 0.78125rem; color: var(--text-muted); }

/* Locked/read-only display fields (e.g. the fixed Type on entry forms) are
   built as `.form-control.bg-light` divs — map Bootstrap's .bg-light to the
   same fill as real fields so locked and editable fields sit flush. */
.form-control.bg-light { background-color: var(--bg-field) !important; color: var(--text-secondary); }

/* Checkboxes & radios — Bootstrap ships these in its own blue; recolor to
   the brand accent and enlarge slightly for touch. */
.form-check-input { width: 1.15em; height: 1.15em; border: 1.5px solid var(--gray-300); }
.form-check-input:checked { background-color: var(--accent-500); border-color: var(--accent-500); }
.form-check-input:focus { border-color: var(--accent-500); box-shadow: 0 0 0 3px var(--accent-50); }

/* Native date inputs — keep iOS from rendering them shorter than text fields */
input[type="date"].form-control { min-height: calc(1.5em + 1.2rem + 2px); }
input[type="date"].form-control-lg { min-height: calc(1.5em + 1.5rem + 2px); }

.form-select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%2362687a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5l3.5 3.5 3.5-3.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.75rem center;
  background-size: 12px;
  padding-right: 2.2rem;
}

.form-section { padding-bottom: 1rem; margin-bottom: 1.25rem; border-bottom: 1px solid var(--border-subtle); }
.form-section:last-of-type { border-bottom: none; margin-bottom: 0; }
.form-section__title { font-size: 0.9375rem; font-weight: 700; margin-bottom: 0.2rem; }
.form-section__hint { font-size: 0.8125rem; color: var(--text-muted); margin-bottom: 1rem; }

/* "Grain also received/dispatched today" shortcut (Purchase/Sale/Kisani
   forms) — a quiet, bordered strip so it doesn't compete with the primary
   fields above it but still reads as its own decision point. */
.aw-shortcut-toggle { padding: 0.875rem 1rem; background: var(--gray-50); border: 1px solid var(--border-subtle); border-radius: var(--r-md); }
.aw-shortcut-toggle .form-text { margin-top: 0.35rem; margin-bottom: 0; }

.form-actions-sticky {
  position: sticky; bottom: 0;
  background: var(--bg-surface);
  border-top: 1px solid var(--border-subtle);
  padding: 1rem var(--space-card);
  display: flex; gap: 0.5rem;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* ---------- 10. Badges / status pills ---------- */
.pill {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 0.75rem; font-weight: 600;
  padding: 0.25rem 0.625rem;
  border-radius: var(--r-pill);
  line-height: 1.3;
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.pill-success { background: var(--success-soft); color: var(--success); }
.pill-warning { background: var(--warning-soft); color: var(--warning); }
.pill-danger  { background: var(--danger-soft);  color: var(--danger); }
.pill-info    { background: var(--info-soft);    color: var(--info); }
.pill-neutral { background: var(--gray-100); color: var(--text-secondary); }

/* ---------- 11. Alerts / flash ---------- */
.alert { border: 1px solid transparent; border-radius: var(--r-md); font-size: 0.875rem; padding: 0.85rem 1.1rem; }
.alert-success { background: var(--success-soft); color: #0f5c2e; border-color: #cdeada; }
.alert-danger  { background: var(--danger-soft);  color: #8a1f1f; border-color: #f4cfcf; }
.alert-light   { background: var(--gray-50); color: var(--text-secondary); border-color: var(--border-subtle); }

/* ---------- 12. Empty & loading states ---------- */
.empty-state { text-align: center; padding: 2rem 1.5rem; color: var(--text-muted); }
.empty-state__icon {
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--gray-100); color: var(--gray-400);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.4rem; margin-bottom: 1rem;
}
.empty-state__title { font-size: 0.9375rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 0.25rem; }
.empty-state__body { font-size: 0.8125rem; max-width: 340px; margin: 0 auto 1rem; }

@keyframes aw-shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }
.skeleton {
  background: linear-gradient(90deg, var(--gray-100) 25%, var(--gray-50) 37%, var(--gray-100) 63%);
  background-size: 400px 100%;
  animation: aw-shimmer 1.4s ease infinite;
  border-radius: var(--r-sm);
}

/* ---------- 13. Breadcrumb ---------- */
.aw-breadcrumb {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem;
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin-bottom: 0.6rem;
}
.aw-breadcrumb a { color: var(--text-muted); }
.aw-breadcrumb a:hover { color: var(--accent-600); }
.aw-breadcrumb .crumb-sep { color: var(--gray-300); }
.aw-breadcrumb .crumb-current { color: var(--text-secondary); font-weight: 600; }

/* ---------- 14. Modals ---------- */
.modal {
  background: transparent;
  --bs-backdrop-opacity: 0.35;
}
.modal-content { border: none; border-radius: var(--r-lg); box-shadow: var(--shadow-lg); }
.modal-header { padding: 1.25rem 1.5rem 0; }
.modal-title { font-size: 1.0625rem; font-weight: 700; }
.modal-body { padding: 0.75rem 1.5rem 1.25rem; font-size: 0.875rem; }
.modal-footer { padding: 0 1.5rem 1.25rem; }
.modal-backdrop.show,
.offcanvas-backdrop.show {
  opacity: var(--bs-backdrop-opacity);
}

/* Dropdowns / popovers — the one place shadow is used, since they float. */
.dropdown-menu {
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  padding: 0.375rem;
  margin-top: 0.4rem;
}
/* Our Stimulus dropdown only toggles `.show` (no Bootstrap Popper), so
   Bootstrap's `.dropdown-menu-end[data-bs-popper]` rule never fires and the
   menu left-aligns under the avatar — spilling past the viewport and
   looking "cut off" on the right. Force end alignment in CSS instead. */
.dropdown-menu-end {
  right: 0;
  left: auto;
}
.aw-topbar .dropdown-menu {
  min-width: 16rem;
  max-width: min(20rem, calc(100vw - 2rem));
}
.dropdown-item { border-radius: var(--r-sm); font-size: 0.875rem; padding: 0.5rem 0.75rem; color: var(--text-primary); }
.dropdown-item:hover, .dropdown-item:focus { background: var(--gray-100); }
.dropdown-item.text-danger:hover { background: var(--danger-soft); color: var(--danger); }

.column-picker-menu {
  min-width: 220px;
  max-height: min(24rem, calc(100dvh - 10rem));
  overflow-y: auto;
}
.column-picker-menu .dropdown-item { font-weight: 500; }
.column-picker-menu .form-check-input { flex-shrink: 0; }
table.aw-table .col-off { display: none !important; }

/* ---------- 15. Utilities ---------- */
.progress { background-color: var(--gray-100); border-radius: var(--r-pill); overflow: hidden; }
.progress-bar { border-radius: var(--r-pill); }

.divider-y > * + * { border-top: 1px solid var(--border-subtle); }

/* Status color utilities — for the rare icon or accent that needs to carry
   meaning outside a full component variant. Used sparingly by design: most
   icons stay neutral gray, color is reserved for genuine status. */
.icon-tone-success   { color: var(--success) !important; }
.icon-tone-info       { color: var(--info) !important; }
.icon-tone-warning    { color: var(--warning) !important; }
.icon-tone-danger     { color: var(--danger) !important; }
.icon-tone-secondary  { color: var(--text-muted) !important; }

/* Section header accent — a thin colored bar keying a single card/table
   header to a genuine status (e.g. an outstanding-amount table). Use at
   most once or twice per page, never as a default per-card treatment. */
.header-accent { border-left: 3px solid var(--border-default); padding-left: 0.75rem; }
.header-accent.tone-success  { border-left-color: var(--success); }
.header-accent.tone-warning   { border-left-color: var(--warning); }
.header-accent.tone-danger    { border-left-color: var(--danger); }

@media print {
  @page { margin: 1cm; size: A4 landscape; }

  .no-print,
  .aw-sidebar,
  .offcanvas,
  .aw-topbar,
  .aw-bottom-nav,
  .aw-table-toolbar,
  .toolbar-actions,
  .page-header > .d-flex,
  .mobile-header-actions,
  .filter-chips,
  .mlist,
  .btn,
  .btn-group,
  .pagination,
  .aw-breadcrumb,
  .bill-picker-bar,
  .form-actions-sticky,
  .modal,
  .modal-backdrop,
  .alert,
  .alert-dismissible .btn-close,
  #awInstallBanner,
  #aw-install-banner,
  #aw-install-banner:not(.hidden) {
    display: none !important;
    visibility: hidden !important;
  }

  html, body {
    background: white !important;
    color: #14161f !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Drop the app shell so content uses the full page width. */
  .aw-shell,
  .aw-content-wrap,
  .app-main {
    display: block !important;
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    min-height: 0 !important;
    overflow: visible !important;
  }

  .page-header { margin-bottom: 0.75rem !important; }
  .page-title { font-size: 1.25rem !important; margin: 0 !important; }
  .page-subtitle { display: none !important; }

  .card, .kpi-card, .aw-table-card {
    box-shadow: none !important;
    border: none !important;
    border-radius: 0 !important;
  }

  .table-responsive {
    max-height: none !important;
    overflow: visible !important;
    border-radius: 0 !important;
  }

  a { color: inherit !important; text-decoration: none !important; }

  /* Always print as a real table — never the mobile stacked-card layout
     or the Phase 2 mobile list-row pattern, regardless of the print-time
     width a given browser reports (see the comment above .aw-desktop-table
     in section 19). */
  .aw-desktop-table, .table-responsive { display: block !important; }
  table.aw-table {
    display: table !important;
    width: 100% !important;
    border-collapse: collapse !important;
    font-size: 0.75rem !important;
  }
  table.aw-table thead { display: table-header-group !important; }
  table.aw-table tbody { display: table-row-group !important; }
  table.aw-table tfoot { display: table-footer-group !important; }
  table.aw-table tr {
    display: table-row !important;
    border: none !important;
    border-radius: 0 !important;
    margin: 0 !important;
    width: auto !important;
    overflow: visible !important;
    page-break-inside: avoid;
  }
  table.aw-table th,
  table.aw-table td {
    display: table-cell !important;
    width: auto !important;
    padding: 0.35rem 0.4rem !important;
    border-bottom: 1px solid #d7dae0 !important;
    white-space: nowrap !important;
    text-align: left !important;
    vertical-align: middle !important;
  }
  table.aw-table th.text-end,
  table.aw-table td.text-end { text-align: right !important; }
  table.aw-table td::before { content: none !important; display: none !important; }

  table.aw-table thead th {
    background: white !important;
    color: #262b3a !important;
    border-bottom: 2px solid #454b5c !important;
    font-weight: 700 !important;
  }
  table.aw-table tbody tr:hover { background: transparent !important; }
  table.aw-table tfoot td {
    border-top: 2px solid #454b5c !important;
    border-bottom: none !important;
    font-weight: 700 !important;
    padding-top: 0.55rem !important;
  }

  /* Keep column-picker hidden columns and chrome cells hidden when printing. */
  table.aw-table .col-off,
  table.aw-table th.no-print,
  table.aw-table td.no-print { display: none !important; }
}

/* ---------- 16. Compact screens ---------- */
@media (max-width: 1199.98px) {
  .app-main { padding: 1.25rem 1.25rem 3rem; }

  /* Filters wrap to the content width instead of forcing a page-wide
     horizontal scroll next to the sidebar. */
  .aw-table-toolbar { padding: 0.875rem 1rem; }
  .aw-table-toolbar form { width: 100%; }
  .aw-table-toolbar .search-wrap { max-width: 100% !important; flex: 1 1 220px; }
  .aw-table-toolbar .form-select,
  .aw-table-toolbar input[type="date"] { max-width: 100% !important; flex: 1 1 160px; }
}

/* ---------- 17. Mobile (app-like, not shrunken-desktop) ----------
   Threshold is 991.98px, matching the sidebar's own offcanvas-lg
   breakpoint — the whole shell (nav, tables, modals, toolbars) commits to
   the app-like treatment for the entire tablet band too, instead of
   leaving 768–991 as a half-broken desktop layout. Density tweaks that
   only make sense on the narrowest phones (title size, KPI stepping) get
   their own nested ≤767.98px query further down. Desktop (≥992px) is
   untouched. */
@media (max-width: 991.98px) {
  :root {
    /* Total height the fixed bottom nav occupies, safe-area included —
       every other fixed/sticky element at the bottom of the screen
       (install banner, sticky form actions) offsets against this so
       nothing stacks on top of anything else. 0 on desktop by virtue of
       this custom property not existing outside this query. */
    --bnav-h: 60px;
    --bnav-total-h: calc(var(--bnav-h) + env(safe-area-inset-bottom, 0px));
  }

  .app-main { padding: 1rem 1rem calc(var(--bnav-total-h) + 1.5rem); }
  .aw-topbar { padding: env(safe-area-inset-top, 0px) 1rem 0; }
  .page-header { margin-bottom: 1.25rem; }

  /* iOS Safari zooms the whole page on focusing any input under 16px —
     bump to exactly 16px here rather than globally, so desktop keeps its
     tighter 0.9rem density. */
  .form-control, .form-select { font-size: 1rem; }

  /* Touch targets: every button comfortably tappable. Row-action icon
     buttons especially — desktop-sized btn-sm is too small for thumbs. */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 40px; min-width: 40px; padding: 0.45rem 0.8rem; }
  .form-control, .form-select { min-height: 44px; }
  .form-check-input { width: 1.4em; height: 1.4em; }
  .sidebar-link { padding: 0.7rem 0.65rem; }
  .dropdown-item { padding: 0.65rem 0.75rem; }

  /* Two-way segmented toggle (Payment Made/Received, Purchase/Sale Bills) —
     full-width, equal-split buttons instead of a cramped inline pair. */
  .aw-tab-toggle { display: flex; width: 100%; }
  .aw-tab-toggle .btn { flex: 1; }

  /* Warehouse switcher: was hidden below `sm` before Phase 1 — now always
     shown, so it needs to actually fit next to the bell/avatar on a
     360px phone instead of pushing them off-screen. */
  .aw-warehouse-switcher { min-width: 0; flex: 1 1 auto; }
  .aw-warehouse-switcher select { min-width: 0; max-width: 100%; }

  .aw-table-toolbar .search-wrap { flex: 1 1 100%; }
  .aw-table-toolbar .form-select,
  .aw-table-toolbar input[type="date"] { flex: 1 1 44%; }
  .aw-table-toolbar .toolbar-actions { width: 100%; justify-content: flex-end; }

  .table-responsive { max-height: none; overflow: visible; }

  /* Tables become stacked cards */
  table.aw-table thead { display: none; }
  table.aw-table, table.aw-table tbody, table.aw-table tfoot, table.aw-table tr, table.aw-table td { display: block; width: 100%; }
  table.aw-table tr {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    margin: 0.75rem;
    width: auto;
    overflow: hidden;
  }
  table.aw-table td {
    display: flex; justify-content: space-between; align-items: center;
    gap: 1rem; text-align: right;
    padding: 0.7rem 1rem;
    border-bottom: 1px solid var(--border-subtle);
    white-space: normal;
  }
  table.aw-table td:last-child { border-bottom: none; }
  table.aw-table td::before {
    content: attr(data-label);
    font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-muted); text-align: left; flex-shrink: 0;
  }
  table.aw-table td[data-label=""] { justify-content: flex-end; gap: 0.5rem; }
  table.aw-table td[data-label=""]::before { content: none; }
  table.aw-table tfoot td[colspan] { justify-content: flex-end; }
  table.aw-table tfoot td[colspan]::before { content: none; }

  /* Sticky form actions: full-width, thumb-reachable buttons, floated
     above the fixed bottom nav (var(--bnav-total-h)) instead of the bare
     viewport edge — position:sticky's "bottom" offset is measured from
     the scrollport edge, which the bottom nav visually covers, so without
     this the Save button ends up hidden behind the nav bar at the end of
     a long form. */
  .form-actions-sticky {
    flex-direction: column-reverse;
    padding: 0.875rem 1rem;
    bottom: var(--bnav-total-h);
  }
  .form-actions-sticky .btn { width: 100%; }

  /* Live Net Weight / Net Amount strip pinned above Save (Phase 4) — see
     transactions/_form.html.erb for why it's the last DOM child here. */
  .mobile-summary-strip {
    display: flex; width: 100%; gap: 1rem;
    padding-bottom: 0.75rem; margin-bottom: 0.125rem;
    border-bottom: 1px solid var(--border-subtle);
  }
  .mobile-summary-strip__item { flex: 1 1 0; min-width: 0; }
  .mobile-summary-strip__item--primary { text-align: right; }
  .mobile-summary-strip__label {
    display: block; font-size: 0.6875rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.15rem;
  }
  .mobile-summary-strip__value { font-size: 1.125rem; font-weight: 800; color: var(--text-primary); }
  .mobile-summary-strip__item--primary .mobile-summary-strip__value { color: var(--accent-600); }

  /* Show-page header buttons (Edit/Void/Unvoid etc.) — bare buttons yielded
     directly into the page header, not wrapped in .btn-group. Direct-child
     selector so this never touches buttons already handled one level
     deeper by .btn-group or .mobile-header-actions (both set their own
     internal layout below). */
  .page-header__actions { width: 100%; }
  .page-header__actions > .btn { flex: 1 1 auto; min-width: 45%; }

  /* Page-header actions: stretch to a comfortable row. Pages that opt into
     the primary+more pattern (see mobile-header-actions below) hide the
     full desktop button group instead — this rule is the fallback for any
     page-header that hasn't been split yet. */
  .page-header .btn-group { width: 100%; display: flex; }
  .page-header .btn-group .btn { flex: 1; }

  /* Primary+More header pattern (Phase 8): one full-width primary action
     plus an icon-only "More" trigger for secondary actions, shown only
     below lg in place of the desktop button group. See stock_entries,
     transactions and dashboard index views. */
  .mobile-header-actions { display: flex; gap: 0.5rem; width: 100%; }
  .mobile-header-actions .btn-primary { flex: 1 1 auto; }
  .mobile-header-actions .dropdown-toggle-more { flex: 0 0 auto; padding-left: 0.9rem; padding-right: 0.9rem; }

  /* Modals: bottom-sheet feel — slides reads as native app, not web dialog */
  .modal-dialog-centered { align-items: flex-end; min-height: 100%; margin: 0; max-width: none; }
  .modal-dialog-centered .modal-content { border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .modal-footer { flex-direction: column-reverse; gap: 0.5rem; }
  .modal-footer .btn, .modal-footer form, .modal-footer form .btn { width: 100%; }
  .modal-footer > * { margin: 0; }

  /* Pagination: centered, roomy */
  .pagination { justify-content: center; flex-wrap: wrap; }
  .pagination .page-link { min-width: 2.75rem; padding: 0.6rem 0.7rem; }

  /* KPI value size steps down so 4-up grids never clip. A large rupee
     figure (crores, 8+ digits with Indian grouping) can still be wider
     than a 2-up card on a 360–390px phone even at this size — wrapping to
     a second line beats letting it silently bleed past the card edge. */
  .kpi-card { padding: 1rem 1.125rem; min-width: 0; }
  .kpi-card__value { font-size: 1.5rem; white-space: normal; overflow-wrap: break-word; line-height: 1.15; }

  /* Bill picker bar: stack count + button */
  .bill-picker-bar { flex-direction: column; align-items: stretch !important; }
  .bill-picker-bar .btn { width: 100%; }

  /* Narrowest phones only — a 360–430px screen needs a smaller title than
     a 768–991 tablet does; everything else in this file already scales
     fine down to 360px without a phone-only override. */
  @media (max-width: 767.98px) {
    .page-title { font-size: 1.375rem; }
    .kpi-card__value { font-size: 1.25rem; }
  }

  /* An 8-digit rupee figure (crores) in a 2-up grid still doesn't fit on
     one line at 1.25rem below ~380px — it wraps mid-number and orphans a
     single trailing digit onto its own line. One more step down here
     keeps it to one line (or a clean two-line break) instead. */
  @media (max-width: 380px) {
    .kpi-card__value { font-size: 1.0625rem; }
  }
}

/* Dashboard "See more" disclosure (Phase 5) — Warehouse-wise Stock /
   Occupancy / Low Stock are collapsed by default below lg via Bootstrap's
   own .collapse, toggled by a d-lg-none button. At/above lg that trigger
   never renders, so nothing calls .show — force the panel open there
   regardless of collapse state rather than relying on it. */
@media (min-width: 992px) {
  .dash-desktop-open.collapse { display: block !important; height: auto !important; visibility: visible !important; }
}

/* ---------- 18. Bottom navigation ---------- */
/* Persistent tab bar, below lg only (markup carries d-lg-none so this file
   doesn't have to duplicate the breakpoint) — the phone-app equivalent of
   the sidebar. Items are built server-side per user's feature flags (see
   layouts/_bottom_nav.html.erb); "More" always opens the same offcanvas
   the desktop sidebar uses, so there's exactly one nav drawer in the app,
   not two competing ones. */
.aw-bottom-nav {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 1030;
  display: flex;
  background: var(--bg-surface);
  border-top: 1px solid var(--border-subtle);
  box-shadow: 0 -2px 12px rgba(15, 23, 42, 0.06);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
.aw-bottom-nav__item {
  flex: 1 1 0;
  min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.15rem;
  height: var(--bnav-h, 60px);
  border: none; background: transparent;
  color: var(--text-muted);
  font-size: 0.6875rem; font-weight: 600;
  text-decoration: none;
  transition: color var(--transition-fast);
}
.aw-bottom-nav__item i { font-size: 1.2rem; line-height: 1; }
.aw-bottom-nav__item span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.aw-bottom-nav__item.active { color: var(--accent-600); }
.aw-bottom-nav__item:active { background: var(--gray-50); }
.aw-bottom-nav__item:hover { color: var(--accent-600); }

/* ---------- 18b. Mobile "More" menu ---------- */
/* The bottom nav's "More" tab is a real page (see more/index.html.erb),
   not the desktop sidebar reused in a drawer — that reuse was exactly
   what made it feel like laptop navigation shrunk onto a phone. Rows
   reuse the sidebar's own icon set but at thumb size, grouped into
   titled sections that are always open (no collapsible submenus — this
   page has nothing else competing for space, unlike the sidebar rail). */
.more-section-title {
  font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); margin: 1.5rem 0 0.5rem;
}
.more-section-title:first-child { margin-top: 0; }
.more-menu {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.more-menu-row {
  display: flex; align-items: center; gap: 0.9rem;
  padding: 0.9rem 1.1rem;
  min-height: 44px;
  color: var(--text-primary);
  text-decoration: none;
  border-bottom: 1px solid var(--border-subtle);
}
.more-menu-row:last-child { border-bottom: none; }
.more-menu-row:hover { color: var(--text-primary); background: var(--gray-50); }
.more-menu-row:active { background: var(--gray-50); }
.more-menu-row > i:first-child { font-size: 1.1rem; color: var(--text-secondary); width: 1.5rem; text-align: center; flex-shrink: 0; }
.more-menu-row span { flex: 1 1 auto; font-weight: 600; font-size: 0.9375rem; }
.more-menu-row .bi-chevron-right { color: var(--gray-300); font-size: 0.8rem; width: auto; flex-shrink: 0; }

/* ---------- 19. Mobile list row ---------- */
/* The scannable list pattern for Phase 2: title + subline + one hero
   number + a status pill, the whole row a single tap target to the show
   page. This replaces the stacked-spreadsheet `data-label` table on
   mobile for the highest-traffic lists (desktop keeps the real table —
   see each index view for the `d-none d-lg-block` / `d-lg-none` split).
   Kept in application.css rather than scoped to one page since the same
   four-piece shape (title/subline/hero/status) applies to every
   transaction-like list the product has. */
/* Desktop table / mobile list-row split — deliberately NOT Bootstrap's
   .d-none/.d-lg-block utilities. Those are viewport-width media queries
   with no "type: screen" qualifier, so a browser that evaluates print
   layout against anything other than the on-screen window width (several
   do) can re-satisfy the min-width condition while printing and bring
   the .d-lg-block table back — or worse, leave the mobile list showing.
   These two classes make the screen/print split explicit and print-only
   rules below force the correct one regardless of print-time width. */
@media (max-width: 991.98px) { .aw-desktop-table { display: none !important; } }
@media (min-width: 992px) { .mlist { display: none !important; } }

.mlist { display: flex; flex-direction: column; }
.mlist-row {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.85rem 1.25rem;
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-primary);
  text-decoration: none;
  min-height: 44px;
}
.mlist-row:last-child { border-bottom: none; }
.mlist-row:active { background: var(--gray-50); }
.mlist-row:hover { color: var(--text-primary); background: var(--gray-50); }
.mlist-row__main { flex: 1 1 auto; min-width: 0; }
.mlist-row__title {
  font-weight: 600; font-size: 0.9375rem; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mlist-row__subline {
  font-size: 0.8125rem; color: var(--text-muted); margin-top: 0.15rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mlist-row__side { flex: 0 0 auto; text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 0.3rem; }
.mlist-row__hero { font-weight: 700; font-size: 0.9375rem; }
.mlist-row__chevron { flex: 0 0 auto; color: var(--gray-300); font-size: 0.9rem; }
.mlist-empty { padding: 3rem 1.5rem; }

/* Stock Position mobile cards — one per warehouse+commodity group, since a
   12-column table doesn't fit a phone (see stock_position/index.html.erb). */
.aw-position-cards { display: flex; flex-direction: column; gap: 0.75rem; padding: 0.25rem 0; }
.aw-position-card { border: 1px solid var(--border-subtle); border-radius: var(--r-lg); padding: 1rem; background: var(--bg-surface); }
.aw-position-card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 0.75rem; margin-bottom: 0.75rem; }
.aw-position-card__title { font-weight: 700; font-size: 1rem; }
.aw-position-card__hero { text-align: right; }
.aw-position-card__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem 1rem; }
.aw-position-card__grid--sub { margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px dashed var(--border-subtle); }
.aw-position-card__toggle {
  display: flex; align-items: center; gap: 0.3rem; width: 100%; justify-content: center;
  background: none; border: none; color: var(--text-muted); font-size: 0.8125rem;
  padding: 0.5rem 0 0; margin-top: 0.4rem;
}
.aw-position-card__toggle[aria-expanded="true"] i { transform: rotate(180deg); }

/* ---------- 20. Filter sheet ---------- */
/* Reuses the exact same trick as .aw-sidebar.offcanvas.offcanvas-lg
   (section 4): Bootstrap's own `.offcanvas-lg` makes the element a
   full drawer below lg and a plain static block at/above lg. Below lg we
   want the drawer (a bottom sheet); at/above lg we want the filter
   fields to render inline exactly as the current desktop toolbar does —
   so this override cancels the "static panel" behavior Bootstrap ships
   for the ABOVE-lg case and replaces it with an inline flex row instead
   of a full-height block. One set of <select>/<input> fields in the DOM,
   never duplicated, so params/persist-filters/backend stay untouched. */
.aw-filter-sheet.offcanvas-bottom {
  height: auto;
  max-height: min(80vh, 620px);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.aw-filter-sheet .offcanvas-header { padding: 1rem 1.25rem 0.5rem; }
.aw-filter-sheet .offcanvas-title { font-size: 1rem; font-weight: 700; }
.aw-filter-sheet .offcanvas-body { padding: 0.5rem 1.25rem 1.25rem; padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px)); }
.aw-filter-sheet .offcanvas-body .form-label { display: block; margin-top: 0.85rem; }
.aw-filter-sheet .offcanvas-body .form-label:first-child { margin-top: 0; }
.aw-filter-sheet__actions { display: flex; gap: 0.5rem; margin-top: 1.25rem; }
.aw-filter-sheet__actions .btn { flex: 1; }

@media (min-width: 992px) {
  .aw-filter-sheet.offcanvas.offcanvas-lg {
    position: static !important;
    visibility: visible !important;
    transform: none !important;
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    /* Claim the toolbar row's remaining width instead of shrink-wrapping
       to its own natural content size — without this, this element (a
       flex item in the toolbar form's row alongside the search box) sizes
       narrowly, so its OWN nested flex-wrap below kicks in far too early
       and a filter sheet with just 1-2 fields (e.g. Stock In/Out) visibly
       wraps one field per line instead of sitting inline with the search
       box like every other toolbar control. */
    flex: 1 1 auto !important;
    min-width: 0 !important;
    width: auto !important;
    max-width: none !important;
    height: auto !important;
    max-height: none !important;
    background: transparent !important;
    box-shadow: none !important;
    border-radius: 0 !important;
  }
  .aw-filter-sheet .offcanvas-header,
  .aw-filter-sheet__actions { display: none !important; }
  .aw-filter-sheet .offcanvas-body {
    display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem;
    padding: 0; overflow: visible;
    flex: 1 1 auto; min-width: 0;
  }
  .aw-filter-sheet .offcanvas-body .form-label { display: none; }
  .aw-filter-sheet .filter-date-input { max-width: 150px; }
  .aw-filter-sheet .offcanvas-body select.form-select { max-width: 190px; }
}

/* Trigger button badge — count of active (non-empty) filters, computed
   server-side in the view so no JS is needed just to show a number. */
.filter-trigger__badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.125rem; height: 1.125rem; padding: 0 0.3rem;
  border-radius: var(--r-pill);
  background: var(--accent-500); color: #fff;
  font-size: 0.6875rem; font-weight: 700;
  margin-left: 0.35rem;
}

/* Active filter chips row above a list — each one removable independently
   without opening the sheet. */
.filter-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; padding: 0.75rem 1.25rem 0; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  background: var(--accent-50); color: var(--accent-700);
  border-radius: var(--r-pill);
  padding: 0.3rem 0.5rem 0.3rem 0.75rem;
  font-size: 0.8125rem; font-weight: 600;
  text-decoration: none;
  line-height: 1.2;
}
.filter-chip:hover { background: var(--accent-100); color: var(--accent-700); }
.filter-chip i { font-size: 0.7rem; }
.filter-chip__clear {
  background: var(--gray-100); color: var(--text-secondary);
  border-radius: var(--r-pill);
  padding: 0.3rem 0.75rem;
  font-size: 0.8125rem; font-weight: 600;
  text-decoration: none;
}

/* ---------- 21. Combo select (mobile searchable picker) ---------- */
/* Party/Broker on the Purchase-Sale form: a plain <select> with 100+
   options is unusable with a thumb. Below lg the real <select> is
   visually replaced by a form-control-styled trigger button that opens a
   search sheet; the select itself stays in the DOM, focusable and part
   of native constraint validation (see the clip technique below) rather
   than display:none, so a "required" Party field still surfaces a real
   error instead of silently blocking Save with nothing visible. */
.combo-select { position: relative; }

@media (max-width: 991.98px) {
  .combo-select__native {
    position: absolute !important;
    width: 1px !important; height: 1px !important;
    padding: 0 !important; margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
  }
}

.combo-select__trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  width: 100%; text-align: left;
  background-color: var(--bg-field); border: 1px solid transparent; border-radius: var(--r-sm);
  padding: 0.6rem 0.8rem; font-size: 1rem; color: var(--text-primary);
  min-height: 44px;
}
.combo-select__trigger span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.combo-select__trigger--empty span { color: var(--text-muted); }
.combo-select__trigger i { color: var(--text-muted); font-size: 0.75rem; flex-shrink: 0; }
.combo-select__trigger:focus-visible { outline: 3px solid var(--accent-100); outline-offset: 1px; }

.combo-select__sheet.offcanvas-bottom { height: auto; max-height: 75vh; border-radius: var(--r-lg) var(--r-lg) 0 0; }
.combo-select__sheet .offcanvas-header { padding: 1rem 1.25rem 0.5rem; }
.combo-select__sheet .offcanvas-title { font-size: 1rem; font-weight: 700; }
.combo-select__sheet .offcanvas-body { padding: 0; display: flex; flex-direction: column; min-height: 0; }
.combo-select__search-wrap { padding: 0 1.25rem 0.75rem; flex-shrink: 0; }
.combo-select__list { overflow-y: auto; padding: 0.25rem 0 calc(0.5rem + env(safe-area-inset-bottom, 0px)); }
.combo-select__option {
  display: block; width: 100%; text-align: left; background: none; border: none;
  padding: 0.75rem 1.25rem; font-size: 0.9375rem; color: var(--text-primary);
  min-height: 44px;
}
.combo-select__option:active, .combo-select__option:hover { background: var(--gray-50); }

/* Against picker (Stock In/Out ↔ Purchase/Kisani Purchase/Sale/Kisani Sale) —
   reuses combo-select__option but needs a two-line row (title + remaining). */
.against-picker__option { min-height: 56px; padding-top: 0.625rem; padding-bottom: 0.625rem; }
.against-picker__title { display: block; font-weight: 600; }
.against-picker__title--active::before { content: "✓ "; color: var(--accent-500); }
.against-picker__subline { display: block; font-size: 0.8125rem; margin-top: 0.125rem; }

/* Quick "Against" linking straight from the Stock In/Out index (no need to
   open the full edit page) — a small pencil trigger next to the badge/link,
   opening against_link_modal.html.erb. */
.against-link-trigger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: var(--r-md);
  border: none; background: none; color: var(--text-muted); font-size: 0.875rem;
  flex-shrink: 0;
}
.against-link-trigger:hover, .against-link-trigger:focus-visible { background: var(--gray-100); color: var(--text-primary); }
.against-link-modal .modal-content { overflow: hidden; }
.against-link-modal__list { max-height: 50vh; overflow-y: auto; }

/* ---------- 22. Mobile dynamic rows (Kisani trucks/payments, Stock Transfer lines) ---------- */
/* Repeating "add a row" fields (Kisani's Delivery Trucks / Payments,
   Stock Transfer's Split lines) used fixed-width columns and hid their
   header labels below md — on a phone the fields collapsed onto each
   other with no clue what was what. Below lg (matching the rest of the
   mobile pass) each row becomes a labeled, stacked mini-card instead;
   at/above lg this resolves back to the original compact single-line row
   (.mobile-row-label is hidden there, .kisani-col-* widths return to the
   fixed px the desktop layout was designed around). Dynamically added
   rows (Add Truck / Add Payment / Add line) use the exact same classes —
   see kisani_trucks_controller.js, kisani_payments_controller.js and
   stock_transfer_form_controller.js — so this applies without further JS
   changes beyond keeping their generated markup in sync. */
.mobile-row-label {
  display: block;
  font-size: 0.75rem; font-weight: 600; color: var(--text-secondary);
  margin-bottom: 0.25rem;
}

@media (min-width: 992px) {
  .mobile-row-label { display: none; }
  .kisani-col-bags { width: 140px; }
  .kisani-col-qty { width: 160px; }
}

@media (max-width: 991.98px) {
  .kisani-truck-row, .kisani-payment-row, .stock-transfer-line-row {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    padding: 0.85rem 0.85rem 0.25rem;
    margin-left: 0; margin-right: 0;
  }
  .mobile-row-remove { margin-top: 0.25rem; }
  .mobile-row-remove .btn { width: 100%; }
  .mobile-row-actions { margin-top: 0.25rem; gap: 0.5rem; }
  .mobile-row-actions .btn { flex: 1; }
}
