* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
  --bg: #f7f4f1;             /* warm ivory */
  --card: #ffffff;
  --ink: #2b2622;            /* warm near-black */
  --muted: #948b82;          /* warm grey */
  --line: #e8e1d9;           /* warm border */
  --primary: #b17a5e;        /* rose gold / copper (TF brand) */
  --primary-dark: #915f45;   /* deeper copper */
  --gold: #c9a184;           /* light rose gold accent */
  --taupe: #ada294;          /* brand banner taupe */
  --taupe-soft: #efeae3;     /* soft taupe fill */
  --green: #4f8163;
  --amber: #c07d33;
  --blue: #4a6d8c;
  --red: #b5514a;
  --zone-sharjah: #a3503f;   /* terracotta */
  --zone-dubai: #3a5c7a;     /* muted navy */
  --zone-ajman: #4a7c62;     /* muted green */
  --zone-others: #6d5480;    /* muted plum */
  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  /* The app's one bar. Every screen that needs to know how much room is left
     below it reads this instead of guessing a number of its own. */
  --head-h: 54px;
  --chrome-h: 106px;   /* the bar plus the board's tabs+views row */

  /* ------------------------------------------------------------------
     APPLE GRAMMAR, TULIP PALETTE
     The shapes, weights, hairlines and press behaviour follow Apple's
     system; the colours and the wordmark stay Tulip's. Specifically:
       · ONE interactive colour. Terracotta --primary is this app's
         Action Blue — every "click me" is that colour and nothing else.
         Green/amber/red survive only to REPORT STATUS, never to invite
         a click.
       · Radius is a grammar, not a decoration: 8px compact utility,
         12px capsule, 18px cards, full pill for actions. Nothing between.
       · Elevation comes from surface change and 1px hairlines. Apple
         ships exactly one drop-shadow and spends it on product
         photography; this app has no product photography, so it spends
         it on nothing. Every decorative card shadow is gone.
       · Every button presses in — scale(.96) — the system-wide
         micro-interaction.
     ------------------------------------------------------------------ */
  --r-sm: 8px;               /* compact utility buttons, chips */
  --r-md: 12px;              /* capsules, inputs, small panels */
  --r-lg: 18px;              /* cards, sections, modals */
  --r-pill: 9999px;          /* anything that is an ACTION */
  --hairline: rgba(43, 38, 34, .10);
  --hairline-soft: rgba(43, 38, 34, .06);
  --ink-2: #6b625a;          /* secondary text */
  --press: scale(.96);
  /* SF Pro on Apple hardware, the platform's own UI face everywhere else.
     The serif above is untouched — it is the brand and stays on the
     wordmark, the page titles and the numbers that want a voice. */
  --ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI Variable Text", "Segoe UI", Roboto, sans-serif;
  /* Reserved for the things that are READ AS DIGITS rather than as words — the
     TF number above all. Fixed-width means a column of them lines up and a
     wrong digit shows itself against its neighbours. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
}
html, body { height: 100%; }
body {
  font-family: var(--ui);
  background: var(--bg); color: var(--ink); font-size: 15px;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  letter-spacing: -0.01em;   /* the "Apple tight" cadence, at UI scale */
}
.hidden { display: none !important; }

/* Apple's ladder is 300 / 400 / 600 / 700 — 500 is deliberately absent,
   so a heading is either normal or properly bold, never almost-bold. */
h1, h2, h3, h4, .page-title, .ws-title { letter-spacing: -0.02em; }

/* Every button presses in. One rule, whole app. */
.btn, .ws-btn, .view-btn, .tab, .ws-seg-btn, .home-tile { transition: background .18s ease, border-color .18s ease, transform .12s ease, box-shadow .18s ease; }
.btn:active, .ws-btn:active, .view-btn:active, .ws-seg-btn:active, .home-tile:active { transform: var(--press); }

/* One focus signal, in the one accent colour. */
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--r-sm); }

/* ---------- brand marks ---------- */
.tf-mono {
  font-family: "Century Gothic", "Futura", "Trebuchet MS", sans-serif;
  font-size: 40px; font-weight: 700; fill: var(--primary); letter-spacing: 1px;
}
.brand-wordmark {
  font-family: var(--serif); font-size: 26px; letter-spacing: .32em;
  text-transform: uppercase; color: var(--primary); margin: 14px 0 4px; font-weight: 600;
}
.brand-block { display: flex; flex-direction: column; line-height: 1.15; }
.brand-name {
  font-family: var(--serif); font-size: 20px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--primary-dark);
}
.brand-tag { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }

/* ---------- buttons ----------
   Two grammars, exactly as Apple runs them: a PILL when the button is the
   action you came to the page for, and a compact 8px rectangle for utility.
   A pill in this app is always terracotta — the shape and the colour say the
   same thing, so nobody has to learn which is which. */
