/* Fleet Manager — shared styles */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg: #f5f2eb;
  --surface: #ffffff;
  --border: #e0dbd0;
  --text: #1a1814;
  --muted: #7a7568;
  --col-hl-bg: #e4eaf6;        /* the tapped-column tint — deliberately NOT
                                  yellow, so it never reads as today/weekend
                                  (pilot, 25 Aug) */
  --green-bg: #e8f4e8;
  --green-text: #2d6a2d;
  --amber-bg: #fdf3dc;
  --amber-text: #8a5c00;
  --red-bg: #fdeaea;
  --red-text: #8a2020;
  --blue-bg: #e8eef6;
  --blue-text: #2d4a6a;
  /* The accent: the surface of the header, primary buttons and active tabs.
     Defaults to the ink so an unthemed company looks exactly as always. */
  --accent: var(--text);
  --accent-ink: var(--bg);
  /* The two typefaces, switchable per company from Settings → Appearance. */
  --font-ui: 'DM Mono', monospace;
  --font-display: 'Fraunces', serif;
}

html {
  /* iOS Safari enlarges text it judges too small, and does it unevenly —
     which made booking names balloon while their times stayed tiny.
     Turning it off keeps every size exactly as specified. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--font-ui);
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

/* ---------- Header & nav ---------- */
header {
  background: var(--accent);
  color: var(--accent-ink);
  /* horizontal insets: a phone held sideways puts a notch on one edge */
  padding: 0.7rem calc(2rem + env(safe-area-inset-right)) 0 calc(2rem + env(safe-area-inset-left));
}
/* Wordmark and company logo on one line — the masthead earns its height back */
.header-brand { display: flex; align-items: center; gap: 12px; }

.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: 0.5rem;
}

header h1 {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1;
}

header h1 em { font-style: normal; }

.header-company {
  font-size: 11px;
  color: #9a9080;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 4px;
}

.header-right { display: flex; align-items: center; gap: 1rem; }

.sync-status {
  font-size: 11px;
  letter-spacing: 0.06em;
  display: flex;
  align-items: center;
  gap: 6px;
  color: #9a9080;
}

.sync-dot { width: 6px; height: 6px; border-radius: 50%; background: #5a9a5a; }
.sync-dot.loading { background: #c8a030; animation: pulse 1s infinite; }
.sync-dot.error { background: #c03030; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }

.btn-logout {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.05em;
  background: transparent;
  color: var(--bg);
  border: 1px solid #4a453c;
  border-radius: 4px;
  padding: 6px 14px;
  cursor: pointer;
}

.btn-logout:hover { background: #2a2620; }

nav {
  display: flex;
  gap: 2px;
  /* Seven tabs do not fit a narrow window, so the strip scrolls sideways rather
     than squeezing every tab down to its longest word. Applied at all widths,
     not just phones: a part-width desktop window overflows too. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;             /* the strip is short; a bar would crowd it */
}
nav::-webkit-scrollbar { display: none; }

nav a {
  flex: 0 0 auto;          /* keep the whole label; overflow rather than shrink */
  white-space: nowrap;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9a9080;
  text-decoration: none;
  padding: 7px 14px;
  border-radius: 4px 4px 0 0;
}

nav a:hover { color: var(--bg); }
nav a.active { background: var(--bg); color: var(--text); }

/* ---------- Layout ---------- */
.main { max-width: 1080px; margin: 0 auto;
  padding: 2rem
    calc(1.5rem + env(safe-area-inset-right))
    calc(2rem + env(safe-area-inset-bottom))
    calc(1.5rem + env(safe-area-inset-left)); }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 2rem; }

.stat { background: var(--surface); border: 1px solid var(--border); padding: 1.2rem 1.4rem; border-radius: 4px; min-width: 0; }
.stat-label { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
/* Which period a summary figure covers, under its name rather than trailing
   it — the two read as one label but the figures still line up card to card. */
.stat-scope { display: block; font-size: 9px; letter-spacing: 0.06em; opacity: 0.7; margin-top: 2px; }
.stat-val { font-family: var(--font-display); font-size: 2.2rem; font-weight: 300; line-height: 1; }
.stat-val.green { color: var(--green-text); }
.stat-val.amber { color: var(--amber-text); }
.stat-val.red { color: var(--red-text); }
.stat-val.blue { color: var(--blue-text); }

/* ---------- Controls ---------- */
.toolbar { display: flex; gap: 8px; margin-bottom: 1rem; flex-wrap: wrap; align-items: center; }

/* THE standard box. One size for every text, number, date and choice box in
   the whole app, now and for every future feature: DM Mono at 16px, the same
   padding, the same 44px height. 16px is also the exact threshold below which
   iPhones zoom the page when a field is tapped. Widths follow the layout;
   height and type never vary. */
input[type="text"], input[type="date"], input[type="email"], input[type="password"],
input[type="number"], input[type="tel"], input:not([type]), select, textarea {
  font-family: var(--font-ui);
  font-size: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 10px 12px;
  min-height: 44px;
  color: var(--text);
  outline: none;
}
textarea { min-height: 0; line-height: 1.45; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); }

.toolbar input[type="text"] { flex: 1; min-width: 180px; }
input:focus, select:focus { border-color: var(--text); }

/* ---------- No steppers on money fields ----------
   A number field's up/down arrows sit right where the cursor rests, and the
   same control answers the mouse wheel: scrolling the page with the pointer
   still inside a total quietly changed Rs 14,500 into Rs 14,498, and the
   booking confirmation went out with the wrong figure on it. An amount is
   never one-more-than the last one, so the stepper buys nothing and costs
   that. Hidden here; the wheel and the arrow keys are stopped in app.js.
   To change an amount you now clear it and type the new one. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}


.btn-primary {
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.05em;
  background: var(--accent);
  color: var(--accent-ink);
  border: none;
  border-radius: 4px;
  padding: 8px 18px;
  cursor: pointer;
  white-space: nowrap;
}

.btn-primary:hover { opacity: 0.82; }
.btn-primary:disabled { opacity: 0.4; cursor: default; }

.filter-row { display: flex; gap: 6px; margin-bottom: 1.2rem; flex-wrap: wrap; }

.tab {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 5px 14px;
  cursor: pointer;
  color: var(--muted);
}

.tab:hover { border-color: var(--text); color: var(--text); }
.tab.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* ---------- Cards ---------- */
.card-list { display: flex; flex-direction: column; gap: 8px; }

.item-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--border);
  animation: fadeIn 0.2s ease;
}

@keyframes fadeIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none} }

.item-card.available, .item-card.active-b { border-left-color: var(--green-text); }
.item-card.rented, .item-card.upcoming { border-left-color: var(--amber-text); }
.item-card.overdue { border-left-color: var(--red-text); }
.item-card.completed { border-left-color: var(--border); opacity: 0.7; }

.card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.card-title { font-family: var(--font-display); font-size: 1.15rem; font-weight: 400; }
.card-sub { font-size: 11px; color: var(--muted); letter-spacing: 0.08em; margin-top: 2px; }

.badge { font-size: 10px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; padding: 3px 10px; border-radius: 2px; }
.badge.available, .badge.active-b { background: var(--green-bg); color: var(--green-text); }
.badge.rented, .badge.upcoming { background: var(--amber-bg); color: var(--amber-text); }
.badge.overdue { background: var(--red-bg); color: var(--red-text); }
.badge.completed { background: var(--bg); color: var(--muted); }

.card-details { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); display: flex; gap: 24px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.card-details strong { color: var(--text); }

.card-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }

.btn {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.04em;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 5px 12px;
  cursor: pointer;
  color: var(--text);
}

.btn:hover { border-color: var(--text); background: var(--bg); }
.btn.danger { color: var(--red-text); border-color: #f0c0c0; }
.btn.danger:hover { background: var(--red-bg); }

.empty { text-align: center; padding: 3rem; color: var(--muted); font-size: 13px; border: 1px dashed var(--border); border-radius: 4px; }

/* ---------- Modal ---------- */
.overlay { display: none; position: fixed; inset: 0; background: rgba(26,24,20,0.5); z-index: 100; align-items: center; justify-content: center; padding: 1rem; }
.overlay.open { display: flex; }

.modal {
  position: relative;   /* anchors .modal-pins to the frame (25 Aug) */
  background: var(--surface); border-radius: 6px; border: 1px solid var(--border);
  width: 400px; max-width: 100%;
  /* A column: the body scrolls, the action bar is the popup's own bottom
     edge. No stickiness anywhere — the bar cannot detach from the popup,
     and no browser can choose the wrong scroller for it. */
  display: flex; flex-direction: column;
  padding: 0; max-height: 90vh; max-height: 90dvh; overflow: hidden;
}
.modal-body {
  overflow-y: auto; flex: 1 1 auto; min-height: 0;
  padding: 2rem; padding-bottom: 1.5rem;
}
/* The booking form carries far more fields than the other dialogs. On a
   desktop screen it gets the width to lay them out in rows and the full
   window height, so the whole form is visible without scrolling inside it.
   Phones keep the narrow single-column layout. */
@media (min-width: 900px) {
  #booking-form-root .modal { width: 780px; max-height: 96vh; }
}
.modal h2 { font-family: var(--font-display); font-size: 1.4rem; font-weight: 300; margin-bottom: 1.5rem; padding-right: 36px; }

/* The close button pinned to every popup's top-right corner (injected in
   app.js). Sticky, so it rides at the top edge however far the form scrolls.
   The negative bottom margin gives back the space it occupies, so titles sit
   exactly where they always did — they just keep clear of the corner. */
.modal-pins {
  /* the frame's own corner: absolute beside the scrolling body — no scroll
     exists in its coordinate system (25 Aug) */
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 40;
}
.modal-x {
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  font-family: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.modal-x:hover { color: var(--text); border-color: var(--text); }
/* Wraps, because the booking form now carries six buttons and a single row
   pushed the first of them off the left edge where they could not be reached. */
.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  flex-shrink: 0;
  padding: 12px 2rem calc(12px + env(safe-area-inset-bottom)) 2rem;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 0 0 6px 6px;
  box-shadow: 0 -10px 14px -12px rgba(0, 0, 0, 0.18);
}
/* The primary action owns a full line of its own, on every screen — Cancel
   and the document buttons share the row above it (pilot, Aug 18: "same as
   on the phone"). order pushes it below whatever else the bar holds. */
.modal-actions .btn-primary { flex: 1 1 100%; order: 99; padding: 13px 16px; }
/* The document and send actions. margin-right: auto keeps them on the left and
   pushes Cancel and Save to the right, so the two you press most often are
   always in the same place however many of these are showing. */
.modal-actions-side {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-right: auto;
}

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); margin-bottom: 5px; }
.field input, .field select { width: 100%; }

/* A heading over a group of fields — same type as a field label, but with a
   rule above it so the group reads as its own section. Used for the car
   form's company-defined extra fields, which sit after everything standard. */
.field-label-lead {
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted);
  border-top: 1px solid var(--border);
  padding-top: 12px; margin-bottom: 10px;
}

.form-error { background: var(--red-bg); color: var(--red-text); font-size: 12px; padding: 10px 12px; border-radius: 4px; margin-bottom: 14px; display: none; }
.form-error.show { display: block; }

/* ---------- Login ---------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
.login-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 2.5rem; width: 400px; max-width: 100%; }
/* Password reset: a quiet text link under the button, and a green note that
   confirms the email is on its way. */
.login-forgot {
  display: block; width: 100%; margin-top: 14px; padding: 4px;
  background: none; border: none; cursor: pointer;
  font: inherit; font-size: 12px; color: var(--muted);
  text-align: center; text-decoration: underline;
}
.login-forgot:hover { color: var(--text); }
.login-forgot:disabled { cursor: default; text-decoration: none; }
.login-note {
  display: none; margin-top: 12px; padding: 10px 12px; border-radius: 4px;
  font-size: 12px; line-height: 1.5;
  background: var(--green-bg); color: var(--green-text);
}
.login-note.show { display: block; }
.login-card h1 { font-family: var(--font-display); font-size: 2rem; font-weight: 300; margin-bottom: 4px; }
.login-card h1 em { font-style: normal; }
.login-sub { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin-bottom: 2rem; }
.login-card .btn-primary { width: 100%; margin-top: 6px; padding: 10px; }

/* ---------- Calendar ---------- */
/* In the installed app the planner's frozen header paints one status-bar
   height ABOVE its resting place (the standalone-pin technique). This padding
   is the sacrificial strip it covers — without it, the overhang sliced the
   month-jump and Prev/Today/Next controls in half (pilot's screenshot,
   24 Aug). Zero in every browser. */
.cal-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px 16px; margin-bottom: 1rem; }
.cal-title { font-family: var(--font-display); font-size: 1.3rem; font-weight: 400; white-space: nowrap; }
.cal-nav { display: flex; gap: 6px; flex-wrap: wrap; }



.cal-dow { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); text-align: center; padding: 6px 0; }






.cal-more { font-size: 9px; color: var(--muted); margin-top: 2px; }

.boot { min-height: 100vh; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 13px; letter-spacing: 0.05em; }

.loading-screen { display: flex; align-items: center; justify-content: center; min-height: 200px; color: var(--muted); font-size: 13px; }

/* ---------- Mobile ---------- */
@media (max-width: 640px) {
  header { padding: 1rem 1rem 0; }
  /* Kept as a variable so the full-bleed grids at the foot of this file can
     cancel exactly this much padding and never drift out of step with it. */
  .main { --main-pad-x: 0.9rem;
    padding: 1.25rem
      calc(var(--main-pad-x) + env(safe-area-inset-right))
      calc(1.25rem + env(safe-area-inset-bottom))
      calc(var(--main-pad-x) + env(safe-area-inset-left)); }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Stack the date range above the controls instead of squeezing both
     into one row, which forced the title to wrap letter by letter. */
  .cal-header { flex-direction: column; align-items: stretch; }
  .cal-title { font-size: 1.05rem; white-space: normal; }
  .cal-nav { justify-content: flex-start; }
  /* cal-nav controls use the standard sizes like everything else (25 Aug) */
}

@media (max-width: 640px) {
  .cal-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--amber-text); }
  .cal-dot.active-b { background: var(--green-text); }
  .cal-dot.overdue { background: var(--red-text); }
}

/* ---------- Single-page view switching ---------- */
.view { display: none; }
.view.active { display: block; animation: viewIn 0.12s ease; }
@keyframes viewIn { from { opacity: 0.6; } to { opacity: 1; } }

/* ---------- Timeline (fleet planner) view ---------- */
.timeline-wrap {
  overflow-x: auto;
  overflow-y: visible;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  margin-bottom: 2rem;
  -webkit-overflow-scrolling: touch;
}

.timeline {
  display: grid;
  /* The width is set from JavaScript, as the column count times the width the
     current zoom level asks for. It used to be min-width: max-content, which
     sized the grid to whatever the longest booking label wanted and then let the
     1fr tracks stretch to fill it — so a day column was as wide as the wordiest
     booking on screen, and the planner showed five days instead of a fortnight.
     Searching visibly changed the column width, which was the giveaway. */
  font-size: 11px;
}

