/* ============================================================================
   Water Party Pack ERP — THE stylesheet.

   WP-1.4's styling contract: every color, font, space, radius and shadow in
   this app is defined ONCE, here, as a custom property (a "design token"), and
   every rule below references those tokens instead of naming a value again.
   No page ships its own colors or fonts — the auth pages included. That is
   what keeps the visual refresh parked in PROJECT_PLAN §9 a one-file job: a
   future chat re-tunes the :root block and the whole app follows.

   The palette and proportions are deliberately V1's (the accounting app), so
   the two read as the same family of software.
   ========================================================================= */

:root {
  color-scheme: light;

  /* ---- Color: surfaces and text ---- */
  --bg: #f4f6f8;
  --panel: #ffffff;
  --panel-alt: #f0f4f8;      /* table headers, section strips */
  --panel-hover: #f7fafd;
  --panel-faint: #fafbfc;    /* days either side of the month on the calendar */
  --muted-faint: #b6bfc8;
  --ink: #1f2933;
  --muted: #6b7280;
  --line: #e3e8ee;

  /* ---- Color: brand + intent ----
     --accent-dark and --warn keep ONE value across both themes: they're each
     used as a solid fill behind fixed white text (button.primary:hover,
     button.warn, and the always-white .tab.active pill), a role that wants
     the same rich, saturated color regardless of what's around it. Where
     that same hue needs to work as TEXT sitting directly on a themed panel —
     th.sortable:hover, .tag.current, .notice.policy, the override-note — the
     rules below reach for --accent / --warn-ink instead, which DO retune per
     theme. */
  --accent: #1d6fb8;         /* lake blue */
  --accent-dark: #155488;
  --accent-soft: #eef5fb;
  --accent-line: #cfe3f4;
  --focus-ring: #bcd9f0;
  --good: #117a4d;
  --good-bg: #e7f5ee;
  --good-line: #bfe3ce;
  --warn: #8a5a00;
  --warn-bg: #fff3da;
  --warn-line: #e8c55a;
  --warn-ink: #8a5a00;       /* --warn's text-on-panel twin — see note above */
  --bad: #b42318;
  --bad-bg: #fdecea;
  --bad-line: #f4c7c1;

  /* ---- Color: reservation states ----
     One token per state in the WP-1.3 machine, so the calendar, the list
     badges and the detail header can never disagree about what "confirmed"
     looks like. Ordered as the state machine runs: grey while it's only an
     idea, amber while money is owed, blue once it's real, green once the
     trailer is physically out, then settled greys. */
  --st-draft: #7c8794;
  --st-pending_payment: #c08a1e;
  --st-confirmed: #1d6fb8;
  --st-waiver_signed: #4f5bd5;
  --st-checked_out: #117a4d;
  --st-returned: #0f766e;
  --st-closed: #52606d;
  --st-cancelled: #b42318;
  /* Downtime isn't a reservation state, but it shares the calendar. One
     token since BD-20 collapsed maintenance/blackout into one block; grey
     rather than a warning color, because a blocked-out day is a fact about
     the trailer, not an error. */
  --st-downtime: #6b7280;
  --st-expired: #8a6d3b;
  --st-payment_processing: #0e7490; /* ACH in flight — money moving, not landed */
  /* Counter pills ride on top of a colored bar, so they're defined as
     translucent white rather than as their own colors — they stay legible
     over every state color above without needing one token per state. */
  --bar-count-bg: rgba(255, 255, 255, .22);
  --bar-count-edge-bg: rgba(255, 255, 255, .42);

  /* ---- Typography ---- */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, Menlo, monospace;
  --fs-xs: 11px;
  --fs-sm: 13px;
  --fs-base: 15px;
  --fs-md: 17px;
  --fs-lg: 19px;
  --fs-xl: 24px;
  --lh: 1.45;

  /* ---- Space, shape, depth ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 14px;
  --sp-4: 18px;
  --sp-5: 24px;
  --radius: 8px;
  --radius-sm: 6px;
  --radius-pill: 999px;
  --shadow: 0 1px 3px rgba(0, 0, 0, .08), 0 1px 2px rgba(0, 0, 0, .04);
  --shadow-lifted: 0 12px 40px rgba(15, 30, 45, .25);
  --content-width: 1080px;
}

/* ---- Dark palette ----
   WP-1.4r: hand-tuned, not inverted. Reservation-state and downtime tokens
   (line ~53 on) are deliberately absent from both blocks below — every one
   of them is a saturated fill behind fixed white text (badges, calendar
   bars), so the same color reads correctly on either background and the
   calendar can't disagree with itself about what "confirmed" looks like
   depending on which theme is active. Same reasoning kept the sandbox
   banner and the topbar's lake-blue fill out of here.

   Two selectors carry the identical property list on purpose: the media
   query is the OS-default path (no explicit choice saved yet), and the
   attribute selector is the toggle's explicit override — app.js sets
   `data-theme` on <html> and both need the same values so neither path
   reads as "more correct" than the other. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #10151b;
    --panel: #1a232c;
    --panel-alt: #212c37;
    --panel-hover: #26313c;
    --panel-faint: #161e25;
    --muted-faint: #566270;
    --ink: #e8eef4;
    --muted: #9aa7b3;
    --line: #2c3944;

    --accent: #3f93d6;
    --accent-soft: #16293a;
    --accent-line: #1f3b52;
    --focus-ring: #4f8fc4;
    --good: #35c98d;
    --good-bg: #123423;
    --good-line: #1f4a34;
    --warn-bg: #3a2c08;
    --warn-line: #7a5a1c;
    --warn-ink: #e0aa3e;
    --bad: #f2685c;
    --bad-bg: #3a1512;
    --bad-line: #7a2c24;

    --shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 1px 2px rgba(0, 0, 0, .3);
    --shadow-lifted: 0 12px 40px rgba(0, 0, 0, .6);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #10151b;
  --panel: #1a232c;
  --panel-alt: #212c37;
  --panel-hover: #26313c;
  --panel-faint: #161e25;
  --muted-faint: #566270;
  --ink: #e8eef4;
  --muted: #9aa7b3;
  --line: #2c3944;

  --accent: #3f93d6;
  --accent-soft: #16293a;
  --accent-line: #1f3b52;
  --focus-ring: #4f8fc4;
  --good: #35c98d;
  --good-bg: #123423;
  --good-line: #1f4a34;
  --warn-bg: #3a2c08;
  --warn-line: #7a5a1c;
  --warn-ink: #e0aa3e;
  --bad: #f2685c;
  --bad-bg: #3a1512;
  --bad-line: #7a2c24;

  --shadow: 0 1px 3px rgba(0, 0, 0, .45), 0 1px 2px rgba(0, 0, 0, .3);
  --shadow-lifted: 0 12px 40px rgba(0, 0, 0, .6);
}

* { box-sizing: border-box; }

html {
  background: var(--bg);
  /* iOS inflates text after an orientation change unless told not to. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
/* No 300 ms tap delay and no double-tap zoom on controls; pinch-zoom on the
   page itself stays available (never maximum-scale=1 — that's an
   accessibility regression, not a fix). */
button, .tab, a, input, select, textarea, label, summary { touch-action: manipulation; }

body {
  margin: 0;
  font: var(--fs-base)/var(--lh) var(--font-sans);
  color: var(--ink);
  background: var(--bg);
}

.hidden { display: none !important; }
.muted { color: var(--muted); }
.amt { text-align: right; font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }

/* ============================ Shell ====================================== */

/* Sandbox banner — only rendered when /api/env says this process is pointed
   at sandbox data. Amber, above the topbar, so it pushes the page down
   rather than being something you could scroll past. V1's pattern. */
.env-banner {
  padding: 7px var(--sp-4);
  text-align: center;
  background: repeating-linear-gradient(135deg, #f6c453, #f6c453 14px, #edb735 14px, #edb735 28px);
  color: #3d2f00;
  font-size: var(--fs-sm);
  border-bottom: 1px solid #d9a520;
}

.topbar {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-3) var(--sp-5);
  background: var(--accent);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 50;
}
.brand { display: flex; align-items: center; gap: var(--sp-3); line-height: 1.1; }
.brand-text { display: flex; flex-direction: column; }
/* The uploaded logo beside the name (Connor, 2026-09-24): the bar's height
   sets the size, so a tall or wide file never grows the header. The file is
   placed straight on the bar — no box or background behind it (it is a
   transparent, circular PNG; Connor, same day). */
.brand-logo { width: 40px; height: 40px; object-fit: contain; flex: 0 0 auto; }
.brand-name { font-size: var(--fs-lg); font-weight: 700; letter-spacing: .01em; }
.brand-sub { font-size: 12px; color: #dceaf6; margin-top: 2px; }
/* Nine tabs since WP-2.4's "Integrations" — one more than the topbar could
   fit at 1200px, which pushed the whole page sideways (UI_CONVENTIONS: the
   page never scrolls sideways). Wrapping is the fix that keeps working as
   sections are added, rather than a width tuned to today's tab count. */
.topbar { flex-wrap: wrap; }
.topbar nav { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.topbar .who { margin-left: auto; display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-sm); color: #dceaf6; }
.topbar .who a { color: #fff; }

.tab {
  background: transparent;
  color: #dceaf6;
  border: 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
}
.tab:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.tab.active { background: #fff; color: var(--accent-dark); font-weight: 600; }

/* ---- Six sections, with NetSuite-style hover menus (wp-1.4r3, BD-105) ---- */
.navitem { position: relative; }
.navitem.active > .tab { background: #fff; color: var(--accent-dark); font-weight: 600; }
/* A section with more under it says so, quietly. */
.navitem.has-menu > .tab::after { content: " ▾"; opacity: .6; font-size: 11px; }
.navmenu {
  display: none; position: absolute; top: calc(100% + 2px); left: 0; z-index: 60;
  min-width: 190px; padding: var(--sp-1);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lifted);
}
.navitem.open > .navmenu { display: block; }
.navmenu-item {
  display: block; width: 100%; text-align: left;
  background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
  font: inherit; font-size: var(--fs-sm); color: var(--ink); cursor: pointer;
}
.navmenu-item:hover, .navmenu-item:focus-visible { background: var(--panel-hover); }

/* Recent records (wp-4.2r round 2): the clock before Today and its list. */
.tab-icon { display: inline-flex; align-items: center; padding: var(--sp-2); }
.navitem.recent.has-menu > .tab::after { content: ""; }
.recent-menu { min-width: 300px; max-width: 360px; max-height: 70vh; overflow-y: auto; }
.recent-item { display: flex; flex-direction: column; gap: 1px; }
.recent-label { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.recent-meta { color: var(--muted); font-size: 12px; }
.recent-empty { margin: 0; padding: var(--sp-2) var(--sp-3); color: var(--muted); font-size: var(--fs-sm); }

/* The sub-tab strip under the header: the section you are in, and the screens
   it holds. A second bar, deliberately quieter than the blue one above it. */
.subnav {
  display: flex; gap: var(--sp-1); align-items: center;
  padding: var(--sp-2) var(--sp-5);
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.subnav .tab { color: var(--muted); font-size: var(--fs-sm); }
.subnav .tab:hover { background: var(--panel-hover); color: var(--ink); }
.subnav .tab.active { background: var(--accent-soft); color: var(--accent-dark); font-weight: 600; }

main { max-width: var(--content-width); margin: var(--sp-5) auto; padding: 0 var(--sp-5); }
main.wide { max-width: none; } /* the bank reconciliation's two columns use the whole window (Connor, 2026-09-24) */
h2 { font-size: var(--fs-md); margin: 0 0 var(--sp-4); }
h3 { font-size: var(--fs-base); margin: 0 0 var(--sp-3); }
.hint { color: var(--muted); font-size: var(--fs-sm); margin: calc(-1 * var(--sp-2)) 0 var(--sp-4); }
.empty { padding: 28px; text-align: center; color: var(--muted); }

/* ============================ Controls =================================== */

button {
  font: inherit;
  cursor: pointer;
}
button.primary {
  background: var(--accent); color: #fff; border: 0;
  padding: 9px 16px; border-radius: var(--radius-sm); font-weight: 600;
}
button.primary:hover { background: var(--accent-dark); }
button.primary:disabled { background: #9fb6c8; cursor: not-allowed; }
button.secondary {
  background: var(--panel); color: var(--accent); border: 1px solid var(--accent);
  padding: 9px 16px; border-radius: var(--radius-sm); font-weight: 600;
}
button.secondary:hover { background: var(--accent-soft); }
button.secondary:disabled { opacity: .5; cursor: not-allowed; }
/* The override button (D12 policy tier). Amber, never red: overriding a
   policy rule is a judgement call an operator is allowed to make, not an
   error — red is reserved for the hard tier, which has no button at all. */
button.warn {
  background: var(--warn); color: #fff; border: 0;
  padding: 9px 16px; border-radius: var(--radius-sm); font-weight: 600;
}
button.warn:hover { filter: brightness(1.1); }
button.link {
  background: none; border: 0; color: var(--accent);
  padding: var(--sp-1) var(--sp-2); font-size: 14px;
}
button.link:hover { text-decoration: underline; }
button.link:disabled { color: var(--muted); cursor: not-allowed; }
button.link:disabled:hover { text-decoration: none; }
button.link.danger { color: var(--bad); }

input, select, textarea {
  font: inherit;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--ink);
}
textarea { width: 100%; resize: vertical; line-height: var(--lh); }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--focus-ring);
  border-color: var(--accent);
}
label { display: inline-flex; flex-direction: column; gap: var(--sp-1); font-size: var(--fs-sm); color: var(--muted); }
label.block { display: flex; }
label > input, label > select, label > textarea { color: var(--ink); }
.req { color: var(--bad); font-weight: 700; }
.field-help { font-size: 12px; color: var(--muted); margin-top: var(--sp-1); }

.toolbar {
  display: flex; align-items: flex-end; gap: var(--sp-3); flex-wrap: wrap;
  background: var(--panel); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: var(--sp-4);
}
.toolbar label { flex-direction: row; align-items: center; gap: var(--sp-2); }
.toolbar .spacer { margin-left: auto; }

.form-row { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-3); flex-wrap: wrap; }
.form-row .grow { flex: 1; min-width: 220px; }
.form-row .grow input, .form-row .grow select, .form-row label input, .form-row label select { width: 100%; }

.msg { font-size: 14px; }
.msg.ok { color: var(--good); }
.msg.err { color: var(--bad); }

/* ============================ Searchable combo =========================== */
/* docs/UI_CONVENTIONS.md — the house picker. The list is position:fixed and
   placed by JS against the input, so a modal or a table's overflow can't clip
   it (a lesson from V1). */

.combo { position: relative; }
.combo-input { width: 100%; }
.combo-list {
  position: fixed; z-index: 250;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-shadow: var(--shadow);
  max-height: 260px; overflow-y: auto;
  max-width: min(440px, 92vw);
}
.combo-item {
  display: flex; align-items: baseline; gap: var(--sp-3);
  padding: 8px 10px; cursor: pointer; white-space: nowrap;
}
.combo-item:hover, .combo-item.hl { background: var(--accent-soft); }
/* The label is the flexible part: a long name shrinks rather than shoving
   the hint off the end of the row. */
.combo-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.combo-hint { flex: 0 0 auto; margin-left: auto; color: var(--muted); font-size: var(--fs-sm); }
.combo-empty { padding: var(--sp-2) 10px; color: var(--muted); font-size: var(--fs-sm); }

/* Inline, next-to-the-field validation in human words. */
.field-error { display: block; color: var(--bad); font-size: var(--fs-sm); margin-top: var(--sp-1); }

/* Money: right-aligned, columns line up digit for digit. */
input.money { text-align: right; font-variant-numeric: tabular-nums; }

/* ============================ Cards & panels ============================= */

.panel {
  background: var(--panel); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: var(--sp-4); margin-bottom: var(--sp-4);
}
.panel-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-3);
}
.panel-head h3 { margin: 0; }

.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); margin-bottom: var(--sp-4); }
/* Bank reconciliation (WP-3.3 Stage C): what the ERP says beside what the bank says. */
.rec-columns { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--sp-3); align-items: start; }
.rec-columns > .panel { margin-bottom: 0; min-width: 0; }
@media (max-width: 1100px) { .rec-columns { grid-template-columns: 1fr; } }
.card { background: var(--panel); border-radius: var(--radius); padding: var(--sp-4); box-shadow: var(--shadow); }
.card .label { color: var(--muted); font-size: var(--fs-sm); }
.card .value { font-size: var(--fs-xl); font-weight: 700; margin-top: var(--sp-1); }

