/* IMP design tokens + core components + app shell. Mobile-first; big touch
   targets. Type is the OS system-UI stack (matching the old IMP); IMP blue.

   SECTION INDEX
   1. tokens      — light theme (:root), dark theme (media query + data-theme)
   2. base        — reset, focus, typography, links, .container, utilities
   3. layout+shell — page header, topbar, theme toggle, sidebar, page, footer
   4. components  — buttons, flash messages, table controls, search, forms,
                    tables (incl. stacked mobile cards), badges, panels, cards,
                    alerts, checklists
   5. pages       — auth, storefront, legal, dashboard, onboarding, booking
                    detail, yard, activity, messaging, profile, inspections,
                    staff metrics (charts, stat tiles, month expanders), gate
   6. responsive  — cross-cutting mobile tap targets

   Every color in this file is a token defined in section 1 — no hex/rgb
   literals below the token blocks. That is what makes dark mode a pure
   token swap. */

/* ==================================================================== */
/* 1. TOKENS — light theme                                              */
/* ==================================================================== */

:root {
  color-scheme: light;
  /* color — admin-dashboard feel ported from the old app: gray page,
     white cards with a soft shadow, blue accent. */
  --color-bg: #f9fafb;             /* gray-50 page behind cards */
  --color-card: #ffffff;           /* white cards / panels / shell */
  --color-surface: #f3f4f6;        /* gray-100 — hovers, active fills, chrome */
  --color-border: #e5e7eb;         /* gray-200 hairlines */
  --color-border-strong: #d1d5db;  /* gray-300 — dashed file-input border */
  --color-text: #111827;           /* gray-900 — sidebar + general text */
  --color-text-secondary: #4b5563; /* gray-600 — table row text */
  --color-text-label: #374151;     /* gray-700 — form labels, intro copy */
  --color-text-muted: #6b7280;     /* gray-500 */
  --color-primary: #4379ea;        /* IMP blue — buttons, accents (large elements) */
  --color-primary-hover: #2d5dc1;  /* darker blue — hover + small text links */
  --color-primary-contrast: #ffffff; /* text on filled primary/danger chips */
  --color-primary-tint: #eff6ff;   /* blue-50 — checked radio pill fill */
  --color-primary-soft: #eef4fb;   /* own-message chat bubble */
  --color-highlight-bg: #eef3fd;   /* faint-blue booking info card / hero */
  --color-highlight-border: #cdddfb;
  /* success/danger are a deliberately colorblind-separable pair: a cooler
     emerald vs a warmer vivid red differ in the blue–yellow channel that
     red–green color blindness still sees (Jason, 2026-07-24). The status word
     always renders too, so color is a fast second signal, not the only one. */
  --color-success: #067647;        /* emerald-700 — cooler green */
  --color-success-bg: #ecfdf5;     /* green-50 — "Done" checklist chip */
  --color-success-border: #a7f3d0; /* green-200 */
  --color-warning: #b35c00;
  --color-warning-strong: #b45309; /* amber-700 — document warnings */
  --color-warning-bg: #fffbeb;     /* amber-50 — warning alert */
  --color-warning-border: #fde68a; /* amber-200 */
  --color-warning-text: #92400e;   /* amber-800 */
  --color-danger: #d5271b;         /* red-600 — warmer, higher-contrast */
  --color-info-bg: #eff6ff;        /* blue-50 — info alert */
  --color-info-text: #1e3a8a;      /* blue-900 */
  --color-star: #eab308;           /* rating stars */
  /* Catalog-type identity hues (Jason, 2026-08-10): one distinct color per
     listing type so type chips tell apart at a glance. Equipment keeps the
     IMP blue (--color-primary); these cover the rest. */
  --color-type-yard: #7c3aed;      /* violet-600 — yard space */
  --color-type-service: #0f766e;   /* teal-700 — service */
  --color-type-load: #be185d;      /* pink-700 — load */
  /* Equipment-item chips: hue is hashed per item name (core badges.item_hue);
     saturation/lightness live here so dark mode brightens like every badge. */
  --color-item-s: 55%;
  --color-item-l: 37%;
  /* Form-select chevron (replaces the native arrow); embedded stroke is
     gray-500 to match --color-text-muted, swapped lighter in dark. */
  --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%236b7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --color-chip-active-bg: #111827; /* active filter chip (inverse fill) */
  --color-chip-active-text: #ffffff;
  --color-backdrop: rgba(26, 33, 41, 0.4);  /* mobile sidebar scrim */

  /* elevation */
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-topbar: 0 1px 2px rgba(26, 33, 41, 0.05);
  --shadow-raised: 0 2px 6px rgba(0, 0, 0, 0.15);
  --shadow-auth-card: 0 12px 40px rgba(16, 24, 40, 0.10);  /* the sign-in card's lift */
  --shadow-auth-footer: 0 -2px 10px rgba(16, 24, 40, 0.05); /* upward, mirrors the topbar */

  /* theme-conditional display helpers (logo swap, sun/moon toggle):
     show-in-dark is none in light, block in dark — and vice versa. */
  --show-in-dark: none;
  --show-in-light: block;

  /* spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;

  /* type scale */
  --text-xs: 0.75rem;   /* button labels (old-platform parity) */
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.25rem;
  --text-xl: 1.75rem;

  --radius: 8px;
  --radius-sm: 6px;     /* buttons (old-platform parity) */
  --touch-target: 44px;

  /* shell */
  --topbar-height: 56px;
  --sidebar-width: 16rem;
}

/* Dark theme — token swap only; no selectors below change for dark mode.
   Two hooks, SAME token values (keep the blocks in sync, byte for byte):
   1. system preference, unless the user forced light via the toggle;
   2. the user forced dark via the toggle (data-theme stamped pre-paint
      by the inline script in base.html, persisted in localStorage).
   Values: the app's grays inverted (gray-900 cards on a near-black navy
   page), same IMP blue, status colors brightened for AA on dark cards,
   shadows dropped (they read as glow on dark — borders carry elevation). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --color-bg: #0b1220;
    --color-card: #111827;
    --color-surface: #1f2937;
    --color-border: #374151;
    --color-border-strong: #4b5563;
    --color-text: #f9fafb;
    --color-text-secondary: #d1d5db;
    --color-text-label: #d1d5db;
    --color-text-muted: #9ca3af;
    --color-primary-hover: #6c96ee;   /* links/hover need the LIGHTER blue on dark */
    --color-primary-tint: #1b2a4a;
    --color-primary-soft: #1d3050;
    --color-highlight-bg: #16233d;
    --color-highlight-border: #2b4373;
    --color-success: #3fb950;
    --color-success-bg: #12261b;
    --color-success-border: #1f6f3d;
    --color-warning: #d29922;
    --color-warning-strong: #e3a53a;
    --color-warning-bg: #2a2110;
    --color-warning-border: #5c451a;
    --color-warning-text: #edc25e;
    --color-danger: #f85149;
    --color-type-yard: #a78bfa;      /* violet-400 — brightened for dark cards */
    --color-type-service: #2dd4bf;   /* teal-400 */
    --color-type-load: #f472b6;      /* pink-400 */
    --color-item-s: 62%;
    --color-item-l: 71%;             /* brightened hashed-item chips on dark */
    --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%239ca3af' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --color-info-bg: #16233d;
    --color-info-text: #a8c3f5;
    --color-chip-active-bg: #f9fafb;
    --color-chip-active-text: #111827;
    --color-backdrop: rgba(0, 0, 0, 0.6);
    --shadow-card: none;
    --shadow-topbar: 0 1px 2px rgba(0, 0, 0, 0.6);
    --shadow-raised: 0 2px 6px rgba(0, 0, 0, 0.6);
    --shadow-auth-card: none;
    --shadow-auth-footer: none;
    --show-in-dark: block;
    --show-in-light: none;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --color-bg: #0b1220;
  --color-card: #111827;
  --color-surface: #1f2937;
  --color-border: #374151;
  --color-border-strong: #4b5563;
  --color-text: #f9fafb;
  --color-text-secondary: #d1d5db;
  --color-text-label: #d1d5db;
  --color-text-muted: #9ca3af;
  --color-primary-hover: #6c96ee;   /* links/hover need the LIGHTER blue on dark */
  --color-primary-tint: #1b2a4a;
  --color-primary-soft: #1d3050;
  --color-highlight-bg: #16233d;
  --color-highlight-border: #2b4373;
  --color-success: #3fb950;
  --color-success-bg: #12261b;
  --color-success-border: #1f6f3d;
  --color-warning: #d29922;
  --color-warning-strong: #e3a53a;
  --color-warning-bg: #2a2110;
  --color-warning-border: #5c451a;
  --color-warning-text: #edc25e;
  --color-danger: #f85149;
  --color-type-yard: #a78bfa;      /* violet-400 — brightened for dark cards */
  --color-type-service: #2dd4bf;   /* teal-400 */
  --color-type-load: #f472b6;      /* pink-400 */
  --color-item-s: 62%;
  --color-item-l: 71%;             /* brightened hashed-item chips on dark */
  --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%239ca3af' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --color-info-bg: #16233d;
  --color-info-text: #a8c3f5;
  --color-chip-active-bg: #f9fafb;
  --color-chip-active-text: #111827;
  --color-backdrop: rgba(0, 0, 0, 0.6);
  --shadow-card: none;
  --shadow-topbar: 0 1px 2px rgba(0, 0, 0, 0.6);
  --shadow-raised: 0 2px 6px rgba(0, 0, 0, 0.6);
  --shadow-auth-card: none;
  --shadow-auth-footer: none;
  --show-in-dark: block;
  --show-in-light: none;
}

/* ==================================================================== */
/* 2. BASE — reset, focus, typography, links, container, utilities      */
/* ==================================================================== */

* { box-sizing: border-box; }

/* visible keyboard focus for buttons/links/toggles (a11y basics) */
:where(a, button, summary, [tabindex]):focus-visible,
:where(input[type="checkbox"], input[type="radio"], input[type="file"]):focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* Typing fields and selects show focus as a quiet border darken, never a
   ring — browsers apply :focus-visible to them on tap/click too, and a blue
   halo around every tapped search box read as noise (Jason, 2026-07-16).
   Keyboard focus stays visible: the darkened border is the indicator. */
:where(input, select, textarea):not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus {
  outline: none;
  border-color: var(--color-border-strong);
}
/* Every select wears the app's look, not the browser's (Jason, 2026-08-19).
   This used to be per-container — .form-row, .filter-bar, .page-size,
   .action-bar, .table-edit — so a bare <select> anywhere else (the Team page's
   role picker, a filter dropdown on a new page) fell back to the platform
   widget and read as a different application. Written with :where() so it
   carries ZERO specificity: every container rule below still wins on size and
   width without needing to out-specify anything. */
:where(select):not([multiple]) {
  min-height: 34px;
  padding: var(--space-1) calc(var(--space-2) * 2 + 16px) var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card) var(--select-arrow) no-repeat right var(--space-2) center;
  background-size: 16px;
  appearance: none;
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}

body {
  margin: 0;
  /* Old-platform parity (Jason, 2026-07-26): dj-imp declared Poppins but
     never loaded it, so every app page fell through to the browser's GENERIC
     sans-serif (Helvetica on macOS, Arial on Windows) rendered with
     Tailwind's `antialiased`. That combination is the thin text Jason wants
     — the previous system-ui stack (SF Pro) plus subpixel smoothing read
     heavier at the same weights. */
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--color-text);
  background: var(--color-bg);
}

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

/* visually hidden, still read by screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Consistent heading typography app-wide — without these, headings fall to
   browser defaults (h1 32px/700, h2 24px/700) except where a context class
   overrides them, which drifts weight/size across pages. Compact contexts
   (.detail-panel h2, .table-card-head, .dash-card) override intentionally. */
/* dj-imp type scale (Jason, 2026-07-12): page/card titles were 20px bold
   (text-xl/700), sub-labels 18px semibold (text-lg/600) — tighter and bolder
   than the 28px/500 scale this shipped with. */
h1 { font-size: 1.25rem; font-weight: 700; line-height: 1.25; margin: 0 0 var(--space-3); }
h2 { font-size: 1.125rem; font-weight: 600; line-height: 1.3; margin: 0 0 var(--space-2); }
h3 { font-size: var(--text-base); font-weight: 600; line-height: 1.35; margin: 0 0 var(--space-1); }

.container {
  /* Fill the space between the sidebar and the window's right edge — no
     centered max-width box. */
  margin: 0;
  padding: var(--space-3);
  width: 100%;
}
/* Browse is a wide data table that reads thin when stretched edge-to-edge on a
   big monitor, so cap and center just this page (opt-in via body.page-browse).
   Narrower viewports fall below the cap and stay full-width. */
.page-browse .container { max-width: 82rem; margin-inline: auto; }
/* Uniform gap from the top bar to the first element on every page, whatever
   it is (back-link, page-header, title-row, bare h1). */
.container > :first-child { margin-top: 0; }
.messages + * { margin-top: 0; }

/* list-page header: title on the left, primary action(s) on the right */
.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
/* The text block shares the row: a long intro paragraph wraps inside it
   instead of filling the row and pushing the actions onto a line of their
   own at the left. The 20rem basis is where the actions drop below the text
   (phones), never before. */
.page-header-text { flex: 1 1 20rem; min-width: 0; }
.page-header-text h1 { margin: 0; }
.page-header-text .title-row { margin: 0; }
.page-header-text p { margin: var(--space-1) 0 0; color: var(--color-text-muted); }
.page-actions { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; }
.page-actions p { margin: 0; }

/* ==================================================================== */
/* 3. LAYOUT + SHELL — topbar, sidebar, page, footer                    */
/* ==================================================================== */

/* top navbar — fixed, card-colored, above everything */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--topbar-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  background: var(--color-card);
  border-bottom: 1px solid var(--color-border);
  box-shadow: var(--shadow-topbar);
  z-index: 50;
}
/* request progress: a 3px IMP-blue sweep pinned to the very top edge of the
   topbar, shown by shell.js while an htmx request (incl. boosted navigation)
   is in flight for longer than ~200ms — quick swaps never flicker it. */
.hx-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 60; /* above the topbar's 50 */
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 150ms ease;
}
.hx-progress::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--color-primary) 45%, var(--color-primary) 55%, transparent);
  transform: translateX(-100%);
}
.hx-progress.active { opacity: 1; }
.hx-progress.active::before { animation: hx-progress-sweep 1.1s ease-in-out infinite; }
@keyframes hx-progress-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .hx-progress.active::before { animation: none; transform: none; background: var(--color-primary); }
}

/* Add a Payment Method: the box Stripe's card fields will fill, pulsing until
   the element is ready (removed on its "ready" event). Sized to the fields so
   the page never shows an empty gap with a dead Save button. */
.pe-skeleton {
  min-height: 8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  animation: pe-skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes pe-skeleton-pulse { 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) {
  .pe-skeleton { animation: none; }
}

.topbar-left, .topbar-right { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
/* phones: the toggle + bell + buttons only just fit — tighten the gaps. The
   buttons need no size override any more: the standard button is compact
   enough for the 56px bar on its own (2026-07-15). */
@media (max-width: 47.99rem) {
  .topbar-left, .topbar-right { gap: var(--space-1); }
}
.topbar-logo { display: flex; align-items: center; text-decoration: none; }
.topbar-logo img { height: 40px; width: auto; display: block; }
/* theme-aware logo swap: the wordmark is navy in the light asset, near-white
   in the dark one; only one renders at a time (tokens flip the displays).
   img-qualified to outrank the .topbar-logo img display:block above. */
img.logo-light { display: var(--show-in-light); }
img.logo-dark { display: var(--show-in-dark); }
.inline-form { display: inline; }

/* hamburger — collapses the sidebar (desktop) / opens the drawer (mobile) */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.nav-toggle:hover { background: var(--color-surface); color: var(--color-text); }
/* the toggle is available at all widths (collapse on desktop, drawer on mobile) */
body.has-sidebar .nav-toggle { display: inline-flex; }

/* alerts — a bell icon with an unread-count bubble */
.nav-alerts {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border-radius: var(--radius);
  color: var(--color-text-muted);
  text-decoration: none;
}
.nav-alerts:hover { background: var(--color-surface); color: var(--color-text); }
.nav-bell { display: block; }
/* sun/moon theme switch — a quiet icon button matching the alerts bell;
   shows the moon in light mode ("switch to dark") and the sun in dark */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.theme-toggle:hover { background: var(--color-surface); color: var(--color-text); }
.theme-toggle .theme-icon-moon { display: var(--show-in-light); }
.theme-toggle .theme-icon-sun { display: var(--show-in-dark); }
/* plain text link in the topbar (e.g. public "Browse") */
.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
  color: var(--color-text);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.nav-link:hover { background: var(--color-surface); }
.nav-alerts-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--color-danger);
  color: var(--color-primary-contrast);
  font-size: 11px;
  font-weight: 500;
  line-height: 16px;
  text-align: center;
}

/* left sidebar — fixed below the navbar; drawer on mobile */
.sidebar {
  position: fixed;
  top: var(--topbar-height);
  bottom: 0;
  left: 0;
  width: var(--sidebar-width);
  padding: var(--space-3) var(--space-2);
  background: var(--color-card);
  border-right: 1px solid var(--color-border);
  overflow-y: auto;
  z-index: 40;
  transform: translateX(-100%);
  transition: transform 0.2s ease;
}
.sidebar.open { transform: none; }
.sidebar-backdrop {
  position: fixed;
  top: var(--topbar-height);
  right: 0;
  bottom: 0;
  left: 0;
  background: var(--color-backdrop);
  z-index: 35;
}
.sidebar-group { margin-bottom: var(--space-3); }
/* Section heading doubles as a collapse toggle (button in markup). Styled
   like the links it expands — same size/weight/color, no small caps (the
   old platform's section toggles look like the items, Jason 2026-07-26). */