/* Header row */
.tl-corner,
.tl-daynum {
  position: sticky;
  /* vertical pinning belongs to .tl-head-wrap, which owns the status-bar
     strip via its safe-area padding — these stay at 0 so there is exactly
     one place that handles the inset */
  top: 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  z-index: 3;
}

.tl-corner {
  left: 0;
  z-index: 5;
  border-right: 1px solid var(--border);
  padding: 6px 10px;
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  display: flex;
  align-items: center;
}

/* The month pinned in the corner — the wall-planner label. It names whatever
   month the first visible column belongs to, and the JS swaps it the moment
   scrolling crosses into the next one. */
.tl-corner .tl-month {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--text);
}

.tl-daynum {
  text-align: center;
  padding: 4px 0 5px;
  color: var(--muted);
  line-height: 1.25;
  border-left: 1px solid #f0ece3;
}

.tl-daynum .dow {
  display: block;
  font-size: 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.65;
}

.tl-daynum.weekend { background: #efeade; }
.tl-daynum.today { background: var(--accent); color: var(--accent-ink); }
.tl-daynum.today .dow { opacity: 0.8; }

/* Car label column */
.tl-car {
  position: sticky;
  left: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid #f0ece3;
  padding: 7px 10px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.tl-car strong {
  font-weight: 500;
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  display: block;
}

/* Scoped to the plate line specifically: a bare "span" selector also caught
   the drag grip and the name highlight, greying out the car name. */
.tl-car .tl-plate {
  color: var(--muted);
  font-size: 9px;
  letter-spacing: 0.06em;
  display: block;
}

.tl-car.oos strong { opacity: 0.55; }

/* Background day cells */
.tl-cell {
  border-left: 1px solid #f5f1e8;
  border-bottom: 1px solid #f0ece3;
  min-height: 34px;
}

.tl-cell.weekend { background: #faf7f0; }
.tl-cell.today { background: #f0ece0; }

/* Booking bars */
.tl-bar {
  align-self: center;
  margin: 3px 2px;
  border-radius: 3px;
  padding: 3px 7px;
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  cursor: pointer;
  z-index: 1;
  border: 1px solid transparent;
  transition: filter 0.12s;
}

.tl-bar:hover { filter: brightness(0.94); }

.tl-bar.active-b  { background: var(--green-bg); color: var(--green-text); border-color: #cfe3cf; }
.tl-bar.upcoming  { background: var(--amber-bg); color: var(--amber-text); border-color: #ecdcbb; }
.tl-bar.overdue   { background: var(--red-bg);   color: var(--red-text);   border-color: #f0c9c9; }
.tl-bar.completed { background: #efece5;         color: var(--muted);      border-color: #e2ddd2; }

/* Out-of-service stripe */
/* Spans the full width and carries nowrap text, so it has the same power to
   force the tracks open unless told otherwise. */
.tl-oos-bar { min-width: 0; }
.tl-oos-bar {
  align-self: center;
  margin: 3px 2px;
  border-radius: 3px;
  font-size: 10px;
  padding: 3px 7px;
  background: repeating-linear-gradient(45deg, #f2eee6, #f2eee6 5px, #e8e2d6 5px, #e8e2d6 10px);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  z-index: 1;
}

.tl-legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  font-size: 10px;
  color: var(--muted);
  letter-spacing: 0.04em;
  margin: -1.4rem 0 2rem;
  padding: 0 2px;
}

.tl-legend span { display: flex; align-items: center; gap: 5px; }
.tl-key { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.tl-key.active-b { background: var(--green-bg); border: 1px solid #cfe3cf; }
.tl-key.upcoming { background: var(--amber-bg); border: 1px solid #ecdcbb; }
.tl-key.overdue { background: var(--red-bg); border: 1px solid #f0c9c9; }
.tl-key.oos { background: repeating-linear-gradient(45deg, #f2eee6, #f2eee6 3px, #e8e2d6 3px, #e8e2d6 6px); border: 1px solid #e2ddd2; }

.tl-empty { padding: 2.5rem; text-align: center; color: var(--muted); font-size: 13px; }

@media (max-width: 640px) {
  .tl-car strong { max-width: 100px; font-size: 10px; }
}


/* ---------- Side-by-side fields (date + time, staff pairs) ---------- */
.field-row { display: flex; gap: 10px; }
.field-row .field { flex: 1; margin-bottom: 14px; }
.field-row .field:first-child { flex: 1.6; }

/* ---------- Timeline bar detail (times, locations, paid marker) ---------- */
.tl-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  /* The end labels do not shrink, so anything that will not fit has to be cut
     off at the bar's edge rather than spilling across the next day's column. */
  overflow: hidden;
  /* And the bar must not decide how wide a day is. A grid item's min-width
     defaults to its min-content, which for nowrap text is the entire label —
     so one bar reading "✓ customer 1  12:00" forced the days it spanned to be
     that wide, and because every day track is an equal 1fr, every other column
     grew to match. The planner ended up showing five days instead of a
     fortnight, and the width changed depending on which bookings happened to be
     on screen. Zero lets the tracks keep the width the zoom level asked for. */
  min-width: 0;
}

.tl-bar-start, .tl-bar-end {
  font-size: 9px;
  line-height: 1.3;
  opacity: 0.85;
  white-space: nowrap;
  flex-shrink: 0;
}

.tl-bar-name {
  /* Sized to its content and pushed to the middle by the auto margins, so the
     name sits centred in the bar rather than crowded against the start time. */
  flex: 0 1 auto;
  margin: 0 auto;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
  font-size: 10px;      /* stated, not inherited, so nothing can rescale it */
  line-height: 1.3;
}

/* Times and locations give way before the name does */
.tl-bar-start, .tl-bar-end { min-width: 0; overflow: hidden; }

.tl-bar-start, .tl-bar-end { flex: 0 0 auto; }

/* Paid bookings get a solid left marker so payment status is visible
   at a glance without losing the booking-state colour. */
.tl-bar.paid { border-left-width: 4px; border-left-style: solid; }
.tl-bar.paid.active-b  { border-left-color: var(--green-text); }
.tl-bar.paid.upcoming  { border-left-color: var(--amber-text); }
.tl-bar.paid.overdue   { border-left-color: var(--red-text); }
.tl-bar.paid.completed { border-left-color: var(--muted); }

.tl-bar.unpaid { border-left-width: 4px; border-left-style: dotted; border-left-color: rgba(0,0,0,0.25); }

.tl-key.paid { background: var(--green-bg); border-left: 4px solid var(--green-text); }
.tl-key.unpaid { background: var(--green-bg); border-left: 4px dotted rgba(0,0,0,0.35); }

/* Add-booking affordance on empty timeline cells */
.tl-cell { position: relative; }
.tl-cell.addable { cursor: pointer; }
.tl-cell.addable:hover { background: #e9e3d6; }
.tl-cell.addable:hover::after {
  content: "+";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 15px;
  line-height: 1;
}

@media (max-width: 640px) {
  .field-row { flex-direction: column; gap: 0; }

  /* One consistent scale on a phone: names, times and locations all sized
     relative to each other rather than left to the browser. */
  .tl-bar { gap: 4px; padding: 3px 5px; font-size: 9px; }
  .tl-bar-name { font-size: 9px; }
  .tl-bar-start, .tl-bar-end { font-size: 8px; }
  .tl-oos-bar, .tl-service { font-size: 8px; }
  .tl-car strong { font-size: 10px; }
  .tl-car .tl-plate { font-size: 8px; }
  .tl-daynum { font-size: 9px; }
  .tl-daynum .dow { font-size: 7px; }
}

/* ---------- Deliveries schedule ---------- */
.day-group { margin-bottom: 1.5rem; }

.day-head {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-ink);
  background: var(--accent);
  padding: 7px 12px;
  border-radius: 4px 4px 0 0;
}

.job-row {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  padding: 10px 12px;
}

.job-row:last-child { border-radius: 0 0 4px 4px; }
.job-row.done { opacity: 0.5; }
.job-row.late { background: #fdf6f6; }

.job-tick {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--green-text);
  line-height: 1;
  padding: 0;
}

.job-tick:hover { border-color: var(--text); }
.job-row.done .job-tick { background: var(--green-bg); border-color: #cfe3cf; }

.job-time {
  font-size: 12px;
  width: 44px;
  flex-shrink: 0;
  color: var(--muted);
  letter-spacing: 0.03em;
}

.job-kind {
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 7px;
  border-radius: 2px;
  flex-shrink: 0;
  width: 64px;
  text-align: center;
}

.job-kind.delivery { background: var(--amber-bg); color: var(--amber-text); }
.job-kind.recovery { background: var(--green-bg); color: var(--green-text); }
.job-kind.task     { background: var(--blue-bg);  color: var(--blue-text); }

.job-main { flex: 1; min-width: 0; }
.job-title { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.job-row.done .job-title { text-decoration: line-through; }
.job-sub { font-size: 11px; color: var(--muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.job-staff { color: var(--text); }

.job-flag {
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--red-bg);
  color: var(--red-text);
  padding: 3px 8px;
  border-radius: 2px;
  flex-shrink: 0;
}

.job-edit { flex-shrink: 0; }

@media (max-width: 640px) {
  .job-row { flex-wrap: wrap; gap: 8px; }
  .job-main { flex-basis: 100%; order: 5; }
  .job-kind { width: auto; }
  .job-sub { white-space: normal; }
}

/* Draw the eye to a validation message that may have been scrolled past */
.form-error.flash { animation: errFlash 0.9s ease; }
@keyframes errFlash {
  0%   { transform: translateY(-4px); box-shadow: 0 0 0 0 rgba(138,32,32,0.35); }
  35%  { box-shadow: 0 0 0 5px rgba(138,32,32,0.12); }
  100% { transform: none; box-shadow: 0 0 0 0 rgba(138,32,32,0); }
}

/* ---------- 24-hour time pair ---------- */
.time-pair { display: flex; align-items: center; gap: 5px; }
.time-pair select { flex: 1; min-width: 0; text-align: center; }
.time-sep { color: var(--muted); font-size: 13px; }

/* Filter selects sit alongside search in the toolbar */
.toolbar select { min-width: 130px; }
@media (max-width: 640px) {
  .toolbar select { min-width: 0; flex: 1; }
}


/* ---------- Additions: service marker, car link, checkboxes, day add ---------- */
.tl-service {
  align-self: center;
  margin: 3px 1px;
  border-radius: 3px;
  font-size: 8px;
  letter-spacing: 0.06em;
  text-align: center;
  padding: 3px 2px;
  background: var(--blue-bg);
  color: var(--blue-text);
  border: 1px dashed #b9c6d8;
  overflow: hidden;
  z-index: 1;
}

.tl-carlink { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--muted); }
.tl-carlink:hover { color: var(--text); border-bottom-style: solid; }

.tl-car.due .tl-plate { color: var(--amber-text); }
.tl-duetag {
  font-size: 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--amber-bg);
  color: var(--amber-text);
  padding: 1px 4px;
  border-radius: 2px;
}

.checkbox-field label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--text);
  cursor: pointer;
}
.checkbox-field input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--text); }

.field textarea {
  width: 100%;
  font-family: var(--font-ui);
  font-size: 13px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 8px 12px;
  color: var(--text);
  outline: none;
  resize: vertical;
  line-height: 1.5;
}
.field textarea:focus { border-color: var(--text); }

.day-head { display: flex; align-items: center; justify-content: space-between; }
.day-add {
  background: transparent;
  border: 1px solid #4a453c;
  color: var(--bg);
  border-radius: 3px;
  width: 20px; height: 20px;
  line-height: 1;
  cursor: pointer;
  font-family: var(--font-ui);
  padding: 0;
}
.day-add:hover { background: #2a2620; }

.job-tick.disabled {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  background: var(--bg);
  cursor: default;
}
.job-kind.service { background: var(--blue-bg); color: var(--blue-text); }

/* ---------- Timeline zoom control ---------- */
.zoom-control {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 6px;
}
.zoom-control input[type="range"] {
  width: 76px;
  accent-color: var(--text);
  cursor: pointer;
}

/* ---------- Drag to reorder cars ---------- */
/* Padding only. This rule used to also set position: relative — to anchor the
   drag grip — which silently cancelled the position: sticky set further up and
   stopped the vehicle column freezing when scrolling sideways. A sticky element
   anchors an absolutely positioned child just as well as a relative one, so the
   grip is unaffected. */
.tl-car { padding-left: 22px; }

.tl-grip {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--border);
  font-size: 11px;
  cursor: grab;
  user-select: none;
  touch-action: none;      /* let us handle the drag rather than scrolling */
  line-height: 1;
  padding: 4px 2px;
}
.tl-grip:hover { color: var(--muted); }
.timeline.dragging .tl-grip { cursor: grabbing; }
.timeline.dragging { user-select: none; }

.tl-car.drag-source { background: var(--bg); opacity: 0.55; }
.tl-car.drag-target { box-shadow: inset 0 -2px 0 0 var(--text); }

@media (max-width: 640px) {
  .tl-grip { font-size: 13px; padding: 6px 4px; }
}

/* ---------- Colour swatches ---------- */
.swatches { display: flex; gap: 6px; flex-wrap: wrap; }

.swatch {
  width: 30px; height: 30px;
  border-radius: 4px;
  border: 2px solid var(--border);
  cursor: pointer;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--muted);
  background: var(--bg);
  transition: transform 0.1s, border-color 0.1s;
}
.swatch:hover { transform: translateY(-1px); }
.swatch.selected { border-color: var(--text); box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--text); }
.swatch.auto { letter-spacing: 0; }

/* A booking with its own colour: overdue still shows through as a red ring */
.tl-bar.custom.overdue { box-shadow: 0 0 0 2px var(--red-text) inset; }
.tl-key.custom {
  background: linear-gradient(135deg, #cfe8cf 0 33%, #c9dcf0 33% 66%, #f7e2b0 66% 100%);
  border: 1px solid var(--border);
}

/* ---------- Draggable tabs ---------- */
/* pan-y told the browser to handle vertical panning only and hand every
   horizontal gesture to the drag-to-reorder code, so the strip could not be
   swiped sideways on a phone. Reordering is mouse-only now (see app.js), so the
   browser gets the swipe back. "manipulation" keeps panning and tapping while
   dropping the double-tap-to-zoom delay, which makes taps feel immediate. */
#main-nav a { touch-action: manipulation; }
#main-nav a.tab-dragging {
  opacity: 0.6;
  background: #2a2620;
  border-radius: 4px 4px 0 0;
  cursor: grabbing;
}


/* ---------- Car row colour (highlighter behind the name) ---------- */
.tl-car .car-hl {
  padding: 1px 5px;
  border-radius: 3px;
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* ================= Dashboard ================= */
.dash-alerts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 1.5rem; }

.dash-alert {
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: left;
  padding: 12px 16px;
  border-radius: 4px;
  border: 1px solid var(--border);
  border-left-width: 3px;
  cursor: pointer;
  background: var(--surface);
  color: var(--text);
  transition: background 0.12s;
}
.dash-alert:hover { background: var(--bg); }
.dash-alert.red { border-left-color: var(--red-text); }
.dash-alert.amber { border-left-color: var(--amber-text); }

.dash-allclear {
  font-size: 13px;
  color: var(--green-text);
  background: var(--green-bg);
  border: 1px solid #cfe3cf;
  border-radius: 4px;
  padding: 12px 16px;
}

/* minmax(0, 1fr) rather than plain 1fr. A bare 1fr means minmax(auto, 1fr), and
   that auto floor stops a track shrinking below its content's min-content width
   — which, for a row of nowrap text like "Vitali (localrent) 2025 Suzuki Baleno
   (1234FF22)", is the whole string. The card then pushed past the screen edge on
   a phone and the ellipsis never fired, because the box was never constrained.
   Letting the track reach zero puts the row back in charge of its own truncation. */
.dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 14px; }
.dash-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* A column of cards inside one grid cell. The cell still stretches to the
   tallest column in the row, but inside it each card takes only the height
   its content needs and the LAST card absorbs whatever is left over — so a
   short card (the expenses strip) is never inflated to fill a row set by a
   long one (the documents list). */
.dash-stack { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.dash-stack > .dash-card { margin: 0; }
/* This row's columns are NOT stretched to the tallest one. Everywhere else on
   the dashboard equal-height cards look tidy, but here one column is a long
   documents list and the other a short strip, and stretching inflated the
   short card into half a screen of white (pilot, 25 Aug 18:27). Each card
   keeps the height its own content needs. */
.dash-grid-top { align-items: start; }
/* When a card in a stack does get extra height, its body sinks to the bottom
   rather than leaving the graph stranded under the heading. */
.dash-flexbody { display: flex; flex-direction: column; }
.dash-flexbody > [data-el="expense-months"] { margin-top: auto; }

.dash-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1.1rem 1.25rem 1.25rem;
  /* Shrinking the grid track is only half the job: a grid item's min-width also
     defaults to auto, so without this the card itself still refuses to go
     narrower than the longest line of text inside it. */
  min-width: 0;
}
.dash-wide { margin-bottom: 14px; }

.dash-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 0.9rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--border);
}
.dash-head h3 {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 400;
}
.dash-link {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.dash-link:hover { color: var(--text); }

/* Figures */
.dash-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: 10px; min-width: 0; }

.dash-figure {
  background: transparent;
  border: none;
  padding: 6px 0;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-ui);
  border-radius: 3px;
}
.dash-figure:hover { background: var(--bg); }

.dash-figure-val {
  display: block;
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 300;
  line-height: 1.05;
  color: var(--text);
}
.dash-figure-val.green { color: var(--green-text); }
.dash-figure-val.amber { color: var(--amber-text); }
.dash-figure-val.red { color: var(--red-text); }
.dash-figure-val.blue { color: var(--blue-text); }

.dash-figure-label {
  display: block;
  font-size: 9px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 3px;
}

/* Job and invoice rows */
.dash-job {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 6px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  cursor: pointer;
}
.dash-job:last-of-type { border-bottom: none; }
.dash-job:hover { background: var(--bg); }
.dash-job-time { color: var(--muted); width: 40px; flex-shrink: 0; }
.dash-job-main { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-dim { color: var(--muted); }
.dash-amount { font-weight: 500; flex-shrink: 0; }

.dash-more {
  font-family: var(--font-ui);
  font-size: 11px;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 8px 6px 0;
  text-align: left;
  width: 100%;
}
.dash-more:hover { color: var(--text); }

.dash-empty { font-size: 12px; color: var(--muted); padding: 10px 6px; }

/* ---------- Dashboard: documents expiring, by month (Arshad, 7 Aug) ----------
   A month heading over the documents due in it. Months with nothing due are
   not printed at all, so the card shows only the months that need work. */
.dash-month + .dash-month { margin-top: 10px; }
.dash-month-head {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted); padding: 4px 6px; border-bottom: 1px solid var(--border);
}
.dash-month-head.dash-red { color: var(--red-text); }
.dash-month-head.dash-amber { color: var(--amber-text); }
.dash-month-n {
  font-size: 10px; letter-spacing: 0; padding: 1px 7px; border-radius: 10px;
  background: var(--bg); border: 1px solid var(--border); color: var(--muted);
}
.dash-doc-date { flex-shrink: 0; font-variant-numeric: tabular-nums; color: var(--muted); }
.dash-doc-date.dash-red { color: var(--red-text); font-weight: 500; }
.dash-doc-date.dash-amber { color: var(--amber-text); }

/* ---------- Dashboard: monthly expenses strip ----------
   Past, current and coming months side by side — his request was all three
   at once, which a stepper cannot show. Scrolls with the thumb on a phone,
   the same gesture as the expenses category totals. */
.dash-mstrip {
  display: flex; gap: 6px; align-items: flex-end;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: 6px 2px 0;
}
.dash-mcol {
  flex: 1 0 52px; min-width: 52px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  cursor: pointer; border-radius: 5px; padding: 4px 2px;
}
.dash-mcol:hover { background: var(--bg); }
.dash-mcol .dash-mbar {
  width: 60%; border-radius: 3px 3px 0 0;
  background: var(--accent); opacity: 0.55;
  flex-shrink: 0;   /* a short bar must not be squeezed to nothing */
}
.dash-mcol.now .dash-mbar { opacity: 1; }
.dash-mcol.ahead .dash-mbar { opacity: 0.28; }
.dash-mval { font-size: 10px; font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }
.dash-mlabel { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.dash-mcol.now .dash-mlabel { color: var(--text); font-weight: 500; }
.dash-mnote { font-size: 11px; color: var(--muted); margin-top: 8px; }
/* The bars need a height to grow into; the column is the reference box. */
.dash-mstrip { min-height: 96px; }
.dash-mcol { justify-content: flex-end; height: 88px; }

/* The dashboard planner is a glance, not a workspace */
.dash-timeline { max-height: 340px; overflow-y: auto; margin-bottom: 0; }
.dash-timeline .tl-car { padding-left: 10px; cursor: pointer; }
.dash-timeline .tl-bar { cursor: pointer; }

@media (max-width: 640px) {
  .dash-grid, .dash-grid-3 { grid-template-columns: minmax(0, 1fr); }
  .dash-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-figure-val { font-size: 1.4rem; }
}

.dash-range { font-family: var(--font-ui); font-size: 11px; color: var(--muted); font-weight: 400; margin-left: 6px; }
.dash-minnav { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* dash-minnav buttons use the standard sizes (25 Aug) */

/* ---------- Wider planner on the Bookings view ----------
   Lists and cards read better in a narrow centred column, so .main stays at
   1080px. The planner is different: it is a grid, and every extra pixel is
   another slice of calendar visible without scrolling sideways. So the planner
   block alone steps outside the page column, up to 1700px, and never wider
   than the window. min() makes it self-limiting, so phones are unaffected. */
#view-bookings .cal-header,
#view-bookings .tl-head-wrap,
#view-bookings .timeline-wrap,
#view-bookings .tl-legend {
  --planner-w: min(100vw - 2rem, 2600px);
  width: var(--planner-w);
  margin-left: calc(-1 * max(0px, (var(--planner-w) - 100%) / 2));
}

/* ---------- Frozen dates and vehicle column ----------
   Both are already position: sticky, but sticky pins to the nearest scrolling
   box and the planner had no height of its own, so scrolling down scrolled the
   whole page and carried the date row with it. Giving the planner its own
   height makes it the thing that scrolls. */
/* The date row lives in its own thin strip that sticks to the top of the
   page while the planner body scrolls beneath it. It cannot live inside the
   planner box: a horizontally-scrolling container captures position:sticky,
   which is exactly why the dates vanished. The strip never scrolls on its
   own — JS mirrors the body's sideways position into it, so the columns
   always line up. */
#view-bookings .tl-head-wrap {
  position: sticky;
  top: 0;
  z-index: 20;
  overflow-x: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 4px 4px 0 0;
}
#view-bookings .timeline-wrap {
  border-top: none;
  border-radius: 0 0 4px 4px;
}

#view-bookings .timeline-wrap {
  /* The page owns vertical scrolling (option A): the planner grows to fit
     every car and the wheel always moves the page, wherever the cursor is.
     Two nested vertical scrollbars made the wheel do different things
     depending on what it happened to hover — the "double scrolling" the
     pilot reported. The wrap keeps only sideways scrolling for the day
     columns, which never conflicts with the wheel. The known trade-off:
     scrolled deep into a long fleet, the day-number header is off-screen. */
  max-height: none;
  overflow-y: visible;
}

/* ---------- Collapsible panels on the Bookings view ----------
   Collapsing uses a class on the section rather than inline styles, so .stats
   keeps its grid layout and .filter-row its flex layout when reopened. */
.panel-toggle {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--muted);
  padding: 6px 13px;
  cursor: pointer;
  margin-bottom: 1rem;
  transition: color 0.12s, border-color 0.12s;
}
.panel-toggle:hover { color: var(--text); border-color: var(--text); }

/* In the toolbar it is one control among several, so it drops the row spacing. */
.toolbar .panel-toggle { margin-bottom: 0; }

#view-bookings.hide-list .filter-row,
#view-bookings.hide-list .card-list { display: none; }

/* Totals for exactly the invoices listed underneath, so the figures at the top
   of the Billing page can always be reconciled against what is on screen. */
.list-total {
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: -0.5rem 0 1.1rem;
  padding: 0 2px;
}

/* ================= Mobile refinements =================
   Everything below is inside the phone breakpoint, so desktop is untouched.
   The dashboard was designed at desktop width and its padding, type sizes and
   heading rows were all a little oversized once squeezed into ~350px. */
@media (max-width: 640px) {
  /* Cards: 22px of the card's inner width was going to padding alone. min-width
     lets a card shrink to its column instead of being propped open by a long
     line of text inside it. */
  .dash-card { padding: 0.85rem 0.8rem 0.95rem; min-width: 0; }
  .dash-grid, .dash-grid-3 { gap: 9px; margin-bottom: 9px; }
  .dash-stack { gap: 9px; }
  .dash-wide { margin-bottom: 9px; }

  /* Heading rows carry a title and a set of controls. On a phone they cannot
     sit side by side, so let them stack instead of squeezing each other. */
  .dash-head { flex-wrap: wrap; gap: 6px 10px; }
  .dash-head h3 { font-size: 1rem; }
  .dash-range { margin-left: 0; display: block; }

  /* Figures: two per row. The values were set for a desktop card and left a lot
     of empty space around them once halved in width, so they come down a size
     and the block tightens up. */
  .dash-figures { gap: 6px 8px; }
  .dash-figure { padding: 2px 0; }
  .dash-figure-val { font-size: 1.15rem; }
  .dash-figure-label { font-size: 8px; letter-spacing: 0.05em; margin-top: 1px; }

  /* Card headings: the rule and the space under it were sized for desktop */
  .dash-head { margin-bottom: 0.65rem; padding-bottom: 0.45rem; }
  .dash-head h3 { font-size: 0.95rem; }
  .dash-link { font-size: 9px; letter-spacing: 0.06em; }

  /* Alerts and rows */
  .dash-alerts { gap: 6px; margin-bottom: 0.8rem; }
  .dash-alert, .dash-allclear { font-size: 11px; padding: 9px 11px; }
  .dash-job { gap: 6px; padding: 7px 0; font-size: 11px; }
  .dash-job-time { width: 32px; }
  /* The OUT / BACK badge was 64px — a fifth of the row on a phone, and its
     labels are four letters. */
  .dash-job .job-kind { width: 40px; font-size: 8px; padding: 2px 4px; }
  .dash-amount { font-size: 11px; }
  .dash-more { font-size: 10px; padding-top: 6px; }
  .dash-empty { font-size: 11px; padding: 7px 0; }

  /* A phone screen has less room to give a glance-only planner */
  .dash-timeline { max-height: 260px; }

  /* The Bookings planner breakout is a desktop trick: it assumed .main's 1.5rem
     padding, but the phone breakpoint sets 0.9rem, which left the planner
     narrower than every other block on the page. On a phone it simply fills the
     column like everything else. */
  #view-bookings .cal-header,
  #view-bookings .tl-head-wrap,
  #view-bookings .timeline-wrap,
  #view-bookings .tl-legend {
    width: 100%;
    margin-left: 0;
  }
}