/* ============================ Tables ===================================== */

table.grid { width: 100%; border-collapse: collapse; background: var(--panel); }
table.grid { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.grid th, .grid td { text-align: left; padding: 10px var(--sp-3); border-bottom: 1px solid var(--line); }
.grid th { background: var(--panel-alt); font-size: var(--fs-sm); color: var(--muted); font-weight: 600; }
.grid tbody tr.clickable { cursor: pointer; }
.grid tbody tr.clickable:hover { background: var(--panel-hover); }
.grid td.actions { text-align: right; white-space: nowrap; width: 1%; }

/* Wide tables scroll inside their own box rather than widening the page. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ============================ Badges ===================================== */

/* Sortable list headers (NetSuite-style): the arrow only appears on the
   active column; the reserved space stops the header from jiggling. */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--accent-dark); }
th.sortable .sort-arrow { display: inline-block; min-width: 14px; font-size: 9px; padding-left: 3px; color: var(--accent); }

.badge {
  display: inline-block; padding: 2px 9px; border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 600; color: #fff; white-space: nowrap;
}
/* One rule per state, each pointing at its token — see :root. */
.badge.draft { background: var(--st-draft); }
.badge.pending_payment { background: var(--st-pending_payment); }
.badge.confirmed { background: var(--st-confirmed); }
.badge.waiver_signed { background: var(--st-waiver_signed); }
.badge.checked_out { background: var(--st-checked_out); }
.badge.returned { background: var(--st-returned); }
.badge.closed { background: var(--st-closed); }
.badge.cancelled { background: var(--st-cancelled); }
.badge.downtime { background: var(--st-downtime); }
.badge.expired { background: var(--st-expired); }
.badge.payment_processing { background: var(--st-payment_processing); }
.badge.payment_failed { background: var(--st-cancelled); }