.sidebar-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  margin: 0 0 var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-base);
  font-family: inherit;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
}
.sidebar-heading:hover { background: var(--color-surface); }
.sidebar-caret {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  color: currentColor;
  fill: currentColor;           /* solid chevron, like the icons */
  stroke: none;
  transition: transform 0.15s ease;
}
.sidebar-group.collapsed .sidebar-caret { transform: rotate(-90deg); }
.sidebar-group.collapsed .sidebar-group-items { display: none; }
.sidebar-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-base);
  font-weight: 400;             /* regular — thinner than dj-imp's nominal
                                   font-medium, by request (Jason, 2026-07-26) */
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.sidebar-link:hover { background: var(--color-surface); }
/* current page — light gray fill, like the old dashboard (not brand color) */
.sidebar-link.active { background: var(--color-surface); }
/* nav glyphs — 24px SOLID Heroicons like dj-imp's w-6 h-6 fill icons
   (Jason, 2026-07-13), muted until active/hover. fill inherits across the
   <use> shadow boundary via CSS. */
.sidebar-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--color-text-muted); /* gray-500, dj-imp */
  fill: currentColor;
  stroke: none;
}
.sidebar-link:hover .sidebar-icon,
.sidebar-link.active .sidebar-icon { color: var(--color-text); }
/* The group above already ends in a --space-3 margin; the rule adds none of
   its own on top and mirrors --space-3 below, so the gap is symmetric. */
.sidebar-divider { border: 0; border-top: 1px solid var(--color-border); margin: 0 0 var(--space-3); }

/* page area — offset for the fixed navbar (and sidebar on desktop) */
.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding-top: var(--topbar-height);
}
.page > main { flex: 1 0 auto; }

@media (min-width: 48rem) {
  .sidebar { transform: none; transition: transform 0.2s ease; }
  .sidebar-backdrop { display: none; }
  .page.with-sidebar { margin-left: var(--sidebar-width); transition: margin-left 0.2s ease; }
  /* collapsed: slide the sidebar out and reclaim its space */
  html.sidebar-collapsed .sidebar { transform: translateX(-100%); }
  html.sidebar-collapsed .page.with-sidebar { margin-left: 0; }
}
@media (max-width: 47.99rem) {
  .topbar-logo img { height: 32px; }
}

/* footer */
.site-footer {
  padding: var(--space-3);
  border-top: 1px solid var(--color-border);
  background: var(--color-card);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.site-footer strong { color: var(--color-text); }
.site-footer a { color: var(--color-text-muted); }
.site-footer a:hover { color: var(--color-primary-hover); }

/* mobile tile launcher (MOBILE_TILE_NAV) — an Alpine overlay opened by the
   topbar grid button (Jason, 2026-07-16; replaced the /menu/ pages). CSS
   swaps the hamburger for the grid button on phones; the overlay itself is
   a fixed full-screen sheet with client-side folder drill-in. */
[x-cloak] { display: none !important; }
.nav-tiles {
  display: none;               /* phones only (see the media query below) */
  align-items: center;
  padding: var(--space-2);
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--color-text-muted);
  fill: currentColor;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-tiles:active { background: var(--color-primary-tint); color: var(--color-primary); }
.tile-launcher {
  position: fixed;
  inset: 0;
  z-index: 80;                 /* above the topbar (50) and the sweep (60) */
  padding: var(--space-3);
  background: var(--color-bg);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* the page behind the open overlay must not scroll along */
.launcher-open, .launcher-open body { overflow: hidden; }
.tile-close {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-card);
  color: var(--color-text);
  fill: currentColor;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tile-close:active {
  transform: scale(0.97);
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
}
.tile-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
/* Sign Out and Switch Company are POST forms; let their button be the tile. */
.tile-grid form { display: flex; }
.tile-grid form > .tile { flex: 1; }
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 116px;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  background: var(--color-card);
  color: var(--color-text);
  font-family: inherit;         /* buttons (category tiles) inherit the UI font */
  font-size: var(--text-base);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  box-shadow: var(--shadow-card);
  cursor: pointer;
  /* release eases; the press itself is instant (see .tile:active) */
  transition: transform 0.18s ease, background-color 0.18s ease, border-color 0.18s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
/* pressed: color change + dip the instant the finger lands (Jason,
   2026-07-16 — "should feel like an app"); transition-duration 0 makes the
   press immediate while the release keeps the ease above. */
.tile:active {
  transition-duration: 0s;
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
  transform: scale(0.96);
}
/* a tapped tile stays lit while its boosted page is fetched (htmx marks the
   requesting element) — the overlay holds, the tile shows what's loading. */
.tile.htmx-request {
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
}
@media (prefers-reduced-motion: reduce) {
  .tile { transition: none; }
}
/* category tile: a small chevron marks that it opens a folder */
.tile-cat::after {
  content: "\203A";            /* › */
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-lg);
  line-height: 1;
}
/* folder header: Back pill + the category name. Every panel carries one and it
   is a fixed height, so the tile grid sits at the same spot on every screen —
   without this the grid jumps when you drill in or come back. */
.tile-subhead {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  margin-bottom: var(--space-3);
}
.tile-subhead-title { font-size: var(--text-lg); font-weight: 700; }
.tile-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-card);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.tile-back:active {
  transform: scale(0.97);
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
}
.tile-back-chev { width: 16px; height: 16px; fill: currentColor; stroke: none; transform: rotate(90deg); }
.tile.active { border-color: var(--color-primary); background: var(--color-surface); }
.tile-icon { width: 32px; height: 32px; color: var(--color-primary); fill: currentColor; stroke: none; }
.tile-label { line-height: 1.2; }
.tile-badge {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--color-danger);
  color: var(--color-primary-contrast);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 20px;
  text-align: center;
}
@media (max-width: 47.99rem) {
  /* no sidebar drawer on phones when tiles are on — the launcher replaces it */
  body.tile-nav .sidebar,
  body.tile-nav .sidebar-backdrop { display: none; }
  /* swap the topbar hamburger for the grid link to the launcher */
  body.tile-nav #nav-toggle { display: none; }
  body.tile-nav .nav-tiles { display: inline-flex; }
  /* My Account + Sign Out move into the launcher's account folder */
  body.tile-nav .topbar-account { display: none; }
  /* compact footer strip on phones (reverts with the flag) */
  body.tile-nav .site-footer {
    padding: var(--space-2) var(--space-3);
    gap: var(--space-1) var(--space-2);
    font-size: var(--text-sm);
  }
  body.tile-nav .site-footer p { margin: 0; }
}

/* ==================================================================== */
/* 4. COMPONENTS — buttons, flash, controls, forms, tables, badges,     */
/*    panels, cards, alerts, checklists                                 */
/* ==================================================================== */

/* buttons — the old platform's exact look (dj-imp `.button`, 2026-07-14):
   12px uppercase with 0.1em tracking, 6px radius, 8px/16px padding, the same
   IMP blue, and a 150ms ease. Height is padding-derived (~34px) like dj-imp —
   the 44px touch target and full-width-on-phones both went, they made the
   buttons far too big on mobile (Jason, 2026-07-15, superseding 2026-07-13).
   Labels never wrap (uneven two-line topbar pills looked broken on phones).
   dj-imp only ever had one variant — the blue — so the secondary/danger colors
   stay ours. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: 400;
  line-height: 1rem;    /* dj-imp pins this; inheriting 1.5 made buttons taller */
  text-transform: uppercase;
  letter-spacing: 0.1em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    color 150ms cubic-bezier(0.4, 0, 0.2, 1),
    background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
    border-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1),
    transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
}
/* pressed: feedback lands the instant the finger goes down (no ease-in;
   the release still eases) — app feel (Jason, 2026-07-16). shell.js
   registers a touchstart no-op so iOS applies :active at all. */
.btn:active:not(:disabled) {
  transition-duration: 0s;
  transform: scale(0.97);
  background: var(--color-primary-tint);
}
.btn-primary:active:not(:disabled) { background: var(--color-primary-hover); }
.btn-danger:active:not(:disabled) {
  background: var(--color-danger);
  color: var(--color-primary-contrast);
}
/* plain text links get press feedback on touch screens too (Jason,
   2026-07-16); buttons and tiles carry their own press states above. */
@media (hover: none) {
  a:not(.btn):not(.tile):active { opacity: 0.55; }
}
.btn:focus-visible {
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-primary);
}
.btn:disabled,
.btn[disabled] { opacity: 0.25; cursor: default; }
/* An author display rule beats the UA's [hidden]; a hidden button (the
   gate's "Switch To …") must actually be gone. */
.btn[hidden] { display: none; }
.btn-primary { background: var(--color-primary); color: var(--color-primary-contrast); }
.btn-primary:hover { background: var(--color-primary-hover); }
.btn-secondary { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border); }
.btn-secondary:hover { border-color: var(--color-primary-hover); }
.btn-danger { background: var(--color-surface); color: var(--color-danger); border-color: var(--color-danger); }
.btn-danger:hover { background: var(--color-danger); color: var(--color-primary-contrast); }
.btn-sm { padding: var(--space-1) var(--space-2); }

tr.slot-empty td { color: var(--color-text-muted); }

/* messages */
.messages { padding: var(--space-2) 0; }
.message {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  margin-bottom: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}
.message-success { border-color: var(--color-success); color: var(--color-success); }
.message-error { border-color: var(--color-danger); color: var(--color-danger); }
.message-warning { border-color: var(--color-warning); color: var(--color-warning); }

/* pagination for the generic table (appears on every list page) */
.pagination { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: var(--space-3) 0; }
.pagination-status { color: var(--color-text-muted); font-size: var(--text-sm); }

/* generic table controls row: search / filters on the left, results-per-page
   and pagination pinned top-right, above the table. One layout everywhere. */
.table-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
}
.table-controls-left { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; }
.table-controls-right { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-left: auto; }
/* inside the controls row the search/filter bar and pagination lose their own
   vertical margins — the row owns the spacing. */
.table-controls .filter-bar,
.table-controls .pagination { margin: 0; }
.page-size { margin: 0; }
.page-size-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}
/* compact on desktop (Jason 2026-07-16: the tall select looked padded);
   phones bump it back to the 44px tap target in the responsive section. */
.page-size select {
  min-height: 26px;
  padding: 2px var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-sm);
}
/* status/filter tab chips that sit next to a search box in a filter bar */
.filter-tabs { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }
.filter-tabs .btn.active {
  background: var(--color-accent, var(--color-chip-active-bg));
  color: var(--color-chip-active-text);
}
/* sibling-page tabs under a page header (Invoices | Payments): the same
   chips, set off from the filter bar below by a rule */
.page-tabs {
  display: flex;
  margin: var(--space-3) 0 0;
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

/* forms */
.form-row { margin-bottom: var(--space-3); }
/* A row the page hides (e.g. the other term's fields on the custom booking
   form) must not come back through any display rule below. */
.form-row[hidden] { display: none; }
.form-row label { display: block; font-weight: 500; font-size: var(--text-sm); color: var(--color-text-label); margin-bottom: var(--space-1); }
/* Field labels render Title Case however they're authored; choice labels
   (radio/checkbox options inside widget-rendered divs) keep their casing. */
.form-row > label { text-transform: capitalize; }
/* The star after a required field's label (generic/_field.html). */
.required-mark { color: var(--color-danger); margin-left: 2px; font-weight: 600; }
.form-row input:not([type="radio"]):not([type="checkbox"]),
.form-row select,
.form-row textarea {
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-base);
  /* Token fill/type, not the platform's — the UA's dark-gray fields stood
     out against the cards in dark mode (Jason, 2026-08-10). */
  background-color: var(--color-card);
  color: var(--color-text);
}
/* selects match the text inputs instead of the platform look (Jason,
   2026-08-10): same fill and type, a token-colored chevron for the arrow. */
.form-row select:not([multiple]) {
  appearance: none;
  color: var(--color-text);
  background: var(--color-card) var(--select-arrow) no-repeat right var(--space-2) center;
  background-size: 16px;
  padding-right: calc(var(--space-2) * 2 + 16px);
  cursor: pointer;
}
/* radio/checkbox groups (Django RadioSelect/CheckboxSelectMultiple): keep the
   control inline with its choice label instead of stretching it full-width */
.form-row input[type="radio"],
.form-row input[type="checkbox"] {
  width: auto;
  min-height: 0;
  margin: 0 var(--space-2) 0 0;
}
.form-row [id] > div label,
.form-row [id] > div > label {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  margin: 0;
  font-weight: 500;
}
/* desktop: denser forms — shorter controls, tighter rows (mobile keeps the
   44px touch targets above) */
@media (min-width: 48rem) {
  .form-row { margin-bottom: var(--space-2); }
  .form-row input:not([type="radio"]):not([type="checkbox"]),
  .form-row select,
  .form-row textarea {
    min-height: 38px;
    padding: var(--space-1) var(--space-2);
    font-size: var(--text-sm);
  }
}
/* file inputs: a dashed pick-a-file zone with a real button — the text-box
   treatment above looks broken around the native "Choose File" control */
.form-row input[type="file"] {
  padding: var(--space-2);
  border-style: dashed;
  border-color: var(--color-border-strong); /* one step darker so the dashes read */
  background: var(--color-bg);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
}
.form-row input[type="file"]::file-selector-button {
  margin-right: var(--space-2);
  min-height: 36px;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.form-row input[type="file"]:hover::file-selector-button {
  border-color: var(--color-primary-hover);
}

/* validation errors — Django renders field errors as <ul class="errorlist"> */
.errorlist {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: 0;
  color: var(--color-danger);
  font-size: var(--text-sm);
}
/* form-level (non-field) errors sit above the first field, not under a
   control — give them the same rhythm a form-row owns so they don't jam
   against the field below (they keep the top gap from the copy/heading). */
.errorlist.nonfield { margin: 0 0 var(--space-3); }
.form-row:has(.errorlist) input,
.form-row:has(.errorlist) select,
.form-row:has(.errorlist) textarea { border-color: var(--color-danger); }
/* tables holding form controls (the invoice line editor): app-styled
   controls that fill their cells instead of bare browser defaults */
.table-edit td input:not([type="checkbox"]),
.table-edit td select,
.table-edit td textarea {
  width: 100%;
  min-height: 38px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-sm);
  background: var(--color-card);
}
/* A button in an edit-table cell (Save, Remove) stands at the fields' 38px,
   not the small button's ~26px, so the row reads as one line. */
.table-edit td .btn { min-height: 38px; }
/* An info-grid inside a form is an edit grid (the Billing and Communications
   settings pages): the label in the dt, the control in the dd. Its controls
   take the edit table's 38px field chrome and fill the column; the dd is a
   flex row so a unit after the box ("%") and a checkbox sit on the control's
   midline, and the dt centres its label on that same 38px band. */
form .info-grid { row-gap: var(--space-2); }
form .info-grid dt,
form .info-grid dd {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 38px;
  font-size: var(--text-sm);
}
form .info-grid dt { align-self: start; }
form .info-grid dd > input:not([type="checkbox"], [type="radio"]),
form .info-grid dd > select,
form .info-grid dd > textarea {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 38px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-sm);
  background-color: var(--color-card);
  color: var(--color-text);
}
/* phones: the 44px touch target and 1rem type, like .form-row fields (the
   rule above outranks the page-wide phone font-size rule, so restate it) */
@media (max-width: 47.99rem) {
  form .info-grid dt,
  form .info-grid dd { min-height: var(--touch-target); }
  form .info-grid dd > input:not([type="checkbox"], [type="radio"]),
  form .info-grid dd > select,
  form .info-grid dd > textarea {
    min-height: var(--touch-target);
    font-size: var(--text-base);
  }
}
/* time inputs (location hours grid) match the rest of the design language */
.panel input[type="time"] {
  min-height: 38px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font: inherit;
  font-size: var(--text-sm);
}
/* location hours editor — compact 7-row grid: day, opens, closes */
.hours-presets { margin: var(--space-2) 0 var(--space-3); }
.hours-grid {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-1) var(--space-2);
  align-items: center;
  max-width: 26rem;
  margin-bottom: var(--space-3);
}
.hours-grid .hours-day { font-weight: 600; font-size: var(--text-sm); }
.hours-grid input[type="time"] { width: 100%; }
.hours-grid .errorlist { margin: 0; font-size: var(--text-sm); }
/* segmented radio group — bordered option pills (e.g. booking rental type) */
.radio-group { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
.radio-group .radio-option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-weight: 500;
  cursor: pointer;
}
.radio-group .radio-option:hover { border-color: var(--color-primary); }
.radio-group .radio-option:has(input:checked) {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}

/* tables — sit inside a white rounded card, admin-dashboard style */
.table-card {
  margin: var(--space-3) 0;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  overflow-x: auto;
}
.table { width: 100%; border-collapse: collapse; margin: 0; font-size: var(--text-sm); }
.table thead th {
  /* Old-platform header chrome (Jason, 2026-07-26): gray-50 band, 12px
     UPPERCASE gray-500 with 0.05em tracking, px-4 py-2.5. dj-imp never reset
     the UA's `th { font-weight: bold }`, so its headers render bold — kept. */
  background: var(--color-bg);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
  padding-top: 10px;
  padding-bottom: 10px;
}
/* Sortable headers are blue so they read as links (old-platform style,
   Jason 2026-07-26) — no underline; the ↑/↓ arrow marks the active sort. */