/* ---------- Collapsible planner legend ----------
   Closed by default on a phone, where four lines of colour key competes with
   the planner itself for a small screen. Open by default on a desktop. */


/* ---------- Collapsible summary panels on every view ----------
   The bookings view had this first; the same disclosure button now sits on
   Fleet, Customers, Billing, Maintenance and Tasks, so the working part of each
   view is what greets you. Scoped to .view rather than one id so all six share
   the rule. */
.view.hide-summary .stats { display: none; }
.view.hide-legend .tl-legend { display: none; }

/* Jump straight to any month and year instead of pressing Next repeatedly. On a
   phone this opens the native month-and-year wheel. Styled to sit quietly
   alongside the Prev / Today / Next buttons rather than shout. */
.cal-jump { display: inline-flex; gap: 4px; }
.cal-jump select {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 6px;
  cursor: pointer;
}
.cal-jump select:hover { border-color: var(--text); }

/* Phone: the date row shrinks so a month of planning is visible at once, which
   matters more here than on desktop because there is no room for the Days
   slider. Paired with the narrower columns set in view-bookings.js. */
@media (max-width: 640px) {
  .tl-daynum { font-size: 8px; letter-spacing: 0; }
  .tl-daynum .dow { font-size: 7px; }
  .cal-jump select { font-size: 10px; padding: 3px 5px; }
}

/* The summary tiles on the other views carry long values too — a formatted
   amount, a car description — and the same automatic minimum applies, so give
   their contents somewhere to go rather than pushing the tile wide. */
.stat-val, .stat-label { overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 640px) {
  .stat { padding: 0.8rem 0.85rem; }
  .stat-val { font-size: 1.3rem; }
  .stat-label { font-size: 8px; letter-spacing: 0.06em; }
  .stats { gap: 8px; margin-bottom: 1.2rem; }
}

/* The day-count readout beside the slider, so the control says what it does. */
.zoom-count {
  font-variant-numeric: tabular-nums;
  color: var(--text);
  min-width: 22px;
}

@media (max-width: 640px) {
  /* The Days slider used to be hidden on phones, which forced the default to be
     right for everyone. It is more use than most of the row it sits in, so it
     gets its own full-width line under the Prev / Today / Next buttons and a
     thumb big enough for a finger. How much of the month fits on a small screen
     is a matter of preference, so it is better asked than assumed. */
  .cal-nav { flex-wrap: wrap; row-gap: 8px; }
  .zoom-control {
    order: 10;                  /* after the buttons, whatever the source order */
    flex: 1 0 100%;
    justify-content: flex-start;
    padding: 2px 0 0;
    gap: 10px;
  }
  .zoom-control input[type="range"] {
    flex: 1;
    width: auto;
    height: 26px;               /* a comfortable target without a visible change */
  }
  .zoom-control input[type="range"]::-webkit-slider-thumb {
    width: 22px; height: 22px;
  }
}