.tag {
  display: inline-block; padding: 1px 7px; border-radius: var(--radius-sm);
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .03em;
  text-transform: uppercase; color: var(--muted); background: var(--panel-alt);
}

/* ============================ Notices ==================================== */

/* The two tiers of D12, drawn so they can't be confused for one another:
   .notice.hard is red and carries no button anywhere near it; .notice.policy
   is amber and always sits directly above an override control. */
.notice {
  border-radius: var(--radius); padding: var(--sp-3) var(--sp-3);
  margin-bottom: var(--sp-3); font-size: 14px; line-height: 1.5;
}
.notice h4 { margin: 0 0 var(--sp-1); font-size: 14px; }
.notice ul { margin: var(--sp-1) 0 0; padding-left: var(--sp-4); }
.notice li { margin-bottom: 3px; }
.notice.hard { background: var(--bad-bg); border: 1px solid var(--bad-line); color: var(--bad); }
.notice.policy { background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-ink); }
.notice.info { background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--ink); }
.notice.ok { background: var(--good-bg); border: 1px solid var(--good-line); color: var(--good); }
.notice .override-box { margin-top: var(--sp-3); }
.notice .override-box textarea { color: var(--ink); background: var(--panel); }
.notice .override-actions { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); flex-wrap: wrap; }

/* ============================ Calendar =================================== */

.cal-head {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
.cal-title { font-size: var(--fs-md); font-weight: 700; min-width: 190px; }

.cal-legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3); font-size: 12px; color: var(--muted); }
.cal-legend span { display: inline-flex; align-items: center; gap: 5px; }
.cal-swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }

/* The month is a stack of week grids. Each week owns 7 columns; the day cells
   fill it from top to bottom as background and click target, and the bars are
   placed on top with grid-column spans — that span is what makes a five-day
   rental one bar instead of five chips. */
.cal-grid {
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); background: var(--line);
}
.cal-dow-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.cal-dow {
  background: var(--panel-alt); color: var(--muted);
  font-size: var(--fs-sm); font-weight: 600; text-align: center; padding: var(--sp-2) 0;
}

.cal-week {
  --week-min-h: 96px;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  /* Row 1 holds the date numbers; every later row is one lane of bars. */
  grid-template-rows: 22px;
  /* minmax, not a fixed height: bar rows sit at 20px, but the filler row at
     the bottom has to be free to grow to whatever keeps the week full-height. */
  grid-auto-rows: minmax(20px, auto);
  column-gap: 1px;
  row-gap: 2px;
  padding-bottom: 6px;
  background: var(--line);
  border-top: 1px solid var(--line);
}
.cal-day {
  /* grid-row is set inline per week — see calendar.js. */
  position: relative;
  background: var(--panel);
  cursor: pointer;
}
.cal-day:hover { background: var(--accent-soft); }
.cal-day.outside { background: var(--panel-faint); }
.cal-day.today { box-shadow: inset 0 0 0 2px var(--accent); }
/* BD-20: a day where every trailer is blocked out reads as greyed. */
.cal-day.blocked { background: var(--panel-alt); }
.cal-day.blocked:hover { background: var(--accent-soft); }

.cal-daynum {
  /* Positioned so it paints ABOVE the day cell: .cal-day is position:relative
     (for the hover "+"), and a positioned element covers a static sibling
     whatever the DOM order says. */
  position: relative;
  grid-row: 1; align-self: center;
  padding: 0 6px;
  font-size: 12px; font-weight: 600; color: var(--muted);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.cal-daynum.outside { color: var(--muted-faint); }
/* A quiet "+" on hover — the day cell is the book-this-day target. */
.cal-add {
  position: absolute; top: 1px; right: 5px;
  color: var(--accent); font-weight: 700; font-size: 12px; opacity: 0;
}
.cal-day:hover .cal-add { opacity: 1; }

/* wp-4.2r: the small availability calendar inside the Reschedule dialog
   (Connor, round 1). One month of the fleet; a day is free when any trailer
   is free, full when none is; the order's own days and the chosen range. */
.mini-cal { margin: var(--sp-2) 0 var(--sp-3); }
.mini-cal-head { display: flex; align-items: center; gap: var(--sp-2); font-weight: 600; font-size: var(--fs-sm); }
.mini-cal-head .link { padding: 0 6px; font-size: 18px; line-height: 1; }
.mini-cal-key { margin-left: auto; font-weight: 400; color: var(--muted); font-size: 12px; display: inline-flex; gap: 6px; align-items: center; }
.mini-cal-key i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; border: 1px solid var(--line); }
.mini-cal-key i.free { background: var(--good-bg); }
.mini-cal-key i.full { background: var(--bad-bg); }
.mini-cal-key i.mine { background: var(--accent-soft); border-color: var(--accent); }
.mini-cal-dow, .mini-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.mini-cal-dow span { text-align: center; font-size: 11px; color: var(--muted); padding: 2px 0; }
.mini-day {
  min-height: 30px; border: 1px solid var(--line); border-radius: 4px; background: var(--panel);
  font-size: 12px; font-variant-numeric: tabular-nums; color: var(--ink); cursor: pointer; padding: 0;
  touch-action: manipulation;
}
.mini-day.outside { border: 0; background: transparent; cursor: default; }
.mini-day.free { background: var(--good-bg); }
.mini-day.full { background: var(--bad-bg); }
.mini-day.mine { background: var(--accent-soft); border-color: var(--accent); }
.mini-day.past { background: var(--panel-faint); color: var(--muted-faint); cursor: default; }
.mini-day.chosen { box-shadow: inset 0 0 0 2px var(--accent); font-weight: 700; }
.mini-day.today { text-decoration: underline; }
.mini-day:not(.past):not(.outside):hover { filter: brightness(0.95); }
.mini-policy { font-size: var(--fs-sm); color: var(--warn-ink); background: var(--warn-bg); border: 1px solid var(--warn-line); border-radius: var(--radius); padding: 6px 10px; margin: 0 0 var(--sp-2); }
.mini-notes { margin: var(--sp-2) 0 0; padding-left: 18px; font-size: var(--fs-sm); }
.mini-notes li { margin: 2px 0; }

/* Keeps a bar-less week the same height as a busy one: row 1 is a fixed
   track, so without this the day cells have nothing to stretch them. */
.cal-week-filler {
  grid-column: 1 / 8;
  height: max(6px, calc(var(--week-min-h) - 22px - var(--lanes) * 22px));
  pointer-events: none;
}

/* One bar per booking or block, spanning its days. */
.cal-bar {
  position: relative;
  border: 0; padding: 0; margin: 0 1px;
  height: 18px; border-radius: 0;
  color: #fff; font-size: var(--fs-xs); line-height: 18px;
  text-align: left; overflow: hidden;
  display: block; width: calc(100% - 2px);
}
.cal-bar:hover { filter: brightness(1.12); }
/* Square where a run continues into the next week, rounded at its real ends —
   the Google Calendar tell for "this carries on". */
.cal-bar.starts { border-top-left-radius: 4px; border-bottom-left-radius: 4px; margin-left: 3px; }
.cal-bar.ends { border-top-right-radius: 4px; border-bottom-right-radius: 4px; margin-right: 3px; }

.cal-bar.draft { background: var(--st-draft); }
.cal-bar.pending_payment { background: var(--st-pending_payment); }
.cal-bar.confirmed { background: var(--st-confirmed); }
.cal-bar.waiver_signed { background: var(--st-waiver_signed); }
.cal-bar.checked_out { background: var(--st-checked_out); }
.cal-bar.returned { background: var(--st-returned); }
.cal-bar.closed { background: var(--st-closed); }
.cal-bar.downtime { background: var(--st-downtime); }