.table thead th a { color: var(--color-primary-hover); text-decoration: none; }
/* Reference/ID cells never wrap ("IMP-Y4ENG2" split across lines is unreadable). */
.table td.cell-id { white-space: nowrap; }
/* Long free-text cells (company names, listing titles) clamp to one line with an
   ellipsis and a hover title, so every row is the same height and the table
   scans as straight columns instead of a ragged 1–3 line stack (Jason,
   2026-07-24). The cap scales with the window — a flat 20ch chopped names even
   with the screen half empty (Jason, 2026-07-26); 20ch stays the floor so
   narrow windows keep one-line rows, and the hover title covers what's left. */
.table td.cell-truncate { max-width: max(20ch, 16vw); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Emphasis cells for scannable lists: the row's subject (equipment name) reads
   in full text weight, its price as a paired figure — everything else stays
   the muted secondary color so the eye lands on the two that matter. */
.table td.cell-strong { color: var(--color-text); font-weight: 600; }
.table td.cell-strong .badge { margin-left: var(--space-1); font-weight: 600; }
/* The daily rate is the row's decision figure, so it carries the one spot of
   brand color per row — matching the phone card, where the rate is blue too.
   Its unit stays muted so the number leads (Jason, 2026-07-24). */
.table td.cell-price { color: var(--color-primary); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.table td.cell-price .cell-price-unit { color: var(--color-text-muted); font-weight: 400; }
/* The quiet meta tier — category, vendor, city — recedes so the bold name and
   the blue price are the only things the eye lands on. */
.table td.cell-muted { color: var(--color-text-muted); }
.table th, .table td {
  text-align: left;
  padding: 8px var(--space-3);   /* dj-imp cell padding (px-4 py-2) */
  border-bottom: 1px solid var(--color-border);
}
.table tbody td { color: var(--color-text-secondary); }   /* table row text */
.table tbody tr:last-child td,
.table tbody tr:last-child th { border-bottom: 0; }   /* th too — row-label tables (hours) */
.table tbody tr:hover { background: var(--color-surface); }
.checkbox-row { display: flex; align-items: center; gap: var(--space-2); font-weight: 500; }
/* The Communications page: a message preview in its own frame, and the
   wording editor's fields at the platform's input size. */
.email-preview { width: 100%; min-height: 28rem; border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-card); }
/* Team page: the role dropdown, the role badge and the row buttons (Remove,
   Resend, Delete) stand at one 34px height with one padding, so a row reads
   as one line of controls (Jason, 2026-09-10). */
.team-page .table td select,
.team-page .table td .btn,
.team-page .table td .badge {
  min-height: 34px;
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  font-size: var(--text-sm);
  vertical-align: middle;
}
.team-page .table td .badge { border-radius: var(--radius); }
.team-page .table td select { padding-right: calc(var(--space-2) * 2 + 16px); }
.team-page .table td .inline-form { display: inline-block; vertical-align: middle; }
/* A tick box in a settings grid: the browser's own size, centred in its cell. */
.grid-check { width: auto; min-height: 0; margin: 0; vertical-align: middle; }
.checkbox-row input { width: auto; min-height: auto; }
/* generic form checkboxes: the label wraps the control (clickable), stays
   inline, and keeps a comfortable touch target */
.form-row label.checkbox-row {
  display: inline-flex;
  align-items: center;
  margin-bottom: 0;
  min-height: var(--touch-target);
  font-size: var(--text-base);
}
.form-row label.checkbox-row input { min-height: 0; margin: 0; }

/* Phone list cards (Jason, 2026-07-15) — the alternative to the labelled stack
   for lists that opt in with a card_template. A stacked table repeats a column
   name beside every value, which reads as a wall of labels; a card leads with
   the thing itself, keeps one quiet line of context under it, and puts the
   action at the end. Desktop never sees them. */
.list-cards { display: none; }
/* Each card is its own tappable box, matching the stacked-table cards
   (Jason, 2026-07-17): whole box opens the record (shell.js follows the
   card's action link), launcher-style press. */
.list-card {
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  cursor: pointer;
  transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.list-card:last-child { margin-bottom: 0; }
.list-card:active {
  transition-duration: 0s;
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
  transform: scale(0.98);
}
.list-card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.list-card-title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.3;
}
.list-card-figure {
  flex: 0 0 auto;
  font-size: var(--text-base);
  font-weight: 600;
  white-space: nowrap;
}
.list-card-meta {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.4;
}
.list-card-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.list-card-note { color: var(--color-text-muted); font-size: var(--text-sm); }
.list-card-note-link { color: var(--color-primary-hover); font-weight: 500; }

/* responsive tables — on phones, stack each row into a labeled card so wide
   tables (bookings, fleet, invoices, the booking equipment list) read cleanly
   top-to-bottom instead of scrolling sideways and clipping. Each cell prints
   its column name from data-label, stamped by shell.js. */
@media (max-width: 47.99rem) {
  /* a list that ships cards shows those instead of the labelled stack */
  .table-card.has-cards { display: none; }
  .list-cards { display: block; }
  /* the stacked rows are the cards — the wrapper's own card chrome would
     box them in with no edge breathing room (Jason, 2026-07-17); the
     sideways editor tables keep theirs */
  .table-card { overflow-x: visible; }
  .table-card:not(.table-card-scroll) {
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .table thead { display: none; }
  .table, .table tbody { display: block; }
  .table tbody { width: 100%; }
  .table tr {
    /* flex column (not block) so action cells can re-order to the end */
    display: flex;
    flex-direction: column;
    padding: var(--space-2) var(--space-3);
  }
  /* each stacked data row is its own box (Jason, 2026-07-16); label/value
     rows (rates, totals — the row's <th> is the label) stay flat */
  .table tbody tr:not(:has(> th)) {
    margin-bottom: var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-card);
  }
  .table tbody tr:not(:has(> th)):last-child { margin-bottom: 0; }
  .table tbody tr:hover { background: var(--color-card); }
  /* rows with a record link are whole-box tappable (shell.js follows the
     row's first link) and press like a launcher tile — unless the row holds
     form controls: those are work surfaces, not buttons (Jason, 2026-07-17) */
  .table tbody tr:has(td a:not(.btn)):not(:has(form, button, input, select, textarea)) {
    cursor: pointer;
    transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
  }
  .table tbody tr:has(td a:not(.btn)):not(:has(form, button, input, select, textarea)):active {
    transition-duration: 0s;
    background: var(--color-primary-tint);
    border-color: var(--color-primary);
    transform: scale(0.98);
  }
  /* narrow desktop windows have a mouse: hover previews the press */
  @media (hover: hover) {
    .table tbody tr:has(td a:not(.btn)):not(:has(form, button, input, select, textarea)):hover,
    .list-card:hover {
      background: var(--color-primary-tint);
      border-color: var(--color-primary);
    }
  }
  .table td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-1) 0;
    border: 0;
    text-align: right;
  }
  .table td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    margin-right: auto;
    color: var(--color-text-muted);
    font-size: 0.72rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    text-align: left;
  }
  /* Stacked cells wrap instead of truncating: the desktop one-line clamp
     (max-width 20ch + nowrap) crushed the label/value pair and clipped the
     value on phones (Jason, 2026-07-26). */
  .table td.cell-truncate {
    max-width: none;
    overflow: visible;
    white-space: normal;
  }
  /* action / checkbox columns have no header text — drop the empty label and
     let the control span the card for a comfortable tap target. */
  .table td[data-label=""] {
    display: block;
    padding-top: var(--space-2);
    text-align: left;
    /* actions come after the facts — a big "Book" button above the listing
       it belongs to reads backwards on a stacked card */
    order: 99;
  }
  .table td[data-label=""]::before { content: none; }
  .table td[data-label=""] .btn,
  .table td[data-label=""] form { width: 100%; }
  .table td[data-label=""] .btn { text-align: center; }
  /* Action cells whose column has a real header (the bookings Message
     column) stack the same way as label-less ones: full-width button after
     the facts, no "MESSAGE" label beside a squeezed button. */
  .table td.row-actions {
    display: block;
    padding-top: var(--space-2);
    text-align: left;
    order: 99;
  }
  .table td.row-actions::before { content: none; }
  .table td.row-actions .btn,
  .table td.row-actions form { width: 100%; }
  .table td.row-actions .btn { text-align: center; }
  /* equipment editor rows (stacked): inputs center against their labels and
     share one value width, so the right edge lines up row to row instead of
     rag-jumping between text and inputs (Jason, 2026-07-16). */
  .equipment-table td { align-items: center; min-height: 34px; }
  .equipment-table .cell-nowrap { white-space: nowrap; }
  .equipment-table .cell-edit form { width: 11rem; max-width: 60%; }
  /* one shared column for unit + dates — same box both edges, value
     centered (Jason, 2026-07-17). iOS won't shrink a native date control
     below its intrinsic width (its right edge poked past the unit field),
     so drop the native chrome; the value needs its own centering rule. */
  .equipment-table .cell-edit input {
    width: 100%;
    text-align: center;
  }
  .equipment-table .cell-edit input[type="date"] {
    max-width: none;
    min-width: 0;
    -webkit-appearance: none;
    appearance: none;
  }
  .equipment-table .cell-edit input[type="date"]::-webkit-date-and-time-value {
    text-align: center;
  }
  /* The equipment editor keeps its TABLE shape on phones and scrolls
     sideways inside its card, like the old platform — stacked label rows
     full of inline inputs read as a giant form per unit (Jason,
     2026-07-13, mobile screenshot). */
  .table-card.table-card-scroll { overflow-x: auto; }
  .table-card-scroll .table { display: table; min-width: 640px; }
  .table-card-scroll .table thead { display: table-header-group; }
  .table-card-scroll .table tbody { display: table-row-group; }
  .table-card-scroll .table tr {
    display: table-row;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
  }
  .table-card-scroll .table td,
  .table-card-scroll .table th {
    display: table-cell;
    padding: 8px var(--space-2);
    text-align: left;
  }
  .table-card-scroll .table td::before { content: none; }
  .table-card-scroll .table td[data-label=""] { order: 0; padding-top: 8px; }
  .table-card-scroll .table td[data-label=""] .btn { width: auto; }
  /* label/value rows (rates, cost preview, invoice totals): the row's <th>
     IS the label — keep label left / amount right on one line instead of
     stacking each into its own block. */
  .table tfoot { display: block; }
  .table tr:has(> th) {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
  }
  .table tr > th {
    padding: var(--space-1) 0;
    border: 0;
    text-align: left;
    color: var(--color-text-muted);
    font-size: 0.72rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .table tr:has(> th) > td { padding: var(--space-1) 0; }
}

/* status badge — color + text together (low-literacy rule) */
.badge {
  display: inline-block;
  white-space: nowrap;   /* a chip broken across two lines reads as damage */
  padding: 1px 7px;
  border-radius: 5px;
  font-size: 0.6875rem;
  /* 600, not 500: the badge is the densest signal on every list, so it earns a
     touch more weight to read at a glance (Jason, 2026-07-24). */
  font-weight: 600;
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  /* Tinted fill in the badge's own color — 20% (was 14%) so the chip reads with
     a bit more contrast without going solid (Jason, 2026-07-24). */
  background: color-mix(in srgb, currentColor 20%, transparent);
}
/* Badge status → color map. Every status class the app emits, grouped by the
   semantic color it renders in; domain + staff/ops statuses together so a
   "failed" never renders as plain black text.

   THE BADGE VOCABULARY (Jason, 2026-07-15) — four colors, one meaning each.
   Pick by what the reader should DO, not by which app the status came from,
   and never introduce a fifth color or a one-off chip.

     green  (badge-success) — settled, nothing to do: paid, approved, active,
                              published, on file.
     yellow (badge-warning) — in flight or waiting on someone: pending, open,
                              requested, unpaid, in transit.
     red    (badge-danger)  — failed, refused, or blocking: declined, expired,
                              failed, void, cancelled, COI missing.
     blue   (badge-blue)    — information, no judgement: a type or a label that
                              is neither good nor bad.

   gray (badge-gray) is the only other one: not a status color at all, it means
   inactive or not applicable (superseded, detached). If a new status needs a
   badge, add its class to one of the four lists below — do not style it where
   it is used. Same word, same color, everywhere: "Unpaid" was gray on the
   booking page and yellow in the lists, and that is the bug this prevents. */
.badge-success, .badge-published, .badge-available,
.badge-approved, .badge-active, .badge-completed, .badge-paid,
.badge-succeeded, .badge-processed, .badge-invoiced,
.badge-fulfilled, .badge-received, .badge-sent { color: var(--color-success); }
.badge-warning, .badge-draft, .badge-paused, .badge-assigned, .badge-requested,
.badge-open, .badge-pending, .badge-pending_review, .badge-partially_paid, .badge-running,
.badge-processing, .badge-requires_action,
.badge-requires_capture, .badge-requires_verification, .badge-in_transit { color: var(--color-warning); }
.badge-danger, .badge-archived, .badge-out_of_service,
.badge-declined, .badge-expired, .badge-cancelled, .badge-withdrawn,
.badge-failed, .badge-failure, .badge-rejected, .badge-void, .badge-voided,
.badge-uncollectible, .badge-reversed, .badge-written_off,
.badge-canceled, .badge-ignored, .badge-skipped { color: var(--color-danger); }
.badge-blue { color: var(--color-primary); }
.badge-gray, .badge-superseded, .badge-detached { color: var(--color-text-muted); }
/* A row of badges each carrying its own remove button (Communications →
   Copies: one badge per message an address is copied on). */
.badge-row { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.badge .badge-remove {
  background: none; border: 0; padding: 0 0 0 var(--space-1); margin: 0;
  color: inherit; font: inherit; font-weight: 700; line-height: 1; cursor: pointer;
}
.badge .badge-remove:hover, .badge .badge-remove:focus-visible { color: var(--color-danger); }
/* Catalog-type identity chips (Jason, 2026-08-10) — a deliberate extension of
   the four-color vocabulary above: these are IDENTITY hues (which type is
   this?), not status semantics, keyed by listing-type slug. Templates emit
   "badge-blue badge-type-<slug>" so an unlisted type falls back to plain
   informational blue. Equipment keeps the IMP blue on purpose — it is the
   common case. */
.badge-type-yard_space { color: var(--color-type-yard); }
.badge-type-service { color: var(--color-type-service); }
.badge-type-load { color: var(--color-type-load); }
/* Equipment-item identity chips (Jason, 2026-08-10): the hue is hashed from
   the item's name (core badges.item_hue, emitted as --item-hue inline) and
   constrained to the cyan→magenta arc so item chips never mimic a status
   green/amber/red; saturation/lightness ride the theme tokens above. */
.badge-item { color: hsl(var(--item-hue, 220) var(--color-item-s) var(--color-item-l)); }
/* Identity chips inside the uppercase eyebrows keep normal chip casing. */
.booking-eyebrow .badge, .listing-detail-eyebrow .badge {
  text-transform: none;
  letter-spacing: normal;
}

/* Onboarding-milestone icons (staff Companies roster): a compact line of
   small icons, green when the step is reached and faint gray when not. */
.milestones { display: inline-flex; align-items: center; gap: var(--space-2); }
.milestone { display: inline-flex; }
.milestone-icon { width: 18px; height: 18px; fill: currentColor; stroke: none; }
.milestone-on { color: var(--color-success); }
.milestone-off { color: var(--color-text-muted); opacity: 0.5; }

/* catalog: browse + vendor management */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin: var(--space-3) 0; }
.filter-bar input, .filter-bar select {
  min-height: 34px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
}
/* the search / clear buttons match the shorter input height */

/* Search bars (Jason, 2026-07-13): one fused control — a compact
   magnifier button integrated on the LEFT of the field. Both live inside
   a .search-group so other filter controls never separate them. */
/* The filter bar sits on the button's scale (Jason, 2026-07-15): once buttons
   came down to the old platform's 34px, a 44px search bar towered over the
   page it filters. Form fields keep their 44px targets — this is only the
   filter row. */
.filter-bar select,
.filter-bar input[type="search"],
.search-group .search-btn { min-height: 34px; }
.search-group { display: inline-flex; align-items: stretch; }
.search-group .search-btn {
  min-height: 34px;
  flex: 0 0 34px;
  width: 34px;
  padding: 0;
  justify-content: center;
  border-radius: var(--radius) 0 0 var(--radius);
  display: inline-flex;
  align-items: center;
}
/* The button keeps all four borders (Jason, 2026-07-26); the input drops
   its left one so the shared edge stays a single hairline. */
.search-group input[type="search"] {
  border-radius: 0 var(--radius) var(--radius) 0;
  border-left: 0;
  min-width: 0;
}
.search-group .search-btn:focus-visible { position: relative; z-index: 1; }
@media (max-width: 47.99rem) {
  /* the filter bar (and the fused group inside it) spans the phone row */
  .table-controls-left,
  .filter-bar,
  .search-group { width: 100%; }
  /* compact control chrome (Jason, 2026-07-17): no per-page picker on
     phones, and a single-page list drops its "Page 1 of 1" row entirely —
     multi-page lists keep Previous / Next + the count. */
  .table-controls .page-size { display: none; }
  .pagination-single { display: none; }
  /* Phones lay the dropdowns out three to a row (Jason, 2026-07-15) — one
     per line wasted the width and pushed the listings below the fold. The
     search keeps its own full-width row underneath. At a third of the width
     the label has to make room for the arrow the browser draws itself, so it
     drops to the button's text size and truncates rather than running under
     it. */
  .filter-bar select {
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--text-xs);
    padding-left: var(--space-2);
    padding-right: 1.15rem;
    text-overflow: ellipsis;
  }
  .search-group { flex: 1 1 100%; }
  .search-group input[type="search"] { flex: 1 1 auto; }
}
/* Server-backed autocomplete (portal/widgets.py). The results box is empty in
   the page and htmx fills it as you type — nothing about the picker's table
   ships in the HTML. */