/* ================= Settings page ================= */
.settings-grid {
  /* Two columns whose heights the browser balances itself — the cards flow
     down the left and continue on the right, breaking wherever the halves
     come out even. Cards never split internally. */
  columns: 2;
  column-gap: 14px;
  align-items: start;
}
.settings-card {
  break-inside: avoid;
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1.3rem 1.4rem 1.5rem;
  min-width: 0;
}
.settings-card h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.2rem;
  margin-bottom: 0.5rem;
}
.settings-note {
  font-size: 11px;
  line-height: 1.55;
  color: var(--muted);
  margin: 0.35rem 0 1rem;
}
.settings-note.warn { color: var(--red-text); }
.settings-note strong { color: var(--text); }
.settings-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 0.4rem;
}
.saved-note { font-size: 11px; color: var(--green-text); }

.check-list { display: flex; flex-direction: column; gap: 4px; }
/* Beats ".field label", which would otherwise shout these in capitals — that is
   right for a field's own label, wrong for the text of a choice. */
.field .check-row,
.check-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  cursor: pointer;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
}
/* ".field input" stretches every input in a field to the full width, which is
   right for a text box and wrong for a tick box. Once these rows became flex the
   checkbox took the whole row and pushed its own label to the far edge. */
.check-row input,
.multi-row input {
  width: auto;
  flex: 0 0 auto;
  cursor: pointer;
}

/* Logo picker */
.logo-row { display: flex; gap: 12px; align-items: flex-start; }
.logo-preview {
  width: 92px; height: 92px;
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.logo-preview img { max-width: 100%; max-height: 100%; display: block; }
.logo-empty { font-size: 9px; color: var(--muted); text-align: center; padding: 4px; }
.logo-actions { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.logo-actions input[type="file"] { font-family: var(--font-ui); font-size: 11px; max-width: 100%; }
.logo-actions .settings-note { margin: 0; }

/* The logo repeated small in the header, once one has been set */
.header-logo { margin-top: 0; }
.header-logo img { max-height: 26px; max-width: 150px; display: block; border-radius: 2px; }

/* ---------- Backup reminder banner ---------- */
.backup-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  background: #fdf6e3;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--amber-text, #8a6d1f);
  padding: 10px 1.5rem;
  font-size: 12px;
}
.backup-banner-actions { display: flex; gap: 6px; flex-shrink: 0; }

@media (max-width: 640px) {
  .settings-grid { columns: 1; column-gap: 10px; }
  .settings-card { padding: 1rem 0.9rem 1.1rem; }
  .settings-card h3 { font-size: 1.05rem; }
  .logo-row { flex-direction: column; }
  .logo-preview { width: 76px; height: 76px; }
  .backup-banner { padding: 9px 0.9rem; font-size: 11px; }
  .backup-banner-actions { width: 100%; }
}

/* ---------- One-line planner controls (desktop) ----------
   The search row and the planner controls used to sit on two separate lines,
   which cost the planner a whole row of height on the screen where it matters
   most. On anything wider than a phone they share one line: search and its
   buttons on the left, the date range and planner controls on the right. It
   still wraps if the window is too narrow, which is no worse than before.
   Phones are untouched — the rule below only applies from 641px up. */
@media (min-width: 641px) {
  .planner-bar {
    display: flex;
    align-items: center;
    gap: 10px 18px;
    flex-wrap: wrap;
    margin-bottom: 0.85rem;
  }
  .planner-bar .toolbar {
    flex: 1 1 300px;
    margin-bottom: 0;
    min-width: 0;
  }
  .planner-bar .cal-header {
    flex: 0 1 auto;
    margin-bottom: 0;
    gap: 8px 12px;
  }
  /* Smaller now it shares a line with everything else, and it may wrap onto its
     own line rather than force the controls off the edge. */
  .planner-bar .cal-title { font-size: 1.05rem; }
}

/* ---------- More cars on screen (phones) ----------
   About 438px of an 844px phone was being spent before the planner even began,
   leaving room for roughly five vehicles. Two things are done about it: the
   furniture above the planner is trimmed, and the vehicle rows themselves are
   made shorter. Together that is worth three or four more cars without anything
   becoming hard to read. */
@media (max-width: 640px) {
  /* The masthead was sized for a desktop */
  header { padding: 0.7rem 1rem 0; }
  header h1 { font-size: 1.15rem; }
  .header-company { margin-top: 2px; }
  .header-logo { margin-top: 4px; }
  .header-logo img { max-height: 24px; }

  /* Space between the controls, none of which needs to be generous */
  .main { padding-top: 0.7rem; }
  .toolbar { margin-bottom: 0.5rem; }
  .cal-header { margin-bottom: 0.5rem; gap: 6px; }
  .cal-title { font-size: 0.95rem; }
  .panel-toggle { margin-bottom: 0.5rem; padding: 5px 11px; }

  /* Shorter vehicle rows. The two lines of text stay the same size; it is the
     padding and the leading around them that comes down, which is what was
     making each row taller than its contents needed. */
  .tl-car { padding-top: 3px; padding-bottom: 3px; line-height: 1.2; }
  .tl-car strong { line-height: 1.2; }
  .tl-car .tl-plate { line-height: 1.15; margin-top: 0; }
  .tl-cell { padding-top: 2px; padding-bottom: 2px; }
  .tl-bar { margin-top: 2px; margin-bottom: 2px; }
}

/* ---------- Billing period picker ---------- */
.period-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: -0.6rem 0 0.9rem;
}
.period-row label {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.period-row select {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 7px;
  cursor: pointer;
}
.period-row select:hover { border-color: var(--text); }

/* ---------- Customers A to Z ---------- */
.alpha-index {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  margin-bottom: 0.8rem;
}
.alpha-btn {
  font-family: var(--font-ui);
  font-size: 11px;
  min-width: 26px;
  padding: 4px 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.alpha-btn:hover:not(:disabled) { border-color: var(--text); }
.alpha-btn.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* Shown but not clickable, so the row keeps its shape and an empty letter reads
   as empty rather than as something that failed to respond. */
.alpha-btn:disabled { color: var(--border); cursor: default; }

@media (max-width: 640px) {
  .alpha-btn { min-width: 0; flex: 1 1 auto; padding: 5px 4px; font-size: 10px; }
  .period-row { margin-top: -0.3rem; gap: 6px; }
  .period-row select { font-size: 10px; padding: 3px 5px; }
}

/* The All button sits with the letters but needs room for a word, and a little
   separation so it reads as "everything" rather than as another letter. */
.alpha-btn.alpha-all {
  min-width: 40px;
  padding-left: 6px;
  padding-right: 6px;
  margin-right: 5px;
}

/* ---------- Brief confirmations ---------- */
#toast-host {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;              /* above the modals, which sit at 100 */
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;      /* never blocks a click on what is underneath */
}
.toast {
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.03em;
  padding: 11px 18px;
  border-radius: 4px;
  box-shadow: 0 6px 20px rgba(26,24,20,0.22);
  max-width: 92vw;
  text-align: center;
  animation: toast-in 0.18s ease-out;
}
.toast.leaving { opacity: 0; transition: opacity 0.45s ease-in; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (max-width: 640px) {
  #toast-host { bottom: 14px; }
  .toast { font-size: 11px; padding: 10px 14px; }
}

/* The booking reference, wherever it appears alongside other card detail. */
.ref {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--text);
  opacity: 0.75;
}

/* ---------- Drag across days to book ---------- */
.tl-cell.picking { background: rgba(46,90,140,0.10); }
.tl-drag-preview {
  align-self: center;
  margin: 3px 2px;
  border-radius: 3px;
  padding: 3px 7px;
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-align: center;
  background: rgba(46,90,140,0.18);
  border: 1px dashed var(--blue-text, #2e5a8c);
  color: var(--blue-text, #2e5a8c);
  z-index: 4;                 /* above bars, so the range stays readable */
  pointer-events: none;       /* must never swallow the pointer mid-drag */
}
/* While dragging, stop the browser turning the gesture into a text selection. */
.timeline.picking-range { user-select: none; cursor: crosshair; }


/* ---------- Reclaiming planner height on desktop ----------
   The planner is bounded by the window, because the frozen date row is sticky
   inside it and only stays put while the whole frame is on screen. So the way to
   make it taller is to give it more of the window: everything above it comes
   down a little. Phones already have their own, tighter set of these. */
@media (min-width: 641px) {
  header { padding: 0.8rem 2rem 0; }
  header h1 { font-size: 1.35rem; }
  .header-company { margin-top: 2px; font-size: 10px; }
  .header-logo { margin-top: 4px; }
  .header-logo img { max-height: 26px; }
  .header-top { gap: 0.8rem; }

  .main { padding-top: 1.1rem; }
  .planner-bar { margin-bottom: 0.6rem; }
  #view-bookings .panel-toggle { margin-bottom: 0.6rem; }

  /* Shorter vehicle rows. This is the biggest lever on how many vehicles fit,
     since the planner itself cannot grow past the window. The name keeps its
     size — it is what you read — while the padding, the leading, and the plate
     underneath all come down a step. About 34px a row becomes about 26px, which
     is three or four more vehicles on a laptop. */
  #view-bookings .tl-car { padding-top: 3px; padding-bottom: 3px; line-height: 1.15; }
  #view-bookings .tl-car strong { line-height: 1.15; }
  #view-bookings .tl-car .tl-plate { font-size: 8px; line-height: 1.1; }
  #view-bookings .tl-cell { padding-top: 2px; padding-bottom: 2px; }
  #view-bookings .tl-bar { margin-top: 2px; margin-bottom: 2px; }
}

/* ---------- Staff board (Tasks) ----------
   One column per person, one row per day. A grid rather than a table so the
   header row and the day column can be frozen the same way the planner's are. */
.staff-board {
  display: grid;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  /* Sideways only. The board grows to fit its rows and the page owns vertical
     scrolling — same fix as the bookings planner, same reason: two nested
     vertical scrollbars made the wheel unpredictable. */
  overflow-x: auto;
  overflow-y: visible;
  margin-bottom: 2rem;
  -webkit-overflow-scrolling: touch;
}
.board-cell {
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 6px;
  min-width: 0;              /* or a long chip props the whole column open */
  /* Taller rows: an empty day is a drop target, and 44px was a thin one to
     hit with a dragged chip. */
  min-height: 72px;
}
.board-head, .board-corner {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--bg);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 8px 6px;
  min-height: 0;
}
.board-corner { left: 0; z-index: 5; }
.board-day {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--surface);
  font-size: 11px;
  color: var(--text);
}
.board-day.today { background: var(--bg); font-weight: 500; }
/* Unassigned is the column worth noticing, so it is tinted rather than left to
   look like just another person. */
.board-cell.unassigned { background: rgba(138,92,0,0.045); }

.board-chip {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-size: 10px;
  line-height: 1.35;
  padding: 4px 6px;
  margin-bottom: 4px;
  border-radius: 3px;
  border-left: 3px solid var(--border);
  background: var(--bg);
  cursor: pointer;
}
.board-chip:last-child { margin-bottom: 0; }
.board-chip.kind-delivery { border-left-color: var(--amber-text); }
.board-chip.kind-recovery { border-left-color: var(--green-text); }
.board-chip.kind-task     { border-left-color: var(--blue-text); }
.board-chip.late          { border-left-color: var(--red-text); }
.board-chip.done { opacity: 0.45; text-decoration: line-through; }
.board-chip-time { font-variant-numeric: tabular-nums; color: var(--muted); flex-shrink: 0; }
.board-chip-kind { font-size: 8px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); flex-shrink: 0; }
/* The full job name shows — chips wrap instead of cutting off with "…".
   The rows grow to fit; a truncated task name on a work board was a task
   nobody could read. */
.board-chip-main { min-width: 0; white-space: normal; overflow-wrap: break-word; line-height: 1.35; }
.board-dim { color: var(--muted); }

/* Same frozen-header pattern as the Bookings planner: the staff-name row
   lives in its own strip that sticks to the top of the page, and JS mirrors
   the board's sideways scroll into it. It cannot live inside the board — a
   horizontally-scrolling container captures position:sticky. */
#view-tasks .board-head-wrap,
#view-expenses .board-head-wrap {
  position: sticky;
  /* True top, like the planner's date strip — NOT env(safe-area-inset-top).
     That offset pinned the strip BELOW the status bar, so scrolled rows
     stayed readable in the strip above it in the installed app (pilot's
     screenshots, 27 Aug 09:23). The strip itself masks that zone while
     pinned — the .hdr-pinned rule further down, the planner's sentinel
     technique. In the browser env() is 0, so nothing there ever changes. */
  top: 0;
  z-index: 20;
  overflow-x: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 4px 4px 0 0;
}
#view-tasks .board-head-wrap .staff-board,
#view-expenses .board-head-wrap .staff-board {
  border: none;
  border-radius: 0;
  margin-bottom: 0;
  background: transparent;
  overflow: visible;
}
/* The expenses board is a spreadsheet: it scrolls BOTH axes inside itself,
   so its sticky header cells freeze against its own scroll — the one
   arrangement where position:sticky provably works here. Capped so the
   controls above stay reachable. */
#view-expenses [data-el="xboard"] {
  /* Sideways only — the page owns vertical scrolling, like the planner.
     The window-in-a-window double scroll was the pilot's complaint (24 Aug). */
  overflow-x: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
}
#view-tasks [data-el="board"] {
  overflow-x: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
  border-top: none;
  border-radius: 0 0 4px 4px;
}

/* A phone cannot show three or four readable columns at once, so the board
   scrolls sideways there rather than squeezing every name to nothing.
   (No height cap here any more — the page owns vertical scrolling on phones
   too; a leftover cap was quietly reintroducing the nested scrollbar.) */
@media (max-width: 640px) {
  .board-cell { padding: 4px; min-height: 38px; }
  /* Wider columns carry the text now, so it can be big enough to read. */
  .board-chip { font-size: 11px; padding: 4px 6px; }
}

/* ---------- Board width ----------
   Lists read better in a narrow centred column, but a board with a column per
   person needs room or the people fall off the right edge. Same breakout the
   Bookings planner uses: step outside the page column, up to 1700px, never wider
   than the window. Self-limiting, so a phone is unaffected — there the board is
   already as wide as the column and it scrolls sideways instead. */
@media (min-width: 641px) {
  #view-tasks [data-el="board"],
  #view-tasks .board-head-wrap,
  #view-expenses .board-head-wrap {
    --board-w: min(100vw - 2rem, 2600px);
    width: var(--board-w);
    margin-left: calc(-1 * max(0px, (var(--board-w) - 100%) / 2));
  }
  /* The strip's inner grid must not double the breakout shift */
  #view-tasks .board-head-wrap .staff-board { width: 100%; margin-left: 0; }
  /* The expenses header's width is set in pixels by JS, measured from the
     board itself (see syncHeadWidth) — CSS width rules on two separate grids
     produced different column tracks on phones. */
  #view-expenses .board-head-wrap .staff-board { min-width: 100%; margin-left: 0; }
}