.bar-label {
  position: absolute; inset: 0 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  pointer-events: none;
}
.bar-label .chip-asset { font-weight: 700; opacity: .85; margin-right: 4px; }

/* The per-day counter: one cell per day of the span, number at the right of
   its own day. It sits above the label on a translucent pill so a long name
   can pass underneath and both stay readable. */
.bar-days {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: repeat(var(--span), 1fr);
  pointer-events: none;
}
.bar-day {
  justify-self: end; align-self: center;
  margin-right: 3px; padding: 0 4px;
  font-size: 9px; font-variant-numeric: tabular-nums;
  background: var(--bar-count-bg); border-radius: 3px;
}
/* First and last day of a run — the two that need someone to do something. */
.bar-day.edge { background: var(--bar-count-edge-bg); font-weight: 700; }

/* ============================ Detail screens ============================= */

.detail-head {
  display: flex; align-items: baseline; gap: var(--sp-3);
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.detail-head h2 { margin: 0; }
.detail-head .spacer { margin-left: auto; }

.kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-2) var(--sp-4); font-size: 14px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; }

/* The state-machine control strip: where a booking can go next, and nowhere
   else — the buttons ARE TRANSITIONS[state] from the server. */
.transitions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.transitions .final { color: var(--muted); font-size: var(--fs-sm); }

/* Event history — the append-only trail, oldest first. */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  display: flex; gap: var(--sp-3); padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line); font-size: 14px;
}
.timeline li:last-child { border-bottom: 0; }
.timeline .when { color: var(--muted); font-size: 12px; min-width: 140px; font-variant-numeric: tabular-nums; }
.timeline .what { flex: 1; }
.timeline .override-note {
  display: block; margin-top: var(--sp-1); padding: 7px 10px;
  background: var(--warn-bg); border-left: 3px solid var(--warn-line);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--fs-sm); color: var(--warn-ink);
}

/* Quote breakdown: the per-day price the reservation froze at booking time. */
.quote-day { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.quote-total { display: flex; justify-content: space-between; padding: var(--sp-2) 0; font-weight: 700; }
.quote-sub { display: flex; justify-content: space-between; padding: 3px 0; color: var(--muted); font-size: 14px; }

/* ============================ Settings =================================== */

.route-picks { display: flex; flex-direction: column; gap: var(--sp-2); }
.route-picks label { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-sm); color: var(--muted); }
.setting-row {
  display: grid; grid-template-columns: 1fr 200px auto;
  gap: var(--sp-3); align-items: start;
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--line);
}
.setting-row:last-child { border-bottom: 0; }
.setting-name { font-weight: 600; font-size: 14px; }
.setting-desc { color: var(--muted); font-size: var(--fs-sm); margin-top: 3px; }
.setting-meta { color: var(--muted); font-size: 12px; margin-top: var(--sp-1); }
.setting-row input { width: 100%; }
.setting-row select { width: 100%; }
/* wp-1.4r3: a gear checklist or a page of customer-facing copy doesn't fit a
   200px column, so those rows put the editor on its own full-width line under
   the name and explanation. */
.setting-row.wide { grid-template-columns: 1fr auto; }
.setting-row.wide > :nth-child(2) { grid-column: 1 / -1; }
.setting-row.wide > :nth-child(3) { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--sp-3); }
/* The Moment column lost the long description that used to hold it open
   (Connor, 2026-09-19 — it is hover help now), and the table promptly squeezed
   it to ~120px, wrapping every name over three to six lines. */
.grid th.flow-moment, .grid td.flow-moment { min-width: 210px; }

/* wp-1.4r3 round 2 (Connor): each settings group collapses to its heading, so
   a long screen can be read as six lines and opened where needed. <details>
   for the keyboard and screen-reader behaviour that comes free with it. */
details.panel.group > summary {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  cursor: pointer; list-style: none; margin: 0;
}
details.panel.group > summary::-webkit-details-marker { display: none; }
details.panel.group > summary h3 { margin: 0; }
/* The chevron turns with the group; ▸ / ▾ rather than the browser's default
   triangle, which sits outside the flex row and misaligns the heading. */
details.panel.group > summary h3::before {
  content: "\25B8"; display: inline-block; width: 1em; color: var(--muted);
  transition: transform .12s ease;
}
details.panel.group[open] > summary h3::before { transform: rotate(90deg); }
details.panel.group > summary:hover h3 { color: var(--accent-dark); }
details.panel.group[open] > summary { margin-bottom: var(--sp-3); }

/* The find-a-setting box: the widest thing on the screen, because typing into
   it is the fastest route to any of the 31. */
#set-find input[type="search"] { width: 100%; font-size: var(--fs-md); padding: 10px 12px; }
.setting-row .msg { font-size: var(--fs-sm); }

/* A row of checkboxes (the block-out trailer picker). */
.check-row { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); margin-top: var(--sp-1); }
.check-row .checkbox-row { color: var(--ink); font-size: 14px; }

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

.modal { position: fixed; inset: 0; z-index: 200; background: rgba(15, 30, 45, .35); display: flex; align-items: center; justify-content: center; padding: var(--sp-3); }
.modal.hidden { display: none; }
.modal-box {
  background: var(--panel); border-radius: var(--radius); padding: var(--sp-5);
  width: min(520px, 100%); max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lifted);
}
.modal-box h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-md); }
.modal-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-3); margin-top: var(--sp-4); }
.modal-actions .msg { margin-right: auto; }

/* ---- Field-level help (docs/UI_CONVENTIONS.md, wp-1.4r2) ------------------
   A small "?" beside a label; hovering/focusing it for ~1s shows the card.
   Content comes from the field_help table (public/js/ui.js owns the fetch +
   the hover delegation) — a label only gets the icon when help exists for
   it, so this stays selective rather than cluttering every field. */
.help-topic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 4px;
  border-radius: var(--radius-pill); border: 1px solid var(--line);
  background: var(--panel-alt); color: var(--muted);
  font-size: 10px; font-weight: 700; line-height: 1; cursor: help;
  vertical-align: middle;
}
.help-topic:hover, .help-topic:focus-visible { border-color: var(--accent); color: var(--accent); outline: none; }
.help-card {
  position: fixed; z-index: 300; width: min(300px, calc(100vw - 2 * var(--sp-3)));
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lifted); padding: var(--sp-3); font-size: var(--fs-sm);
  color: var(--ink); line-height: var(--lh);
}
.help-card.hidden { display: none; }
/* Screen headings inside Settings → Field help, which lists the catalog
   grouped by the screen each field lives on. */
.fh-screen {
  margin: var(--sp-4) 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.fh-screen:first-of-type { margin-top: var(--sp-2); }

/* ============================ Rich-text editor =========================== */
/* WP-2.3 round 2: the shared template editor (public/js/richtext.js) — a
   formatting toolbar over a contenteditable box, with a code view showing
   the actual email HTML. Shared so WP-2.2's waiver templates inherit it. */

.rt { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel); }
.rt-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 6px; border-bottom: 1px solid var(--line);
  background: var(--panel-alt); border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.rt-spacer { flex: 1 1 auto; }