.ac { position: relative; }
.ac-input { width: 100%; }
.ac-results-box { position: relative; }
.ac-results {
  position: absolute;
  top: 2px;
  left: 0;
  right: 0;
  z-index: 30;
  max-height: 15rem;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  box-shadow: var(--shadow-card);
}
.ac-option {
  display: block;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.ac-option:hover,
.ac-option:focus-visible { background: var(--color-surface); }
.ac-empty { margin: var(--space-1) 0 0; }

.card { display: block; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); text-decoration: none; color: inherit; }
.card:hover { border-color: var(--color-primary); }
/* Notes is a panel like every other section — white ground, no hover lift —
   just shallower, since it holds one line and a box (Jason, 2026-09-09). */
.panel.notes-block { padding: var(--space-2) var(--space-3); }
.notes-block:hover { border-color: var(--color-border); }
.card-meta { color: var(--color-text-muted); font-size: var(--text-sm); margin: var(--space-1) 0 0; }
.photo-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) 0; }
.photo-row img { max-width: 240px; max-height: 180px; border-radius: var(--radius); object-fit: cover; }
/* A listing photo with its Remove control underneath (the manage page). */
.photo-card { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; }
.photo-card form { margin: 0; }
.photo-caption { max-width: 240px; font-size: var(--text-sm); color: var(--color-text-muted); }
/* The "you get $X after the IMP fee" sentence under a listing's rate inputs:
   plain help text on its own line (Jason, 2026-08-25). */
.net-line { display: block; }
.action-bar { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) 0; }
/* phones: action buttons share the row evenly instead of ragged widths */
@media (max-width: 40rem) {
  /* filter and page-tab chips are thumb targets on a phone (26px tall before) */
  .filter-tabs .btn, .page-tabs .btn { min-height: var(--touch-target); }
  .action-bar .btn { flex: 1 1 auto; text-align: center; }
  .action-bar .inline-form { display: flex; flex: 1 1 auto; }
  .action-bar .inline-form .btn { width: 100%; }
}
.help { color: var(--color-text-muted); font-size: var(--text-sm); margin: var(--space-1) 0 0; }

/* ==================================================================== */
/* Recent-agents picker (COI request page): big, obvious one-tap cards  */
/* that fill the form. A quiet text link undersold the shortcut         */
/* (Jason, 2026-07-26).                                                 */
/* ==================================================================== */
.agent-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.agent-chip {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 14rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius);
  background: var(--color-primary-tint);
  color: var(--color-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.18s ease, transform 0.18s ease;
}
.agent-chip:hover { background: var(--color-card); }
.agent-chip:active { transition-duration: 0s; transform: scale(0.98); }
.agent-chip-name { font-weight: 600; color: var(--color-primary-hover); }
.agent-chip-detail { font-size: var(--text-sm); color: var(--color-text-muted); }
@media (max-width: 47.99rem) {
  .agent-chip { flex: 1 1 100%; min-width: 0; }
}

/* ==================================================================== */
/* New Booking page (Jason, 2026-07-26): a one-tap listing-type row,     */
/* "Book Again" shortcut chips, and a bold card grid on every screen.    */
/* ==================================================================== */
.browse-types {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
.browse-type-chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-card);
  color: var(--color-text);
  font-size: var(--text-base);
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.18s ease, background-color 0.18s ease;
}
.browse-type-chip:hover { border-color: var(--color-primary); text-decoration: none; }
.browse-type-chip.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-contrast);
}
.browse-again {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
}
.browse-again-title {
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
.browse-again-chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 2px var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-card);
  color: var(--color-primary-hover);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
}
.browse-again-chip:hover { border-color: var(--color-primary); text-decoration: none; }
.browse-again-chip.is-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-contrast);
}
.browse-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: var(--space-3);
  align-items: stretch;
}
.browse-pagination {
  display: flex;
  justify-content: center;
  margin-top: var(--space-4);
}

/* Assignment edit: two-up panels on desktop, the danger strip full-width
   below (Jason, 2026-07-26). Phones stack. */
.assignment-edit-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  max-width: 64rem;
}
/* stretch (grid default) keeps the two cards the same height per row */
.assignment-edit-grid .panel { margin: 0; }
@media (min-width: 48rem) {
  .assignment-edit-grid { grid-template-columns: 1fr 1fr; }
  .assignment-edit-wide { grid-column: 1 / -1; }
}

/* ==================================================================== */
/* Browse listing card. The equipment name leads, the daily rate sits    */
/* under it (never wrapping beside it — Jason, 2026-07-26), and a quiet  */
/* line carries vendor + city. The card is NOT clickable: Book is the    */
/* only action.                                                          */
/* ==================================================================== */
.listing-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
}
.listing-card:hover { border-color: var(--color-primary); }
@media (min-width: 48rem) {
  .listing-card-name { font-size: var(--text-xl); }
  .listing-rate-amount { font-size: var(--text-xl); }
}
.listing-card-body { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.listing-card-name {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: 1.25;
  color: var(--color-text);
}
/* The name is the listing link, styled as the heading it is (no underline
   until hover) so the card keeps its look. */
.listing-card-name a { color: inherit; text-decoration: none; }
.listing-card-name a:hover,
.listing-card-name a:focus-visible { text-decoration: underline; }
.listing-rate {
  margin: 0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.listing-rate-amount { font-size: var(--text-lg); font-weight: 700; color: var(--color-primary); }
.listing-rate-unit { font-size: var(--text-sm); color: var(--color-text-muted); }
.listing-rate-ask { font-size: var(--text-sm); font-weight: 500; color: var(--color-text-muted); }
.listing-card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.4;
}
.listing-card-pin {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  fill: currentColor;
  stroke: none;
}
.listing-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
}
.listing-card-chips { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.listing-card-vendor { color: var(--color-primary-hover); font-weight: 500; }

/* ==================================================================== */
/* Listing detail — a single centered column: content leads, the         */
/* rate-forward booking box is a centered card below it.                 */
/* ==================================================================== */
/* One --space-3 rhythm between blocks — the booking detail page's. The
   column gap is the only spacing: every direct child (a panel, the COI
   banner, the request page's h1 and fix-it line) drops its own vertical
   margins and the head's last line carries none, so nothing doubles up
   against the gap (Jason, 2026-09-10: uneven gaps). */
.listing-detail { display: flex; flex-direction: column; gap: var(--space-3); align-items: stretch; max-width: 44rem; margin-inline: auto; }
.listing-detail-head { min-width: 0; }
.listing-detail > .panel,
.listing-detail > .message,
.listing-detail > h1,
.listing-detail > p { margin: 0; }
/* inside a panel the heading's --space-2 leads every body, the Details table too */
.listing-detail .detail-panel > .table-card { margin-top: 0; }
/* The head stacks like the booking hero: the identity lines close together
   (eyebrow, title, vendor line), the badge row --space-2 below. */
.listing-detail-eyebrow {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-primary);
}
.listing-detail-title {
  margin: var(--space-1) 0 0;
  font-size: var(--text-xl);
  font-weight: 700;
  line-height: 1.15;
}
.listing-detail-sub { margin: var(--space-1) 0 0; color: var(--color-text-muted); }
.listing-detail-badges { margin-top: var(--space-2); }
.listing-detail-desc { margin: 0; line-height: 1.6; }

/* Listing photo carousel (browse/_photo_carousel.html + static/js/carousel.js):
   one fixed-ratio box, centered in the column, photos cover-fit so every
   listing gets the same shape whatever the vendor uploaded. The track is a
   native scroll-snap strip (swipe/drag for free); buttons and dots sit over it. */
.carousel {
  position: relative;
  width: 100%;
  max-width: 44rem;
  margin: 0 auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  overflow: hidden;
}
.carousel-track {
  display: flex;
  aspect-ratio: 16 / 10;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-track:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.carousel-slide { position: relative; flex: 0 0 100%; margin: 0; scroll-snap-align: start; scroll-snap-stop: always; }
.carousel-slide img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* the vendor's caption, along the bottom of its slide above the dots */
.carousel-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: var(--space-1) var(--space-3) calc(var(--space-2) + 16px);
  background: var(--color-backdrop);
  color: var(--color-primary-contrast);
  font-size: var(--text-sm);
  text-align: center;
}
.carousel-single .carousel-caption { padding-bottom: var(--space-2); }
.carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-card);
  color: var(--color-text);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  opacity: 0.92;
}
.carousel-btn:hover { opacity: 1; border-color: var(--color-primary); color: var(--color-primary); }
.carousel-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.carousel-btn svg { width: 20px; height: 20px; fill: currentColor; stroke: none; }
.carousel-prev { left: var(--space-2); }
.carousel-next { right: var(--space-2); }
.carousel-prev svg { transform: rotate(90deg); }
.carousel-next svg { transform: rotate(-90deg); }
.carousel-count {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--color-backdrop);
  color: var(--color-primary-contrast);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.carousel-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--space-2);
  display: flex;
  justify-content: center;
  gap: 6px;
}
.carousel-dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: 1px solid var(--color-primary-contrast);
  border-radius: 50%;
  background: var(--color-backdrop);
  cursor: pointer;
}
.carousel-dot.is-active { background: var(--color-primary-contrast); }
.carousel-dot:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* no photos yet: a quiet placeholder that keeps the page's shape */
.carousel-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-4);
  color: var(--color-text-muted);
}
.carousel-empty-icon { width: 32px; height: 32px; fill: currentColor; stroke: none; }
.carousel-empty p { margin: 0; }
@media (max-width: 40rem) {
  .carousel-track { aspect-ratio: 4 / 3; }
  .carousel-btn { width: 36px; height: 36px; }
}

/* the sticky booking box: rate lines, then the action stack */
.booking-box { margin: 0; }
.rate-lines { list-style: none; margin: 0 0 var(--space-3); padding: 0; }
.rate-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
/* no rule or padding after the last line: the list's own margin is the only
   space before the buttons (listing page) or the panel edge (request page) */
.rate-line:last-child { border-bottom: 0; padding-bottom: 0; }
.rate-line-label { color: var(--color-text-muted); font-size: var(--text-sm); }
.rate-line-amount { font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.rate-line-per { color: var(--color-text-muted); font-weight: 400; font-size: var(--text-sm); }
/* the headline rate — the daily figure a customer decides on */
.rate-line-primary { padding-top: 0; align-items: baseline; }
.rate-line-primary .rate-line-label {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.rate-line-primary .rate-line-amount { font-size: var(--text-xl); color: var(--color-primary); }
.booking-box-ask { margin: 0 0 var(--space-3); color: var(--color-text-muted); }
.booking-box-actions { display: flex; flex-direction: column; gap: var(--space-2); }
.booking-box-actions .help { margin: 0; }   /* the stack's gap spaces it */
.btn-block { width: 100%; }

/* white content card — wraps forms and standalone panels on the gray page */
.panel {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-4);
  margin: var(--space-3) 0;
}
/* A <summary> is a control wherever it appears — the yard's "Remove this
   row" disclosure among them (Jason, 2026-08-19). */
summary { cursor: pointer; }
.panel > :first-child { margin-top: 0; }
.panel > :last-child { margin-bottom: 0; }
/* horizontal rule between sections that share one panel */
.panel-divider { border: 0; border-top: 1px solid var(--color-border); margin: var(--space-3) 0; }
/* Forms don't need the full page width — cap them at a readable measure so a
   single short field doesn't stretch edge-to-edge. Tables keep the full width. */
.panel-form { max-width: 48rem; }
/* Money panels on the staff invoice page (Mark Paid Offline, Record Vendor
   Payout): several short fields, so they sit on one grid line each instead of
   a column of full-width boxes, and the panel keeps the page compact
   (Jason, 2026-09-09). Its rows stay stacked on a phone. */
.panel-compact { padding: var(--space-3); }
.panel-compact .field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-2) var(--space-3);
  align-items: end;
}
.panel-compact .form-row { margin-bottom: 0; }
.panel-compact .form-row input,
.panel-compact .form-row select {
  min-height: 2.25rem;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
}
.panel-compact .btn { margin-top: var(--space-3); }
.panel-compact .info-grid { margin-bottom: var(--space-3); }
@media (max-width: 40rem) {
  .panel-compact .field-grid { grid-template-columns: 1fr; }
  .panel-compact .form-row input,
  .panel-compact .form-row select { min-height: var(--touch-target); }
}
/* narrow manage pages: header + panels share one column, so the top-right
   actions sit beside the content instead of drifting across the screen */
.page-narrow { max-width: 48rem; }
/* two-up form fields — pairs short fields across the panel width instead of a
   tall single column; .span-2 fields (long text) take the full row. */
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--space-3); }
.form-grid .span-2 { grid-column: 1 / -1; }
@media (max-width: 40rem) {
  .form-grid { grid-template-columns: 1fr; }
  /* the booking request pairs (pay + quantity, start + end) stay two-up on
     phones so the form fits one screen (Jason, 2026-07-17) */
  .form-grid.form-grid-compact { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--space-2); }
}
/* The price preview: one line of math, one quiet note, one bold total. */
/* Booking-request price preview: one bordered box, one type size — the
   math line, an optional muted coverage note, and a ruled-off bold total
   (Jason, 2026-07-26). */
.quote-box {
  margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  font-size: var(--text-base);
}
.quote-math { margin: 0; }
.quote-note { margin: var(--space-1) 0 0; color: var(--color-text-muted); }
.quote-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
  font-weight: 600;
}

/* dashboard — quick-link cards in a responsive grid, like the old home */
.dash-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(600px, 100%), 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.dash-panels .table-card { margin: 0; }
.table-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-2);
}
.table-card-head h2 { margin: 0; font-size: var(--text-lg); }
.dash-panels .muted {
  margin: 0;
  padding: 0 var(--space-3) var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.dash-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.dash-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.dash-card h2 { margin: 0; font-size: var(--text-lg); }
.dash-card p { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }
/* clickable quick-link cards (metrics pages) reuse .dash-card */
a.dash-card { text-decoration: none; color: inherit; }
a.dash-card:hover { border-color: var(--color-primary); }

/* Staff home: plain grouped links (Jason, 2026-07-17) — the card grid got
   crowded. Columns on desktop, one stack on phones. */
.staff-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-4);
  max-width: 56rem;
  margin: var(--space-3) 0;
}
.staff-links h2 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}
.staff-links ul { list-style: none; margin: 0; padding: 0; }
/* create-action sub-links (e.g. New Booking / Custom Deal under Bookings):
   indented, slightly smaller, with a muted "+" so they read as actions */
.staff-links ul ul { padding-left: var(--space-3); }
.staff-links ul ul li { font-size: var(--text-sm); }
.staff-links ul ul li a::before { content: "+ "; color: var(--color-text-muted); }
.staff-links li { padding: var(--space-1) 0; font-size: var(--text-base); }
.staff-links li a { color: var(--color-primary); text-decoration: none; }
.staff-links li a:hover { text-decoration: underline; }
@media (max-width: 47.99rem) {
  /* finger-sized rows when stacked */
  .staff-links li { padding: var(--space-2) 0; }
}

/* dashboard layout — booking panels with the setup checklist as a narrow
   side column on wide screens; everything stacks on small screens */
.dash-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); align-items: start; margin: var(--space-3) 0; }
.dash-layout.has-side { grid-template-columns: minmax(0, 1fr) 300px; }
.dash-layout .dash-panels { margin: 0; }
.dash-side { min-width: 0; }
@media (max-width: 64rem) {
  .dash-layout.has-side { grid-template-columns: 1fr; }
  .dash-side { order: -1; } /* undone setup steps come first on mobile */
}

/* dashboard onboarding checklist — done/undone setup steps */
.checklist-card { margin: var(--space-3) 0; }
/* compact variant for the dashboard side column: smaller type, tighter rows */
.checklist-compact { margin: 0; }
.checklist-compact .table-card-head { padding: var(--space-2) var(--space-2) var(--space-1); }
.checklist-compact .table-card-head h2 { font-size: var(--text-base); }
.checklist-compact .checklist-item { padding: var(--space-1) var(--space-2); gap: var(--space-2); }
.checklist-compact .checklist-label { font-size: var(--text-sm); }
.checklist-compact .checklist-hint { font-size: 0.75rem; }
.checklist-compact .checklist-mark { width: 18px; font-size: 0.95rem; }
.checklist-compact .checklist-status { font-size: 0.75rem; }
.checklist-compact .btn-sm { padding: 2px var(--space-2); font-size: 0.75rem; min-height: 0; }
/* X button hides the card; choice is remembered in localStorage per org */
.checklist-dismiss {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -4px -6px -4px 0; /* enlarge hit area without pushing the head taller */
  border: 0;
  border-radius: 999px;
  background: none;
  padding: 0;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.checklist-dismiss:hover {
  background: var(--color-surface);
  color: var(--color-text);
}
.checklist { list-style: none; margin: 0; padding: 0; }
.checklist-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.checklist-item:last-child { border-bottom: 0; }
.checklist-mark {
  flex: 0 0 auto;
  width: 22px;
  text-align: center;
  font-size: 1.1rem;
  line-height: 1;
}
.checklist-item.is-done .checklist-mark { color: var(--color-success); }
.checklist-item.is-todo .checklist-mark { color: var(--color-text-muted); }
.checklist-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.checklist-label { font-weight: 500; }
.checklist-item.is-done .checklist-label { color: var(--color-text-muted); font-weight: 500; }
.checklist-hint { color: var(--color-text-muted); font-size: var(--text-sm); }
/* completed rows read as a quiet chip, matching the Set Up button's weight */
.checklist-status {
  color: var(--color-success);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  border-radius: 999px;
  padding: 2px var(--space-2);
  font-size: var(--text-sm);
  font-weight: 500;
}

/* info banner — the old dashboard's welcome/onboarding alert */
.alert {
  padding: var(--space-3);
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  background: var(--color-info-bg);
  color: var(--color-info-text);
  margin: var(--space-3) 0;
}
.alert-warning {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  color: var(--color-warning-text);
}

/* onboarding role question — two big pick-one buttons (customer / vendor) */
.role-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  max-width: 40rem;
  margin: var(--space-3) 0;
}
@media (max-width: 40rem) { .role-options { grid-template-columns: 1fr; } }
.role-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.role-option:hover, .role-option:focus-visible { border-color: var(--color-primary); }
.role-option.is-current { border-color: var(--color-primary); }
.role-option-title { font-size: var(--text-lg); font-weight: 600; }
.role-option-desc { color: var(--color-text-muted); }