/* A card jumped to from a planner. Marked briefly so it is obvious which of
   twenty near-identical vehicles was meant, then fades back to normal. */
.item-card.just-focused {
  box-shadow: 0 0 0 2px var(--blue-text);
  transition: box-shadow 0.25s ease-out;
}

@media (max-width: 640px) {
  /* On a phone the row becomes two stacked groups rather than a wrapping line,
     so Save is always the full-width control at the bottom where a thumb is. */
  .modal-actions { gap: 6px; }
  .modal-actions-side { width: 100%; margin-right: 0; }
  .modal-actions-side .btn { flex: 1 1 auto; }
  .modal-actions .btn-primary { flex: 1 1 100%; padding: 11px; }
  .modal-actions > .btn { flex: 1 1 auto; }
}

/* The Tasks controls share the Bookings layout, but its filter rows need to sit
   tight rather than each claiming a full line of their own. */
#view-tasks .planner-bar .filter-row { margin-bottom: 0; }
#view-tasks .planner-bar .cal-nav { gap: 6px 10px; }
@media (min-width: 641px) {
  /* The toolbar owns the whole row now that every control lives on it — the
     old 380px basis was for sharing the row, and made the merged line wrap
     inside a narrow box as if nothing had changed. */
  #view-tasks .planner-bar .toolbar { flex: 1 1 100%; }
}

/* ---------- Board: add button, tick box, job detail ---------- */
/* Faint until the cell is hovered, so a grid of plus signs does not compete with
   the jobs themselves. On a touchscreen there is no hover, so it stays visible. */
.board-add {
  display: block;
  width: 100%;
  border: 1px dashed transparent;
  border-radius: 3px;
  background: transparent;
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1;
  padding: 5px 0;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s;
}
.board-cell:hover .board-add { opacity: 1; border-color: var(--border); }
.board-add:hover { color: var(--text); border-color: var(--text); }
@media (hover: none) {
  .board-add { opacity: 0.55; border-color: var(--border); }
}

.board-tick {
  flex-shrink: 0;
  width: 13px; height: 13px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: var(--surface);
  font-size: 9px;
  line-height: 11px;
  text-align: center;
  cursor: pointer;
}
.board-tick.disabled { border-style: dashed; cursor: default; }

.jd-list { display: flex; flex-direction: column; gap: 2px; margin-bottom: 0.5rem; }
.jd-row { display: flex; gap: 10px; font-size: 12px; padding: 5px 0; border-bottom: 1px solid var(--border); }
.jd-row:last-child { border-bottom: 0; }
.jd-k { flex: 0 0 110px; color: var(--muted); }
.jd-v { flex: 1; min-width: 0; font-weight: 500; }
@media (max-width: 640px) {
  .jd-k { flex-basis: 92px; }
  .jd-row { font-size: 11px; }
}

/* A field row where every field is the same width. The default makes the first
   one wider, which suits a date beside a time but left the three staff fields
   visibly out of step with each other. */
.field-row.equal .field:first-child { flex: 1; }

/* A quiet "optional" beside a label, so a blank field does not look unfinished. */
.field-hint { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--muted); }

/* ---------- Dragging a job to another person ---------- */
.staff-board.dragging-chip { cursor: grabbing; user-select: none; }
.staff-board.dragging-chip .board-chip { cursor: grabbing; }
/* Only the cell you are actually over lights up, and only when the drop would
   do something — the row it came from and its own column stay plain. */
.board-cell.drop-target {
  background: rgba(46,90,140,0.12);
  outline: 2px dashed var(--blue-text);
  outline-offset: -3px;
}

/* "Picked up": the moment a press-and-hold arms a finger drag. Without it a
   phone gives no sign that holding did anything, and the natural response is
   to keep pressing harder or give up (26 Aug). Chips are never scaled by more
   than a hair — a board is dense and a big lift would cover its neighbours. */
.board-chip.chip-held, .chip-held {
  transform: scale(1.04);
  box-shadow: 0 4px 12px rgba(0,0,0,0.22);
  position: relative;
  z-index: 5;
}
@media (prefers-reduced-motion: no-preference) {
  .board-chip, [data-chip] { transition: transform 0.12s ease, box-shadow 0.12s ease; }
}

/* ---------- Multi-select picker ---------- */
.multi-pick { position: relative; display: inline-block; }
.multi-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 60;                 /* above the board's sticky header row */
  min-width: 170px;
  max-height: 260px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 6px 18px rgba(26,24,20,0.14);
  padding: 5px;
}
.multi-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 6px;
  border-radius: 3px;
  font-size: 12px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.multi-row:hover { background: var(--bg); }
.multi-clear {
  display: block;
  width: 100%;
  text-align: left;
  font-family: var(--font-ui);
  font-size: 11px;
  padding: 5px 6px;
  margin-bottom: 3px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  cursor: pointer;
}
.multi-clear:hover { color: var(--text); }

/* ---------- Moving a booking to another car ---------- */
.timeline.moving-bar { cursor: grabbing; user-select: none; }
.timeline.moving-bar .tl-bar { cursor: grabbing; }
/* The whole target row lights up, not just the cell under the pointer — the row
   is what the booking is moving to, so that is what should be shown. */
.tl-cell.move-target { background: rgba(46,90,140,0.14); }

.move-summary { margin-bottom: 1rem; }
.move-summary .jd-row:last-child { border-bottom: 0; }

/* The saved customer's contact details on the booking form — information, not a
   field, so it reads quietly until something is missing. */
.contact-note { margin: -0.4rem 0 0.9rem; }
.contact-note.warn { color: var(--red-text); }

/* ---------- Contact details on the booking form ---------- */
.contact-box { margin: -0.4rem 0 1rem; }
.contact-edit { display: flex; gap: 8px; flex-wrap: wrap; margin: 6px 0; }
.contact-edit input { flex: 1 1 150px; min-width: 0; }
.contact-edit .btn-primary { flex: 0 0 auto; }
.contact-links { display: flex; gap: 14px; flex-wrap: wrap; }
/* Plain text buttons: these are asides, not actions competing with Save. */
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}
.link-btn:hover { color: var(--text); }
@media (max-width: 640px) {
  .contact-edit .btn-primary { flex: 1 1 100%; }
}

/* The price box under "Set a price for this rental" */
.move-custom-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 2px 0 6px 22px; }
.move-custom-row input { flex: 0 1 200px; min-width: 0; }
.move-custom-row .settings-note { margin: 0; }

/* ---------- Draggable things must not be selectable ----------
   These all carry text, and pressing on text and moving starts a native text
   selection — at which point the browser fires pointercancel and the drag is
   abandoned before it begins. Until the capture was moved off pointerdown (to
   stop it stealing the click) that side effect was suppressing the selection;
   this does the job explicitly instead of relying on it. */
.tl-bar,
.tl-oos-bar,
.tl-car,
.board-chip {
  user-select: none;
  -webkit-user-select: none;
}

/* ---------- Skipped-days markers on the Tasks list and board ----------
   Drawn wherever the day rows jump over an empty stretch, so two rows months
   apart can never be mistaken for neighbouring days. Clicking one reveals the
   days it stands for, so a task can be dragged onto any date. */
.day-gap, .board-gap {
  display: block;
  width: 100%;
  font-family: inherit;
  text-align: center;
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 0.06em;
  padding: 7px 0;
  background: var(--bg);
  border: none;
  border-top: 1px dashed var(--border);
  border-bottom: 1px dashed var(--border);
  cursor: pointer;
}
.day-gap:hover, .board-gap:hover {
  color: var(--text);
  background: var(--surface);
}
.day-gap { margin: 10px 0; }
.board-gap { grid-column: 1 / -1; }

/* A day with no jobs still shows its heading and its "+" — slightly quieter,
   so the days with work stand out. */
.day-group.empty .day-head { opacity: 0.75; }
.day-group.empty { margin-bottom: 4px; }

/* ---------- Month boundaries on the planners ----------
   The first day of a month names the month where the weekday would sit, and
   the boundary carries a line down the whole grid — so day 31 next to day 1
   can never be read as one continuous month. */
.tl-daynum.month-start { border-left: 2px solid #b9b29f; }
.tl-daynum.month-start .dow {
  color: var(--text);
  font-weight: 500;
  opacity: 1;
}
.tl-daynum.today.month-start .dow { color: var(--bg); }
.tl-cell.month-start { border-left: 2px solid #d8d2bf; }

/* ---------- Range dropdowns on the Tasks page ----------
   Styled to sit in the tab row as peers of the Today tab. When a side holds
   the current range it shows its choice and darkens like an active tab. */
.tab-select {
  appearance: auto;
  color: var(--muted);
  padding-right: 6px;
  max-width: 180px;
}
.tab-select.active {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--text);
}

/* (the Tasks bar sizes were promoted to the desktop-wide CONTROL STANDARD, 25 Aug) */

#view-tasks .planner-bar select { min-width: 0; }
#view-tasks .planner-bar .tab-select { max-width: 150px; }
#view-tasks .planner-bar .toolbar,
#view-tasks .planner-bar .filter-row { gap: 6px; margin-bottom: 0; }
#view-tasks .planner-bar .cal-nav { gap: 6px; }

/* On a phone: the search gets its own full line, every control drops to a
   smaller size, and the range row — Past · Today · Ahead — is locked to one
   line: the two dropdowns share the space Today leaves, instead of the third
   control falling onto a lonely line of its own. Desktop is untouched. */
@media (max-width: 640px) {
  #view-tasks .planner-bar input[type="text"] { flex: 1 1 100%; }
  /* (sizes come from the CONTROL STANDARD — the old 26px compact set
     contradicted the 44px mobile rule and is gone, 25 Aug) */
  /* Air between the rows — the compact controls read as cramped when the
     lines sit tight on each other. Horizontal gaps stay small; it is the
     vertical rhythm that was missing. */
  #view-tasks .planner-bar { row-gap: 14px; }
  #view-tasks .planner-bar .toolbar { row-gap: 14px; }
  #view-tasks .planner-bar .cal-nav { row-gap: 14px; }
  #view-tasks .planner-bar .filter-row { flex-wrap: nowrap; }
  #view-tasks .planner-bar .tab-select {
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
    width: auto;
  }
  #view-tasks .planner-bar .filter-row .tab:not(.tab-select) { flex: 0 0 auto; }
}

/* Brief outline on an invoice jumped to from its booking, so the eye lands on
   the right card among many. */
.item-card.card-flash { outline: 2px solid var(--text); outline-offset: 2px; }
/* The same brief outline for a table row. .card-flash is scoped to .item-card
   and does nothing on a <tr>, and an outline on a row is unreliable across
   browsers anyway — the cells carry it instead (25 Aug, "View invoice" now
   lands on the Reports register). */
.inv-row.row-flash > th, .inv-row.row-flash > td {
  background: var(--bg);
  box-shadow: inset 0 2px 0 var(--text), inset 0 -2px 0 var(--text);
}
.inv-row.row-flash > th.rep-car { box-shadow: inset 2px 0 0 var(--text), inset 0 2px 0 var(--text), inset 0 -2px 0 var(--text); }

/* The floating date tag that follows the cursor during planner drags. Dark on
   light so it reads over any bar colour; never catches the pointer itself. */
.drag-tip {
  position: fixed;
  z-index: 999;
  display: none;
  pointer-events: none;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.03em;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 4px 9px;
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  white-space: nowrap;
}
.drag-tip.on { display: block; }

/* The Dashboard is an overview, and an overview earns the window: the whole
   view section steps out of the reading column, and the alert bars, the
   Today/Money cards and the mini planner all fill it naturally. Same
   self-limiting formula as the planner and the boards; phones unaffected. */
@media (min-width: 641px) {
  #view-dashboard.view {
    --dash-w: min(100vw - 2rem, 2600px);
    width: var(--dash-w);
    margin-left: calc(-1 * max(0px, (var(--dash-w) - 100%) / 2));
  }
}

/* Settings gets the breakout too: its two-column layout (company details
   beside backup) was designed for the reading column and ends up with wide
   dead margins on a big screen. Same formula, phones unaffected. */
@media (min-width: 641px) {
  #view-settings.view {
    --settings-w: min(100vw - 2rem, 2600px);
    width: var(--settings-w);
    margin-left: calc(-1 * max(0px, (var(--settings-w) - 100%) / 2));
  }
}

/* The whole Expenses view gets the window, like the Dashboard and Settings:
   with only the board breaking out, the search, filters, totals and category
   strip crowded the narrow centre while the sides sat empty. Inside the
   full-width view the board's own breakout resolves to zero shift, so the two
   rules coexist harmlessly. */
@media (min-width: 641px) {
  #view-expenses.view {
    --exp-w: min(100vw - 2rem, 2600px);
    width: var(--exp-w);
    margin-left: calc(-1 * max(0px, (var(--exp-w) - 100%) / 2));
  }
  #view-expenses [data-el="xboard"] {
    --board-w: min(100vw - 2rem, 2600px);
    width: var(--board-w);
    margin-left: calc(-1 * max(0px, (var(--board-w) - 100%) / 2));
  }
}

/* Expenses board: the cell a dragged chip would land in, and settled entries. */
.board-cell.drop-target { outline: 2px dashed var(--muted); outline-offset: -2px; }
.item-card.expense-done { opacity: 0.55; }
.item-card.expense-done .card-title { text-decoration: line-through; }