.rt-btn {
  padding: 4px 9px; font-size: 12px; line-height: 1.4;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.rt-btn:hover { background: var(--panel-hover); border-color: var(--accent-line); }
.rt-btn.active { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); }
.rt-var { font-size: 12px; padding: 3px 6px; max-width: 150px; }
.rt-rich, .rt-code {
  display: block; width: 100%; box-sizing: border-box;
  min-height: 180px; max-height: 380px; overflow-y: auto;
  padding: 10px 12px; border: 0; background: transparent; color: var(--ink);
}
.rt-rich:focus, .rt-code:focus { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.rt-rich p { margin: 0 0 var(--sp-3); }
.rt-rich a { color: var(--accent); }
.rt-code { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; resize: vertical; white-space: pre; }

/* The rendered-email preview: a light card that reads like an inbox, and
   deliberately does NOT inherit the app's link/paragraph styling, so what
   you see is closer to what the customer gets. */
.email-preview {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: var(--sp-4); background: var(--panel);
  max-height: 320px; overflow-y: auto; font-size: .92rem; line-height: 1.5;
}
.email-preview p { margin: 0 0 var(--sp-3); }
.email-preview hr { margin: var(--sp-4) 0; }
.email-meta {
  font-size: 12px; color: var(--muted); border: 1px solid var(--line);
  border-bottom: 0; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--panel-alt); padding: var(--sp-2) var(--sp-3);
}
.email-meta + .email-preview { border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
.email-meta dt { display: inline; font-weight: 600; }
.email-meta dd { display: inline; margin: 0 var(--sp-3) 0 4px; }

/* A record's email history — collapsed by default so it adds no page noise
   (Connor, round 2), the same posture as the field-help panel. */
.record-comms > summary {
  cursor: pointer; font-size: .88rem; color: var(--accent);
  padding: var(--sp-2) 0; list-style: none;
}
.record-comms > summary::-webkit-details-marker { display: none; }
.record-comms > summary::before { content: "▸ "; }
.record-comms[open] > summary::before { content: "▾ "; }

/* ============================ Auth pages ================================= */
/* login / setup / login-history. They live on the same tokens as the app —
   that's the styling contract: no page defines its own colors. */

.auth-wrap { min-height: 100vh; min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: var(--sp-5); }
.auth-card {
  width: 100%; max-width: 400px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 10px;
  padding: 32px; box-shadow: var(--shadow);
}
.auth-card.wide { max-width: 640px; }
.auth-card h1 { font-size: 1.3rem; margin: 0 0 var(--sp-1); }
.auth-card h2 { font-size: 1.05rem; margin: 28px 0 var(--sp-1); }
.auth-card .brand-sub { color: var(--muted); font-size: .85rem; margin: 0 0 var(--sp-5); }
.auth-card p.help { color: var(--muted); font-size: .9rem; line-height: 1.4; }
.auth-card label { display: block; font-size: .85rem; font-weight: 600; color: var(--ink); margin: var(--sp-4) 0 6px; }
.auth-card input[type="text"], .auth-card input[type="password"] {
  width: 100%; padding: 10px 12px; font-size: 1rem;
}
.auth-card button.primary { width: 100%; margin-top: 20px; padding: 11px; font-size: 1rem; }
.auth-card button.link { display: block; margin-top: var(--sp-3); padding: 0; font-size: .85rem; text-decoration: underline; }
.auth-card button.link.danger { display: inline; margin-top: 0; }
.auth-card .error {
  background: var(--bad-bg); color: var(--bad); border-radius: var(--radius-sm);
  padding: 10px 12px; font-size: .88rem; margin-top: var(--sp-4);
}
.checkbox-row {
  /* flex-direction has to be stated: the base `label` rule sets `column`, and
     a class selector overriding `display` doesn't undo it — which is what
     stacked every checkbox above its own text. */
  display: flex; flex-direction: row; align-items: center;
  gap: var(--sp-2); margin: var(--sp-4) 0 0;
  font-size: .85rem; font-weight: 400; color: var(--muted); cursor: pointer;
}
.checkbox-row input[type="checkbox"] { margin: 0; width: auto; flex: 0 0 auto; }
.qr-box { display: flex; justify-content: center; margin: var(--sp-4) 0; }
/* The account roles group (BD-110): nine tick boxes read as a wall without a
   boundary and a heading saying what they have in common. */
.role-set {
  margin: var(--sp-4) 0 0; padding: var(--sp-3) var(--sp-4) var(--sp-4);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.role-set > legend { padding: 0 var(--sp-2); font-size: .8rem; color: var(--muted); }
.role-set .checkbox-row:first-child { margin-top: 0; }
.qr-box svg { border: 1px solid var(--line); border-radius: var(--radius-sm); }
.secret-box {
  font-family: var(--font-mono); background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 10px 12px; word-break: break-all;
  font-size: .9rem; margin-bottom: var(--sp-2);
}
.recovery-codes {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2);
  font-family: var(--font-mono); background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--sp-4); margin: var(--sp-4) 0;
}
table.history { width: 100%; border-collapse: collapse; font-size: .88rem; }
table.history th, table.history td { text-align: left; padding: var(--sp-2) 10px; border-bottom: 1px solid var(--line); }
.tag.ok { background: var(--good-bg); color: var(--good); }
.tag.fail { background: var(--bad-bg); color: var(--bad); }
.tag.current { background: var(--accent-soft); color: var(--accent-dark); }
.empty-state { color: var(--muted); font-size: .88rem; }

/* ============================================================================
   Narrow screens / phones.

   Per Connor (PROJECT_PLAN WP-1.4): every screen is BUILT responsive and the
   builder chat verifies it at a phone viewport; his own first phone pass is
   batched into WP-3.1. The calendar is the screen that needs the most help —
   a 7-column month grid on a 375px screen gives each day ~50px, so day cells
   shrink and chips drop to a color bar with the trailer initial.
   ========================================================================= */