/* ==================================================================== */
/* 5. PAGES — auth, storefront, legal, booking detail, yard,           */
/*    messaging, profile, inspections, gate                             */
/* ==================================================================== */

/* ------------------------------------------------- allauth account pages */
/* allauth pages (login, signup, password reset, email management, 2FA)
   render into the shell via templates/allauth/layouts/base.html (.auth-page
   body class); the element overrides in templates/allauth/elements/ emit the
   app's own .form-row / .panel / .btn markup, so the pages inherit the
   generic-form look (small muted labels, white cards, blue buttons). This
   block only centers a narrow column and tidies the leftovers. */
.auth-page .container { max-width: 30rem; margin-inline: auto; }
.auth-page .action-bar { flex-wrap: wrap; }
/* Entrance forms (Sign In / Sign Up / Reset) have one prominent action. */
.auth-page .btn-block { width: 100%; }
/* Radio/checkbox rows (email management): control inline with its label. */
.auth-page .choice-row label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  margin: 0;
}
/* TOTP QR code only — a bare `img` selector here collapses the topbar's
   SVG logo (no intrinsic size) to zero width on every auth page. */
.auth-page .container img { max-width: 100%; height: auto; }
/* Auth cards (Jason, 2026-07-10 — match dj-imp): each auth page is ONE white
   card with the title inside it — big bold h1, then intro copy, then the
   form. No page-header floating above the card; buttons keep their natural
   width instead of stretching full-bleed. */
.auth-card { padding: var(--space-5); }
.auth-card h1 { font-size: 1.75rem; font-weight: 700; line-height: 1.25; margin: 0 0 var(--space-4); }
.auth-card .form-row { margin-bottom: var(--space-4); }
/* Intro copy inside the card (and on any allauth page we haven't branded,
   via elements/p.html): body tone, tight margins. */
.auth-page .auth-copy { color: var(--color-text-label); margin: var(--space-2) 0 var(--space-3); }
/* Authenticator setup: center the QR code in its card. */
.auth-page .qr-row { text-align: center; }
.auth-page .qr-row img { width: 12rem; }

/* Entrance pages — Sign In / Sign Up / reset / verify (Jason, 2026-07-24):
   a focused, modern screen. The topbar keeps only the logo, the card floats
   centered in the viewport on a faint brand-tinted wash, and the card + fields
   are more generous than the dense in-app forms. Manage pages (change password,
   2FA) are NOT auth-entrance, so they keep the full shell and the compact form
   idiom. */
/* Keep the standard white topbar; just drop the redundant right-side links so
   the entrance pages show a clean logo-only navbar. */
.auth-entrance .topbar-right { display: none; }
/* A soft upward shadow lifts the footer off the page wash, mirroring the
   topbar's shadow. */
.auth-entrance .site-footer { border-top: 0; box-shadow: var(--shadow-auth-footer); }
.auth-entrance .page {
  background:
    radial-gradient(1000px 520px at 50% -12%,
      color-mix(in srgb, var(--color-primary) 12%, transparent), transparent 70%);
}
/* Center the card in the space between the topbar and footer. */
.auth-entrance .page > main.container {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  max-width: 26rem;
  margin-inline: auto;
  padding-block: var(--space-5);
}
.auth-entrance .auth-card {
  border: 1px solid var(--color-border);
  border-radius: 16px;
  box-shadow: var(--shadow-auth-card);
}
.auth-entrance .auth-card h1 { margin-bottom: var(--space-2); }
/* Comfortable fields (46px, base size, soft focus ring) regardless of the
   desktop-density rule that shrinks in-app inputs. */
.auth-entrance .auth-card .form-row input:not([type="radio"]):not([type="checkbox"]) {
  min-height: 46px;
  padding: 0 var(--space-3);
  font-size: var(--text-base);
  border-radius: 10px;
}
.auth-entrance .auth-card .form-row input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 22%, transparent);
}
/* One prominent, full-width primary action per card — whether it's a form's
   submit button or a link (e.g. "Request a New Link", "Sign In"), so every
   entrance card closes the same way. Primaries inside an action-bar (a
   multi-button row) keep their natural width. */
.auth-entrance .auth-card .btn-primary {
  width: 100%;
  margin-top: var(--space-2);
  padding-block: 12px;
}
.auth-entrance .auth-card .action-bar .btn-primary {
  width: auto;
  margin-top: 0;
}

/* public vendor storefront (/company/<slug>/) — same card language as the
   New Booking page (Jason, 2026-07-26): centered bold masthead, one-tap
   listing-type chips (shared .browse-type-chip), one section per location
   whose listings are bold browse-style cards. The yard header shows hours
   of operation, not location info, with a red 24/7 badge beside the name
   when the yard is open around the clock (Jason, 2026-09-10). */
.storefront { max-width: 72rem; margin-inline: auto; }
.storefront-masthead { text-align: center; margin: var(--space-4) 0; }
.storefront-masthead h1 { font-size: 2rem; margin-bottom: var(--space-1); }
.storefront-dba { margin: 0 0 var(--space-2); color: var(--color-text-muted); }
.storefront-coi { font-size: var(--text-sm); padding: 2px var(--space-2); }
/* A badge that is a link (the storefront's and the browse card's Request COI
   chips) darkens its fill on hover, the way the buttons do. */
a.badge:hover { background: color-mix(in srgb, currentColor 24%, transparent); }
/* type chips: GET links; the All chip is the "clear". Shares the browse
   page's .browse-type-chip look, centered under the masthead. */
.storefront-chips { justify-content: center; margin: 0 0 var(--space-4); }
/* anonymous hero CTA: centered card right under the masthead */
.storefront-cta {
  max-width: 26rem;
  margin: 0 auto var(--space-4);
  text-align: center;
}
.storefront-cta-lead { font-weight: 600; margin: 0 0 var(--space-3); }
.storefront-cta-note { color: var(--color-text-muted); font-size: var(--text-sm); margin: var(--space-3) 0 0; }
/* Each location is its own boxed section — a light surface band the white
   listing cards sit on, so the grouping reads at a glance (Jason,
   2026-07-26). */
.storefront-location {
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
}
.storefront-location-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}
.storefront-location-name .listing-card-pin { width: 18px; height: 18px; }
.storefront-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-3);
}
.storefront-card-head h2 { margin-bottom: 0; }
/* The yard's week under its name: one span per grouped line ("Mon-Fri: 8:00
   AM-5:00 PM"), wrapping onto new lines as the width runs out. */
.storefront-hours {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-3);
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
/* The 24/7 badge beside the yard name (Jason, 2026-09-10: "a nice bright
   red badge"). The one solid-filled badge — the tinted badge-danger is too
   quiet to be a highlight; same fill the sidebar's count bubble uses, same
   size as the masthead's COI badge. Nothing else may borrow this look. */
.storefront-24-7 {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-primary-contrast);
  font-size: var(--text-sm);
  padding: 2px var(--space-2);
}
.storefront-count { color: var(--color-text-muted); font-size: var(--text-sm); white-space: nowrap; }
.storefront-monthly { color: var(--color-success); font-weight: 500; }
.storefront-empty { text-align: center; color: var(--color-text-muted); margin: var(--space-5) 0; }

/* legal accept gate: document review buttons stacked above the agree action */
.legal-accept { max-width: 26rem; margin: var(--space-5) auto; }
.legal-accept h1 { margin-bottom: var(--space-2); }
.legal-accept > p { margin: 0 0 var(--space-4); color: var(--color-text-muted); }
.legal-accept-docs {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.legal-accept-doc { display: flex; width: 100%; padding: var(--space-3); }
.legal-accept-agree { width: 100%; padding: var(--space-3); }

/* legal pages: readable prose from injected body_html; separated agreements */
.legal-body { margin-top: var(--space-2); max-width: 60ch; }
.legal-body :where(h1, h2, h3) { font-size: var(--text-base); margin: var(--space-3) 0 var(--space-1); }
.legal-body p, .legal-body li { font-size: var(--text-sm); }
.legal-agreement { padding-top: var(--space-2); }
.legal-agreement + .legal-agreement { border-top: 1px solid var(--color-border); margin-top: var(--space-2); }
.legal-agreement h3 { margin: 0 0 var(--space-1); font-size: var(--text-base); }

/* staff help pages: rendered markdown from docs/help/, a page list beside it */
.help-layout { display: grid; grid-template-columns: 14rem minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.help-nav { display: flex; flex-direction: column; gap: 2px; position: sticky; top: var(--space-4); }
.help-nav a { padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); color: var(--color-text-muted); font-size: var(--text-sm); }
.help-nav a:hover { background: var(--color-surface); text-decoration: none; }
.help-nav a.active { background: var(--color-surface); color: var(--color-text); font-weight: 600; }
.help-body { max-width: 72ch; }
.help-body h1 { display: none; }   /* the page header already names the page */
.help-body h2 { font-size: var(--text-lg); margin: var(--space-4) 0 var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--color-border); }
.help-body h2:first-of-type { border-top: 0; padding-top: 0; margin-top: var(--space-2); }
.help-body h3 { font-size: var(--text-base); margin: var(--space-3) 0 var(--space-1); }
.help-body p, .help-body li, .help-body td, .help-body th { font-size: var(--text-sm); line-height: 1.55; }
.help-body ul, .help-body ol { padding-left: 1.4rem; }
.help-body li + li { margin-top: var(--space-1); }
.help-body code { background: var(--color-surface); border-radius: 4px; padding: 0 4px; font-size: 0.85em; }
.help-body pre { background: var(--color-surface); border-radius: var(--radius-sm); padding: var(--space-2); overflow-x: auto; }
.help-body pre code { background: none; padding: 0; }
.help-body table { border-collapse: collapse; margin: var(--space-2) 0; }
.help-body th, .help-body td { border: 1px solid var(--color-border); padding: var(--space-1) var(--space-2); text-align: left; vertical-align: top; }
.help-body blockquote { margin: var(--space-2) 0; padding-left: var(--space-2); border-left: 3px solid var(--color-border); color: var(--color-text-muted); }
.help-body :target { scroll-margin-top: var(--space-4); }
.help-toc { margin: 0 0 var(--space-3); padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-1); }
.help-toc a { font-size: var(--text-sm); padding: 2px var(--space-2); border: 1px solid var(--color-border); border-radius: 999px; }
.help-toc a:hover { background: var(--color-surface); text-decoration: none; }
.help-hit { padding: var(--space-2) 0; border-top: 1px solid var(--color-border); }
.help-hit:first-child { border-top: 0; }
.help-hit-where { font-size: var(--text-sm); color: var(--color-text-muted); }
.help-hit p { margin: var(--space-1) 0 0; font-size: var(--text-sm); }
.help-pager { display: flex; justify-content: space-between; gap: var(--space-2); margin-top: var(--space-4); padding-top: var(--space-2); border-top: 1px solid var(--color-border); }
.page-help { margin: var(--space-1) 0 0; font-size: var(--text-sm); }
@media (max-width: 720px) {
  .help-layout { grid-template-columns: 1fr; }
  .help-nav { position: static; flex-direction: row; flex-wrap: wrap; }
}

/* read-only info rows (e.g. FMCSA company identity on Company settings) */
.readonly-info .form-row { margin-bottom: var(--space-2); }
.readonly-info .form-row:last-child { margin-bottom: 0; }
.readonly-label { display: block; font-weight: 500; font-size: var(--text-sm); color: var(--color-text-label); margin-bottom: 2px; }
.readonly-value { display: block; color: var(--color-text-muted); }


/* booking detail — two-up info/location row, then equipment/activity cards */
.detail-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-3); margin: var(--space-3) 0; }
@media (min-width: 48rem) { .detail-grid { grid-template-columns: 1fr 1fr; } }
/* a lone panel fills the row — no phantom empty second column */
.detail-grid > .panel:only-child { grid-column: 1 / -1; }
/* grid items may shrink below their tables' min-content width — the
   table-card scrolls inside the panel instead of the page growing sideways.
   margin 0: the grid gap alone spaces the cards — panels' own margins would
   double up against it (the excess vertical spacing Jason flagged). */
.detail-grid > .panel { min-width: 0; margin: 0; }
/* stacked variant: always one column */
.detail-grid.stacked { grid-template-columns: 1fr; }
/* booking-detail cards: responsive multi-column grid, equal-height (bottoms
   aligned within a row), compact vertical rhythm (old-platform look). */
.booking-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0;
  align-items: stretch;
}
/* Even stacking on the booking detail page: full-width panels, the card grid,
   and the sections below all share one space-3 vertical rhythm. The cards
   inside the grid rely on the grid gap alone — their own .panel margin would
   otherwise double up against it and space the cards farther than the rest. */
#booking-content .booking-cards .panel { margin: 0; }
.booking-cards .detail-panel { display: flex; flex-direction: column; font-size: var(--text-sm); }
.booking-cards .detail-panel h2 { margin-bottom: var(--space-2); }
.booking-cards .detail-line { margin: 2px 0; }
.booking-cards .detail-section { margin-top: var(--space-2); }
/* The booking Manage page (portal/booking/manage.html), laid out as the
   legacy platform's: one outer card with a title row and a Back link; one
   card per action, the button beside its one line; under Approve, the
   tinted Booking Summary box (two-column grid of "Label: value") and the
   Certificate of Insurance box (label and expiry left, badge and buttons
   right, the verifications list beneath). */
.manage-page { max-width: 56rem; margin: 0 auto; }
.manage-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-4); }
.manage-header h1 { margin: 0; }
.manage-cards { display: flex; flex-direction: column; gap: var(--space-3); }
.manage-card { border: 1px solid var(--color-border); border-radius: var(--radius); padding: var(--space-3); }
.manage-card-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.manage-card-row p { flex: 1 1 auto; margin: 0; font-size: var(--text-sm); color: var(--color-text-secondary); }
.manage-summary { margin-top: var(--space-3); padding: var(--space-2); background: var(--color-highlight-bg); border: 1px solid var(--color-highlight-border); border-radius: var(--radius); }
.manage-summary h4 { margin: 0 0 var(--space-2); font-size: var(--text-sm); font-weight: 600; color: var(--color-text-label); }
.manage-summary-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); font-size: var(--text-sm); }
.manage-label { color: var(--color-text-muted); }
.manage-value { margin-left: var(--space-1); font-weight: 500; }
.manage-coi { margin-top: var(--space-2); padding: var(--space-2); background: var(--color-surface); border-radius: var(--radius); font-size: var(--text-sm); }
.manage-coi-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); }
.manage-coi-row > span { color: var(--color-text-secondary); }
.manage-coi-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.manage-coi h3 { font-size: var(--text-sm); margin: var(--space-3) 0 var(--space-2); }
.manage-coi .table td:first-child { white-space: nowrap; }
@media (max-width: 47.99rem) {
  .manage-summary-grid { grid-template-columns: 1fr; }
}
/* "Rate This Rental" card (completed bookings): big tappable stars over
   hidden radios (Jason, 2026-07-17 — no bare radio buttons). The row is
   DOM-reversed + row-reverse so "this star and every ~ sibling" paints the
   star and everything to its LEFT, for both hover preview and the checked
   fill; keyboard still works through the radio group. */
.rating-panel { max-width: 34rem; }
.rating-stars {
  display: inline-flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
}
.rating-star-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  border-radius: var(--radius);
  cursor: pointer;
}
.rating-star-btn input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.rating-star-glyph {
  font-size: 2rem;
  line-height: 1;
  color: var(--color-border);
}
.rating-star-btn:hover .rating-star-glyph,
.rating-star-btn:hover ~ .rating-star-btn .rating-star-glyph,
.rating-star-btn:has(input:checked) .rating-star-glyph,
.rating-star-btn:has(input:checked) ~ .rating-star-btn .rating-star-glyph {
  color: var(--color-star);
}
.rating-star-btn:has(input:focus-visible) {
  box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-primary);
}
.rating-star { color: var(--color-star); }
/* Staff invoice/payment pages: actions are one horizontal button bar, no
   help text (Jason, 2026-07-26 — supersedes the 2026-07-17 bordered rows).
   The method picker / attach input sits inline with its button and takes
   the filter-bar chrome, not the raw browser default. Every control in the
   bar shares one 40px height so buttons and the select line up (Jason,
   2026-08-06). */
.action-bar { align-items: center; }
.action-bar .btn { min-height: 40px; }
/* A note beside an action (the Remove Location bar) sits on the button's
   midline, not a line below it. */
.action-bar .help { margin: 0; }
/* The actions block ends in a help paragraph (margin-bottom 0) and the next
   section heading carries no top margin — without this the two sat cramped. */
#invoice-actions { margin-bottom: var(--space-4); }
.action-bar .method-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* Every typing control in a bar — the method picker, the attach box, an
   email or search box beside its button — shares the buttons' 40px and the
   fields' token radius; checkboxes and radios stay native. */
.action-bar select,
.action-bar input:not([type="checkbox"], [type="radio"]) {
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
}

/* The pay page's Pay With form: the menu, the quote it drives, and the one
   button — a narrow column beside the bill (Jason, 2026-09-09). */