/* The damage diagram and signature pad in the booking form. */
.damage-wrap svg { width: 100%; height: auto; display: block; touch-action: none; cursor: crosshair; }
.damage-row { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
.damage-row input { flex: 1; }
.sign-pad { width: 100%; height: auto; border: 1px solid var(--border); border-radius: 4px; background: #fff; touch-action: none; display: block; }

/* The damage, signature and receipt-number dialogs open from inside the booking
   form, so they must paint above it — explicit stacking, immune to DOM order. */
.overlay[data-el="damage-modal"],
.overlay[data-el="receipt-modal"],
.overlay[data-el="invoice-modal"],
.overlay[data-el="sign-modal"] { z-index: 110; }

/* Per-category totals on the Expenses page: a strip of small cards that
   always matches whatever the filters currently show. */
.cat-total-strip {
  /* A grid, so every box is the same width and every row the same height —
     chips sized by their own text made a ragged shelf (pilot, Aug 16). The
     phone keeps its one-line scroll strip via the mobile override below. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 14px;
}
.cat-total {
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 7px 12px;
  background: var(--surface);
  min-width: 120px;
  flex: 1 1 180px;
  max-width: 380px;
}
.cat-total-name {
  font-family: var(--font-ui);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.cat-total-val { font-size: 1.15rem; line-height: 1.35; }
.cat-total-sub { font-size: 10px; color: var(--muted); }
.board-head-total {
  display: block;
  font-size: 9.5px;
  color: var(--muted);
  letter-spacing: 0;
  text-transform: none;
  margin-top: 2px;
}
@media (max-width: 640px) {
  .cat-total { min-width: 100px; padding: 6px 9px; }
  .cat-total-val { font-size: 1rem; }
}

/* ---------- Wide layout, remaining pages ----------
   Fleet, Customers, Billing and Maintenance now take the window like the
   others. Their card lists become a responsive grid rather than one column of
   very wide cards: cards keep a comfortable reading width and simply sit side
   by side when there is room. */
@media (min-width: 641px) {
  /* Bookings and Tasks join the whole-view breakout: without it their
     toolbars and Summary buttons stayed in the centred column — indented,
     narrow, and wrapping — while every other page had moved on. Their inner
     planner/board breakouts resolve to zero shift inside a full-width parent,
     so the old rules coexist harmlessly. */
  #view-bookings.view,
  #view-tasks.view,
  #view-fleet.view,
  #view-customers.view,
  #view-billing.view,
  #view-maintenance.view {
    --page-w: min(100vw - 2rem, 2600px);
    width: var(--page-w);
    margin-left: calc(-1 * max(0px, (var(--page-w) - 100%) / 2));
  }

  /* Fleet cards: capped width (pilot: "too much space in the middle") — on a
     wide screen more, narrower cards instead of two enormous ones. */
  #view-fleet .card-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
    gap: 10px;
  }

  #view-customers .card-list,
  #view-billing .card-list,
  #view-maintenance .card-list {
    display: grid;
    /* Two equal columns, and every row the same height as the tallest card —
       the grid reads as uniform tiles rather than a staggered mosaic. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 1fr;
    gap: 10px;
  }

  /* A search field does not need the whole window — long enough to read a
     typed query, then the other controls sit beside it. */
  .toolbar input[type="text"] { max-width: 460px; }
}

/* The Expenses totals sentence rides on the search line, pushed to the right
   so the controls keep the left. It drops onto its own line on a phone, where
   the toolbar wraps anyway. */
.inline-total {
  margin: 0 0 0 18px;
  align-self: center;
  white-space: nowrap;
}
@media (max-width: 640px) {
  .inline-total { margin-left: 0; white-space: normal; }
}

.stat-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }

/* One control line on the Expenses page: everything vertically centred so
   inputs, buttons, tabs and selects sit on a shared axis, with quiet
   small-caps labels instead of heavy text between groups. Wraps in order on
   narrower windows. */
.one-line-tools { align-items: center; }
.inline-label {
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-left: 14px;
}

/* Tasks: the whole control set on one centred line, like Expenses. The two
   filter groups keep their internal grouping and wrap as units on narrower
   windows; the compact uniform control size from the toolbar pass keeps the
   shared axis clean. */
#view-tasks .planner-bar .toolbar { align-items: center; }
#view-tasks .planner-bar .filter-row { display: inline-flex; }

/* One line for the Tasks controls even on a laptop: the search field is the
   flexible member — it compresses before anything wraps, down to a still
   typeable minimum. Everything else keeps its natural size. */
@media (min-width: 641px) {
  /* The twelve controls must genuinely fit ~1150px-wide windows, which means
     reclaiming real width: the search compresses first (still typeable), the
     three dropdowns are capped (they are mostly empty air), and paddings
     tighten one notch. Verified by arithmetic: the set now sums to ~1030px. */
  #view-tasks .planner-bar input[type="text"] {
    flex: 0 1 300px;
    min-width: 110px;
  }
  #view-tasks .planner-bar select,
  #view-tasks .planner-bar select[data-el="kind-filter"] { max-width: 120px; }
  #view-tasks .planner-bar .tab-select { max-width: 112px; }
  #view-tasks .planner-bar .toolbar { gap: 6px; }
  #view-tasks .planner-bar .filter-row { gap: 6px; }
}

/* Expenses summary behind the same toggle as other pages: the stat cards and
   the category strip hide together. */
#view-expenses.hide-summary .stats,
#view-expenses.hide-summary [data-el="cat-totals"] { display: none; }

/* The Summary toggle rides at the far right of each page's control line. */
.toolbar .push-right { margin-left: auto; }
@media (max-width: 640px) { .toolbar .push-right { margin-left: 0; } }

/* ---------- Full-bleed grids on a phone ----------
   Lists and cards want the page margin; the three grids do not. The planner
   and the two boards are the only places where a column falls off the right
   edge, and on a phone every pixel of margin is a slice of calendar or a
   staff column paid for in sideways scrolling. So on a phone alone these
   three step outside .main's padding and run to both screen edges.

   The width is 100% plus the padding it is cancelling, and the left margin
   pulls back by that same padding, so the right edge lands exactly on the
   screen edge — no page-wide sideways scroll. Side borders and corner radii
   go with it: a rounded corner hard against the glass reads as a mistake.

   Everything above each grid — search, tabs, date controls, the legend —
   keeps the normal margin, so the controls stay in line with the rest of the
   app and only the grid itself widens. */
@media (max-width: 640px) {
  /* Full-bleed belongs to SCROLLERS and page-pinned WRAPS only — named by
     data-el so the head grids inside the wraps (which share the .staff-board
     class) can never inherit it. The generic .staff-board selector bled the
     inner grids a second time: dates clipped off the left of the screen and
     the expenses header sat a page-padding away from its own columns
     (pilot's circled screenshots, 24 Aug). */
  #view-bookings .tl-head-wrap,
  #view-bookings .timeline-wrap,
  #view-tasks .board-head-wrap,
  #view-expenses .board-head-wrap,
  #view-tasks [data-el="board"],
  #view-expenses [data-el="xboard"],
  #view-reports .rep-head-wrap,
  #view-reports .rep-wrap {
    width: calc(100% + 2 * var(--main-pad-x, 0.9rem));
    margin-left: calc(-1 * var(--main-pad-x, 0.9rem));
    border-left: none;
    border-right: none;
    border-radius: 0;
  }

  /* The frozen date/name strip holds its own copy of the grid. It must fill
     that strip and not shift again, or the two would sit a padding apart and
     the columns would stop lining up. */
  .board-head-wrap .staff-board {
    width: 100%;
    margin-left: 0;
  }
}

/* ---------- Bigger tick boxes on a phone ----------
   The board's tick is 13px square. On a desktop that is a precise little
   control under a mouse pointer; under a thumb it is a miss waiting to happen,
   and the pilot's staff mark jobs done on their phones all day. It roughly
   doubles on a phone — 24px, which is 3.4 times the area — and centres itself
   in the chip rather than sitting on the text baseline, where a taller box
   would hang low.

   Deliberately not stretched into an invisible 44px pad: chips sit a few pixels
   apart in a cell, and a pad that big would swallow taps meant for the chip
   beside it and mark the wrong job done. A visible box you can actually see and
   hit beats a large invisible one.

   Desktop is untouched — every rule here is inside the phone breakpoint. */
@media (max-width: 640px) {
  .board-tick {
    width: 24px;
    height: 24px;
    /* Top-aligned, not centred: a chip whose text wraps to four lines would
       float the box halfway down it, away from the line it belongs to. The
       chip's own alignment is baseline, which suits a 13px box and hangs a
       24px one well below the first line. */
    align-self: flex-start;
    border-radius: 4px;
    font-size: 15px;
    line-height: 22px;
  }

  /* The same job in List mode. 22px was already the better of the two, but it
     is still under the thumb and the two views should not disagree. */
  #view-tasks .job-tick {
    width: 30px;
    height: 30px;
    font-size: 16px;
  }
}

/* ---------- Reports: revenue by car and month ----------
   Twelve money columns plus a total and an average will not fit a phone and
   should not try: shrinking them to fit produces a grid nobody can read a
   figure out of. The table keeps a legible size and the box scrolls sideways,
   with the car column frozen so a row never loses the vehicle it belongs to —
   the same treatment the planner and the boards get. */
.rep-title { font-family: var(--font-display); font-size: 1.05rem; font-weight: 400; margin: 0 8px 0 0; }

.rep-wrap {
  /* Sideways only. The page owns vertical scrolling — a height cap here is
     the window-in-a-window the pilot rejected (24 Aug), and it came back by
     mistake on 26 Aug when the frozen heading was first attempted this way.
     The heading is frozen instead by the boards' method: a copy of it lives
     OUTSIDE this scroller (.rep-head-wrap), sticks to the page, and mirrors
     this element's sideways scroll. Same rule as the planner and the two
     boards — nothing that must stay still may live inside a scrolled box. */
  overflow-x: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface);
  margin-bottom: 1rem;
}

/* The frozen heading. Sticky against the PAGE, sized and positioned in JS to
   sit exactly over the real heading row, which stays in the table so the
   columns keep their natural widths. At rest it covers the real one exactly;
   once the table scrolls up it stays behind while the rows move under it.
   overflow-x hidden, never auto: it is scrolled only by the mirror, so it
   can never disagree with the table or add a second scrollbar. */
.rep-region { position: relative; }
.rep-head-wrap {
  position: sticky;
  /* True top, like the planner and the boards — NOT env(safe-area-inset-top),
     which pinned it below the status bar and left scrolled rows readable in
     the strip above it in the installed app. Masked while pinned by the
     .hdr-pinned rule further down; in the browser env() is 0 either way. */
  top: 0;
  z-index: 6;
  overflow-x: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: 4px 4px 0 0;
  display: none;
}
.rep-head-wrap .rep-table { margin: 0; background: transparent; }
.rep-head-wrap .rep-table thead th { position: static; }
/* ...except the frozen first column. The copy's car cell must pin left
   exactly like the real one while the mirror scrolls the months beneath it,
   and the resize grip positions against it — sticky IS a positioning
   context (the planner's corner, same lesson). Making every cloned cell
   static was what let the copy's first column drift sideways and sent its
   grip floating over the month headings, dead to the drag listener.
   top: auto — the only stickiness here is sideways; the wrap owns vertical. */
.rep-head-wrap .rep-table thead th.rep-car { position: sticky; left: 0; top: auto; }

.rep-table { border-collapse: collapse; width: 100%; font-size: 12px; }
.rep-table th, .rep-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; }
/* A heading sits over its own column: left above words, right above figures.
   The default was right for every heading, which read correctly over the
   money columns and wrongly over Issued / Period / Customer / Vehicle /
   Broker, whose text starts at the left (pilot, 25 Aug 18:04).
   .rep-num is restated with the thead prefix on purpose — a bare .rep-num
   scores (0,1,0) and would lose to .rep-table thead th at (0,1,2), which is
   how the money headings would have silently gone left. */
.rep-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg);
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted); font-weight: 400; text-align: left;
}
.rep-table thead th.rep-num { text-align: right; }
.rep-table thead th.rep-car { text-align: left; z-index: 4; }

/* The vehicle stays put while the months scroll past it. */
/* The first column's width is the person's to set (the grip in its heading,
   same control as the planner's). --rep-car-w is written on the scroll
   wrapper by the drag; with no drag yet the fallbacks below are the old
   fixed widths, so nothing changes until the handle is used.
   max-width matters as much as min-width: without it the column simply grows
   back to its content and the handle appears to do nothing in one direction. */
.rep-car {
  position: sticky; left: 0; z-index: 3;
  background: var(--surface);
  text-align: left; font-weight: 400;
  min-width: var(--rep-car-w, 190px);
  max-width: var(--rep-car-w, none);
  overflow: hidden;
}
/* Narrowed, the contents must trail off rather than spill over the figures.
   inline-block, not block: the chevron sits beside the number and a block
   would push it onto its own line. */
.rep-car .inv-open {
  display: inline-block; vertical-align: bottom;
  max-width: calc(100% - 18px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rep-car .rep-model, .rep-car .rep-plate {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Room for the grip so it never sits on top of the heading word. */
.rep-table thead th.rep-car { padding-right: 32px; }
.rep-table thead .rep-car, .rep-table tfoot .rep-car { background: var(--bg); }
.rep-plate { display: block; font-weight: 500; }
.rep-model { display: block; font-size: 10px; color: var(--muted); margin-top: 1px; }

.rep-num { text-align: right; font-variant-numeric: tabular-nums; }
.rep-strong { font-weight: 500; border-left: 1px solid var(--border); }
.rep-zero { color: var(--border); }
.rep-idle .rep-car { opacity: 0.55; }

.rep-table tfoot th, .rep-table tfoot td {
  background: var(--bg);
  border-top: 1px solid var(--text);
  border-bottom: 0;
  font-weight: 500;
  position: sticky; bottom: 0;
}
/* Same rule below the table: a th spanning the text columns is a label, not a
   figure, so it starts where those columns start rather than floating in the
   middle of them (the Billing tab's "N bookings"). */
.rep-table tfoot th { text-align: left; }
.rep-table tfoot th.rep-num { text-align: right; }

.rep-note {
  font-size: 11px; color: var(--muted); line-height: 1.6;
  max-width: 62ch; margin: 0 0 2rem;
}

@media (max-width: 640px) {
  .rep-table th, .rep-table td { padding: 6px 8px; }
  .rep-car { min-width: var(--rep-car-w, 150px); }   /* the drag still wins on a phone */
  .rep-title { font-size: 1rem; }
}

/* ---------- Reports ----------
   One report at a time behind a tab row, so each one gets the whole width.
   The first attempt stacked all three as collapsible panels — what Arshad
   asked for — but with three headings on screen and a single open table, no
   heading obviously owned what you were looking at, and nothing got the width
   a fifteen-column grid needs. */
.rep-headline {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 400;
  margin: 0.2rem 0 0.9rem;
}

@media (max-width: 640px) {
  .rep-headline { font-size: 1.1rem; }
  /* Three controls on one line squeezed the year box to a single digit. It
     needs room for a four-digit year whatever else is on the row. */
  #view-reports [data-el="rep-year"] { min-width: 84px; }
}

/* Month by month has fewer columns than the car grids, so it carries the
   working behind the occupancy figure — days rented out of days available —
   rather than leaving most of the row empty. It fills the width like the rest. */
.rep-months { width: 100%; min-width: 620px; }
.rep-neg { color: var(--red-text); }

/* ---------- Reports get the window ----------
   Two of the three reports are fifteen-column grids. Held inside the reading
   column they spent most of a wide screen on empty margin while the months
   scrolled sideways out of view — the worst of both. Same breakout the
   Dashboard, Settings and Expenses views use, so the page still ends where
   every other page ends. Phones are untouched: min() makes it self-limiting
   and the rule only applies above the phone breakpoint. */
@media (min-width: 641px) {
  #view-reports.view {
    --rep-w: min(100vw - 2rem, 2600px);
    width: var(--rep-w);
    margin-left: calc(-1 * max(0px, (var(--rep-w) - 100%) / 2));
  }
}


/* ---------- Fleet card as a working table ----------
   Compact (pilot: "no reason for all the extra space"), and every value is an
   input: type, leave the field, saved. Cells look like printed figures until
   touched — a border appears on hover and focus. Past dates red, dates inside
   30 days amber, on the field itself. */
.item-card.car-compact { padding: 8px 12px 6px; }
.car-compact .card-actions { margin-top: 6px; padding-top: 6px; }
.car-rows { margin-top: 6px; padding-top: 4px; border-top: 1px solid var(--border); }
.car-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 21px; }
.car-row + .car-row { border-top: 1px dashed color-mix(in srgb, var(--border) 55%, transparent); }
.car-row-l { color: var(--muted); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; }
.car-cell {
  font-family: var(--font-ui); font-size: 16px; color: var(--text);
  background: transparent; border: 1px solid transparent; border-radius: 3px;
  padding: 2px 6px; text-align: right; width: 132px; min-height: 0;
}
input.car-cell, input.car-cell[type="date"] { min-height: 0; padding: 2px 6px; font-size: 13px; background: transparent; border: 1px solid transparent; }
input.car-cell:hover { border-color: var(--border); }
input.car-cell:focus { border-color: var(--accent); outline: none; background: var(--surface); }
.car-rates { display: flex; gap: 4px; }
.car-rates .car-cell-num { width: 58px; }
.car-cell-num { width: 96px; }
.car-cell-red, .car-cell-amber { border-radius: 3px; }
.car-cell-red .car-cell { color: var(--red-text); font-weight: 500; }
.car-cell-amber .car-cell { color: var(--amber-text); font-weight: 500; }
/* date inputs paint their own chrome on iOS; keep them tight */
.car-cell[type="date"] { width: 132px; padding: 2px 4px; }
.car-compact .card-actions { margin-top: 8px; padding-top: 8px; }

/* ---------- Fuel gauge on the damage diagram ---------- */
.fuel-opts { display: flex; gap: 8px; flex-wrap: wrap; }
.fuel-opt.selected { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* ---------- Invoices tab controls ---------- */
.inv-controls { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; font-size: 12px; color: var(--muted); }
/* On a phone the filter row pins to the top while the register scrolls —
   dates, kind filter and Export stay reachable at row 200 as at row 1. */
@media (max-width: 640px) {
  /* not sticky: the pilot voted these should scroll away with the page
     like everything else (25 Aug) */
  .inv-controls { background: var(--bg); padding: 8px 2px; margin: 0 0 8px; }
}
.inv-controls label { display: flex; gap: 6px; align-items: center; }

/* The filter block collapses on a phone only (25 Aug). The DOM stays flat —
   every control is still a direct child of .inv-controls, so the grid and the
   CONTROL STANDARD sizing below apply unchanged; only visibility moves.
   The desktop hide cannot live here: the CONTROL STANDARD block at the end of
   this file sets display:inline-flex on every .btn inside a control bar, and
   its selector outscores a bare .inv-more-toggle wherever that sits. So the
   toggle appeared on desktop (pilot's screenshot, 25 Aug 17:39). The hide is
   at the foot of this file instead, after that block and above it on
   specificity. */


/* ---------- Appearance picker ---------- */
.theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; }
.theme-opt {
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 11px; cursor: pointer; text-align: left;
}
.theme-opt.selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.theme-chips { display: flex; gap: 4px; }
.theme-chip { width: 22px; height: 14px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.18); }
.theme-pick { display: flex; gap: 8px; align-items: center; }
.theme-pick input[type="color"] {
  width: 52px; height: 38px; padding: 2px;
  border: 1px solid var(--border); border-radius: 4px; background: var(--surface); cursor: pointer;
}