@media (max-width: 760px) {
  /* ---- The phone pass (Connor, 2026-09-16, first real phone test) ----
     1. iOS Safari zooms the page into any field whose text is under 16px
        and leaves it zoomed — "everything kept resizing". 16px is the
        documented threshold, so every field is 16px on a phone; the page
        stays pinch-zoomable.
     2. Ten tabs wrapped into three rows plus a fourth for the account
        controls: a third of the screen before any content. Now one
        scrollable strip (industry pattern), sticky, with the active tab
        kept in view by app.js.
     3. Modals become bottom sheets: full width, scroll inside, safe-area
        padding above the home indicator.
     4. Every real button is at least 44px tall. */
  input, select, textarea { font-size: 16px; }
  button.primary, button.secondary, button.warn { min-height: 44px; }

  .topbar {
    flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3) 0;
    padding-top: max(var(--sp-2), env(safe-area-inset-top));
    position: sticky; top: 0;
  }
  .brand { flex: 1 1 auto; min-width: 0; }
  .brand-name { font-size: var(--fs-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .brand-sub { display: none; }
  /* Brand and the account controls share the first row; the tab strip is the
     second. "Login history" (an audit page you read at a desk) and the
     signed-in name (you know who you are) step aside so the row fits 375px. */
  .topbar .who { margin-left: auto; flex: 0 0 auto; gap: var(--sp-1); justify-content: flex-end; }
  .topbar .who #whoami, .topbar .who a { display: none; }
  .topbar nav {
    order: 3; flex: 1 0 100%;
    flex-wrap: nowrap; gap: 4px;
    overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; scroll-snap-type: x proximity;
    margin: 0 calc(-1 * var(--sp-3)); padding: 2px var(--sp-3) var(--sp-2);
  }
  .topbar nav::-webkit-scrollbar { display: none; }
  .tab { flex: 0 0 auto; white-space: nowrap; min-height: 40px; padding: 7px 12px; font-size: var(--fs-sm); scroll-snap-align: start; }
  .navitem { flex: 0 0 auto; scroll-snap-align: start; }
  /* No hover on a phone, so no hover menus: a tap on a section goes to its
     first screen and the strip below offers the rest. A menu that opened on
     tap would swallow the tap that meant to navigate. */
  .navmenu { display: none !important; }
  .navitem.has-menu > .tab::after { content: ""; }
  /* …except the recent-records list, which opens on a tap and goes nowhere
     by itself, so there is no tap to swallow. */
  .navitem.recent.open > .navmenu { display: block !important; max-width: 92vw; }

  /* The sub-tab strip scrolls sideways, exactly like the one above it, rather
     than stacking into one full-width button per line. */
  .subnav {
    padding: var(--sp-2) var(--sp-3);
    overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; scroll-snap-type: x proximity;
    position: sticky; top: 0; z-index: 5;
  }
  .subnav::-webkit-scrollbar { display: none; }
  .subnav .tab { flex: 0 0 auto; }

  /* A screen's OWN row of parts (Accounting's six, Promotions' two) is the
     same thing one level down, so it scrolls the same way. Without this the
     shell's `.toolbar { flex-direction: column }` turned Accounting's seven
     buttons into a 434px wall — 74% of a 375px screen before a single number
     (Connor, 2026-09-18). */
  .toolbar.subtabs {
    flex-direction: row; flex-wrap: nowrap; align-items: center;
    overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; scroll-snap-type: x proximity;
    margin: 0 calc(-1 * var(--sp-3)); padding: 0 var(--sp-3) var(--sp-1);
  }
  .toolbar.subtabs::-webkit-scrollbar { display: none; }
  .toolbar.subtabs > button { flex: 0 0 auto; scroll-snap-align: start; }
  .toolbar.subtabs .spacer { display: none; }

  main { padding: 0 var(--sp-3) max(var(--sp-3), env(safe-area-inset-bottom)); margin: var(--sp-3) auto; }
  h2 { font-size: var(--fs-base); }
  .panel { padding: var(--sp-3); margin-bottom: var(--sp-3); }
  .detail-head { flex-wrap: wrap; gap: var(--sp-2); }

  .cards { grid-template-columns: 1fr; }
  .toolbar { flex-direction: column; align-items: stretch; }
  .toolbar label { flex-direction: column; align-items: stretch; }
  .toolbar .spacer { margin-left: 0; }
  .form-row { flex-direction: column; gap: 10px; }
  .form-row label, .form-row .grow { width: 100%; }

  .kv { grid-template-columns: 1fr; gap: 2px var(--sp-3); }
  .kv dd { margin-bottom: var(--sp-2); }

  .setting-row { grid-template-columns: 1fr; }
  .setting-row input { width: 100%; }

  .timeline li { flex-direction: column; gap: 2px; }
  .timeline .when { min-width: 0; }

  /* Wide tables keep a floor width and scroll inside their wrapper. */
  .table-scroll table { min-width: 560px; }

  /* Calendar: tighter cells and shorter bars. The customer name stays on a
     bar (a multi-day bar has room for it, unlike the old per-day chips) but
     drops on a single-day bar, where only the counter fits. */
  .cal-week { --week-min-h: 68px; grid-template-rows: 18px; grid-auto-rows: minmax(15px, auto); row-gap: 2px; }
  .cal-week-filler { height: max(4px, calc(var(--week-min-h) - 18px - var(--lanes) * 17px)); }
  .cal-daynum { font-size: 10px; padding: 0 3px; }
  .cal-bar { height: 13px; line-height: 13px; font-size: 9px; }
  .bar-label { inset: 0 2px; }
  .bar-day { font-size: 8px; padding: 0 2px; margin-right: 2px; }
  .cal-title { min-width: 0; font-size: var(--fs-base); }
  .cal-head { gap: var(--sp-2); }

  /* The sandbox banner's database path is five wrapped lines on a phone and
     the word SANDBOX is the part that matters. (Never shown in production.) */
  .env-detail { display: none; }

  /* Field-flow touch targets: a combo row and a calendar day are both things
     you tap standing at a boat ramp. */
  .combo-item { min-height: 44px; align-items: center; }
  .cal-day { min-height: 62px; }

  /* Bottom sheet. */
  .modal { align-items: flex-end; padding: 0; }
  .modal-box {
    width: 100%; max-height: 92vh; max-height: 92dvh;
    border-radius: var(--radius) var(--radius) 0 0;
    padding: var(--sp-4) var(--sp-4) max(var(--sp-4), env(safe-area-inset-bottom));
    overscroll-behavior: contain;
  }
  .modal-actions { flex-wrap: wrap; }
  .modal-actions button { min-height: 44px; }
  .auth-card { padding: 22px; }
  .recovery-codes { grid-template-columns: 1fr; }
}

/* ============================ E-sign (WP-2.2) ============================ */
/* The rendered document, as the signer and the ERP preview see it. Reads
   like a printed page: measured line length, real headings, and the legal
   blocks set off from the prose. Same tokens as everything else. */
.es-doc { line-height: 1.55; font-size: var(--fs-base); }
.es-doc h1 { font-size: var(--fs-xl); margin: 0 0 var(--sp-3); }
.es-doc h2 { font-size: var(--fs-lg); margin: var(--sp-5) 0 var(--sp-2); }
.es-doc h3 { font-size: var(--fs-md); margin: var(--sp-4) 0 var(--sp-2); }
.es-doc p { margin: 0 0 var(--sp-3); }
.es-doc .es-block {
  display: block; margin: var(--sp-3) 0; padding: var(--sp-3);
  border: 1px dashed var(--accent-line); border-radius: var(--radius-sm); background: var(--accent-soft);
}
.es-doc .es-block.es-initials {
  display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0 4px; padding: 3px 8px;
  vertical-align: middle; font-size: var(--fs-sm);
}
.es-doc .es-block.es-absent { border-style: dotted; color: var(--muted); }
.es-doc .es-slot-label { font-size: var(--fs-sm); font-weight: 600; color: var(--muted); }
.es-doc .es-input-line { margin-top: var(--sp-1); }
/* Checkbox and choice lines: the box and its words on ONE line (Connor, 2026-09-04). */
.es-doc .es-input-line[data-es-kind="checkbox"], .es-doc .es-input-line[data-es-kind="choice"] { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.es-doc .es-input-line[data-es-kind="checkbox"] .es-check, .es-doc .es-input-line[data-es-kind="choice"] .es-check { display: inline-flex; flex-direction: row; align-items: flex-start; gap: var(--sp-2); margin: 0; font-size: var(--fs-base); }
.es-doc .es-input-line[data-es-kind="checkbox"] .es-check input, .es-doc .es-input-line[data-es-kind="choice"] .es-check input { margin-top: 3px; flex: 0 0 auto; }
.es-doc .es-input-line[data-es-kind="checkbox"] .es-check input, .es-doc .es-input-line[data-es-kind="choice"] .es-check input { margin: 0; width: 20px; height: 20px; }
/* A blank autofill field asked inline as a question. */
.es-doc .es-block.es-inline { display: inline-block; padding: 2px 8px; margin: 0 2px; }
.es-doc .es-block.es-inline .es-input-line { display: inline-flex; align-items: center; gap: var(--sp-2); margin: 0; }
.es-doc .es-block.es-inline input[type="text"] { width: 220px; max-width: 60vw; }
.es-consent-moved { margin-bottom: var(--sp-4); }
.es-consent-moved .es-block { margin: 0; }
.grid.answers td:first-child { max-width: 480px; white-space: normal; }
.grid.answers td:last-child { white-space: normal; word-break: break-word; }
.es-doc .es-input-line input[type="text"], .es-doc .es-input-line input[type="date"] { width: 100%; max-width: 420px; }
.es-doc .es-in-initials { width: 70px; text-transform: uppercase; text-align: center; font-weight: 700; }
.es-doc .es-who { font-size: var(--fs-sm); color: var(--muted); }
.es-doc input.waived { opacity: 0.55; }
.es-doc .es-disclosure { font-size: var(--fs-sm); color: var(--muted); margin-bottom: var(--sp-2); }
.es-doc .es-consent-line { font-size: var(--fs-sm); }
.es-doc .es-sig-img { display: block; max-width: 320px; max-height: 120px; background: #fff; border-bottom: 1px solid var(--ink); }
.es-doc .es-sig-meta { font-size: var(--fs-sm); color: var(--muted); margin: var(--sp-1) 0 0; }
/* flex-direction: row is explicit — the app's global label rule stacks a
   label's children in a column, which put every checkbox above its words. */
.es-check { display: flex; flex-direction: row; align-items: flex-start; gap: var(--sp-2); font-size: var(--fs-sm); color: var(--ink); cursor: pointer; }
.es-check input { margin-top: 3px; width: auto; }
.es-check.big { font-size: var(--fs-base); margin: var(--sp-4) 0; padding: var(--sp-3); border: 1px solid var(--line); border-radius: var(--radius-sm); }
.es-check.big input { width: 20px; height: 20px; }
.es-disclosure-box { padding: var(--sp-3); background: var(--panel-alt); border-radius: var(--radius-sm); font-size: var(--fs-sm); }

/* The public signing pages: one card, phone-first. */
.sign-body { background: var(--bg); }
.sign-wrap { max-width: 1100px; margin: 0 auto; padding: var(--sp-3); }
.sign-card {
  background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: var(--sp-5);
}
.sign-card h1 { font-size: var(--fs-lg); margin: 0 0 var(--sp-1); color: var(--accent); }
.sign-card h2 { font-size: var(--fs-md); margin: var(--sp-5) 0 var(--sp-2); }
.sign-sub { color: var(--muted); font-size: var(--fs-sm); margin: 0 0 var(--sp-2); }
.sign-fine { color: var(--muted); font-size: 12px; margin-top: var(--sp-4); line-height: 1.45; }
.sign-scroller {
  max-height: 72vh; overflow-y: auto; padding: var(--sp-5) var(--sp-6);
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel);
}
.sign-readline { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin: var(--sp-3) 0; font-size: var(--fs-sm); }
.sign-block { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line); }
.sign-actions { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); flex-wrap: wrap; }
.sign-actions .msg { flex: 1; color: var(--bad); font-size: var(--fs-sm); }
.sign-actions button.primary { min-height: 44px; min-width: 120px; margin-left: auto; }
.sigpad-wrap { border: 1px solid var(--line); border-radius: var(--radius-sm); background: #fff; touch-action: none; }
.sigpad { display: block; width: 100%; height: 170px; touch-action: none; cursor: crosshair; }
@media (max-width: 760px) {
  .sign-wrap { padding: var(--sp-2); }
  .sign-card { padding: var(--sp-4); }
  .sign-scroller { max-height: 60vh; padding: var(--sp-3); }
  .sigpad { height: 150px; }
}

/* Signature box (round 2): typed "Adopt and sign" by default, draw as the
   alternative. Round 3: the typed face is Great Vibes (SIL OFL, bundled in
   /fonts — served by us, nothing fetched from outside), so the preview and
   the adopted image match on every device. */
@font-face {
  font-family: "Great Vibes";
  src: url("/fonts/great-vibes-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
.sigbox { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel); }
.sig-tabs { display: flex; gap: 4px; padding: 6px; border-bottom: 1px solid var(--line); background: var(--panel-alt); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.sig-tab { padding: 6px 12px; font-size: var(--fs-sm); background: var(--panel); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer; }
.sig-tab.active { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); font-weight: 600; }
.sig-mode { padding: var(--sp-3); }
.sig-preview {
  min-height: 96px; display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); border-radius: var(--radius-sm); background: #fff; color: var(--ink);
}
.sig-preview.adopted { border-style: solid; border-color: var(--good-line); background: var(--good-bg); }
.sig-typed { font-size: 48px; line-height: 1.1; padding: 0 var(--sp-3); }
@media (max-width: 760px) { .sig-typed { font-size: 32px; } }

/* The order's User-Agreement share link: URL + copy, and the same link as a QR. */
.join-box { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-4); align-items: start; padding: var(--sp-3); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-alt); }
.join-url { display: flex; gap: var(--sp-2); }
.join-url input { flex: 1; font-family: var(--font-mono); font-size: var(--fs-sm); }
.join-qr svg { width: 120px; height: 120px; display: block; background: #fff; border-radius: var(--radius-sm); }
@media (max-width: 760px) { .join-box { grid-template-columns: 1fr; } .join-qr { justify-self: center; } }

/* ---- Integrations (WP-2.4) ------------------------------------------------
   The connection cards, the payload viewer, and the two tag variants the
   screen needs. Tokens only, like everything above. */
.integration-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--sp-3); }
.integration-card { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-alt); padding: var(--sp-3); }
.integration-card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-1); }
.integration-card-head h4 { margin: 0; font-size: var(--fs-md); }
.integration-card .grid { background: transparent; }
.grid.compact th, .grid.compact td { padding: 4px var(--sp-2); font-size: var(--fs-sm); border-bottom: none; }
.tag.warn { background: var(--warn-bg); color: var(--warn-ink); }
.tag.muted { background: var(--panel-faint); color: var(--muted); }
.text-bad { color: var(--bad); }
/* The exact message the other side sent — read, not edited, so it scrolls
   inside its own box rather than stretching the modal. */