.pay-form { max-width: 26rem; margin-left: auto; padding: var(--space-3); }
.pay-form .form-row { margin-bottom: var(--space-2); }
.pay-form .pay-quote { border-top: 1px solid var(--color-border); }
.pay-form .pay-quote-note { margin: 0 0 var(--space-3); }
.pay-form .btn { width: 100%; }
@media (max-width: 40rem) { .pay-form { max-width: none; } }
/* The clerk's door: one Stripe button per method kind, stacked full width. */
.pay-online { display: flex; flex-direction: column; gap: var(--space-2); }
.pay-online form { margin: 0; }

/* The staff invoice page. Each section is one block with its heading, and the
   collect panel is one labelled row per way of asking for the money, so a
   link and its Send box read as a single control (Jason, 2026-09-09). */
.invoice-section { margin-bottom: var(--space-5); }
.invoice-section > h2 { margin-bottom: var(--space-2); }

/* Full width, every block: the tables already run edge to edge, so a panel
   that stops half way reads as broken (Jason, 2026-09-09). The staff company
   page shares this frame — the rhythm, the control heights and the select
   chrome below carry .company-page too — so the two pages read as one
   (Jason, 2026-09-10: "match the company page to the invoice page"). */
.invoice-page .panel,
.invoice-page .table-card,
.company-page .panel,
.company-page .table-card { max-width: none; width: 100%; }

/* The facts bar: each label sits over its own value, as many across as fit.
   The pairs are wrapped so a dt and its dd stay one cell — loose in the grid
   they flow into separate cells and interleave. */
.fact-bar { padding: var(--space-3); }
.fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-3);
  margin: 0;
}
.fact-grid > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fact-grid dt {
  color: var(--color-text-secondary);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.2;
}
.fact-grid dd {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* One vertical rhythm for the page: sections apart, headings tight to what
   they name, panels and messages evenly spaced inside. */
/* One rhythm: a heading opens a section, its blocks sit tight beneath it,
   and the next heading is the only thing that adds space (Jason, 2026-09-09). */
/* Jason, 2026-09-09: every section gap is 20px, exactly. */
.invoice-page h2,
.company-page h2 { margin: 1.25rem 0 var(--space-2); }
.invoice-page > h2:first-of-type,
.company-page > h2:first-of-type { margin-top: 1.25rem; }
.invoice-page .title-row + *,
.company-page .title-row + * { margin-top: 0; }
/* A heading that opens its own boxed block carries the section gap on the
   box, not on itself, or the two stack. */
.invoice-page .card > h2:first-child,
.invoice-page .panel > h2:first-child,
.company-page .panel > h2:first-child { margin-top: 0; }

.invoice-page .panel,
.invoice-page .table-card,
.company-page .panel,
.company-page .table-card { margin: 0 0 var(--space-2); }
/* The totals ARE the last rows of the table above them, so the two boxes are
   drawn as one card: corners squared where they meet, one shared border, no
   shadow between them (Jason, 2026-09-09). */
.invoice-page .table-card:has(+ .table-card) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom: 0;
  box-shadow: none;
}
.invoice-page .table-card + .table-card {
  margin-top: calc(-1 * var(--space-2));
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.invoice-page .panel:last-child,
.invoice-page .table-card:last-child,
.company-page .panel:last-child,
.company-page .table-card:last-child { margin-bottom: 0; }
/* The include wrappers must not add a gap of their own on top of the 20px. */
.invoice-page #invoice-actions,
.invoice-page #invoice-edit,
.invoice-page #invoice-paid-offline { margin: 0; }
.invoice-page .message,
.company-page .message { margin: 0 0 var(--space-3); }
.invoice-page p.help,
.company-page p.help { margin: var(--space-2) 0 0; }
.invoice-page .fact-bar p.help:first-of-type { margin-top: var(--space-3); }

.collect-panel {
  display: grid;
  grid-template-columns:
    [label] 11rem
    [main] minmax(10rem, 22rem)
    [act] max-content
    [to] minmax(8rem, 14rem)
    [send] max-content
    [rest] 1fr;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
}
/* The rows and their forms dissolve into the grid so every row's controls
   land in the same columns (Jason, 2026-09-09). */
.collect-row,
.collect-links,
.collect-link,
.collect-panel form { display: contents; }
.collect-label { grid-column: label; }
.collect-main { grid-column: main; width: 100%; }
.collect-act { grid-column: act; }
.collect-to { grid-column: to; }
.collect-send { grid-column: send; }
.collect-state { grid-column: label / -1; }
.collect-value { font-size: var(--text-sm); font-weight: 600; }
.correct-panel { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding: var(--space-3); }
.collect-corrections { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.collect-state { margin: 0; color: var(--color-text-secondary); font-size: var(--text-sm); }
.collect-state strong { color: var(--color-text); }
.collect-label {
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
}
.collect-panel form { margin: 0; }
.collect-note { flex: 1 1 18rem; margin: 0; font-size: var(--text-sm); }

/* One control height across the page: a button, a dropdown and a text box on
   the same row must line up (Jason, 2026-09-09). */
.invoice-page .btn,
.invoice-page .btn-sm,
.invoice-page select,
.company-page .btn,
.company-page .btn-sm,
.company-page select,
.invoice-page input[type="text"],
.invoice-page input[type="email"],
.invoice-page input[type="date"],
.invoice-page input[type="number"],
.invoice-page input[type="search"] {
  height: 1.9rem;
  min-height: 0;
  padding-block: 0;
  box-sizing: border-box;
  font-size: var(--text-xs);
}
.invoice-page .btn,
.invoice-page .btn-sm,
.company-page .btn,
.company-page .btn-sm { padding-inline: var(--space-2); }
.invoice-page select,
.invoice-page input,
.company-page select { padding-inline: var(--space-2); }
/* The arrow needs its own room or the chosen option reads clipped. */
.invoice-page select,
.company-page select { padding-right: var(--space-5); min-width: 9rem; }
/* The compact panels set their own control sizes; keep them on the same
   height as everything else on the page. */
.invoice-page .panel-compact .form-row input,
.invoice-page .panel-compact .form-row select,
.invoice-page .form-row input,
.invoice-page .form-row select { height: 1.9rem; min-height: 0; }
.invoice-page .form-row label { font-size: var(--text-xs); }
/* Every text box on the page wears the same border, radius and ground —
   an unstyled input beside a styled one is what reads as broken. */
.invoice-page input[type="text"],
.invoice-page input[type="email"],
.invoice-page input[type="date"],
.invoice-page input[type="number"],
.invoice-page input[type="search"],
.invoice-page input:not([type]),
.invoice-page textarea {
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  color: var(--color-text);
  font-family: inherit;
}
/* A select keeps its own background: that is where the chevron lives. */
.invoice-page select,
.company-page select {
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-family: inherit;
}
.invoice-page input::placeholder { color: var(--color-text-muted); }
.invoice-page input:focus-visible,
.invoice-page select:focus-visible,
.invoice-page textarea:focus-visible,
.company-page select:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}
.invoice-page input[readonly] { background: var(--color-surface); }
/* Inline rows size their boxes to the row, not to the panel. */
.invoice-page .inline-form input,
.invoice-page .inline-form select { width: auto; max-width: 18rem; }
.invoice-page .collect-panel .collect-main,
.invoice-page .collect-panel .collect-to { width: 100%; max-width: none; }
.invoice-page .copy-source { min-width: 0; }
.collect-panel .copy-source { min-width: 14rem; }
@media (max-width: 40rem) {
  .collect-row,
  .collect-link { flex-direction: column; align-items: stretch; }
  .collect-label { flex: none; }
}

/* The staff company page's own facts (it shares the invoice page's frame,
   above). A Billing fact carries its control: the badge, the dropdown and the
   button sit on one line inside the cell, the dropdown taking what is left,
   and those cells start wide enough for a dropdown and its Save side by side.
   A total among the money facts reads as the answer: its label in the text
   colour, its figure a step larger. A button bar under a table keeps the
   page's rhythm instead of the bar's own margin (Jason, 2026-09-10). */
.company-page .fact-grid dd { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.company-page .fact-grid dd form { display: flex; flex: 1 1 auto; align-items: center; gap: var(--space-1); margin: 0; }
.company-page .fact-grid dd select { flex: 1 1 auto; width: auto; }
.company-page .fact-grid-controls { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.company-page .fact-total dt { color: var(--color-text); font-weight: 700; }
.company-page .fact-total dd { font-size: var(--text-base); }
.company-page .action-bar { margin: 0 0 var(--space-2); }

/* The pay page's total block: the figure the payer is here for, stated once
   and large, with any credit above it (Jason, 2026-09-09). */
.bill-total { max-width: 26rem; margin-left: auto; padding: var(--space-3); }
.bill-line {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-1) 0;
  color: var(--color-text-secondary);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.bill-line + .bill-line { border-top: 1px solid var(--color-border); }
.bill-due { color: var(--color-text); font-size: var(--text-base); padding-top: var(--space-2); }
.bill-due strong { font-size: var(--text-xl); }
@media (max-width: 40rem) { .bill-total { max-width: none; } }

/* Staff invoice detail: the vendor/platform split headline — the two figures
   staff scan for, stated big above the component rows. */
.split-headline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-3);
}
.split-headline .split-figure {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.split-headline strong {
  color: var(--color-text);
  font-size: var(--text-lg);
  font-variant-numeric: tabular-nums;
  margin-left: var(--space-1);
}
/* The split's component rows: two label/value columns on a wide screen so the
   whole derivation is read at once, one on a phone (Jason, 2026-09-09). The
   last row (Vendor Share) is the answer, so it carries the weight. */
.split-rows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0 var(--space-4);
  margin: 0 0 var(--space-3);
}
.split-rows > div {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.split-rows dt { color: var(--color-text-muted); margin: 0; }
.split-rows dd { margin: 0; font-variant-numeric: tabular-nums; }
.split-rows > div.split-total { border-bottom: 0; font-weight: 600; }
.split-rows > div.split-total dt { color: var(--color-text); }

/* Staff payment page: the collapsed raw-Stripe inspector — bounded,
   bordered code block instead of a bare browser <pre>. */
.raw-json { margin: 0 0 var(--space-3); }
.raw-json > summary {
  cursor: pointer;
  padding: var(--space-2) 0;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.raw-json > summary:hover { color: var(--color-text); }
.raw-json pre {
  margin: var(--space-1) 0 0;
  padding: var(--space-3);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-size: 0.75rem;
  line-height: 1.5;
  max-height: 32rem;
  overflow: auto;
}

/* Staff payment/invoice log: dense log lines — mono timestamp, the event
   text, and who did it in the right gutter. Reads like a terminal log, not
   a table (Jason, 2026-07-25). Capped and scrollable so a long-lived
   invoice's log never swallows the page; the page keeps scrolling normally
   past it (overscroll containment). */
.event-log {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  padding: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3);
  max-height: min(60vh, 32rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.event-log-line {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
  line-height: 1.5;
}
.event-log-line + .event-log-line { border-top: 1px solid var(--color-border); }
.event-log-time {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
  flex: 0 0 10.5rem;
}
.event-log-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere; /* long Stripe ids/equations wrap, never overflow */
  color: var(--color-text-muted); /* the log reads as background detail (Jason, 2026-07-26) */
}
.event-log-who {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 640px) {
  .event-log-line { flex-wrap: wrap; gap: 0 var(--space-2); }
  .event-log-time { flex-basis: auto; }
  .event-log-who { order: 2; margin-left: auto; }
  .event-log-text { order: 3; flex-basis: 100%; }
}

/* Staff invoice page: the collapsed "Add a Charge" escape hatch. */
.invoice-add-charge { margin: var(--space-3) 0; }
.invoice-add-charge > summary {
  cursor: pointer;
  padding: var(--space-2) 0;
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.invoice-add-charge > summary:hover { color: var(--color-text); }

.rating-edit summary {
  cursor: pointer;
  color: var(--color-primary-hover);
  width: fit-content;
}
.rating-edit > form { margin-top: var(--space-2); }

/* label / value rows: muted label above (or beside) the emphasized value */
.info-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-2) 0 0;
}
.info-grid dt { color: var(--color-text-muted); font-size: var(--text-sm); }
.info-grid dd { margin: 0; font-weight: 400; }

/* booking detail — dense header: a lead band (category + type/dates + price),
   then a compact row of Documents / Contacts / Location cards. 0.6875rem is an
   intentional micro-caps size below --text-sm for tiny field labels. */
.booking-hero {
  background: var(--color-highlight-bg);   /* faint-blue highlight tint */
  border-color: var(--color-highlight-border);
  padding: var(--space-3) var(--space-4);
}
/* One row at every width: the lead shrinks and its facts wrap; the code +
   badges never drop under or above it (Jason, 2026-09-11). */
.booking-hero-main {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-3);
}
.booking-lead { flex: 1 1 auto; min-width: 0; }
.booking-eyebrow {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-primary);
}
.booking-category {
  margin: var(--space-2) 0 0;  /* room under the type badge (Jason, 2026-09-11) */
  font-size: var(--text-lg);
  line-height: 1.15;
  font-weight: 500;
  color: var(--color-text);
}
.booking-subtitle {
  margin: 2px 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.booking-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}
.booking-facts .fact { display: inline-flex; align-items: baseline; gap: var(--space-1); font-weight: 500; }
.booking-facts .fact-label {
  font-size: 0.6875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.booking-hero .help { margin-top: var(--space-2); }
/* the confirmation code + status/paid badges, pinned to the hero's top-right */
.booking-hero-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-1);
  text-align: right;
  flex-shrink: 0;
}
.booking-code {
  font-weight: 600;
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  color: var(--color-text);
}
/* phones (Jason, 2026-09-11: "the id always stays top right; the badges
   can wrap"): the hero becomes a grid — the type badge and the booking
   number share the first line, number at the right; the title and facts
   span the row; the status badges are the full-width wrapping row under
   the facts they were a month ago. `display: contents` lifts the lead's and
   the meta box's children into the grid; the markup is untouched. Above
   this breakpoint the column (number over badges) stays beside the title,
   top right, and never drops under the facts. */
@media (max-width: 47.99rem) {
  .booking-hero-main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0 var(--space-3);
  }
  .booking-lead, .booking-hero-meta { display: contents; }
  .booking-eyebrow { grid-row: 1; grid-column: 1; }
  .booking-code { grid-row: 1; grid-column: 2; justify-self: end; align-self: center; }
  .booking-category, .booking-subtitle, .booking-facts, .booking-hero-meta .badge-row { grid-column: 1 / -1; }
  .booking-hero-meta .badge-row {
    order: 1;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
  }
}

/* Documents card — label-less list of self-describing links + COI status */
.doc-status { font-weight: 500; color: var(--color-success); }
.doc-status-warn { color: var(--color-warning-strong); }
.doc-item details { margin: 0; }
.doc-list { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-1); }
.doc-item { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-2); margin: 0; }

/* Contacts card — customer then vendor, stacked as plain sub-sections (no
   quote-style indent); emails break cleanly */
.contact-cols { display: flex; flex-direction: column; gap: var(--space-3); }
.contact p { margin: 0 0 2px; overflow-wrap: anywhere; }
.contact-role {
  font-size: 0.6875rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.contact-org { font-weight: 500; }

/* Location card — tight address block */
.loc-address { margin: 0; line-height: 1.5; }

.detail-panel h2 { font-size: var(--text-base); margin: 0 0 var(--space-2); }
.detail-section { margin-bottom: var(--space-3); }
.detail-section:last-child { margin-bottom: 0; }
.detail-section h3 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-sm);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}
.detail-line { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-1) 0; }
.detail-line .sidebar-icon { color: var(--color-text-muted); width: 18px; height: 18px; }
/* status + payment badges — beside the page title, and (legacy) inline rows */
.badge-row { display: inline-flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }
/* hero badges (Active / Paid) headline the booking — bigger than list chips
   (Jason, 2026-07-17) */
.booking-hero-meta .badge { font-size: var(--text-sm); padding: 2px 8px; white-space: nowrap; }
.booking-hero-meta .badge-row { flex-wrap: nowrap; }
.detail-line.badge-row { display: flex; }
.title-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); margin: 0 0 var(--space-3); }
.title-row h1 { margin: 0; font-weight: 500; }
/* booking detail page title (the badges/code live in the hero, not here) */
.page-title { margin: 0 0 var(--space-3); font-weight: 500; }
/* booking equipment table — four lean columns (Unit / Out / In / actions) that
   fit without scrolling; the unit number and dates edit inline (save on change) */
.equipment-table th, .equipment-table td { vertical-align: middle; }
/* dj-imp's roomier booking-equipment density (py-4 px-6) — DESKTOP ONLY.
   On phones the cells fall through to the shared stacked `.table td` rhythm
   (same label-left / value-right cards as every other table), so there's no
   equipment-specific mobile rule duplicating that behavior. */
@media (min-width: 48rem) {
  .equipment-table th, .equipment-table td { padding: var(--space-3) var(--space-4); }
}
/* row reference — bold, stable id like the old Reference column */
.equipment-table .cell-ref { font-weight: 500; color: var(--color-text); white-space: nowrap; }
/* plain blue in-table link (old "View" inspections link) */
.cell-link { color: var(--color-primary); text-decoration: none; }
.cell-link:hover { text-decoration: underline; }
.cell-edit form { margin: 0; }
/* always look like an editable field (a bare, borderless input read as static
   text, so people couldn't tell a just-assigned unit was still editable) */
.cell-edit input {
  width: 100%;
  min-width: 0;
  max-width: 11rem;
  min-height: 32px;
  box-sizing: border-box;
  padding: 4px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
}
.cell-edit input:hover { border-color: var(--color-text-muted); }
.cell-edit input:focus {
  border-color: var(--color-primary);
  outline: none;
}
/* Keep the datalist dropdown arrow visible so a unit field reads as a picker.
   Chrome hides the picker indicator until focus, which made a just-assigned
   unit look like plain, non-editable text. */