/* The last control row keeps the same distance from the content below it as
   the rows above keep from each other — it was sitting right on the first
   task (pilot, Aug 18). */
/* Collapse-proof: the air lives as padding on the content, not as a margin
   that can vanish into an adjacent one — which is how two attempts at this
   gap produced nothing visible. */
#view-tasks [data-el="list"] { padding-top: 16px; }
#view-tasks .board-head-wrap { margin-top: 16px; }
#view-tasks [data-el="board"] { margin-top: 0; }

/* ---------- Mobile standard (≤640px) ----------
   One layout language for every page: the search bar is always full width on
   its own line, every other toolbar control shares a two-column grid of equal
   44px buttons, and popup field rows stack to a single centred column. */
@media (max-width: 640px) {
  /* Nothing may ever drag the page sideways: the page clips, and every
     control row is forced to fit. The desktop layout's inline margins
     (used to group controls on one wide line) are neutralised here — on a
     phone the grid does the grouping. */
  html, body { max-width: 100vw; overflow-x: clip; }

  .toolbar, .one-line-tools {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
  }
  .toolbar > *, .one-line-tools > * { min-width: 0; margin: 0 !important; }
  .toolbar input[type="text"] { grid-column: 1 / -1; width: 100%; }
  .toolbar .btn, .toolbar .btn-primary, .toolbar select { width: 100%; }
  .toolbar .multi-pick { position: relative; width: 100%; }
  .toolbar .multi-pick .btn { width: 100%; }
  /* a tab strip inside a toolbar takes a full line of its own, tabs 2×2 */
  .toolbar .filter-row { grid-column: 1 / -1; display: flex !important; flex-wrap: wrap; gap: 6px; }

  .planner-bar { max-width: 100%; }
  .planner-bar > *, .cal-header > * { min-width: 0; margin-left: 0 !important; }

  .filter-row { display: flex; flex-wrap: wrap; gap: 6px; margin-left: 0 !important; }
  .filter-row .tab, .filter-row select, .filter-row .btn, .filter-row .btn-primary {
    flex: 1 1 calc(50% - 6px); min-width: 0; text-align: center;
  }

  /* the invoice filters stay visible while the register scrolls */
  #view-reports [data-el="rep-controls"] {
    background: var(--bg); padding: 6px 0;
  }

  /* boards scroll inside themselves, never the page */

  /* the expenses category totals become one thumb-scrollable row of chips
     instead of a stack that fills the screen */
  .cat-total-strip {
    display: flex !important; overflow-x: auto; gap: 6px;
    -webkit-overflow-scrolling: touch; padding-bottom: 4px;
  }
  .cat-total-strip > * { flex: 0 0 auto; min-width: 128px; }

  /* item: nothing sits in a 60%-wide column with dead space beside it */
  /* Boards are deliberately absent here: they have their own full-bleed
     width rules, and a blanket width:100% capped them into a left-shifted
     column with a blank strip on the right (pilot, Aug 2026 — twice). */
  .view, .toolbar, .planner-bar, .card-list { width: 100%; }

  /* checkbox rows (card payment, marked as paid) stack their extras below
     the tick instead of squeezing them into a right-hand sliver */
  .modal .checkbox-field { flex-direction: column !important; align-items: flex-start !important; gap: 8px !important; }

  /* expenses category totals: collapsed behind one button until wanted */
  .cat-totals-toggle { display: block; width: 100%; margin-bottom: 6px; }
  .cat-totals:not(.open) .cat-total-strip { display: none !important; }

  .modal .field-row, .modal .field-row.equal { flex-direction: column; gap: 0; }
  .modal .field { width: 100%; }
  .modal .field input, .modal .field select, .modal .field textarea { width: 100%; }
  .modal-actions .btn { flex: 1 1 auto; }
}

/* invoice numbers open the document */
.inv-open { background: none; border: none; padding: 0; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; }
.inv-open:hover { opacity: 0.75; }

/* ---------- The money block on the booking form ---------- */
.money-block { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--border); }
.money-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 2px 0; font-size: 13px; }
.money-row strong { font-size: 15px; }
.money-due strong { color: var(--red-text); }
.money-settled strong { color: var(--green-text); }

/* Tap anywhere on a card to open its record */
#view-fleet .item-card, #view-bookings .card-list .item-card { cursor: pointer; }


/* Expenses totals toggle — a phone control; desktops always show the strip */
.cat-totals-toggle { display: none; }

/* The money block never lets its last row kiss the action bar */
.money-block { padding-bottom: 6px; }

/* ---------- Money page bits ---------- */
.pay-chip { font-size: 10px; letter-spacing: 0.06em; padding: 2px 7px; border-radius: 3px; }
.pay-chip.paid { background: var(--green-bg); color: var(--green-text); }
.btn-small { padding: 4px 10px; font-size: 11px; min-height: 0; }
.app-version { margin-top: 18px; font-size: 11px; color: var(--muted); text-align: right; }

/* ---------- Payment history rows ---------- */
.pay-row { display: flex; justify-content: space-between; gap: 10px; padding: 4px 0; font-size: 13px; border-bottom: 1px dashed var(--border); }
.pay-row > span:first-child { flex: 0 0 auto; }
.pay-row > span:nth-child(2) { flex: 1; color: var(--muted); }
.pay-amt { font-weight: 500; }
.pay-neg { color: var(--red-text); }
.pay-sum { border-bottom: none; border-top: 1px solid var(--border); }
.pay-note { font-size: 11px; color: var(--muted); padding: 2px 0 6px; }

/* ---------- Version stamp ---------- */
.app-version { margin-top: 20px; font-size: 11px; color: var(--muted); }

/* ---------- Expenses board: grand-total header cell at the right end of
   the frozen row. (First attempt froze the category-chip strip instead —
   wrong row; the pilot meant the board's own header, like Tasks.) ---------- */
.board-grand { box-shadow: inset 3px 0 0 var(--accent); color: var(--text); }
.board-grand .board-head-total { font-weight: 600; }

/* The + square on the colour row */
.swatch-add {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; color: var(--muted);
  background: var(--surface); border: 1px dashed var(--border);
}
.swatch-add:hover { border-color: var(--accent); color: var(--accent); }

/* The + swatch is a label around a real colour input: the input covers the
   label invisibly, so the person's own click opens the native picker — the
   one activation every browser honours. (A programmatic .click() on a
   zero-size input is silently refused by several.) */
.swatch-add { position: relative; overflow: hidden; }
.swatch-add .swatch-pick {
  position: absolute; inset: 0; width: 100%; height: 100%;
  min-height: 0; padding: 0; border: 0; opacity: 0; cursor: pointer;
}


/* ---------- Mobile header (≤640): the sign-out and export buttons live in
   the top-right corner, on the brand's row — not on a second row brushing
   against the tabs, where a thumb aiming for DASHBOARD kept signing the
   pilot out. The Live label shrinks to its dot. ---------- */
@media (max-width: 640px) {
  .header-top { display: grid; grid-template-columns: 1fr auto; align-items: start; row-gap: 2px; }
  .header-top > div:first-child { grid-column: 1; }
  .header-right { grid-column: 2; display: flex; gap: 6px; align-items: center; justify-self: end; }
  .header-right .btn-logout { padding: 6px 10px; font-size: 11px; min-height: 0; }
  .sync-status #sync-label, .sync-status span { display: none; }
  .sync-status { margin-right: 2px; }
}


/* ---------- Installed-app polish ----------
   The things that make a home-screen install read as an app, not a page:
   the phone's notch and home bar get real clearance, taps don't flash grey,
   the page doesn't rubber-band, double-tap doesn't zoom, views fade in, and
   launch shows the wordmark on cream instead of the word "Loading". */

html { -webkit-tap-highlight-color: transparent; }
body { overscroll-behavior-y: none; touch-action: manipulation; }

/* the notch and the home indicator: the header owns the top inset (it is
   dark, so the status bar melts into it), page + pinned bars own the bottom */
header { padding-top: calc(0.7rem + env(safe-area-inset-top)); }

/* a view arrives with a short fade — opacity only, so nothing that measures
   or positions itself (planners, pinned bars) is ever affected */
@keyframes view-in { from { opacity: 0.4; } to { opacity: 1; } }
.view.active { animation: view-in 130ms ease-out; }

/* launch: the wordmark on cream, with a small living dot beneath it */
.boot { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; min-height: 100vh; }
.boot-mark { font-family: var(--font-display); font-size: 1.8rem; color: var(--text); }
.boot-mark em { font-style: normal; }
.boot-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); animation: boot-pulse 1s ease-in-out infinite; }
@keyframes boot-pulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }

/* ---------- Trading companies (Settings) and the fleet tag ---------- */
.ent-row {
  /* three columns, the middle one fixed — so every prefix starts at the
     same x and the list reads as a table (pilot, 21 Aug) */
  display: grid; grid-template-columns: 1fr 120px auto;
  align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px dashed var(--border);
}
.ent-row:last-child { border-bottom: none; }
.ent-row .ent-name { font-weight: 500; }
.ent-row .ent-sub { font-size: 11px; color: var(--muted); }
.car-ent { color: var(--muted); font-style: normal; }

/* the day's line total, under the Total header at the board's right edge */
.board-day-total { font-weight: 500; text-align: right; align-content: start; font-variant-numeric: tabular-nums; }

/* The car column's right edge is a drag handle (planner) */
.tl-car { cursor: default; }
.tl-car::after { content: ""; position: absolute; right: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; }

/* REMOVED 25 Aug: "#view-bookings .cal-nav { padding-bottom:
   env(safe-area-inset-top) }". It belonged to the older always-on masking of
   the frozen header, and was left behind when that was replaced by the
   .hdr-pinned rule further down, which masks the status bar only while the
   date strip is actually pinned. With both in place the installed app carried
   a whole status-bar of blank space between the Days row and the planner —
   the browser showed none, because env(safe-area-inset-top) is 0 there.
   That is the difference in the pilot's two screenshots (25 Aug 18:25).
   Nothing else donates padding here: the pinned mask is self-contained. */

/* The car-column resize grip: lives in the frozen corner so it is always on
   screen; touch-action none means a finger on it drags width, never scrolls */
/* (the grip positions against the corner's sticky context — sticky IS a
   positioning context, so no relative override; that override was what
   un-pinned the month, pilot's screenshots 25 Aug) */
.tl-colgrip {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  padding: 6px 5px; font-size: 11px; letter-spacing: -1px; line-height: 1;
  color: var(--muted); cursor: col-resize; touch-action: none;
  border: 1px solid var(--border); border-radius: 4px; background: var(--surface);
  user-select: none; -webkit-user-select: none;
}
.tl-colgrip:active { color: var(--text); border-color: var(--text); }

/* Dashboard money figures explain their populations; fleet focus banner */
.dash-figure-sub { display: block; font-size: 10px; color: var(--muted); margin-top: 2px; }
.fleet-focus { display: flex; gap: 10px; align-items: center; font-size: 13px;
  background: var(--amber-bg); color: var(--amber-text); border: 1px solid #edd9a3;
  border-radius: 6px; padding: 8px 12px; margin-bottom: 12px; }

/* Car grids in Reports: Total and Average ride pinned at the right while the
   months scroll — no more scrolling to learn the answer (pilot, 23 Aug) */
.rep-cargrid th:last-child, .rep-cargrid td:last-child {
  position: sticky; right: 0; min-width: 92px; background: var(--surface); z-index: 2; }
.rep-cargrid th:nth-last-child(2), .rep-cargrid td:nth-last-child(2) {
  position: sticky; right: 92px; min-width: 104px; background: var(--surface); z-index: 2;
  box-shadow: inset 10px 0 8px -10px rgba(0,0,0,0.25); }
.rep-cargrid thead th:last-child, .rep-cargrid thead th:nth-last-child(2) { background: var(--bg); z-index: 6; }