.payload {
  font-family: var(--font-mono); font-size: 12px; line-height: 1.5; margin: var(--sp-2) 0 0;
  max-height: 320px; overflow: auto; padding: var(--sp-2);
  background: var(--panel-faint); border: 1px solid var(--line); border-radius: var(--radius-sm);
  white-space: pre-wrap; word-break: break-word;
}
/* Round 3 (2026-09-13): the log's filter chips, an unhandled-error row, and a
   checkbox label that sits inline with them. */
.chip { padding: 4px 10px; font-size: var(--fs-sm); }
.chip.active { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); font-weight: 600; }
.chk { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--muted); }
.grid tbody tr.row-error td { background: var(--bad-bg); }

/* ---- WP-3.0 day-of logistics: the public pickup page and the Today board ---- */
.pickup-wrap { max-width: 560px; }
.pickup-choices { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin: var(--sp-3) 0; }
.pickup-choice {
  min-height: 72px; font-size: var(--fs-lg); font-weight: 700;
  background: var(--panel); color: var(--ink); border: 2px solid var(--line); border-radius: var(--radius);
}
.pickup-choice.selected { border-color: var(--accent); background: var(--accent-soft); }
.pickup-actions { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-4); }
.pickup-big { min-height: 64px; font-size: var(--fs-lg); width: 100%; }
.pickup-instructions { padding: var(--sp-3); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-alt); }

.today-head { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.today-head h2 { margin: 0; }
.today-cards { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.today-card { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.today-card.provisional { border: 1px dashed var(--warn-line); }
.today-card.unpaid { border: 1px solid var(--warn-line); } /* BD-89: holding the trailer, not paid */
.today-card .who { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.today-card .who strong { font-size: var(--fs-md); }
.today-card .trailer { font-weight: 700; }
.today-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-sm); }
.today-steps li { display: flex; gap: var(--sp-2); align-items: baseline; }
.today-steps .done { color: var(--good); }
.today-steps .todo { color: var(--muted); }
.today-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: auto; padding-top: var(--sp-2); }
.today-actions button { min-height: 44px; }
.break-all { word-break: break-all; overflow-wrap: anywhere; }
.copy-box { width: 100%; min-height: 140px; font: inherit; font-size: var(--fs-sm); padding: var(--sp-3); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-alt); color: var(--ink); resize: vertical; }
@media (max-width: 640px) {
  .today-cards { grid-template-columns: 1fr; }
  .pickup-choices { grid-template-columns: 1fr; }
}
.order-chip { padding: 2px 8px; border: 1px solid var(--accent-line); border-radius: 999px; background: var(--accent-soft); font-size: var(--fs-sm); }

/* ---- WP-3.1 gear inspection (phone-first) + Copy-text ---- */
.insp-who { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; margin: -6px 0 var(--sp-3); }
.insp-checklist { display: grid; gap: 4px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.insp-item {
  display: flex; flex-direction: row; align-items: center; gap: var(--sp-3);
  min-height: 44px; padding: 0 var(--sp-3); margin: 0; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--panel-alt); font-size: var(--fs-base); font-weight: 400; color: var(--ink); cursor: pointer;
}
.insp-item input[type="checkbox"] { width: 22px; height: 22px; margin: 0; flex: 0 0 auto; accent-color: var(--accent); }
.insp-item:has(input:checked) { border-color: var(--good-line); background: var(--good-bg); }
.insp-photos { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
.insp-photo { position: relative; aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden; background: var(--panel-alt); border: 1px solid var(--line); }
.insp-photo-open { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; }
.insp-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.insp-photo-remove {
  position: absolute; top: 4px; right: 4px; width: 32px; height: 32px; padding: 0; border-radius: var(--radius-pill);
  border: 0; background: var(--bad); color: #fff; font-size: var(--fs-lg); line-height: 1; cursor: pointer;
}
.insp-photo-full { max-width: 100%; max-height: 70vh; display: block; margin: 0 auto; border-radius: var(--radius-sm); }
.insp-finish { display: flex; flex-direction: column; gap: var(--sp-2); }
.insp-finish button { min-height: 48px; }
.insp-chips { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-bottom: var(--sp-2); }
.insp-notes { padding: var(--sp-3); border-left: 3px solid var(--warn-line); background: var(--warn-bg); color: var(--ink); border-radius: var(--radius-sm); }
.insp-old { padding: var(--sp-2) 0; border-top: 1px solid var(--line); font-size: var(--fs-sm); }
.copy-text-block { padding: var(--sp-2) 0; border-top: 1px solid var(--line); }
.copy-text-block:first-of-type { border-top: 0; }
.items-editor .items-row { display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
.items-editor .items-row input { flex: 1 1 auto; min-width: 0; }
.items-editor .items-row button { min-width: 32px; }
/* The order's add-ons on a Today card (BD-127): what to load, what must come back. */
.today-addons { font-size: 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
/* Checkout questions (WP-4.2): the wording, its kind, required, then — on its
   own line under a pick-one question — the choices. */
.items-editor .question-row { flex-wrap: wrap; }
.items-editor .question-row .q-type { flex: 0 0 auto; width: auto; }
.items-editor .question-row .q-required-label { flex: 0 0 auto; margin: 0; gap: 5px; }
.items-editor .question-row .q-options { flex: 1 1 100%; }
@media (min-width: 641px) { .insp-finish { flex-direction: row; flex-wrap: wrap; } }

/* ---- In-page camera (WP-3.1 refinement; V1's pattern) ---- */
body.camera-open { overflow: hidden; }
.camera { position: fixed; inset: 0; z-index: 300; background: #000; display: flex; flex-direction: column; }
.camera video { flex: 1; width: 100%; min-height: 0; object-fit: cover; background: #000; }
.cam-flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; }
.cam-flash.flash-anim { animation: cam-flash .35s ease-out; }
@keyframes cam-flash { 0% { opacity: .9; } 100% { opacity: 0; } }
.cam-freeze { position: absolute; left: 0; right: 0; top: 0; bottom: 100px; width: 100%; height: auto; object-fit: cover; pointer-events: none; }
.cam-bar {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 100px; padding: var(--sp-4); padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom)); background: #000;
}
.cam-shutter { width: 72px; height: 72px; border-radius: 50%; border: 4px solid #fff; background: rgba(255, 255, 255, .25); padding: 0; cursor: pointer; }
.cam-shutter:active { transform: scale(.92); background: #fff; }
.cam-done { position: absolute; left: var(--sp-4); top: 50%; transform: translateY(-50%); background: none; border: 1px solid #fff; color: #fff; padding: 8px 14px; border-radius: var(--radius-sm); min-height: 44px; font-weight: 600; }
.cam-count { position: absolute; right: var(--sp-4); top: 50%; transform: translateY(-50%); color: #fff; font-size: var(--fs-sm); max-width: 32%; text-align: right; }

/* ---- wp-3.1r trailer log: the timeline ---- */
.tl { list-style: none; margin: 0; padding: 0; }
.tl-row { display: grid; grid-template-columns: 110px 1fr; gap: var(--sp-3); padding: var(--sp-3) 0; border-top: 1px solid var(--line); }
.tl-row:first-child { border-top: 0; padding-top: 0; }
.tl-when { color: var(--muted); font-size: var(--fs-sm); padding-top: 2px; }
.tl-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; font-size: var(--fs-sm); }
.tl-title { font-size: var(--fs-base); display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.tl-kind { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--accent-dark); background: var(--accent-soft); padding: 2px 6px; border-radius: var(--radius-pill); }
.tl-kind-m { color: var(--warn-ink); background: var(--warn-bg); }
.tl-links { display: flex; flex-wrap: wrap; gap: 2px var(--sp-2); }
.tl-links button.link { padding-left: 0; }
@media (max-width: 640px) { .tl-row { grid-template-columns: 1fr; gap: 2px; } }

/* ============================ Books (WP-3.2) ============================= */
/* Related records (D17): one list, both directions, on entries, orders and
   maintenance records. The picker in the modal is a short list of hits. */
.rl-list { list-style: none; margin: 0; padding: 0; }
.rl-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-2) 0; border-bottom: 1px solid var(--line); }
.rl-row:last-child { border-bottom: 0; }
.rl-results { max-height: 220px; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius-sm); margin-top: var(--sp-2); background: var(--panel); }
.rl-hit { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; text-align: left; padding: var(--sp-2) var(--sp-3); background: transparent; color: var(--ink); border: 0; border-bottom: 1px solid var(--line); cursor: pointer; font: inherit; }
.rl-hit:last-child { border-bottom: 0; }
.rl-hit:hover { background: var(--panel-hover); }
.rl-hit.active { background: var(--accent-soft); color: var(--accent-dark); }
/* Report trees: a parent row reads as a heading over its children. */
.grid tr.tree-parent td { font-weight: 600; }
.grid tfoot th { background: var(--panel-alt); }
/* Round 4: the GL before/after inside an entry's system history, side by side
   on a desktop and stacked on a phone; and the filing how-to list. */
.gl-diff { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); margin-top: var(--sp-2); }
.gl-diff table.grid { box-shadow: none; }
.how-to { margin: var(--sp-2) 0 0; padding-left: var(--sp-4); }
.how-to li { margin-bottom: var(--sp-2); }
@media (max-width: 760px) { .gl-diff { grid-template-columns: 1fr; } }
/* wp-1.4r3: the system-wide audit screen. The Record cell carries a kind and
   a name of very variable length, so it stacks rather than being squeezed
   into one line by whatever the widest Change happens to be. */
.audit-record { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 150px; }
/* The five filters as a grid, not a flex row: on a phone the shell turns a
   .form-row into one control per line, which put five stacked boxes between
   the operator and the first row of the log. This wraps two-up instead. */
.audit-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-2); align-items: end; margin: 10px 0 0; }
.audit-filters label { width: auto; }
/* A row that STAYS a row on a phone: the shell turns .form-row into a column
   there, which stranded the help "?" on a line of its own under its button
   and the pager's buttons one per line. */