.cell-edit input[list] { appearance: auto; }
.cell-edit input[list]::-webkit-calendar-picker-indicator {
  opacity: 0.55;
  cursor: pointer;
}

/* Yard Log panel header. */
.yard-log-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* location picker + search above the yard log */
/* Equipment-table inline date editors stay a sane width; Chrome pads the
   inner date text and calendar icon generously, so trim both to match the
   unit field's density (Jason, 2026-07-16). */
.cell-edit input[type="date"] { max-width: 9.5rem; padding: 2px 4px; }
.cell-edit input[type="date"]::-webkit-datetime-edit { padding: 0; }
.cell-edit input[type="date"]::-webkit-calendar-picker-indicator {
  margin: 0;
  padding: 0;
  opacity: 0.55;
}

/* ---- Yard v3: chips + entry bar + a single-column item list. The same
   structure on every viewport (no table, nothing scrolls sideways); the
   booking detail page renders the identical read-only slice, so these
   rules serve both surfaces. ---- */

/* Occupancy chips: one per open yard booking — "IMP-XX · Customer — N in
   yard / M paid". On the Yard page they are buttons that pick the booking
   in the check-in bar (shell.js). Over capacity renders hot on the warning
   tokens (it's a billing event to act on, not an error). */
.yard-chip {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 0.35em;
  padding: 4px var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-bg);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  text-decoration: none;
}
/* the inert "N in yard" count is a fact, not a control — plain muted text
   so it never reads as another pressable chip */
button.yard-chip { cursor: pointer; }
a.yard-chip:hover,
button.yard-chip:hover { border-color: var(--color-primary); color: var(--color-primary); }
.yard-chip-over,
a.yard-chip-over:hover,
button.yard-chip-over:hover {
  border-color: var(--color-warning-border);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  font-weight: 600;
}

/* The booking page is ONE yard table now — a row per space (Jason,
   2026-09-12) — so it auto-sizes to its contents. The fixed widths that
   made the old in/departed pair line up were written for five columns and
   pushed the last one off the right edge here, taking Remove with it; the
   actions moved into the Equipment cell and the widths are gone. */
.yard-table th:nth-child(1) { width: 5rem; }
/* Shared column hints so the still-in and Left the Yard tables line up
   (two theads, same seven columns). Yard page only — the booking slice
   has four columns. */
#yard-content .yard-table th:nth-child(1) { width: 12%; }
#yard-content .yard-table th:nth-child(2) { width: 16%; }
#yard-content .yard-table th:nth-child(3) { width: 18%; }
#yard-content .yard-table th:nth-child(4) { width: 14%; }
#yard-content .yard-table th:nth-child(5) { width: 14%; }
#yard-content .yard-table th:nth-child(6) { width: 10%; }
/* Row time editors: comfortable width, one visible at a time. */
.yard-table input[type="datetime-local"] {
  min-height: 32px;
  box-sizing: border-box;
  padding: 3px 4px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-card);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  max-width: 12rem;
}
/* Phones: rows stack into labeled cards via the generic .table treatment;
   here we make every target finger-sized and re-fit the yard chrome to a
   single column (designer pass, 2026-07-17). */
@media (max-width: 47.99rem) {
  /* chips against their labels center like the equipment list; keep the
     line height snug — the chips carry their own padding */
  .yard-table td { align-items: center; min-height: 30px; padding: 2px 0; }
}
/* Equipment tables on phones scroll sideways inside their card: fade the
   right edge as a "more columns" affordance (the card-colored cover scrolls
   with the content and hides the shadow at the end). The yard log doesn't
   use this — its table stacks into cards like the other lists. */
@media (max-width: 47.99rem) {
  .table-card-scroll {
    background-color: var(--color-card);
    background-image:
      linear-gradient(to left, var(--color-card) 30%, transparent),
      radial-gradient(farthest-side at 100% 50%, var(--color-backdrop), transparent);
    background-position: right center, right center;
    background-size: 36px 100%, 12px 100%;
    background-repeat: no-repeat, no-repeat;
    background-attachment: local, scroll;
  }
}
.yard-gate-info summary:hover,
.yard-gate-photos { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.yard-gate-photos img {
  display: block;
  width: 72px;
  height: 72px;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.radio-group .radio-option[hidden] { display: none; }
/* Unit detail: retire is a quiet footer action inside Unit Info, its warning
   inline beside the button, separated from the facts above by a rule. */
.unit-retire {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
.unit-retire .help { margin: 0; }
.unit-retire .btn { white-space: nowrap; flex-shrink: 0; }

/* Activity log — muted, no card, parked at the bottom; shows ~7 rows then
   scrolls (it's a low-priority audit trail, not a headline). */
/* Activity renders as a white card like every other detail section (it was
   a bare gray block, which read as unfinished next to the panels above). */
.activity-log {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  color: var(--color-text-muted);
}
.activity-log h2 { margin: 0 0 var(--space-2); color: var(--color-text); }
.activity-feed {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 16rem; /* bounded (Jason 2026-07-08): the feed scrolls, the page never grows */
  overflow-y: auto;
  font-size: var(--text-sm);
  border-top: 1px solid var(--color-border);
}
.activity-feed li { padding: var(--space-1) 0; border-bottom: 1px solid var(--color-border); }
.activity-time { font-size: var(--text-sm); margin-right: var(--space-2); }
/* ------------------------------------------------------------------ */
/* messaging — sidebar badge, thread list indicator, thread page       */
/* ------------------------------------------------------------------ */
.sidebar-badge {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--color-danger);
  color: var(--color-primary-contrast);
  font-size: 11px;
  font-weight: 500;
  line-height: 18px;
  text-align: center;
}
.unread-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-danger);
  vertical-align: middle;
}
/* Full-height chat screen (Jason, 2026-07-14): no card, full width/height,
   the log fills and scrolls, the composer is pinned at the bottom, the site
   footer is hidden. Driven by the `.msg-page` body class. */
.msg-page .site-footer { display: none; }
.msg-page .page { height: 100dvh; }
.msg-page .page > main.container {
  flex: 1 1 auto;
  min-height: 0;
  max-width: none;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.msg-screen { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.msg-screen-head {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-4) var(--space-2);
  border-bottom: 1px solid var(--color-border);
}
.msg-back { font-size: var(--text-sm); }
.msg-title { margin: var(--space-1) 0 0; font-size: var(--text-lg); }
.msg-log {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}
.msg { max-width: 42rem; }
.msg-mine { align-self: flex-end; text-align: right; }
.msg-meta { font-size: var(--text-sm); color: var(--color-text-muted); display: flex; gap: var(--space-2); flex-wrap: wrap; }
.msg-mine .msg-meta { justify-content: flex-end; }
.msg-sender { font-weight: 600; color: var(--color-text); }
.msg-body {
  margin: 2px 0 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  background: var(--color-surface);
  display: inline-block;
  text-align: left;
}
.msg-mine .msg-body { background: var(--color-primary-soft); }
.msg-compose {
  flex: 0 0 auto;
  display: flex;
  gap: var(--space-2);
  /* stretch keeps Send exactly as tall as the box whatever the box measures
     (Jason, 2026-07-14) — no hard-coded height to drift from the button */
  align-items: stretch;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-card);
}
.msg-compose textarea {
  flex: 1 1 8rem;
  min-width: 0;
  /* one row defines the height (rows=1 on the widget); Send stretches to it */
  resize: vertical;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-base);
  resize: none;
}
.msg-compose textarea:focus-visible { outline-offset: 1px; border-color: var(--color-primary); }
/* keep Send compact next to the textarea — the global mobile rule stretches
   non-.btn-sm buttons to full width, which would collapse the composer */
.msg-compose .btn { flex: 0 0 auto; width: auto; }
.msg-disclaimer {
  margin: var(--space-3) 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.msg-admin {
  display: inline-block;
  padding: 0 6px;
  border-radius: var(--radius);
  background: var(--color-primary);
  color: var(--color-primary-contrast);
  font-size: var(--text-sm);
}
.msg-refs { margin: 4px 0 0; display: flex; gap: var(--space-2); flex-wrap: wrap; }
.msg-mine .msg-refs { justify-content: flex-end; }
.msg-ref {
  display: inline-block;
  padding: 1px 8px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: var(--text-sm);
  text-decoration: none;
}
.form-error { color: var(--color-danger); flex-basis: 100%; margin: 0; }
/* panel with a title + action button on one header line (profile, contacts) */
.panel-header-row {
  display: flex;
  flex-wrap: wrap;  /* a long heading + button (Contacts → Message Vendor) wraps, never overflows (Jason, 2026-09-11) */
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.panel-header-row h2 { margin: 0; }
/* profile page — an identity hero card, then settings-list panels where
   every row is label / value / one action link */
.profile-stack {
  max-width: 44rem;
  display: grid;
  gap: var(--space-3);
}
/* grid items default to min-width auto — long unbroken emails must not
   widen the column past the viewport. margin:0 because the grid `gap` already
   spaces the cards — the panel's own vertical margin stacked on top of it,
   doubling the gap (Jason, 2026-07-24), the same reason the storefront/detail
   grids zero it. */
.profile-stack > .panel { min-width: 0; margin: 0; }
/* A form wrapping several cards keeps the stack's rhythm: it is a stack too. */
.profile-stack > form { display: grid; gap: var(--space-3); }
.profile-stack > form > .panel { min-width: 0; margin: 0; }
.profile-hero {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.profile-avatar {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-primary-tint);
  color: var(--color-primary-hover);
  font-size: var(--text-lg);
  font-weight: 600;
}
.profile-who { flex: 1; min-width: 0; }
.profile-name {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  overflow-wrap: anywhere;
}
.profile-mail {
  margin: 2px 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}
.profile-section h2 { margin: 0 0 var(--space-1); }
.setting-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}
.setting-row:last-child { border-bottom: 0; padding-bottom: 0; }
.setting-label {
  flex: 0 0 10.5rem;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.setting-value {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  overflow-wrap: anywhere;
}
.setting-empty { color: var(--color-text-muted); }
.setting-action { margin-left: auto; white-space: nowrap; }
/* phones: label gets its own line; value + action share the second line */
@media (max-width: 47.99rem) {
  .setting-row { flex-wrap: wrap; row-gap: 2px; }
  .setting-label { flex: 1 0 100%; }
}
/* a button that reads as a plain blue link (inline actions like Verify Phone) */
.btn-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--color-primary-hover);
  cursor: pointer;
}
.btn-link:hover { text-decoration: underline; }

/* inspections — the public wizard (minimal logo-only chrome, START/STOP
   buttons, additive component reports) — old-platform parity */
.insp-public { background: var(--color-bg); }
.insp-public-nav {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 56px;
  background: var(--color-card);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 10;
}
.insp-public-main {
  max-width: 42rem;
  margin: 0 auto;
  padding: var(--space-3);
}
.insp-public-main .panel { margin-bottom: var(--space-3); }
.insp-public-main .btn-block { width: 100%; display: block; text-align: center; }
.insp-type-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.insp-type-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-4) var(--space-3);
  border: 0;
  border-radius: calc(var(--radius) * 2);
  color: var(--color-primary-contrast);
  cursor: pointer;
  box-shadow: var(--shadow-raised);
}
.insp-type-btn:hover { filter: brightness(0.95); }
.insp-type-start { background: var(--color-success); }
.insp-type-stop { background: var(--color-danger); }
.insp-type-word { font-size: var(--text-xl); font-weight: 700; }
.insp-type-sub { font-size: var(--text-sm); opacity: 0.9; }
.insp-report-list { display: flex; flex-direction: column; gap: var(--space-2); }
.insp-report {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.insp-report-ok { border-color: var(--color-success); }
.insp-report-damage { border-color: var(--color-danger); }
.insp-report-text { flex: 1; }
.insp-report-text .help { margin: 2px 0 0; }
.insp-report-name { font-weight: 500; margin-right: var(--space-1); }
.insp-thumb { width: 40px; height: 40px; border-radius: var(--radius); object-fit: cover; }
.insp-warning {
  border: 3px solid var(--color-danger);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  padding: var(--space-3);
  margin: var(--space-3) 0;
  text-align: center;
}
.insp-warning p { margin: 0; color: var(--color-danger); font-weight: 700; }
.insp-warning p + p { margin-top: var(--space-1); font-weight: 500; }
.insp-back { text-align: center; margin-top: var(--space-2); }

/* staff metrics pages — chart panels, stat tiles, month expanders */
.chart-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-3);
  margin: var(--space-3) 0;
}
/* Chart.js needs a sized box when maintainAspectRatio is off. */
.chart-box { position: relative; height: 320px; }
.stat-grid .dash-card { gap: var(--space-1); }
.stat-grid .dash-card > p { margin: 0; color: var(--color-text-label); font-size: var(--text-sm); }
.stat-value { font-size: var(--text-xl); font-weight: 700; color: var(--color-text); }
.stat-grid .stat-hint { color: var(--color-text-muted); }
.month-details {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  margin: var(--space-2) 0;
}
.month-details summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  min-height: var(--touch-target);
  cursor: pointer;
}
.month-details summary:hover { background: var(--color-surface); border-radius: var(--radius); }
.month-details-body { padding: 0 var(--space-3) var(--space-3); }
.month-details-body h3 { font-size: var(--text-base); margin: var(--space-2) 0 var(--space-1); }
.month-details-body ul { margin: 0; padding-left: var(--space-4); }
.month-details .muted { color: var(--color-text-muted); font-size: var(--text-sm); }
.metrics-filters { align-items: end; }
.metrics-filters label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-label);
}

/* Story pages (staff): the wordy log's When column stays on one line. */
.table .cell-nowrap { white-space: nowrap; }
/* Staff Charges: twelve of the thirteen columns never wrap, so a narrow
   window squeezed the one wrapping cell (What: "Overage day · Ref DVT9WK ·
   Unit DV1-001") to a word per line and rows five lines tall. The cell keeps
   a readable width and the card scrolls sideways instead (its overflow-x).
   Desktop only: on phones the rows stack into cards (overflow visible) and a
   20rem floor would push the page sideways on a 320-375px screen. */
@media (min-width: 48rem) {
  .table td.cell-what { min-width: 20rem; max-width: 28rem; }
}

/* --- Gate ------------------------------------------------------------- */
/* Big-touch gate screens (Jason, 2026-08-25): gloves on, a truck waiting.
   One column ≤ 44rem at every width; primary taps ≥ 56px; the bar is
   sticky at every width; colors are tokens only. */
.gate-page { max-width: 44rem; margin: 0 auto; }
.gate-page .page-title { font-size: var(--text-xl); }
.gate-page h2.gate-section { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-4) 0 var(--space-2); font-size: var(--text-lg); }
.gate-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin: 0 0 var(--space-2); }
.gate-yard { color: var(--color-text-secondary); }
.gate-count { display: inline-flex; align-items: center; justify-content: center; min-width: 32px; height: 32px; margin-left: var(--space-2); padding: 0 var(--space-2); border-radius: 999px; background: var(--color-primary); color: var(--color-primary-contrast); font-size: var(--text-base); font-weight: 700; }

/* Gate-sized buttons on top of .btn: readable case, 56px, one radius. */
.gate-btn { min-height: 56px; padding: var(--space-2) var(--space-3); gap: var(--space-2); border-radius: var(--radius); font-size: var(--text-base); font-weight: 600; line-height: 1.25; text-transform: none; letter-spacing: 0; white-space: normal; }
.gate-btn svg { width: 24px; height: 24px; fill: currentColor; flex: none; }
.gate-bar { position: sticky; bottom: 0; z-index: 5; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) calc(-1 * var(--space-4)) calc(-1 * var(--space-4)); padding: var(--space-3) var(--space-4); background: var(--color-card); border-top: 1px solid var(--color-border); border-radius: 0 0 var(--radius) var(--radius); }
/* .panel > :last-child would pull the bar's bottom bleed back to 0. */
.gate-page .panel > .gate-bar:last-child { margin-bottom: calc(-1 * var(--space-4)); }
.gate-bar .btn { flex: 1 1 8rem; }
.gate-bar .btn-primary { flex: 2 1 12rem; font-size: var(--text-lg); }
.gate-bar .gate-back { flex: 0 1 8rem; }
/* Stacked on phones: flex-basis would become the HEIGHT, so it is reset. */
@media (max-width: 40rem) { .gate-bar .btn { flex: 1 1 7rem; } }
/* The words that say what went wrong read as big as the fields. */
.gate-page .help { font-size: var(--text-base); color: var(--color-text-secondary); }
.gate-page .errorlist { font-size: var(--text-base); font-weight: 600; }
/* A wrong box turns red, not only the sentence under it. */
.gate-page .form-row:has(.errorlist) input:not([type="radio"]):not([type="checkbox"]), .gate-page .form-row:has(.errorlist) select, .gate-page .form-row:has(.errorlist) textarea { border: 2px solid var(--color-danger); }
.gate-page .form-row:has(.errorlist) .radio-group .radio-option { border: 2px solid var(--color-danger); }

/* Done banner (the flash message, gate-sized). */
.gate-done { margin: 0 0 var(--space-3); padding: var(--space-3) var(--space-4); border: 2px solid var(--color-success-border); border-radius: var(--radius); background: var(--color-success-bg); color: var(--color-success); font-size: var(--text-lg); line-height: 1.35; }
.gate-done strong { font-weight: 700; }
.gate-done-bad { border-color: var(--color-danger); background: var(--color-card); color: var(--color-danger); font-weight: 600; }