/* The quick Paid pill: pinned inside the booking dialog's top edge */
.paid-quick {
  font-family: var(--font-ui); font-size: 12px; padding: 6px 12px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.paid-quick.on { background: var(--green-bg); color: var(--green-text); border-color: var(--green-text); }

/* Tap a date header: its column tints (planner) */
.tl-daynum { cursor: pointer; }
.tl-daynum.col-hl { background: var(--col-hl-bg); color: #3a4d7a; }
.tl-cell.col-hl { background: var(--col-hl-bg); }

/* Page-level sideways panning is prevented globally (html/body overflow-x
   clip). A clip on the SECTION itself amputated the boards' intended
   edge-to-edge bleed — the strip it cut is exactly one page padding, the
   "DAY"→"AY" clipping in the pilot's circled screenshot (24 Aug). */
#view-tasks .planner-bar { max-width: 100%; }
.licence-info { font-size: 13px; color: var(--text); display: grid; gap: 3px;
  background: var(--bg); border: 1px dashed var(--border); border-radius: 4px; padding: 10px 12px; }

/* Voided rows in the invoice register */
.inv-void { opacity: 0.62; }
.inv-void th, .inv-void td { text-decoration: none; }
.pay-chip.void { background: #f3e3e3; color: #9c2f2f; border: 1px solid #d9b3b3; }

/* ---------- Invoices: the breakdown under a row (25 Aug) ----------
   Billing's card, opened beneath the invoice it belongs to. The detail row is
   always in the table and shown by class, so opening one is a class flip —
   nothing above it redraws or moves. */
.inv-row { cursor: pointer; }
.inv-row:hover td, .inv-row:hover th.rep-car { background: var(--bg); }
.inv-chev {
  display: inline-block; width: 12px; margin-right: 4px;
  color: var(--muted); font-size: 10px;
  transition: transform 0.15s ease;
}
.inv-chev-none { visibility: hidden; }
.inv-row.open .inv-chev { transform: rotate(90deg); }
.inv-detail { display: none; }
.inv-detail.open { display: table-row; }
.inv-detail > td { padding: 0 10px 10px; white-space: normal; background: var(--bg); }
/* The breakdown holds its place on the left while the wide table scrolls
   past — the same stickiness the car column already uses in this scroller. */
.inv-detail-body {
  position: sticky; left: 0;
  max-width: calc(100vw - 2 * var(--main-pad-x, 0.9rem) - 20px);
  padding: 4px 0 0;
}
.inv-detail-body .card-details { margin-top: 6px; padding-top: 8px; gap: 10px 22px; }
.inv-detail-body .card-details:first-child { border-top: none; padding-top: 4px; margin-top: 0; }
.inv-detail-actions { margin-top: 10px; }
/* The reminder panel: one row under the actions showing where the message is
   about to go. Hidden by class, shown in place, so opening it never redraws
   the breakdown above. Buttons carry only the standard .btn .btn-small — the
   panel sets layout, never size. */
.inv-remind { display: none; }
.inv-remind.open { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.inv-remind-lead { font-size: 11px; color: var(--muted); margin-right: 2px; }
@media (max-width: 640px) {
  .inv-remind.open { gap: 6px; }
  .inv-remind-lead { width: 100%; margin: 0 0 2px; }
}
@media (max-width: 640px) {
  .inv-detail-body { max-width: calc(100vw - 16px); }
  .inv-detail-body .card-details { gap: 6px 14px; }
}

/* The linked-customer chip under Name on booking */
.linked-chip {
  margin-top: 6px; font-size: 12px; color: var(--green-text);
  background: var(--green-bg); border: 1px solid var(--green-text);
  border-radius: 4px; padding: 6px 10px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.linked-chip [data-unlink] { margin-left: auto; }

/* ---------- Dashboard charts ---------- */
.dash-charts-card { grid-column: 1 / -1; }
.dash-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; }
.chart h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); font-weight: 500; }
.chart svg { width: 100%; height: auto; }
.ch-lbl { font-family: var(--font-ui); font-size: 9px; fill: var(--muted); }
.ch-line-inc { stroke: var(--green-text); stroke-width: 2; }
.ch-line-exp { stroke: #b0483a; stroke-width: 2; stroke-dasharray: 4 3; }
.ch-dot-inc { fill: var(--green-text); }
.ch-dot-exp { fill: #b0483a; }
.ch-note { font-size: 11px; color: var(--muted); margin-top: 4px; }
.ch-key { display: inline-block; width: 14px; height: 3px; vertical-align: middle; border-radius: 2px; }
.ch-key-inc { background: var(--green-text); }
.ch-key-exp { background: #b0483a; }
.occ-num { font-family: var(--font-display); font-size: 2rem; margin: 6px 0 8px; }
.occ-track { height: 10px; border-radius: 5px; background: var(--border); overflow: hidden; }
.occ-fill { height: 100%; background: var(--green-text); border-radius: 5px; }



/* ---------- Tasks toolbar, phone (pilot's sketch, 24 Aug) ----------
   Search, then the staff/jobs pickers, then FOUR compact buttons in the
   middle — Add task, Show completed, List, Board — and the Past/Today/Ahead
   range row at the bottom, closest to the board it filters. */
@media (max-width: 640px) {
  /* Tasks uses the SAME two-column toolbar grid as Expenses — one mechanism,
     one look (pilot: "use the expenses page as reference", 25 Aug). Only the
     order is tasks-specific: pickers, then the action/view pairs, and the
     Past/Today/Ahead ranges last, closest to the board they filter. */
  #view-tasks .toolbar > .multi-pick { order: 2; }
  #view-tasks .toolbar > select { order: 3; }
  #view-tasks .toolbar > .filter-row-last { order: 4; }
  #view-tasks .toolbar > [data-el="filters"] { order: 5; }
}


/* The status-bar mask, applied ONLY while the date strip is actually pinned
   (a sentinel observer toggles the class). The always-on padding technique
   cost ~75px of dead space at rest — the pilot's circled gap (24 Aug). */
#view-bookings .tl-head-wrap.hdr-pinned {
  padding-top: env(safe-area-inset-top, 0px);
}

/* The boards' staff strips had the older arrangement — pinned below the
   status bar, scrolled rows readable in the strip above them in the
   installed app (pilot's screenshots, 27 Aug 09:23, Tasks and Expenses;
   the browser showed nothing because env() is 0 there). Same cure as the
   planner, same class, same sentinel: mask the status bar only while
   actually pinned, so there is no dead space at rest. */
#view-tasks .board-head-wrap.hdr-pinned,
#view-expenses .board-head-wrap.hdr-pinned {
  padding-top: env(safe-area-inset-top, 0px);
}

/* Reports' heading: same disease, same cure — with one extra word.
   syncRepHead() sets the wrap's height in px to lay the copied heading
   exactly over the real one, and the page's border-box would take the
   status-bar padding OUT of that height and clip the copy. content-box,
   only while pinned, stacks the padding above the measured height instead.
   At rest the rule does not apply and nothing changes. */
.rep-head-wrap.hdr-pinned {
  box-sizing: content-box;
  padding-top: env(safe-area-inset-top, 0px);
}


/* ---------- Reports on a phone (pilot's screenshots, 25 Aug) ----------
   1. The pinned Total/Average columns are a desktop luxury: on a ~390px
      screen the pinned pair swallowed the width and left a sliver for the
      months. On a phone they unpin and scroll like ordinary columns.
   2. The invoice controls are JS-rendered and never joined the shared
      two-column toolbar grid — they free-styled their own sizes. Same grid,
      same rhythm as every other toolbar now. */
@media (max-width: 640px) {
  .rep-cargrid th:last-child, .rep-cargrid td:last-child,
  .rep-cargrid th:nth-last-child(2), .rep-cargrid td:nth-last-child(2) {
    position: static;
    min-width: 0;
    box-shadow: none;
  }

  .inv-controls {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    align-items: stretch;
  }
  .inv-controls > * { min-width: 0; margin: 0 !important; }
  .inv-controls > input[type="text"] { grid-column: 1 / -1; }   /* the search spans the row, per the control standard */
  .inv-controls label {
    display: flex; align-items: center; gap: 6px;
    font-size: 11px; color: var(--muted);
  }
  .inv-controls label input[type="date"] { flex: 1; min-width: 0; }
  .inv-controls .btn { width: 100%; }

  /* One toggle on its own line, then everything except the search hidden
     while closed. Named selectors rather than :not() chains, so a control
     added here later is visible by default rather than silently hidden. */
  /* layout only — height, padding and font come from the CONTROL STANDARD
     via .btn, so this adds no sizing of its own */
  .inv-more-toggle {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    grid-column: 1 / -1;
  }
  .inv-more-scope { color: var(--muted); text-transform: none; letter-spacing: 0; opacity: 0.8; }
  .inv-controls.filters-closed > select,
  .inv-controls.filters-closed > label,
  .inv-controls.filters-closed > [data-inv-export] { display: none; }
}


/* Reports: the tab row shares the top toolbar line on a desktop (pilot's
   arrow, 25 Aug). On a phone the shared toolbar grid already gives the tabs
   a full line of their own in pairs. */
@media (min-width: 641px) {
  /* display:contents dissolves the tab container so every tab flows as a
     direct toolbar item — the row can never wrap as one solid block below
     the selects again (pilot, 25 Aug) */
  #view-reports .toolbar [data-el="rep-tabs"] { display: contents; }
}


/* ================= CONTROL STANDARD (25 Aug 2026) =================
   THE rule for every current and future page. Do not invent per-page
   control layouts; put controls in the standard containers and they
   inherit the standard automatically.

   MOBILE (reference: the Expenses page)
   - Controls live in a .toolbar (or .one-line-tools): a 2-column grid,
     6px gap. Text/search inputs span the full row; everything else fills
     its half-width cell. Tab groups go in a .filter-row: full line,
     items in half-width pairs.
   - Every control is the same height (the variable below) — no control
     may set its own height, padding or font on mobile.

   DESKTOP (reference: the Tasks page)
   - Controls flow inline in wrapping rows (the .planner-bar/.toolbar
     flex layout): compact paddings, auto widths, search stretching.
   - Sizes come from the base .btn/.tab/select/input rules only.

   New page checklist: wrap controls in <div class="toolbar"> (+ 
   .filter-row for tab groups), use .btn/.btn-primary/.tab/select/input
   with NO inline sizing — and both layouts are already correct. */
:root {
  --ctl-h-mobile: 31px;     /* 30% down from 44px — pilot's call, 25 Aug */
  --ctl-font-mobile: 10px;
  --ctl-h-desktop: 30px;    /* the Tasks-bar size, now THE desktop size */
  --ctl-font-desktop: 11px;
}
/* DESKTOP: every control in every page control bar uses the Tasks recipe.
   Dialog forms keep the 44px field standard — density belongs to control
   bars, comfort belongs to forms. */
@media (min-width: 641px) {
  :is(.toolbar, .planner-bar, .filter-row, .cal-header, .inv-controls, [data-el="rep-controls"])
  :is(.btn, .btn-primary, .tab, .panel-toggle, select, input[type="text"], input[type="date"]) {
    height: var(--ctl-h-desktop);
    min-height: 0;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 12px;
    font-size: var(--ctl-font-desktop);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  /* typed text keeps its case and reading size — only labels shout */
  :is(.toolbar, .planner-bar, .filter-row, .cal-header, .inv-controls, [data-el="rep-controls"]) input[type="text"] {
    justify-content: flex-start;
    text-transform: none;
    letter-spacing: 0;
    font-size: 12px;
  }
}
@media (max-width: 640px) {
  /* EVERY control, everywhere — not just inside toolbars (25 Aug). The one
     documented exception is the customers A–Z index strip below. */
  .btn, .btn-primary, .tab, select,
  input[type="text"], input[type="date"], input[type="email"],
  input[type="tel"], input[type="number"], input[type="password"] {
    min-height: var(--ctl-h-mobile);
    font-size: var(--ctl-font-mobile);
  }
  /* keyboard inputs keep 16px type — the exact iOS threshold below which the
     page zooms on every tap; they take the compact height through padding */
  input[type="text"], input[type="email"], input[type="tel"],
  input[type="number"], input[type="password"] {
    font-size: 16px;
    padding-top: 4px;
    padding-bottom: 4px;
  }
  /* date inputs open a wheel, not a keyboard — no zoom risk, so they take
     the full compact size; a hard height tames the native chrome that made
     them taller than their neighbours (pilot's PDF, 25 Aug) */
  input[type="date"], .field input[type="date"], .modal .field input[type="date"] {
    font-size: 13px;
    height: var(--ctl-h-mobile);
    min-height: 0;
    padding-top: 2px;
    padding-bottom: 2px;
    /* iOS date controls carry an intrinsic native width that overflowed the
       card edge (pilot's red line, 25 Aug) — it yields here */
    width: 100%;
    min-width: 0;
    max-width: 100%;
    -webkit-appearance: none;
    appearance: none;
    text-align: left;
  }
  /* a checkbox and its words are one row: box first, text beside it,
     wrapping within the row — never a word-per-line column adrift of its
     box (pilot's PDF, 25 Aug) */
  label:has(> input[type="checkbox"]) {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    text-align: left;
  }
  label:has(> input[type="checkbox"]) input[type="checkbox"] { flex: 0 0 auto; width: auto; }
  .alpha-btn { min-height: 0; font-size: 10px; }  /* documented exception: 26-key index strip */
}

/* The planner month + grip as a fixed overlay on the head strip — anchored
   to the wrap, which never scrolls, so no scroll can move it (25 Aug). */
.tl-head-wrap { position: sticky; }  /* (already sticky — restated as the overlay's anchor) */
.tl-corner-fixed {
  position: absolute; left: 0; top: 0; bottom: 0; z-index: 8;
  display: flex; align-items: center;
  padding: 6px 10px; box-sizing: border-box;
  background: var(--bg); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
  font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.tl-corner-fixed .tl-colgrip { right: 2px; }

/* The head strip, restructured so the month CANNOT move (25 Aug):
   - .tl-head-wrap is now the OUTER frame: page-sticky, bordered, full-bleed —
     and it is never scrolled by anything.
   - .tl-head-scroll is the inner strip the mirror scrolls (overflow hidden).
   - .tl-corner-fixed is absolute in the OUTER frame — outside the scrolled
     canvas entirely. Absolute children ride their container's scroll, which
     is why every in-wrap technique failed; this one is outside it. */
.tl-head-scroll { overflow-x: hidden; }
.tl-head-wrap { overflow-x: visible; }


/* The invoices filter toggle is a phone-only control. On a desktop the filter
   row already fits on one line, so the button collapses nothing and does
   nothing — it is removed there rather than left as a dead control (pilot,
   25 Aug). Placed last, and naming both of the button's classes, so it beats
   the CONTROL STANDARD's display:inline-flex above on order AND specificity.
   Mobile is untouched: the toggle's own rule lives in the max-width block. */
@media (min-width: 641px) {
  .inv-controls .inv-more-toggle.btn { display: none; }
}