.audit-verify { display: flex; flex-direction: row; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.audit-filters select, .audit-filters input { width: 100%; }
.audit-record .tag { font-size: var(--fs-xs); }
table.grid td .audit-record + * { margin-top: 0; }

/* wp-3.2r: a receipt that arrived as a PDF has no thumbnail, so its tile says
   what it is instead of showing a broken image. */
.receipt-pdf { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  width: 100%; height: 100%; font-size: 26px; background: var(--panel-alt); color: var(--muted); border: 0; cursor: pointer; }
.receipt-pdf span:last-child { font-size: var(--fs-xs); font-weight: 600; letter-spacing: .04em; }

/* ============================ Setup → Website (WP-4.4) =================== */
/* The site's words in page order: one row per piece, the id in mono, the
   builder's note beside it, the words in a box that grows with them. */
.ws-toolbar { margin-bottom: var(--sp-3); }
.ws-pages { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.ws-pages .chip { border: 1px solid var(--line); background: var(--panel); border-radius: var(--radius-pill); color: var(--ink); cursor: pointer; }
.ws-dot { color: var(--accent); font-size: 9px; vertical-align: middle; }
.ws-route { font-family: var(--font-mono); font-size: var(--fs-sm); font-weight: 400; margin-left: var(--sp-2); }
.ws-decisions { font-size: var(--fs-sm); color: var(--muted); background: var(--panel-alt); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); }
.ws-section { margin: var(--sp-5) 0 var(--sp-2); padding-bottom: var(--sp-1); border-bottom: 1px solid var(--line); color: var(--muted); font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: .04em; }
.ws-block, .ws-slot, .ws-review, .ws-faq { padding: var(--sp-3) 0; border-bottom: 1px solid var(--line); }
.ws-block.has-draft, .ws-slot.has-draft, .ws-review.has-draft, .ws-faq.has-draft { border-left: 3px solid var(--accent); padding-left: var(--sp-3); margin-left: calc(-1 * var(--sp-3) - 3px); }
.ws-faq.is-hidden { opacity: .6; }
.ws-block-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-2); font-size: var(--fs-sm); }
.ws-id { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent-dark); background: var(--accent-soft); padding: 1px 6px; border-radius: var(--radius-sm); }
.ws-note { font-size: var(--fs-xs); }
.ws-note code { font-family: var(--font-mono); font-size: var(--fs-xs); }
.ws-text { font: inherit; line-height: var(--lh); min-height: 2.4em; }
.ws-block-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
.ws-draft-note { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-3); margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--muted); }
.ws-published summary { cursor: pointer; }
.ws-published div { margin-top: var(--sp-1); color: var(--ink); background: var(--panel-alt); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm); }
.ws-locked-text { color: var(--ink); background: var(--panel-alt); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm); }
.ws-locked { margin: var(--sp-2) 0 0; font-size: var(--fs-sm); color: var(--muted); }
.ws-small { font-size: var(--fs-xs); }
/* a list in its dialog: one box per item (WP-4.3 Step 2b round 1) */
.ws-item { display: flex; align-items: flex-start; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.ws-item textarea { flex: 1 1 auto; min-height: 2.4em; }
.ws-mover { margin-top: var(--sp-3); }
/* pieces taken off the page: kept, read-only, folded away */
.ws-retired > summary { cursor: pointer; font-weight: 600; color: var(--muted); }
.ws-retired[open] > summary { margin-bottom: var(--sp-2); }
.ws-block--retired { opacity: .85; }
.ws-flash { animation: ws-flash 2.4s ease-out; }
@keyframes ws-flash { 0%, 60% { background: var(--warn-bg); } 100% { background: transparent; } }
/* Photo spots: what it needs on the left, what is there on the right. */
.ws-slot { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); }
.ws-slot-info p { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); }
.ws-slot-photos { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.ws-photo { margin: 0; width: 260px; max-width: 100%; }
.ws-photo img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--panel-alt); }
.ws-photo figcaption { font-size: var(--fs-xs); color: var(--muted); margin-top: var(--sp-1); }
.ws-slot-form input[type="text"] { width: 100%; }
.ws-quote { margin: 0; font-style: italic; color: var(--ink); }
.ws-faq-summary { margin: 0 0 var(--sp-1); }
.ws-fieldset { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); margin: 0; min-width: 220px; }
.ws-fieldset legend { font-size: var(--fs-sm); color: var(--muted); padding: 0 var(--sp-1); }
.ws-fieldset .checkbox-row { margin: var(--sp-1) 0; }
.ws-changes { margin: var(--sp-2) 0 0; padding-left: var(--sp-4); font-size: var(--fs-sm); }
.ws-diff del { display: block; color: var(--bad); text-decoration: line-through; opacity: .8; }
.ws-diff ins { display: block; color: var(--good); text-decoration: none; }
@media (min-width: 760px) {
  .ws-slot { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); grid-template-areas: "info photos" "form form"; }
  .ws-slot-info { grid-area: info; }
  .ws-slot-photos { grid-area: photos; }
  .ws-slot-form { grid-area: form; }
}
/* The draft preview (wp-4.4r): the built site in a frame above the page's pieces. */
.ws-preview-panel { padding-bottom: var(--sp-3); }
.ws-frame { display: block; width: 100%; height: max(560px, calc(100vh - 260px)); border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-alt); }
.ws-log pre { font-family: var(--font-mono); font-size: var(--fs-xs); white-space: pre-wrap; color: var(--muted); max-height: 240px; overflow: auto; }
.ws-frame-notice { margin: 0 0 var(--sp-2); }