/* The code box: the one way in (Jason, 2026-08-30) — big, loud, autofocus. */
.gate-code-form { display: flex; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.gate-code { flex: 1 1 auto; min-width: 0; min-height: 64px; padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border-strong); border-radius: var(--radius); background: var(--color-card); color: var(--color-text); font: inherit; font-size: var(--text-xl); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
.gate-code::placeholder { color: var(--color-text-muted); font-weight: 400; }
.gate-code-form .btn { flex: 0 0 auto; min-width: 6.5rem; }

/* People in the yard: a card with a live code is the link to its out leg. */
.gate-people { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.gate-person { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--space-3); align-items: center; min-height: 72px; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-card); color: var(--color-text); text-decoration: none; box-shadow: var(--shadow-card); touch-action: manipulation; }
a.gate-person:active { border-color: var(--color-primary); background: var(--color-primary-tint); transform: scale(0.99); }
.gate-person-name { grid-column: 1; font-size: var(--text-lg); font-weight: 700; }
.gate-person-sub { grid-column: 1; color: var(--color-text-muted); }
.gate-person-go { grid-column: 2; grid-row: 1 / span 2; display: flex; align-items: center; gap: var(--space-1); color: var(--color-primary); font-weight: 600; white-space: nowrap; }

/* The pass screen's header: who the code says is coming, for which company. */
.gate-verify-name { margin: 0 0 2px; font-size: var(--text-xl); font-weight: 700; }
.gate-company { margin: 0 0 var(--space-3); color: var(--color-text-secondary); font-size: var(--text-lg); }
.gate-early { margin: 0 0 var(--space-3); color: var(--color-warning-strong); font-size: var(--text-lg); font-weight: 600; }

/* The ID Checked pill: a hidden checkbox, the card wears the tick. */
.gate-pill { display: inline-flex; align-items: center; min-height: 56px; margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-card); font-size: var(--text-base); font-weight: 600; cursor: pointer; }
.gate-pill:has(input:checked) { border-color: var(--color-primary); background: var(--color-primary-soft); }

/* One arriving item: the planned type badge, the number, its questions. */
.gate-card { margin: 0 0 var(--space-3); padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-surface); }
.gate-badge { display: inline-flex; align-items: center; margin-right: var(--space-1); padding: 2px var(--space-2); border-radius: var(--radius-sm); background: var(--color-primary-soft); color: var(--color-text); font-size: var(--text-sm); font-weight: 700; vertical-align: middle; }
.gate-unit { margin: 0 0 var(--space-2); font-size: var(--text-xl); font-weight: 700; }

/* Camera button, thumbnails, the saved note. */
.gate-camera { display: flex; width: 100%; margin: 0 0 var(--space-1); border: 1px solid var(--color-border-strong); background: var(--color-surface); color: var(--color-text); font-size: var(--text-base); }
.gate-camera svg { fill: var(--color-primary); }
.gate-photos { position: relative; margin: 0 0 var(--space-3); }
.gate-photos input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; clip: rect(0 0 0 0); }
/* Keyboard focus on the 1px input shows on the label-button instead. */
.gate-photos:has(input[type="file"]:focus-visible) .gate-camera { box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--color-primary); }
.gate-photos-grid { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.gate-photos-grid:not(:empty) { margin: var(--space-2) 0; }
.gate-photo { width: 84px; height: 84px; }
.gate-photo img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--color-border); cursor: zoom-in; }
.gate-note { margin: var(--space-1) 0 0; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); background: var(--color-highlight-bg); color: var(--color-info-text); font-weight: 600; }

/* Any gate photo opens big in the lightbox dialog (gate.js). */
.gate-lightbox { max-width: min(92vw, 44rem); padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-card); }
.gate-lightbox::backdrop { background: var(--color-backdrop); }
.gate-lightbox img { display: block; max-width: 100%; max-height: 70vh; margin: 0 auto var(--space-3); border-radius: var(--radius); }
.gate-lightbox button { width: 100%; }

/* Gate-sized inputs and pills at every width (the ≥48rem "dense" rule is for desks). */
.gate-page .form-row input:not([type="radio"]):not([type="checkbox"]), .gate-page .form-row select, .gate-page .form-row textarea { min-height: 48px; padding: var(--space-2) var(--space-3); font-size: var(--text-base); }
.gate-page .form-row > label { font-size: var(--text-base); }
/* The box says its own name: an uppercasing box must not shout it. */
.gate-page input::placeholder, .gate-page textarea::placeholder { text-transform: none; }
/* No radio dots and no checkboxes anywhere on a gate screen (Jason,
   2026-08-26): the input stays in the DOM — the POST and the screen reader
   need it — but the CARD itself shows the pick, highlighted. */
.gate-page input[type="radio"], .gate-page input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
/* The card wears the focus ring the hidden control cannot show. */
.gate-page .radio-option:has(input:focus-visible),
.gate-page .gate-pill:has(input:focus-visible),
.gate-page .checkbox-row:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.gate-page .radio-group .radio-option { flex: 1 1 40%; max-width: calc(50% - var(--space-1)); justify-content: center; min-height: 48px; font-size: var(--text-base); }
.gate-page .radio-group .radio-option:has(input:checked) { border-color: var(--color-primary); background: var(--color-primary-soft); font-weight: 600; }
.gate-page .checkbox-row:has(input:checked) { font-weight: 600; }

/* Disclosure without script: the forms are prefixed per item, so the
   names are matched by suffix, scoped to one card or condition row. */
.gate-card:not(:has([name$="-seal_present"][value="yes"]:checked)) [data-seal-only],
.gate-card:not(:has([name$="-damage_present"][value="yes"]:checked)) [data-damage-only],
.gate-cond:not(:has([name$="-seal_intact"][value="no"]:checked)) [data-seal-note],
.gate-cond:not(:has([name$="-damage_present"][value="yes"]:checked)) [data-damage-only] { display: none; }

/* The out leg's table: a row per unit, every cell a label for the row's
   hidden checkbox; the ticked row reveals its condition row underneath. */
.gate-table { width: 100%; margin: 0 0 var(--space-2); border-collapse: collapse; table-layout: fixed; }
.gate-table th, .gate-table td { padding: 0; text-align: left; vertical-align: middle; }
.gate-row { border-top: 1px solid var(--color-border); }
.gate-row label { display: flex; align-items: center; min-height: 56px; padding: var(--space-2); cursor: pointer; }
.gate-row:has(input:checked) { background: var(--color-primary-soft); }
.gate-page .gate-row:has(input:focus-visible) { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.gate-cell-number { width: 11rem; font-size: var(--text-base); font-weight: 700; }
.gate-cell-what { color: var(--color-text-muted); }
.gate-row-cond { display: none; }
.gate-row:has(input:checked) + .gate-row-cond { display: table-row; }
.gate-row-cond > td { padding: var(--space-2) var(--space-2) var(--space-3); }
/* On a phone the two text columns collide: the row stacks instead. */
@media (max-width: 33.99rem) {
  .gate-table, .gate-table tbody, .gate-table tr, .gate-table td, .gate-table th { display: block; }
  .gate-row { display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; min-height: 56px; padding: var(--space-2); }
  .gate-row label { min-height: 0; padding: 0; }
  .gate-cell-number, .gate-cell-what { width: auto; }
  .gate-cell-what:empty, .gate-cell-what:has(label:empty) { display: none; }
  .gate-row:has(input:checked) + .gate-row-cond { display: block; }
}

/* On a desk the gate is just a form: ≥ 64rem returns to the app's scale
   (Jason, 2026-08-25 pm); the iPad and the phone — the screens a gate
   actually runs on — keep every big target. */
@media (min-width: 64rem) and (pointer: fine) {
  .gate-page { max-width: 40rem; }
  .gate-page .help, .gate-page .errorlist { font-size: var(--text-sm); }
  .gate-btn { min-height: 44px; font-size: var(--text-sm); }
  .gate-btn svg { width: 20px; height: 20px; }
  .gate-camera { font-size: var(--text-sm); }
  .gate-bar { position: static; margin: var(--space-3) 0 0; padding: var(--space-3) 0 0; }
  /* Unpinned, the bar is an ordinary last row: no bleed to the panel edge. */
  .gate-page .panel > .gate-bar:last-child { margin-bottom: 0; }
  .gate-bar .btn-primary { font-size: var(--text-base); }
  .gate-page .form-row input:not([type="radio"]):not([type="checkbox"]), .gate-page .form-row select, .gate-page .form-row textarea { min-height: 40px; }
  .gate-page .radio-group .radio-option { min-height: 40px; }
  .gate-person { min-height: 60px; }
  .gate-person-name { font-size: var(--text-base); }
  .gate-code { min-height: 48px; font-size: var(--text-lg); }
  .gate-pill { min-height: 44px; }
  .gate-row label { min-height: 40px; }
  .gate-unit { font-size: var(--text-lg); }
  .gate-verify-name { font-size: var(--text-lg); }
  .gate-company { font-size: var(--text-base); }
  .gate-done { font-size: var(--text-base); }
  .gate-photo { width: 64px; height: 64px; }
}

/* ==================================================================== */
/* 6. RESPONSIVE — cross-cutting mobile tap targets                     */
/* ==================================================================== */

/* Mobile tap targets (Jason, 2026-07-13 audit): every actionable
   control reaches the ~44px touch guideline on phones without changing
   the desktop look. The padding/negative-margin pairs grow the HIT AREA
   of inline links without shifting layout.

   Buttons are the exception (Jason, 2026-07-15): they stand at the old
   platform's height everywhere, which is shorter than the guideline — being
   stretched to 44px here is what made them read as oversized on phones. */
@media (max-width: 47.99rem) {
  /* iOS zooms the whole page into any focused control under 16px — pin
     every typing control to 1rem on phones so tapping a search bar (or any
     field) never zooms (Jason, 2026-07-16). */
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea { font-size: 1rem; }
  /* form fields keep the touch target; the filter row sits on the button's
     scale instead (see .filter-bar above) */
  select:not(.filter-bar select),
  .page-size select { min-height: 44px; }
  input[type="checkbox"],
  input[type="radio"] { width: 20px; height: 20px; }
  /* drawer section headings (BOOKINGS / COMPANY / ...) were 24px */
  .sidebar-heading { min-height: 44px; }
  /* the dashboard checklist's X was 32px; the head stays the same height */
  .checklist-dismiss { width: 44px; height: 44px; margin: -10px -12px -10px 0; }
  /* the primary link of a stacked table row is the only way to open the
     record — give it a full-height hit area */
  .table td a:not(.btn) {
    display: inline-block;
    padding: 12px 0;
    margin: -12px 0;
  }
  /* standalone action/back links (the <p><a>← Back</a></p> pattern),
     field-help actions (Add Payment Method), auth-card links (forgot
     password / sign up), and document links */
  p > a:only-child,
  .help a,
  .auth-copy a,
  .doc-item a {
    display: inline-block;
    padding: 12px 0;
    margin: -12px 0;
  }
}


/* --- Staff notes (core.Note) ------------------------------------------- */
.note-form {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  margin: var(--space-3) 0;   /* balanced breathing room above and below */
}
.note-form textarea {
  flex: 1;
  /* Fixed height: the box must not drag the page around as it is typed in
     (Jason, 2026-07-21). It scrolls instead. */
  resize: none;
  min-height: 3.25rem;
  border-radius: var(--radius);
}
.note-form .btn { flex: none; }
@media (max-width: 34rem) {
  .note-form { flex-direction: column; align-items: stretch; }
}
.note-list { list-style: none; margin: var(--space-4) 0 0; padding: 0; }
.note {
  border-top: 1px solid var(--color-border);
  padding: var(--space-3) 0;
}
.note-meta {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-1);
}
.note-body { margin: 0 0 var(--space-2); white-space: pre-wrap; }

/* --- Staff bookings: truncated unit list + count ----------------------- */

/* --- Gate / Yard Log tiles ---------------------------------------------- */
/* One clickable .dash-card per yard (the site's quick-link idiom): the
   yard's name and place, then what is in it. The count reads large. */
.yard-tile p strong { font-size: var(--text-lg); color: var(--color-text); }

/* The chevron, restated for the styled containers. Those rules were written
   before selects had a base style, so each sets the `padding` AND `background`
   SHORTHANDS — which reset the arrow image and the gutter the base rule
   reserved for it, leaving a select with no affordance at all. Restated here,
   after all of them, at matching specificity: the container still controls
   size and color, this only puts the arrow back. */
.page-size select,
.filter-bar select,
.action-bar select,
.table-edit td select,
form .info-grid dd > select {
  padding-right: calc(var(--space-2) * 2 + 16px);
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right var(--space-2) center;
  background-size: 16px;
  appearance: none;
}

/* Current photos on the correction page: a card per photo, its side named,
   its Remove box directly beneath it — no cross-referencing a separate list. */
.yard-photo-cards { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-3); }
.yard-photo-card { margin: 0; }
.yard-photo-card img {
  display: block;
  width: 120px;
  height: 120px;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.yard-photo-card figcaption {
  margin: var(--space-1) 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* Add Equipment (portal/yard/space_add.html): a field only shows for the
   equipment types that collect it, matched to forms_yard.TYPE_FIELDS. The
   switch is a checked RADIO — the gate's own :has disclosure, which is
   reliable where a select's selected option is not — so nothing shows until
   a type is picked. */
.space-add [data-types] { display: none; }
.space-add:has([name="equipment_type"][value="container"]:checked) [data-types~="container"],
.space-add:has([name="equipment_type"][value="chassis"]:checked) [data-types~="chassis"],
.space-add:has([name="equipment_type"][value="trailer"]:checked) [data-types~="trailer"],
.space-add:has([name="equipment_type"][value="truck"]:checked) [data-types~="truck"] { display: block; }

/* Yard spaces on the booking page: a space's equipment and the buttons that
   act on it share one cell, so the table needs no Actions column off the
   right edge (Jason, 2026-09-12 — the overflow was hiding Remove). One item
   per line, its button at the end of its own line. */
/* The space's buttons get their own column (Jason, 2026-09-12) — In to put
   something in the space, Out to take one out — so the Equipment cell names
   equipment and nothing else. Side by side, wrapping on a narrow screen. */
.yard-space-actions { white-space: nowrap; }
.yard-space-actions .btn { margin-right: var(--space-1); }
.yard-space-actions form { display: inline; }

/* A departed row is history: muted, so the live yard reads first. */
.yard-row-out td { color: var(--color-text-muted); }

/* --- Yard log: the correction page ------------------------------------ */
/* Grouped sections inside one form panel. The headings need air above them
   (they follow a field, not a panel edge) and the empty .form-row spacers in
   the grid must not add height of their own. */
.yard-correct h2 { margin-top: var(--space-4); }
.yard-correct h2:first-of-type { margin-top: 0; }
.yard-correct .form-grid .form-row:empty { margin: 0; padding: 0; }

/* vendor payouts — month tiles above the deposits table, and the setup page
   sections (status grid, then the one Stripe button) inside a single panel */
.payout-stats { margin-top: 0; }
.payout-stats .stat-value { font-variant-numeric: tabular-nums; }
.payout-export { flex-wrap: wrap; }
.payout-lines .card-meta { display: block; }
.payout-setup .info-grid { margin-top: 0; }
.payout-setup .detail-section h3 { margin-bottom: var(--space-2); }
.payout-setup .payout-setup-action { margin-top: var(--space-2); }
.payout-setup .help { margin: 0 0 var(--space-2); }

/* --- Booking Settings / Booking Requirements: the requirement grid -------- */
/* /staff/settings/booking/ and /staff/companies/<id>/requirements/. One row
   per switch: its name, a few words on what it does, the On box and the
   Locked box side by side (Jason, 2026-09-10). Rows are display:contents so
   every cell sits on the same column lines; a select in the On column takes
   the edit grid's 38px field chrome (44px on phones) and checkboxes stay
   native; the documents multiselect sits under its words in the wide middle
   column, since the 4rem On column cannot hold it; the phone layout folds
   the words under the name and keeps the two boxes in their own columns. */
.requirement-grid {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) minmax(0, 2fr) 4rem 8rem;
  column-gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-3);
}
.requirement-grid-head,
.requirement-grid-row,
.requirement-grid .requirement-text { display: contents; }
.requirement-grid-head > span {
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}
.requirement-grid-head > .requirement-on,
.requirement-grid-head > .requirement-lock { text-align: center; }
.requirement-grid-row > *,
.requirement-grid-row .requirement-text > * {
  display: flex;
  align-items: center;
  min-height: 38px;
  min-width: 0;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--text-sm);
}
.requirement-grid-row:last-child > *,
.requirement-grid-row:last-child .requirement-text > * { border-bottom: 0; }
.requirement-name { font-weight: 500; color: var(--color-text); }
.requirement-desc { color: var(--color-text-muted); }
.requirement-grid-row .requirement-desc:has(select) {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-1);
  padding-bottom: var(--space-2);
}
.requirement-on, .requirement-lock { justify-content: center; }
.requirement-grid select {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 38px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: var(--text-sm);
  background-color: var(--color-card);
  color: var(--color-text);
}
.requirement-grid select[multiple] { width: 100%; height: auto; }
.requirement-grid select[multiple] option { padding: var(--space-1) var(--space-2); }
.requirement-errors { grid-column: 1 / -1; min-height: 0; border-bottom: 0; }
@media (max-width: 47.99rem) {
  .requirement-grid { grid-template-columns: minmax(0, 1fr) 3rem 3.5rem; column-gap: var(--space-2); }
  .requirement-grid-head > .requirement-desc { display: none; }
  .requirement-grid .requirement-text {
    display: block;
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--color-border);
  }
  .requirement-grid-row .requirement-text > * { display: block; min-height: 0; padding: 0; border-bottom: 0; }
  .requirement-grid-row:last-child .requirement-text { border-bottom: 0; }
  .requirement-grid-row > *,
  .requirement-grid select { min-height: var(--touch-target); }
  .requirement-grid select { font-size: var(--text-base); }
}