.btn {
  font-family: var(--ui);
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink);
  padding: 8px 14px; border-radius: var(--r-sm); cursor: pointer; font-size: 15px;
  white-space: nowrap; letter-spacing: -0.01em;
}
.btn:hover { background: var(--taupe-soft); }
.btn-primary {
  background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
  border-radius: var(--r-pill); padding: 8px 18px;
}
.btn-primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--taupe-soft); }
.btn-danger { background: transparent; border-color: rgba(181,81,74,.35); color: var(--red); }
.btn-danger:hover { background: #fdf2f1; }
.btn-block { width: 100%; }

/* ---------- login ---------- */
.login-screen {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(155deg, #f3ece5 0%, #ece4dc 45%, #e4d8cd 100%);
}
.login-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 6px;
  padding: 44px 40px; width: 380px; max-width: 92vw; text-align: center;
  box-shadow: 0 18px 50px rgba(120, 90, 70, .16);
  position: relative;
}
.login-card::before {
  content: ""; position: absolute; inset: 8px; border: 1px solid var(--line);
  border-radius: 6px; pointer-events: none;
}
.login-logo { line-height: 0; }
.login-card h1 {
  font-family: var(--serif); font-size: 26px; font-weight: 600; margin: 2px 0 2px;
  letter-spacing: .06em; color: var(--ink);
}
.login-sub { color: var(--muted); margin-bottom: 26px; font-size: 13.5px; letter-spacing: .04em; }
.login-card input {
  width: 100%; padding: 12px 13px; margin-bottom: 12px;
  border: 1px solid var(--line); border-radius: 6px; font-size: 15px;
  background: #fdfbfa;
}
.login-card input:focus { outline: 2px solid var(--primary); outline-offset: 0; border-color: var(--primary); }
.login-error { color: var(--red); margin-top: 12px; min-height: 20px; font-size: 15px; }
.build-stamp { margin-top: 14px; font-size: 11px; color: var(--muted); text-align: center; letter-spacing: .03em; }

/* ---------- topbar ---------- */
.topbar {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 9px 18px; background: var(--card); border-bottom: 2px solid var(--gold);
  position: sticky; top: 0; z-index: 20;
}
.topbar-left { display: flex; align-items: center; gap: 11px; }
.logo { line-height: 0; }
/* A STATUS MESSAGE, not a title. "Shopify not connected - sample data" is three
   times the width of "synced 2 min ago", and while it sat inside the never-shrink
   title block it pushed the search box and the buttons into each other. It gives
   way first now: truncated here, gone entirely once the bar is tight. */
.sync-badge {
  font-size: 12.5px; color: var(--muted);
  min-width: 0; flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch;
}
.sync-badge.ok::before { content: "● "; color: var(--green); }
.sync-badge.warn::before { content: "● "; color: var(--amber); }
.date-nav { display: flex; align-items: center; gap: 6px; }
.date-nav input[type=date] {
  padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 15px; font-weight: 600;
}
.topbar-right { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.user-chip {
  background: #f5ede6; color: var(--primary-dark); font-weight: 600;
  padding: 6px 12px; border-radius: var(--r-pill); font-size: 13.5px;
}

/* ---------- control bar ----------
   ONE row now. It carried three full-width rows — which kind of work, which
   state, how it is arranged — drawn as three identical grey segmented tracks, so
   205px of the screen was spent before any work appeared and nothing told you the
   three rows were three different KINDS of question. Owner, 2026-08-05: "كتير صار
   فوضوي هاد module".
   Arrangement went up into the app bar beside the date, where Dispatch and Staff
   tasks already keep theirs. What is left is the question actually asked all day —
   which work — with the state filter beside it in a lighter weight. */
.controlbar {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 16px 5px; background: var(--bg); flex-wrap: wrap;
}
.view-group { display: inline-flex; background: var(--taupe-soft); border-radius: var(--r-md); padding: 3px; gap: 2px; }
.view-btn {
  border: 1px solid var(--hairline); background: var(--card); color: var(--muted);
  padding: 6px 14px; border-radius: var(--r-sm); cursor: pointer; font-size: 13.5px; font-weight: 600;
  font-family: var(--ui); letter-spacing: -0.01em;
}
.view-btn.active { background: var(--accent-fill); border-color: var(--accent-fill); color: #fff; }
.view-group .view-btn { border: 0; background: none; padding: 6px 13px; border-radius: var(--r-sm); }
.view-group .view-btn:hover { color: var(--ink); }
.view-group .view-btn.active { background: var(--accent-fill); color: #fff; border-color: var(--accent-fill); }
.view-month { border-radius: var(--r-pill); padding: 6px 15px; }
/* ============================================================
   THE DAY'S COUNTS, AS FILTERS  —  one strip, two screens
   ============================================================
   These rules were dispatch.css's private property until 2026-08-05. The Work
   board carried its own pair of strips instead — a filled `.formbar` of five
   buttons over a quieter `.tabbar` of four tabs — which asked the same two
   questions Dispatch asks in one row of pills, in a different shape, with
   different counting rules and no way to hold two forms at once.

   The owner's instruction was "it should be exactly the same way that shows in
   dispatch", so the pills moved HERE and both screens now render the same
   markup. dispatch.css keeps only what is Dispatch's alone. The class names
   still carry their `db-` birthmark on purpose: renaming them would have meant
   rewriting every chip in Dispatch to gain nothing a reader can see.

   The grammar, left to right:
     WHICH FORM   four pills that COMBINE, so each has to say whether it is in —
                  a ✓ when it is, a faint + when pressing it would add it.
                  "All work" is simply none of them held.
     ·hairline·
     WHICH STATE  a single choice, because you are in one bucket at a time.
     ·hairline·
     LATE         the cross-date pills. Everything left of this hairline counts
                  the day on screen; these two count every day there has ever
                  been, and pressing one leaves the day behind rather than
                  filtering it.
   ============================================================ */
.db-filters { display: flex; flex-wrap: wrap; gap: 6px; }
.db-fchip {
  display: inline-flex; align-items: baseline; gap: 5px;
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink-2);
  border-radius: var(--r-pill); padding: 6px 13px; font: inherit; font-size: 12.5px;
  cursor: pointer; white-space: nowrap;
}
.db-fchip b { font-size: 13.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.db-fchip:hover { border-color: var(--primary); }
.db-fchip.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.db-fchip.on b { color: #fff; }
/* The four form pills combine, so each one has to say whether it is IN. A tick
   when it is, a faint + when pressing it would add it — with two of four lit,
   colour alone leaves you counting which is which. */
.db-fchip-tick {
  font-style: normal; font-size: 11px; line-height: 1; opacity: .45;
  width: 9px; display: inline-block; text-align: center;
}
.db-fchip.on .db-fchip-tick { opacity: 1; }
.db-fchip-form:hover .db-fchip-tick { opacity: .8; }
.db-fchip.warn b { color: var(--amber); }
.db-fchip.bad b { color: var(--red); }
/* Finished work: green, and set apart from the pills to its left. Those all
   count something to go and DO; this one counts what is already behind you and
   is only there so you can find it, so it does not compete for the eye. */
.db-fchip.good b { color: var(--green); }
.db-fchip.good { margin-left: 4px; }
.db-fchip.on.warn b, .db-fchip.on.bad b, .db-fchip.on.good b { color: #fff; }

/* ---------- the four FORM pills ----------
   WHICH KIND of work, which is a different question from which STATE it is in —
   so they lead the row and a hairline separates them from the state pills. */
.db-fchip-form { border-radius: var(--r-sm); }
/* Deliveries are the priority, so they are the one pill that is never fully grey
   when unpressed — the hairline says "this is the default" without shouting. */
.db-fchip-form.is-priority:not(.on) { color: var(--ink); border-color: var(--line); }

/* ---------- the cross-date pills ----------
   The hairline is what stops "next to the current pills" being a lie: a reader
   can see they are a different kind of thing before pressing one. */
.db-fsep {
  width: 1px; align-self: stretch; margin: 2px 4px;
  background: var(--hairline);
}
.db-fchip-late { border-style: solid; }
.db-fchip-late.on { border-style: solid; }

/* ---------- the late list ----------
   Shared for the same reason the pills are: the Work board's late pills open the
   very same list from the very same endpoint, so it must not be drawn twice. */
.db-late { padding: 2px 0 20px; }
.db-late-head { margin: 6px 2px 14px; }
.db-late-head h3 { margin: 0 0 4px; font-size: 17px; font-weight: 600; }
.db-late-head p {
  margin: 0 0 10px; color: var(--muted); font-size: 12.5px; max-width: 62ch; line-height: 1.5;
}
.db-late-head p b { color: var(--ink); font-variant-numeric: tabular-nums; }

.db-late-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.db-late-table th {
  text-align: left; font-weight: 600; font-size: 11.5px; letter-spacing: .02em;
  text-transform: uppercase; color: var(--muted);
  padding: 0 10px 8px; border-bottom: 1px solid var(--hairline);
}
.db-late-table td { padding: 11px 10px; border-bottom: 1px solid var(--hairline); vertical-align: top; }
.db-late-row[data-openjob] { cursor: pointer; }
.db-late-row[data-openjob]:hover { background: var(--hover, rgba(0, 0, 0, .03)); }
/* Not done is the one that can still be rescued this morning, so it is the one
   with the edge. Finished-late is history and reads quietly. */
.db-late-row.is-out td:first-child { box-shadow: inset 3px 0 0 var(--red); }

/* The little state lozenge inside a late row ("Not done" / "Done late"). Moved
   here with the list for the same reason the list moved. */
.db-chip {
  display: inline-flex; align-items: center; border-radius: var(--r-sm);
  padding: 2px 7px; font-size: 11px; font-weight: 600;
  background: var(--taupe-soft); color: var(--ink-2); white-space: nowrap;
}
.db-chip.good { background: rgba(79, 129, 99, .13); color: var(--green); }
.db-chip.warn { background: rgba(192, 125, 51, .15); color: var(--amber); }
.db-chip.bad  { background: rgba(181, 81, 74, .13); color: var(--red); }

.db-late-when b { display: block; font-weight: 600; white-space: nowrap; }
.db-late-when span { color: var(--muted); font-size: 11.5px; white-space: nowrap; }
.db-late-name { font-weight: 600; }
.db-late-sub { color: var(--muted); font-size: 11.5px; margin-top: 2px; }
.db-late-who { white-space: nowrap; }
.db-late-state { white-space: nowrap; }
.db-late-by { display: block; color: var(--muted); font-size: 11.5px; margin-top: 3px; }

/* A four-column table is unreadable on a phone, so each row becomes a card and
   the headings — which only make sense above columns — are dropped. */
@media (max-width: 700px) {
  .db-late-table thead { display: none; }
  .db-late-table, .db-late-table tbody, .db-late-row, .db-late-table td { display: block; width: 100%; }
  .db-late-row {
    border: 1px solid var(--hairline); border-radius: var(--r-md, 10px);
    padding: 10px 12px; margin-bottom: 8px; background: var(--card);
  }
  .db-late-table td { border: 0; padding: 2px 0; }
  .db-late-row.is-out { border-left: 3px solid var(--red); }
  .db-late-row.is-out td:first-child { box-shadow: none; }
  .db-late-when b { display: inline; }
  .db-late-when span::before { content: ' · '; }
  .db-late-when span { display: inline; }
}

/* ---------- the arrangement menu, in the app bar ----------
   Six arrangements used to be six permanent buttons in a row under the board.
   They are a choice you make once and live in, so they are one button that says
   which arrangement you are in, and a menu behind it. */
/* The two arrangement questions travel together — see the re-parenting in app.js.
   `on-page` is the narrow-screen home, beside the state filter. */
.board-arrange { display: flex; align-items: center; gap: 8px; flex: none; min-width: 0; }
.board-arrange.on-page { margin-left: auto; }
.bview { position: relative; flex: none; }
.bview-btn {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink);
  padding: 6px 11px; border-radius: var(--r-sm); cursor: pointer;
  font-size: 13.5px; font-weight: 600; font-family: var(--ui); letter-spacing: -0.01em;
  white-space: nowrap;
}
.bview-btn:hover { border-color: var(--taupe); }
.bview-btn[aria-expanded="true"] { background: var(--accent-tint); border-color: var(--accent); color: var(--accent); }
.bview-label { display: inline-flex; align-items: center; gap: 6px; }
.bview-label svg { width: 16px; height: 16px; }
.bview-caret { font-size: 10px; color: var(--muted); }
.bview-btn[aria-expanded="true"] .bview-caret { color: var(--accent); }
.bview-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 40;
  display: flex; flex-direction: column; gap: 2px; padding: 5px; min-width: 190px;
  background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-md);
  box-shadow: 0 12px 28px rgba(43, 38, 34, .16);
}
.bview-menu.hidden { display: none; }
/* Inside the menu the six stop being pills and become plain rows — a menu item
   that looks like a button invites you to think the others are unavailable. */
.bview-menu .view-btn {
  border: 0; background: none; text-align: left; width: 100%;
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; color: var(--ink);
}
.bview-menu .view-btn:hover:not(:disabled) { background: var(--taupe-soft); }
.bview-menu .view-btn.active { background: var(--accent-fill); color: #fff; }
.bview-menu .view-btn.is-blocked, .bview-menu .view-btn:disabled { opacity: .4; cursor: not-allowed; }
.bview-menu .view-btn svg { width: 16px; height: 16px; }
/* Across/down at the foot of the menu, under a hairline: an option OF the chosen
   arrangement, not a seventh arrangement. Hidden — not disabled — for the two that
   have no columns to lay out. */
.bview-foot { margin-top: 4px; padding-top: 5px; border-top: 1px solid var(--hairline); }
.bview-foot .view-btn { width: 100%; justify-content: flex-start; }
.bview-foot.hidden, #boardFlow.hidden { display: none; }
/* Every control in the bar is one line. Nothing here is long enough to need two,
   and anything that takes two drags the whole bar down with it — a wrapped button
   set the bar to 64px and squeezed the search box out of existence. */
.top-strip .view-btn, .top-strip .bview-btn { white-space: nowrap; }
/* The period group is three short words; it does not need button-sized padding. */
.top-strip .view-group .view-btn { padding: 6px 11px; }

/* ---------- driver chips (admin) ---------- */
.drivers-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.driver-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: #f5ede6; color: var(--primary-dark); border: 1px solid #e6d3c4;
  border-radius: var(--r-pill); padding: 5px 6px 5px 12px; font-size: 13.5px; font-weight: 600;
}
.driver-chip button { border: 0; background: rgba(177,122,94,.16); color: var(--primary-dark); border-radius: var(--r-pill); width: 20px; height: 20px; cursor: pointer; font-size: 15px; line-height: 1; }
.driver-chip button:hover { background: var(--primary); color: #fff; }

/* ---------- board: all zones in ONE row, scroll right for more ---------- */
.board {
  display: flex; flex-wrap: nowrap; gap: 12px;
  padding: 14px; align-items: stretch;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: thin; scrollbar-color: var(--gold) transparent;
  min-height: calc(100vh - var(--chrome-h) - 34px);
}
.board::-webkit-scrollbar { height: 10px; }
.board::-webkit-scrollbar-thumb { background: var(--gold); border-radius: var(--r-pill); }
.board::-webkit-scrollbar-track { background: var(--taupe-soft); border-radius: var(--r-pill); }
.board .zone-col { flex: 0 0 300px; max-width: 300px; }
.zone-col { background: var(--taupe-soft); border-radius: var(--r-md); display: flex; flex-direction: column; min-height: 300px; }
.zone-head {
  padding: 10px 14px; font-weight: 800; letter-spacing: .5px; font-size: 15px;
  display: flex; justify-content: space-between; align-items: center;
  border-radius: var(--r-md) 12px 0 0; color: #fff;
}
.zone-head .count { background: rgba(255,255,255,.25); border-radius: var(--r-pill); padding: 1px 10px; font-size: 13.5px; }
.zone-SHARJAH .zone-head { background: var(--zone-sharjah); }
.zone-DUBAI .zone-head { background: var(--zone-dubai); }
.zone-AJMAN .zone-head { background: var(--zone-ajman); }
.zone-ABUDHABI .zone-head { background: #8a7a45; }
.zone-RAK .zone-head { background: #7c5a3f; }
.zone-UAQ .zone-head { background: #4f7676; }
.zone-FUJAIRAH .zone-head { background: #6b6a52; }
.zone-OTHERS .zone-head { background: var(--zone-others); }
.zone-body { padding: 10px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.zone-body.drag-over { outline: 3px dashed var(--primary); outline-offset: -6px; border-radius: 0 0 12px 12px; background: #f5ede6; }
.zone-empty { color: var(--muted); text-align: center; font-size: 13.5px; padding: 24px 8px; }

/* ---------- Areas board: zones STACKED vertically (no side-scroll) ---------- */
.board.board-stacked {
  display: block; flex-wrap: initial; overflow-x: visible; gap: 0;
  padding: 14px 16px 40px; min-height: calc(100vh - var(--chrome-h) - 34px);
}
.zsec {
  background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--taupe);
  border-radius: var(--r-lg); margin-bottom: 14px; overflow: hidden;
}
.zsec.drag-over { outline: 2px dashed var(--primary); outline-offset: -2px; background: #faf5f0; }
.zsec-head {
  width: 100%; display: flex; align-items: center; gap: 10px; padding: 13px 16px;
  background: none; border: 0; border-bottom: 1px solid var(--taupe-soft);
  cursor: pointer; font-family: inherit; text-align: left;
}
.zsec-head:hover { background: var(--taupe-soft); }
.zsec-dot { width: 11px; height: 11px; border-radius: 50%; flex: none; background: var(--taupe); box-shadow: 0 0 0 3px rgba(0,0,0,.04); }
.zsec-name { font-size: 15px; font-weight: 800; letter-spacing: .02em; color: var(--ink); }
.zsec-count { font-size: 12.5px; font-weight: 700; color: var(--muted); background: var(--taupe-soft); border-radius: var(--r-pill); padding: 1px 10px; font-variant-numeric: tabular-nums; }
.zsec-chev { margin-left: auto; color: var(--muted); font-size: 20px; line-height: 1; transition: transform .15s; }
/* The four fold headers wrote their chevron as the character "⌄", which most UI
   fonts draw as a lowercase v sitting low on the line — it read as a stray letter.
   Drawn instead: two strokes of currentColor in a fixed box, so it is the same shape
   on every phone and computer. The span keeps any rotate() its own stylesheet gives
   it for the folded state, because the drawing is on ::before, not on the span. */
.zst-chev, .zsec-chev, .db-fold-chev, .mw-fold-chev {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none; font-size: 0 !important; line-height: 0;
}
.zst-chev::before, .zsec-chev::before, .db-fold-chev::before, .mw-fold-chev::before {
  content: ''; width: 7px; height: 7px; margin-top: -4px; box-sizing: border-box;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.zsec.collapsed .zsec-chev { transform: rotate(-90deg); }
.zsec.collapsed .zsec-head { border-bottom: 0; }
.zsec.collapsed .zsec-body { display: none; }
.zsec-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 12px; padding: 14px 16px; }
/* zone accents (left bar + dot), reusing the palette */
.zsec.zone-SHARJAH { border-left-color: var(--zone-sharjah); } .zsec.zone-SHARJAH .zsec-dot { background: var(--zone-sharjah); }
.zsec.zone-DUBAI { border-left-color: var(--zone-dubai); }     .zsec.zone-DUBAI .zsec-dot { background: var(--zone-dubai); }
.zsec.zone-AJMAN { border-left-color: var(--zone-ajman); }     .zsec.zone-AJMAN .zsec-dot { background: var(--zone-ajman); }
.zsec.zone-ABUDHABI { border-left-color: #8a7a45; }            .zsec.zone-ABUDHABI .zsec-dot { background: #8a7a45; }
.zsec.zone-RAK { border-left-color: #7c5a3f; }                 .zsec.zone-RAK .zsec-dot { background: #7c5a3f; }
.zsec.zone-UAQ { border-left-color: #4f7676; }                 .zsec.zone-UAQ .zsec-dot { background: #4f7676; }
.zsec.zone-FUJAIRAH { border-left-color: #6b6a52; }            .zsec.zone-FUJAIRAH .zsec-dot { background: #6b6a52; }
.zsec.zone-OTHERS { border-left-color: var(--zone-others); }   .zsec.zone-OTHERS .zsec-dot { background: var(--zone-others); }

.zsec-others { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 2px 2px 8px; }
.zso-label { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.zso-pill { font-size: 12.5px; color: var(--muted); background: var(--card); border: 1px solid var(--line); border-radius: var(--r-pill); padding: 4px 13px; }
.zso-pill.drag-over { background: var(--primary); color: #fff; border-color: var(--primary); }

.board-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; text-align: center; padding: 70px 20px; color: var(--muted); }
.be-emoji { font-size: 40px; }
.be-msg { font-size: 15px; }
.be-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; }

/* readable, unambiguous board date */
.date-pick { position: relative; display: inline-flex; align-items: center; border: 1px solid var(--hairline); border-radius: var(--r-pill); padding: 7px 14px; background: var(--card); cursor: pointer; font-size: 15px; color: var(--ink); white-space: nowrap; }
.date-pick:hover { border-color: var(--primary); }
.date-label b { font-weight: 800; }
.date-pick input[type=date] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: 0; padding: 0; }

/* ---------- cards ---------- */
.card-entry {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 10px 12px; cursor: grab;
  border-left: 4px solid var(--line);
}
.card-entry:active { cursor: grabbing; }
.card-entry.st-new { border-left-color: color-mix(in srgb, var(--muted) 75%, #fff); }
.card-entry.st-scheduled { border-left-color: var(--blue); }
.card-entry.st-out { border-left-color: var(--amber); }
.card-entry.st-delivered, .card-entry.st-collected { border-left-color: var(--green); opacity: .75; }
.card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.order-no { font-weight: 800; font-size: 15px; color: var(--primary-dark); }
.task-badge { font-size: 11px; font-weight: 700; background: color-mix(in srgb, var(--zone-others) 13%, #fff); color: color-mix(in srgb, var(--zone-others) 90%, #1e1830); border-radius: 6px; padding: 2px 6px; }
.collection-badge { font-size: 11px; font-weight: 700; background: color-mix(in srgb, var(--amber) 15%, #fff); color: color-mix(in srgb, var(--amber) 88%, #2a1706); border-radius: 6px; padding: 2px 6px; }
.event-badge { font-size: 11px; font-weight: 700; background: color-mix(in srgb, var(--zone-others) 13%, #fff); color: color-mix(in srgb, var(--zone-others) 90%, #1e1830); border-radius: 6px; padding: 2px 6px; }
.status-pill { margin-left: auto; font-size: 11px; font-weight: 700; border-radius: var(--r-pill); padding: 2px 9px; }
.status-pill.new { background: color-mix(in srgb, var(--bg) 70%, #fff); color: var(--ink-2); }
.status-pill.scheduled { background: color-mix(in srgb, var(--blue) 13%, #fff); color: color-mix(in srgb, var(--blue) 88%, #101c28); }
.status-pill.out { background: color-mix(in srgb, var(--amber) 15%, #fff); color: color-mix(in srgb, var(--amber) 88%, #2a1706); }
.status-pill.delivered, .status-pill.collected { background: color-mix(in srgb, var(--green) 13%, #fff); color: color-mix(in srgb, var(--green) 88%, #0e1a14); }
.card-line { font-size: 13.5px; color: var(--ink); margin: 2px 0; overflow: hidden; text-overflow: ellipsis; }
.card-line.muted { color: var(--muted); font-size: 12.5px; }
.card-meta { display: flex; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
.chip { font-size: 12.5px; background: color-mix(in srgb, var(--bg) 70%, #fff); border-radius: 6px; padding: 2px 8px; color: var(--ink-2); }
.chip.time { background: color-mix(in srgb, var(--blue) 8%, #fff); color: var(--blue); font-weight: 600; }
.chip.assignee { background: #f5ede6; color: var(--primary-dark); font-weight: 600; }
.card-quick { display: flex; gap: 6px; margin-top: 8px; }
/* Big, tappable status buttons — many users work from a phone with one hand. */
.quick-btn {
  flex: 1; font-size: 13.5px; font-weight: 600; padding: 9px 4px; border-radius: var(--r-sm); cursor: pointer;
  border: 1px solid var(--line); background: color-mix(in srgb, var(--bg) 55%, #fff); color: var(--ink-2); min-height: 38px;
}
.quick-btn:hover { background: color-mix(in srgb, var(--primary) 7%, #fff); border-color: color-mix(in srgb, var(--primary) 30%, #fff); }

/* ---------- modal ---------- */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(20,22,30,.5); z-index: 50;
  display: flex; align-items: flex-start; justify-content: center; padding: 4vh 12px;
  overflow-y: auto;
}
.modal {
  background: var(--card); border-radius: var(--r-lg); width: 640px; max-width: 100%;
  box-shadow: 0 24px 60px rgba(0,0,0,.25); display: flex; flex-direction: column; max-height: 92vh;
}
.modal-wide { width: 780px; }
.modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.modal-header h2 { font-size: 26px; font-family: var(--serif); font-weight: 600; letter-spacing: .04em; }
/* The serif is the brand voice for words. A TF number is not a word. */
.modal-header h2.is-ref { font-family: var(--mono); font-weight: 700; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.modal-close { border: 0; background: none; font-size: 26px; cursor: pointer; color: var(--muted); line-height: 1; }
.modal-body { padding: 18px 20px; overflow-y: auto; flex: 1 1 auto; min-height: 0; }
/* "Somebody else changed this job while you have been editing." Amber, not red:
   nothing has gone wrong and nothing of theirs is lost — it is news, and the
   choice of what to do with it stays with the person reading it. */
.jc-stale {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #fdf4ea; border: 1px solid #e6c9a8; border-radius: var(--r-md);
  padding: 10px 12px; margin-bottom: 12px; font-size: 13px; line-height: 1.45;
}
.jc-stale span { flex: 1; min-width: 200px; }
.modal-footer {
  display: flex; gap: 10px; padding: 14px 20px; border-top: 1px solid var(--line); align-items: center;
}
/* ONLY THE BODY MAY GIVE. .modal is a flex column with a max-height, and every
   child of a flex container is shrinkable by default — so on a card taller than
   the screen the browser squeezed the header, the tab strip and the footer as
   well as the body. The tab strip needs 45px; it was being handed 19px on a
   1280×720 desktop and 16px on a phone, and its buttons then painted straight
   over the content beneath — which reads as "the tabs are broken on desktop and
   gone on mobile". The body scrolls; nothing else should ever be compressed. */
.modal-header, .modal-footer, .jc-tabs, .jc-stage { flex: none; }
.modal-footer .spacer { flex: 1; }

.field { margin-bottom: 12px; position: relative; }
.field label { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 4px; }
.field .hint { font-weight: 400; color: var(--muted); }
.field input, .field select, .field textarea {
  width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 15px;
  font-family: inherit; background: #fff;
}
.field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid #d8bda9; border-color: var(--primary); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.field-inline { display: flex; align-items: center; gap: 12px; margin: 10px 0; }
.checkbox-label { display: flex; align-items: center; gap: 6px; font-size: 15px; }
.form-error { color: var(--red); font-size: 15px; min-height: 18px; margin-top: 6px; }

/* ============ TABBED JOB CARD (TF / manual) ============ */
.modal.jobcard { width: 680px; }
.jc-header { align-items: flex-start; }
.jc-head-main { min-width: 0; }
.jc-sub { font-size: 13.5px; color: var(--muted); margin-top: 3px; letter-spacing: .01em; }
.jc-head-actions { display: flex; align-items: center; gap: 10px; flex: none; }
.jc-status-chip { white-space: nowrap; }
.btn-sm { padding: 6px 12px; font-size: 13.5px; }

/* The stage strip: card chrome between the header and the tabs, so "where has this
   job got to" is answered before you choose a tab. Quiet ground and a hairline —
   it is a readout, not a panel, and it must not compete with the tabs under it. */
.jc-stage {
  padding: 12px 20px 10px; border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--taupe-soft) 55%, #fff);
}
.jc-stage .stagebar { margin: 0; }
.jc-stage .stage-hint { margin: 8px 0 0; }

.jc-tabs { display: flex; gap: 2px; padding: 0 20px; border-bottom: 1px solid var(--line); }
.jc-tabs button {
  border: 0; background: none; padding: 11px 14px; font-size: 15px; font-weight: 600;
  color: var(--muted); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.jc-tabs button:hover { color: var(--ink); }
.jc-tabs button.active { color: var(--accent-ink); border-bottom-color: var(--accent); }
.jc-count { color: var(--muted); font-weight: 500; font-size: 12.5px; }

/* locked "From the customer (Shopify)" block — reference only */
.ov-locked { border: 1px solid var(--line); background: var(--taupe-soft); border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 16px; }
.ov-locked-head { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
.ov-lock { font-size: 11px; font-weight: 700; background: #e7ddd0; color: #6a5a45; border-radius: 6px; padding: 0 6px; letter-spacing: .02em; text-transform: none; }
.ov-locked .ov-row { padding: 4px 0; }
.ov-locked .ov-k { color: var(--muted); }

/* "updated details" leads (view + edit) */
.ov-working-lead { font-size: 13.5px; font-weight: 700; color: var(--primary-dark); margin-bottom: 12px; }
.ov-working-lead span { font-weight: 400; color: var(--muted); }
.ov-edit-lead { font-size: 12.5px; color: var(--primary-dark); background: #fbf3ec; border: 1px solid #ecd9c9; border-radius: var(--r-sm); padding: 8px 10px; margin-bottom: 14px; }

/* read-only overview */
.ov-group { margin-bottom: 18px; }
.ov-group:last-child { margin-bottom: 4px; }
.ov-group-head {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--primary-dark); margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1px solid var(--taupe-soft);
}
.ov-row { display: flex; gap: 14px; padding: 6px 0; align-items: baseline; }
.ov-k { flex: 0 0 116px; font-size: 13.5px; color: var(--muted); font-weight: 600; }
.ov-v { flex: 1; min-width: 0; font-size: 15px; color: var(--ink); word-break: break-word; }
.ov-empty { color: var(--muted); font-style: italic; }
.ov-mini { font-size: 12.5px; color: var(--primary-dark); text-decoration: none; margin-left: 6px; white-space: nowrap; }
/* Some of these are <button>s ("Make a task from this"). They read as links, so the
   browser's own button box must not show — it appeared as a grey native button the
   moment the touch rules gave them a 44px height. */
button.ov-mini { background: none; border: 0; padding: 0 4px; font-family: inherit; cursor: pointer; }
.ov-mini:hover { text-decoration: underline; }
.ov-edited {
  display: inline-block; font-size: 11px; font-weight: 700; color: #8a5a00;
  background: #fbe7cf; border: 1px solid #efce9b; border-radius: 6px; padding: 0 6px; margin-left: 8px; cursor: help;
}

/* grouped edit form */
.ov-edit .jc-group { border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px 14px 4px; margin-bottom: 14px; }
.ov-edit .jc-group-head {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--primary-dark); margin-bottom: 10px;
}

/* "changed from the customer's original" block (History tab) */
.ov-changes { border: 1px solid #efce9b; background: #fdf6ea; border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 16px; }
.chg-head { font-size: 13.5px; font-weight: 700; color: #8a5a00; margin-bottom: 10px; }
.chg-row { padding: 7px 0; border-top: 1px solid #e6d3ab; }
.chg-row:first-of-type { border-top: 0; }
.chg-field { font-size: 12.5px; font-weight: 700; color: var(--ink); margin-bottom: 3px; }
.chg-line { font-size: 13.5px; color: var(--ink); padding: 1px 0; }
.chg-tag {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  border-radius: 6px; padding: 0 6px; margin-right: 6px; background: #e7ddd0; color: #6a5a45;
}
.chg-tag.now { background: var(--primary); color: #fff; }

@media (max-width: 560px) {
  .ov-row { flex-direction: column; gap: 2px; }
  .ov-k { flex-basis: auto; }
  .jc-tabs { overflow-x: auto; }
}

.type-toggle { display: flex; gap: 0; margin-bottom: 16px; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.type-toggle button {
  flex: 1; padding: 9px; border: 0; background: color-mix(in srgb, var(--bg) 55%, #fff); cursor: pointer; font-size: 15px; font-weight: 600; color: var(--muted);
}
.type-toggle button.active { background: var(--accent); color: #fff; }

/* autocomplete */
.autocomplete-wrap { position: relative; }
.suggestions {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
  background: #fff; border: 1px solid var(--line); border-radius: 0 0 10px 10px;
  box-shadow: 0 12px 24px rgba(0,0,0,.12); max-height: 260px; overflow-y: auto;
}
.suggestion {
  padding: 9px 12px; cursor: pointer; border-bottom: 1px solid color-mix(in srgb, var(--bg) 70%, #fff);
}
.suggestion:hover, .suggestion.active { background: var(--accent-tint); }
.suggestion .s-top { font-weight: 700; font-size: 15px; color: var(--primary-dark); }
.suggestion .s-sub { font-size: 12.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggestion.none { color: var(--muted); cursor: default; font-size: 13.5px; }
/* ONE PERSON PICKER (unification step 6) — personSuggest in app.js. A name, and
   beside it whether that person is in on the day: green on shift, amber on leave. */
.pp-wrap { position: relative; flex: 1 1 200px; min-width: 0; }
/* That 200px basis is a WIDTH in a row of fields. Inside a field stacked top to
   bottom it becomes a HEIGHT: Team's Time off sheet drew a 200px box round a 44px
   input, leaving an empty gap under "Person" on every phone. A stacked field sizes
   the picker to its input. */
/* Dispatch's crew picker (.db-field) and Cash's hand-over sheet (.mwr-sheet-body)
   stack the same way. */
:is(.ws-field, .field, .wiz-field, .te-field, .br-f, .db-field, .mwr-sheet-body) > .pp-wrap { flex: 0 0 auto; }
.pp-wrap > input { width: 100%; box-sizing: border-box; }
.pp-sug { max-height: 300px; overflow-y: auto; }
.suggestion.pp-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.pp-name { min-width: 0; font-weight: 600; font-size: 15px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pp-state {
  flex: none; font-size: 12px; font-weight: 600; padding: 2px 9px; border-radius: 999px; white-space: nowrap;
  background: var(--taupe-soft); color: var(--muted);
}
.pp-state.is-shift { background: color-mix(in srgb, var(--green) 14%, #fff); color: color-mix(in srgb, var(--green) 82%, #10231a); }
.pp-state.is-leave { background: color-mix(in srgb, var(--amber) 16%, #fff); color: color-mix(in srgb, var(--amber) 82%, #2a1706); }
@media (pointer: coarse) { .suggestion.pp-row { min-height: 44px; } }
.linked-order {
  margin-top: 8px; background: color-mix(in srgb, var(--green) 8%, #fff); border: 1px solid color-mix(in srgb, var(--green) 25%, #fff); color: color-mix(in srgb, var(--green) 88%, #0e1a14);
  border-radius: var(--r-sm); padding: 8px 12px; font-size: 13.5px; display: flex; justify-content: space-between; align-items: center;
}
.linked-order button { border: 0; background: none; color: color-mix(in srgb, var(--green) 88%, #0e1a14); cursor: pointer; font-weight: 700; }

/* history inside entry modal */
.entry-history { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 10px; }
.entry-history h4 { font-size: 13.5px; color: var(--muted); margin-bottom: 8px; }
/* The job's story, newest first and grouped by day — it used to be a flat wall of
   rows each repeating the same date. The icon column lets the eye skip straight to
   the kind of event it is after. */
.hist-day { margin-bottom: 14px; }
.hist-daylabel {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); padding-bottom: 5px; margin-bottom: 5px; border-bottom: 1px solid var(--hairline);
}
.hist-row {
  display: grid; grid-template-columns: 20px 1fr auto; gap: 8px; align-items: baseline;
  font-size: 12.5px; color: var(--ink-2); padding: 4px 0;
}
.hist-row b { color: var(--ink); font-weight: 600; }
.hist-ico { text-align: center; font-size: 12.5px; }
.hist-body { min-width: 0; overflow-wrap: anywhere; }
.hist-meta { font-size: 11px; color: var(--muted); white-space: nowrap; }
/* The two that change what the job IS get a little more weight than the rest. */
.hist-row.act-created b, .hist-row.act-deleted b { color: var(--primary-dark); }
@media (max-width: 560px) {
  .hist-row { grid-template-columns: 20px 1fr; }
  .hist-meta { grid-column: 2; }
}

/* activity */
.activity-list { display: flex; flex-direction: column; }
.act-row { display: flex; gap: 10px; padding: 9px 4px; border-bottom: 1px solid color-mix(in srgb, var(--bg) 70%, #fff); font-size: 13.5px; align-items: baseline; }
.act-time { color: var(--muted); font-size: 12.5px; white-space: nowrap; min-width: 120px; }
.act-user { font-weight: 700; color: var(--primary-dark); min-width: 80px; }
.act-action { font-size: 11px; font-weight: 700; text-transform: uppercase; border-radius: 6px; padding: 2px 6px; background: color-mix(in srgb, var(--bg) 70%, #fff); color: var(--ink-2); white-space: nowrap; }
.act-details { color: var(--ink-2); }

/* admin */
.readonly-badge { font-size: 11px; font-weight: 700; background: color-mix(in srgb, var(--green) 13%, #fff); color: color-mix(in srgb, var(--green) 88%, #0e1a14); padding: 3px 8px; border-radius: 6px; vertical-align: middle; }
.modal h3 { font-size: 15px; margin: 10px 0 12px; }
.modal h4 { font-size: 15px; margin: 14px 0 8px; }
.modal hr { border: 0; border-top: 1px solid var(--line); margin: 18px 0; }
.users-list { display: flex; flex-direction: column; gap: 6px; }
.user-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: color-mix(in srgb, var(--bg) 55%, #fff); border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 15px; }
.user-row .role { font-size: 11px; font-weight: 700; background: color-mix(in srgb, var(--zone-others) 13%, #fff); color: color-mix(in srgb, var(--zone-others) 90%, #1e1830); padding: 2px 8px; border-radius: 6px; }
.user-row .inactive { color: var(--red); font-size: 12.5px; }
.user-row .spacer { flex: 1; }
.user-row button { font-size: 12.5px; padding: 4px 10px; }

/* toast — ONE MESSAGE, FOUR KINDS (step 8). See toast() in app.js. It rises into
   place, carries an icon for its kind, and wraps rather than running off a phone. */
#toast {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%) translateY(12px);
  display: flex; align-items: center; gap: 10px;
  width: max-content; max-width: min(560px, calc(100vw - 28px));
  background: var(--ink); color: #fff; padding: 9px 18px 9px 12px; border-radius: 22px;
  font-size: 14.5px; line-height: 1.35; z-index: 100; box-shadow: 0 12px 32px rgba(20,22,30,.28);
  opacity: 0; transition: opacity .22s ease, transform .26s cubic-bezier(.2,.9,.3,1.2); pointer-events: none;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
#toast .t-ico {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; background: rgba(255,255,255,.16);
}
#toast .t-ico:empty { display: none; }
#toast .t-ico b { font-size: 14px; font-weight: 800; line-height: 1; }
#toast .t-msg { min-width: 0; }
#toast:has(.t-ico:empty) { padding-left: 18px; }
#toast.k-success { background: #2e5a42; }
#toast.k-success .t-ico { background: rgba(255,255,255,.2); animation: tfToastBloom .5s cubic-bezier(.2,1.5,.4,1) both; }
#toast.k-error { background: #8e3a33; }
#toast.k-error .t-ico { background: rgba(255,255,255,.22); }
#toast.k-warn { background: #7d5314; }
#toast.k-busy .t-ico { background: none; width: 28px; height: 28px; }
@keyframes tfToastBloom { from { transform: scale(.3) rotate(-40deg); opacity: 0; } }

/* THE FLOWER (step 8) — flowerSvg() in app.js. A tulip whose petals open and close
   while it sways. Sized by --fl; coloured here so it reads on the page and on a toast. */
.tf-flower { display: inline-block; width: var(--fl, 28px); height: var(--fl, 28px); flex: none; vertical-align: middle; line-height: 0; }
.tf-flower svg { width: 100%; height: 100%; overflow: visible; }
.tf-flower svg * { transform-box: view-box; }
.tf-flower .fl-stem { fill: none; stroke: var(--fl-leaf, #6f9a7d); stroke-width: 2.4; stroke-linecap: round; }
.tf-flower .fl-leaf { fill: var(--fl-leaf, #6f9a7d); }
.tf-flower .fl-l, .tf-flower .fl-r { fill: var(--fl-petal, #c98d6f); }
.tf-flower .fl-c { fill: var(--fl-petal-mid, #a86a4d); }
.tf-flower .fl-head { transform-origin: 24px 30px; animation: tfFlSway 2.2s ease-in-out infinite; }
.tf-flower .fl-l { transform-origin: 24px 27px; animation: tfFlOpenL 1.1s ease-in-out infinite alternate; }
.tf-flower .fl-r { transform-origin: 24px 27px; animation: tfFlOpenR 1.1s ease-in-out infinite alternate; }
.tf-flower .fl-c { transform-origin: 24px 26px; animation: tfFlRise 1.1s ease-in-out .12s infinite alternate; }
.tf-flower.is-still * { animation: none !important; }
.tf-flower.on-dark { --fl-petal: #f1cdb8; --fl-petal-mid: #fbe6d9; --fl-leaf: #a9d6b8; }
@keyframes tfFlOpenL { from { transform: rotate(9deg); } to { transform: rotate(-15deg); } }
@keyframes tfFlOpenR { from { transform: rotate(-9deg); } to { transform: rotate(15deg); } }
@keyframes tfFlRise { from { transform: scale(.84); } to { transform: scale(1.05); } }
@keyframes tfFlSway { 0%, 100% { transform: rotate(-5deg); } 50% { transform: rotate(5deg); } }
@media (prefers-reduced-motion: reduce) { .tf-flower * { animation: none !important; } #toast .t-ico { animation: none !important; } }

/* A whole area waiting: the flower and a word, where the content will be. */
.tf-loading {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 56px 16px; color: var(--muted); font-size: 13.5px; text-align: center;
}
.tf-loading.is-compact { flex-direction: row; padding: 18px 12px; }

/* ONE DIALOG HEADER (step 8, 2026-09-11). Seven kinds of dialog printed their titles seven
   ways — 26px, 20px, 1.25em, bare h3. Same face, size and weight now, and one gap between
   footer buttons. `body` in front so a module's own stylesheet, loaded later, cannot undo it. */
body :is(.sch-modal-head, .db-modal-head, .fl-modal-head) h3,
body .mwr-sheet > h4,
body .tf-panel-title { font-family: var(--serif); font-size: 22px; font-weight: 600; letter-spacing: .01em; line-height: 1.25; }
body .modal-header h2:not(.is-ref) { font-size: 24px; }
body :is(.modal-footer, .sch-modal-foot, .db-modal-foot, .br-form-acts, .te-actions) { gap: 10px; }
/* One page padding: Home started 6px lower than every other page. */
@media (min-width: 761px) { body .home-wrap { padding-top: 20px; } }

/* ---------- uploading a photo: the bar, and the tiles ----------
   See public/upload.js. Two different waits are shown, because they feel
   different: squeezing a 4 MB phone photo has no percentage to give and gets a
   moving stripe, and sending it does and gets a filling bar. A bar sitting at 0%
   while a phone re-encodes reads as a bar that has hung. */
.up-bar {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%) translateY(8px);
  width: min(420px, 92vw); z-index: 1310;
  background: var(--ink); color: #fff; border-radius: var(--r-md);
  padding: 11px 15px 13px; box-shadow: 0 10px 30px rgba(0,0,0,.32);
  opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
.up-bar.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* The toast steps aside rather than sitting under it — they are both "what is
   happening right now" and two of them stacked is one too many. */
.up-bar.show ~ #toast.show { transform: translateX(-50%) translateY(-74px); }
.up-bar-row { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; }
.up-bar-what { flex: 1; min-width: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-bar-pct { font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.up-bar-track { height: 5px; border-radius: 3px; background: rgba(255,255,255,.22); overflow: hidden; }
.up-bar-fill { height: 100%; width: 0; background: #fff; border-radius: 3px; transition: width .18s linear; }
/* No number to show yet. A stripe that moves says "working" without lying about
   how far along it is. */
.up-bar.waiting .up-bar-fill {
  background: repeating-linear-gradient(115deg, rgba(255,255,255,.85) 0 12px, rgba(255,255,255,.3) 12px 24px);
  animation: upStripe 900ms linear infinite;
}
@keyframes upStripe { from { background-position: 0 0; } to { background-position: 34px 0; } }
@media (prefers-reduced-motion: reduce) { .up-bar.waiting .up-bar-fill { animation: none; } }

/* The tile that stands where the photo is about to appear. Sized to the two
   thumbnail strips it lands in (.kb-thumb 52px, .td-thumb 54px). */
.up-tiles { display: contents; }
.up-tile {
  display: inline-grid; place-items: center; position: relative;
  width: 54px; height: 54px; border-radius: 6px; margin-right: 6px; vertical-align: top;
  border: 1px dashed var(--taupe); background: var(--taupe-soft);
  /* The ring fills clockwise with --pct, so the tile itself is the progress. */
  --pct: 0;
}
.up-tile-ring {
  position: absolute; inset: 4px; border-radius: 4px;
  background: conic-gradient(var(--primary) calc(var(--pct) * 1%), transparent 0);
  opacity: .3;
}
.up-tile-pct {
  position: relative; font-size: 11px; font-weight: 700; color: var(--primary-dark);
  font-variant-numeric: tabular-nums;
}
.up-tile.waiting .up-tile-ring { opacity: .18; }
.up-tile.failed { border-color: var(--red); border-style: solid; background: color-mix(in srgb, var(--red) 8%, #fff); }
.up-tile-retry {
  width: 100%; height: 100%; border: 0; background: none; cursor: pointer;
  color: var(--red); font-size: 20px; line-height: 1; border-radius: 6px;
}
.up-tile-retry:hover { background: color-mix(in srgb, var(--red) 14%, #fff); }

/* ---------- confirmation dialog ---------- */
/* Sits above the modal that raised it, and above the toast. */
#confirmModal { z-index: 150; }
.modal-confirm { width: 430px; max-width: 94vw; }
.modal-confirm .modal-header { border-bottom: none; padding-bottom: 0; }
.confirm-message { font-size: 15px; line-height: 1.5; }
.confirm-changes {
  margin: 12px 0 0; padding: 10px 12px 10px 28px;
  background: var(--taupe-soft); border-radius: var(--r-sm);
  font-size: 13.5px; line-height: 1.7; color: var(--ink);
}
.confirm-changes li { margin: 0; }

/* ---------- app shell: sidebar + pages ---------- */
#app { display: flex; height: 100vh; overflow: hidden; }
.side-nav {
  width: 236px; flex: none; background: var(--card); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0;
}
.side-brand { display: flex; align-items: center; gap: 10px; padding: 14px 14px 12px; border-bottom: 1px solid var(--line); }
.nav-list { flex: 1; overflow-y: auto; padding: 10px 10px 16px; }
.nav-heading {
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase; font-weight: 700;
  color: var(--muted); margin: 16px 10px 5px;
}
.nav-item {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 10px 12px; border: none; border-radius: var(--r-sm); background: none; cursor: pointer;
  font-size: 15px; color: var(--ink); font-family: inherit; margin-top: 2px;
}
.nav-item:hover { background: var(--taupe-soft); }
.nav-item.active { background: var(--accent-fill); color: #fff; font-weight: 600; }
.nav-ico { width: 22px; font-size: 17px; text-align: center; flex: none; }
.nav-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.side-foot { border-top: 1px solid var(--line); padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }
.side-foot-btns { display: flex; flex-wrap: wrap; gap: 6px; }

/* The account block. Name first and full strength, role beneath it and quiet —
   the old version crammed "Administrator (super admin)" into one pill, which
   made the role shout as loudly as the person. */
.side-acct { display: flex; align-items: center; gap: 10px; padding: 2px 2px 4px; min-width: 0; }
.side-avatar {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--r-pill);
  display: grid; place-items: center; font-size: 12.5px; font-weight: 700;
  letter-spacing: .02em; background: var(--accent-soft); color: var(--accent-ink);
}
.side-acct-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.side-acct-name { font-weight: 600; font-size: 13.5px; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.side-acct-role { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .07em; }
.btn-sm { padding: 5px 10px; font-size: 12.5px; }

.main-area { flex: 1; min-width: 0; overflow-y: auto; display: flex; flex-direction: column; }

/* ---------- the app's one bar ----------
   This row used to carry the page name and nothing else, with the best real
   estate on screen — the full width, always visible — left blank, while every
   page stacked its own toolbars underneath it. Now it carries three things:
   who you are looking at (title), how you are looking at it (#headTools), and
   what you can do to it (#headActions). It is white like the sidebar, so the
   chrome reads as one frame around the ivory work area, and it is sticky, so
   the controls never scroll away from the work they act on. */
/* Frosted, the way Apple floats a sub-nav over the content it belongs to:
   translucent paper + a saturating blur, and a hairline instead of a border.
   The work scrolling underneath stays faintly visible, so the bar reads as
   sitting ON the page rather than cutting it in half. */
.top-strip {
  position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 10px;
  padding: 7px 16px; min-height: 54px;
  /* .main-area is a column flex container, so without this the bar is a
     shrinkable item: the moment it wrapped to two lines the container squashed
     it back to min-height and the second line spilled over the page. */
  flex: none;
  background: rgba(255, 255, 255, .82);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--hairline);
}
/* Browsers without backdrop-filter must not get a see-through bar. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .top-strip { background: var(--card); }
}
.top-strip .spacer { flex: 1; min-width: 8px; }
/* The page's name never shrinks — a title squeezed to "W" tells you nothing,
   and the bar wraps to a second line before it comes to that. */
/* The block may shrink — but only by giving up the status message; .page-title
   below is flex:none, so the page's own name is still never squeezed. */
.head-id { display: flex; align-items: baseline; gap: 8px; flex: 0 1 auto; min-width: 0; }
.page-title {
  font-family: var(--serif); font-size: 20px; font-weight: 600; letter-spacing: -0.01em;
  white-space: nowrap;
  flex: none;              /* the page's own name is the one thing that never gives */
}
#syncBadge { white-space: nowrap; }
/* A hairline instead of a gap: the eye separates the page's name from its
   controls without spending width on emptiness. */
/* `flex-wrap: wrap` is not decoration — it is what makes an overlap IMPOSSIBLE.
   When the controls genuinely cannot share a line, the row takes a second one and
   the bar gets taller. Every previous fix here was a breakpoint tuned to the widths
   somebody happened to measure, and each new control in the bar broke it again at
   some width nobody tried. A bar that wraps is right at every width by
   construction; the media queries below are now only there to keep the second line
   RARE, not to keep the bar correct. */
.head-tools { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; row-gap: 6px; }
/* Both slots are always in the markup — the board's and the open module's — so
   an empty one has to disappear completely, or it claims a whole wrapped line
   for nothing. */
.head-tools:empty { display: none; }
.head-tools:not(:empty) { padding-left: 13px; border-left: 1px solid var(--hairline); }
.head-actions { display: flex; align-items: center; gap: 7px; flex: none; }
.head-actions:empty { display: none; }
/* Controls in the bar run one size tighter than in the page body — a toolbar
   is scanned, not read. */
.top-strip .btn { padding: 6px 12px; font-size: 13.5px; }
.top-strip .btn-ico { padding: 3px 9px; font-size: 20px; line-height: 1.1; }
.top-strip .date-pick { padding: 5px 12px; font-size: 13.5px; }
/* min-width:0, NOT 170px. .head-tools is `min-width: 0`, so flexbox will shrink
   that box below its own content — and when the content inside refuses to shrink
   it does not get clipped, it PAINTS OUT over whatever comes next in the bar.
   Measured on the Work board at 1400px: the tools box was 425px wide while its
   children reached 156px past its right edge, putting the search box and "More
   search" underneath the "+ Add order or task" button.
   A search input also carries an intrinsic default width of about 170px, so the
   input itself needs min-width:0 too or the wrapper can never actually give. */
.top-strip .search-wrap { min-width: 0; max-width: 320px; }
/* A floor, because a search box narrower than this is not a search box — it shows
   about two characters and reads as a decoration. `min-width: 0` above lets the
   WRAPPER give way (that is what stops the bar wrapping); this stops the INPUT
   inside it from being ground to a sliver. When the bar genuinely runs out of
   room, the "Other days" word goes first — see below — and the input keeps its
   floor. Measured after the arrangement controls moved up here on 2026-08-05:
   without this the input was 65px at 1920. */
.top-strip .search-wrap input[type=search] { padding: 6px 12px; font-size: 13.5px; min-width: 128px; }
/* …AND THE WRAPPER CARRIES THE SAME FLOOR. This is the bug the owner reported on
   2026-08-05: "the search is hidden behind" the Add button. `min-width: 0` let
   flexbox squeeze the WRAPPER to 50px while the input inside it still insisted on
   128px — and content that cannot shrink is not clipped, it PAINTS OUTWARD, so the
   input came out from under its own box and landed beneath "+ Add order or task".
   Measured at 1400px: wrapper 50px, input 128px, elementFromPoint over the input's
   right edge returned the Add button's label.
   It is the same mechanism as the 2026-08-02 fix one level deeper: that one gave
   the input `min-width: 0` so it COULD shrink; the floor added on 2026-08-05 (for
   the good reason that a 65px search is not a search) took that away again without
   giving the wrapper the matching floor. Both are needed: a floor the box respects,
   and — below — a row that wraps rather than overlaps when the floor cannot be met. */
.top-strip .search-wrap { min-width: 128px; flex: 1 1 128px; }
/* The least-useful pixels in the bar when it is tight: the word beside a magnifier
   that already means "search other days", and whose tooltip says so. */
@media (max-width: 1500px) {
  .top-strip #advSearchBtn .btn-word { display: none; }
  /* The arrangement trigger keeps its ▦ icon, its caret and its tooltip; the word
     beside them ("Areas", "By day"…) is ~55px the search needs more. Scoped to the
     bar, so the copy that drops onto the page below 1150px keeps its label. */
  /* The WORD goes, the icon stays. This used to hide the whole label — and the
     default arrangement, Areas, had no icon — so from 1500px down the trigger was
     an empty box with a caret in it. Every arrangement now carries an icon. */
  .top-strip .bview-label .bview-word { display: none; }
  .top-strip .bview-btn { padding-left: 9px; padding-right: 7px; }
}
/* The date picker is the other thing in this slot that cannot give: it holds a
   written-out date. It may shrink and ellipsise rather than push its neighbours. */
.top-strip .date-nav { min-width: 0; }
.top-strip .date-pick { min-width: 0; overflow: hidden; }
.top-strip .date-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-btn { display: none; font-size: 20px; }
.page { flex: 1; min-width: 0; }
.nav-shade { position: fixed; inset: 0; background: rgba(20,16,12,.4); z-index: 59; }

/* ---------- when the bar runs out of room ----------
   Below ~1250px the busiest page (the Work board: a day picker, a search box
   and two buttons) can no longer share one line with its title. Rather than
   letting the controls slide over each other, the bar becomes two lines: the
   page's name and its buttons stay on top, and the controls that say WHAT YOU
   ARE LOOKING AT drop underneath and take the full width. Still one bar; it
   just breathes.

   Raised from 1180 to 1250 on 2026-08-02. At 1190 nothing overflowed any more,
   but the search box had been squeezed to 37px — technically inside its box and
   useless. A control compressed past legibility is not a fix, so the bar now
   wraps while the search is still ~80px, and gets the whole width once it does. */
/* THE BOARD REACHES THAT POINT MUCH EARLIER THAN ANY OTHER PAGE, and did so the
   moment the arrangement control moved into the bar on 2026-08-05. Its tools are a
   day picker (302px), that arrangement block (270px) and a search — 747px of a
   slot that is only about 43% of the viewport once the sidebar, the title and the
   buttons have taken theirs.

   MEASURED, not guessed: at 1800px the slot is 777px and the search sits exactly
   on its 128px floor; at 1600px the slot is 724px against a 908px need. So one
   line genuinely stops fitting just under 1800 — which is why the owner saw the
   search underneath the Add button on an ordinary screen.

   Scoped with :has() so My work, Dispatch and every lighter page keep their single
   line down to 1250 and do not lose 37px of vertical to a rule that is not about
   them. If the board's bar is ever made lighter, lower this number — but MEASURE
   it. Every previous number here was a guess, and every one of them broke. */
@media (max-width: 1780px) {
  /* `:not(.hidden)`, NOT `:not(:empty)` — the board's slot keeps its controls in
     the DOM when you navigate away and is merely given `.hidden`, so an emptiness
     test matches on every page and would drop the whole app to a two-line bar. */
  .top-strip:has(#boardHeadTools:not(.hidden)) { flex-wrap: wrap; row-gap: 7px; }
  .top-strip:has(#boardHeadTools:not(.hidden)) #boardHeadTools {
    order: 10; flex: 1 0 100%; padding-left: 0; border-left: 0;
  }
  .top-strip:has(#boardHeadTools:not(.hidden)) #boardHeadTools .search-wrap { max-width: none; flex: 1 1 128px; }
}
@media (max-width: 1250px) {
  .top-strip { flex-wrap: wrap; row-gap: 7px; }
  /* flex-basis, not width: a module that gives its own slot `flex: 1` (dispatch
     does, to spread its day picker) would otherwise win with a 0 basis and
     squeeze the whole row into a sliver. */
  .head-tools { order: 10; flex: 1 0 100%; padding-left: 0; border-left: 0; }
  .head-tools .search-wrap { max-width: none; flex: 1; min-width: 120px; }
}
/* Between the wrap breakpoint and a wide screen there is a band where the Work
   board's day picker + search genuinely cannot share the line with two full-width
   buttons. "⟳ Check for new orders" is ~180px of it and is the least-used control
   on the bar, so its WORD goes first (the icon and its tooltip stay). That buys
   back enough for the search box to stay usable instead of collapsing to a sliver
   and spilling over the buttons. Measured: at 1250px the search had shrunk to 26px
   and still overflowed by 38px; with the word dropped it fits with room to spare. */
@media (max-width: 1400px) {
  .head-actions .btn-word { display: none; }
}
/* The WORK BOARD's bar carries more than any other: the date, how much to show,
   how it is arranged, and the search. That is deliberate — those questions belong
   together and it bought the page back 87px — but it means this bar runs out of
   room sooner than the rest, so it gives up the same word sooner. Scoped to the
   board so no other screen loses a label it has room for. */
@media (max-width: 1600px) {
  :root[data-module="board"] .head-actions .btn-word { display: none; }
}
/* Tighter still, and the board drops the one control that repeats its neighbour:
   the day pill beside it already reads "Today · Wed 5 Aug". */
@media (max-width: 1330px) {
  :root[data-module="board"] #todayBtn { display: none; }
}
/* Tighter still: the buttons keep their icons and drop their words. */
@media (max-width: 760px) {
  .head-actions .btn-word, .head-actions .dx-word { display: none; }
  .head-actions .btn, .head-actions .ws-btn { padding: 6px 11px; }
  .head-actions .btn-primary, .head-actions .ws-btn.primary { padding: 6px 14px; }
}

/* module pages — full width on purpose. Every module is a working screen
   (boards, rosters, maps, calendars); a centred 1100px column wasted a third of
   a wide monitor and forced sideways scrolling inside the modules themselves. */
.module-root { padding: 20px 26px 40px; width: 100%; }
.ws-empty { color: var(--muted); text-align: center; padding: 40px 16px; font-size: 15px; }

/* home */
.home-wrap { padding: 26px 26px 40px; width: 100%; }
.home-hello h2 { font-family: var(--serif); font-size: 26px; font-weight: 600; letter-spacing: .01em; }
.home-sub { color: var(--muted); margin-top: 2px; font-size: 15px; }
.home-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; margin-top: 22px; }
.home-tile {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-height: 150px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 18px;
  cursor: pointer; text-align: left; font-family: inherit; font-size: inherit; color: var(--ink);
  transition: background .18s ease, border-color .18s ease, transform .12s ease;
}
.home-tile:hover { border-color: var(--primary); background: #fffdfb; }
.tile-big { font-family: var(--serif); font-size: 40px; font-weight: 700; color: var(--primary-dark); line-height: 1; }
.tile-txt { font-size: 15px; line-height: 1.45; }
.tile-cta { margin-top: auto; font-size: 13.5px; font-weight: 600; color: var(--primary-dark); }
.home-welcome { margin-top: 24px; line-height: 1.65; }
.home-welcome .btn { margin-left: 10px; }

/* ---------- foldable sidebar (desktop) ----------
   The ☰ button folds the menu down to icons so a wide working screen — a
   kanban, a month calendar, the live map — gets the whole window. Icons stay
   visible (and keep their tooltips) so nobody loses their way; the choice is
   remembered in localStorage. On phones the same button opens the drawer
   instead, which is the behaviour that was always there. */
.menu-btn { display: inline-block; }
.side-nav { transition: width .16s ease; }
body.nav-folded .side-nav { width: 66px; }
body.nav-folded .brand-block,
body.nav-folded .nav-text,
body.nav-folded .fb-label,
body.nav-folded .side-acct-txt { display: none; }
body.nav-folded .side-acct { justify-content: center; }
body.nav-folded .side-brand { justify-content: center; padding: 14px 6px 12px; }
body.nav-folded .nav-list { padding: 10px 8px 16px; }
body.nav-folded .nav-item { justify-content: center; padding: 11px 0; gap: 0; }
body.nav-folded .nav-ico { width: auto; font-size: 20px; }
/* Group headings would be unreadable at this width, so they become the rule
   they were always standing in for. */
body.nav-folded .nav-heading {
  font-size: 0; margin: 12px 6px 8px; height: 1px; background: var(--line);
}
body.nav-folded .side-foot { padding: 10px 8px; align-items: center; }
body.nav-folded .side-foot-btns { flex-direction: column; gap: 4px; width: 100%; }
body.nav-folded .side-foot-btns .btn { width: 100%; text-align: center; padding: 7px 0; font-size: 15px; }

@media (max-width: 860px) {
  .side-nav {
    position: fixed; left: 0; top: 0; bottom: 0; z-index: 60; width: 260px;
    transform: translateX(-105%); transition: transform .2s ease; box-shadow: 8px 0 30px rgba(0,0,0,.18);
  }
  body.nav-open .side-nav { transform: none; }
  .module-root, .home-wrap { padding: 16px 14px 40px; }
  .top-strip { padding: 7px 12px; }
  /* Folding is a desktop idea; on a phone the drawer already does this job. */
  body.nav-folded .side-nav { width: 260px; }
  body.nav-folded .brand-block,
  body.nav-folded .nav-text,
  body.nav-folded .fb-label,
  body.nav-folded .side-acct-txt { display: revert; }
  body.nav-folded .nav-item { justify-content: flex-start; padding: 10px 12px; gap: 11px; }
  body.nav-folded .nav-heading { font-size: 11px; height: auto; background: none; margin: 16px 10px 5px; }
  body.nav-folded .side-foot-btns { flex-direction: row; }
  body.nav-folded .side-foot-btns .btn { width: auto; padding: 5px 10px; font-size: 12.5px; }
}

/* shared module toolkit — modules use these so they look like one product
   and never need to add global CSS of their own. */
.ws-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
/* A row with nothing in it still costs its margin. Several modules print an
   empty one on the way to their content. */
.ws-head:empty, .ws-toolbar:empty { display: none; }
.ws-title { font-family: var(--serif); font-size: 26px; font-weight: 600; letter-spacing: .01em; }
/* The app bar already prints the page's name (showPage sets #pageTitle from the
   menu). A module that prints it again puts two different-sized copies of the
   same word 30px apart — and several of them disagreed with the menu about what
   the page is even called. Modules now mark their own copy .ws-title-dup and it
   is shown only to screen readers, so nothing is lost for anyone. */
.ws-title-dup {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.ws-sub { color: var(--muted); font-size: 13.5px; margin: 0 0 12px; line-height: 1.5; max-width: 92ch; }
.ws-toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; }
.ws-toolbar .spacer { flex: 1; }
/* ONE segmented control for the whole app. The board, Dispatch, Team and Staff
   tasks each grew their own (.view-group, .dx-views, .sch-views, .stf-viewpick)
   — same widget, four looks. New switchers use this one. */
.ws-seg { display: inline-flex; background: var(--taupe-soft); border-radius: var(--r-md); padding: 3px; gap: 2px; }
.ws-seg-btn {
  border: 0; background: none; color: var(--muted); font-family: inherit; font-size: 13.5px;
  padding: 5px 12px; border-radius: 6px; cursor: pointer; white-space: nowrap;
}
.ws-seg-btn:hover { color: var(--ink); }
.ws-seg-btn.on { background: var(--accent-fill); color: #fff; font-weight: 600; }
/* the Reports custom day range — only on screen once you ask for it */
.rp-range { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
/* How many days the report covers, said out loud beside the pill — "3 – 9 Aug"
   is a span you still have to count, and the number is the thing being compared
   when somebody puts two reports side by side. */
.rp-range-n { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.rp-range label { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.rp-range .ws-input { width: auto; }
.ws-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.ws-card { border: 1px solid var(--hairline); border-radius: var(--r-lg); background: var(--card); padding: 16px 17px; }
.ws-card h4 { margin: 0 0 4px; font-size: 15px; }
.ws-card .muted { color: var(--muted); font-size: 13.5px; }
.ws-btn { border: 1px solid var(--hairline); background: var(--card); color: var(--ink); padding: 7px 13px; border-radius: var(--r-sm); cursor: pointer; font-size: 13.5px; font-family: var(--ui); white-space: nowrap; letter-spacing: -0.01em; }
.ws-btn:hover { background: var(--taupe-soft); }
.ws-btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; border-radius: var(--r-pill); padding: 7px 17px; }
.ws-btn.primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.ws-btn.warn { background: #fbf0e2; border-color: #e6c79b; color: #8a5a00; font-weight: 600; }
.ws-btn.warn:hover { background: #f6e6cf; }
.kb-shared-note { font-size: 11px; color: var(--muted); margin-top: 6px; }
.ct-time { font-variant-numeric: tabular-nums; color: var(--muted); }
.ct-time.live { color: var(--green); font-weight: 700; }
.chip.stage.partial { background: var(--amber); color: #fff; }
.ws-btn.danger { background: #fff1f1; border-color: #f3c1c1; color: var(--red); }
.ws-btn.sm { padding: 4px 9px; font-size: 12.5px; }
.ws-input, .ws-select, .ws-textarea { width: 100%; padding: 9px 12px; border: 1px solid var(--hairline); border-radius: var(--r-md); font-size: 13.5px; font-family: var(--ui); background: var(--card); color: var(--ink); letter-spacing: -0.01em; }
.ws-input:focus, .ws-select:focus, .ws-textarea:focus { outline: 2px solid var(--primary); outline-offset: 0; border-color: var(--primary); }
.ws-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
.ws-field > label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.ws-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 560px) { .ws-form-grid { grid-template-columns: 1fr; } }
.ws-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.ws-table th, .ws-table td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.ws-table thead th { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.ws-table tbody tr:hover { background: #faf7f4; }
.ws-scroll { overflow-x: auto; }
.ws-chip { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: var(--r-pill); background: var(--taupe-soft); color: var(--primary-dark); }
.ws-chip.green { background: color-mix(in srgb, var(--green) 14%, #fff); color: var(--green); }
.ws-chip.amber { background: #f6ecdd; color: var(--amber); }
.ws-chip.blue  { background: color-mix(in srgb, var(--blue) 14%, #fff); color: var(--blue); }
.ws-chip.red   { background: #f7e7e6; color: var(--red); }
.ws-chip.grey  { background: #eee9e4; color: var(--muted); }
.ws-chip.purple { background: color-mix(in srgb, var(--zone-others) 14%, #fff); color: var(--zone-others); }
.ws-note { background: var(--taupe-soft); border-radius: var(--r-sm); padding: 10px 12px; font-size: 13.5px; color: var(--ink); }
/* An error slot that is holding no error should take no room. Several modules
   put theirs ABOVE the content, so the `min-height: 1em` was a permanent blank
   band pushing every page down for a message that is almost never there. */
.ws-error { color: var(--red); font-size: 13.5px; margin-top: 8px; min-height: 1em; }
.ws-error:empty { display: none; margin: 0; min-height: 0; }
.ws-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 16px; flex-wrap: wrap; }
.ws-tab { padding: 8px 13px; border: none; background: none; cursor: pointer; font-size: 13.5px; color: var(--muted); font-family: inherit; border-bottom: 2px solid transparent; }
.ws-tab.active { color: var(--accent-ink); font-weight: 600; border-bottom-color: var(--accent); }

/* ---------- My work kanban ---------- */
/* day summary strip */
.kb-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 14px; }
.kb-sum-chip { font-size: 13.5px; font-weight: 600; padding: 6px 12px; border-radius: var(--r-pill); background: var(--taupe-soft); color: var(--ink); border: 1px solid var(--line); }
.kb-sum-chip.amber { background: #f6ecdd; color: var(--amber); border-color: #ecd9bd; }
.kb-sum-chip.green { background: color-mix(in srgb, var(--green) 14%, #fff); color: var(--green); border-color: color-mix(in srgb, var(--green) 28%, #fff); }
.kb-sum-chip.red   { background: #f7e7e6; color: var(--red); border-color: #eecac6; }
.kb-sum-chip.blue  { background: color-mix(in srgb, var(--blue) 14%, #fff); color: var(--blue); border-color: color-mix(in srgb, var(--blue) 27%, #fff); }
.kb-sum-chip.purple { background: color-mix(in srgb, var(--zone-others) 14%, #fff); color: var(--zone-others); border-color: color-mix(in srgb, var(--zone-others) 27%, #fff); }

/* super-admin "see anyone's work" picker + read-only banner */
.kb-picker { display: flex; align-items: center; gap: 8px; margin: 2px 0 12px; flex-wrap: wrap; }
.kb-picker label { font-size: 13.5px; font-weight: 600; color: var(--primary-dark); white-space: nowrap; }
.kb-picker .ws-select { max-width: 320px; }
/* In the app bar it must not make the bar taller than one row. */
/* nowrap keeps this on one line in the 54px bar — but a box that may not wrap must
   be allowed to SHRINK, or it simply runs off the right edge instead. At 320px the
   label plus a 210px select is wider than the phone, and the picker hung past the
   screen with no way to scroll to it. min-width:0 is the part that actually lets a
   flex item give (the default is min-content, which is why capping max-width alone
   never fixes this). Same trap as the app-bar overflow work. */
.head-tools .kb-picker { margin: 0; flex-wrap: nowrap; min-width: 0; }
.head-tools .kb-picker label { font-size: 12.5px; }
.head-tools .kb-picker .ws-select { width: auto; max-width: 210px; min-width: 0; flex: 0 1 auto; padding: 5px 8px; font-size: 13.5px; }
.kb-viewbanner { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; background: #f3ede6; border: 1px solid var(--line); border-radius: var(--r-sm); padding: 8px 12px; margin-bottom: 12px; font-size: 13.5px; color: var(--ink); }

/* phone segmented control (hidden on desktop) */
.kb-seg { display: none; gap: 6px; margin-bottom: 12px; }
.kb-seg-btn { flex: 1; padding: 9px 6px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-sm); font-size: 13.5px; font-weight: 600; color: var(--muted); cursor: pointer; font-family: inherit; }
.kb-seg-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }

.kb-board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 8px; align-items: start; }
.kb-col { background: var(--taupe-soft); border-radius: var(--r-md); padding: 10px; min-height: 120px; }
.kb-col-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; padding: 0 2px; }
.kb-count { font-size: 13.5px; color: var(--muted); font-weight: 600; }
.kb-col-body { display: flex; flex-direction: column; gap: 10px; }
.kb-empty { color: var(--muted); font-size: 13.5px; text-align: center; padding: 18px 6px; }
.kb-card { background: var(--card); border: 1px solid var(--hairline); border-left: 4px solid var(--line); border-radius: var(--r-md); padding: 12px; }
/* urgency stripe down the left of each card */
.kb-card.urg-over  { border-left-color: var(--red); }
.kb-card.urg-today { border-left-color: var(--amber); }
.kb-card.urg-run   { border-left-color: var(--blue); }
.kb-card.urg-hold  { border-left-color: #8f79b8; }
.kb-card.urg-done  { border-left-color: var(--green); opacity: .82; }
.kb-title { font-size: 15px; font-weight: 600; line-height: 1.3; }
.kb-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.kb-cust { font-size: 13.5px; margin-top: 8px; }
.kb-contact a { color: var(--primary-dark); text-decoration: none; font-weight: 600; white-space: nowrap; }
.kb-link { display: inline-block; margin-top: 6px; font-size: 12.5px; color: var(--primary-dark); text-decoration: none; line-height: 1.4; }
.kb-link:hover { text-decoration: underline; }
.kb-mats { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.kb-team { font-size: 12.5px; color: var(--muted); margin-top: 6px; }
.kb-notes { font-size: 12.5px; color: var(--ink); background: var(--taupe-soft); border-radius: 6px; padding: 6px 8px; margin-top: 8px; }
.kb-running { margin-top: 10px; font-size: 17px; font-weight: 700; color: var(--amber); font-variant-numeric: tabular-nums; }
.kb-run-label { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
.kb-total { margin-top: 8px; font-size: 13.5px; color: var(--ink); }
.kb-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.kb-actions .ws-btn { flex: 1; padding: 11px 8px; font-size: 15px; }
.kb-notebtn { margin-top: 8px; color: var(--muted); }
.kb-note-form { display: flex; gap: 6px; margin-top: 8px; }
.kb-note-form input { flex: 1; }
.kb-file-btns { display: flex; gap: 6px; margin-top: 8px; }
.kb-files { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.kb-thumb { width: 52px; height: 52px; object-fit: cover; border-radius: 6px; border: 1px solid var(--line); }
.kb-file { font-size: 12.5px; color: var(--primary-dark); text-decoration: none; background: var(--taupe-soft); padding: 4px 8px; border-radius: 6px; }
.kb-upload { display: inline-block; cursor: pointer; }
@media (max-width: 900px) {
  .kb-seg { display: flex; flex-wrap: wrap; }
  .kb-board { grid-template-columns: 1fr; }
  .kb-col { min-height: 0; display: none; }
  .kb-board[data-mobile-col="todo"] .kb-col[data-col="todo"],
  .kb-board[data-mobile-col="in_progress"] .kb-col[data-col="in_progress"],
  .kb-board[data-mobile-col="pending"] .kb-col[data-col="pending"],
  .kb-board[data-mobile-col="done"] .kb-col[data-col="done"] { display: block; }
  .kb-col-head { display: none; }
}

/* ============ MY WORK — redesigned kanban ============ */
.mw-summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 14px; }
.mw-sum-chip { font-size: 13.5px; font-weight: 700; padding: 6px 13px; border-radius: var(--r-pill); background: var(--taupe-soft); color: var(--ink); border: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.mw-sum-chip.amber { background: #f6ecdd; color: var(--amber); border-color: #ecd9bd; }
.mw-sum-chip.red   { background: #f7e7e6; color: var(--red); border-color: #eecac6; }
.mw-sum-chip.blue  { background: color-mix(in srgb, var(--blue) 14%, #fff); color: var(--blue); border-color: color-mix(in srgb, var(--blue) 27%, #fff); }
.mw-seg { display: none; gap: 6px; margin: 4px 0 12px; }
.mw-seg-btn { flex: 1; padding: 9px 6px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-sm); font-size: 13.5px; font-weight: 700; color: var(--muted); cursor: pointer; font-family: inherit; display: flex; align-items: center; justify-content: center; gap: 6px; }
.mw-seg-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.mw-seg-n { font-size: 11px; background: rgba(0,0,0,.08); border-radius: var(--r-pill); padding: 0 6px; font-variant-numeric: tabular-nums; }
.mw-seg-btn.active .mw-seg-n { background: rgba(255,255,255,.28); }

.mw-board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 8px; align-items: start; }
.mw-col { background: var(--taupe-soft); border-radius: var(--r-lg); padding: 10px 10px 12px; min-height: 140px; border-top: 3px solid var(--line); transition: background .12s, border-color .12s; }
.mw-col.col-todo { border-top-color: var(--taupe); }
.mw-col.col-in_progress { border-top-color: var(--amber); }
.mw-col.col-pending { border-top-color: #8f79b8; }
.mw-col.col-done { border-top-color: var(--green); }
.mw-col.drop-on { background: #f2ebe3; border-top-style: dashed; outline: 2px dashed var(--primary); outline-offset: -2px; }
.mw-col-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; padding: 2px 4px 0; }
.mw-col-title { font-size: 12.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.mw-col-n { font-size: 12.5px; font-weight: 700; color: var(--muted); background: var(--card); border-radius: var(--r-pill); padding: 1px 9px; font-variant-numeric: tabular-nums; }
.mw-col-body { display: flex; flex-direction: column; gap: 9px; }
.mw-empty { color: var(--muted); font-size: 12.5px; text-align: center; padding: 20px 8px; border: 1px solid var(--line); border-radius: var(--r-md); }

.mw-card { background: var(--card); border: 1px solid var(--hairline); border-left: 4px solid var(--line); border-radius: var(--r-md); padding: 11px 12px; cursor: grab; transition: background .15s, border-color .15s, transform .08s; }
.mw-card:hover { border-color: var(--gold); background: #fffdfb; }
.mw-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.mw-card.dragging { opacity: .55; cursor: grabbing; box-shadow: 0 10px 24px rgba(43,38,34,.18); }
.mw-card.urg-over  { border-left-color: var(--red); }
.mw-card.urg-today { border-left-color: var(--amber); }
.mw-card.urg-run   { border-left-color: var(--blue); }
.mw-card.urg-hold  { border-left-color: #8f79b8; }
.mw-card.urg-done  { border-left-color: var(--green); opacity: .78; }

.mw-top { display: flex; align-items: baseline; gap: 7px; }
.mw-type { font-size: 15px; flex: none; }
.mw-title { font-size: 15px; font-weight: 700; line-height: 1.25; color: var(--ink); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.mw-badge { flex: none; font-size: 11px; font-weight: 700; border-radius: 6px; padding: 1px 7px; white-space: nowrap; }
.mw-badge.red { background: #f6e0dd; color: #9a3a33; }
.mw-badge.amber { background: #f6e7cf; color: #8a5a00; }
.mw-badge.green { background: color-mix(in srgb, var(--green) 13%, #fff); color: color-mix(in srgb, var(--green) 88%, #0e1a14); }
.mw-badge.grey { background: var(--taupe-soft); color: var(--muted); }
.mw-meta { font-size: 12.5px; color: var(--muted); margin-top: 6px; font-variant-numeric: tabular-nums; }
.mw-ord { color: var(--blue); font-weight: 700; }
.mw-where { display: block; margin-top: 5px; font-size: 12.5px; color: var(--primary-dark); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mw-where:hover { text-decoration: underline; }
/* The DETAILED address, under the place it belongs to. Deliberately not a link and
   deliberately not in the link colour: it is what you read once you have arrived,
   not what you tap to set off. */
.mw-addr { margin-top: 3px; font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* No place to go. Said plainly, in the same words Dispatch uses, because a driver
   who knows to ring the office loses a minute and one who follows a made-up pin
   loses an hour. */
.mw-noloc { margin-top: 4px; font-size: 12.5px; color: var(--amber); display: flex; align-items: center; gap: 5px; }
.td-noloc { color: var(--amber); display: inline-flex; align-items: center; gap: 6px; }
.mw-team { font-size: 12.5px; color: var(--muted); margin-top: 5px; }
.mw-time { margin-top: 7px; font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums; }
.mw-time.live { color: var(--amber); font-weight: 700; }
.mw-noteslot:empty { display: none; }

.mw-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.mw-quick { display: flex; gap: 4px; flex: none; }
.mw-ic { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-sm); font-size: 15px; cursor: pointer; text-decoration: none; color: var(--ink); }
.mw-ic:hover { background: var(--taupe-soft); }
.mw-actions { display: flex; gap: 6px; flex: 1; justify-content: flex-end; flex-wrap: wrap; }
.mw-actions .ws-btn.sm { padding: 8px 12px; font-size: 13.5px; }

@media (max-width: 900px) {
  /* One line, whole words. "On hold" was breaking onto two lines at 393px, which
     made its button taller than the other three and the strip look broken. The
     four fit at 360px once the padding stops being wasted; below that the strip
     scrolls rather than wrapping. */
  .mw-seg { display: flex; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .mw-seg::-webkit-scrollbar { display: none; }
  .mw-seg-btn { white-space: nowrap; padding-left: 4px; padding-right: 4px; gap: 4px; flex: 1 0 auto; }
  .mw-board { grid-template-columns: 1fr; gap: 0; }
  .mw-col { min-height: 0; display: none; border-top: 0; background: transparent; padding: 0; }
  .mw-board[data-mobile-col="todo"] .mw-col[data-col="todo"],
  .mw-board[data-mobile-col="in_progress"] .mw-col[data-col="in_progress"],
  .mw-board[data-mobile-col="pending"] .mw-col[data-col="pending"],
  .mw-board[data-mobile-col="done"] .mw-col[data-col="done"] { display: block; }
  .mw-col-head { display: none; }
  .mw-card { cursor: pointer; }   /* drag is desktop-only; phones use the buttons */
}

/* My Work — standalone task detail modal */
.mw-detail { width: 560px; max-width: 100%; }
.mw-detail .modal-header { align-items: flex-start; }
.td-head h2 { font-size: 20px; font-family: var(--serif); font-weight: 600; letter-spacing: .02em; }
.td-sub { margin-top: 5px; font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 8px; }
.td-row { display: flex; gap: 14px; padding: 8px 0; border-top: 1px solid var(--taupe-soft); }
.td-row:first-child { border-top: 0; }
.td-k { flex: 0 0 96px; font-size: 12.5px; font-weight: 700; color: var(--muted); }
.td-v { flex: 1; min-width: 0; font-size: 15px; color: var(--ink); word-break: break-word; }
.td-v a { color: var(--primary-dark); text-decoration: none; font-weight: 600; }
.td-v a:hover { text-decoration: underline; }
/* Under the place's name on a task: the detailed address, in the quieter voice of
   something you read once you have arrived rather than to decide where to go. */
.td-subaddr { margin-top: 3px; font-size: 13px; color: var(--muted); line-height: 1.45; }
.td-notes { background: var(--taupe-soft); border-radius: var(--r-sm); padding: 8px 10px; font-size: 13.5px; line-height: 1.5; }
.td-files { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.td-thumb { width: 54px; height: 54px; object-fit: cover; border-radius: 6px; border: 1px solid var(--line); }
/* Editing a task from My work. Two columns on a desk, one on a phone — the same
   shape as the job card's editor so the two screens feel like one app. */
.td-edit { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }
.td-edit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.td-f { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.td-f.wide { grid-column: 1 / -1; }
.td-f > span { font-size: 12.5px; font-weight: 700; color: var(--muted); }
.td-f > span i { font-style: normal; font-weight: 400; }
.td-edit-actions { display: flex; gap: 8px; margin-top: 12px; }
@media (max-width: 560px) { .td-edit-grid { grid-template-columns: 1fr; } }

/* Files that came with the ORDER, not with this task — said plainly so nobody
   thinks a colleague's reference photo is one they took themselves. */
.td-files-sub {
  flex: 0 0 100%; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-top: 4px;
}
.td-upload { display: inline-block; cursor: pointer; }
/* ---------- the time window: two clocks, not a sentence ----------
   One control shared by the job card, the wizard and the tasks, so a window is
   picked the same way wherever it is asked for. */
.tw { display: flex; flex-direction: column; gap: 7px; }
.tw-row { display: flex; align-items: center; gap: 7px; }
.tw-clock { flex: 1 1 0; min-width: 0; font-variant-numeric: tabular-nums; }
.tw-dash { color: var(--muted); flex: none; }
.tw-clear {
  flex: none; border: 1px solid var(--hairline); background: var(--card);
  color: var(--muted); border-radius: var(--r-sm); font: inherit; font-size: 12px;
  padding: 6px 9px; cursor: pointer;
}
.tw-clear:hover { color: var(--ink); border-color: var(--primary); }
.tw-presets { display: flex; flex-wrap: wrap; gap: 5px; }
.tw-chip {
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink-2);
  border-radius: var(--r-pill); font: inherit; font-size: 12px; padding: 4px 11px; cursor: pointer;
}
.tw-chip:hover { border-color: var(--primary); color: var(--primary); }
/* A value from before this control existed — shown, never silently overwritten. */
.tw-was { font-size: 12px; color: #a3762a; }
@media (pointer: coarse) {
  .tw-chip, .tw-clear { min-height: 44px; }
  .tw-clock { font-size: 16px; }        /* iOS zooms anything smaller on focus */
}

.td-actions { display: flex; gap: 6px; }
.mw-time-live { color: var(--amber); }
/* Where a finished task's "Reopen" used to be: the reason, instead of a button
   that would be refused. */
.td-locked { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.5; }

/* ---------- notifications (bell + panel) ---------- */
.notif-wrap { position: relative; }
#bellBtn { position: relative; }
.bell-count {
  position: absolute; top: -3px; right: -3px; min-width: 17px; height: 17px; padding: 0 4px;
  background: var(--red); color: #fff; border-radius: var(--r-pill);
  font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
}
.notif-panel {
  position: absolute; right: 0; top: calc(100% + 8px); width: 360px; max-width: 92vw; z-index: 60;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: 0 14px 40px rgba(0,0,0,.16); overflow: hidden;
}
.notif-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--taupe-soft);
}
.notif-head-actions { display: flex; gap: 10px; }
.btn-link {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--primary-dark); font-size: 12.5px; text-decoration: underline;
}
.btn-link:disabled { color: var(--muted); cursor: default; text-decoration: none; }
.notif-list { max-height: 60vh; overflow-y: auto; }
.notif-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; border-bottom: 1px solid var(--line); cursor: pointer;
}
.notif-row:last-child { border-bottom: none; }
.notif-row:hover { background: #faf7f4; }
.notif-row.unread { background: #fdf6f1; }
.notif-row.unread .notif-title::before { content: "● "; color: var(--primary); }
.notif-icon { font-size: 15px; line-height: 1.5; }
.notif-text { display: flex; flex-direction: column; min-width: 0; }
.notif-title { font-size: 13.5px; font-weight: 600; }
.notif-body { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.notif-time { font-size: 11px; color: var(--muted); margin-top: 3px; }
.notif-empty { padding: 22px 16px; text-align: center; color: var(--muted); font-size: 13.5px; }

@media (max-width: 620px) {
  .notif-panel { position: fixed; top: auto; left: 8px; right: 8px; width: auto; max-width: none; }
}

/* ---------- quick search ---------- */
.search-wrap { display: flex; gap: 6px; align-items: center; flex: 1; min-width: 220px; max-width: 460px; }
.search-wrap input[type=search] {
  flex: 1; padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--r-pill);
  font-size: 15px; background: #fdfbfa;
}
.search-wrap input[type=search]:focus { outline: 2px solid var(--primary); outline-offset: 0; border-color: var(--primary); }

/* ---------- viewer / driver (limited) modes ---------- */
body.no-write .write-only { display: none !important; }
body.viewer .card-entry, body.worker .card-entry { cursor: pointer; }
/* a driver's own deliveries stand out on the board */
.card-entry.mine { box-shadow: inset 4px 0 0 var(--primary); }

/* ---------- source & note chips ---------- */
.chip.source { background: var(--taupe-soft); color: #6b5f52; border: 1px solid var(--line); }
.map-link { text-decoration: none; }
.s-source { font-size: 11px; font-weight: 600; background: var(--taupe-soft); color: #6b5f52; border-radius: 6px; padding: 1px 6px; margin-left: 6px; }

/* ---------- shopify note ---------- */
.shopify-note {
  background: #fbf6ef; border: 1px solid var(--gold); border-radius: var(--r-sm);
  padding: 9px 12px; font-size: 15px; color: #6b5330; white-space: pre-wrap;
}
.input-row { display: flex; gap: 8px; }
.input-row input { flex: 1; }

/* ---------- attachments ---------- */
/* read-only driver display on the card (driver is set via the delivery task) */
.assigned-show { padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--r-sm); background: #faf7f4; color: var(--ink); font-size: 15px; }

/* ---------- tasks on a job card ---------- */
.chip.tasks { background: #f3ede7; color: var(--primary-dark); font-weight: 600; }
.task-section { margin: 14px 0 4px; border-top: 1px solid var(--line); padding-top: 12px; }
.card-task-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.card-task { border: 1px solid var(--line); border-radius: var(--r-md); padding: 10px 12px; background: #fdfbfa; }
/* A task wears its state as colour, so a job holding six tasks can be read at a
   glance instead of by opening six status dropdowns. Same grammar the board
   cards already use: a stripe down the leading edge over a faint tint. */
/* Grey is the base, so 'todo' AND the legacy 'open' that dispatch still writes
   both land on it without needing a rule of their own. */
.card-task { border-left-width: 3px; border-left-color: #c9c1b8; }
.card-task.ts-in_progress { border-left-color: var(--amber); background: #fdf7ef; }
.card-task.ts-pending     { border-left-color: var(--zone-others); background: color-mix(in srgb, var(--zone-others) 6%, #fff); }
.card-task.ts-done        { border-left-color: var(--green); background: color-mix(in srgb, var(--green) 7%, #fff); }
.card-task.ts-cancelled   { border-left-color: var(--red); opacity: .6; }
/* A task reads as one quiet line of facts — what kind, by when, where it stands —
   with the task itself in dark type underneath. Before this the title competed
   with four coloured chips and a dropdown on the same row, and a long florist
   description pushed all of them onto their own line. */
.ct-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 5px; }
.ct-tag {
  font-size: 11px; font-weight: 600; letter-spacing: .01em; white-space: nowrap;
  color: var(--ink-2); background: #fff; border: 1px solid var(--hairline);
  border-radius: var(--r-pill); padding: 2px 8px;
}
.ct-tag.run       { color: var(--amber); border-color: rgba(192,125,51,.35); }
.ct-tag.st-amber  { color: var(--amber); border-color: rgba(192,125,51,.35); background: #fdf7ef; }
.ct-tag.st-purple { color: var(--zone-others);      border-color: rgba(107,88,150,.30); background: color-mix(in srgb, var(--zone-others) 6%, #fff); }
.ct-tag.st-green  { color: var(--green); border-color: rgba(79,129,99,.35);  background: color-mix(in srgb, var(--green) 7%, #fff); }
.ct-tag.st-red    { color: var(--red);   border-color: rgba(181,81,74,.35); }
.ct-acts { margin-left: auto; display: flex; gap: 2px; }
.ct-title { font-size: 15px; font-weight: 600; line-height: 1.4; color: var(--ink); }
/* The task's own words, kept as they were typed — a pasted flower list stays a
   list instead of collapsing into one run-on line. */
.ct-details { margin-top: 4px; font-size: 13.5px; line-height: 1.5; color: var(--ink-2); }
.ct-area { min-height: 68px; resize: vertical; line-height: 1.45; font-family: inherit; }
.note2task { margin-left: 8px; white-space: nowrap; }
.ct-remove { border: none; background: none; cursor: pointer; font-size: 15px; opacity: .5; padding: 2px 5px; border-radius: 6px; }
.ct-remove:hover { opacity: 1; background: rgba(43, 38, 34, .06); }
/* The two things you can do to a task. Bordered and worded, because the previous
   pair were bare icons at 50% opacity that read as decoration — and one of them
   had lost its icon entirely and was invisible. */
.ct-act {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line); background: var(--card); color: var(--ink-2, var(--muted));
  cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 4px 9px; border-radius: var(--r-pill); line-height: 1.2;
}
.ct-act:hover { border-color: var(--primary); color: var(--primary-dark); background: var(--card); }
.ct-act:active { transform: var(--press); }
.ct-act-danger:hover { border-color: var(--red); color: var(--red); }
/* The WhatsApp reminder: an action like its neighbours, in WhatsApp's own green so it is
   found at a glance (owner, 2026-09-10: "it needs to be out visible"). Also in the task
   editor's action bar, on the left, away from Save. */
.ct-act-wa, .te-actions .te-remind {
  color: #1c7a4a; font-weight: 600;
  border-color: color-mix(in srgb, #25a266 45%, var(--line));
  background: color-mix(in srgb, #25a266 9%, #fff);
}
.ct-act-wa:hover, .te-actions .te-remind:hover {
  color: #145c37; border-color: #1c7a4a;
  background: color-mix(in srgb, #25a266 16%, #fff);
}
.te-actions .te-remind { margin-right: auto; }

/* Unification step 2 (2026-09-10): Cars, Branches, Places and Team add through the
   floating + (public/addfab.js), which clicks these. Hidden, not removed, so every page
   keeps its own form and its own rules. */
#flOpenForm, #brAdd, #plAdd, #schTimeoff { display: none !important; }

/* Unification step 3 (2026-09-10): correcting a person's time on a job-card task, and
   the job's bin of removed files. */
.ct-timefix { margin-top: 10px; padding: 12px 14px 6px; display: grid; gap: 8px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md); }
.ct-timefix > b { font-size: 14px; }
.ct-timefix-btn svg { width: 12px; height: 12px; }
.files-bin { margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--hairline-soft); }
.files-bin > summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ink-2); padding: 4px 0; }
.file-row.is-removed a { color: var(--muted); }
@media (pointer: coarse) { .ct-act { min-height: 36px; padding: 6px 12px; } }
.ct-people { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ct-chip { display: inline-flex; align-items: center; }
.ct-x { border: none; background: none; color: #fff; cursor: pointer; font-weight: 700; margin-left: 4px; padding: 0 3px; opacity: .8; }
.ct-x:hover { opacity: 1; }
.ct-assign-btn { margin-top: 8px; font-size: 12.5px; padding: 5px 10px; }
.ct-btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
.ct-materials { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }
.ct-input { padding: 7px 9px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 13.5px; font-family: inherit; background: #fff; }
.ct-status { max-width: 130px; }
.ct-assign { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 8px; background: var(--taupe-soft); padding: 8px; border-radius: var(--r-sm); }
/* Editing a task opens in place, under its own title, so you can see what you
   are changing. Same fill as the assign strip — it is the same kind of drawer. */
.ct-edit {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin-top: 8px; background: var(--taupe-soft); padding: 10px; border-radius: var(--r-sm);
}
.ct-edit-actions { grid-column: 1 / -1; display: flex; gap: 8px; }
@media (max-width: 620px) { .ct-edit { grid-template-columns: 1fr; } }
/* Putting a second (third, fourth) person on a task while writing it. */
.ct-extra-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.ct-extra-row .ct-input:first-child { flex: 1; min-width: 140px; }
.ct-role-mini { border: 0; background: transparent; font: inherit; font-size: 11px; color: inherit; cursor: pointer; max-width: 108px; }
.ct-assign .ct-input:first-child { flex: 1; min-width: 140px; }

/* the "add a task" form on a job card — folded away behind .ct-form-open until
   someone actually wants to add one */
.ct-form-open { width: 100%; margin-top: 8px; }
.card-task-form { border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px; background: #fff; margin-top: 8px; }
.ctf-title { font-size: 13.5px; font-weight: 600; color: var(--primary-dark); margin-bottom: 10px; }
/* Three across: the short fields (kind, person, date, car) fit one line instead
   of stringing down the page in pairs with a hole beside the last one. */
.card-task-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.ctf-field { display: flex; flex-direction: column; gap: 4px; }
.ctf-field > span { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.ctf-wide { grid-column: 1 / -1; }
/* The optional half — a second person, photos — sits below a hairline so the
   four things a task actually needs are not buried among the things it may not. */
.ctf-extra {
  margin-top: 12px; padding-top: 11px; border-top: 1px solid var(--line);
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
.ctf-actions { margin-top: 12px; display: flex; gap: 8px; }
.ctf-actions .btn { flex: 1; }
.ctf-actions .btn-ghost { flex: 0 0 auto; }
@media (max-width: 960px) { .card-task-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .card-task-grid, .ctf-extra { grid-template-columns: 1fr; } }

/* photos & files, right inside "Tasks & people" — attach or shoot without leaving the tab */
.ct-files { border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px; background: #fff; margin-top: 10px; }
.ct-file-strip { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 10px; }
.ct-file-thumb { width: 58px; height: 58px; object-fit: cover; border-radius: var(--r-sm); border: 1px solid var(--line); cursor: zoom-in; }
.ct-file-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.ct-file-actions .btn { font-size: 13.5px; }
/* the same pair on a single task, one notch quieter than the job-level box */
.ct-task-files { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
.ct-task-files .ct-file-strip { margin-bottom: 6px; }
.ct-task-files .ct-file-thumb { width: 46px; height: 46px; }
/* Inside a task these are secondary actions, not headline ones — the task's own
   words should be the biggest thing in its box. */
.ct-task-files .ct-file-actions .btn { font-size: 12.5px; padding: 4px 9px; }
.card-task .ct-assign-btn { font-size: 12.5px; padding: 4px 9px; }

.files-section { margin: 14px 0 4px; border-top: 1px solid var(--line); padding-top: 12px; }
.files-label { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 8px; }
.files-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.file-row { display: flex; align-items: center; gap: 10px; font-size: 13.5px; background: #faf7f4; border: 1px solid var(--line); border-radius: var(--r-sm); padding: 6px 10px; }
.file-row a { color: var(--primary-dark); text-decoration: none; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-row .hint { margin-left: auto; white-space: nowrap; }
.file-del { border: 0; background: none; color: var(--red); font-size: 20px; cursor: pointer; line-height: 1; }
.file-pick { display: inline-block; cursor: pointer; }

/* ---------- attachment previews ---------- */
.card-thumbs { display: flex; gap: 6px; margin: 6px 0 2px; flex-wrap: wrap; }
.card-thumbs img {
  height: 52px; width: 52px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--line); cursor: zoom-in;
}
.file-thumb {
  height: 36px; width: 36px; object-fit: cover; border-radius: 6px;
  border: 1px solid var(--line); cursor: zoom-in; flex-shrink: 0;
}
#lightbox {
  position: fixed; inset: 0; z-index: 200; background: rgba(30, 24, 20, .85);
  display: none; align-items: center; justify-content: center; cursor: zoom-out;
}
#lightbox.show { display: flex; }
#lightbox img { max-width: 94vw; max-height: 94vh; border-radius: var(--r-md); box-shadow: 0 12px 48px rgba(0,0,0,.5); }

/* ---------- advanced search results ---------- */
.field-btn { display: flex; align-items: flex-end; }
.as-count { font-size: 13.5px; color: var(--muted); margin: 10px 0 6px; }
.as-results { max-height: 46vh; overflow-y: auto; }
.as-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 10px; border-bottom: 1px solid #f0ece7; cursor: pointer; font-size: 13.5px;
}
.as-row:hover { background: #f5ede6; }
.as-date { font-weight: 700; color: var(--primary-dark); min-width: 90px; }

/* ---------- timeline view (hour-by-hour list) ---------- */
.timeline { padding: 14px; }
.tl-row { display: flex; gap: 14px; margin-bottom: 14px; }
.tl-hour {
  min-width: 92px; text-align: right; font-family: var(--serif); font-weight: 700;
  font-size: 17px; color: var(--primary-dark); padding-top: 10px;
  border-right: 2px solid var(--gold); padding-right: 14px;
}
.tl-cards { flex: 1; display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 10px; }

/* ---------- hour board view (kanban by hour) ---------- */
.hours {
  display: flex; flex-wrap: nowrap; gap: 12px;
  padding: 14px; align-items: stretch;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: thin; scrollbar-color: var(--gold) transparent;
  min-height: calc(100vh - var(--chrome-h) - 34px);
}
.hours::-webkit-scrollbar { height: 10px; }
.hours::-webkit-scrollbar-thumb { background: var(--gold); border-radius: var(--r-pill); }
.hours::-webkit-scrollbar-track { background: var(--taupe-soft); border-radius: var(--r-pill); }
.tl-col { flex: 0 0 300px; max-width: 300px; background: var(--taupe-soft); border-radius: var(--r-md); display: flex; flex-direction: column; min-height: 300px; }
.tl-col-head {
  padding: 10px 14px; font-family: var(--serif); font-weight: 700; letter-spacing: .5px; font-size: 17px;
  display: flex; justify-content: space-between; align-items: center;
  border-radius: var(--r-md) 12px 0 0; color: #fff; background: var(--primary);
}
.tl-col-head .count { background: rgba(255,255,255,.25); border-radius: var(--r-pill); padding: 1px 10px; font-size: 13.5px; }

/* ---------- calendar view ---------- */
.calendar { padding: 14px; width: 100%; }
.cal-header { display: flex; align-items: center; justify-content: center; gap: 18px; margin-bottom: 12px; }
.cal-header h3 { font-family: var(--serif); font-size: 26px; font-weight: 600; letter-spacing: .04em; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cal-dow { text-align: center; font-size: 12.5px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .1em; padding: 6px 0; }
.cal-cell {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-sm);
  min-height: 86px; padding: 6px 8px; cursor: pointer;
}
.cal-cell:hover { border-color: var(--primary); }
.cal-cell.empty { background: transparent; border: none; cursor: default; }
.cal-cell.today { border-color: var(--primary); border-width: 2px; }
.cal-cell.selected { background: #f5ede6; }
.cal-daynum { font-weight: 700; font-size: 15px; margin-bottom: 4px; }
.cal-stats { display: flex; flex-direction: column; gap: 3px; }
.cal-badge { font-size: 11px; font-weight: 600; border-radius: 6px; padding: 1px 6px; width: fit-content; }
.cal-badge.timed { background: color-mix(in srgb, var(--blue) 9%, #fff); color: var(--blue); }
.cal-badge.flex { background: var(--taupe-soft); color: #6b5f52; }
.cal-badge.done { background: color-mix(in srgb, var(--green) 11%, #fff); color: var(--green); }

/* ---------- week view ----------
   Seven days side by side. The month grid says how BUSY a day is; this says what
   is ON it, close enough to read a name and drag a job to the day it belongs on.
   Columns keep a readable width and the grid scrolls rather than squeezing seven
   cards down to a sliver. */
.week { padding: 14px; width: 100%; min-height: calc(100vh - var(--chrome-h) - 34px); }
.wk-bar { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 12px; }
.wk-bar h3 { font-family: var(--serif); font-size: 26px; font-weight: 600; letter-spacing: .03em; white-space: nowrap; }
.wk-this { margin-left: 8px; font-size: 13.5px; }
.wk-grid {
  display: grid; grid-template-columns: repeat(7, minmax(168px, 1fr)); gap: 8px;
  align-items: start; overflow-x: auto; padding-bottom: 10px;
  scrollbar-width: thin; scrollbar-color: var(--gold) transparent;
}
.wk-col { background: var(--taupe-soft); border-radius: var(--r-sm); display: flex; flex-direction: column; min-height: 180px; }
.wk-col.drag-over { outline: 3px dashed var(--primary); outline-offset: -3px; background: #f5ede6; }
.wk-head {
  width: 100%; display: flex; align-items: baseline; gap: 7px; padding: 9px 11px;
  background: none; border: 0; border-bottom: 1px solid rgba(0,0,0,.06);
  cursor: pointer; font-family: inherit; text-align: left; border-radius: var(--r-sm) 10px 0 0;
}
.wk-head:hover { background: rgba(255,255,255,.5); }
.wk-dow { font-size: 12.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.wk-dnum { font-size: 17px; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
.wk-today { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: #fff; background: var(--primary); border-radius: var(--r-pill); padding: 1px 7px; }
.wk-count { margin-left: auto; font-size: 12.5px; font-weight: 700; color: var(--muted); background: var(--card); border-radius: var(--r-pill); padding: 1px 9px; font-variant-numeric: tabular-nums; }
.wk-body { padding: 8px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.wk-empty { color: var(--muted); text-align: center; font-size: 13.5px; padding: 16px 8px; }
/* today gets the accent; the day the board is standing on gets the tint */
.wk-col.is-today { box-shadow: inset 0 0 0 2px var(--primary); }
.wk-col.is-picked .wk-head { background: rgba(255,255,255,.6); }
/* days already worked stay readable but stop competing with the ones ahead */
.wk-col.is-past { opacity: .62; }
.wk-col.is-past:hover, .wk-col.is-past.drag-over { opacity: 1; }
/* a week column is half the width of a board column, so the card gives up its
   roomier spacing rather than its words */
.wk-body .card-entry { padding: 9px 10px; }
.wk-body .c-thumbs img { width: 36px; height: 36px; }
.wk-body .c-name { font-size: 15px; }

/* the waiting list, above the week: the one screen where both are visible */
.wk-tray { background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--gold); border-radius: var(--r-lg); margin-bottom: 12px; overflow: hidden; }
.wk-tray-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; font-weight: 800; font-size: 15px; border-bottom: 1px solid var(--taupe-soft); }
.wk-tray-head .wk-count { margin-left: 0; }
.wk-hint { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--muted); }
.wk-tray-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; padding: 12px 14px; max-height: 260px; overflow-y: auto; }

/* stacked down: the week reads as an agenda, one day after another */
.week.flow-down .wk-grid { display: block; overflow-x: visible; }
.week.flow-down .wk-col { margin-bottom: 10px; min-height: 0; }
.week.flow-down .wk-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
.week.flow-down .wk-empty { grid-column: 1 / -1; padding: 8px; }

@media (max-width: 860px) {
  .wk-grid { display: block; overflow-x: visible; }
  .wk-col { margin-bottom: 10px; min-height: 0; }
  .wk-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
  .wk-empty { grid-column: 1 / -1; padding: 8px; }
  .wk-bar h3 { font-size: 20px; }
}

/* ---------- roles ---------- */
.role.viewer-role { background: color-mix(in srgb, var(--blue) 9%, #fff); color: var(--blue); }
.role.super-role { background: #f3e9dc; color: #8a6135; }
.role.driver-role { background: color-mix(in srgb, var(--blue) 11%, #fff); color: color-mix(in srgb, var(--blue) 88%, #10202a); }
.role-select { padding: 4px 6px; border: 1px solid var(--line); border-radius: 6px; font-size: 12.5px; }
.role-legend { margin-bottom: 10px; }
.inline-input { flex: 1; padding: 9px 10px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 15px; }
.date-readable { display: block; margin-top: 5px; font-size: 12.5px; font-weight: 600; color: var(--primary-dark); }

/* ---------- new-job wizard ---------- */
.wiz-backdrop { position: fixed; inset: 0; z-index: 90; background: rgba(20,22,30,.5); display: flex; align-items: center; justify-content: center; padding: 3vh 12px; }
.wiz-modal { background: var(--card); border-radius: var(--r-lg); width: 100%; max-width: 640px; max-height: 94vh; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,.28); overflow: hidden; }
.wiz-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 22px 10px; }
.wiz-head h2 { font-size: 26px; margin: 0; }
.wiz-x { border: none; background: none; font-size: 26px; line-height: 1; color: var(--muted); cursor: pointer; padding: 0 4px; }
.wiz-x:hover { color: var(--ink); }
/* progress */
.wiz-progress { display: flex; align-items: center; gap: 4px; padding: 4px 22px 14px; }
.wiz-pstep { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 13.5px; font-weight: 600; white-space: nowrap; }
.wiz-pdot { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12.5px; background: var(--taupe-soft); color: var(--muted); border: 1px solid var(--line); }
.wiz-pstep.active .wiz-pdot { background: var(--accent); color: #fff; border-color: var(--accent); }
.wiz-pstep.active { color: var(--accent-ink); }
.wiz-pstep.done .wiz-pdot { background: var(--green); color: #fff; border-color: var(--green); }
.wiz-pline { flex: 1; height: 2px; background: var(--line); min-width: 12px; }
/* body */
.wiz-body { padding: 6px 22px 18px; overflow-y: auto; }
.wiz-lead { color: var(--muted); font-size: 15px; margin: 2px 0 14px; }
.wiz-toggle { display: flex; gap: 8px; margin-bottom: 14px; }
.wiz-tg { flex: 1; padding: 11px 8px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-md); font-size: 13.5px; font-weight: 600; color: var(--muted); cursor: pointer; font-family: inherit; }
.wiz-tg.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.wiz-field { margin-bottom: 13px; position: relative; }
.wiz-field > label { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); margin-bottom: 5px; }
.wiz-field .hint { font-weight: 400; color: var(--muted); font-size: 12.5px; }
.wiz-input { width: 100%; padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 15px; font-family: inherit; background: #fdfbfa; color: var(--ink); }
.wiz-input:focus { outline: 2px solid var(--primary); outline-offset: 0; border-color: var(--primary); }
textarea.wiz-input { resize: vertical; }
/* Three across, matching the job card's add-task form — the wizard now asks for
   the same things, so it should ask for them the same way. */
.wiz-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 14px; }
.wiz-grid .wiz-wide { grid-column: 1 / -1; }
/* Must sit ABOVE 760px: the phone block further down already takes the wizard to
   one column there, and being the later rule it would win anyway. */
@media (max-width: 960px) { .wiz-grid { grid-template-columns: repeat(2, 1fr); } }
.wiz-ok { display: none; color: var(--green); font-weight: 700; }
.wiz-field.ok .wiz-ok { display: inline; }
.wiz-err { color: var(--red); font-size: 12.5px; margin-top: 5px; font-weight: 600; }
/* step 3 */
.wiz-summary { display: flex; flex-wrap: wrap; gap: 6px 14px; background: var(--taupe-soft); border-radius: var(--r-md); padding: 10px 12px; margin-bottom: 14px; font-size: 13.5px; color: var(--ink); }
.wiz-tasks { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.wiz-tasks-empty { color: var(--muted); font-size: 13.5px; text-align: center; padding: 12px; background: var(--taupe-soft); border-radius: var(--r-md); }
.wiz-taskrow { border: 1px solid var(--line); border-left: 3px solid var(--primary); border-radius: var(--r-sm); padding: 9px 11px; }
.wiz-taskrow-main { font-size: 15px; }
.wiz-taskrow-main .hint { font-weight: 400; color: var(--muted); font-size: 12.5px; }
.wiz-taskmats { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 5px; }
.wiz-taskrow-actions { margin-top: 8px; display: flex; gap: 6px; }
.wiz-matform { margin-top: 8px; display: flex; gap: 6px; align-items: center; }
.wiz-matform .wiz-input { flex: 1; padding: 7px 9px; font-size: 13.5px; }
.wiz-chip-x { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 15px; padding: 0 2px; }
.wiz-addtask { background: var(--taupe-soft); border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 14px; }
.wiz-addtask-title { font-size: 13.5px; font-weight: 700; color: var(--primary-dark); margin-bottom: 10px; }
.wiz-addtask .wiz-field { margin-bottom: 8px; }
.wiz-addtask .wiz-input { background: var(--card); padding: 8px 10px; font-size: 13.5px; }
.wiz-addtask label { font-size: 12.5px; }
.wiz-files { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.wiz-fchip { font-size: 12.5px; background: var(--taupe-soft); border-radius: var(--r-pill); padding: 4px 10px; color: var(--ink); }
.btn.sm { padding: 5px 10px; font-size: 12.5px; }
/* footer */
.wiz-foot { display: flex; align-items: center; gap: 12px; padding: 14px 22px; border-top: 1px solid var(--line); background: #fdfbfa; }
.wiz-foot-hint { flex: 1; text-align: center; font-size: 12.5px; color: var(--muted); }
.wiz-foot .btn { padding: 11px 20px; font-size: 15px; }
.wiz-foot #wizNext { min-width: 130px; }

/* ---------- by-person board view ---------- */
.ppl-col .zone-head { background: var(--primary); color: #fff; }
.ppl-col .zone-head .count { background: rgba(255,255,255,.25); }
/* by-stage view: one column per delivery stage */
.stage-col .zone-head { background: var(--primary); color: #fff; }
.stage-col .zone-head .count { background: rgba(255,255,255,.25); }
.stage-col.stage-done { opacity: .9; }
.stage-col.stage-done .zone-head { background: var(--green); }
.ppl-loading { padding: 40px 16px; text-align: center; color: var(--muted); }
.ppl-task { background: var(--card); border: 1px solid var(--line); border-left: 3px solid var(--gold); border-radius: var(--r-sm); padding: 9px 11px; margin-bottom: 8px; cursor: pointer; }
.ppl-task:hover { border-color: var(--primary); }
.ppl-task.st-done { opacity: .7; border-left-color: var(--green); }
.ppl-task.st-in_progress { border-left-color: var(--amber); }
.ppl-task.st-pending { border-left-color: #8f79b8; }
.ppl-task-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; font-size: 15px; }
.ppl-task-sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.ppl-task-meta { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }

/* ---------- reports dashboard bars ---------- */
.rp-bar-row { display: grid; grid-template-columns: minmax(88px, 32%) 1fr auto; align-items: center; gap: 10px; padding: 5px 0; }
.rp-bar-label { font-size: 13.5px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rp-bar-track { background: var(--taupe-soft); border-radius: var(--r-pill); height: 10px; overflow: hidden; }
.rp-bar-fill { height: 100%; border-radius: var(--r-pill); background: var(--primary); min-width: 4px; }
.rp-bar-fill.green { background: var(--green); }
.rp-bar-fill.amber { background: var(--amber); }
.rp-bar-fill.red { background: var(--red); }
.rp-bar-fill.blue { background: var(--blue); }
.rp-bar-fill.grey { background: var(--muted); }
.rp-bar-fill.purple { background: #8f79b8; }
.rp-bar-val { font-size: 13.5px; font-weight: 700; color: var(--ink); white-space: nowrap; }

/* ---------- driver days (Reports › Driver days) ----------
   Added 2026-08-04, when Start / Finish were removed from Dispatch and the day's
   record moved here. Everything on this screen is measured off the driver's
   phone rather than declared by two taps. */
.rp-views { margin: 0 0 14px; }

.rp-dd-wrap { overflow-x: auto; }         /* a wide table scrolls itself, never the page */
.rp-dd { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.rp-dd th {
  text-align: left; font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--muted);
  padding: 0 10px 8px; border-bottom: 1px solid var(--hairline); white-space: nowrap;
}
.rp-dd td { padding: 10px; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.rp-dd tbody tr { cursor: pointer; }
.rp-dd tbody tr:hover { background: var(--taupe-soft); }
.rp-dd-day { font-weight: 600; }
.rp-dd-who { font-weight: 600; }
.rp-dd-num { font-variant-numeric: tabular-nums; }

.rp-day-head { display: flex; align-items: center; gap: 12px; margin: 0 0 14px; flex-wrap: wrap; }
.rp-day-head h3 { margin: 0; font-family: var(--serif); font-size: 1.35em; font-weight: 600; }
.rp-day-tiles { margin-bottom: 12px; }
.rp-day-when, .rp-day-old { margin: 0 0 12px; font-size: 12.5px; }
.rp-day-old { color: var(--muted); }

/* The replay. A fixed height rather than an aspect ratio: this sits between two
   blocks of text and a tall map on a phone pushes the stops off the screen. */
.rp-map {
  height: 380px; border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--line); background: #ece7e0; margin: 0 0 14px;
}
.rp-map-fail { padding: 28px 20px; text-align: center; color: var(--muted); font-size: 13px; }
.rp-pin { display: block; cursor: pointer; }
.rp-pop { font: inherit; font-size: 13px; min-width: 190px; }
.rp-pop > b { display: block; margin-bottom: 4px; }
.rp-pop .rp-stop-card { padding: 0; border: 0; }
.rp-pop .rp-stop-no { display: none; }     /* the popup title already says which stop */

.rp-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.rp-sub { font-size: 15px; margin-bottom: 10px; }

.rp-stop-card {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 0; border-bottom: 1px solid var(--hairline);
}
.rp-stop-card:last-child { border-bottom: 0; }
.rp-stop-no {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--green); color: #fff; font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* The phone at rest — overnight at home, usually. It is shown because it is
   true, and greyed because it is not somewhere he went. */
.rp-stop-card.is-resting { opacity: .62; }
.rp-stop-rest {
  background: transparent; color: var(--muted); border: 1px solid var(--hairline);
  font-size: 15px; line-height: 1;
}
.rp-stop-main { min-width: 0; }
.rp-stop-where { font-weight: 600; }
.rp-stop-unknown { font-weight: 400; color: var(--muted); font-style: italic; }
.rp-stop-ref { font-size: 11.5px; color: var(--muted); font-weight: 400; }
.rp-stop-time { font-size: 12.5px; color: var(--ink-2); margin-top: 2px; font-variant-numeric: tabular-nums; }
.rp-stop-addr { font-size: 11.5px; color: var(--muted); margin-top: 2px; }

.rp-del {
  display: flex; gap: 10px; align-items: flex-start; justify-content: space-between;
  padding: 9px 0; border-bottom: 1px solid var(--hairline);
}
.rp-del:last-child { border-bottom: 0; }
.rp-del-main { min-width: 0; }
.rp-del-sub { font-size: 11.5px; color: var(--muted); margin-top: 2px; }

@media (max-width: 860px) {
  .rp-cols { grid-template-columns: 1fr; }
  .rp-map { height: 300px; }
}

/* ---------- Reports › Car log ----------
   Added 2026-08-04 for the check-out ledger. The lookup sits ABOVE the table on
   purpose: this view is opened with a fine or an accident in hand far more often
   than it is browsed, and the answer to "who had it" must not be something you
   scroll to find. */
.rp-who { margin: 0 0 14px; }
.rp-who-why { margin: 0 0 10px; }
.rp-who-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.rp-who-row .ws-select { flex: 1 1 190px; min-width: 0; }
.rp-who-row .ws-input { flex: 1 1 170px; min-width: 0; }
.rp-who-row .ws-btn { flex: 0 0 auto; }
.rp-who-out {
  margin-top: 12px; padding: 11px 13px; border-radius: var(--r-sm);
  background: var(--taupe-soft); font-size: 13.5px; line-height: 1.55;
}
.rp-who-out.is-good {
  background: color-mix(in srgb, var(--green) 8%, #fff);
  border: 1px solid color-mix(in srgb, var(--green) 26%, #fff);
}
.rp-who-out.is-bad {
  background: #fff5f5; border: 1px solid #f3c1c1; color: var(--red);
}
.rp-who-note { margin-top: 5px; font-style: italic; color: var(--ink-2); }
.rp-who-near { margin: 7px 0 0; padding-left: 18px; }
.rp-who-near li { margin: 2px 0; }

.rp-carsub { margin: 0 0 10px; }
.rp-carfilters { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.rp-carfilters .ws-select { flex: 0 1 220px; min-width: 0; }
/* Nothing in the car log opens a detail view, so its rows must not pretend to. */
.rp-carlog tbody tr { cursor: default; }
.rp-carlog tbody tr.is-problem { background: #fff6f6; }
.rp-carlog tbody tr.is-problem:hover { background: #ffeeee; }
.rp-carlog td { white-space: normal; }
.rp-carlog .rp-dd-num, .rp-carlog .rp-dd-day { white-space: nowrap; }
.rp-carplate {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@media (pointer: coarse) {
  .rp-who-row .ws-btn { min-height: 44px; }
}

/* print: just the report content, no app chrome */
@media print {
  @page { size: landscape; margin: 10mm; }
  /* .top-strip now carries every page's controls, so hiding it replaces the
     old per-module list of toolbars. */
  #sideNav, .top-strip, .controlbar, .nav-shade, .ws-toolbar, .ws-head .ws-btn,
  .ws-seg, .sch-views, .sch-nav .ws-btn, .sch-daymode, .ws-sub, .sch-modal-back { display: none !important; }
  .main-area { margin: 0 !important; }
  .ws-card { break-inside: avoid; }
  .sch-scroll, .ws-scroll { overflow: visible !important; }
  .sch-grid, .sch-month, .sch-hours { min-width: 0 !important; }
  .sch-bar { justify-content: flex-start; }
  .sch-period { font-size: 20px; margin-left: 0; }
  .sch-cell, .sch-name, .sch-daycol { font-size: 11px; }
  /* The leave REASON is for the person reading the screen, not for the sheet on
     the wall. Owner, 2026-08-04: "ما في داعي يطلع بالطباعة". The word "Leave"
     stays — the roster still has to show the day is not free. (The roster PDF
     built server-side in lib/modules/hr.js never carried it at all.) */
  .sch-leave-why { display: none !important; }
  .kb-card, .sch-row, .sch-moncell, .sch-hrow { break-inside: avoid; }
}

/* ---------- Shifts & Staff schedule ---------- */
.sch-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 6px 0 12px; }
.sch-views { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.sch-vbtn { padding: 8px 16px; border: none; background: var(--card); color: var(--muted); font-weight: 600; font-size: 13.5px; cursor: pointer; font-family: inherit; }
.sch-vbtn.active { background: var(--accent); color: #fff; }
.sch-nav { display: inline-flex; align-items: center; gap: 6px; }
.sch-period { font-weight: 700; color: var(--primary-dark); margin-left: 6px; font-size: 15px; }
.sch-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.sch-legchip { font-size: 12.5px; font-weight: 600; padding: 3px 10px; border-radius: var(--r-pill); color: var(--ink); }
.sch-legtime { font-weight: 400; opacity: .75; }
.sch-scroll { overflow-x: auto; }
.sch-block { display: inline-flex; align-items: baseline; gap: 5px; font-size: 12.5px; color: var(--ink); padding: 3px 8px; border-radius: 6px; line-height: 1.3; }
.sch-block b { font-weight: 700; }
.sch-time { font-size: 11px; opacity: .8; }
.sch-leave {
  font-size: 12.5px; color: var(--muted); font-style: italic;
  display: inline-flex; flex-direction: column; align-items: center; line-height: 1.25;
  max-width: 100%; min-width: 0;
}
/* WHY they are off, under the word. Owner, 2026-08-04: "Leave" alone does not tell
   a manager whether he can ask them to come in — a yearly vacation and a sick day
   are not the same answer. Shortened to fit a week column; the cell's title carries
   the whole thing, and the Day view prints it in full. */
.sch-leave-why {
  font-style: normal; font-size: 10.5px; font-weight: 600;
  color: color-mix(in srgb, var(--ink-2) 78%, transparent);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sch-dayrow .sch-leave { flex-direction: row; gap: 6px; align-items: baseline; }

/* The leave already on a day, shown in the block picker so it can be read AND
   lifted from the same square you clicked. */
.sch-onleave {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 11px; font-size: 13px;
  background: repeating-linear-gradient(135deg, #f6efe7 0 6px, #efe4d8 6px 12px);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
.sch-onleave-txt { flex: 1; min-width: 140px; }
.sch-onleave svg { width: 15px; height: 15px; vertical-align: -2px; }
.sch-onleave-span { display: block; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.sch-pt { display: inline-block; font-size: 11px; font-weight: 700; color: var(--zone-others); background: color-mix(in srgb, var(--zone-others) 14%, #fff); border-radius: var(--r-pill); padding: 0 6px; margin-left: 6px; vertical-align: middle; }
/* week grid */
/* `min-width: 820px` was a GUESS at how wide a week needs to be, and a week with
   real shift labels in it is 1074px. The box stayed at 820, `overflow: hidden`
   clipped the rest, and .sch-scroll only ever scrolled as far as the box — so on a
   phone SATURDAY AND SUNDAY were cut off with no way to reach them. Measured
   2026-08-04 at 375px: grid content 1074, reachable 820.

   max-content makes the box as wide as the week actually is, so the scroller can
   reach all of it and nothing is clipped. Desktop is unchanged: the container is
   wider than max-content there, so the row's 1fr columns still fill it (verified at
   1440px — 1135px before and after). */
.sch-grid { min-width: max-content; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.sch-row { display: grid; grid-template-columns: 150px repeat(7, 1fr); }
.sch-head-row { background: var(--taupe-soft); font-size: 12.5px; font-weight: 700; color: var(--muted); }
.sch-daycol { padding: 8px 6px; text-align: center; border-left: 1px solid var(--line); text-transform: uppercase; letter-spacing: .04em; }
.sch-daycol.is-today { background: #fbf3ec; color: var(--primary-dark); }
.sch-daynum { display: block; font-weight: 600; color: var(--ink); text-transform: none; }
.sch-pub { font-size: 11px; }
.sch-corner { display: flex; align-items: center; padding-left: 12px; }
.sch-grouphead { grid-column: 1 / -1; background: var(--taupe-soft); padding: 6px 12px; font-weight: 700; font-size: 13.5px; color: var(--primary-dark); border-top: 1px solid var(--line); }
.sch-gcount { color: var(--muted); font-weight: 600; }
.sch-name { padding: 9px 12px; font-size: 13.5px; display: flex; align-items: center; flex-wrap: wrap; border-top: 1px solid var(--line); }
.sch-cell { border-left: 1px solid var(--line); border-top: 1px solid var(--line); min-height: 40px; padding: 4px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.sch-cell.empty:hover { background: #fbf3ec; }
.sch-cell.filled { cursor: pointer; }
.sch-plus { color: var(--line); font-size: 20px; font-weight: 700; }
.sch-cell.empty:hover .sch-plus { color: var(--primary); }
/* day view */
.sch-daysummary { background: var(--taupe-soft); border-radius: var(--r-md); padding: 10px 14px; font-size: 15px; margin-bottom: 12px; }
.sch-daygroup { margin-bottom: 14px; }
.sch-daygroup .sch-grouphead { border-radius: var(--r-sm); border: none; }
.sch-dayrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.sch-dayrow.tap { cursor: pointer; }
.sch-dayrow.tap:hover { background: #fbf3ec; }
.sch-dayname { font-size: 15px; }
.sch-none { color: var(--muted); font-size: 12.5px; font-style: italic; }
/* month view */
.sch-month { min-width: 640px; }
.sch-monrow { display: grid; grid-template-columns: repeat(7, 1fr); }
.sch-monhead { text-align: center; font-size: 11px; font-weight: 700; color: var(--muted); text-transform: uppercase; padding: 6px 0; }
.sch-moncell { min-height: 74px; border: 1px solid var(--line); margin: -0.5px 0 0 -0.5px; padding: 5px 6px; cursor: pointer; }
.sch-moncell:hover { background: #fbf3ec; }
.sch-moncell.muted { background: #faf8f5; color: var(--muted); }
.sch-moncell.is-today { box-shadow: inset 0 0 0 2px var(--gold); }
.sch-mondate { font-size: 12.5px; font-weight: 700; color: var(--ink); }
.sch-moncount { font-size: 12.5px; color: var(--primary-dark); margin-top: 4px; }
.sch-dots { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }
.sch-dot { width: 8px; height: 8px; border-radius: 50%; }
/* schedule modals */
.sch-modal-back { position: fixed; inset: 0; z-index: 120; background: rgba(20,22,30,.5); display: flex; align-items: center; justify-content: center; padding: 3vh 12px; }
.sch-modal { background: var(--card); border-radius: var(--r-lg); width: 100%; max-width: 440px; max-height: 92vh; overflow-y: auto; padding: 18px 20px; box-shadow: 0 20px 60px rgba(0,0,0,.28); }
.sch-modal.wide { max-width: 640px; }
.sch-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.sch-modal-head h3 { margin: 0; font-size: 20px; }
.sch-modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.sch-picks { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 10px 0; }
.sch-pick { border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px 10px; cursor: pointer; font-family: inherit; text-align: center; display: flex; flex-direction: column; gap: 3px; color: var(--ink); font-size: 13.5px; }
.sch-pick b { font-size: 15px; }
.sch-pick:hover { outline: 2px solid var(--primary); }
.sch-pick-emoji { font-size: 20px; }
.sch-pick-foot { display: flex; gap: 8px; }
.sch-custom { display: flex; align-items: flex-end; gap: 8px; margin-top: 10px; }
.sch-custom label { font-size: 12.5px; color: var(--muted); display: flex; flex-direction: column; gap: 3px; }
.sch-seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; }
.sch-seg-btn { padding: 8px 16px; border: none; background: var(--card); color: var(--muted); font-weight: 600; cursor: pointer; font-family: inherit; font-size: 13.5px; }
.sch-seg-btn.active { background: var(--accent); color: #fff; }
.sch-cred-done { background: color-mix(in srgb, var(--green) 14%, #fff); border: 1px solid color-mix(in srgb, var(--green) 28%, #fff); border-radius: var(--r-md); padding: 12px 14px; margin-top: 12px; font-size: 13.5px; line-height: 1.6; }
.sch-cred-done code { background: #fff; padding: 1px 7px; border-radius: 6px; font-size: 15px; font-weight: 700; }
.sch-blocks { display: flex; flex-direction: column; gap: 8px; }
.sch-blockrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sch-blockrow .sch-block { min-width: 110px; }
.ws-input.sm { padding: 6px 8px; font-size: 13.5px; width: auto; }
.sch-color { width: 38px; height: 32px; border: 1px solid var(--line); border-radius: 6px; padding: 2px; background: #fff; cursor: pointer; }
/* admin panel: starter password + part-time badge */
.starter-pw { font-size: 11px; font-weight: 700; color: var(--primary-dark); background: #fbf3ec; border: 1px solid #ecd9bd; border-radius: 6px; padding: 1px 7px; margin-left: 6px; }
.pt-badge { display: inline-block; font-size: 11px; font-weight: 700; color: var(--zone-others); background: color-mix(in srgb, var(--zone-others) 14%, #fff); border-radius: var(--r-pill); padding: 1px 7px; margin-left: 6px; vertical-align: middle; }
/* day view sub-toggle + hourly timeline */
.sch-daymode { display: inline-flex; gap: 6px; margin: 0 0 12px; }
.sch-dm { padding: 6px 14px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-sm); font-weight: 600; font-size: 13.5px; color: var(--muted); cursor: pointer; font-family: inherit; }
.sch-dm.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.sch-hours { min-width: 660px; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.sch-hrow { display: grid; grid-template-columns: 130px 1fr; align-items: center; border-top: 1px solid var(--line); }
.sch-hrow:first-child { border-top: none; }
.sch-hhead .sch-htrack { height: 22px; }
.sch-hname { font-size: 13.5px; padding: 0 8px 0 12px; }
.sch-htrack { position: relative; height: 34px; }
.sch-htick { position: absolute; top: 0; bottom: 0; font-size: 11px; color: var(--muted); transform: translateX(-1px); border-left: 1px solid var(--line); padding-left: 3px; }
.sch-hbar { position: absolute; top: 5px; bottom: 5px; min-width: 3%; border-radius: 6px; display: flex; align-items: center; gap: 5px; padding: 0 8px; font-size: 11px; color: var(--ink); overflow: hidden; white-space: nowrap; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
.sch-hbar.tap { cursor: pointer; }
.sch-hbar b { font-weight: 700; }

/* delivery lifecycle stage bar (on the order card) */
.stagebar { display: flex; align-items: center; gap: 2px; margin: 2px 0 14px; overflow-x: auto; padding: 2px 0 4px; }
.stage-step { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 50px; flex-shrink: 0; }
.stage-dot { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; background: var(--taupe-soft); color: var(--muted); border: 1px solid var(--line); }
.stage-lbl { font-size: 11px; color: var(--muted); font-weight: 600; text-align: center; white-space: nowrap; }
.stage-step.done .stage-dot { background: var(--green); color: #fff; border-color: var(--green); }
.stage-step.done .stage-lbl { color: var(--green); }
.stage-step.now .stage-dot { background: var(--primary); color: #fff; border-color: var(--primary); box-shadow: 0 0 0 3px #f0e2d8; }
.stage-step.now .stage-lbl { color: var(--primary-dark); font-weight: 700; }
.stage-line { flex: 1; height: 2px; background: var(--line); min-width: 6px; }
.stage-step[data-setstage] { cursor: pointer; }
.stage-step[data-setstage]:hover .stage-dot { border-color: var(--primary); box-shadow: 0 0 0 3px #f0e2d8; }
.stage-step.partial .stage-dot { background: var(--amber); color: #fff; border-color: var(--amber); }
.stage-step.partial .stage-lbl { color: var(--amber); font-weight: 700; }
.stagebar.pinned { opacity: .96; }
.stage-manual { font-size: 11px; color: var(--amber); font-weight: 600; margin: -8px 0 12px; }
.stage-hint { font-size: 11px; color: var(--muted); margin: -8px 0 12px; }
.linklike { border: 0; background: none; color: var(--primary-dark); font: inherit; font-weight: 700; cursor: pointer; padding: 0; text-decoration: underline; }
.chip.stage { background: #f6ecdd; color: var(--amber); font-weight: 700; }
/* ONE STAGE CHIP (unification step 7) — TF.stageChip in app.js. A capsule with a dot,
   so a stage is recognisable as a stage beside the other chips on a row, and one
   colour per step of the ladder: grey New · brand Preparing/Ready · blue on the road ·
   green finished · amber partly · red failed or cancelled. */
.stage-chip {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 2px 9px; border-radius: var(--r-pill); font-size: 12px; font-weight: 600; line-height: 1.5;
  white-space: nowrap; background: var(--taupe-soft); color: var(--ink-2);
}
.stage-chip::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.stage-chip.t-grey { background: #eee9e4; color: var(--muted); }
.stage-chip.t-shop { background: color-mix(in srgb, var(--primary) 13%, #fff); color: var(--primary-dark); }
.stage-chip.t-road { background: color-mix(in srgb, var(--blue) 13%, #fff); color: color-mix(in srgb, var(--blue) 85%, #13202c); }
.stage-chip.t-good { background: color-mix(in srgb, var(--green) 14%, #fff); color: color-mix(in srgb, var(--green) 85%, #0e1a14); }
.stage-chip.t-warn { background: color-mix(in srgb, var(--amber) 17%, #fff); color: color-mix(in srgb, var(--amber) 82%, #2a1706); }
.stage-chip.t-bad  { background: color-mix(in srgb, var(--red) 13%, #fff); color: var(--red); }
.stage-chip.mw-stage { font-size: 11px; padding: 1px 8px; }

/* ---------- responsive (phones & tablets) ---------- */
@media (max-width: 760px) {
  .topbar { gap: 8px; padding: 8px 10px; }
  .brand-tag { display: none; }
  .brand-name { font-size: 15px; }
  .sync-badge { display: none; }
  .page-title { font-size: 17px; }
  .search-wrap { max-width: none; }
  .date-nav { flex: none; }
  .date-pick { justify-content: center; }
  .controlbar { padding: 8px 8px 0; }
  /* Ten pills will not fit a phone. They WRAP rather than side-scroll, because
     unlike the old two strips this is one row of one kind of thing: a pill that
     has wrapped to the second line is still a pill, whereas a strip that has
     scrolled is hiding choices behind an edge nothing points at. */
  .db-filters { width: 100%; gap: 5px; }
  .db-fchip { padding: 7px 11px; }
  .view-group { flex: 1; overflow-x: auto; }
  .view-group .view-btn { flex: 1 0 auto; white-space: nowrap; }
  .board, .hours { padding: 10px; }
  .board:not(.board-stacked) .zone-col, .hours .tl-col { flex: 0 0 85vw; max-width: 85vw; }
  .board.board-stacked { padding: 10px 10px 32px; }
  .zsec-body { grid-template-columns: 1fr; padding: 12px; }
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .modal { max-height: 96vh; border-radius: var(--r-md); }
  .modal-backdrop { padding: 2vh 6px; }
  .tl-row { flex-direction: column; gap: 6px; }
  .tl-hour { text-align: left; border-right: none; border-bottom: 2px solid var(--gold); padding: 0 0 4px; min-width: 0; }
  .tl-cards { grid-template-columns: 1fr; }
  .cal-cell { min-height: 64px; padding: 4px 5px; }
  .cal-badge { font-size: 11px; padding: 1px 4px; }
  /* This used to be a bare `.user-chip { display: none }`, which also hit the
     sidebar's account block — so on a phone the drawer showed no name at all and
     "Sign out" sat alone at the bottom of an empty column. Header chip only. */
  .top-strip .user-chip { display: none; }

  /* A driver's nav has two entries. `.nav-list { flex: 1 }` then stretched that
     to the full height of the phone and pinned Sign out to the very bottom of an
     otherwise empty column — the owner's words were "barely to find it". Let the
     list be its own height and the account block follow it. */
  .nav-list { flex: 0 1 auto; }
  .side-foot { margin-top: 4px; }
  .side-acct { padding: 6px 2px; }
  .side-foot-btns .btn { flex: 1 1 auto; justify-content: center; min-height: 44px; }
  /* wizard on phones: full-height sheet, single-column, compact progress */
  .wiz-backdrop { padding: 0; }
  /* dvh AFTER vh: the vh line is the fallback for a browser without dvh, the dvh
     line is what iOS actually needs. The earlier fix in the modal block above was
     being undone right here. */
  .wiz-modal {
    max-width: none; border-radius: 0;
    max-height: 100vh; height: 100vh;
    max-height: 100dvh; height: 100dvh;
  }
  .wiz-grid { grid-template-columns: 1fr; }
  .wiz-plabel { display: none; }
  .wiz-pstep.active .wiz-plabel { display: inline; }
  .wiz-progress { padding: 4px 16px 12px; }
  .wiz-head, .wiz-body, .wiz-foot { padding-left: 16px; padding-right: 16px; }
  .wiz-foot .btn { padding: 12px 16px; }
}

/* ---------- the 🚚 run chip on a board card ----------
   Says whether a delivery has a driver yet, and takes you to Dispatch to
   change it. Colour is status only: nothing here invites a click except the
   chip itself, which is why it is the one chip with a border. */
.chip.run {
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink-2);
  font-family: var(--ui); cursor: pointer; font-size: 11px; font-weight: 600;
}
.chip.run:hover { border-color: var(--primary); color: var(--primary-dark); background: #fdf8f4; }
.chip.run.run-none { color: var(--amber); border-color: #eccfa4; background: #fdf7ee; }
.chip.run.run-draft { color: var(--muted); }
.chip.run.run-sent { color: var(--blue); border-color: color-mix(in srgb, var(--blue) 26%, #fff); }
.chip.run.run-live { color: var(--green); border-color: color-mix(in srgb, var(--green) 28%, #fff); }
.chip.run.run-done { color: var(--green); border-color: transparent; background: color-mix(in srgb, var(--green) 9%, #fff); }

/* ============================================================
   SETTINGS — rebuilt 2026-08-01.
   It was one long scroll: Shopify keys, then driver names, then every team
   member, then the add-person form, all stacked in a narrow modal. You scrolled
   past the API token to disable somebody's login.
   Now it is four panels behind four tabs, each answering one question.
   ============================================================ */
/* Settings is a PAGE (2026-08-01). As a modal the tab bar was a flex item that
   shrank — with 37 team members below it, it collapsed to a few pixels and the
   tabs were invisible. On a page there is nothing to fight over, and
   `flex: 0 0 auto` makes sure of it wherever this markup ends up. */
.settings-page { max-width: 1080px; }
.set-tabs {
  display: flex; gap: 2px; flex: 0 0 auto; margin-bottom: 18px;
  border-bottom: 1px solid var(--line); overflow-x: auto;
}
.set-tab {
  border: 0; background: none; cursor: pointer; font-family: inherit;
  padding: 11px 15px; font-size: 13.5px; font-weight: 600; color: var(--muted);
  border-bottom: 2px solid transparent; white-space: nowrap;
}
.set-tab:hover { color: var(--ink); }
.set-tab.active { color: var(--accent-ink); border-bottom-color: var(--accent); }
/* Settings › Danger zone is red before it is opened, so nobody lands there thinking
   it is one more list of switches. Three classes, so it outranks `.set-tab.active`. */
.set-tab.set-tab-danger { color: var(--red); }
.set-tab.set-tab-danger.active { color: var(--red); border-bottom-color: var(--red); }
.set-body { min-height: 0; }
.set-panel { display: block; }
.set-panel.hidden { display: none; }

/* team */
.set-bar { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }
.set-search {
  flex: 1; min-width: 200px; padding: 8px 12px; border: 1px solid var(--line);
  border-radius: var(--r-sm); font-size: 13.5px; font-family: inherit; background: #fff;
}
.set-search:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.set-newuser {
  background: var(--taupe-soft); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 14px 16px; margin-bottom: 16px;
}
.set-newuser.hidden { display: none; }
.set-newuser h4 { margin: 0 0 10px; font-size: 15px; }

/* One person = one card. The controls that change what they can do sit on their
   own line, so a role dropdown is never mistaken for a name. */
.users-list { display: flex; flex-direction: column; gap: 8px; }
.user-row {
  display: block; padding: 11px 13px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--r-md); font-size: 15px;
}
.user-row.is-off { opacity: .62; }
.user-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.user-av {
  width: 30px; height: 30px; border-radius: 50%; flex: 0 0 auto;
  background: var(--primary); color: #fff; display: flex; align-items: center;
  justify-content: center; font-size: 11px; font-weight: 700;
}
.user-name { font-weight: 700; }
.user-at { color: var(--muted); font-size: 12.5px; }
.user-ctl {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--line);
}
.user-ctl .role-select { min-width: 120px; }
.user-ctl .spacer { flex: 1; }
.user-row button { font-size: 12.5px; padding: 5px 11px; }
.user-row .inactive { color: var(--red); font-size: 12.5px; font-weight: 600; }
.set-empty { color: var(--muted); text-align: center; padding: 26px; font-size: 13.5px; }

.set-legend { margin-top: 16px; font-size: 13.5px; }
.set-legend summary { cursor: pointer; color: var(--primary-dark); font-weight: 600; }
.set-legend-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 8px; margin-top: 10px;
}
.set-legend-row {
  background: var(--taupe-soft); border-radius: var(--r-sm); padding: 8px 10px; line-height: 1.5;
}
.set-legend-row b { display: block; }
.set-legend-row span { color: var(--muted); font-size: 12.5px; }

.set-note {
  background: color-mix(in srgb, var(--green) 9%, #fff); border: 1px solid color-mix(in srgb, var(--green) 26%, #fff); border-left: 4px solid var(--green);
  border-radius: var(--r-sm); padding: 10px 13px; font-size: 13.5px; line-height: 1.6;
  color: color-mix(in srgb, var(--green) 88%, #0e1a14); margin-bottom: 16px;
}

/* app switches */
.set-switch {
  display: flex; align-items: flex-start; gap: 16px; justify-content: space-between;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md); padding: 14px 16px;
}
.set-switch-txt { flex: 1; }
.set-switch-txt b { display: block; margin-bottom: 3px; font-size: 15px; }
.set-switch-txt .hint { line-height: 1.55; display: block; }
.set-about { font-size: 13.5px; }

/* a real on/off switch — a checkbox reads as "tick to agree", not "turn on" */
.switch { position: relative; display: inline-block; width: 50px; height: 28px; flex: 0 0 auto; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-slider {
  position: absolute; inset: 0; cursor: pointer; background: #cfcbc5;
  border-radius: var(--r-pill); transition: background .18s;
}
.switch-slider::before {
  content: ''; position: absolute; height: 22px; width: 22px; left: 3px; top: 3px;
  background: #fff; border-radius: 50%; transition: transform .18s;
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.switch input:checked + .switch-slider { background: var(--green, #4f8a63); }
.switch input:checked + .switch-slider::before { transform: translateX(22px); }
.switch input:focus-visible + .switch-slider { outline: 2px solid var(--primary); outline-offset: 2px; }

@media (max-width: 700px) {
  .set-tabs { gap: 0; }
  .set-tab { padding: 11px 10px; font-size: 12.5px; }
  .user-ctl .role-select { flex: 1; min-width: 0; }
}

/* ---------- Settings › Driver phones ----------
   Moved here 2026-08-01 from the retired Driver GPS screen, restyled to the
   settings page's own vocabulary rather than carrying gps.css across. */
.ph-list { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.ph-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 11px 13px;
}
.ph-row.ph-off { opacity: .6; }
.ph-main { flex: 1; min-width: 160px; }
.ph-name { font-weight: 700; letter-spacing: -.015em; }
.ph-acts { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- "is the tracking working?" — the answer, on demand ----
   One dot per phone, four states. The colour is the whole message: green is
   reporting, amber has gone quiet within the day, red has been dark longer than
   a day, grey has never spoken at all. */
.ph-health {
  background: var(--taupe-soft); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 13px 15px; margin: 12px 0;
}
.ph-h-head { font-size: 14.5px; margin-bottom: 10px; }
.ph-h-list { display: flex; flex-direction: column; gap: 6px; }
.ph-h-row {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--hairline);
  border-radius: var(--r-md); padding: 8px 11px; font-size: 13.5px;
}
.ph-h-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }
.ph-h-row.is-live  .ph-h-dot { background: #3ddc84; }
.ph-h-row.is-cold  .ph-h-dot { background: var(--amber); }
.ph-h-row.is-dark  .ph-h-dot { background: #c0574a; }
.ph-h-name { font-weight: 600; min-width: 92px; }
.ph-h-label { color: var(--muted); font-size: 12px; }
.ph-h-state { color: var(--ink-2); }
.ph-h-note { color: var(--muted); font-size: 12.5px; margin-left: auto; text-align: right; }
.ph-h-foot { margin-top: 10px; line-height: 1.5; }

.ph-modal-back {
  position: fixed; inset: 0; z-index: 950; background: rgba(43, 38, 34, .32);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.ph-modal {
  background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-lg);
  width: min(680px, 100%); max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
}
.ph-modal-head {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  border-bottom: 1px solid var(--hairline-soft);
}
.ph-modal-head h3 { flex: 1; font-family: var(--serif); font-size: 1.25em; font-weight: 600; }
.ph-modal-body { padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.ph-modal-body h4 { margin-top: 6px; font-size: 1.02em; }
.ph-qr-wrap {
  display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap;
  background: var(--taupe-soft); border-radius: var(--r-md); padding: 12px;
}
.ph-qr { width: 148px; height: 148px; flex: none; background: #fff; border-radius: var(--r-sm); padding: 6px; }
.ph-qr svg { width: 100%; height: 100%; display: block; }
.ph-f-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); font-weight: 600; display: block; margin-top: 6px;
}
.ph-copy { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ph-copy code {
  flex: 1; min-width: 0; overflow-wrap: anywhere; background: var(--bg);
  border: 1px solid var(--hairline); border-radius: var(--r-sm); padding: 6px 9px; font-size: 12.5px;
}
.ph-warn {
  background: rgba(192, 125, 51, .12); color: var(--amber);
  border-radius: var(--r-md); padding: 9px 12px; font-size: 13.5px;
}
.ph-list-notes { margin: 4px 0 0 18px; font-size: 13.5px; color: var(--ink-2); }
/* Setting up a phone opens in the one side panel (2026-09-14). The setup details are
   the blocks the old dialog drew, stacked with the panel's own spacing. */
.ph-setup { display: flex; flex-direction: column; gap: 10px; }
.ph-setup h4 { margin-top: 6px; font-size: 1.02em; }
.ph-panel .ws-field > .ws-error { margin-top: 2px; }
.ph-acts { align-items: center; }
/* An answer this account may not give stays on screen, with the reason, instead of vanishing. */
.tf-pick-row.is-disabled { cursor: not-allowed; opacity: .55; }
.tf-pick-row.is-disabled:hover { background: none; }

/* ============================================================
   TEAM SCHEDULE — COLOUR GRADES
   The grid is read at a glance by people who are not looking for detail: they
   want "who is off, who is in early, who is on nights". The GRADE carries that
   meaning and is consistent for everyone; the block's own colour survives as a
   tint (--tint) so a custom block is still recognisable as itself.
   Colour is never the only signal — the label and the times are always present.
   ============================================================ */
.sch-block {
  background: linear-gradient(0deg, var(--tint, color-mix(in srgb, var(--blue) 16%, #fff)), var(--tint, color-mix(in srgb, var(--blue) 16%, #fff)));
  box-shadow: inset 3px 0 0 0 var(--grade, transparent);
  padding-left: 10px;
}
/* ONE colour language, the shop's own: green working, orange vacation, red off.
   The stripe carries the STATE; the block's own colour still fills it, so which
   shift it is stays readable without competing with whether they are in at all. */
.sch-block.grade-work { --grade: #4f8163; }   /* 🟢 working */
.sch-block.grade-off  { --grade: #b5514a; }   /* 🔴 off */

/* The cell itself carries a soft wash, so a whole row reads without focusing. */
.sch-cell.g-work  { background: rgba(79, 129, 99, .11); }
.sch-cell.g-off   { background: rgba(181, 81, 74, .11); }
.sch-cell.g-leave { background: repeating-linear-gradient(135deg,
                      rgba(192,125,51,.12) 0 7px, rgba(192,125,51,.22) 7px 14px); }  /* 🟠 vacation */

/* A day worked in two or more blocks — the pieces stack with the break between.
   Two blocks in one week-grid column is tight, so a split block drops to its times
   only: the label was wrapping a letter at a time ("Mo / rni / ng"). The hours are
   what anyone is reading a split shift FOR anyway. */
.sch-cell.is-split { display: flex; flex-direction: column; gap: 3px; }
.sch-cell.is-split .sch-block {
  width: 100%; padding: 2px 6px; gap: 3px; font-size: 11px;
  white-space: nowrap; overflow: hidden;
}
.sch-cell.is-split .sch-block b { display: none; }
.sch-cell.is-split .sch-time { font-size: 11px; opacity: 1; font-weight: 600; }
.sch-dayblocks { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; }

/* Legend keys reuse the same grades so the map from colour to meaning is literal. */
.sch-legchip {
  background: linear-gradient(0deg, var(--tint, var(--taupe-soft)), var(--tint, var(--taupe-soft)));
  box-shadow: inset 3px 0 0 0 var(--grade, transparent);
  padding-left: 10px;
}
.sch-legchip.grade-work { --grade: #4f8163; }
.sch-legchip.grade-off  { --grade: #b5514a; }
.sch-legchip.g-leave-chip  { background: repeating-linear-gradient(135deg,
                               rgba(192,125,51,.12) 0 7px, rgba(192,125,51,.22) 7px 14px); }
.sch-legsep { width: 1px; height: 18px; background: var(--hairline); margin: 0 4px; display: inline-block; }

.sch-pick {
  background: linear-gradient(0deg, var(--tint, var(--taupe-soft)), var(--tint, var(--taupe-soft)));
  box-shadow: inset 0 3px 0 0 var(--grade, transparent);
}
.sch-pick.grade-work { --grade: #4f8163; }
.sch-pick.grade-off  { --grade: #b5514a; }

/* ---- shift block editor ---- */
.sch-blockrow { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; padding: 7px 0; }
.sch-bname { min-width: 130px; }
.sch-bemoji { width: 62px; text-align: center; }
.sch-bt { display: flex; flex-direction: column; gap: 3px; font-size: 11px;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 600; }
.sch-opt { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--muted); font-size: 11px; }
.sch-newblock { border-top: 1px solid var(--hairline); margin-top: 12px; padding-top: 12px; }
.sch-newblock h4 { font-size: 13.5px; margin-bottom: 6px; }
.sch-offline { display: flex; align-items: center; gap: 8px; font-size: 13.5px; margin-top: 6px; }
.sch-hint { font-size: 12.5px; color: var(--muted); margin-top: 8px; line-height: 1.45; }

@media (max-width: 680px) {
  .sch-blockrow { gap: 6px; }
  .sch-bname { flex: 1 1 100%; }
  .sch-blockrow .ws-btn { flex: 1; }
}

/* ============================================================
   COLUMNS ACROSS ↔ STACKED DOWN  (work board)
   The board was across-only, so finding one order on a laptop meant scrolling
   sideways through six columns. Stacked, each column becomes a full-width band
   whose cards flow into as many tracks as the screen allows — the same choice
   Staff tasks already offered.
   ============================================================ */
.board.flow-down, #hours.flow-down {
  display: block; overflow-x: visible; padding-bottom: 40px;
}
.board.flow-down .zone-col, #hours.flow-down .zone-col {
  flex: none; max-width: none; width: 100%; margin-bottom: 12px; min-height: 0;
}
.board.flow-down .zone-body, #hours.flow-down .zone-body {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px;
}
/* An empty stacked column should be a thin line, not a 300px hole. */
.board.flow-down .zone-col:has(.zone-empty) { margin-bottom: 8px; }
.board.flow-down .zone-empty, #hours.flow-down .zone-empty { padding: 10px; grid-column: 1 / -1; }
.view-flow { margin-left: 6px; }

/* ============================================================
   PHONES — the shell itself
   Reported as "cut off both ways": the page could be scrolled sideways off its
   own content, and the bottom was unreachable. Two causes, both here.
   ============================================================ */
@media (max-width: 860px) {
  /* 100vh on a phone is the viewport WITHOUT the browser's chrome, so the last
     ~60px of every page sat under the address bar with no way to scroll to it.
     dvh follows the chrome as it hides; vh stays as the fallback for old browsers. */
  #app { height: 100vh; height: 100dvh; }

  /* Nothing at the shell level may scroll sideways. Inner panes that genuinely
     need it (the board, tables, the tab strips) keep their own overflow-x and are
     unaffected — this only stops the PAGE sliding off its own edge. */
  html, body { overflow-x: hidden; max-width: 100%; }
  .main-area { overflow-x: hidden; }
  .page, .module-root, .home-wrap { max-width: 100%; min-width: 0; }

  /* Long unbroken strings — order refs, addresses, emails — were what pushed the
     page wider than the screen in the first place. */
  .home-wrap, .module-root { overflow-wrap: anywhere; }

  /* Respect the notch and the home indicator. */
  .side-nav { padding-top: env(safe-area-inset-top); }
  .main-area { padding-bottom: env(safe-area-inset-bottom); }
}

@media (max-width: 560px) {
  .home-wrap { padding: 16px 14px 48px; }
  .home-hello h2 { font-size: 26px; }
  .home-sub { font-size: 13.5px; }
  /* One tile per row. Two 230px tiles plus gap need 474px, which no phone has —
     the grid was overflowing rather than wrapping. */
  .home-tiles { grid-template-columns: 1fr; gap: 11px; margin-top: 16px; }
  .home-tile { min-height: 0; padding: 15px; }
  .tile-big { font-size: 40px; }
  .tile-txt { font-size: 15px; }
  .home-welcome { margin-top: 18px; font-size: 15px; }
  .home-welcome .btn { margin-left: 0; margin-top: 10px; display: inline-block; }

  /* The board is one column wide on a phone whichever flow is chosen — there is
     no room for two, and a sideways board cannot be operated with a thumb. */
  .board, #hours { padding: 10px; }
  .board .zone-col, #hours .zone-col { flex: 1 1 auto; max-width: none; width: 100%; }
  .board.flow-across { display: block; overflow-x: visible; }
  .board.flow-across .zone-col { margin-bottom: 12px; }
  .board.flow-down .zone-body, #hours.flow-down .zone-body { grid-template-columns: 1fr; }
  .view-group { flex-wrap: wrap; }
}

/* ---------- Cars: usage history ---------- */
.fl-modal-back {
  position: fixed; inset: 0; z-index: 950; background: rgba(43, 38, 34, .32);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.fl-modal {
  background: var(--card); border: 1px solid var(--hairline); border-radius: var(--r-lg);
  width: min(760px, 100%); max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
}
.fl-modal-head {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  border-bottom: 1px solid var(--hairline-soft);
}
.fl-modal-head h3 { flex: 1; font-family: var(--serif); font-size: 1.25em; font-weight: 600; }
.fl-modal-body { padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
.fl-totals {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1px;
  background: var(--hairline-soft); border: 1px solid var(--hairline-soft); border-radius: var(--r-md);
  overflow: hidden;
}
.fl-totals > div { background: var(--card); padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
.fl-totals span {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); font-weight: 600;
}
.fl-totals b { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.015em; }
.fl-drivers { margin: 0; }
.fl-mono { font-variant-numeric: tabular-nums; }
@media (max-width: 560px) {
  .fl-modal-back { padding: 0; align-items: flex-end; }
  .fl-modal { max-height: 92vh; border-radius: var(--r-lg) var(--r-lg) 0 0; }
}

/* ============================================================
   WORK BOARD — REDESIGN, 2026-08-01
   The board was loud in every direction at once: saturated zone bars, a coloured
   status pill, a type badge and nine emoji chips per card, all at one weight.
   Colour now means exactly one thing — STATE — and it appears in exactly one
   place per card: the stripe. Everything else is ranked by typography.
   ============================================================ */

/* ---- columns: a heading, not a banner ---- */
.zone-col {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.zone-head {
  background: var(--card) !important;      /* was a saturated per-zone fill */
  color: var(--ink) !important;
  border-bottom: 1px solid var(--hairline);
  border-radius: 0;
  padding: 12px 14px 11px;
  font-weight: 700; font-size: 13.5px; letter-spacing: -.015em;
  position: relative;
}
/* The zone's identity survives as a 3px rule, which is all it needed to be. */
.zone-head::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--zone, var(--taupe));
}
.zone-SHARJAH  .zone-head { --zone: var(--zone-sharjah); }
.zone-DUBAI    .zone-head { --zone: var(--zone-dubai); }
.zone-AJMAN    .zone-head { --zone: var(--zone-ajman); }
.zone-ABUDHABI .zone-head { --zone: #8a7a45; }
.zone-RAK      .zone-head { --zone: #7c5a3f; }
.zone-UAQ      .zone-head { --zone: #4f7676; }
.zone-FUJAIRAH .zone-head { --zone: #6b6a52; }
.zone-OTHERS   .zone-head { --zone: var(--zone-others); }
.zone-head .count {
  background: var(--taupe-soft) !important; color: var(--ink-2) !important;
  font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums;
  padding: 1px 9px; border-radius: var(--r-pill);
}
.zone-body { background: transparent; padding: 10px; gap: 8px; }
.zone-empty { padding: 20px 8px; font-size: 12.5px; }

/* ---- the card ---- */
.card-entry {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--state, var(--muted));
  border-radius: var(--r-md);
  padding: 11px 12px 12px;
  display: flex; flex-direction: column; gap: 3px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.card-entry:hover { border-color: var(--hairline); border-left-color: var(--state, var(--muted)); background: #fffdfb; }
.card-entry.mine { box-shadow: inset 0 0 0 1px rgba(177, 122, 94, .35); }

/* The ONE use of colour on a card: what state it is in. */
.card-entry.st-new        { --state: color-mix(in srgb, var(--muted) 65%, #fff); }
.card-entry.st-scheduled  { --state: var(--blue); }
.card-entry.st-out        { --state: var(--amber); }
.card-entry.st-delivered,
.card-entry.st-collected  { --state: var(--green); opacity: .72; }
.card-entry.st-cancelled  { --state: var(--red); opacity: .6; }

.c-eyebrow { display: flex; align-items: center; gap: 7px; margin-bottom: 1px; }
/* The TF number is what staff call across the shop and read off a phone, so it
   stopped being a muted 11px eyebrow. Fixed-width and in ink it is scannable
   down a column, while the 15px customer name below still leads the card. */
.c-ref {
  font-family: var(--mono);
  font-size: 13.5px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: 0;
}
.c-kind {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-2); background: var(--taupe-soft); border-radius: 6px; padding: 1px 6px;
}
/* One kanban column per place a job came from. A real shop leads with the brand
   copper; a channel with no shop behind it stays grey so the two never read alike. */
.branch-col.is-shop .zone-head { color: var(--primary-dark); }
.branch-col.is-channel .zone-head, .branch-col.is-unset .zone-head { color: var(--ink-2); }
.branch-col.is-unset { opacity: .82; }

/* Which shop took the order. Quiet — it answers "whose is this?" at a glance
   without competing with the customer's name below. */
.c-branch {
  font-size: 11px; font-weight: 600; letter-spacing: .01em; white-space: nowrap;
  color: var(--primary-dark); background: #f6efe9; border-radius: 6px; padding: 1px 6px;
  overflow: hidden; text-overflow: ellipsis; max-width: 150px;
}
/* The entry point for the eye. Everything else is measured against it. */
.c-name {
  font-size: 15px; font-weight: 700; letter-spacing: -.018em; line-height: 1.25;
  color: var(--ink); margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.c-sub { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.c-where {
  font-size: 12.5px; color: var(--ink-2); line-height: 1.35; margin-top: 3px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.c-where a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--hairline); }
.c-where a:hover { color: var(--primary-dark); border-bottom-color: var(--primary); }

.c-thumbs { display: flex; gap: 5px; margin-top: 7px; align-items: center; }
.c-thumbs img {
  width: 42px; height: 42px; object-fit: cover; border-radius: var(--r-sm);
  border: 1px solid var(--hairline); display: block; cursor: zoom-in;
}
.c-more { font-size: 11px; color: var(--muted); font-weight: 600; }

/* The two facts a dispatcher scans for. */
.c-facts { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.c-fact {
  font-size: 11px; font-weight: 600; letter-spacing: -.01em;
  padding: 2px 8px; border-radius: var(--r-sm);
  background: var(--taupe-soft); color: var(--ink-2);
}
.c-fact.c-time { font-variant-numeric: tabular-nums; }
.c-fact.c-who { background: rgba(177, 122, 94, .12); color: var(--primary-dark); }
/* .c-none — the amber "Nobody yet" chip — was removed on 2026-08-04. An unassigned
   card simply has no name on it. */

/* Everything else, in one quiet line. */
.c-detail {
  font-size: 11px; color: var(--muted); margin-top: 6px; line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.c-note { border-bottom: 1px dotted var(--muted); cursor: help; }

.c-quick { margin-top: 9px; }
.c-go {
  width: 100%; border: 1px solid var(--hairline); background: var(--bg); color: var(--ink);
  border-radius: var(--r-pill); padding: 7px 12px;
  font: inherit; font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; cursor: pointer;
}
.c-go:hover { border-color: var(--primary); color: var(--primary-dark); background: var(--card); }
.c-go:active { transform: var(--press); }

/* Old card furniture, retired with the redesign. */
.card-entry .status-pill,
.card-entry .event-badge,
.card-entry .task-badge,
.card-entry .collection-badge { display: none; }

@media (max-width: 560px) {
  .c-name { font-size: 15px; white-space: normal; }
  .c-go { padding: 10px 12px; font-size: 13.5px; }
  .c-thumbs img { width: 48px; height: 48px; }
}

/* The original .card-entry.st-* rules set border-left-color directly and carry
   specificity (0,2,0) — higher than the .card-entry shorthand above — so they, not
   --state, decided the stripe. Two definitions of the same thing is how a cascade
   quietly rots. These restate them at matching specificity, later in the file, so
   --state is the single source of the stripe colour. */
.card-entry.st-new       { border-left-color: var(--state); }
.card-entry.st-scheduled { border-left-color: var(--state); }
.card-entry.st-out       { border-left-color: var(--state); }
.card-entry.st-delivered,
.card-entry.st-collected { border-left-color: var(--state); opacity: .72; }
.card-entry.st-cancelled { border-left-color: var(--state); opacity: .6; }

/* ---- shops on the roster ----
   The shop sits beside the person's name as one quiet line. It was a panel of 35
   dropdowns; nobody wants to fill that in, and nobody had to — everyone belongs to
   the usual shop until someone says otherwise.
   These live here, not in a modules/hr.css: the HR module never injects a
   stylesheet of its own, so a file there would simply never load. */
.sch-branchbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 2px 0 12px; }
.sch-bflabel { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.sch-shop, .sch-shop-ro {
  display: block; margin-top: 1px; max-width: 100%;
  font-size: 11px; font-family: var(--ui); color: var(--muted);
  border: 1px solid transparent; border-radius: 6px; padding: 1px 4px;
  background: transparent; cursor: pointer;
  text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
}
.sch-shop:hover, .sch-shop:focus { border-color: var(--hairline); background: var(--card); color: var(--ink-2); outline: none; }
.sch-shop-ro { cursor: default; }

/* ---- department colour grades ----
   Each section of the roster gets its own hue so a long list reads as parts, not
   one wall of names. It tints the section heading and the leading edge of its rows
   — a hint only: the shift colours still have to be the thing you see. */
.sch-grouphead.dept-drivers    { --dept: #3a5c7a; }
.sch-grouphead.dept-florists   { --dept: #a3503f; }
.sch-grouphead.dept-helpers    { --dept: #4a7c62; }
.sch-grouphead.dept-sales      { --dept: #8a6a2f; }
.sch-grouphead.dept-events     { --dept: #6d5480; }
.sch-grouphead.dept-store      { --dept: #4b6a6a; }
.sch-grouphead.dept-management { --dept: #915f45; }
.sch-grouphead.dept-office     { --dept: #5b5f6b; }
.sch-grouphead.dept-other      { --dept: #8a8279; }
.sch-grouphead[class*="dept-"] {
  box-shadow: inset 4px 0 0 0 var(--dept, transparent);
  color: var(--dept, var(--primary-dark));
}
.sch-row[class*="dept-"]      { box-shadow: inset 3px 0 0 0 color-mix(in srgb, var(--dept, transparent) 45%, transparent); }
.sch-row.dept-drivers    { --dept: #3a5c7a; }
.sch-row.dept-florists   { --dept: #a3503f; }
.sch-row.dept-helpers    { --dept: #4a7c62; }
.sch-row.dept-sales      { --dept: #8a6a2f; }
.sch-row.dept-events     { --dept: #6d5480; }
.sch-row.dept-store      { --dept: #4b6a6a; }
.sch-row.dept-management { --dept: #915f45; }
.sch-row.dept-office     { --dept: #5b5f6b; }
.sch-row.dept-other      { --dept: #8a8279; }

/* "No date yet" — a job the customer has not fixed a day for. It sits in its own
   tray rather than being parked on an arbitrary date where it would read as work
   for that day. */
.wiz-check, .f-check {
  display: flex; align-items: center; gap: 7px; margin-top: 6px;
  font-size: 12.5px; color: var(--muted); cursor: pointer;
}
.wiz-check input, .f-check input { accent-color: var(--primary); margin: 0; }
.wiz-check span, .f-check span { user-select: none; }
input[type=date]:disabled { opacity: .5; cursor: not-allowed; }

/* ---- the work board bar, at every width ----
   Two rows of controls that both grew this month (a Branch view, a No-date tab).
   Rather than let them collide, each gives way in a defined order: the status
   message first, then the view row scrolls, and the page's name never moves. */
/* The badge is the widest optional thing on the bar and its own rule above says
   it gives way FIRST — but it was still holding 30ch at 1400px, which is where
   the Work board's controls ran out of room. It now starts giving back earlier. */
@media (max-width: 1500px) {
  .sync-badge { max-width: 18ch; }
}
@media (max-width: 1300px) {
  /* A status cut mid-word — "Shopify no…", "Synced 09…" — reads as a fault, and
     at 12ch even the healthy "● Synced 09:14" was cut. Below this the coloured dot
     says it and the tooltip carries the words. */
  .sync-badge { font-size: 0; max-width: none; overflow: visible; cursor: help; }
  .sync-badge.ok::before, .sync-badge.warn::before { font-size: 13px; }
}
@media (max-width: 1120px) {
  /* Below this the message is noise competing with the controls — the same
     information is on the Settings page. */
  .sync-badge { display: none; }
}
/* On a phone even the wrapped tools row is too narrow for a day picker AND a
   search box side by side — measured at 420px, they needed 392px in a 381px row
   and the search slid off the right. The row wraps internally instead: the day
   picker keeps its natural width, the search takes the line below. */
@media (max-width: 620px) {
  /* wrapping itself is now unconditional — see .head-tools above */
  .head-tools .search-wrap { flex: 1 1 100%; max-width: none; }
}
/* If the view buttons still cannot fit, they scroll sideways instead of being
   clipped by the edge of the screen — "Across" was falling off. */
.view-group { flex: 0 0 auto; }
.controlbar { min-width: 0; }
.controlbar .db-filters { flex: 0 1 auto; min-width: 0; }

/* ---------- a task's own reference (TS-000000) ----------
   The same treatment the TF order number gets: monospaced, tabular figures, quiet.
   It is an identifier to quote, not a headline — the task's NAME is the headline,
   so this sits under it at eyebrow size rather than competing with it. */
.ct-ref, .mw-ref {
  font-family: var(--mono); font-size: 11px; font-variant-numeric: tabular-nums;
  letter-spacing: .02em; color: var(--muted); margin-top: 1px;
}
/* Who wrote the task, on the same quiet line as its reference — one line of
   provenance, not a field somebody has to go and look for. */
.ct-by { font-family: var(--font-sans, inherit); margin-left: 6px; }

/* ---------- activity / history chips ----------
   Which job and which task a row was about. The job chip is a button because
   opening that job is the first thing anyone wants after reading the row. */
.act-chips { display: inline-flex; flex-wrap: wrap; gap: 5px; margin-left: 6px; vertical-align: baseline; }
.act-chip {
  display: inline-block; border-radius: var(--r-pill); padding: 1px 9px;
  font-size: 11px; font-weight: 600; line-height: 1.6; white-space: nowrap;
  max-width: 34ch; overflow: hidden; text-overflow: ellipsis;
}
.act-chip-job {
  border: 1px solid var(--line); background: var(--card); color: var(--primary-dark);
  font-family: inherit; cursor: pointer;
}
.act-chip-job:hover { border-color: var(--primary); background: #fffdfb; }
.act-chip-task {
  background: var(--taupe-soft); color: var(--ink-2);
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 11px;
}
/* The Activity page reuses the job card's history rows, so it inherits their
   layout — but it is a full page, not a panel, so it may breathe a little. */
#activityList .hist-row { align-items: flex-start; }
#activityList .hist-body { line-height: 1.55; }

/* ---------- overlay stacking, stated in one place ----------
   The job card is opened FROM other overlays — the Activity list, Dispatch's stop
   sheet, My work, Staff tasks — so it has to sit above all of them. It did not:
   #entryModal is a .modal-backdrop at z-index 50, while #activityModal is the SAME
   class and comes later in index.html, and Dispatch's own sheet is 950. The card
   was opening correctly every time and landing completely underneath, which reads
   as "the click does nothing".

   Ordering, highest last:
     950  module sheets (.db-modal-back, .ph-modal-back, .fl-modal-back)
     960  the job card — it is what you clicked THROUGH to, so it wins
    1200  confirm dialogs — they interrupt the card, so they must beat it
    1300  toasts — always readable, above everything
   Closing the card returns you to the list you came from, which is why the sheet
   underneath is left open rather than dismissed. */
#entryModal { z-index: 960; }
/* THE JOB CARD IS A SIDE PANEL (unification step 4, 2026-09-11). It opened as a box in
   the middle of the screen that hid the list it was opened from; it now comes in from
   the right like everything else that opens, same width as before, full height, and
   the list stays in view beside it. A click beside it closes it, as it always did.
   A CSS animation, not a transition: the element goes from display:none to shown, and
   a paused animation still shows its first frame — which is on screen, nearly in place. */
#entryModal {
  justify-content: flex-end; align-items: stretch; padding: 0; overflow: hidden;
  background: rgba(20,22,30,.28);
}
#entryModal .modal.jobcard {
  width: min(680px, 100vw); height: 100%; max-height: none; border-radius: 0;
  box-shadow: -18px 0 50px rgba(0,0,0,.18); animation: tfPanelIn .22s ease;
}
@keyframes tfPanelIn { from { transform: translateX(28px); opacity: .85; } }
@keyframes tfSheetIn { from { transform: translateY(28px); opacity: .85; } }
@media (max-width: 760px) {
  #entryModal { align-items: flex-end; }
  #entryModal .modal.jobcard { width: 100vw; height: 94vh; height: 94dvh; border-radius: 18px 18px 0 0; animation-name: tfSheetIn; }
}
@media (prefers-reduced-motion: reduce) { #entryModal .modal.jobcard { animation: none; } }
#confirmModal { z-index: 1200; }
/* #toast, not .toast — the element is an id, and an id also outranks the class
   selector, so a rule written the other way silently did nothing. The toast sat at
   100 and raising the card to 960 would have hidden every "Saved ✓" behind it. */
#toast { z-index: 1300; }

/* ---------- Cars: who had it, day by day ----------
   Replaces the old route-only table. A day is the unit, because "who took the van
   on Tuesday" is the question — and a day can hold a delivery run in the morning
   and a supplier trip after lunch. */
.fl-day { border-top: 1px solid var(--hairline-soft, #efe8e2); padding: 10px 0 4px; }
.fl-day:first-of-type { border-top: 0; }
.fl-day-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 6px;
}
.fl-day-date {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted);
}
.fl-day-who { font-size: 13.5px; color: var(--ink); }
.fl-use {
  display: flex; align-items: baseline; gap: 8px; padding: 5px 0 5px 2px; font-size: 13.5px;
}
.fl-use-ico { flex: 0 0 auto; font-size: 13.5px; }
.fl-use-main { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.fl-use-what { color: var(--ink-2); overflow-wrap: anywhere; }
.fl-use-detail { color: var(--muted); font-size: 12.5px; }
.fl-use-ref {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
@media (max-width: 560px) {
  .fl-use { flex-wrap: wrap; }
  .fl-use-main { flex: 1 1 100%; }
}

/* ---------- Cars: the filter header ----------
   Search · state · kind, in the app bar like every other screen's controls.
   The search box shrinks rather than pushing the segment and the export buttons
   off the edge — min-width:0 on the FLEX ITEM, because that is what lets a box
   with content inside it shrink at all (see the app-bar overflow note). */
.fl-search { position: relative; display: flex; align-items: center; flex: 1 1 190px; min-width: 96px; }
.fl-search svg {
  position: absolute; left: 9px; width: 15px; height: 15px;
  color: var(--muted); pointer-events: none;
}
.fl-searchbox { width: 100%; min-width: 0; padding-left: 30px; }
.fl-kind { flex: 0 0 auto; max-width: 140px; }

/* An export button names a FILE FORMAT, and the ≤1400px rule that strips words off
   head-actions would leave a document icon sitting beside a chart icon — which says
   "PDF" and "Excel" to nobody, and cannot be hovered for a tooltip on a tablet.
   These two keep their words down to the phone breakpoint, where the bar genuinely
   runs out of room and everything else drops its word too. Wins on specificity
   (0,3,0 against 0,2,0), so no !important is needed. */
@media (min-width: 761px) {
  .head-actions .fl-exp .btn-word { display: inline; }
}

/* "Show all again" — a sentence with a button in it, not a button in a row. */
.ws-linkbtn {
  border: 0; background: none; padding: 0; font: inherit; color: var(--primary);
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.ws-linkbtn:hover { color: var(--primary-dark); }

@media (pointer: coarse) {
  .fl-searchbox { min-height: 44px; }
  .fl-kind { min-height: 44px; }
  .ws-linkbtn { display: inline-block; min-height: 32px; }
}

/* ---------- Cars: check out / check in ----------
   Added 2026-08-04. A car somebody is holding has to be readable across the room
   — the office asks "which vans are out" far more often than it asks anything
   else on this page — so the card is tinted rather than merely captioned. */
.fl-card.is-out { border-color: color-mix(in srgb, var(--amber) 42%, var(--line)); }
.fl-out-now {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 13px; color: var(--ink-2);
  background: color-mix(in srgb, var(--amber) 9%, #fff);
  border-radius: var(--r-sm); padding: 7px 9px;
}
.fl-out-now svg { width: 15px; height: 15px; color: var(--muted); flex: none; }
.fl-out-now .ws-btn { margin-left: auto; }

.fl-heldnow {
  display: flex; align-items: center; gap: 8px;
  margin: 0; padding: 9px 11px; font-size: 13.5px;
  background: color-mix(in srgb, var(--amber) 10%, #fff);
  border: 1px solid color-mix(in srgb, var(--amber) 30%, #fff);
  border-radius: var(--r-sm);
}
.fl-heldnow svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
.fl-problems { color: var(--red); }

/* A check-out is the one line in the history the DRIVER wrote, so it is marked
   apart from the runs and tasks that were planned for him by somebody else. */
.fl-use.is-took { background: var(--taupe-soft); border-radius: var(--r-sm); padding-left: 6px; }
.fl-use.has-problem { box-shadow: inset 3px 0 0 var(--red); }

@media (pointer: coarse) {
  .fl-out-now .ws-btn { min-height: 40px; }
}

/* ============================================================
   TOUCH DEVICES — phones and tablets
   ============================================================
   Keyed on `pointer: coarse`, NOT on width. A 1024px iPad is a touch device and
   needs finger-sized controls; a 1024px laptop window does not. Width alone gets
   both wrong.

   Measured on this app at 375px before any of this existed:
     · 33 of 33 tappable controls were under 44px, 16 of them under 32px —
       the day arrows were 25×27px, which is a miss-tap machine.
     · every text field was under 16px, which makes iOS Safari ZOOM the whole
       page the moment one is focused, and it never zooms back out. That single
       behaviour is most of what "it isn't responsive" feels like on an iPhone.

   44px is the floor Apple and Google both publish. Nothing here changes the
   desktop rendering — a mouse pointer never matches these rules.
   ============================================================ */
@media (pointer: coarse) {
  /* The bar grows with its buttons; things are positioned against this var. */
  :root { --head-h: 62px; }

  /* ---- 1 · no more zoom-on-focus ---- */
  /* !important, deliberately. Dozens of narrower selectors set a smaller size —
     `.top-strip .search-wrap input[type=search]` is (0,2,2) and beats anything
     reasonable written here — and EVERY one of them reintroduces the zoom. On a
     touch device there is no case for a field under 16px, so this is a floor
     rather than a preference. Scoped to coarse pointers, so desktop is untouched. */
  input, select, textarea,
  .ws-input, .ws-select, .ct-input, .wiz-input, .db-input, .db-search, .db-pick,
  .br-input, .stf-input {
    font-size: 17px !important;
  }
  /* The date pickers show their value as text, not through an <input> font. */
  .date-pick, .date-label { font-size: 17px; }

  /* HR's roster carries one "which shop" <select> PER PERSON — 35 of them on a
     real roster, each 28px tall. A 28px select is a genuinely hard target, and
     this one changes where somebody works, so a mis-tap is expensive. The rows
     get taller; the roster already scrolls, so that costs nothing that matters. */
  .sch-shop, .sch-shop-ro { min-height: 44px; }

  /* My work IS the driver's screen and is only ever held in one hand, often
     while standing at a door. Its own segmented control and the "whose work"
     picker were 36-37px — close enough to look fine and not close enough to hit. */
  .mw-seg-btn, .ws-select { min-height: 44px; }

  /* The date pill is a <label>, so `button, select` never reached it: it sat at
     34px between two 44px arrows, which looked wrong AND was the smallest target
     in the most-used control in the app. */
  .date-pick { min-height: 44px; }

  /* Settings > Team is one role <select> PER PERSON — 108 of them on this data.
     An earlier round fixed their WIDTH (they had collapsed to 38px, showing one
     character of "Super admin") and stopped there; they were still 30px tall.
     Same reasoning as the roster's shop picker: this control changes what
     somebody can do in the system, so it gets a proper target. */
  .role-select { min-height: 44px; }

  /* ---- 2 · fingers, not cursors ---- */
  .btn, .ws-btn, .view-btn, .set-tab, .ws-seg-btn, .wiz-tg,
  .stf-tab, .db-btn, .db-view, .ws-tab, .br-btn, .bview-btn {
    min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* The filter pills take the height and NOT the centring: a chip is a number
     beside a word sharing one baseline, and `align-items: center` above would
     break that alignment on phones only. */
  .db-fchip { min-height: 44px; }
  /* The arrangement menu's rows are left-aligned list items, not centred pills —
     they take the 44px floor from `.view-btn` above and keep their own alignment. */
  .bview-menu .view-btn { justify-content: flex-start; }
  /* Icon-only buttons need width too, or they stay 25px wide however tall. */
  .btn-ico, .menu-btn, .db-ico, .ct-remove, .ct-x, .modal-close, .db-outcome,
  .ws-btn.sm, .btn-sm {
    min-width: 44px; min-height: 44px;
  }
  /* A 13px checkbox cannot be hit reliably; these are real controls on the
     job card and in the crew pickers. */
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; }

  .top-strip { min-height: var(--head-h); }

  /* Roomier rows where a finger has to land on one of several. */
  .set-tab { padding-left: 18px; padding-right: 18px; }
  /* The menu is the one list every screen is reached through. */
  .nav-item { min-height: 44px; }
  /* My work is the DRIVER's screen — the one that is only ever used on a phone,
     so its controls matter most. .mw-ic is a 30px icon button on every job row. */
  .mw-ic { width: 44px; height: 44px; }
  .mw-actions .ws-btn.sm { min-height: 44px; }
  .view-btn, .db-view { padding-left: 15px; padding-right: 15px; }
}

/* A segmented control is a row of buttons and must scroll rather than be cut off
   — Reports' period switcher was losing its last 6px at 375px. */
.ws-seg { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.ws-seg::-webkit-scrollbar { display: none; }
.ws-seg-btn { flex: 0 0 auto; }

/* ============================================================
   PHONES — the real device, not a narrow desktop
   ============================================================
   Taken from screenshots on an iPhone Pro Max (428 CSS px). Measured there, the
   Work board spent 383px of a 750px screen on chrome before a single job card —
   51% of the phone showing controls. The bar alone was 211px because the bell had
   wrapped onto a row of its own.

   Two separate causes, both fixed below:
     · the touch sizing added earlier made every row 44px, which is right for a
       finger but turned three rows into four;
     · everything the desktop shows was still being shown, at full length.
   ============================================================ */

/* ---- 1 · iOS measures `vh` against the viewport WITHOUT Safari's chrome ----
   So a `max-height: 86vh` modal is taller than what you can actually see, and
   since these are centred or bottom-aligned it is the TOP that goes off screen —
   which is why "Assign multi steps" appeared sliced in half with no ✕ to press.
   `dvh` follows the real visible area. The vh line stays first as the fallback
   for browsers without dvh. #app was already fixed this way; no modal was. */
.modal { max-height: 92vh; max-height: 92dvh; }
.wiz-modal { max-height: 94vh; max-height: 94dvh; }
.sch-modal { max-height: 92vh; max-height: 92dvh; }
.db-modal { max-height: 86vh; max-height: 86dvh; }
.ph-modal, .fl-modal { max-height: 88vh; max-height: 88dvh; }
@media (max-width: 700px) {
  .modal { max-height: 96vh; max-height: 96dvh; }
  .wiz-modal { max-height: 100vh; max-height: 100dvh; height: 100dvh; }
}

/* ---- 2 · a tab strip scrolls, it never truncates ----
   "Tasks & people" was being cut to "Tasks &" on the job card. A half-word is
   worse than a scroll: it reads as a rendering fault. */
.jc-tabs {
  overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap;
}
.jc-tabs::-webkit-scrollbar { display: none; }
.jc-tabs button { flex: 0 0 auto; white-space: nowrap; }

@media (max-width: 560px) {
  /* ---- 3 · give the screen back to the work ---- */

  /* The primary action keeps its "+" and drops its sentence. The whole reason
     the bell wrapped to its own line was 192px of button text in a 413px bar. */
  .btn-word-lg { display: none; }
  #addBtn { min-width: 44px; }

  /* "Today" the button repeats "Today · Sun 2 Aug" the pill directly beside it. */
  #todayBtn { display: none; }


  /* The pill counts stay, the padding goes. */
  .db-fchip { padding-left: 10px; padding-right: 10px; }

  /* Date and search SHARE a row rather than taking one each. With the redundant
     "Today" button gone the day picker is ~238px and the search can live in the
     rest; below about 400px it still wraps, which is the right behaviour there. */
  /* basis 0, not 130px: flexbox breaks the line on the HYPOTHETICAL size, so a
     130px basis wrapped at 428px (257+8+130 = 395 in a 389px row) before it ever
     tried shrinking. With basis 0 and a floor it stays on the row and then grows
     into whatever the day picker leaves — about 124px here. */
  /* 90px was enough for the WRAPPER but not for the input inside it — the
     "More search" button took 64 of it and left a 26px search box. The floor is
     now what the INPUT needs; if the day picker leaves less than that the row
     wraps and the search gets a full line of its own, which is the right answer
     rather than a field nobody can type in. */
  /* "More search" is a secondary door to the same thing and was taking 64 of the
     ~130px the whole search had, leaving a 26px field. Dropping it on a phone
     buys a usable search box AND keeps the day picker on the same row — 130px of
     wrapper is now 130px of input. */
  #advSearchBtn { display: none; }
  .head-tools .search-wrap { flex: 1 1 0; min-width: 96px; max-width: none; }
  .head-tools .date-nav { flex: 0 1 auto; min-width: 0; }

  /* The stage strip is permanent chrome now, so it pays rent on every card. The
     steps stay (that is the point); the instruction line under them goes — they are
     tappable and each carries its own title, and the caption cost ~22px of the job
     on every single card. */
  .jc-stage { padding: 9px 12px 8px; }
  .jc-stage .stage-hint { display: none; }

  /* My work tells the driver to "drag a job between columns" — you cannot drag
     comfortably on a phone, and the sentence costs two lines before any work. */
  .mw-hint, .kb-hint { display: none; }
}

/* ============================================================
   PHONE FIXES — round 3, from screenshots on a real iPhone
   ============================================================ */

/* 1 · Settings › Team: three dropdowns were sharing one row with min-width:0 and
   collapsing to 38px each, showing a single character of "Super admin". A control
   you cannot read is worse than one you have to scroll to. One per line. */
@media (max-width: 700px) {
  .user-ctl .role-select { flex: 1 1 100%; min-width: 0; }
  .user-ctl .spacer { display: none; }
  .user-ctl button { flex: 1 1 calc(50% - 4px); }
}

/* 2 · The roster grid had 95px day columns, so a shift block broke its words
   letter by letter — "S / hi / ft" over three lines. The grid already lives in
   .sch-scroll (overflow-x:auto), so it may be as wide as it needs and scroll. */
.sch-daycol { min-width: 132px; }
.sch-block, .sch-block * { overflow-wrap: normal; word-break: keep-all; hyphens: none; }
.sch-block { min-width: 0; }

/* 3 · A button in the app bar keeps its WORD on a phone.
   The ≤1400px rule that hides .btn-word exists so a mouse-sized bar can breathe —
   but on a phone it left Team showing four unlabelled pictures (📄 🎨 🏖️ 📣) with
   no way to know what they did. Below 700px the words come back and the strip
   scrolls instead. */
@media (max-width: 700px) {
  /* The words came back here when these buttons were bare EMOJI and unreadable.
     They now carry drawn icons with titles, so below 560px the icon alone is
     intelligible and the label goes again — Team's bar was 233px of a 800px
     screen, most of it four labelled buttons. Between 560 and 700 the words
     stay, because there is room for them. */
  .head-actions .btn-word { display: inline; }
  .head-actions {
    max-width: 100%; overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap;
  }
  .head-actions::-webkit-scrollbar { display: none; }
  .head-actions > * { flex: 0 0 auto; }
}

/* 4 · My work is a phone screen with a desktop instruction on it — you cannot
   comfortably drag a card with a thumb, and the sentence cost two lines above the
   only thing that matters. The columns already say what they are. */
@media (max-width: 700px) {
  #page-mytasks .ws-sub, .mw-wrap > .ws-sub { display: none; }
}

/* ---------- the icon set ----------
   24px artboard, sized by the text around it so an icon in a 13.5px chip is
   smaller than one in a 20px heading without anyone specifying either. `em`, not
   px, is what makes a set feel drawn for the type rather than dropped beside it.
   currentColor means an icon in a terracotta button is terracotta. */
.i {
  width: 1.25em; height: 1.25em; flex: 0 0 auto;
  vertical-align: -0.22em; display: inline-block;
}
.ico-slot { display: inline-flex; align-items: center; }
/* Buttons that are only an icon get it optically centred rather than baseline-set. */
.btn .i, .ws-btn .i, .db-btn .i { vertical-align: -0.2em; }
.nav-ico .i { width: 1.35em; height: 1.35em; vertical-align: -0.3em; }
/* The nav icon column keeps its width whether the item is an SVG or a leftover
   emoji from a module that has not been converted, so the labels stay aligned. */
.nav-ico { display: inline-flex; align-items: center; justify-content: center; width: 1.5em; }

/* ---------- the connection banner ----------
   One place that answers "did that save?". Sits above everything including the
   job card (960) and confirm dialogs (1200), because it is the one message that
   changes whether you should trust what is on screen. */
.conn-banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: max(16px, env(safe-area-inset-bottom, 0px));
  z-index: 1400; display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-radius: var(--r-pill); font-size: 13.5px; font-weight: 600;
  box-shadow: 0 8px 28px rgba(0,0,0,.22); max-width: calc(100vw - 24px);
}
.conn-banner.is-down { background: #6b3f3a; color: #fff; }
.conn-banner.is-ok   { background: #2f5d46; color: #fff; }
.conn-banner .i { width: 1.1em; height: 1.1em; }

/* ============================================================
   MODULE ACCENTS — the same theming, a different hue
   ============================================================
   The owner asked for each module to carry its own colour so you know where you
   have landed, and for that to be CONSISTENT. Those two only hold together if the
   *shape* of the theming is identical everywhere and only the hue moves — which
   means one token, set here, used by every module.

   Before this, each module CSS carried its own hard-coded hexes: Staff tasks had
   drifted to teal (#aed5c8, #d3e6df) and My work was using green, blue and pink at
   once. Nothing tied them to the brand or to each other.

   The soft/tint/line steps are MIXED from the accent rather than hand-picked, so a
   new module needs one colour and gets the whole set correct by construction. */
:root {
  --accent: var(--primary);                                   /* terracotta by default */
  --accent-ink:  color-mix(in srgb, var(--accent) 72%, #2b2622);
  --accent-tint: color-mix(in srgb, var(--accent) 10%, transparent);
  --accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 28%, transparent);
  /* The fill behind WHITE text. The raw accent is tuned to read as INK on ivory;
     terracotta #b5764a only reaches 3.71:1 under white, short of AA for a 13.5px
     tab label. Deepening it slightly clears AA on every module and nobody notices
     the hue moved. */
  --accent-fill: color-mix(in srgb, var(--accent) 84%, #2b2622);
}
/* Chosen by measuring, not by eye. Every pair is at least 15.5 CIELAB deltaE
   apart (the just-noticeable difference is ~2.3, so 15 reads as "a different
   colour" without shouting), every one is at least 15.2 from --green/--blue/
   --amber/--red so an accent can never be mistaken for a STATUS, and every one
   clears WCAG AA large-text contrast on the ivory background (3.39-6.23).
   The first version failed all three: dispatch was byte-identical to the
   "done" green and board sat 17 from fleet. */
:root[data-module="board"]      { --accent: #b5764a; }   /* terracotta — the brand, the home surface */
:root[data-module="dispatch"]   { --accent: #2f7a86; }   /* teal — the road */
:root[data-module="mytasks"]    { --accent: #4f8a4a; }   /* green — a driver's own work */
:root[data-module="stafftasks"] { --accent: #5f6ea8; }   /* indigo — the whole team */
:root[data-module="drivertasks"] { --accent: #2f7a86; }  /* teal — the road, as Dispatch */
:root[data-module="staffpickups"] { --accent: #8e5d9e; } /* violet — the team, as Team, where the runs are planned */
:root[data-module="hr"]         { --accent: #8e5d9e; }   /* violet — people & time */
:root[data-module="fleet"]      { --accent: #a07c33; }   /* ochre — vehicles */
:root[data-module="branches"]   { --accent: #7a5230; }   /* bark — the premises */
:root[data-module="reports"]    { --accent: #7d5350; }   /* brick — numbers */
:root[data-module="events"]     { --accent: #a5537a; }   /* rose — receptions & engagements */
:root[data-module="roles"]      { --accent: #5d6620; }   /* olive — who may do what */
:root[data-module="settings"]   { --accent: #6f655b; }   /* warm grey — configuration */
:root[data-module="home"]       { --accent: #b5764a; }

/* Where the accent is allowed to show. Deliberately few and always the same
   places, so the hue reads as "which room am I in" rather than decoration:
   the page name, the active tab, and a module's own section headings. */
.page-title { color: var(--accent-ink); }
/* Two different kinds of "selected" live here and they need opposite text colours.
   FILLED controls (view buttons, and any pressed filter pill, paint solid
   --accent) must be white, or you get dark terracotta on terracotta.
   TINTED and UNDERLINE ones keep the accent as their ink.
   `.db-fchip.on` sets its own white in the shared strip block above; it is named
   here too so the accent-per-module sweep cannot leave it behind. */
.db-fchip.on, .view-btn.active { color: #fff; }
.ws-tab.active, .set-tab.active { color: var(--accent-ink); border-bottom-color: var(--accent); }
.top-strip { box-shadow: inset 0 -2px 0 0 var(--accent-tint); }

/* ---------- Team: give the roster the screen ----------
   Measured at 428px: 438px of a 800px screen went on chrome before the grid — 55%.
   The legend alone was 80px of colour chips explaining a code the blocks already
   carry in words, and the instruction line repeated what the + cells show. */
@media (max-width: 700px) {
  /* The legend folds away. It is a key, not a control — read once, then noise. */
  .sch-legend {
    display: flex; gap: 6px; overflow-x: auto; flex-wrap: nowrap;
    scrollbar-width: none; padding-bottom: 2px;
  }
  .sch-legend::-webkit-scrollbar { display: none; }
  .sch-legend > * { flex: 0 0 auto; }
  #pageHr .ws-sub, .sch-wrap > .ws-sub { display: none; }
  .sch-branchbar { overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap; }
  .sch-branchbar::-webkit-scrollbar { display: none; }
  .sch-branchbar > * { flex: 0 0 auto; }
}

@media (max-width: 560px) {
  .head-actions .btn-word { display: none; }
  .head-actions .ws-btn, .head-actions .btn { min-width: 44px; padding-left: 12px; padding-right: 12px; }
}


/* ---------- shift blocks: a card per block ----------------------------------
   Replaces a seven-control flex row that could not fit and wrapped into a mess.
   Colour and mark are PICKERS now (the app's own swatches and drawn icons), not
   a browser colour well and a text box you were expected to type an emoji into. */
.sch-bcard {
  border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--card);
  padding: 12px 13px; display: flex; flex-direction: column; gap: 10px;
}
.sch-blocks { display: flex; flex-direction: column; gap: 10px; }
.sch-bcard-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
.sch-bdot {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: var(--r-md);
  background: var(--sw, var(--line)); display: grid; place-items: center;
  color: color-mix(in srgb, var(--ink) 78%, transparent); font-size: 15px;
}
.sch-bname { flex: 1 1 0; min-width: 0; font-weight: 600; }
.sch-boff { flex: 0 0 auto; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .07em; }
.sch-bgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.sch-bfield { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.sch-bfield > span {
  font-size: 11px; letter-spacing: .07em; text-transform: uppercase; font-weight: 700; color: var(--muted);
}
.sch-bfield > span i { font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 400; }
.sch-brow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sch-blabel {
  flex: 0 0 54px; font-size: 11px; letter-spacing: .07em; text-transform: uppercase;
  font-weight: 700; color: var(--muted);
}
.sch-swrap { display: flex; gap: 6px; flex-wrap: wrap; }
.sch-sw {
  width: 26px; height: 26px; border-radius: var(--r-pill); background: var(--sw);
  border: 1px solid var(--hairline); cursor: pointer; padding: 0;
}
.sch-sw.on { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent); }
.sch-mark {
  width: 30px; height: 30px; border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--card); color: var(--ink-2); cursor: pointer; display: grid; place-items: center; padding: 0;
}
.sch-mark.on { border-color: var(--accent); color: var(--accent); background: var(--accent-tint); }
.sch-bfoot { display: flex; gap: 8px; align-items: center; }
.sch-bfoot .danger { color: var(--red); }
.sch-newblock .sch-bcard.is-new { border-style: solid; background: color-mix(in srgb, var(--bg) 55%, #fff); }

@media (pointer: coarse) {
  .sch-sw { width: 34px; height: 34px; }
  .sch-mark { width: 44px; height: 44px; }
  .sch-bgrid { grid-template-columns: 1fr; }
  .sch-bfoot .ws-btn { flex: 1; min-height: 44px; }
}


/* ---------- the one date picker --------------------------------------------
   Staff tasks had grown a proper calendar — a month grid, a dot per day showing
   how busy it is, a legend, a way back to today. The board, Dispatch and My work
   were still opening the browser's own date control: same job, two different
   things to learn, and the native one cannot show you which days are busy.
   This is that calendar, lifted out and themed from --accent so it belongs to
   whichever module opened it. Fixed-positioned because it is appended to <body>
   and must escape the app bar's overflow. */
.tfcal {
  position: fixed; z-index: 120;
  background: var(--card); border: 1px solid var(--accent-line);
  border-radius: var(--r-lg); box-shadow: 0 14px 38px rgba(43, 38, 34, .20);
  padding: 12px; width: 306px; max-width: calc(100vw - 16px);
}
/* ONE DAY, OR A RANGE. Two words, not two controls — the same calendar answers
   both, and the switch says which question is being asked. */
.tfcal-modes {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px; margin-bottom: 10px;
  padding: 2px; background: var(--taupe-soft); border-radius: var(--r-sm);
}
.tfcal-mode {
  border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--ink-2); padding: 6px 8px; border-radius: calc(var(--r-sm) - 2px); cursor: pointer;
}
.tfcal-mode.on { background: var(--card); color: var(--accent-ink); box-shadow: 0 1px 2px rgba(43, 38, 34, .10); }

/* Type a date instead of paging to it. Sits above the month, because it is the
   faster of the two ways in and the one a keyboard reaches first. */
.tfcal-type { margin-bottom: 10px; }
.tfcal-in {
  width: 100%; padding: 8px 10px; font: inherit; font-size: 15px;
  border: 1px solid var(--hairline); border-radius: var(--r-sm);
  background: var(--card); color: var(--ink); font-variant-numeric: tabular-nums;
}
.tfcal-in:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
.tfcal-in::placeholder { color: var(--muted); font-variant-numeric: normal; }
/* The reading, spelled out. 15/9 and 9/15 look alike and mean months apart, so the
   answer is never left to be inferred from a highlighted square. */
.tfcal-echo { margin-top: 5px; min-height: 15px; font-size: 12px; color: var(--accent-ink); font-weight: 600; }
.tfcal-echo.bad { color: var(--muted); font-weight: 500; }

.tfcal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 8px; color: var(--accent-ink); font-size: 15px;
}
.tfcal-nav {
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink-2);
  border-radius: var(--r-sm); width: 30px; height: 30px; cursor: pointer; font-size: 15px;
}
.tfcal-nav:hover { border-color: var(--accent); color: var(--accent-ink); }
.tfcal-dow {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
  font-size: 11px; color: var(--muted); text-align: center; margin-bottom: 4px;
}
.tfcal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.tfcal-day {
  position: relative; aspect-ratio: 1; max-height: 40px; border: 1px solid transparent;
  background: none; border-radius: var(--r-sm); cursor: pointer; font: inherit;
  display: flex; align-items: center; justify-content: center;
  font-size: 13.5px; color: var(--ink);
}
.tfcal-day:hover { background: var(--accent-soft); }
.tfcal-day.muted { opacity: .32; }
.tfcal-day.today { font-weight: 800; box-shadow: inset 0 0 0 1.5px var(--accent); }
.tfcal-day.l1 { background: var(--accent-tint); }
.tfcal-day.l2 { background: var(--accent-soft); }
.tfcal-day.l3 { background: var(--accent-line); }
.tfcal-day.sel,
.tfcal-day.sel.l1, .tfcal-day.sel.l2, .tfcal-day.sel.l3 { background: var(--accent-fill); color: #fff; }
.tfcal-day.sel .tfcal-dot { background: #fff; }
.tfcal-day.in-range { box-shadow: inset 0 -2px 0 var(--accent); }
.tfcal-day.in-range.today { box-shadow: inset 0 0 0 1.5px var(--accent), inset 0 -2px 0 var(--accent); }

/* A SPAN IS ONE SHAPE, not two lit squares with shading between them. The middle
   is a continuous band (negative margins close the 2px grid gap) and the two ends
   cap it, so a range reads at a glance and you can see where it starts and stops. */
.tfcal.picking-range .tfcal-day.in-range {
  background: var(--accent-tint); box-shadow: none;
  border-radius: 0; margin: 0 -2px; padding: 0 2px;
}
.tfcal.picking-range .tfcal-day.in-range.today { box-shadow: inset 0 0 0 1.5px var(--accent); }
.tfcal.picking-range .tfcal-day.is-start { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.tfcal.picking-range .tfcal-day.is-end { border-top-left-radius: 0; border-bottom-left-radius: 0; }
/* The band has to reach the caps, or a 2px gap shows either side of the ends. */
.tfcal.picking-range .tfcal-day.is-start,
.tfcal.picking-range .tfcal-day.is-end { position: relative; }
.tfcal.picking-range .tfcal-day.is-start::after,
.tfcal.picking-range .tfcal-day.is-end::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 2px;
  background: var(--accent-tint); z-index: -1;
}
.tfcal.picking-range .tfcal-day.is-start::after { right: -2px; }
.tfcal.picking-range .tfcal-day.is-end::after { left: -2px; }

/* The spans worth one tap. Wrapped chips rather than a list — they are shortcuts
   beside the calendar, not a menu that replaces it. */
.tfcal-quick {
  display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--hairline);
}
.tfcal-quick.hidden { display: none; }
.tfcal-qk {
  border: 1px solid var(--hairline); background: var(--card); color: var(--ink-2);
  font: inherit; font-size: 12px; padding: 5px 9px; border-radius: var(--r-pill); cursor: pointer;
}
.tfcal-qk:hover { border-color: var(--accent); color: var(--accent-ink); background: var(--accent-tint); }
.tfcal-dot { position: absolute; bottom: 4px; width: 4px; height: 4px; border-radius: 50%; background: var(--accent); }
.tfcal-foot {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--hairline);
  font-size: 11px; color: var(--ink-2);
}
.tfcal-key { display: inline-flex; align-items: center; gap: 4px; }
.tfcal-key i { width: 11px; height: 11px; border-radius: 6px; display: inline-block; }
.tfcal-key i.l1 { background: var(--accent-tint); }
.tfcal-key i.l2 { background: var(--accent-soft); }
.tfcal-key i.l3 { background: var(--accent-line); }
.tfcal-hint { margin-left: auto; }
.tfcal-today {
  border: 1px solid var(--hairline); background: var(--card); color: var(--accent-ink);
  border-radius: var(--r-pill); padding: 5px 12px; font: inherit; font-size: 13.5px;
  font-weight: 600; cursor: pointer;
}
.tfcal-today:hover { border-color: var(--accent); background: var(--accent-tint); }

/* The pill is a button now, so it needs the affordances a label never had. */
.date-pick { gap: 7px; }
.date-pick svg, .date-pick .ico-slot { opacity: .55; flex: 0 0 auto; }
.date-pick:hover svg, .date-pick:hover .ico-slot { opacity: .9; }

@media (pointer: coarse) {
  .tfcal { padding: 14px; }
  .tfcal-day { max-height: 46px; font-size: 15px; }
  .tfcal-nav { width: 40px; height: 40px; }
  .tfcal-today { min-height: 40px; }
  /* A thumb has to be able to land in the typing box, not just a mouse pointer.
     (The 17px floor above already stops iOS zooming into it.) */
  .tfcal-in { min-height: 44px; }
  .tfcal-mode { min-height: 40px; }
  /* The quick spans ARE the range control on a phone: two precise taps on a
     6mm square is not how anybody picks "last week" one-handed. */
  .tfcal-qk { min-height: 38px; font-size: 13px; padding: 6px 12px; }
}


/* What the driver carried and what they collected, on the job card. Money and
   valuables are the two things people argue about afterwards, so they sit above
   the tasks rather than buried among the photos. */
.jc-handover {
  display: flex; gap: 10px; align-items: flex-start;
  border: 1px solid color-mix(in srgb, var(--amber) 34%, transparent);
  background: color-mix(in srgb, var(--amber) 8%, #fff);
  border-radius: var(--r-md); padding: 10px 12px; margin: 0 0 12px;
}
.jc-handover > svg { flex: 0 0 auto; color: color-mix(in srgb, var(--amber) 85%, #2a1706); margin-top: 1px; }
.jc-hand-list { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.jc-hand-item { font-size: 13.5px; color: var(--ink); line-height: 1.45; }
.jc-hand-item b { font-weight: 700; }
.jc-hand-quiet { color: var(--muted); font-weight: 500; }


/* A view that cannot honestly draw the chosen period. Dimmed and unpressable, with
   a title that says why — rather than letting somebody pick "Hours" over a month
   and wonder what they broke. */
.view-btn.is-blocked { opacity: .38; cursor: not-allowed; }
.view-btn.is-blocked:hover { border-color: var(--line); background: var(--card); }


/* ---------- sheets on a phone: nothing cut off, no naked scroll tracks ---------
   The owner's photos showed "Assign multi steps" sliced in half and grey scroll
   tracks down the side and along the bottom. The height was the cause (see the
   dvh notes above); these two make the rest of it behave. */

/* The title and the way out stay put while the content moves. The wizard's footer
   was already pinned; its header was not, so on a long step the name of what you
   were filling in scrolled away. */
.wiz-head { position: sticky; top: 0; z-index: 3; background: var(--card); }
.modal .modal-head, .sch-modal-head { position: sticky; top: 0; z-index: 3; background: var(--card); }

/* A scrollable region does not need to advertise itself. The content still scrolls
   exactly as before — the grey track is simply not drawn. */
.wiz-body, .db-modal-body, .modal-body, .sch-modal-body, .mwr-sheet-body {
  scrollbar-width: none; -ms-overflow-style: none;
}
.wiz-body::-webkit-scrollbar, .db-modal-body::-webkit-scrollbar,
.modal-body::-webkit-scrollbar, .sch-modal-body::-webkit-scrollbar,
.mwr-sheet-body::-webkit-scrollbar { width: 0; height: 0; }

/* `overflow-x: auto` on a column that only ever scrolls DOWN reserves a horizontal
   track for content that is never wider than the box. That is the grey bar along
   the bottom of the sheet in the photo. */
.wiz-body, .db-modal-body { overflow-x: hidden; }


/* ---------- the wizard should feel like being asked, not filled in -----------
   "What kind of job is this?" decides half of what follows, so it is a choice you
   make rather than a line you scroll past in a dropdown. */
.wiz-field-wide { grid-column: 1 / -1; }
.wiz-picks { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 8px; }
.wiz-pick {
  display: flex; align-items: flex-start; gap: 10px; text-align: left;
  border: 1px solid var(--line); background: var(--card); border-radius: var(--r-md);
  padding: 12px; cursor: pointer; font: inherit; color: var(--ink);
  transition: border-color .16s ease, background .16s ease, transform .12s ease;
}
.wiz-pick:hover { border-color: var(--accent); }
.wiz-pick:active { transform: var(--press); }
.wiz-pick.on { border-color: var(--accent); background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent); }
.wiz-pick-ico { flex: 0 0 auto; color: var(--accent-ink); display: grid; place-items: center; margin-top: 1px; }
.wiz-pick-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.wiz-pick-txt b { font-weight: 600; font-size: 13.5px; }
.wiz-pick-txt small { color: var(--muted); font-size: 11px; line-height: 1.35; }

@media (pointer: coarse) {
  .wiz-picks { grid-template-columns: 1fr 1fr; }
  .wiz-pick { min-height: 64px; }
}

/* ============================================================
   ADD ORDER OR TASK — step 0, and the two new forms
   Added 2026-08-03 with public/wizard.js. Same grammar as the rest: hairlines
   not boxes, one accent, weight and space doing the work colour used to.
   ============================================================ */

/* ---- step 0: what are you adding? ---- */
/* Four rows, not four tiles: they are a LIST OF CHOICES and reading down one
   column is faster than scanning a grid. Each is a full-width target, which is
   also the only shape that survives a phone. */
.wiz-forms { display: flex; flex-direction: column; gap: 8px; }
.wiz-form {
  display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
  border: 1px solid var(--line); background: var(--card); border-radius: var(--r-md);
  padding: 15px 16px; cursor: pointer; font: inherit; color: var(--ink); min-height: 68px;
  transition: border-color .16s ease, background .16s ease, transform .12s ease;
}
.wiz-form:hover { border-color: var(--accent); background: var(--accent-tint); }
.wiz-form:active { transform: var(--press); }
.wiz-form:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wiz-form-n {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
  background: var(--taupe-soft); color: var(--muted); border: 1px solid var(--line);
}
.wiz-form:hover .wiz-form-n { background: var(--accent); color: #fff; border-color: var(--accent); }
.wiz-form-ico { flex: 0 0 auto; color: var(--accent-ink); display: grid; place-items: center; }
.wiz-form-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.wiz-form-txt b { font-weight: 600; font-size: 15.5px; letter-spacing: -.01em; }
.wiz-form-txt small { color: var(--muted); font-size: 12.5px; line-height: 1.4; }
.wiz-form-go { flex: 0 0 auto; color: var(--muted); font-size: 22px; line-height: 1; }
.wiz-form:hover .wiz-form-go { color: var(--accent-ink); }
.wiz-progress.hidden { display: none; }

/* ---- a stop: where, and what happens there ---- */
.wiz-stops { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.wiz-stop {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--r-md); padding: 12px 14px 4px; background: var(--card);
}
.wiz-stop-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 9px; }
.wiz-stop-n { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--accent-ink); }
.wiz-stop-x {
  border: none; background: none; color: var(--muted); cursor: pointer;
  padding: 6px; border-radius: var(--r-sm); display: grid; place-items: center;
  min-width: 32px; min-height: 32px;
}
.wiz-stop-x:hover { color: var(--red); background: var(--taupe-soft); }
.wiz-stop-addr { margin-top: 5px; font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
/* The DETAILED address — flat, floor, the landmark to turn at — sitting directly
   under the place it belongs to. Quieter than the place: it is the second half of
   the answer, and it must not compete with the name of where you are going. */
.wiz-stop-detail { margin-top: 8px; resize: vertical; min-height: 54px; line-height: 1.45; }
.wiz-addstop { width: 100%; justify-content: center; border-style: solid; margin-bottom: 6px; }

/* ---- staff pick up: the two legs ---- */
.wiz-leg { border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px 14px; margin-bottom: 10px; background: var(--card); }
.wiz-leg.off { background: var(--taupe-soft); }
.wiz-leg-head { display: flex; align-items: center; gap: 10px; cursor: pointer; margin: 0; flex-wrap: wrap; }
.wiz-leg-head input { width: 20px; height: 20px; accent-color: var(--accent); flex: 0 0 auto; }
.wiz-leg-title { font-size: 15px; font-weight: 600; display: inline-flex; align-items: center; gap: 7px; color: var(--ink); }
.wiz-leg-sub { font-size: 12.5px; color: var(--muted); }
.wiz-leg .wiz-grid { margin-top: 12px; }

/* ---- a quiet aside inside the wizard ---- */
.wiz-note {
  border-left: 3px solid var(--line); background: var(--taupe-soft);
  border-radius: var(--r-sm); padding: 10px 12px; margin: 12px 0;
  font-size: 13.5px; color: var(--ink); line-height: 1.5;
}
.wiz-note.ok { border-left-color: var(--green); }
.wiz-note.warn { border-left-color: var(--amber, #c98a2e); }
.wiz-note-sub { margin-top: 5px; font-size: 12.5px; color: var(--muted); }

/* ---- one errand, several cards, on the board ---- */
.c-jobref { font-size: 11px; color: var(--muted); letter-spacing: .02em; }
.jobgroup {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--taupe-soft); padding: 4px; margin-bottom: 10px;
}
.jg-head { display: flex; align-items: center; gap: 9px; padding: 8px 10px 9px; flex-wrap: wrap; }
.jg-ico { color: var(--accent-ink); display: grid; place-items: center; }
.jg-name { font-weight: 600; font-size: 14px; color: var(--ink); }
/* The reference is pushed to the far end now that the stop count that used to sit
   there is gone — a head of two things reads better than two words huddled left. */
.jg-ref { font-size: 11px; color: var(--muted); letter-spacing: .02em; margin-left: auto; }
.jg-body { display: flex; flex-direction: column; gap: 6px; }

/* ---- the internal-work fold: one line per form, per area ----
   Deliberately the quietest thing in the section. It is not an order and it is not
   competing with one — no left stripe, no fill, hairline only. Shut, it is a single
   grid cell an order can stand beside; open, it takes the full width, because the
   errands inside are titled blocks and 290px would crush them. */
.zstaff { border: 1px solid var(--line); border-radius: var(--r-md); background: var(--card); align-self: start; }
/* Only the Areas board lays its section out as a grid. In the Stage and Branch
   views the fold sits in a one-card-wide flex column, where spanning is meaningless
   and `align-self: start` would shrink it — so both are scoped, not global. */
.zsec-body > .zstaff.open { grid-column: 1 / -1; }
.zone-body > .zstaff { align-self: auto; flex: none; margin-bottom: 8px; }
.zone-body > .zstaff .zst-body { grid-template-columns: 1fr; }
.zst-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 10px 12px; background: none; border: 0; cursor: pointer;
  text-align: left; font: inherit; color: var(--ink); border-radius: var(--r-md);
}
.zst-head:hover { background: var(--hairline-soft); }
.zst-ico { color: var(--muted); display: grid; place-items: center; }
.zst-name { font-weight: 600; font-size: 13.5px; }
/* The time, the stop count and the "N nobody yet" warning that used to sit between
   the name and the arrow were struck out on 2026-08-04. The arrow inherits the
   margin that pushed them apart. */
.zst-chev { color: var(--muted); font-size: 13px; line-height: 1; margin-left: auto; transition: transform .18s ease; }
.zstaff.open .zst-chev { transform: rotate(180deg); }
.zst-body { display: none; }
.zstaff.open .zst-body {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 10px; padding: 0 10px 4px;
}
/* The blocks inside carry the fold's own bottom padding instead of their margin. */
.zstaff.open .zst-body .jobgroup { margin-bottom: 6px; }

@media (max-width: 760px) {
  .wiz-form { padding: 14px; min-height: 72px; }
  .wiz-form-txt b { font-size: 15px; }
}
/* Fingers, not mice: the same rule the rest of the app follows — nothing you tap
   is smaller than 44px, and no field small enough to make iOS zoom the page. */
@media (pointer: coarse) {
  .wiz-form { min-height: 76px; }
  .wiz-stop-x { min-width: 44px; min-height: 44px; }
  .wiz-leg-head input { width: 24px; height: 24px; }
  .wiz-addstop { min-height: 48px; }
  /* The wizard's own close button has always been a 26px ×. It is the control
     somebody reaches for one-handed while holding a phone, and it sits in the
     corner where a mis-tap lands on the page behind it. */
  .wiz-x { min-width: 44px; min-height: 44px; display: grid; place-items: center; }
  /* The lorry beside a name on the roster opens that person's transport. */
  button.sch-lift { min-width: 44px; min-height: 44px; }
}

/* ============================================================
   TEAM — transport on a person, and the staff pick-up sheet
   ============================================================ */
.sch-lift {
  border: none; background: none; padding: 4px; margin-left: 4px; cursor: pointer;
  color: var(--line-strong, #cfc7bf); display: inline-grid; place-items: center;
  border-radius: var(--r-sm); vertical-align: middle;
}
.sch-lift.on { color: var(--accent-ink); }
.sch-lift.gap { color: var(--red); }
.sch-lift:hover { background: var(--taupe-soft); color: var(--accent-ink); }
button.sch-lift { min-width: 30px; min-height: 30px; }

.sch-gap {
  display: flex; flex-direction: column; gap: 4px;
  border-left: 3px solid var(--amber, #c98a2e); background: var(--taupe-soft);
  border-radius: var(--r-sm); padding: 10px 13px; margin-bottom: 14px; font-size: 13.5px;
}
.sch-gap b { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.sch-gap small { color: var(--muted); font-size: 12.5px; }

.sch-pgroup { margin-bottom: 18px; }
.sch-pday { display: flex; align-items: baseline; gap: 10px; font-size: 15px; font-weight: 600; color: var(--ink); margin-bottom: 8px; }
.sch-pcount { font-size: 12.5px; font-weight: 400; color: var(--muted); }
.sch-ptable { border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; background: var(--card); }
.sch-prow {
  display: grid; grid-template-columns: 74px 1.2fr 82px 1.6fr 1.6fr 1.1fr 92px;
  gap: 10px; align-items: center; padding: 11px 13px; font-size: 13.5px;
  border-top: 1px solid var(--line);
}
/* ---- the three arrangements of the sheet ----
   Whatever the sheet is grouped BY stops being a column — repeating "Afsal" down
   every row of Afsal's own round is noise — and the day becomes a column as soon
   as a group can span days. The template changes with it, so the header and the
   rows cannot fall out of step. Same three views the PDF and the spreadsheet use
   (groupPickups in lib/modules/hr.js). */
.sch-pv-driver .sch-prow { grid-template-columns: 96px 74px 1.2fr 82px 1.6fr 1.6fr 92px; }
.sch-pv-staff  .sch-prow { grid-template-columns: 96px 74px 82px 1.6fr 1.6fr 1.1fr 92px; }
.sch-pdaycell { font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.sch-pviews { margin: 0 0 12px; }
.sch-prow:first-child { border-top: none; }
.sch-phead { background: var(--taupe-soft); font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.sch-prow.is-done { color: var(--muted); }
.sch-prow.is-live { background: var(--accent-tint); }
.sch-ptime { font-weight: 600; font-variant-numeric: tabular-nums; }
.sch-pwho { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sch-pwho small { color: var(--muted); font-size: 11px; }
.sch-pplace { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sch-pleg { font-size: 11.5px; font-weight: 600; padding: 3px 8px; border-radius: var(--r-pill); background: var(--taupe-soft); color: var(--muted); white-space: nowrap; }
.sch-pleg.work { background: var(--accent-tint); color: var(--accent-ink); }

/* The transport dialog */
.sch-field { margin-bottom: 13px; position: relative; }
.sch-field > label { display: block; font-size: 13.5px; font-weight: 600; margin-bottom: 5px; }
.sch-input { width: 100%; padding: 11px 12px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 16px; font-family: inherit; background: #fdfbfa; color: var(--ink); }
.sch-input:focus { outline: 2px solid var(--primary); border-color: var(--primary); }
.sch-hint { margin-top: 5px; font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
.sch-check { display: flex; align-items: center; gap: 10px; padding: 12px 0; font-size: 15px; cursor: pointer; }
.sch-check input { width: 22px; height: 22px; accent-color: var(--accent); }
.sch-modal-sub { color: var(--muted); font-size: 13.5px; margin: 2px 0 12px; line-height: 1.5; }
.sch-modal-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
/* Switched off, not hidden: the fields stay visible so it is obvious what turning
   it on would ask for, and nothing jumps when it is ticked. */
#trBody.is-off { opacity: .45; pointer-events: none; }

@media (max-width: 860px) {
  .sch-prow { grid-template-columns: 64px 1fr 74px; row-gap: 4px; }
  .sch-prow > span:nth-child(n+4) { grid-column: 2 / -1; }
  .sch-phead { display: none; }
  .sch-pplace::before { content: '→ '; color: var(--line-strong, #cfc7bf); }
}

/* ---- a task's own reference, who made it, and what has happened to it ----
   Added 2026-08-03. The history rows were always being written and never read;
   these are the styles for finally showing them. Quiet by default — history is
   something you go looking for, not something that should shout over the job. */
.td-ref { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; letter-spacing: .02em; color: var(--muted); }
.td-history { display: flex; flex-direction: column; gap: 6px; max-height: 190px; overflow-y: auto; }
.td-hrow { display: grid; grid-template-columns: 96px 92px 1fr; gap: 8px; font-size: 12.5px; line-height: 1.45; align-items: baseline; }
.td-hwhen { color: var(--muted); font-variant-numeric: tabular-nums; }
.td-hwho { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; }
.td-hwhat { color: var(--ink); }
/* Something that happened to the JOB rather than to this task: still this task's
   story, but one step further away, so it reads one step quieter. */
.td-hrow.is-order .td-hwhat { color: var(--muted); }
@media (max-width: 620px) {
  .td-hrow { grid-template-columns: 1fr; gap: 1px; padding-bottom: 4px; }
  .td-hwho { font-size: 12px; }
}

/* ============================================================
   ADD ORDER OR TASK — the page, and its drafts
   Added 2026-08-04. The wizard is the same component as before; these are the
   differences between living in a dialog and living on a screen.
   ============================================================ */
.nt-wrap { display: flex; flex-direction: column; gap: 22px; max-width: 780px; margin: 0 auto; }

/* On a page there is no backdrop to float above and no window to fit inside: the
   box is the content, it grows with what is in it, and the page scrolls. */
.wiz-modal.wiz-page {
  max-width: 100%; max-height: none; height: auto;
  box-shadow: none; border: 1px solid var(--line);
}
.wiz-modal.wiz-page .wiz-body { overflow: visible; max-height: none; }
.wiz-modal.wiz-page .wiz-head { padding-top: 16px; }
/* Nothing is lost by leaving, so the × is quiet rather than a warning. */
.wiz-modal.wiz-page .wiz-x { font-size: 22px; opacity: .55; }
.wiz-modal.wiz-page .wiz-x:hover { opacity: 1; }

/* "Saved 14:32" — reassurance, not news. It sits in the header where a person
   glances when they wonder whether it is safe to walk away. */
.wiz-head { gap: 10px; }
.wiz-saved { margin-left: auto; margin-right: 4px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.wiz-saved.warn { color: var(--amber); }

.nt-drafts { border-top: 1px solid var(--line); padding-top: 16px; }
.nt-drafts-h { display: flex; align-items: center; gap: 8px; font-size: 15px; margin: 0 0 4px; font-weight: 600; }
.nt-drafts-sub { color: var(--muted); font-size: 13px; margin: 0 0 12px; line-height: 1.5; }
.nt-draft {
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 11px 13px; margin-bottom: 8px; background: var(--card);
}
.nt-draft-ico { color: var(--accent-ink); display: grid; place-items: center; flex: 0 0 auto; }
.nt-draft-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.nt-draft-txt b { font-weight: 600; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nt-draft-txt small { color: var(--muted); font-size: 12px; }
.nt-draft-x { color: var(--muted); }
.nt-draft-x:hover { color: var(--red); border-color: var(--red); }
.nt-drafts-err { margin-top: 12px; }

/* ---- people on a staff run ---- */
.wiz-people { display: flex; flex-direction: column; gap: 8px; margin: 0 0 14px; }
.wiz-empty {
  color: var(--muted); font-size: 13.5px; text-align: center;
  padding: 14px; background: var(--taupe-soft); border-radius: var(--r-md); margin-bottom: 14px;
}
.wiz-person {
  display: flex; align-items: center; gap: 11px;
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 10px 12px; background: var(--card);
}
.wiz-person-ico { color: var(--accent-ink); display: grid; place-items: center; flex: 0 0 auto; }
.wiz-person-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.wiz-person-txt b { font-weight: 600; font-size: 14.5px; }
.wiz-person-txt small { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wiz-person-x {
  border: none; background: none; color: var(--muted); cursor: pointer;
  padding: 6px; border-radius: var(--r-sm); display: grid; place-items: center; min-width: 32px; min-height: 32px;
}
.wiz-person-x:hover { color: var(--red); background: var(--taupe-soft); }

/* One journey, one line: on/off, which way, and the time. */
.wiz-legrow {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 0; border-top: 1px solid var(--taupe-soft); cursor: pointer; margin: 0;
}
.wiz-legrow:first-of-type { border-top: 0; }
.wiz-legrow.off { opacity: .5; }
.wiz-legrow input[type=checkbox] { width: 20px; height: 20px; accent-color: var(--accent); flex: 0 0 auto; }
.wiz-legrow-name { flex: 1; font-size: 14px; font-weight: 500; }
.wiz-legrow-time { flex: 0 0 auto; width: 120px; padding: 7px 9px; }
.wiz-stop-note { font-size: 12px; color: var(--muted); font-weight: 400; letter-spacing: 0; text-transform: none; }
/* The address only appears once "Other location" is chosen — it belongs to that
   answer, so it is indented under it rather than floating beside the select. */
.wiz-other { margin-top: 8px; }

@media (max-width: 760px) {
  .wiz-legrow-time { width: 108px; }
  .nt-draft { flex-wrap: wrap; }
  .nt-draft-txt { flex: 1 1 60%; }
}
@media (pointer: coarse) {
  .wiz-person-x { min-width: 44px; min-height: 44px; }
  .wiz-legrow input[type=checkbox] { width: 24px; height: 24px; }
  .nt-draft { min-height: 60px; }
}

/* ---- choosing a person: name, role, add ----
   Three controls on one line, and the name gets whatever is left. The old markup
   borrowed the job card's .ct-extra-row, whose flex rule only matches .ct-input —
   so in the wizard, where the fields are .wiz-input, every control fell to its own
   full-width row and the form looked broken. Named for what it is instead. */
.wiz-crewrow { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }
.wiz-crew-name { flex: 1 1 200px; min-width: 0; }
.wiz-crew-role { flex: 0 1 auto; width: auto; min-width: 140px; }
.wiz-crew-add { flex: 0 0 auto; white-space: nowrap; }
/* The suggestion list belongs to the field, so it must overlay what follows
   rather than push it down — same rule the address fields already follow. */
.wiz-pick { position: relative; }
.wiz-pick .suggestions { top: 100%; }
@media (max-width: 560px) {
  .wiz-crew-role, .wiz-crew-add { flex: 1 1 100%; width: 100%; }
}

/* ============================================================
   WAITING — skeletons, not the word "Loading"
   Owner, 2026-08-04: "the system is a bit heavy when I tested on low internet
   speed with no loading nice motions". A screen that says "Loading…" in 13px grey
   looks broken on a slow connection; one that shows the SHAPE of what is coming
   reads as busy, and the shape is honest because it is what actually arrives.
   The animation is one shared shimmer, and it stops dead for anybody who asked
   their system for less motion.
   ============================================================ */
.sk-line, .sk-row, .sk-head, .sk-card {
  background: linear-gradient(90deg,
    var(--taupe-soft) 25%,
    color-mix(in srgb, var(--taupe-soft) 55%, #fff) 37%,
    var(--taupe-soft) 63%);
  background-size: 400% 100%;
  animation: sk-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
}
@keyframes sk-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) {
  .sk-line, .sk-row, .sk-head, .sk-card { animation: none; }
}
.sk-cols { display: flex; gap: 10px; padding: 10px 12px; overflow: hidden; }
.sk-col { flex: 0 0 288px; display: flex; flex-direction: column; gap: 8px; }
.sk-head { height: 34px; border-radius: var(--r-md); }
.sk-card { height: auto; padding: 10px 11px; display: flex; flex-direction: column; gap: 7px; animation: none; background: var(--card); border: 1px solid var(--line); }
.sk-card .sk-line { height: 10px; }
.sk-card .sk-line:first-child { width: 55%; }
.sk-card .sk-line:last-child { width: 80%; }
.sk-page { display: flex; flex-direction: column; gap: 10px; padding: 16px; max-width: 900px; }
.sk-row { height: 52px; }
.sk-line.sk-title { height: 20px; width: 220px; margin-bottom: 6px; }

/* The toast, while something is actually happening. A message that is going to
   stay up for twenty seconds needs to look like work in progress rather than an
   announcement that got stuck. */
/* The spinner that sat here is the flower now, in the toast's icon (step 8). */
#toast.busy::before { content: none; }
@keyframes sk-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { #toast.busy::before { animation: none; } }

/* ============================================================
   THE WORK BOARD, TIGHTENED  (2026-08-04)

   Two complaints, one cause. Owner: "workboard cross is not scrolling and bad
   UI" and "workboard blocks are very big and not tidy visually looks messy".

   SCROLLING. The board was `min-height: 100vh - chrome` inside a page that
   scrolls vertically. Its horizontal scrollbar therefore sat at the bottom of a
   very tall element — BELOW the fold — so the columns visibly ran off the right
   of the screen with no way to reach the rest without first scrolling to the
   bottom of the page. It is now a fixed-height region that scrolls INSIDE
   itself: the horizontal bar is always on screen, and each column scrolls
   vertically on its own, which is what a kanban is supposed to do.

   DENSITY. Every measurement below is smaller than it was — column padding,
   card padding, the gaps between the lines of a card, the address clamp (two
   lines to one). Nothing was removed from a card; it simply stopped being
   spaced like a poster. A dispatcher reads a column of these top to bottom, and
   fitting eight on a screen instead of four is the whole job.
   ============================================================ */

/* ---- the across board scrolls inside itself ---- */
.board.flow-across, #stages.flow-across, #hours.flow-across {
  /* fitBoardHeight() in app.js sets the real height — measured, because the
     chrome above this varies with the window. This is only the floor it starts
     from, and what a browser with JS still loading shows. */
  min-height: 320px;
  max-height: none;
  overflow-x: auto; overflow-y: hidden;
  padding: 10px 12px 12px;
  gap: 10px;
  align-items: stretch;
  scroll-snap-type: x proximity;
}
/* A column is a full-height card whose BODY scrolls, so its header stays put
   while you read down it. */
.board.flow-across .zone-col,
#stages.flow-across .zone-col,
#hours.flow-across .zone-col {
  min-height: 0; height: 100%;
  display: flex; flex-direction: column;
  scroll-snap-align: start;
}
.board.flow-across .zone-head,
#stages.flow-across .zone-head,
#hours.flow-across .zone-head { flex: 0 0 auto; }
.board.flow-across .zone-body,
#stages.flow-across .zone-body,
#hours.flow-across .zone-body {
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior-y: contain;
  scrollbar-width: thin; scrollbar-color: var(--gold) transparent;
}
/* The horizontal bar is the one that has to be noticed, so it is not hairline-thin. */
.board.flow-across::-webkit-scrollbar,
#stages.flow-across::-webkit-scrollbar { height: 12px; }
.board.flow-across .zone-body::-webkit-scrollbar,
#stages.flow-across .zone-body::-webkit-scrollbar { width: 8px; }
.board.flow-across .zone-body::-webkit-scrollbar-thumb,
#stages.flow-across .zone-body::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--gold) 70%, transparent); border-radius: var(--r-pill);
}

/* Zone sections (the stacked "board-stacked" layout) keep flowing down the page
   — that one was never the problem. */
.board.board-stacked { height: auto; overflow-x: visible; }

/* ---- columns ---- */
.zone-col { min-height: 0; }
.board .zone-col, #stages .zone-col { flex: 0 0 288px; max-width: 288px; }
.zone-head { padding: 8px 12px; font-size: 13.5px; font-weight: 700; letter-spacing: .01em; }
.zone-head .count { font-size: 12px; padding: 1px 8px; }
.zone-body { padding: 8px; display: flex; flex-direction: column; gap: 8px; }

/* ---- cards ---- */
.card-entry { padding: 9px 11px; border-radius: var(--r-sm); }
.c-eyebrow { gap: 6px; margin-bottom: 2px; }
.c-ref { font-size: 12px; }
.c-name { font-size: 14px; line-height: 1.3; }
/* One line, not two. An address that needs two lines needs the card opening. */
.c-where { -webkit-line-clamp: 1; margin-top: 2px; font-size: 12px; }
.c-facts { margin-top: 6px; gap: 4px; }
.c-fact { font-size: 10.5px; padding: 2px 7px; }
.c-detail { margin-top: 4px; font-size: 10.5px; }
.c-thumbs { margin-top: 5px; }
.c-thumbs img { width: 34px; height: 34px; }
.c-quick { margin-top: 7px; }
.c-go { padding: 6px 10px; font-size: 12px; }

/* One errand's stops, grouped: tighter inside a column than it was on a page. */
.jobgroup { padding: 3px; margin-bottom: 8px; }
.jg-head { padding: 6px 8px 7px; gap: 7px; }
.jg-name { font-size: 12.5px; }
.jg-ref { font-size: 10.5px; }
.jg-body { gap: 5px; }

/* A phone gets one column at a time and the page scrolls, so none of the
   internal-scrolling machinery above applies — it would trap the board in a
   short box with two scrollbars. */
@media (max-width: 760px) {
  .board.flow-across, #stages.flow-across, #hours.flow-across {
    height: auto; overflow-x: visible; overflow-y: visible; display: block;
  }
  .board.flow-across .zone-col, #stages.flow-across .zone-col,
  .board.flow-across .zone-body, #stages.flow-across .zone-body {
    height: auto; overflow: visible; max-width: none;
  }
}
/* The phone at the end of the address line — present, quiet, and not a whole
   line of its own on every card of the board. */
.c-tel { color: var(--muted); font-variant-numeric: tabular-nums; margin-left: 7px; }

/* The Work board's fold of internal work is a real tap target on a phone — it was
   39px, and 44 is the floor. Added 2026-08-04 after a measured sweep found the
   folds built that same day were the ONLY controls in the app the touch layer had
   not been extended to. */
@media (pointer: coarse) {
  .zst-head { min-height: 44px; }
}

/* ---------- Cars: the filter row ----------
   Which DAYS lives in the app bar; which CARS lives here, in the page, sticking
   under the bar as you scroll. Same split and same behaviour as My work's chips.
   Putting all five groups in the bar wrapped it to five rows at 375px — about
   200px of sticky chrome before a single car was visible. */
.fl-filters {
  position: sticky; top: 0; z-index: 3;
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  background: linear-gradient(var(--bg) 78%, transparent);
  padding: 4px 0 10px; margin: 0 0 4px;
}
.fl-filters .fl-search { flex: 1 1 190px; }
.fl-filters .ws-seg { flex: 0 0 auto; }

/* ---------- Cars: what a car did in the chosen days ----------
   The date pill has to change something you can SEE on the page, or it reads as
   broken. This line is that: one card, one window, in the words of the period. */
.fl-period {
  font-size: 12.5px; color: var(--ink-2);
  background: var(--taupe-soft); border-radius: var(--r-sm); padding: 6px 9px;
}
.fl-period.is-idle { color: var(--muted); background: none; padding-left: 0; }
.fl-period-bad { color: var(--red); font-weight: 600; }
.fl-alltime { color: var(--muted); font-weight: 600; padding: 0 4px; }
.fl-window { margin: 0; }

/* ============================================================
   A FINISHED JOB IS CLOSED  —  owner's rule, 2026-08-04
   Once the delivery is complete the card is a RECORD, not a form. Every control
   that would change it goes; everything that reads it stays, including Export to
   PDF and the History tab. The server refuses these writes anyway (refuseFinished
   in server.js) — hiding them is so the screen stops offering what it would refuse.
   ============================================================ */
.jc-locked #jcEditBtn,
.jc-locked #entryDelete,
.jc-locked #ctFormOpen,
.jc-locked .file-pick,
.jc-locked .ct-act,
.jc-locked .ct-x,
.jc-locked .stage-hint,
.jc-locked .ct-addmat,
.jc-locked [data-note],
.jc-locked [data-deltask],
.jc-locked [data-edittask],
.jc-locked [data-unassign] { display: none !important; }
/* The stage bar stays legible — it is the story of the job — but stops being a
   set of buttons. "click a step to correct it" is hidden above. */
.jc-locked .stagebar { pointer-events: none; }

.jc-locked-note {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 20px 12px; padding: 11px 13px;
  border: 1px solid color-mix(in srgb, var(--amber) 35%, var(--line));
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--amber) 9%, #fff);
  color: var(--ink); font-size: 14px; line-height: 1.45;
}
.jc-locked-note svg { width: 17px; height: 17px; color: var(--amber); flex: none; }
.jc-locked-note.hidden { display: none; }
@media (max-width: 760px) { .jc-locked-note { margin: 0 12px 10px; } }

/* The one line that replaces the state pills on the Stage board. They ask "when is
   it scheduled"; the stage columns answer "how far along is it", so they stand
   down rather than sit lit over a board that is ignoring them. */
.db-fnote { font-size: 12.5px; color: var(--muted); align-self: center; padding: 0 2px; }

/* ---------- Settings › Clear old work ----------
   The one screen that destroys work. It reads as a warning without shouting: the
   found-count is the biggest thing on it, what goes WITH the cards is spelled
   out, and the permanent button is the only red thing in Settings. */
.cl-warn {
  background: #fff6ef; border: 1px solid #e8d5c2; border-radius: var(--r-md);
  padding: 12px 14px; margin-bottom: 16px; line-height: 1.55;
}
.cl-warn b { display: block; margin-bottom: 4px; }
.cl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; margin-bottom: 14px; }
.cl-found {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--card); padding: 14px 16px; margin-top: 6px;
}
.cl-big { font-size: 19px; margin-bottom: 4px; }
.cl-big b { font-size: 25px; color: var(--primary-dark); font-variant-numeric: tabular-nums; }
.cl-also { margin-top: 9px; font-size: 12.5px; color: var(--ink-2); }
.cl-found details { margin-top: 10px; font-size: 13px; }
.cl-found summary { cursor: pointer; color: var(--primary-dark); }
.cl-list { margin: 6px 0 0; padding-left: 20px; color: var(--muted); font-size: 12.5px; line-height: 1.7; }
.cl-acts { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.cl-diff { margin-top: 10px; line-height: 1.6; }
.cl-none {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--taupe-soft); padding: 13px 15px; margin-top: 6px; font-size: 13.5px;
}
@media (pointer: coarse) {
  .cl-acts .ws-btn { min-height: 44px; }
}

/* ---------- Settings › Danger zone (2026-09-11) ----------
   Reset the whole system, and undo it. The page itself is calm; the red lives on
   the tab and on the one button that acts. The dialog is a list you READ — counts
   in a right-hand column, empty rows folded into one quiet line — then a typed word. */
.dz-card {
  border: 1px solid var(--line); border-radius: var(--r-md); background: var(--card);
  padding: 16px 18px; margin-bottom: 14px;
}
.dz-card.is-danger { border-color: #f0cfcb; }
.dz-card h3 { font-size: 16px; font-weight: 650; margin: 0 0 6px; }
.dz-head { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.dz-head-text { flex: 1 1 320px; min-width: 0; }
.dz-head-text p { margin: 0; color: var(--ink-2); font-size: 13.5px; line-height: 1.55; }
.dz-go { flex: 0 0 auto; }
.dz-stop {
  margin-top: 12px; padding: 10px 12px; border-radius: var(--r-sm);
  background: #fff1f1; border: 1px solid #f3c1c1; color: var(--red); font-size: 13px; line-height: 1.5;
}
.dz-undo-banner {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  background: #fdf4ea; border: 1px solid #e6c9a8; border-radius: var(--r-md);
  padding: 12px 16px; margin-bottom: 14px; font-size: 13.5px; line-height: 1.5;
}
.dz-undo-banner > div { flex: 1 1 280px; min-width: 0; }
.dz-bks { display: flex; flex-direction: column; }
.dz-bk { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 11px 0; border-top: 1px solid var(--line); }
.dz-bk:first-child { border-top: 0; padding-top: 2px; }
.dz-bk-text { flex: 1 1 280px; min-width: 0; font-size: 13.5px; }
.dz-by { color: var(--muted); }
.dz-bk-what { color: var(--ink-2); font-size: 12.5px; margin-top: 2px; font-variant-numeric: tabular-nums; }
.dz-bk-state { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
.dz-bk.is-undone b { color: var(--muted); }
.dz-bk-acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* the two dialogs — above the app chrome, below the confirmation (150) */
.dz-backdrop { z-index: 120; }
.dz-modal { width: 720px; }
.dz-modal .modal-footer { flex-wrap: wrap; }
.dz-foot-err { flex: 1 1 200px; margin: 0; }
.dz-lead { margin: 0 0 12px; font-size: 14.5px; line-height: 1.5; }
.dz-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 4px 24px; }
.dz-group h4, .dz-sub {
  font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; margin: 12px 0 4px;
}
.dz-sub { margin-top: 20px; }
.dz-rows { list-style: none; margin: 0; padding: 0; }
.dz-rows li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.dz-rows li b { font-variant-numeric: tabular-nums; white-space: nowrap; }
.dz-rows li b small { font-weight: 500; color: var(--muted); margin-left: 4px; }
.dz-none { font-size: 12px; color: var(--muted); padding: 5px 0 0; line-height: 1.5; }
.dz-opt { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; cursor: pointer; }
.dz-opt input { margin-top: 2px; width: 18px; height: 18px; flex: 0 0 auto; accent-color: var(--red); }
.dz-opt span { font-size: 13.5px; line-height: 1.45; min-width: 0; }
.dz-opt small { display: block; color: var(--muted); font-size: 12.5px; }
.dz-list { margin: 0; padding-left: 20px; font-size: 13.5px; line-height: 1.7; }
.dz-note {
  margin-top: 12px; padding: 10px 12px; background: var(--taupe-soft);
  border-radius: var(--r-sm); font-size: 13px; line-height: 1.55;
}
.dz-kept { margin-top: 12px; font-size: 13.5px; }
.dz-kept summary { cursor: pointer; color: var(--primary-dark); font-weight: 600; line-height: 1.5; }
.dz-kept .dz-list { margin-top: 6px; }
.dz-safe {
  margin-top: 12px; padding: 10px 12px; border: 1px solid #cfe3d4; background: #f1f8f3;
  border-radius: var(--r-sm); font-size: 13px; line-height: 1.55;
}
.dz-type { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; font-size: 13.5px; }
.dz-type input { max-width: 260px; font-size: 16px; letter-spacing: .08em; text-transform: uppercase; }
.dz-backdrop.is-busy .modal-body { opacity: .6; pointer-events: none; }
@media (pointer: coarse) {
  .dz-go, .dz-undo-banner .ws-btn, .dz-bk-acts .ws-btn, .dz-modal .modal-footer .btn { min-height: 44px; }
  .dz-opt { min-height: 44px; }
  .dz-opt input { width: 22px; height: 22px; }
  .dz-modal .modal-close { min-width: 44px; min-height: 44px; }
}
/* An empty error line takes no room; the buttons keep to the right by themselves. */
.dz-foot-err:empty { display: none; }
.dz-modal .modal-footer .btn-ghost { margin-left: auto; }
/* Pale until RESET is typed, then the one solid red thing on the screen. */
.dz-modal .btn-danger:not(:disabled) { background: var(--red); border-color: var(--red); color: #fff; }
.dz-modal .btn-danger:not(:disabled):hover { background: color-mix(in srgb, var(--red) 86%, #000); }
.dz-modal .btn:disabled { opacity: .55; cursor: not-allowed; }
@media (max-width: 560px) {
  /* Side by side: a phone's footer must not eat a fifth of the screen. */
  .dz-modal .modal-footer .btn { flex: 1 1 0; min-width: 0; padding-left: 8px; padding-right: 8px; }
  .dz-foot-err { flex-basis: 100%; }
  .dz-type input { max-width: none; }
  .dz-go { flex: 1 1 100%; }
}

/* The Stage board's two endings (2026-09-11): Partially delivered and Failed to deliver
   are columns of their own, headed in the stage chip's warn and bad colours. */
.stage-col.stage-partial .zone-head { color: #9a6a1f; }
.stage-col.stage-failedcol .zone-head { color: var(--red); }
/* Where a failed delivery's package is — under the Failed banner on the job card. */
.jc-return:empty { display: none; }

/* ONE CUSTODY PANEL (2026-09-11) — anything handed to a person who then confirms it.
   The Cash journey's shapes (cash.css) made shared, for a failed delivery's package
   coming back to the shop (public/returns.js): the "where is it now" line, the
   timeline, ticks for what is handed over, the photo steps and the sticky buttons. */
.add-drawer-back.tf-panel.rt-panel { z-index: 1000; }      /* over the job card (960) as well */
.tf-panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.tf-panel-sub { margin: -8px 0 14px; }
.tf-now { border-radius: var(--r-md); padding: 10px 12px; margin: 0 0 18px; font-size: 14px; line-height: 1.45; background: var(--accent-tint); }
.tf-now.is-warn { background: color-mix(in srgb, var(--amber) 14%, #fff); }
.tf-now.is-good { background: color-mix(in srgb, var(--green) 12%, #fff); }
.tf-now.is-bad { background: color-mix(in srgb, var(--red) 9%, #fff); }
.tf-now.is-grey { background: var(--taupe-soft); color: var(--ink-2); }
.tf-now.rt-jc { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 0; }
.tf-now.rt-jc > span { flex: 1 1 240px; min-width: 0; }
.tf-main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.tf-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.tf-sub { font-size: 12.5px; color: var(--muted); line-height: 1.4; }
.tf-sub b { color: var(--ink); font-weight: 600; }
.tf-said { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }
.tf-steps { list-style: none; margin: 0 0 12px; padding: 0; }
.tf-step { position: relative; display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 4px 10px; padding: 0 0 18px; }
.tf-step::before { content: ''; position: absolute; left: 8px; top: 18px; bottom: 2px; width: 2px; background: var(--hairline); }
.tf-step:last-child::before { display: none; }
.tf-dot { width: 12px; height: 12px; margin: 4px 0 0 3px; border-radius: 50%; background: var(--muted); }
.tf-dot.t-good { background: var(--green); }
.tf-dot.t-warn { background: var(--amber); }
.tf-dot.t-bad { background: var(--red); }
.tf-dot.t-road { background: var(--blue, #4a78a8); }
.tf-dot.t-grey { background: #c9bfb5; }
.tf-step-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 14px; }
.tf-photos { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.tf-photos img { width: 64px; height: 64px; object-fit: cover; border-radius: 10px; border: 1px solid var(--hairline); background: var(--taupe-soft); }
.tf-pick { border: 1px solid var(--hairline); border-radius: var(--r-lg); overflow: hidden; margin: 0 0 16px; }
.tf-pick-row {
  display: grid; grid-template-columns: 22px minmax(0, 1fr); align-items: center; gap: 10px;
  padding: 10px 12px; border-top: 1px solid var(--hairline); cursor: pointer;
}
.tf-pick-row:first-child { border-top: 0; }
.tf-pick-row:hover { background: var(--accent-tint); }
.tf-pick-row input { width: 18px; height: 18px; accent-color: var(--primary); }
.tf-checks { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.tf-check { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 10px; padding: 12px; border: 1px solid var(--hairline); border-radius: var(--r-lg); }
.tf-check-n { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700; background: var(--taupe-soft); color: var(--ink-2); }
.tf-check.is-done .tf-check-n { background: color-mix(in srgb, var(--green) 18%, #fff); color: var(--green); }
.tf-cam { align-self: flex-start; margin-top: 8px; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.tf-cam svg { width: 16px; height: 16px; }
.tf-panel-acts {
  position: sticky; bottom: 0; display: flex; justify-content: flex-end; gap: 8px;
  margin-top: auto; padding: 12px 0 10px; background: var(--card); border-top: 1px solid var(--hairline);
}
@media (pointer: coarse) {
  .tf-panel-acts .ws-btn, .tf-cam { min-height: 44px; }
  .tf-pick-row { min-height: 52px; }
}
@media (max-width: 560px) { .tf-panel-acts .ws-btn { flex: 1 1 0; } }
/* Why the "Needs a car" column is shorter than the team is — people off today,
   and people never marked as needing transport. Quiet, but present: an absence
   with no explanation is read as a bug. */
.pl-aside { color: var(--muted); }

/* ============================================================
   THE UNIFIED FORM (2026-08-16) — two questions, then one page.

   Sections, not steps. Each one is a quiet band with a small heading, so the
   page reads as a list of questions in the order the work happens rather than
   as a wall of fields. The rules are the ones the rest of the app already
   keeps: hairline borders, the module accent for anything chosen, 16px inputs
   on a touch screen (the shared `pointer: coarse` block covers .wiz-input).
   ============================================================ */
.uz-sec { padding: 14px 0 4px; border-top: 1px solid var(--line); }
.uz-sec:first-child { border-top: 0; padding-top: 4px; }
.uz-h {
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 10px;
}
.uz-h4 { font-size: 13px; font-weight: 600; color: var(--ink); margin: 10px 0 6px; display: flex; align-items: center; gap: 6px; }
.uz-row { display: flex; gap: 12px; flex-wrap: wrap; }
.uz-row > .field { flex: 1 1 220px; min-width: 0; }   /* min-width:0 or a long value pushes the row wide */
.uz-row > .field.grow { flex: 2 1 320px; }
.uz-quiet { color: var(--muted); font-size: 12.5px; }
.uz-nodriver {
  background: var(--taupe-soft); border-radius: var(--r-md); padding: 10px 12px;
  display: flex; align-items: center; gap: 8px; line-height: 1.45;
}
.uz-nodriver b { color: var(--ink); font-weight: 600; }

/* chips: what it is for, and the common jobs */
.uz-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.uz-chip {
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  border-radius: var(--r-pill); padding: 7px 13px; font: inherit; font-size: 13.5px;
  cursor: pointer; display: inline-flex; align-items: center; gap: 6px; min-height: 34px;
  transition: border-color .14s ease, background .14s ease, color .14s ease;
}
.uz-chip:hover { border-color: var(--accent); }
.uz-chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.uz-chip.on svg { color: #fff; }
.uz-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.uz-find { margin-top: 4px; }
.uz-linked {
  margin-top: 7px; font-size: 13px; color: var(--ink);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.uz-linked svg { color: var(--ok, #2e7d5b); }

/* people already on the job */
.uz-crew { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.uz-person {
  background: var(--taupe-soft); border-radius: var(--r-pill); padding: 5px 8px 5px 12px;
  font-size: 13px; display: inline-flex; align-items: center; gap: 4px;
}
.uz-addcrew { align-items: flex-end; }
.uz-addcrew .field { margin: 0; }

.uz-files { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.uz-stops { margin-bottom: 10px; }
.uz-other.hidden { display: none; }
.uz-err { margin: 12px 0 0; }
.uz-done {
  margin: 12px 0 0; padding: 10px 12px; border-radius: var(--r-md);
  background: var(--ok-tint, #e9f5ef); color: var(--ink); font-size: 13.5px;
  display: flex; align-items: center; gap: 8px;
}

/* A phone gets one field per row — two 220px columns in a 360px window is how
   a form ends up scrolling sideways. */
@media (max-width: 620px) {
  .uz-row { flex-direction: column; gap: 10px; }
  .uz-row > .field, .uz-row > .field.grow { flex: 1 1 auto; }
}

/* `.field label { display: block }` (line 558) beats `.wiz-check` on specificity —
   element+class over class alone — so a checkbox inside a .field stacked ON TOP of
   its own words, and `.field input { width: 100% }` stretched the box across the
   row. This is the exact fault the customer photographed on 2026-08-03; it came
   back the moment the control was reused inside a plain .field. Fixed by name so
   it cannot come back a third time. */
.uz-sec .field label.wiz-check {
  display: flex; align-items: center; gap: 7px; width: auto; margin-bottom: 0;
  font-size: 12.5px; font-weight: 400; color: var(--muted);
}
.uz-sec .field label.wiz-check input[type="checkbox"] { width: auto; flex: 0 0 auto; margin: 0; }

/* The four original forms stay on the picker until the new one has been proved
   beside them (owner, 2026-08-16). The heading is quieter than the first one so
   the eye lands on the new pair first, without the familiar four being buried. */
.wiz-lead-kept {
  margin-top: 18px; font-size: 12px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted); border-top: 1px solid var(--line);
  padding-top: 14px;
}

/* EVENT ORDERS: the shapes of that screen moved to public/modules/events.css
   on 2026-09-05, where every other module with shapes of its own keeps them.
   Only its hue stays global, in the accent block above. */

/* ---- DISPATCH: the two trays, and the driving that comes out of an event ---- */
.db-coming, .db-eventruns { margin-top: 14px; padding-top: 10px; border-top: 1px dashed var(--line); }
.db-coming-head {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 8px;
}
.db-coming-head small {
  text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 11.5px;
  flex-basis: 100%; color: var(--muted);
}
/* Not ready is a state, not a failure: quieter, never alarming, and still legible. */
.db-coming .db-card, .db-card-event.is-coming { opacity: .62; }
.db-coming .db-card:hover, .db-card-event.is-coming:hover { opacity: 1; }
.db-card-event { border-left: 3px solid var(--accent); }
.db-evref { font-family: var(--mono, ui-monospace, monospace); font-weight: 700; }


/* ============================================================
   THE FORM — third pass, 2026-09-05.

   The owner's verdict on the second pass was "the design the design the design",
   and he was right: it was a stack of grey labels and white boxes. Nothing to
   look at, nothing to aim for, no sense of where you were in it.

   This pass gives it a spine.
     · THREE NUMBERED STEPS down the page, each with a heading you can read from
       across the room, so you always know where you are and what is left.
     · A SEGMENTED YES / NO for the first question. It is a decision, and a
       decision deserves a control nobody can miss or half-answer.
     · ICON TILES for the kind of work — six things the shop actually does, each
       with a picture, recognised rather than read. It is the fastest tap on the
       page and it should look like it.
     · A RECEIPT of what is about to be made, carrying where it will go.
     · Required marks that mean something, errors under the field they belong to.

   Colour comes from --uz-accent, set per family on the form itself, so a staff
   task and a driver task can never be mistaken for one another.
   ============================================================ */
.uz-form { --uz-accent: var(--primary); }

/* ---- the family band ---- */
.uz-strip {
  display: flex; align-items: center; gap: 10px;
  background: var(--uz-tint, var(--accent-tint)); border-radius: var(--r-md);
  padding: 12px 14px; margin-bottom: 20px;
}
.uz-strip b { font-size: 15.5px; font-weight: 600; letter-spacing: -.01em; }
.uz-strip-ico { display: grid; place-items: center; color: var(--uz-accent); }
.uz-strip-swap {
  margin-left: auto; border: 0; background: none; color: var(--uz-accent);
  font: inherit; font-size: 13px; cursor: pointer; min-height: 32px; padding: 0 4px;
}
.uz-strip-swap:hover { text-decoration: underline; }

/* ---- a step ---- */
.uz-step { margin-bottom: 24px; transition: opacity .18s ease; }
.uz-step.off { opacity: .38; pointer-events: none; }   /* not yet its turn */
.uz-step-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.uz-step-head h3 {
  margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -.015em; color: var(--ink);
}
.uz-step-n {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; font-size: 13px; font-weight: 700;
  background: var(--taupe-soft); color: var(--muted); border: 1px solid var(--line);
}
.uz-step.done .uz-step-n, .uz-step.now .uz-step-n {
  background: var(--uz-accent); color: #fff; border-color: var(--uz-accent);
}
.uz-req { color: var(--uz-accent); margin-left: 3px; font-weight: 700; }

/* ---- the yes / no ---- */
.uz-seg {
  display: inline-flex; background: var(--taupe-soft); border-radius: var(--r-pill);
  padding: 4px; gap: 4px; border: 1px solid var(--line);
}
.uz-seg-b {
  border: 0; background: none; font: inherit; font-size: 14.5px; font-weight: 600;
  color: var(--muted); padding: 9px 30px; border-radius: var(--r-pill); cursor: pointer;
  min-height: 40px; transition: background .15s ease, color .15s ease;
}
.uz-seg-b:hover { color: var(--ink); }
.uz-seg-b.on { background: var(--uz-accent); color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.uz-find { margin-top: 14px; max-width: 460px; }

/* ---- the tiles: what needs doing ---- */
.uz-tiles {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 9px; margin-bottom: 14px;
}
.uz-tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px;
  border: 1px solid var(--line); background: var(--card); border-radius: var(--r-md);
  padding: 14px 8px; cursor: pointer; font: inherit; color: var(--ink); min-height: 86px;
  transition: border-color .15s ease, background .15s ease, transform .12s ease;
}
.uz-tile:hover { border-color: var(--uz-accent); transform: translateY(-1px); }
.uz-tile:focus-visible { outline: 2px solid var(--uz-accent); outline-offset: 2px; }
.uz-tile-ico { color: var(--uz-accent); display: grid; place-items: center; }
.uz-tile-ico svg { width: 24px; height: 24px; }
.uz-tile-w { font-size: 12.5px; font-weight: 600; text-align: center; line-height: 1.25; }
.uz-tile.on {
  background: var(--uz-accent); border-color: var(--uz-accent); color: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,.13);
}
.uz-tile.on .uz-tile-ico { color: #fff; }
/* "More..." is a tile with a select laid invisibly over it, so it looks like the
   others instead of being a dropdown loose beside them. */
.uz-tile-more { position: relative; border-style: dashed; }
.uz-tile-more select {
  position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; cursor: pointer;
}

.uz-title { font-size: 16.5px; padding: 12px 14px; }

/* ---- fields ---- */
.uz-label {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; color: var(--ink-2); margin-bottom: 6px;
}
.uz-label svg { width: 15px; height: 15px; color: var(--muted); }
.uz-label .hint { font-weight: 400; }
.uz-label-sm { font-size: 12.5px; margin-top: 10px; }
.uz-row { display: flex; gap: 14px; flex-wrap: wrap; }
.uz-row > .field { flex: 1 1 240px; min-width: 0; }
.uz-mt { margin-top: 12px; }
.uz-mt-sm { margin-top: 6px; }
.uz-quiet { color: var(--muted); font-size: 12.5px; line-height: 1.5; }

.uz-note {
  display: flex; align-items: flex-start; gap: 9px; margin-top: 12px;
  background: var(--taupe-soft); border-radius: var(--r-md); padding: 11px 13px;
  font-size: 13px; color: var(--ink-2); line-height: 1.5;
}
.uz-note svg { flex: 0 0 auto; color: var(--uz-accent); margin-top: 1px; }
.uz-note b { color: var(--ink); }

/* ---- editing a task: the same form, inline under the task (2026-09-10) ---- */
.te { margin-top: 12px; padding: 16px 16px 10px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg); }
.te .uz-strip { margin-bottom: 16px; }
.te-ref { margin-left: auto; font-family: var(--mono); font-size: 12.5px; color: var(--ink-2); }
.te .uz-step { margin-bottom: 18px; }
.te .uz-step-head { margin-bottom: 10px; }
.te .uz-step-head h3 { font-size: 15px; }
.te .uz-tiles { grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px; margin-bottom: 12px; }
.te .uz-tile { min-height: 74px; padding: 10px 6px; }
.te-seg { flex-wrap: wrap; }
.te-seg .uz-seg-b { padding: 8px 18px; }
.te-loc { display: flex; gap: 8px; }
.te-loc .wiz-input { flex: 1; min-width: 0; }
.te-actions {
  position: sticky; bottom: 0; z-index: 2; display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 0 4px; border-top: 1px solid var(--line); background: var(--card);
}

.uz-linked { margin-top: 10px; padding: 12px 14px; border-radius: var(--r-md); background: var(--taupe-soft); }
.uz-linked-top { display: flex; align-items: center; gap: 8px; font-size: 14px; flex-wrap: wrap; }
.uz-linked-top svg { color: var(--ok, #2e7d5b); }
.uz-linked-top .wiz-chip-x { margin-left: auto; }

/* ---- the receipt ---- */
.uz-summary {
  margin: 4px 0 16px; padding: 14px 16px; border-radius: var(--r-md);
  background: var(--card); border: 1px solid var(--line);
  border-left: 4px solid var(--uz-accent); box-shadow: 0 1px 4px rgba(0,0,0,.05);
}
.uz-sum-line { font-size: 14.5px; line-height: 1.55; color: var(--ink); }
.uz-sum-line b { font-weight: 600; }
.uz-dot { color: var(--line); margin: 0 8px; }
.uz-sum-go {
  margin-top: 9px; padding-top: 9px; border-top: 1px dashed var(--line);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 13.5px; color: var(--uz-accent);
}
.uz-sum-go svg { width: 16px; height: 16px; }
.uz-sum-go b { font-weight: 700; }
.uz-sum-extra { color: var(--muted); font-size: 12.5px; }

/* ---- everything else, behind one line ---- */
.uz-more-btn {
  width: 100%; text-align: left; border: 1px dashed var(--line); background: transparent;
  border-radius: var(--r-md); padding: 13px 15px; font: inherit; font-size: 14px;
  color: var(--ink-2); cursor: pointer; min-height: 48px;
  display: flex; align-items: center; gap: 9px;
}
.uz-more-btn:hover { border-color: var(--uz-accent); color: var(--uz-accent); }
.uz-more-btn.open { border-style: solid; background: var(--card); }
.uz-more-sign { font-size: 17px; line-height: 1; color: var(--uz-accent); font-weight: 600; }
.uz-more-n {
  margin-left: auto; background: var(--uz-accent); color: #fff; border-radius: var(--r-pill);
  min-width: 22px; height: 22px; display: inline-grid; place-items: center; font-size: 12px; font-weight: 700;
}
.uz-extras { margin-top: 10px; }
.uz-extras.hidden { display: none; }
.uz-block { padding: 14px 0; border-top: 1px solid var(--line); }
.uz-block:first-child { border-top: 0; padding-top: 4px; }

.uz-crew { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }
.uz-person {
  background: var(--taupe-soft); border-radius: var(--r-pill); padding: 6px 8px 6px 13px;
  font-size: 13px; display: inline-flex; align-items: center; gap: 5px;
}
.uz-files { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }
.uz-stops { margin-bottom: 12px; }
.uz-h4 { font-size: 13px; font-weight: 600; color: var(--ink); margin: 10px 0 7px; display: flex; align-items: center; gap: 6px; }
.uz-other.hidden { display: none; }
.uz-err { margin: 14px 0 0; }
.uz-done {
  margin: 14px 0 0; padding: 12px 14px; border-radius: var(--r-md);
  background: var(--ok-tint, #e9f5ef); color: var(--ink); font-size: 14px;
  display: flex; align-items: center; gap: 9px;
}

/* `.field label { display:block }` outranks `.wiz-check` — element+class beats
   class alone — so a checkbox inside a .field stacked on top of its own words.
   That is the fault the customer photographed on 2026-08-03; it is fixed here by
   name so it cannot come back a third time. */
.uz-form .field label.wiz-check {
  display: flex; align-items: center; gap: 7px; width: auto; margin: 7px 0 0;
  font-size: 12.5px; font-weight: 400; color: var(--muted);
}
.uz-form .field label.wiz-check input[type="checkbox"] { width: auto; flex: 0 0 auto; margin: 0; }

@media (max-width: 620px) {
  .uz-row { flex-direction: column; gap: 12px; }
  .uz-row > .field { flex: 1 1 auto; }
  .uz-tiles { grid-template-columns: repeat(3, 1fr); }
  .uz-seg { display: flex; }
  .uz-seg-b { flex: 1; padding: 9px 0; }
}

/* Every tap target clears 44px on a touch screen — the rule the app keeps
   everywhere else, and the one the first version of this form broke. */
@media (pointer: coarse) {
  .uz-seg-b { min-height: 46px; }
  .uz-tile { min-height: 96px; }
  .uz-more-btn { min-height: 54px; }
  .uz-strip-swap { min-height: 44px; }
  .uz-form .wiz-check input[type="checkbox"] { width: 22px; height: 22px; }
}

/* An order the shop cancelled on Shopify. Not a step on the ladder — an end to
   it — so it replaces the bar rather than sitting on it. Everything the card
   holds is kept: a florist who spent an hour on it must still see the hour. */
.stage-cancelled {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 14px; border-radius: var(--r-md);
  background: var(--danger-tint, #fbeceb); color: var(--ink);
  border-left: 4px solid var(--danger, #b5453c);
}
.stage-cancelled svg { color: var(--danger, #b5453c); flex: 0 0 auto; }
.stage-cancelled b { font-weight: 700; }
.stage-cancelled span { color: var(--muted); font-size: 13px; flex: 1 1 240px; }

/* Car check-in on the Homepage (customer item 40, 2026-09-05). Above the tiles,
   because taking a van is the first thing a driver does in the morning — before
   he has looked at a single job. Empty and invisible for anyone who cannot. */
.home-car:empty { display: none; }
.home-car { margin: 0 0 18px; }

/* Failed reads as a problem, not a catastrophe — amber, where Cancelled is red.
   A failed delivery is still a job the shop will try again tomorrow. */
.stage-failed { background: var(--warn-tint, #fdf3e4); border-left-color: var(--warn, #b4801f); }
.stage-failed svg { color: var(--warn, #b4801f); }

/* ROLES & PERMISSIONS: the shapes of that screen moved to
   public/modules/roles.css on 2026-09-09, where every other module with
   shapes of its own keeps them. Only its hue stays global, above. */


/* The shared task form, embedded in the job card's own panel (2026-09-05). No
   title bar and no close button — the card around it already has both — and the
   body is not a scrolling dialog here, it is part of the tab. */
.wiz-embed { box-shadow: none; border: 0; background: transparent; max-height: none; }
.wiz-embed .wiz-body { padding: 0; max-height: none; overflow: visible; }
.wiz-embed .wiz-foot { padding: 14px 0 0; border-top: 1px solid var(--line); margin-top: 8px; }
.wiz-embed .wiz-progress { display: none; }
/* The order is a fact here, not a question: you are standing on its card. */
.uz-locked {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--taupe-soft); border-radius: var(--r-md); padding: 10px 13px; font-size: 13.5px;
}
.uz-locked svg { color: var(--ok, #2e7d5b); flex: 0 0 auto; }
.uz-locked span { color: var(--muted); font-size: 12.5px; }

/* ============================================================
   ROSTER · SELECT SEVERAL DAYS  (2026-09-10)
   Customer: "no bulk edits available". A ticked square wears the accent it would
   wear if it were chosen anywhere else in the app; the bar sticks under the app
   bar so the answer is always in reach however far down the roster you are.
   ============================================================ */
.sch-cell.sel, .sch-dayrow.sel { outline: 2px solid var(--primary); outline-offset: -2px; background: var(--accent-tint); }
.sch-bulkbar {
  position: sticky; top: 62px; z-index: 6; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px 12px; margin: 0 0 12px; background: var(--card); border: 1px solid var(--primary);
  border-radius: var(--r-md); box-shadow: 0 6px 18px rgba(0, 0, 0, .08);
}
.sch-bulk-n { font-size: 13.5px; margin-right: 4px; }
.sch-bulk-sel { width: auto; min-width: 180px; }
.sch-bulk-custom { display: inline-flex; gap: 6px; align-items: center; }
.sch-bulk-custom .ws-input { width: auto; }
#schSelect.on { background: var(--primary); border-color: var(--primary); color: #fff; }
@media (pointer: coarse) {
  .sch-bulkbar .ws-btn, .sch-bulkbar .ws-input { min-height: 44px; }
}

/* The Driver task's "is it for a TF order?" question, sitting inside the older
   step-by-step form rather than the one-page one it was written for. */
.uz-inline { margin-bottom: 4px; }
.uz-inline .uz-step { margin-bottom: 16px; }

/* ============================================================
   ADD — the floating + and its side panel (2026-09-10, public/addfab.js)
   One button on TF Arrangement and Event Orders instead of an Add button in
   the header. Layered under the job card (960) and every confirmation (1200).
   ============================================================ */
.add-fab {
  position: fixed; right: 28px; bottom: 28px; z-index: 930;
  width: 58px; height: 58px; border-radius: 50%; border: 0;
  background: var(--accent, var(--primary)); color: #fff; cursor: pointer;
  box-shadow: 0 10px 28px rgba(0,0,0,.22), 0 2px 6px rgba(0,0,0,.12);
  display: grid; place-items: center; transition: transform .18s ease, box-shadow .18s ease;
}
.add-fab:hover { transform: translateY(-2px); box-shadow: 0 14px 32px rgba(0,0,0,.26); }
/* Never over the sign-in card. addfab.js hides these the moment the card shows;
   this is the same rule said in CSS, so no timing, stale session or script error
   can put the + (or its menu, or a half-open panel) on the sign-in page. The card
   comes first in <body> and these are appended at the end, so ~ reaches them. */
#loginScreen:not(.hidden) ~ .add-fab,
#loginScreen:not(.hidden) ~ .add-fan,
#loginScreen:not(.hidden) ~ .add-fan-back,
#loginScreen:not(.hidden) ~ .add-drawer-back { display: none !important; }
.add-fab:focus-visible { outline: 3px solid var(--accent-tint); outline-offset: 3px; }
.add-fab-plus { display: grid; place-items: center; line-height: 0; transition: transform .2s ease; }
.add-fab-plus svg { width: 26px; height: 26px; }
.add-fab-plus svg, .add-fab-plus svg * { stroke-width: 2.2; }
.add-fab:active { transform: scale(.94); }
.add-fab.open .add-fab-plus { transform: rotate(45deg); }
.add-fab-badge {
  position: absolute; top: -3px; right: -3px; min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 11px; background: #fff; color: var(--accent, var(--primary));
  font-size: 12px; font-weight: 700; display: grid; place-items: center; box-shadow: 0 1px 4px rgba(0,0,0,.2);
}
.add-fan {
  position: fixed; right: 28px; bottom: 100px; z-index: 931; width: 340px;
  max-height: calc(100vh - 140px); overflow: auto;
  display: flex; flex-direction: column; gap: 4px; padding: 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  box-shadow: 0 18px 50px rgba(0,0,0,.18);
  transform-origin: bottom right; animation: addFanIn .16s ease-out;
}
@keyframes addFanIn { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.add-fan-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 10px 12px; min-height: 56px; border: 0; border-radius: 12px;
  background: transparent; font: inherit; color: var(--ink); cursor: pointer;
}
.add-fan-item:hover, .add-fan-item:focus-visible { background: var(--accent-tint); outline: none; }
.add-fan-ico {
  flex: 0 0 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: var(--accent-tint); color: var(--accent, var(--primary));
}
.add-fan-ico svg { width: 20px; height: 20px; }
.add-fan-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.add-fan-txt b { font-weight: 600; font-size: 14.5px; }
.add-fan-txt small { color: var(--muted); font-size: 12.5px; line-height: 1.35; }
.add-fan-n {
  margin-left: auto; min-width: 24px; height: 24px; padding: 0 7px; border-radius: 12px;
  background: var(--accent, var(--primary)); color: #fff; font-size: 12px; font-weight: 700;
  display: grid; place-items: center;
}
.add-fan-sep { height: 1px; background: var(--line); margin: 2px 6px; }
.add-fan-draftlist { display: flex; flex-direction: column; gap: 2px; padding: 0 6px 4px 56px; }
.add-fan-draft {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px; text-align: left;
  border: 0; background: transparent; padding: 7px 8px; border-radius: 8px; font: inherit; cursor: pointer; color: var(--ink);
}
.add-fan-draft:hover { background: var(--accent-tint); }
.add-fan-draft b { font-weight: 500; font-size: 13.5px; }
.add-fan-draft small, .add-fan-more { color: var(--muted); font-size: 12px; }
/* The header's own Add button gives its place back: the + does that job now. */
#addBtn { display: none !important; }

/* ONE SIDE PANEL (unification step 4) — TF.sidePanel in app.js, on the + panel's own
   shapes. Two classes so it outranks .add-drawer-back's z-index, declared further down:
   above the + panel (940), under the job card (960) and the dialogs (1200). */
.add-drawer-back.tf-panel { z-index: 950; }
.tf-panel .add-drawer-host { overflow-y: auto; padding: 20px 20px 10px; }
.tf-panel-title { font-family: var(--serif); font-size: 22px; font-weight: 600; margin: 0 0 14px; }
.fl-card[data-opencar] { cursor: pointer; }

/* ONE ⋯ MENU FOR A ROW (unification step 3) — TF.rowMenu in app.js. */
.row-menu-btn {
  flex: none; min-width: 32px; min-height: 32px; padding: 0 6px; border: 0; border-radius: var(--r-sm, 8px);
  background: none; color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer;
}
.row-menu-btn:hover, .row-menu-btn[aria-expanded="true"] { background: var(--accent-tint, rgba(0,0,0,.06)); color: var(--ink, inherit); }
.row-menu-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.row-menu {
  position: fixed; z-index: 990; min-width: 170px; padding: 6px; display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md); box-shadow: 0 12px 32px rgba(0,0,0,.16);
}
.row-menu-item {
  text-align: left; padding: 9px 12px; border: 0; border-radius: 8px; background: none;
  font: inherit; font-size: 14px; color: inherit; cursor: pointer;
}
.row-menu-item:hover, .row-menu-item:focus-visible { background: var(--accent-tint, rgba(0,0,0,.06)); outline: none; }
.row-menu-item.danger { color: var(--red); }
@media (pointer: coarse) { .row-menu-btn { min-width: 44px; min-height: 44px; } .row-menu-item { min-height: 44px; } }

.add-drawer-back { position: fixed; inset: 0; z-index: 940; background: rgba(20,22,30,0); transition: background .2s ease; }
.add-drawer-back.in { background: rgba(20,22,30,.28); }
.add-drawer {
  position: absolute; top: 0; right: 0; bottom: 0; width: min(600px, 100vw);
  background: var(--card); box-shadow: -18px 0 50px rgba(0,0,0,.18);
  transform: translateX(100%); transition: transform .22s ease; display: flex; flex-direction: column;
}
.add-drawer-back.in .add-drawer { transform: none; }
.add-drawer-host { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.add-drawer .wiz-modal, .add-drawer .wiz-modal.wiz-page {
  max-width: none; width: 100%; height: 100%; max-height: none; border-radius: 0; box-shadow: none; margin: 0;
}
.add-drawer .wiz-modal .wiz-body, .add-drawer .wiz-modal.wiz-page .wiz-body { flex: 1; min-height: 0; overflow-y: auto; max-height: none; }
@media (max-width: 760px) {
  .add-fab { right: 16px; bottom: 16px; }
  .add-fan { left: 12px; right: 12px; width: auto; bottom: 86px; }
  .add-drawer { top: 6vh; width: 100vw; border-radius: 18px 18px 0 0; transform: translateY(100%); }
  .add-drawer-back.in .add-drawer { transform: none; }
}
@media (pointer: coarse) { .add-fan-item { min-height: 60px; } }

/* Dispatch and Staff tasks add through the same + as the boards (2026-09-10),
   so their own header Add buttons give their place back too. */
#dbAddJob, #stfAdd { display: none !important; }

/* ============================================================
   RESPONSIVE PASS — 2026-09-11
   Measured, not eyeballed: a headless Chrome swept every screen, for seven roles,
   at twelve device sizes with real touch emulation (1,095 measurements), and each
   rule below answers a finding from that sweep or from its screenshots. Kept in
   one block at the end, so it wins over the older rules it corrects without
   rewriting them, and so the next person can see exactly what this pass changed.
   ============================================================ */

/* 1 · A strip that scrolls says so. app.js marks a sideways-scrolling strip with
   tf-scroll-l / tf-scroll-r while there is more on that side, and it fades out
   there — instead of ending mid-word at the screen's edge ("Connec", "Mon"), which
   read as a broken layout rather than as "there is more". */
.tf-scroll-r {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
.tf-scroll-l {
  -webkit-mask-image: linear-gradient(270deg, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(270deg, #000 calc(100% - 28px), transparent);
}
.tf-scroll-l.tf-scroll-r {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
}

/* 2 · The filter pills are ONE row on a phone. They wrapped into three rows on
   TF Arrangement and five on Dispatch — 170-230px of pills before a single job.
   One scrolling row keeps every count and gives the screen back to the work. */
@media (max-width: 700px) {
  .db-filters {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    min-width: 0; max-width: 100%;
  }
  .db-filters::-webkit-scrollbar { display: none; }
  .db-filters > * { flex: 0 0 auto; }
  .db-fchip.good { margin-left: 0; }
}

/* 3 · The floating + never sits on the last thing on the page. It covered "Put back
   on the list" on Cars at every size, a row's ⋯ on Places, "Open" on Dispatch's
   roster and "Dismiss" on Home. addfab.js sets has-fab while the + is showing. */
body.has-fab .main-area > .page:not(.hidden) {
  padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px));
}
/* …and it steps aside while the phone's menu drawer is open: it was floating over
   the dimmed page beside the menu, the one moment adding something is not the task. */
body.nav-open .add-fab, body.nav-open .add-fan, body.nav-open .add-fan-back { display: none !important; }

/* 4 · On a phone the page's buttons give way before the bell does. Team's four
   buttons pushed the bell onto a row of its own — a 217px bar. The buttons now take
   what is left of the first row and scroll inside it (with the fade from 1); the
   bell keeps its place at the end of the row, as on every other screen. */
@media (max-width: 700px) {
  .top-strip .spacer { flex: 0 0 0; min-width: 0; }
  .top-strip .head-actions:not(:empty) { flex: 1 1 0; min-width: 0; }
  .top-strip .head-actions:not(:empty) > :first-child { margin-left: auto; }
  .top-strip .notif-wrap { margin-left: auto; flex: none; }
}

/* 5 · Fingers. Everything here measured under 44px on a touch screen. */
@media (pointer: coarse) {
  /* search boxes were 34-40px — the most-used field on TF Arrangement, Dispatch,
     Staff tasks, My work and Settings */
  .top-strip input[type="search"], .search-wrap input[type="search"], .db-search,
  .mw-fsearch, .set-search, #stfQ, #actQ, .rl-find, .ev-find { min-height: 44px; }
  /* words that are buttons — "Check the phones", "Mark all read", "See what
     failed →" — were 14-18px tall */
  .btn-link, .db-link, .wa-link, .ov-mini, .rl-who, .rl-all, .rl-can {
    min-height: 44px; display: inline-flex; align-items: center;
  }
  /* the job card's tabs and stage steps, and a card's own action (30-41px) */
  .jc-tabs button, .stage-step, .c-go { min-height: 44px; }
  /* WhatsApp's tabs and filter pills, Staff tasks' board options, the week's days */
  body .wa-tab, body .wa-pill { min-height: 44px; display: inline-flex; align-items: center; }
  .stf-seg, .stf-flow, .stf-sel { min-height: 44px; }
  .wk-head { min-height: 44px; }
  .tfcal-day { min-height: 44px; }
  /* the picker's month arrows and its Today (40px), and Settings' inline name box */
  .tfcal-nav, .tfcal-today { min-width: 44px; min-height: 44px; }
  .inline-input { min-height: 44px; }
  /* the last small ones the after-sweep still measured: Staff tasks' Today and
     "+2 more", Dispatch's "+ crew" and driver tabs, WhatsApp's switches-as-rows,
     form fields in Settings and Reports */
  .stf-today, .stf-more, .db-rcrew-add, .db-drv-tab { min-height: 44px; }
  .ws-input, .field input, .field select { min-height: 44px; }
  body .wa-check { min-height: 44px; }
  /* A switch is drawn 28px tall and must stay that shape; its hit area grows
     around it instead. */
  .switch::after { content: ''; position: absolute; left: -4px; right: -4px; top: -8px; bottom: -8px; }
  /* the lorry beside a name on the roster (measured 30px despite an older rule) */
  .sch-row button.sch-lift, td button.sch-lift, button.sch-lift { min-width: 44px; min-height: 44px; }
  /* A card's address is a link to the map, 17px tall inside its line of text. The
     hit area grows without moving a single line of the card. */
  .card-entry a[title="Open in Google Maps"] { position: relative; }
  .card-entry a[title="Open in Google Maps"]::after {
    content: ''; position: absolute; left: -2px; right: -2px; top: -13px; bottom: -13px;
  }
}

/* 6 · The date picker fits a phone held sideways — it was 465px tall on a 393px-high
   screen, its last weeks unreachable. It scrolls inside itself instead. */
.tfcal { max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); overflow-y: auto; overscroll-behavior: contain; }

/* 7 · TF Arrangement / Event Orders on a narrow screen. The arrangement row runs the
   full width — Day/Week/Month left, the arrangement right — where it floated right
   beside an empty gap; and its menu opens INWARD, where it opened to the right and
   ran 109px off the side of a phone. */
.board-arrange.on-page .bview-menu { left: auto; right: 0; }
.bview-menu { max-width: calc(100vw - 24px); }
@media (max-width: 560px) {
  .board-arrange.on-page { flex: 1 1 100%; margin-left: 0; justify-content: space-between; }
}

/* 8 · By day: "7 – 13 September 2026 › This week" is wider than a phone — it pushed
   the page 45px sideways and cut "This week" off. The heading may wrap now. */
@media (max-width: 560px) {
  .wk-bar { flex-wrap: wrap; row-gap: 4px; }
  .wk-bar h3 { font-size: 18px; white-space: normal; text-align: center; }
  .wk-this { margin-left: 0; }
}

/* 9 · Settings › Driver names: the name box and "Add driver" wrap, rather than push
   the page 12px sideways at 360px. */
.field-inline { flex-wrap: wrap; }
.field-inline .inline-input { min-width: 0; flex: 1 1 180px; }

/* ============================================================
   THE + MENU - 2026-09-11
   Owner: "find a nicer way for the floating plus menu on mobile, it is not looking
   nice". addfab.js draws one menu; this block decides its shape. On a laptop or a
   tablet it stays a popover beside the button, now with a heading and a way out. On
   a phone - or a phone on its side - it is a sheet rising from the bottom edge over a
   dimmed page: a handle, a title, big tiles. Same shapes as the side panel its
   choices open (rounded top corners, the same dim), so opening a form reads as the
   next step of one gesture, not a second, different surface.
   ============================================================ */
.add-fan-back { display: none; }
.add-fan-grab { display: none; }
.add-fan-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 6px 8px 10px;
}
.add-fan-titles { display: flex; flex-direction: column; min-width: 0; }
.add-fan-title { font-size: 14.5px; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
.add-fan-titles small { font-size: 12px; color: var(--muted); }
.add-fan-close {
  flex: none; width: 32px; height: 32px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--taupe-soft); color: var(--ink-2); display: grid; place-items: center;
}
.add-fan-close:hover { background: var(--accent-tint); color: var(--ink); }
.add-fan-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.add-fan-forms { display: flex; flex-direction: column; gap: 2px; }
.add-fan-section { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--hairline); }
.add-fan-drafts {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 10px 12px; min-height: 52px; border: 0; border-radius: 12px;
  background: transparent; font: inherit; color: var(--ink); cursor: pointer;
}
.add-fan-drafts:hover, .add-fan-drafts:focus-visible { background: var(--accent-tint); outline: none; }
.add-fan-ico.is-quiet { background: var(--taupe-soft); color: var(--ink-2); }
.add-fan-chev {
  margin-left: auto; margin-right: 6px; flex: none; width: 8px; height: 8px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); transition: transform .15s ease;
}
.add-fan-drafts.on .add-fan-chev { transform: rotate(225deg); }

@media (max-width: 700px), (max-height: 500px) {
  .add-fab {
    right: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    width: 56px; height: 56px;
    transition: transform .18s ease, opacity .18s ease, box-shadow .18s ease;
  }
  /* The sheet covers the button's corner and carries its own close. */
  body.add-fan-open .add-fab { opacity: 0; transform: scale(.85); pointer-events: none; }
  /* The page behind stays where it was while the sheet is up. */
  body.add-fan-open .main-area { overflow: hidden; }

  .add-fan-back:not(.hidden) {
    display: block; position: fixed; inset: 0; z-index: 931;
    background: rgba(20, 22, 30, .34);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    animation: addBackIn .2s ease-out;
  }
  .add-fan {
    left: 0; right: 0; bottom: 0; top: auto; width: auto; max-width: 640px; margin: 0 auto;
    z-index: 932; gap: 0; border: 0; border-radius: 22px 22px 0 0;
    padding: 2px 16px calc(18px + env(safe-area-inset-bottom, 0px));
    max-height: 88vh; max-height: 88dvh;
    box-shadow: 0 -14px 44px rgba(0, 0, 0, .20);
    animation: addSheetUp .28s cubic-bezier(.2, .85, .25, 1);
    transition: transform .2s ease;
    overscroll-behavior: contain;
  }
  .add-fan.dragging { transition: none; }
  .add-fan-grab {
    display: block; width: 40px; height: 5px; border-radius: 3px;
    background: color-mix(in srgb, var(--muted) 45%, transparent);
    margin: 8px auto 4px; touch-action: none;
  }
  .add-fan-head { padding: 6px 2px 14px; touch-action: none; }
  .add-fan-title { font-family: var(--serif); font-size: 22px; font-weight: 600; letter-spacing: 0; }
  .add-fan-titles small { font-size: 13px; }
  .add-fan-close { width: 44px; height: 44px; }

  /* The choices: two big tiles a row, and an odd last one across the whole row. */
  .add-fan-forms { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .add-fan-forms .add-fan-item {
    flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 12px;
    min-height: 124px; padding: 14px; border-radius: 16px;
    background: var(--bg); border: 1px solid var(--hairline);
    transition: transform .12s ease, background .12s ease;
  }
  .add-fan.entering .add-fan-forms .add-fan-item {
    animation: addTileIn .32s cubic-bezier(.2, .85, .25, 1) both;
    animation-delay: calc(var(--i, 0) * 35ms + 70ms);
  }
  .add-fan-forms .add-fan-item:active { transform: scale(.97); background: var(--accent-tint); }
  .add-fan-forms .add-fan-item:hover, .add-fan-forms .add-fan-item:focus-visible { background: var(--accent-tint); }
  .add-fan-forms .add-fan-ico {
    flex: none; width: 44px; height: 44px; border-radius: 13px;
    background: var(--accent, var(--primary)); color: #fff;
  }
  .add-fan-forms .add-fan-ico svg { width: 22px; height: 22px; }
  .add-fan-forms .add-fan-txt b { font-size: 15.5px; }
  .add-fan-forms .add-fan-txt small { font-size: 12.5px; line-height: 1.35; }
  .add-fan-forms .add-fan-item:last-child:nth-child(odd) {
    grid-column: 1 / -1; min-height: 0; flex-direction: row; align-items: center;
  }
  .add-fan-section { margin-top: 14px; padding-top: 12px; }
  .add-fan-drafts { min-height: 56px; padding: 10px 6px; }
  .add-fan-draftlist { padding: 2px 0 4px 56px; }
  .add-fan-draft { min-height: 44px; justify-content: center; }
}
/* A phone on its side is wide and short: the choices sit in one row, words only. */
@media (max-height: 500px) and (min-width: 560px) {
  .add-fan-forms { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 1fr; }
  .add-fan-forms .add-fan-item { min-height: 0; gap: 8px; padding: 12px; }
  .add-fan-forms .add-fan-item:last-child:nth-child(odd) { grid-column: auto; flex-direction: column; align-items: flex-start; }
  .add-fan-forms .add-fan-txt small { display: none; }
  .add-fan-head { padding-bottom: 8px; }
}
@keyframes addSheetUp { from { transform: translateY(100%); } to { transform: none; } }
@keyframes addBackIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes addTileIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .add-fan, .add-fan-back:not(.hidden), .add-fan.entering .add-fan-forms .add-fan-item { animation: none !important; }
}

/* ---- the + menu, polish after its first sweep (2026-09-11) ---- */
/* The popover's close is 32px for a mouse; on any touch screen it is a finger's. */
@media (pointer: coarse) { .add-fan-close { width: 44px; height: 44px; } }

/* The + steps aside while any sheet or dialog is up. It floats at z 930, above Team's
   sheets (120) and the older wizard (90), and on Team it sat on the Time off sheet's
   own "Remove". Nothing is added from under a dialog, so it simply isn't there. */
body:has(.sch-modal-back:not(.hidden), .wiz-backdrop:not(.hidden), .mwr-sheet-back:not(.hidden),
         .ph-modal-back:not(.hidden), .fl-modal-back:not(.hidden), .db-modal-back:not(.hidden),
         .modal-backdrop:not(.hidden)) .add-fab {
  opacity: 0; pointer-events: none;
}

/* Drafts, opened: a card you read down - one row per draft, the way in on the right.
   It was a card on the phone first, and the owner asked for the same on a computer
   (2026-09-11: "nice improvement on phone, please do the same for computer"): in the
   popover the drafts were bare lines hanging under the heading. It is one card at
   every size now, sat under the words "Continue a draft" wherever the menu is. */
.add-fan-draftlist {
  gap: 0; margin: 2px 8px 8px 62px; padding: 0;
  background: var(--bg); border: 1px solid var(--hairline); border-radius: 12px; overflow: hidden;
}
.add-fan-draft {
  position: relative; width: 100%; min-height: 48px; justify-content: center;
  padding: 9px 32px 9px 12px; border-radius: 0;
}
.add-fan-draft + .add-fan-draft { border-top: 1px solid var(--hairline); }
.add-fan-draft:hover, .add-fan-draft:focus-visible { background: var(--accent-tint); outline: none; }
.add-fan-draft::after {
  content: ''; position: absolute; right: 12px; top: 50%; width: 7px; height: 7px;
  border-right: 2px solid var(--muted); border-top: 2px solid var(--muted);
  transform: translateY(-50%) rotate(45deg);
}
.add-fan-draftlist .add-fan-more { display: block; padding: 8px 12px; border-top: 1px solid var(--hairline); }
@media (max-width: 700px), (max-height: 500px) {
  .add-fan-draftlist { margin: 4px 0 0 56px; border-radius: 14px; }
  .add-fan-draft { min-height: 52px; padding: 10px 34px 10px 14px; }
}

/* Team sheets on a phone: a list of rows becomes a list of cards. Time off's table put
   "Who / When / Reason / Edit Remove" in four columns of a 336px sheet, so a date
   range wrapped onto three lines and the two buttons stacked. */
@media (max-width: 560px) {
  .sch-modal .ws-table thead { display: none; }
  .sch-modal .ws-table, .sch-modal .ws-table tbody, .sch-modal .ws-table tr, .sch-modal .ws-table td { display: block; width: 100%; }
  .sch-modal .ws-table tr {
    border: 1px solid var(--hairline); border-radius: var(--r-md);
    padding: 10px 12px; margin-bottom: 8px; background: var(--card);
  }
  .sch-modal .ws-table td { border: 0; padding: 2px 0; }
  .sch-modal .ws-table td:first-child { font-weight: 600; }
  .sch-modal .ws-table td:last-child:not(:first-child) { display: flex; gap: 8px; justify-content: flex-end; margin-top: 6px; }
  .sch-modal .ws-table td:empty { display: none; }
}

/* ---- what a TF order says, and what Create does to its job card (2026-09-11) ---- */
.uz-facts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; margin-top: 8px; font-size: 13px; color: var(--ink-2); }
.uz-facts > span { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.uz-facts svg { width: 14px; height: 14px; flex: none; color: var(--muted); }
.suggestion .uz-facts { margin-top: 3px; gap: 2px 12px; font-size: 12px; }
.uz-cardnote {
  display: flex; align-items: flex-start; gap: 10px; margin: -4px 0 14px; padding: 10px 12px;
  border-radius: var(--r-md); background: var(--uz-tint, var(--taupe-soft));
  border-left: 3px solid var(--uz-accent, var(--ink-2)); font-size: 13.5px; line-height: 1.45; color: var(--ink);
}
.uz-cardnote svg { width: 16px; height: 16px; flex: none; margin-top: 2px; color: var(--uz-accent, var(--ink-2)); }
.uz-cardnote b { font-weight: 600; }

/* ---------- signed in AS somebody (super admin, 2026-09-12) ----------
   Unmissable on every screen, and INSIDE the one app bar, so no page's sticky rows
   (lined up under its 54px) move: an amber edge under the bar, and the pill that
   says who you are using the app as, with the way back. */
body.acting-as .top-strip { box-shadow: inset 0 -3px 0 var(--amber, #c77700); }
.as-pill {
  flex: none; display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%;
  border: 1px solid color-mix(in srgb, var(--amber, #c77700) 55%, transparent);
  background: color-mix(in srgb, var(--amber, #c77700) 14%, var(--card, #fff)); color: var(--ink);
  border-radius: 999px; padding: 4px 5px 4px 10px; font: inherit; font-size: 13px; cursor: pointer;
}
.as-pill .ico-slot svg { width: 15px; height: 15px; }
.as-pill-txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.as-pill-back {
  flex: none; background: var(--amber, #c77700); color: #fff; font-weight: 600;
  border-radius: 999px; padding: 3px 11px;
}
@media (max-width: 560px) { .as-pill-txt { max-width: 38vw; } }
@media (pointer: coarse) { .as-pill { min-height: 44px; } }
