/* ============================================================================
   LAUNCH FIXES v159 — loaded last.
   ========================================================================== */

/* ── 1. THE INTEGRATIONS RAIL STAYS PUT ───────────────────────────────────
   It was position:static, so scrolling the directory scrolled the category
   list away with it and you lost your place. It is navigation: it holds
   position and scrolls independently only when it is taller than the viewport. */
body .intg-rail9{
  position:sticky;top:12px;align-self:flex-start;
  max-height:calc(100vh - 96px);overflow-y:auto;overscroll-behavior:contain;
  scrollbar-width:thin}
body .intg-layout9{align-items:flex-start}
@media (max-width:900px){
  /* stacked on a phone — sticking a full-width block to the top would cover
     the results it is filtering */
  body .intg-rail9{position:static;max-height:none;overflow:visible}
}

/* ── 2. AUTOMATIONS: SPACE AND SEPARATION, NO TALLY ───────────────────────
   The tally bar is gone from the markup. What is left needs room to read:
   a rule is a paragraph of real text, not a dense row. */
body .auto-sec9+.auto-sec9{margin-top:34px}
body .auto-sec-head9{
  margin:0 0 12px;padding-bottom:9px;border-bottom:1px solid var(--line-1)}
body .auto-sec-head9 h3{font-size:12.5px}
body .auto-sec-body9{display:flex;flex-direction:column;gap:10px}
body .auto-sec-body9>.auto-card+.auto-card{border-top:1px solid var(--line-1)}
body .auto-card{padding:18px 20px;gap:16px}
body .auto-main .row-title{font-size:15px;line-height:1.35}
body .auto-main .row-sub{font-size:13.5px;line-height:1.6;margin-top:5px;max-width:64ch}
body .auto-flow9{margin-top:14px;padding:12px 14px;gap:6px 12px}
body .auto-flow-v9{font-size:12.5px;line-height:1.55}
body .auto-chan9{margin-top:12px;padding:11px 13px;line-height:1.6}
body .auto-param{margin-top:14px}
body .auto-presets-head9{margin:38px 0 14px;padding-top:26px}

/* ── 3. LIVE FLEET: THE SHEET IS A PEEK, NOT A TAKEOVER ───────────────────
   On a phone, opening a vehicle covered the whole screen, so the map you were
   looking at vanished. It takes the lower ~40% and scrolls inside itself —
   the map stays visible and in context, which is the point of a live map. */
@media (max-width:900px){
  body .cc-rail .ccp{
    position:fixed;left:0;right:0;bottom:0;top:auto;
    height:58vh;max-height:58vh;
    display:flex;flex-direction:column;
    border-top:1px solid var(--line-2);
    box-shadow:0 -10px 34px rgba(0,0,0,.24)}
  body .cc-rail .ccp>*{flex:0 0 auto}
  /* the body of the panel is what scrolls, not the page behind it */
  body .cc-rail .ccp>*:last-child{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain}
  body .cc-rail .ccp-head{position:sticky;top:0;z-index:2;background:var(--panel)}
}

/* the sheet handle keeps its grab bar; the chevron beside it said nothing the
   bar did not already say */
body .cc-sheet-ic{display:none}
body .cc-sheet-handle{padding:8px 0 6px}
body .cc-sheet-bar{width:44px;height:4px}

/* ── 4. DASHBOARD HEADER ALIGNS ───────────────────────────────────────────
   The caret and the person glyph are gone from the markup. The remaining
   controls — refresh stamp, group select, "…", Add Widgets, fullscreen — sit on
   one baseline instead of three different heights. */
body .dash-title-row{display:flex;align-items:center;gap:10px;min-width:0}
body .page-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
body .page-actions>*{align-self:center}
body .dash-scope-wrap{display:flex;align-items:center}
body .dash-scope-wrap>select,
body .page-actions .dash-stamp,
body .page-actions .btn,
body .page-actions .icon-btn{height:34px;min-height:34px}
body .dash-scope-wrap>select{padding-top:0;padding-bottom:0}
@media (max-width:640px){
  body .page-actions{width:100%;justify-content:flex-start}
}

/* ── 5. THE ADD-FILTER MENU SITS ON THE DRAWER ────────────────────────────
   It is opened from a button inside a fixed drawer, so it has to outrank it.
   (The reason it used to appear in the top-left is fixed in app.js: the old
   call dismissed the drawer, detaching the anchor before it could be measured.) */
body .flt-addpop9{z-index:960;min-width:220px}
body .flt-add9{width:100%;justify-content:center;margin-top:6px}

/* the group select was 32px beside 34px neighbours, so the row sat a pixel out */
body .page-actions .dash-scope-wrap>select{
  height:34px;min-height:34px;padding-top:0;padding-bottom:0;line-height:34px}
/* ── 6. THE ADD-FILTER PICKER, IN THE PANEL ───────────────────────────────
   Expands under the button rather than floating. A floating layer cannot work
   here: every menu helper in this codebase routes through closeFloats9, which
   removes .dw9-host and takes the panel with it. */
body .flt-pick9{
  margin-top:8px;border:1px solid var(--line-2);background:var(--surface-2)}
body .flt-pick9[hidden]{display:none}
body .flt-picksearch9{margin:8px;background:var(--panel)}
body .flt-picklist9{max-height:250px;overflow-y:auto;overscroll-behavior:contain;padding-bottom:4px}
body .flt-pickrow9{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:9px 12px;border:0;border-top:1px solid var(--line-1);
  background:none;text-align:left;font-size:13px;color:var(--fg);cursor:pointer}
body .flt-pickrow9 svg{width:13px;height:13px;color:var(--muted);flex:0 0 13px}
@media (hover:hover){
  body .flt-pickrow9:hover{background:var(--hover)}
  body .flt-pickrow9:hover svg{color:var(--tab-active,#D97757)}
}
body .flt-nohit9{padding:14px 12px;font-size:12.5px;color:var(--muted)}
body .flt-add9.on{background:var(--surface-3);color:var(--fg);border-color:var(--line-2)}
/* ── 7. THE RECORD PHOTO CONTROL ──────────────────────────────────────────
   One control on both the vehicle and the contact form. A square preview that
   also accepts a dropped file, with the actions beside it — the button is a
   normal secondary action, not a giant dashed dropzone that dominates the form. */
body .vphoto-box9{
  display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}
body .vphoto-prev9{
  flex:0 0 104px;width:104px;height:104px;
  display:flex;align-items:center;justify-content:center;
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:0;
  overflow:hidden;color:var(--muted)}
body .vphoto-prev9 img{width:100%;height:100%;object-fit:cover;display:block}
body .vphoto-prev9 svg{width:30px;height:30px}
body .vphoto-prev9.drag9{border-color:var(--tab-active,#D97757);background:var(--panel)}
body .vphoto-act9{
  display:flex;flex-direction:column;align-items:flex-start;gap:8px;min-width:0;flex:1 1 200px}
body .vphoto-act9 .btn{min-width:132px;justify-content:center}
body .vphoto-act9 .field-hint{margin:2px 0 0;font-size:12px;line-height:1.5;color:var(--muted)}
@media (max-width:640px){
  body .vphoto-prev9{flex:0 0 84px;width:84px;height:84px}
  body .vphoto-act9 .btn{min-width:0;width:100%}
}

/* ── 8. THE PLAN GATE ─────────────────────────────────────────────────────
   A capability you cannot use should look unavailable rather than broken:
   the control stays visible and readable, dimmed, with the plan named. */
body .rb-act9.locked9 .check-lbl9>span:first-of-type{color:var(--muted)}
body .rb-plan9{
  display:inline-flex;align-items:center;height:18px;padding:0 7px;margin-left:8px;
  background:var(--surface-3);border:1px solid var(--line-2);
  font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--fg-2)}
body .auto-chan9.locked9{border-color:var(--line-2)}

/* ── 9. DASHBOARD WIDGETS KEEP THEIR OWN HEIGHT ───────────────────────────
   Widgets are meant to be different sizes. What they were NOT meant to be is
   inflated: `.dash-grid{align-items:stretch}` made every card in a row match
   the tallest one in that row, so "Open Issues" — two lines of content — was
   stretched to 380px beside a chart and sat 176px empty. The emptiness was not
   the widget's own size, it was a neighbour's.

   Each card now stops at its content. Rows are no longer flush along the
   bottom, which is the correct trade: a per-widget height is what was asked
   for, and a short widget that looks short is honest, where a short widget
   inflated to 380px just looks broken.

   The actual culprit was `.dash-grid > *{height:100%}` in the shared grid rule.
   align-items cannot override that: the card is explicitly asking for the full
   row height, and the row is as tall as its tallest member. Both have to go for
   the widget to keep its own size.

   Scoped to .dash-grid only — the other grids in that shared rule are
   equal-content card grids where stretching is right. */
body .dash-grid{align-items:start}
body .dash-grid>*{height:auto}
/* A widget asked to be tall (a chart, the map) sets its own height and still
   gets it, because it is the content that defines the row now. */
body .dash-grid>.dw{align-self:start}

/* ── 23. MAP SETTINGS: A TIGHTER HEAD AND VISIBLE ROW SEPARATION ───────────
   The four rows ran together with nothing between them, so you could not tell
   where one setting ended and the next began, and the title block added its own
   padding on top of the modal's. Rows get a divider; the head gives back the
   space it was wasting.

   The rows use their own classes (.ms-row9) rather than the settings-page
   primitives. Those primitives are styled by five stylesheets — including width
   rules sized for a 360px settings column and two media queries that restack
   them — and inside a narrow modal they measured: label block 0px wide, control
   block 441px (the whole row) with the label text overflowing it, and rows
   112–412px tall for one line of text. Owning the markup is what makes this
   layout predictable instead of a specificity fight. */
body .map-set-rows9{display:flex;flex-direction:column}
body .ms-row9{
  display:flex;flex-direction:row;flex-wrap:nowrap;
  align-items:center;justify-content:space-between;gap:20px;padding:14px 0}
body .ms-row9+.ms-row9{border-top:1px solid var(--line-1,var(--border))}
body .ms-row9:first-child{padding-top:2px}
body .ms-row9:last-child{padding-bottom:2px}
body .ms-row-tx9{flex:1 1 auto;min-width:0}
body .ms-row-t9{font-size:13.5px;font-weight:620;color:var(--text);line-height:1.35}
body .ms-row-s9{
  margin-top:3px;font-size:12.5px;line-height:1.5;color:var(--muted);max-width:52ch}
/* the control column hugs its switch — never wider than what it holds */
body .ms-row-c9{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end}
body .ms-row-c9 .set-toggle{flex:0 0 44px;width:44px;min-width:44px;max-width:44px}
/* Map type is the one row that stacks: a four-way segment picker does not belong
   squeezed to the right of its own label. */
body .ms-row9.ms-row-stack9{flex-direction:column;align-items:stretch;gap:10px}
body .ms-row9.ms-row-stack9 .seg{margin:0;width:100%}
body .ms-row9.ms-row-stack9 .seg-btn{flex:1 1 0;min-width:0}

/* ── 22. THE TOAST WAS A FIXED 280px BOX ───────────────────────────────────
   design-system.css:335 sets min-width:280px, so every toast was the same size
   whatever it said. Measured: "Saved" is 38px of text in a 280px box — 170px of
   empty space between the message and the close button, which is the gap you can
   see at the bottom of the screen. It now hugs its content, with the existing
   max-width still capping long messages, and a small floor so a one-word toast
   is not a tiny sliver. */
body .toast{
  width:fit-content;
  min-width:0;
  max-width:min(420px,calc(100vw - 28px))}
body .toast .toast-msg{min-width:0}

/* ── 19. TABS MUST NOT RESIZE WHEN SELECTED ────────────────────────────────
   `.detail-tab` was font-weight 500 idle / 600 active (design-system.css:3130).
   Bold text is WIDER, so activating a tab grew it and pushed its neighbours —
   the notification panel visibly jumped between Inbox and Archived. Weight is
   now constant and selection is carried by colour + surface only, so the strip
   never reflows.

   The active background was `--tab-active` (brand orange) at 12%, which reads
   as a muddy wash rather than a selection — and orange indicators were meant to
   be gone app-wide. */
body .detail-tab,
body .detail-tab.on,
body .detail-tab.active,
body .veh-tab,
body .veh-tab.on,
body .ntf-tab,
body .ntf-tab.active,
body .sv-tab,
body .sv-tab.active{font-weight:560!important}
/* Underline, not a filled pill. A grey box floating behind one tab reads as a
   stray rectangle; an underline reads as "you are here" and is what every tab
   strip in this class of product uses. Ink-coloured rather than brand orange,
   because orange active indicators were removed app-wide. */
body .detail-tab,
body .veh-tab,
body .ntf-tab,
body .tk-tab{
  position:relative;background:none!important;border-radius:0;
  color:var(--muted);padding:0 12px}
body .detail-tab.on,
body .detail-tab.active,
body .veh-tab.on,
body .ntf-tab.active,
body .tk-tab.active{color:var(--text)!important}
/* the bar is always present and only becomes visible when selected, so the tab
   never changes size — the reflow that made the panel jump */
body .detail-tab::after,
body .veh-tab::after,
body .ntf-tab::after,
body .tk-tab::after{
  content:''!important;display:block!important;position:absolute;
  left:10px;right:10px;bottom:0;height:2px;background:var(--text);
  opacity:0;transition:opacity .12s ease}
/* background is restated here: an existing `.detail-tab.on::after` rule sets it
   to var(--accent) and outranks the base selector, so the active bar came out
   brand-orange while the idle one was ink. */
body .detail-tab.on::after,
body .detail-tab.active::after,
body .veh-tab.on::after,
body .ntf-tab.active::after,
body .tk-tab.active::after{opacity:1;background:var(--text)!important}
@media (hover:hover){
  body .detail-tab:hover,
  body .ntf-tab:hover{color:var(--text)}
  body .detail-tab:not(.on):not(.active):hover::after{opacity:.28}
}

/* ── 20. THE LABEL PICKER ──────────────────────────────────────────────────
   Two separate faults, both visible in one screenshot.

   (a) The double white box. `.lbl-box9:focus-within` draws its own border plus a
   3px ring, and the generic `body input:focus-visible` rule then draws a 2px
   --accent outline on the bare input INSIDE it. In dark mode --accent is
   near-white, so focusing the field produced two stacked white rectangles. The
   inner input has no box of its own, so it must not draw one. */
body .lbl-box9 input:focus,
body .lbl-box9 input:focus-visible{outline:none!important;box-shadow:none!important}
body .lbl-box9:focus-within{
  border-color:var(--text-2)!important;
  box-shadow:0 0 0 2px rgba(255,255,255,.10)!important}
body.light .lbl-box9:focus-within{box-shadow:0 0 0 2px rgba(23,21,18,.10)!important}

/* (b) `.lbl-row9.on` had NO rule anywhere — the class was applied on click and
   styled nothing, so picking a label looked like a dead control. The markup
   already carries a `.lbl-tick9` span, so that becomes the checkbox rather than
   adding a second indicator with pseudo-elements. It keeps its width whether
   ticked or not, so rows do not shift when you select one. */
body .lbl-row9{border-radius:0;color:var(--text-2)}
body .lbl-tick9{
  flex:0 0 15px;width:15px;height:15px;box-sizing:border-box;
  display:inline-flex;align-items:center;justify-content:center;
  border:1.5px solid var(--border);background:transparent;color:transparent}
body .lbl-tick9 svg{width:11px;height:11px;stroke-width:2.6}
body .lbl-row9.on{background:rgba(255,255,255,.10);color:var(--text);font-weight:600}
body.light .lbl-row9.on{background:rgba(23,21,18,.07)}
body .lbl-row9.on .lbl-tick9{
  border-color:var(--text);background:var(--text);color:var(--surface)}
/* the create-as-you-type row is an action, not a checkbox */
body .lbl-row9.lbl-new9 .lbl-tick9{border:0;background:none;color:var(--text-2)}
@media (hover:hover){
  body .lbl-row9:hover{background:rgba(255,255,255,.06)}
  body.light .lbl-row9:hover{background:rgba(23,21,18,.04)}
  body .lbl-row9.on:hover{background:rgba(255,255,255,.14)}
  body.light .lbl-row9.on:hover{background:rgba(23,21,18,.10)}
}

/* ── 21. SEGMENT BUTTON: WHITE TEXT ON A LIGHT SURFACE ─────────────────────
   `.seg-btn.active{color:#FFF}` with `background:var(--surface-3)`. In light
   mode --surface-3 is a pale grey, so the selected segment was white-on-pale —
   effectively invisible. This is the "Map type" picker in Map Settings, among
   others. The label follows the theme's text colour instead of a fixed white. */
body .seg-btn.active,
body .setx-tabs .seg-btn.active,
body .detail-tab.active{color:var(--text)!important}

/* ── 34. METER ENTRY: THE READING YOU ARE REPLACING ────────────────────────
   Neutral while it is just context; a warning once the typed value goes below
   the last reading, which is nearly always a typo. */
body .meter-last9{margin-top:6px;font-variant-numeric:tabular-nums}
body .meter-last9.warn9{color:var(--warn,#B07E14);font-weight:600}

/* ── 33. SETTINGS RAIL: THE LAST ORANGE ACTIVE STATE ───────────────────────
   Measured at 13% brand orange — the same wash removed from the sidebar nav and
   the tab strips, still living here. Selection is surface + ink weight, as
   everywhere else. */
body .setx-rail .setx-nav-ic.on,
body .setx-rail .setx-nav-ic.active,
body .setx-rail button.on,
body .setx-rail button.active,
body .setx-rail a.on,
body .setx-rail a.active{
  background:rgba(255,255,255,.09)!important;color:var(--text)!important;font-weight:640}
body.light .setx-rail .setx-nav-ic.on,
body.light .setx-rail .setx-nav-ic.active,
body.light .setx-rail button.on,
body.light .setx-rail button.active,
body.light .setx-rail a.on,
body.light .setx-rail a.active{background:rgba(23,21,18,.07)!important}
body .setx-rail .on svg,
body .setx-rail .active svg{color:var(--text)}
@media (hover:hover){
  body .setx-rail button:hover,
  body .setx-rail a:hover{background:rgba(255,255,255,.05)}
  body.light .setx-rail button:hover,
  body.light .setx-rail a:hover{background:rgba(23,21,18,.04)}
}

/* ── 32. REPORT OUTPUT: GROUPED VIEW ───────────────────────────────────────
   A header bar per distinct value with its row count, then that group's own
   table — the reference's "Layout: Group by …". */
body .rep-groupby9{display:flex;align-items:center;flex:0 0 auto}
body .rep-groupby9 select{height:32px;min-height:32px;border-radius:0}
body .rep-groups9{display:flex;flex-direction:column;gap:18px}
body .rep-group9{border:1px solid var(--line-2)}
body .rep-group-head9{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  padding:9px 13px;background:var(--surface-2);border-bottom:1px solid var(--line-2)}
body .rep-group-k9{
  font-size:11px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
body .rep-group-v9{font-size:13.5px;font-weight:640;color:var(--text)}
body .rep-group-n9{
  margin-left:auto;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
@media (max-width:640px){
  body .rep-group-n9{margin-left:0;flex:1 0 100%}
}

/* ── 31. FILTERS TRIGGER ───────────────────────────────────────────────────
   Funnel, the word, then the active count in a badge. The label used to become
   "2 Filters", so the control resized every time a filter was added. */
body .wv-fcount{display:inline-flex;align-items:center;gap:7px}
body .wv-fic9{display:inline-flex;flex:0 0 14px}
body .wv-fic9 svg{width:14px;height:14px}
body .wv-fbadge9{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:18px;height:18px;padding:0 5px;border-radius:999px;
  background:var(--text);color:var(--surface);
  font-size:11px;font-weight:700;font-variant-numeric:tabular-nums}

/* ── 30. PHOTO CONTROL ─────────────────────────────────────────────────────
   Reference layout: a solid Pick File button with the drop target beside it and
   a status line naming the chosen file. Ours showed a fixed hint whatever you
   did, so nothing confirmed a drop had landed. Our preview square stays. */
body .vphoto-pick9{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
body .vphoto-drop9{
  display:inline-flex;align-items:center;height:30px;padding:0 12px;
  border:1px dashed var(--line-2);background:transparent;color:var(--muted);
  font-size:12.5px;cursor:pointer}
body .vphoto-drop9.drag9,
body .vphoto-prev9.drag9{border-color:var(--text-2);color:var(--text)}
body .vphoto-stat9{
  margin:7px 0 0;font-size:12px;font-style:italic;color:var(--muted)}
body .vphoto-stat9.has9{font-style:normal;font-weight:560;color:var(--text)}
body .vphoto-sub9{margin-top:6px}
@media (max-width:640px){
  body .vphoto-pick9 .btn,body .vphoto-drop9{width:100%;justify-content:center}
}

/* ── 29. PER-WIDGET WIDTH ──────────────────────────────────────────────────
   Set from the widget's own ... menu and remembered. data-span9 wins over the
   .dw-w2 the widget definition may have passed, because the person looking at
   the dashboard outranks the default. */
body .dash-grid>.dw[data-span9="3"]{grid-column:span 3}
body .dash-grid>.dw[data-span9="6"]{grid-column:span 6}
body .dash-grid>.dw[data-span9="12"]{grid-column:1 / -1}
@media (max-width:900px){
  body .dash-grid>.dw[data-span9]{grid-column:auto}
}

/* ── 28. CHART TOOLTIP: STRUCTURE ONLY, OUR COLOURS KEPT ───────────────────
   The tooltip stays the dark pill it already is. What was missing is the
   reading order and the pointer: the reference puts the period first with the
   value bold beside it on ONE line, drops the comparison to its own line, and
   points a small tail at the bar being read. */
body .chart-tip{
  display:flex;align-items:baseline;gap:7px;flex-wrap:wrap;
  white-space:nowrap;pointer-events:none}
body .chart-tip span{order:0;font-size:12px;opacity:.75}
body .chart-tip b{order:1;font-size:13px;font-weight:680}
body .chart-tip .chart-tip-cmp{
  order:2;flex:1 0 100%;margin-top:2px;font-size:11px;line-height:1.35;opacity:.75}
/* tail, drawn in the tooltip's own background so it follows the theme */
body .chart-tip::after{
  content:'';position:absolute;left:50%;bottom:-4px;transform:translateX(-50%) rotate(45deg);
  width:8px;height:8px;background:inherit}

/* the hovered bar lifts and its axis label comes forward */
body .bar-col.on .bar-fill{filter:brightness(1.15)}
body .bar-col.on .bar-lbl{color:var(--text);font-weight:620}

/* ── 27. WIDGET TITLE IS THE DRILL-THROUGH ─────────────────────────────────
   `opts.link` existed but only became a dataset attribute, so all 12 widgets
   were dead ends. The glyph is the affordance; it stays dim until hover so a
   dashboard of 12 widgets is not 12 competing icons. */
body .dw-title-link9{
  display:inline-flex;align-items:center;gap:6px;
  border:0;background:none;padding:0;cursor:pointer;
  font:inherit;color:inherit;text-align:left}
body .dw-open9{display:inline-flex;opacity:.35;transition:opacity .12s ease}
body .dw-open9 svg{width:12px;height:12px}
@media (hover:hover){
  body .dw-title-link9:hover{text-decoration:underline;text-underline-offset:2px}
  body .dw-title-link9:hover .dw-open9{opacity:.9}
}
body .dw-title-link9:focus-visible .dw-open9{opacity:.9}

/* ── 26. STATUS-LIST WIDGET BODY ───────────────────────────────────────────
   The second widget body type in the reference: dot, label, and the count in a
   tinted pill. Replaces the donut on Vehicle Status, where a chart made you
   decode an arc to read a number. */
body .statlist9{display:flex;flex-direction:column}
body .statlist-row9{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:8px 4px;border:0;background:none;cursor:pointer;text-align:left}
body .statlist-row9+.statlist-row9{border-top:1px solid var(--line-1,var(--border))}
body .statlist-dot9{
  flex:0 0 8px;width:8px;height:8px;border-radius:50%;background:var(--muted)}
body .statlist-l9{
  flex:1 1 auto;min-width:0;font-size:13px;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* the count pill — a light wash of its own status colour, darker text */
body .statlist-n9{
  flex:0 0 auto;min-width:26px;padding:2px 8px;border-radius:999px;
  font-size:12px;font-weight:660;text-align:center;
  font-variant-numeric:tabular-nums;
  background:var(--surface-3);color:var(--text-2)}
body .statlist-dot9.tone-ok{background:var(--ok,#1F7A4C)}
body .statlist-dot9.tone-warn{background:var(--warn,#9A6B12)}
body .statlist-dot9.tone-danger{background:var(--danger,#B4342A)}
body .statlist-n9.tone-ok{
  background:color-mix(in srgb,var(--ok,#1F7A4C) 16%,transparent);color:var(--ok,#1F7A4C)}
body .statlist-n9.tone-warn{
  background:color-mix(in srgb,var(--warn,#9A6B12) 18%,transparent);color:var(--warn,#9A6B12)}
body .statlist-n9.tone-danger{
  background:color-mix(in srgb,var(--danger,#B4342A) 16%,transparent);color:var(--danger,#B4342A)}
@media (hover:hover){
  body .statlist-row9:hover{background:rgba(255,255,255,.05)}
  body.light .statlist-row9:hover{background:rgba(23,21,18,.04)}
}

/* ── 25. A TRIGGER LOOKS OPEN WHILE ITS MENU IS OPEN ───────────────────────
   Measured: a filter pill's background and border were identical open and
   closed, so a panel on screen had nothing visually tying it to the control it
   came from. popAnchor9 marks the anchor, so this covers pills, the gear and
   every overflow menu at once. */
body .pop-open9{background:var(--surface-3)!important;border-color:var(--text-2)!important}
body .pop-open9 .wv-fpill-chev,
body .pop-open9 .chev,
body .pop-open9 svg{transform:rotate(180deg);transition:transform .14s ease}
body .wv-fpill .wv-fpill-chev,
body .wv-fpill svg{transition:transform .14s ease}
body .icon-btn.pop-open9 svg{transform:none}

/* ── 24. STAT WIDGETS: NUMBER-LED TILES, NOT LABELLED LINES ────────────────
   Measured against the reference dashboard: the same "Service Reminders"
   widget is ~90px there and was 175px here — a 64px header over a 76px body,
   with the two stats stacked as "Overdue : 0 ›" lines at 13.5px, so the number
   was the same size as its own label and nothing was scannable.
   The two stats now sit side by side as number-over-caption tiles. */
body .card.dw .card-head{min-height:0;padding:9px 14px 5px}
/* the h3 carried a 38px min-height, which was the whole 52px header */
body .card.dw .card-head h3{
  font-size:12.5px;font-weight:640;letter-spacing:.01em;
  min-height:0;height:auto;padding:0;margin:0;line-height:1.35}
body .card.dw .card-head .dw-menu{align-self:flex-start;margin-top:-1px}
body .card.dw .dw-body{padding:2px 14px 12px}

/* Two stats across when they FIT, stacked when they do not.
   This was `grid-template-columns:repeat(auto-fit,minmax(84px,1fr))`. The 84px
   is an explicit minimum, so auto-fit counted how many 84px tracks fit and
   ignored the content entirely - a money value 143px wide was handed a 94px
   track and ran straight over its neighbour ("US$0.19US$46,785"), with a
   scrollbar under it. Flex wrap sizes from the content instead: a pair that
   fits shares the line, a pair that does not wraps and stacks. */
body .dw-body>.dw-rows{
  display:flex;flex-wrap:wrap;gap:4px 10px;overflow-x:visible}
/* Each stat asks for half the line and refuses to go under its own min-content.
   Short values ("0", "7") sit side by side; a value that cannot fit in half
   ("US$46,785" needs 143px of a 201px row) forces the pair to wrap and stack. */
body .dw-body>.dw-rows>.dw-row{flex:1 1 calc(50% - 5px);min-width:min-content}
body .dw-body>.dw-rows>.dw-row{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:3px;height:auto;min-height:0;padding:10px 4px;border:0;text-align:center}
/* NO min-width:0 here, deliberately. Forcing the tracks to shrink below their
   content did put two stats on one line, but a money value then ran straight
   over its neighbour: "US$0.19US$46,785" in a 94px track. auto-fit must be
   allowed to see the real min-content width, so a pair that genuinely fits
   sits side by side and a pair that does not falls to one column and stacks. */
body .dw-body>.dw-rows>.dw-row .dw-n{max-width:100%}
/* THE narrow-widget empty space, measured. `.card` gives every card 18px of
   padding and `.dw-body` adds 14px more, so a 235px widget spent 64px - 27% of
   its width - on horizontal padding. That left the stat grid 169px, and
   `minmax(84px,1fr)` needs 178px for two tracks, so it fell to ONE column and
   the two numbers stacked: 253px of card for a pair that fits in 170px.
   The card keeps its vertical padding and hands the horizontal back to the head
   and body, which already set their own. */
body .card.dw{padding-left:0;padding-right:0}
body .card.dw>.card-head{padding-left:16px;padding-right:16px}
body .card.dw .dw-body{padding-left:16px;padding-right:16px}
/* the value leads; `order` reverses the source without touching the markup.
   Its colour comes from the tone set in statRow — the number is what the eye
   goes to, so the severity lives there and the caption stays quiet. */
body .dw-body>.dw-rows>.dw-row .dw-n{
  order:-1;font-size:26px;line-height:1.05;font-weight:600;
  font-variant-numeric:tabular-nums}
body .dw-body>.dw-rows>.dw-row .dw-l2{
  order:1;font-size:11.5px;line-height:1.3;font-weight:500;
  color:var(--muted)!important;letter-spacing:.01em}
/* the colon and per-row chevron are line-grammar; a tile does not need them */
body .dw-body>.dw-rows>.dw-row .dw-sep{display:none}
body .dw-body>.dw-rows>.dw-row .dw-chev{display:none}
@media (hover:hover){
  body .dw-body>.dw-rows>.dw-row:hover{background:rgba(255,255,255,.05)}
  body.light .dw-body>.dw-rows>.dw-row:hover{background:rgba(23,21,18,.04)}
}

/* ── 16. NO GRADIENTS ON SURFACES ──────────────────────────────────────────
   `.card`, `.kpi` and `.row` each carried a linear-gradient, so every card,
   every stat cell and every list row faded from light at the top to darker at
   the bottom. Stacked in a grid that reads as banding across the page. A panel
   is one surface: one colour. */
body .card,
body .kpi,
body .row,
body .card.dw,
body .stat-cell,
body .stat-strip9>*{
  background:var(--surface);background-image:none}
body.light .card,
body.light .kpi,
body.light .row{background:var(--panel,#fff);background-image:none}

/* ── 17. THE GROUP SELECT SAT 8px ABOVE ITS OWN WRAPPER ────────────────────
   Measured: wrapper 32px tall at y=122, the <select> inside it 34px tall at
   y=114 — poking out of the top of its container and out of line with every
   button beside it. The wrapper and the control are now the same 34px box, and
   "Add Widgets" gets square edges like its neighbours instead of an 8px radius. */
body .page-actions .dash-scope-wrap{
  height:34px;min-height:34px;display:flex;align-items:stretch;flex:0 0 auto}
body .page-actions .dash-scope-wrap>select{
  height:34px;min-height:34px;max-height:34px;margin:0;box-sizing:border-box;
  align-self:stretch;border-radius:0}
body .page-actions>.btn,
body .page-actions>.icon-btn,
body .page-actions .btn.btn-ghost{border-radius:0}

/* ── 18. MOBILE PAGE HEADER ────────────────────────────────────────────────
   On a phone the action row wrapped mid-way, leaving one button stranded on a
   second line beside empty space. The actions now take a full row of their own
   below the title, the primary one sized to its label rather than the viewport,
   so nothing is stranded. Desktop is untouched — these rules only exist inside
   the query. */
@media (max-width:640px){
  body .page-head{flex-direction:column;align-items:stretch;gap:10px}
  body .page-actions{width:100%;justify-content:flex-start;flex-wrap:wrap;gap:8px}
  body .page-actions .dash-stamp{order:-1;flex:1 0 100%;padding:0}
  body .page-actions>.btn{flex:0 1 auto}
  body .page-actions .dash-scope-wrap{flex:1 1 140px}
  body .page-actions .dash-scope-wrap>select{width:100%}
  /* A phone is already fullscreen, so the fullscreen toggle does nothing there —
     and keeping it pushed the row to a third line. */
  body .page-actions .dash-full{display:none}
}

/* ── 15. TRIP STATE-CHANGE SHEET ───────────────────────────────────────────
   Start / Complete / Reopen each show what will be recorded before it is, the
   way a dispatch product confirms a state change: the stamp, the duration,
   whether the deadline was met, and what is still missing. */
body .tss-intro9{margin:0 0 12px;font-size:13.5px;line-height:1.55;color:var(--fg-2)}
body .tss9{
  display:flex;flex-direction:column;
  border:1px solid var(--line-2);background:var(--surface-2)}
body .tss-row9{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding:10px 13px}
body .tss-row9+.tss-row9{border-top:1px solid var(--line-1)}
body .tss-l9{font-size:12.5px;color:var(--muted);min-width:0}
body .tss-v9{
  font-size:13px;font-weight:620;color:var(--fg);text-align:right;
  font-variant-numeric:tabular-nums;white-space:nowrap}
body .tss-v9.ok9{color:var(--ok,#1F7A4C)}
body .tss-v9.warn9{color:var(--warn,#9A6B12)}
body .tss-v9.bad9{color:var(--danger,#B4342A)}
@media (max-width:520px){
  body .tss-row9{flex-direction:column;gap:2px}
  body .tss-v9{text-align:left}
}

/* ── 12. THE FIELD-SEARCH BOX HAD RULES ABOVE AND BELOW IT ────────────────
   Three horizontal lines stacked up in a few pixels: `.fc-top` carried a
   border-bottom, then the search box drew its own full border. The search is a
   control inside a card, not a section of it — it needs no rules at all. */
body .fields-card .fc-top{border-bottom:0;padding-bottom:0}
body .fields-card .fc-search{
  border:0;background:transparent;margin:2px 0 6px;padding-left:0}
body .fields-card .fc-search input{
  background:transparent;border:0;padding-left:6px}
/* the focus ring belongs to the field, so it still appears on the input itself */
body .fields-card .fc-search input:focus-visible{outline-offset:1px}
body .fields-card .fc-search svg{color:var(--muted)}

/* ── 13. THE ACTIVE NAV ITEM WAS AN ORANGE WASH ────────────────────────────
   13% brand orange behind an orange glyph. In light mode that reads as a muddy
   tint rather than a selection, and orange indicators were supposed to be gone
   app-wide. Selection is now carried by surface + ink weight, which is legible
   in both themes and does not compete with the brand colour. */
body .sidebar .nav-item.active{
  background:var(--surface-3);color:var(--fg);font-weight:640;border-color:transparent}
body .sidebar .nav-item.active svg{color:var(--fg)}
body.light .sidebar .nav-item.active{
  background:rgba(23,21,18,.06);border-color:transparent}
body:not(.light) .sidebar .nav-item.active{background:rgba(255,255,255,.09)}
@media (hover:hover){
  body .sidebar .nav-item.active:hover{background:rgba(23,21,18,.09)}
  body:not(.light) .sidebar .nav-item.active:hover{background:rgba(255,255,255,.13)}
}

/* ── 14. DASHBOARD HEADER CONTROLS ON ONE LINE ─────────────────────────────
   The group select rendered as a rounded pill against square-edged buttons and
   sat a pixel off their baseline. Everything in that row is now the same height
   with the same edges. */
body .page-actions .dash-scope-wrap>select,
body .page-actions .btn,
body .page-actions .icon-btn,
body .page-actions .dash-stamp{
  height:34px;min-height:34px;border-radius:0;align-self:center}
body .page-actions .dash-scope-wrap>select{
  padding:0 30px 0 11px;line-height:34px;border:1px solid var(--line-2)}
body .page-actions .dash-stamp{
  display:inline-flex;align-items:center;gap:6px;border:0;background:none;
  padding:0 4px 0 0;font-size:12.5px;color:var(--muted);white-space:nowrap}

/* ── 10. THE TRIP RECORD LEADS WITH ITS STOPS ─────────────────────────────
   The lifecycle stepper is gone from the markup — its first step was stamped
   `created_at`, so every trip appeared to begin on the day it was typed in.
   Stops now sit directly under the map, because they hold the only timestamps
   the freight actually produced. */
body .trip-stops-card9{margin-top:14px}
body .trip-stops-card9 .card-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
/* "3 of 5 stops · next Kigali Depot" — the one line a dispatcher reads first */
body .trip-prog9{
  font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}
@media (max-width:640px){
  body .trip-stops-card9 .card-head{gap:4px}
  body .trip-prog9{flex:1 0 100%}
}

/* ── 11. ORDER DOCUMENTS ──────────────────────────────────────────────────
   The order was the one record type that could not hold a file of its own.
   Same list anatomy as the vehicle and driver document tabs: a header that
   states the count and what belongs here, then flat rows with square edges. */
body .od-head9{
  display:flex;align-items:flex-start;justify-content:space-between;gap:16px;
  flex-wrap:wrap;padding-bottom:14px;border-bottom:1px solid var(--line-2)}
body .od-head9 b{display:block;font-size:14px;font-weight:650}
body .od-sub9{
  display:block;margin-top:4px;max-width:70ch;
  font-size:12.5px;line-height:1.6;color:var(--muted)}
body .od-act9{display:flex;align-items:center;gap:8px;flex:0 0 auto}

body .od-list9{display:flex;flex-direction:column}
body .od-row9{
  display:flex;align-items:center;gap:13px;
  padding:13px 2px;border-bottom:1px solid var(--line-1)}
body .od-ic9{
  flex:0 0 30px;width:30px;height:30px;
  display:flex;align-items:center;justify-content:center;
  background:var(--surface-2);border:1px solid var(--line-2);color:var(--muted)}
body .od-ic9 svg{width:15px;height:15px}
body .od-main9{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
body .od-main9 b{
  font-size:13.5px;font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
body .od-main9 span{font-size:12px;color:var(--muted)}
body .od-rowact9{display:flex;align-items:center;gap:14px;flex:0 0 auto}
body .od-rowact9 .linkish{
  border:0;background:none;padding:0;cursor:pointer;
  font-size:12.5px;font-weight:600;color:var(--fg-2)}
body .od-rowact9 .linkish.danger{color:var(--danger,#B4342A)}
@media (hover:hover){body .od-rowact9 .linkish:hover{text-decoration:underline}}

@media (max-width:640px){
  /* the actions drop under the filename rather than squeezing it to an ellipsis */
  body .od-row9{align-items:flex-start;flex-wrap:wrap}
  body .od-main9{flex:1 1 100%;order:2;margin-left:43px;margin-top:-30px}
  body .od-rowact9{flex:1 1 100%;order:3;margin-left:43px;margin-top:8px}
  body .od-head9 .od-act9{width:100%}
  body .od-head9 .od-act9 .btn{width:100%;justify-content:center}
}
/* ── 35. DRAG-AND-DROP ATTACHMENTS ─────────────────────────────────────────
   The reference gives every create page a Photos card and a Documents card,
   each a dashed zone reading "Drag and drop files to upload / or click to pick
   files". Files staged here are committed once the record has an id. */
body .dz-wrap9{display:block}
body .dz9{display:flex;align-items:center;gap:12px;width:100%;padding:16px 18px;
  border:1.5px dashed var(--line-2);border-radius:8px;background:transparent;
  text-align:left;cursor:pointer;transition:border-color .12s,background .12s}
body .dz9.drag9{border-color:var(--tab-active);background:var(--surface-2)}
body .dz-badge9{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;flex:0 0 34px;border-radius:6px;background:var(--surface-3);color:var(--muted)}
body .dz-badge9 svg{width:17px;height:17px}
body .dz-tx9{display:flex;flex-direction:column;gap:1px;min-width:0}
body .dz-tx9 b{font-size:13.5px;font-weight:600;color:var(--text)}
body .dz-tx9 span{font-size:12px;color:var(--muted)}
/* the staged list sits under the zone, one row per picked file */
body .dz-list9{display:flex;flex-direction:column;gap:0}
body .dz-list9:not(:empty){margin-top:10px;border:1px solid var(--line-1);border-radius:6px}
body .dz-row9{display:flex;align-items:center;gap:9px;padding:8px 10px;min-width:0}
body .dz-row9+.dz-row9{border-top:1px solid var(--line-1)}
body .dz-ic9{display:inline-flex;color:var(--muted);flex:0 0 auto}
body .dz-ic9 svg{width:15px;height:15px}
body .dz-nm9{flex:1 1 auto;min-width:0;font-size:13px;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
body .dz-sz9{flex:0 0 auto;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}
body .dz-x9{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;padding:0;border:0;border-radius:5px;background:transparent;
  color:var(--muted);cursor:pointer}
body .dz-x9 svg{width:14px;height:14px}
@media (hover:hover){body .dz9:hover{border-color:var(--muted)}
  body .dz-x9:hover{background:var(--surface-3);color:var(--danger,#B4342A)}}
body .dz-n9{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;
  padding:0 5px;border-radius:9px;background:var(--surface-3);color:var(--text-2);
  font-size:11px;font-weight:600}
@media (max-width:640px){
  body .dz9{padding:14px}
  body .dz-tx9 b{font-size:13px}
}

/* ── 36. "YOU ARE WATCHING …" STRIPS ───────────────────────────────────────
   One row per record type the signed-in user follows, with the real count and
   a working Unwatch all. Matches the strip the reference puts at the head of
   each record type in Notification Settings. */
body .watch-strip9{display:flex;align-items:center;gap:11px;padding:11px 2px}
body .watch-strip9+.watch-strip9{border-top:1px solid var(--line-1);}
body .watch-ic9{display:inline-flex;align-items:center;justify-content:center;
  width:28px;height:28px;flex:0 0 28px;border-radius:6px;background:var(--surface-3);color:var(--muted)}
body .watch-ic9 svg{width:15px;height:15px}
body .watch-tx9{flex:1 1 auto;min-width:0;font-size:13px;color:var(--text-2)}
body .watch-tx9 b{color:var(--text);font-weight:600}
body .watch-act9{display:flex;align-items:center;gap:6px;flex:0 0 auto}
@media (max-width:640px){
  body .watch-strip9{flex-wrap:wrap}
  body .watch-tx9{flex:1 1 100%;order:2;margin-left:39px;margin-top:-24px}
  body .watch-act9{flex:1 1 100%;order:3;margin-left:39px;margin-top:8px}
  body .watch-act9 .btn{flex:1 1 auto;justify-content:center}
}

/* ── 37. USAGE COUNT ON AN EDITABLE OPTION LIST ────────────────────────────
   "3 vehicles" beside each option, so you can see what removing it would
   affect. Zero is dimmed rather than hidden — an empty column reads as broken. */
body .cfg-use9{flex:0 0 auto;font-size:11.5px;color:var(--text-2);
  padding:2px 8px;border-radius:10px;background:var(--surface-3);
  font-variant-numeric:tabular-nums;white-space:nowrap}
body .cfg-use9.zero9{color:var(--muted);background:transparent;
  border:1px solid var(--line-1)}
@media (max-width:640px){body .cfg-use9{order:5}}

/* ── 38. "LEARN MORE" ON AN EMPTY STATE ────────────────────────────────────
   The reference pairs the primary action with a quiet Learn More. Ours opens
   the Help Centre already searched for the topic, and is only rendered when
   that search has answers, so it can never land on "No matches". */
body .empty-state .es-act{align-items:center;justify-content:center;flex-wrap:wrap}
/* Learn More sits on its own line ABOVE the primary action, the way the
   reference stacks them. */
body .es-learnrow9{display:flex;justify-content:center;margin-top:10px}
body .empty-state .es-learnrow9+.es-act{margin-top:10px}
body .es-learn9{display:inline-flex;align-items:center;gap:5px;padding:0 2px;
  border:0;background:transparent;color:var(--tab-active);font:inherit;font-size:13px;
  font-weight:600;cursor:pointer;line-height:32px}
body .es-learn-ic9{display:inline-flex;opacity:.75}
body .es-learn-ic9 svg{width:13px;height:13px}
@media (hover:hover){body .es-learn9:hover{text-decoration:underline}}
body .es-learn9:focus-visible{outline:2px solid var(--tab-active);outline-offset:2px;border-radius:4px}

/* ── 39. EVERY FOCUSABLE THING GETS A RING ─────────────────────────────────
   design-system.css resets the ring for all focusables with
   `body :where(button,a,input,...):focus-visible{outline:none}` and then paints
   one back per component class - .btn, .icon-btn, .seg-btn, .chip, inputs. Any
   button with a bespoke class (the drop zone, the photo drop target, the row X
   buttons) matched the reset and nothing else, so keyboard users had NO visible
   focus at all on those controls.
   Same specificity as the reset (0,1,1) and later in the cascade, so it wins
   over the reset while every component rule with a class still wins over it. */
body :where(button, a, input, select, textarea, [tabindex]):focus-visible{
  outline:2px solid var(--accent);outline-offset:2px}
/* the drop zone is a large target - the ring rides its own radius */
body .dz9:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:8px}
body .dz-x9:focus-visible,body .cfg-x9:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* ── 40. VEHICLE DOCUMENTS TAB ─────────────────────────────────────────────
   Search on top, the list in the middle, the drop zone underneath — the same
   three parts the reference stacks in its Documents drawer. */
body .vdoc-top9{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
body .vdoc-top9 .list-search{flex:1 1 240px;min-width:0}
body .vdoc-list9{margin-top:12px}
body .vdoc-drop9{margin-top:16px;padding-top:14px;border-top:1px solid var(--line-1)}
body .vdoc-drop-h9{display:flex;align-items:center;gap:8px;margin-bottom:9px}
body .vdoc-drop-h9 b{font-size:13px;font-weight:600;color:var(--text)}
@media (max-width:640px){
  body .vdoc-top9 .list-search{flex:1 1 100%}
  body .vdoc-top9 .btn{width:100%;justify-content:center}
}

/* ── 41. ACCOUNT MENU: LABEL LEFT, ICON HARD RIGHT ─────────────────────────
   Matches wvPopRow and the reference's own account menu. The label takes the
   slack so every icon lines up on the right edge whatever the label length. */
body .avatar-menu .am-lb9{flex:1 1 auto;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
body .avatar-menu .am-ic{flex:0 0 auto;margin-left:8px}

/* ── 42. THE PHOTO DROP TARGET IS A BUTTON, SO IT REACTS LIKE ONE ──────────
   "Or drop file here" also opens the file picker on click, but it had no hover
   state at all, so nothing said it was clickable. Same treatment as the drop
   zone: the border firms up, the text darkens. */
@media (hover:hover){
  body .vphoto-drop9:hover{border-color:var(--text-2);color:var(--text)}
}
body .vphoto-drop9{transition:border-color .12s,color .12s}

/* ── 43. TWO-LINE DUE-DATE CELL ────────────────────────────────────────────
   Date on top, the gap underneath in the status colour, so a renewals column
   can be scanned without doing date arithmetic. */
body .due-cell9{display:flex;flex-direction:column;gap:1px;line-height:1.25;min-width:0}
body .due-abs9{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums}
body .due-rel9{font-size:11.5px;font-weight:560}
body .due-rel9.neg{color:var(--danger,#B4342A)}
body .due-rel9.warn9{color:var(--warn,#A6690F)}
body .due-rel9.muted{color:var(--muted)}

/* ── 44. MANAGE TAB MENU ───────────────────────────────────────────────────
   Reuses the Manage Columns row (.mc-row/.mc-grip/.mc-x) so the two dialogs
   feel like one control. VISIBLE and HIDDEN are two drop targets stacked in a
   single pane, the way the reference stacks them. */
body .mt-sec9{margin:14px 0 6px;font-size:10.5px;font-weight:700;letter-spacing:.07em;
  text-transform:uppercase;color:var(--muted)}
body .mt-list9{display:flex;flex-direction:column;gap:2px;min-height:52px;
  border:1px dashed transparent;border-radius:8px;padding:2px;transition:border-color .12s,background .12s}
body .mt-list9.drop-into{border-color:var(--tab-active);background:var(--surface-2)}
body .mt-list9 .mc-row{cursor:grab}
body .mt-list9 .mc-row.dragging{opacity:.5}
body .mt-list9 .mc-row.mt-locked9{cursor:default;opacity:.75}
body .mt-lb9{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
body .mt-none9{margin:8px 4px;font-size:12.5px;color:var(--muted)}
/* the gear at the end of the tab strip */
body .veh-tab-gear9{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;margin-left:auto;padding:0;border:0;background:transparent;
  color:var(--muted);border-radius:6px;cursor:pointer;flex:0 0 auto}
body .veh-tab-gear9 svg{width:16px;height:16px}
@media (hover:hover){body .veh-tab-gear9:hover{background:var(--surface-3);color:var(--text)}}

/* ── 45. FIRST-COMMENT CARD ON A CREATE PAGE ───────────────────────────────
   Just the composer, no thread — there is nothing to list until the record
   exists. Same avatar + textarea as the record thread so the two read as one
   control seen at two moments. */
body .cmt-first9{padding:0}
body .cmt-first9 .cmt-input9{display:flex;align-items:flex-start;gap:10px;padding:0;border:0}
body .cmt-first9 textarea.cmt-input9{flex:1 1 auto;min-height:64px;padding:9px 11px;
  border:1px solid var(--line-2);border-radius:8px;background:var(--surface);
  color:var(--text);font:inherit;font-size:13.5px;resize:vertical}
body .cmt-first9 textarea.cmt-input9::placeholder{color:var(--muted)}

/* ── 46. OPERATOR CELL: AVATAR + NAME ──────────────────────────────────────
   The reference renders the operator as a small round avatar beside a linked
   name. Unassigned stays a dash. */
body .dt-op9{display:flex;align-items:center;gap:7px;min-width:0}
body .avatar.avatar-sm9{width:22px;height:22px;min-width:22px;font-size:9.5px;flex:0 0 auto}
body .dt-op9 .tlink{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ── 47. TWO-FACTOR SETUP ──────────────────────────────────────────────────
   The QR arrives as a data: URI, so it is an <img>, not innerHTML. The setup
   key is a real block with a copy button instead of an inline <code> that
   wrapped mid-string. */
body .mfa-qr{display:flex;align-items:center;justify-content:center}
body .mfa-qr-img9{width:100%;height:100%;display:block;image-rendering:pixelated}
body .mfa-qr-none9{background:var(--surface-2);color:var(--muted);
  padding:16px;text-align:center;font-size:12.5px;line-height:1.4}
body .mfa-secret9{margin:12px auto 4px;max-width:340px}
body .mfa-secret-l9{display:block;font-size:12.5px;color:var(--muted);margin-bottom:6px}
body .mfa-secret-row9{display:flex;align-items:stretch;gap:8px}
body .mfa-key9{flex:1 1 auto;min-width:0;padding:9px 11px;border:1px solid var(--line-2);
  border-radius:7px;background:var(--surface-2);color:var(--text);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;
  letter-spacing:.06em;word-break:break-all;line-height:1.5}
body .mfa-copy9{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;
  padding:0 12px;border:1px solid var(--line-2);border-radius:7px;
  background:var(--surface-2);color:var(--text);font:inherit;font-size:12.5px;
  font-weight:600;cursor:pointer}
body .mfa-copy9 svg{width:14px;height:14px}
@media (hover:hover){body .mfa-copy9:hover{background:var(--surface-3);border-color:var(--muted)}}

/* ── 48. SEARCH FIELDS ARE ONE CONTROL ─────────────────────────────────────
   Two reports, one cause: the shell drew a box and the <input> inside it drew
   its own box as well, so a search field read as an icon sitting next to a
   separate text box. The shell owns the border, background and focus ring; the
   input inside is transparent and never rings on its own. */
body .help-search,
body .hc-search,
body .list-search,
body .fields-card .fc-search,
body .intg-search9{position:relative}
body .help-search .input,
body .help-search input,
body .hc-search input,
body .intg-search9 input{
  background:transparent!important;border:0!important;box-shadow:none!important;
  outline:none!important;border-radius:0}
body .help-search .input:focus,
body .help-search input:focus,
body .hc-search input:focus,
body .intg-search9 input:focus{outline:none!important;border-color:transparent!important}
/* the ring belongs to the whole field, so the icon is inside it */
/* `!important` on the outline only: an earlier rule zeroes it for these shells
   and a visible focus indicator is an accessibility requirement, not a
   preference. The border tint alone is too quiet on a dark surface. */
body .help-search:focus-within,
body .hc-search:focus-within,
body .intg-search9:focus-within{
  outline:2px solid var(--accent)!important;outline-offset:1px!important;
  border-color:var(--accent)}

/* ── 49. TOP-BAR SEARCH: GLASS SPACING AND COLOUR ──────────────────────────
   The magnifier sat 8px from the placeholder and picked up a different grey
   from it. Both now read from one token and the gap is a deliberate 12px. */
body .topbar .tb-search>span{display:inline-flex;align-items:center;
  margin-left:14px;color:var(--placeholder)}
body .topbar .tb-search>span svg{color:inherit;width:16px;height:16px}
body .topbar .tb-search input{padding-left:12px}
body .topbar .tb-search input::placeholder{color:var(--placeholder);opacity:1}

/* ── 50. WIDGET LIST ROWS AND STAT ALIGNMENT ───────────────────────────────
   Reported: "the text/numbers are way too left" and the spacing reads badly.
   Two causes. The stat pair is centred inside its half of the row, so with one
   short and one long value the pair drifted left of the card's centre; and the
   list widgets had no consistent row rhythm. */
body .card.dw .dw-body>.dw-rows{justify-content:center;align-items:stretch}
body .card.dw .dw-body>.dw-rows>.dw-row{padding:12px 6px}
/* list-style widgets inside a widget card */
body .card.dw .dw-body>.hist-list{margin:0}
body .card.dw .hist-row{padding:9px 2px;gap:10px;align-items:center}
body .card.dw .hist-row+.hist-row{border-top:1px solid var(--line-1)}
body .card.dw .hist-ic{width:26px;height:26px;min-width:26px;border-radius:6px;
  background:var(--surface-3);color:var(--muted);
  display:inline-flex;align-items:center;justify-content:center}
body .card.dw .hist-ic svg{width:14px;height:14px}
body .card.dw .hist-main{min-width:0;flex:1 1 auto}
body .card.dw .hist-title{font-size:13px;font-weight:600;color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
body .card.dw .hist-sub{font-size:11.5px;color:var(--muted);line-height:1.35}

/* Latest Meter Readings: name over the timestamp, reading hard right */
body .mtr-row9{width:100%;border:0;background:none;text-align:left;cursor:pointer;font:inherit}
body .mtr-val9{flex:0 0 auto;margin-left:auto;padding-left:10px;
  font-size:14.5px;font-weight:650;color:var(--text);
  font-variant-numeric:tabular-nums;white-space:nowrap}
body .mtr-when9{font-variant-numeric:tabular-nums}
@media (hover:hover){body .mtr-row9:hover{background:var(--surface-2)}}
body .mtr-row9:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:6px}

/* ── 51. THE COLLAPSED RAIL SHOWS TOP LEVEL ONLY ───────────────────────────
   Reported: collapsing the sidebar left every group expanded, so the 64px rail
   listed Vehicle List, Vehicle Assignments, Meter History and the rest as bare
   icons. The rule that was meant to prevent this targets `.nav-sub`; the
   markup renders `.nav-kids`, so it never matched anything. Both are covered
   now. The children are still reachable — clicking or hovering a group in the
   rail flies its list out beside the icon. */
@media (min-width:981px){
  body .layout.side-collapsed .sidebar .nav-kids,
  body .layout.side-collapsed .sidebar .nav-sub{display:none!important}
  /* a section heading is a word, and there is no room for words in the rail */
  body .layout.side-collapsed .sidebar .nav-group-label{display:none}
  body .layout.side-collapsed .sidebar .nav-grp .nav-caret{display:none}
}

/* ── 52. THE TOOLBARS ABOVE A TABLE SCROLL, VISIBLY ────────────────────────
   Reported: "you cant reach the stuff at the end". They already had
   overflow-x:auto, but Windows draws an overlay scrollbar that is invisible in
   a 32px strip, so a cut-off pill read as broken rather than scrollable. A thin
   always-visible track plus a fade at the right edge makes it obvious there is
   more, and the fade disappears once you reach the end. */
body .list-tools,
body .dt-bar{
  scrollbar-width:thin;scrollbar-color:var(--line-2) transparent;
  overscroll-behavior-x:contain}
body .list-tools::-webkit-scrollbar,
body .dt-bar::-webkit-scrollbar{height:6px}
body .list-tools::-webkit-scrollbar-track,
body .dt-bar::-webkit-scrollbar-track{background:transparent}
body .list-tools::-webkit-scrollbar-thumb,
body .dt-bar::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:99px}
body .list-tools::-webkit-scrollbar-thumb:hover,
body .dt-bar::-webkit-scrollbar-thumb:hover{background:var(--muted)}
/* the fade rides the container, so it is only painted when content overflows */
body .list-tools,body .dt-bar{
  -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent 100%);
  mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent 100%)}
body .list-tools.at-end9,body .dt-bar.at-end9{-webkit-mask-image:none;mask-image:none}
body .dt-bar{overflow-x:auto}

/* ── 53. TEXT CONTRAST FLOOR ───────────────────────────────────────────────
   Reported: "some text is really hard to see". Measured against the real
   surfaces rather than guessed:

     light bg #F4F2EE   --muted #786F63 -> 4.42   (WCAG AA needs 4.50)
                        --ok    #1E9E56 -> 3.09
                        --danger#DC2626 -> 4.32
     dark  bg #1A1917   --muted #9E968A -> 6.01   (already passes)

   Every one of those is body text somewhere: the pager count, "Fleet
   workspace", the compliance "Good" pill, em-dashes in empty cells. The values
   below all clear 4.5:1 on their own surface; the dark palette is left alone
   because it already passes and lightening it would wash the hierarchy out. */
/* LIGHT ONLY. This block previously read `body.light,body:not(.dark)` - and
   there is no `.dark` class in this app: the theme toggles `.light` on and off,
   so dark mode is the ABSENCE of a class. `:not(.dark)` therefore matched every
   state, and these light-theme inks were being painted onto the dark surfaces
   too. Measured on the dark card (#1A1917) they came out at --muted 2.97,
   --ok 2.66, --warn 3.02, --danger 2.59 against a 4.5 requirement, which is
   why body text kept disappearing in dark mode. */
body.light{
  --muted:#6B6358;          /* 5.29 on light */
  --ok:#0F6B39;             /* 5.90 */
  --danger:#A82F26;         /* 6.06 */
  --warn:#8A5C00;           /* darker amber, same family */
}
/* Dark mode gets its own set, same hue families, all measured against the real
   dark card rather than assumed: muted 6.01, ok 8.03, warn 7.73, danger 5.90
   (and 5.01 / 6.70 / 6.45 / 4.92 against the lighter --surface-2). */
body:not(.light){
  --muted:#9E968A;
  --ok:#34C77B;
  --warn:#E0A02E;
  --danger:#F26B60;
}
/* the status colours are also used as small dots and pill backgrounds, where a
   darker ink is fine — only the soft tints need to stay light */
body.light .badge.ok{color:#0F6B39}
body.light .dt-ok{color:#0F6B39}
/* the same pills in dark mode, using the dark-safe green */
body:not(.light) .badge.ok,body:not(.light) .dt-ok{color:#34C77B}

/* ── 54. PLAN CHANGE DIALOGS ───────────────────────────────────────────────
   The downgrade sheet lists what stops being available; the upgrade sheet lays
   out the two real routes side by side rather than announcing a dead end. */
body .dg-lose9{margin:6px 0 10px;padding-left:18px;font-size:13px;color:var(--text-2)}
body .dg-lose9 li{margin:3px 0}
body .upg-opt9{padding:12px 0}
body .upg-opt9+.upg-opt9{border-top:1px solid var(--line-1);margin-top:2px}
body .upg-opt9 b{display:block;font-size:13.5px;color:var(--text);margin-bottom:3px}
body .upg-opt9 p{margin:0 0 8px;font-size:12.5px;color:var(--muted);line-height:1.45}

/* ── 55. ASSIGN A DEVICE FROM THE CELL ─────────────────────────────────────
   The "Unassigned" cell is the control, not a grey label. */
body .dev-assign9{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;
  border:1px dashed var(--line-2);border-radius:99px;background:transparent;
  color:var(--text-2);font:inherit;font-size:12.5px;cursor:pointer;
  transition:border-color .12s,color .12s,background .12s}
body .dev-assign9 svg{width:13px;height:13px}
@media (hover:hover){body .dev-assign9:hover{border-color:var(--tab-active);
  color:var(--text);background:var(--surface-2)}}
body .dev-assign9:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ── 56. MAP OPTIONS PANEL ─────────────────────────────────────────────────
   Anchored to the button, sectioned, applies on change. Replaces a centred
   modal that dimmed the map you were trying to look at. */
body .mo-panel9{width:270px;max-width:calc(100vw - 24px);padding:10px 0 8px}
body .mo-sec9{padding:6px 14px 4px;font-size:10.5px;font-weight:700;
  letter-spacing:.07em;color:var(--muted)}
body .mo-radios9{display:flex;flex-direction:column}
body .mo-radio9{display:flex;align-items:center;gap:10px;width:100%;
  padding:8px 14px;border:0;background:none;color:var(--text);
  font:inherit;font-size:13.5px;text-align:left;cursor:pointer}
body .mo-dot9{width:15px;height:15px;flex:0 0 15px;border-radius:50%;
  border:1.5px solid var(--line-2);position:relative;transition:border-color .12s}
body .mo-radio9.on .mo-dot9{border-color:var(--tab-active);border-width:4.5px}
@media (hover:hover){body .mo-radio9:hover{background:var(--surface-2)}
  body .mo-radio9:hover .mo-dot9{border-color:var(--muted)}
  body .mo-radio9.on:hover .mo-dot9{border-color:var(--tab-active)}}
body .mo-radio9:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
body .mo-check9{display:flex;align-items:center;gap:10px;padding:7px 14px;
  font-size:13.5px;color:var(--text);cursor:pointer}
body .mo-check9 input{width:15px;height:15px;flex:0 0 15px;accent-color:var(--tab-active);cursor:pointer}
@media (hover:hover){body .mo-check9:hover{background:var(--surface-2)}}
body .mo-div9{display:block;height:1px;margin:7px 0;background:var(--line-1)}
body .mo-home9{display:flex;align-items:flex-start;gap:8px;padding:2px 14px 8px;
  font-size:12px;color:var(--muted);line-height:1.4}
body .mo-home9 svg{width:13px;height:13px;margin-top:2px;flex:0 0 13px}
body .mo-link9{display:block;width:100%;padding:7px 14px;border:0;background:none;
  color:var(--tab-active);font:inherit;font-size:12.5px;font-weight:600;
  text-align:left;cursor:pointer}
@media (hover:hover){body .mo-link9:hover{background:var(--surface-2);text-decoration:underline}}
body .mo-link9:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* ── 57. EVERY INTERACTIVE THING REACTS ────────────────────────────────────
   Reported: "make sure everything thats meant to have a background has a
   background like buttons ... when you hover over something like graphs or a
   button, is it visibly clear what you are hovering over".

   A safety net, not a restyle: anything that is a button and has NOT been given
   its own hover by a component rule gets a quiet surface lift. Component rules
   carry a class so they always outrank this. */
@media (hover:hover){
  body :where(button, [role="button"], a.card-link, .linkish):not(:disabled):hover{
    cursor:pointer}
  /* icon-only controls read as dead pixels without a hit surface */
  body :where(.icon-btn, .tb-btn, .row-more9, .dw-menu, .veh-rail-ic, .sv-dots9,
              .dz-x9, .cfg-x9, .mc-x, .veh-tab-gear9):not(:disabled):hover{
    background:var(--surface-3);color:var(--text)}
}
/* a disabled control must LOOK disabled, or people click it and blame the app */
body button:disabled,body .btn:disabled,body [role="button"][aria-disabled="true"]{
  opacity:.55;cursor:not-allowed}
/* chart marks: make the hovered element obvious rather than only the tooltip */
@media (hover:hover){
  body .bar-col:hover rect,body .chart-col:hover rect,
  body .vo-chart .bar:hover,body .dash-chart .bar:hover{filter:brightness(1.14)}
  body .donut-seg:hover,body .pie-seg:hover{filter:brightness(1.12)}
  body .legend-row9:hover,body .rep-legend9 .lg-row9:hover{background:var(--surface-2)}
}
/* a row that navigates should say so before it is clicked */
@media (hover:hover){
  body .dtr.click:hover{background:var(--surface-2)}
  body .hist-row[onclick]:hover,body .hist-row-dl:hover{background:var(--surface-2)}
}
/* the busy state on a row that is fetching something */
body .is-busy9{opacity:.6;pointer-events:none}

/* Photos and Documents side by side on a form, as the reference lays them out.
   Two equal columns that fall to one when there is not room for both; a single
   card passed on its own just spans the row. The cards are already .card, so
   only the track is defined here. */
body .form-2up9{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
body .form-2up9.one9{grid-template-columns:1fr}
body .form-2up9>.card{margin:0;min-width:0}
@media (max-width:720px){body .form-2up9{grid-template-columns:1fr}}

/* Widget numbers were hugging the right edge of their tile. `.dw-n` still
   carried `margin-left:auto` from when a widget row was a horizontal
   `label ................ value` line. The row is a centred vertical tile now
   (the number is order:-1, the caption sits under it), and in flexbox an auto
   margin absorbs all free space and beats `align-items:center`, so the centring
   already declared on the row could never take effect. */
body .dw-body>.dw-rows>.dw-row .dw-n{margin-left:0;margin-right:0}
body .dw-body>.dw-rows>.dw-row{align-items:center;text-align:center}
body .dw-body>.dw-rows>.dw-row .dw-l2{text-align:center;width:100%}

/* One category is drawn as a complete RING, not a filled disc and not a
   paragraph. Same 132px geometry and legend as the real pie, so the widget
   still reads as a chart and becomes an ordinary pie as soon as a second
   category appears. The hole is a mask so it works over any card colour. */
body .pie.pie-one9{
  -webkit-mask:radial-gradient(circle, transparent 58%, #000 58.5%);
  mask:radial-gradient(circle, transparent 58%, #000 58.5%);
  box-shadow:none}
body .pie-wrap{position:relative;display:inline-flex}
body .pie-centre9{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:1px;pointer-events:none;text-align:center}
body .pie-centre9 b{font-size:26px;font-weight:700;line-height:1.05;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}
body .pie-centre9 span{font-size:11px;font-weight:600;color:var(--muted)}
body .leg-pct9{margin-left:6px;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* ── WIDE SCREENS ─────────────────────────────────────────────────────────
   `.page` and `.detail-page` were capped at 1400px. On the 27-inch screen the
   reference was captured on (2560 wide) that left the content using 61% of the
   available width and ~900px of empty gutter, while the reference fills the
   window edge to edge.

   Raising the cap to 2200px changes NOTHING below about a 1650px window,
   because under that the available width is already less than 1400 and the cap
   never binds — laptops are untouched. `.form-page` keeps its own 660px cap, so
   forms stay a readable column instead of stretching into unreadable lines. */
body .page{max-width:2200px}
body .detail-page{max-width:2200px}
/* A live map has no reason to stop short of the window on any screen. */
body .page.map-page,body .map-page{max-width:none}

/* Notification filter pills, under the Inbox/Archived tabs. */
body .notif-pills9{display:flex;gap:6px;flex-wrap:wrap;padding:10px 2px 4px}
body .notif-pill9{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;
  border:1px solid var(--border-soft);background:var(--surface-2);color:var(--muted);
  font-size:12px;font-weight:600;cursor:pointer;transition:background .12s,color .12s,border-color .12s}
body .notif-pill9:hover{border-color:var(--tab-active);color:var(--text)}
body .notif-pill9.on{background:var(--tab-active);border-color:var(--tab-active);color:#fff}
body .notif-pill-n9{font-variant-numeric:tabular-nums;opacity:.85}

/* Section headings inside a form, the way the reference splits a long form into
   named groups instead of one undifferentiated run of fields. */
body h4.form-sec9{margin:18px 0 2px;font-size:11.5px;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}
body h4.form-sec9:first-child{margin-top:4px}
/* The confirmation that a place has been located. The position itself is never
   shown - coordinates appear nowhere in this app - only that it is set. */
body .place-picked9{margin:6px 0 2px}
body .place-ok9{display:inline-flex;align-items:center;gap:7px;padding:6px 11px;border-radius:8px;
  background:var(--surface-2);border:1px solid var(--border-soft);font-size:12.5px;font-weight:600}
body .place-ok9 svg{width:14px;height:14px;opacity:.75}

/* Report charts: a trend and a share-of-total sit side by side above the ranked
   bars, collapsing to one column when there is not room for both. */
body .rv-charts9{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;margin-top:4px}
@media (max-width:900px){body .rv-charts9{grid-template-columns:1fr}}
body .rv-charts9>.rv-chart9{min-width:0}
/* The bold rollup that closes each group on a grouped report. */
body .rep-group-total9{display:flex;flex-wrap:wrap;align-items:baseline;gap:18px;
  padding:10px 14px;margin-top:-1px;border:1px solid var(--border-soft);border-top:0;
  border-radius:0 0 8px 8px;background:var(--surface-2)}
body .rgt-l9{font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
body .rgt-c9{display:flex;flex-direction:column;gap:1px}
body .rgt-c9 small{font-size:10.5px;color:var(--muted)}
body .rgt-c9 b{font-size:14px;font-variant-numeric:tabular-nums}

/* Slice highlight. A conic-gradient cannot address one slice, so the hovered
   one is repainted at full colour in an overlay sitting exactly on the pie
   while the pie itself dims behind it. Previously hover only saturated the
   WHOLE pie, which told you nothing about what you were pointing at. */
body .pie-wrap{position:relative;display:inline-flex}
body .pie.pie-hi9{
  position:absolute;inset:0;background:none;box-shadow:none;pointer-events:none;
  opacity:0;transition:opacity .12s ease}
body .pie.pie-hi9.on{opacity:1;filter:drop-shadow(0 2px 6px rgba(0,0,0,.35))}
/* a ring keeps its hole when highlighted */
body .pie.pie-one9+.pie-hi9{
  -webkit-mask:radial-gradient(circle, transparent 58%, #000 58.5%);
  mask:radial-gradient(circle, transparent 58%, #000 58.5%)}
/* the legend row for the hovered slice reads as selected, not just tinted */
body .chart-leg-row.on{background:var(--surface-2);font-weight:600}

/* The Save action for a multi-card settings page sits under the cards, not
   inside an empty one of its own. */
body .set-save9{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:4px}
body .set-save9 .form-error{margin:0}

/* "No Priority" — the reference's default issue priority. Quiet by design: it
   is the absence of a decision, so it must not read as an alert level. */
body .pri-none,body .prio-none{color:var(--muted)}
body .pri-glyph.none,body .pg-none{background:var(--surface-2);color:var(--muted)}

/* ── PRIMARY BUTTON INK ────────────────────────────────────────────────────
   White on the brand orange #D97757 measures 3.12:1. Button labels are 13px,
   so WCAG AA wants 4.5 — this was the last contrast failure left in the app and
   it appeared on every primary action ("Add Vehicle", "Save Vendor", …).

   The brand colour is NOT changed: the palette stays exactly as it is, and
   #D97757 remains the accent on tabs, dots, borders and charts. Only the LABEL
   ink moves, from white to the same near-black the app already uses for body
   text — measured 5.63:1 on that orange. Keeping the hue and fixing the ink was
   the option that satisfies both "keep our palette" and "no bugs". */
body{--on-brand:#1A1917}
body .btn.btn-primary,
body .btn.btn-primary:hover,
body .btn.btn-primary:focus-visible{color:var(--on-brand)}
/* icons inside a primary button inherit the same ink */
/* design-system.css pins `.btn.btn-primary svg { color:#FFF }`, so when the
   label ink moved to dark the ICON stayed white — a white glyph beside dark
   text on the same orange button. The icon has to follow the label. */
body .btn.btn-primary .btn-ic,
body .btn.btn-primary svg{color:var(--on-brand);fill:currentColor;stroke:currentColor}
/* the notification pill and any other solid-brand chip follow the button */
body .notif-pill9.on{color:var(--on-brand)}

/* `--placeholder` is used for real visible labels in a few places (the reports
   rail's "Report types" heading, for one), not only inside empty inputs. At
   #7C746A it measured 3.82 on the page background and 3.18 on the lighter
   surface — under the 4.5 AA needs for body text. #9A9285 keeps it clearly
   quieter than --muted while passing on both surfaces (5.71 / 4.76). */
body:not(.light){--placeholder:#9A9285}

/* Two-step Connect: the step rail above the pane. */
body .cx-steps9{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap}
body .cx-step9{display:flex;align-items:center;gap:8px;padding:7px 13px;border-radius:999px;
  border:1px solid var(--border-soft);background:var(--surface-2);color:var(--muted);
  font-size:12.5px;font-weight:600}
body .cx-step9.on{border-color:var(--tab-active);color:var(--text);background:var(--surface)}
body .cx-step9.done{color:var(--ok);border-color:var(--ok)}
body .cx-num9{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;
  border-radius:50%;background:var(--surface);border:1px solid currentColor;font-size:11px;flex:none}
body .cx-step9.on .cx-num9{background:var(--tab-active);border-color:var(--tab-active);color:var(--on-brand)}
body .cx-pane9{min-height:150px}
/* the numbered setup list — real steps, so they get real numbers */
body .cx-list9{margin:10px 0 14px;padding-left:22px;display:flex;flex-direction:column;gap:9px}
body .cx-list9 li{font-size:13px;line-height:1.5;color:var(--text-2)}
/* Features filter on the integrations toolbar */
body .intg-featfilter9{min-width:0}
body .intg-featfilter9 select{max-width:220px}

/* ── TOUCH TARGETS ────────────────────────────────────────────────────────
   Measured on a 375px phone: the view-options dots are 22x22, the page switch
   and widget kebab 28x28, a saved-view tab 19px tall, and a row-action button
   24px wide. All are comfortable with a mouse and genuinely hard to hit with a
   thumb — the accepted floor is 44px.

   The fix EXPANDS THE HIT AREA WITHOUT RESIZING THE CONTROL: a transparent
   pseudo-element centred on the button grows the tappable region, so the layout
   and the visual weight are untouched. Applied only where a coarse pointer is
   the input device, so desktop density is unaffected. */
@media (pointer:coarse){
  body .sv-dots9, body .pt-switch9, body .dw-menu, body .icon-btn,
  body .sv-tab-hit9, body .cfg-x9, body .dz-x9, body .mc-x{
    position:relative;
  }
  body .sv-dots9::after, body .pt-switch9::after, body .dw-menu::after,
  body .icon-btn::after, body .sv-tab-hit9::after, body .cfg-x9::after,
  body .dz-x9::after, body .mc-x::after{
    content:"";
    position:absolute;
    top:50%; left:50%;
    transform:translate(-50%,-50%);
    width:max(100%,44px);
    height:max(100%,44px);
    /* the hit area only — never paints, never affects layout */
    background:transparent;
  }
  /* a pressed state the thumb can actually perceive, since there is no hover */
  body .sv-dots9:active, body .pt-switch9:active, body .dw-menu:active,
  body .icon-btn:active{ background:var(--surface-2) }
}

/* ── iOS INPUT ZOOM ───────────────────────────────────────────────────────
   Safari on iOS zooms the whole page whenever a focused input has a font-size
   below 16px, then leaves it zoomed — so every tap on a field jumps the layout
   and the user has to pinch back out. Ours measured 13.5px, which triggers it
   on every form in the app.

   16px on coarse pointers only, so desktop density is unchanged. This is a
   platform requirement, not a taste choice: there is no way to opt out of the
   zoom other than meeting the threshold. Touch height goes to 44px at the same
   time, which is the same reason the hit areas above were widened. */
@media (pointer:coarse){
  body input:not([type=checkbox]):not([type=radio]),
  body select,
  body textarea{
    font-size:16px;
    min-height:44px;
  }
  body textarea{ min-height:72px }
  /* the field label stays at its designed size — only the control moves */
  body .form-row-label,body .field-wrap>span{ font-size:13px }
}

/* The rule above was beaten by `.input.field` (0,2,0 against 0,0,2), so selects,
   date/time inputs and the comment box kept their 13.5px and still triggered the
   iOS zoom. Same intent, specificity that actually wins. */
@media (pointer:coarse){
  body .input.field,
  body input.input, body select.input, body textarea.input,
  body input[type=date], body input[type=time], body input[type=datetime-local],
  body textarea.cmt-input9,
  body .form-page input:not([type=checkbox]):not([type=radio]),
  body .form-page select,
  body .form-page textarea,
  body .modal input:not([type=checkbox]):not([type=radio]),
  body .modal select,
  body .modal textarea{
    font-size:16px;
    min-height:44px;
  }
  body textarea.cmt-input9,body .form-page textarea,body .modal textarea{ min-height:72px }
}

/* Final word on the iOS zoom threshold. An existing `.field .input { font-size:13px !important }`
   and a more specific `.cmt-first9 textarea.cmt-input9` were still winning, so
   date/time inputs and the comment box kept triggering the zoom.
   `!important` is warranted here: this is a platform behaviour with no opt-out
   other than meeting the 16px threshold, and it is scoped to coarse pointers so
   it cannot affect desktop typography. */
@media (pointer:coarse){
  body .field .input, body .field input, body .field select, body .field textarea,
  body input[type=date], body input[type=time], body input[type=datetime-local],
  body input[type=number], body input[type=email], body input[type=tel], body input[type=text],
  body .cmt-first9 textarea.cmt-input9, body textarea.cmt-input9,
  body .input, body select, body textarea{
    font-size:16px !important;
  }
}
@media (pointer:coarse){
  /* `.form-grid input { font-size:13px!important }` in design-system.css is the
     rule that kept the paired date/time fields under the iOS threshold — they
     sit in a .form-grid, which none of the selectors above named. */
  body .form-grid .input, body .form-grid input:not([type=checkbox]):not([type=radio]),
  body .form-grid select, body .form-grid textarea,
  body .field textarea{
    font-size:16px !important;
    height:auto !important;
    min-height:44px !important;
  }
}

/* ── OFFSCREEN DRAWER SHADOW ──────────────────────────────────────────────
   On mobile `.sidebar` is parked at translateX(-100%) but kept
   `box-shadow: var(--sh-lg)` at all times. A large shadow on an element whose
   right edge sits exactly at x=0 spills across the page, which is the dark
   gradient running down the left edge of every screen on a phone.

   The shadow belongs to the OPEN state — that is the only time there is a
   drawer above the page to cast one. Also pinned `visibility:hidden` while
   closed so nothing inside it can paint or be reached by a swipe. */
@media (max-width:760px){
  body .sidebar{ box-shadow:none; visibility:hidden }
  body .sidebar.open{ box-shadow:var(--sh-lg); visibility:visible }
}

/* Filter-pill carets read at the old muted value while their label reads at the
   text colour, so the caret looked disabled next to a live label. */
body .wv-fcount9 svg, body .filter-pill svg, body .list-tools .pop-caret,
body .sv-save9 svg{ color:var(--text-2) }

/* ══ UNIVERSAL VISUAL FIXES (reported from real screenshots) ═══════════════ */

/* 1. FIELD BOXES YOU CAN ACTUALLY SEE.
   `--line-2` was #39342F on a #22201E surface — measured 1.32:1, so every
   input, select and textarea had a border that was technically there and
   visually absent. Non-text UI needs ~3:1 to read as a boundary. */
body:not(.light){ --line-2:#7A7266; --line-3:#8B8378 }
body.light{ --line-2:#A39A8E; --line-3:#8C8377 }
body .input, body input:not([type=checkbox]):not([type=radio]),
body select, body textarea, body .select{
  border:1px solid var(--line-2);
  background:var(--surface);
}
body .input:hover, body input:hover, body select:hover, body textarea:hover{
  border-color:var(--line-3);
}
body .input:focus, body input:focus, body select:focus, body textarea:focus{
  border-color:var(--tab-active);
}

/* 2. HELPER TEXT. The one-line explanations under fields were --muted, which is
   correct for a caption but too quiet for text that tells you what to enter. */
body .form-row-hint, body .field-hint, body .set-row-sub, body .dw-hint,
body .muted-note, body .modal-text{ color:var(--text-2) }

/* 3. RADIOS AND CHECKBOXES WHEN NOT SELECTED.
   An unselected radio drew a ring in the old --line-2, i.e. invisible, so the
   Map Type list looked like four labels with nothing to click. */
body input[type=radio], body input[type=checkbox]{
  appearance:none;-webkit-appearance:none;
  width:18px;height:18px;flex:none;margin:0;
  border:2px solid var(--line-3);
  background:var(--surface);
  cursor:pointer;transition:border-color .12s,background .12s;
}
body input[type=radio]{ border-radius:50% }
body input[type=checkbox]{ border-radius:5px }
body input[type=radio]:hover, body input[type=checkbox]:hover{ border-color:var(--tab-active) }
body input[type=radio]:checked{
  border-color:var(--tab-active);
  box-shadow:inset 0 0 0 4px var(--surface), inset 0 0 0 12px var(--tab-active);
}
body input[type=checkbox]:checked{
  border-color:var(--tab-active); background:var(--tab-active);
}
body input[type=checkbox]:checked::after{
  content:""; display:block; width:5px; height:9px;
  margin:1px auto 0; border:solid var(--on-brand);
  border-width:0 2px 2px 0; transform:rotate(45deg);
}

/* 4. SECTION LABELS INSIDE PANELS AND MENUS.
   "MY SAVED VIEWS", "STANDARD VIEWS", "MAP TYPE", "MAP DETAILS" rendered at the
   muted colour with no weight, so they read as stray text rather than the
   headings they are — and they sat flush against the panel edge. */
body .mo-sec9, body .sv-group9, body .pop-group-h, body .top-pop-h,
body .setm-group9>b, body .cfg-sec9{
  display:block;
  padding:12px 14px 5px;
  font-size:11px; font-weight:700; letter-spacing:.075em; text-transform:uppercase;
  color:var(--text-2);
}
/* the rows under a heading get the same gutter, so nothing hugs the edge */
body .mo-radios9, body .mo-check9, body .mo-home9, body .mo-link9{ padding-left:14px; padding-right:14px }
body .mo-radio9{ padding:7px 0 }

/* 5. NO BOX AROUND PAGER / NAV ARROWS.
   The prev-next arrows sat in a bordered square that read as a disabled button
   next to the live one. They are icon affordances, not buttons. */
body .dt-pager button, body .pager9 button, body .pg-arrow9,
body .dt-pager .icon-btn, body .rep-pager button{
  border:0!important; background:transparent!important; box-shadow:none!important;
}
body .dt-pager button:hover, body .pager9 button:hover, body .pg-arrow9:hover{
  background:var(--surface-2)!important;
}

/* 6. RIGHT-HAND COLUMN ALIGNMENT IN TWO-UP FORM ROWS.
   Fuel type / Make / Registration state and every other right-column field sat
   a few pixels lower than its partner because the left cell carried a hint line
   and the right one did not. Aligning the tops makes the pair read as a row. */
body .form-grid{ align-items:start }
body .form-grid>*{ min-width:0 }
body .form-row-half{ align-self:start }

/* 7. SEARCH FIELDS: ICON INSIDE THE BOX.
   The magnifier sat outside the input, so the control read as two things. */
body .list-search, body .sv-search9, body .help-search9, body .fc-search{
  display:flex; align-items:center; gap:8px;
  border:1px solid var(--line-2); border-radius:9px;
  background:var(--surface); padding:0 10px;
}
body .list-search svg, body .sv-search9 svg, body .help-search9 svg, body .fc-search svg{
  flex:none; opacity:.75; width:15px; height:15px;
}
body .list-search input, body .sv-search9 input, body .help-search9 input, body .fc-search input{
  border:0!important; background:transparent!important; flex:1; min-width:0;
  padding-left:0; padding-right:0;
}
body .list-search:focus-within, body .sv-search9:focus-within,
body .help-search9:focus-within, body .fc-search:focus-within{ border-color:var(--tab-active) }

/* 8. DROPDOWN CARETS POINT DOWN.
   The saved-view and group carets rendered the shared chevron glyph, which
   points RIGHT, so a control that opens a panel downwards had an arrow aiming
   sideways. Rotate it to match the direction the panel actually travels, and
   flip it while open. */
body .pop-caret svg, body .sv-caret9 svg, body .vo-range svg, body .gear-caret9 svg{
  transform:rotate(90deg); transition:transform .14s ease;
}
body .pop-open9 .pop-caret svg, body [aria-expanded="true"] .pop-caret svg{
  transform:rotate(-90deg);
}
/* 9. PRIMARY BUTTON LABELS STAY LEGIBLE IN DARK MODE. */
body:not(.light) .btn.btn-primary{ color:var(--on-brand) }
body:not(.light) .btn.btn-primary svg{ color:var(--on-brand) }

/* 10. HELP CENTRE.
   Reported, fairly: the background was stripped from the search field but the
   field and its text were never re-laid-out, so the magnifier floated loose and
   the placeholder sat away from it. And the category list, question rows and
   counts were all --muted, which on a dim overlay is barely readable.
   The search is a real bordered control again (rule 7 above gives it the box);
   these set the type so the panel reads as a hierarchy. */
body .help-search9{ margin-bottom:14px; height:44px }
body .help-search9 input{ font-size:14px; color:var(--text) }
body .help-search9 input::placeholder{ color:var(--muted) }
/* category rail: a list you can actually read, with the active row obvious */
body .help-cat{ color:var(--text-2); padding:9px 11px; border-radius:8px; font-size:13px }
body .help-cat:hover{ background:var(--surface-2); color:var(--text) }
body .help-cat.on{ background:var(--tab-active); color:var(--on-brand); font-weight:600 }
body .help-cat.on .help-n9,body .help-cat.on span{ color:var(--on-brand) }
/* the count beside each category was the same weight as the label */
body .help-cat .help-n9,body .help-cat>span:last-child{
  color:var(--muted); font-variant-numeric:tabular-nums; font-size:12px }
/* questions are the content of this panel — they get full text colour */
body .help-q{ color:var(--text); font-size:13.5px; padding:11px 4px; line-height:1.45 }
body .help-q:hover{ color:var(--text); background:var(--surface-2) }
/* the section heading above a group of questions */
body .help-group9,body .help-sec9{
  color:var(--text-2); font-size:11px; font-weight:700;
  letter-spacing:.075em; text-transform:uppercase; padding:14px 4px 6px }
/* the "268 answers · 32 topics" summary */
body .help-count9{ color:var(--text-2) }

/* 11. TOP SEARCH BAR.
   The magnifier lived in a 38px box at opacity .55, so the icon floated well
   away from the left edge and read as a different (dimmer) colour than the
   placeholder beside it. Tighten the gutter and let the icon share the
   placeholder's colour so the pair reads as one control. */
body .tb-search>span:first-child, body .tb-search .tb-search-ic{
  width:26px; padding-left:10px; opacity:1; color:var(--muted);
}
body .tb-search>span:first-child svg, body .tb-search .tb-search-ic svg{ width:15px; height:15px }
body .tb-search input{ padding-left:2px }
body .tb-search input::placeholder{ color:var(--muted) }

/* 12. DASHBOARD ROW GAPS.
   Measured: a row of three cards at heights 258 / 355 / 343 — a 97px spread.
   With `align-items:start` the two shorter cards left a ragged hole underneath
   them, which is the empty space in the middle of the dashboard. Cards in a row
   now share the row's height, so the grid reads as rows instead of debris. */
body .dash-grid{ align-items:stretch }
body .dash-grid>.dw, body .dash-grid>.card{ height:100% }
body .dash-grid>.dw>.dw-body{ flex:1 1 auto; display:flex; flex-direction:column }
/* a stretched card must not stretch its contents into a void */
body .dash-grid>.dw>.dw-body>.dw-rows{ margin-top:auto; margin-bottom:auto }

/* 13. DIALOGS ARRIVE INSTEAD OF APPEARING.
   A modal snapping into existence at full size reads as a glitch. A short
   scale-and-fade tells you where it came from. Respects reduced-motion. */
@keyframes mvModalIn9{ from{opacity:0; transform:translateY(8px) scale(.985)} to{opacity:1; transform:none} }
@keyframes mvOverlayIn9{ from{opacity:0} to{opacity:1} }
body .overlay{ animation:mvOverlayIn9 .14s ease-out both }
body .modal{ animation:mvModalIn9 .18s cubic-bezier(.2,.8,.3,1) both }
@media (prefers-reduced-motion:reduce){
  body .overlay, body .modal{ animation:none }
}

/* 14. MAP CONTROL BUTTONS SHOW WHAT IS ENGAGED.
   The track / layer / fullscreen buttons looked identical whether or not they
   were active, so nothing told you which mode you were in. An engaged control
   takes the brand fill and its icon takes the brand ink — the same pairing the
   primary button uses. */
body .cc-map-side-btn.on, body .cc-map-side-btn[aria-pressed="true"],
body .cc-map-options.on, body .map-chip.active, body .cc-track-btn.on{
  background:var(--tab-active); border-color:var(--tab-active); color:var(--on-brand);
}
body .cc-map-side-btn.on svg, body .cc-map-side-btn[aria-pressed="true"] svg,
body .cc-map-options.on svg, body .map-chip.active svg, body .cc-track-btn.on svg{
  color:var(--on-brand); fill:currentColor; stroke:currentColor;
}

/* 15. CHART HOVER.
   Bars and segments dimmed the whole chart rather than isolating what the
   pointer is on, so hovering told you nothing. Everything except the hovered
   mark steps back; the hovered one keeps full colour and lifts slightly. */
body .chart-cols:hover .chart-col{ opacity:.42; transition:opacity .12s }
body .chart-cols .chart-col:hover{ opacity:1; filter:brightness(1.12) }
body .hbars:hover .hbar-row{ opacity:.5; transition:opacity .12s }
body .hbars .hbar-row:hover{ opacity:1 }
body .hbar-row:hover .hbar-fill{ filter:brightness(1.12) }

/* 16. ONE COLOUR PER STATUS, EVERYWHERE.
   `STATUS_META` already maps every status to a semantic kind (ok / warn /
   danger / info / muted), but the live-map rail painted its dots from hardcoded
   hex — #22A45D, #F2B705, #B9BfC6 — so the SAME vehicle state was one colour in
   the sidebar and a different one in the table, the badge and the widget.
   Pointing every dot at the shared tokens means a status can only ever have one
   colour, and changing a token changes it in all of them at once. */
body .cc-dot-moving,  body .cc-legend-dot.cc-dot-moving  { background:var(--ok) }
body .cc-dot-idle,    body .cc-legend-dot.cc-dot-idle    { background:var(--warn) }
body .cc-dot-offline, body .cc-legend-dot.cc-dot-offline { background:var(--muted) }
body .cc-dot-stale                                        { background:var(--warn) }
body .cc-dot-shop,    body .cc-dot-maintenance            { background:var(--warn) }
body .cc-dot-oos,     body .cc-dot-danger                 { background:var(--danger) }
/* the canonical set, so anything using .dot-* agrees with the above */
body .dot-ok      { background:var(--ok) }
body .dot-warn,
body .dot-amber   { background:var(--warn) }
body .dot-danger  { background:var(--danger) }
body .dot-info    { background:var(--info) }
body .dot-muted   { background:var(--muted) }
/* a dot is a status signal, so it keeps its colour when its row is hovered or
   selected rather than being washed out by the row treatment */
body .cc-unit-row:hover .cc-dot, body .cc-unit-row.on .cc-dot,
body .dtr:hover .dot, body .row:hover .dot{ opacity:1 }
/* 11b. The search icon is the SECOND child — the scope button comes first in the
   DOM and is moved to the right by `order`. So `>span:first-child` never matched
   and the icon kept its 34px box, 12px indent and .55 opacity. Match the span
   itself. */
body .tb-search>span{
  width:24px!important; padding:0 0 0 10px!important; opacity:1!important; color:var(--muted);
}
body .tb-search>span svg{ width:15px; height:15px }
body .tb-search>input{ padding-left:4px!important }

/* ══ CORRECTIONS TO THE PREVIOUS PASS ═════════════════════════════════════
   Adding a border to every input was too blunt: controls that already sat in a
   bordered wrapper (the top search pill, list search, saved-view search, help
   search) ended up with a box INSIDE a box. Fixed properly below — the WRAPPER
   owns the box, the input owns nothing, and focus lights the wrapper so the
   whole control responds instead of a rectangle inside it. */
body .tb-search input, body .list-search input, body .sv-search9 input,
body .help-search9 input, body .fc-search input, body .ac-wrap input,
body .cc-search9 input, body .intg-search9 input, body .dz-wrap9 input{
  border:0!important; background:transparent!important; box-shadow:none!important; outline:0!important;
}
/* the wrapper is the control: one box, one focus ring, whole-width highlight */
body .tb-search, body .list-search, body .sv-search9, body .help-search9,
body .fc-search, body .cc-search9, body .intg-search9{
  border:1px solid var(--line-2);
  background:var(--surface);
  transition:border-color .12s, box-shadow .12s;
}
body .tb-search:focus-within, body .list-search:focus-within, body .sv-search9:focus-within,
body .help-search9:focus-within, body .fc-search:focus-within,
body .cc-search9:focus-within, body .intg-search9:focus-within{
  border-color:var(--tab-active);
  box-shadow:0 0 0 3px rgba(217,119,87,.34);
}
/* standalone fields light their own box the same way, so focus is consistent */
body .input:focus, body input:focus:not([type=checkbox]):not([type=radio]),
body select:focus, body textarea:focus{
  border-color:var(--tab-active)!important;
  box-shadow:0 0 0 3px rgba(217,119,87,.34)!important;
  outline:none;
}

/* CHART TOOLTIP. The value was white but the month label and the comparison
   line under it were muted, so on a dark tooltip the two lines that give the
   number its meaning were the hardest part to read. */
body .chart-tip, body .chart-tip *{ color:#FFF }
body .chart-tip b, body .chart-tip .ct-v9{ color:#FFF; font-weight:700 }
body .chart-tip small, body .chart-tip span, body .chart-tip .ct-sub9{
  color:rgba(255,255,255,.82)!important;
}

/* COLLAPSED-SIDEBAR FLYOUT. It rendered on a near-transparent panel with the
   rail's own dim link colour, so the submenu was unreadable. It is a menu: it
   takes the theme surface and full-strength text, like every other menu. */
/* ONLY the flyout that appears from the COLLAPSED rail. The previous version
   also matched `.nav-kids`, which is the ordinary expanded-sidebar submenu, so
   every nav group grew a bordered card around it. */
body .layout.side-collapsed .nav-kids, body .nav-fly9, body .rail-fly9{
  background:var(--surface); border:1px solid var(--line-2);
  border-radius:10px; box-shadow:var(--sh-lg);
}
body .layout.side-collapsed .nav-kids a, body .nav-fly9 a, body .rail-fly9 a,
body .layout.side-collapsed .nav-kids button, body .nav-fly9 button{
  color:var(--text)!important; opacity:1!important; text-decoration:none;
}
body .sidebar.collapsed .nav-kids a:hover, body .nav-fly9 a:hover, body .rail-fly9 a:hover{
  background:var(--surface-2); color:var(--text)!important;
}

/* HELP CENTRE: the count on the SELECTED topic. The pill turns brand-orange on
   select and the number kept its muted colour, so "All topics 268" lost its
   number exactly when you picked it. */
body .help-cat.on .help-n9, body .help-cat.on>span:last-child,
body .help-cat.on b, body .help-cat[aria-selected="true"] span{
  color:var(--on-brand)!important; opacity:1!important;
}
/* and the faint box the panel already had — lifted rather than replaced */
body .help-cats{ border-right:1px solid var(--line-2) }

/* MAP OPTIONS: the rows under each heading were still flush left. Indent the
   controls themselves, not just their container. */
body .mo-panel9 .mo-check9, body .mo-panel9 .mo-radio9,
body .mo-panel9 .mo-home9, body .mo-panel9 .mo-link9{
  padding-left:18px!important; padding-right:14px;
}
body .mo-panel9 .mo-sec9{ padding-left:14px }

/* HOVER YOU CAN SEE. Ghost and icon buttons hovered to a colour a few percent
   from their background, so nothing told you the pointer was on them. */
body .btn-ghost:hover, body .icon-btn:hover, body .top-pop-row:hover,
body .setm-row:hover, body .dtr.click:hover, body .sv-tab:hover{
  background:var(--surface-2);
}
body:not(.light) .btn-ghost:hover, body:not(.light) .icon-btn:hover,
body:not(.light) .top-pop-row:hover{ background:rgba(255,255,255,.09) }
body.light .btn-ghost:hover, body.light .icon-btn:hover,
body.light .top-pop-row:hover{ background:rgba(23,21,18,.06) }

/* The live map's bottom strip held only the "Update list when map moves"
   checkbox. That control is gone, so the bar is an empty rule across the panel.
   Its top border was the only thing left of it. */
body .cc-bounds-row{ display:none!important }

/* MAP TYPE radio: the selected dot has to be obvious, and an unselected one has
   to look clickable. `.mo-dot9` is the drawn circle. */
body .mo-radio9{ display:flex; align-items:center; gap:10px; width:100%;
  background:none; border:0; cursor:pointer; color:var(--text); font:inherit; text-align:left }
body .mo-dot9{ width:16px; height:16px; border-radius:50%; flex:none;
  border:2px solid var(--line-3); background:var(--surface); transition:border-color .12s, box-shadow .12s }
body .mo-radio9:hover .mo-dot9{ border-color:var(--tab-active) }
body .mo-radio9.on .mo-dot9{
  border-color:var(--tab-active);
  box-shadow:inset 0 0 0 3px var(--surface), inset 0 0 0 10px var(--tab-active);
}
body .mo-radio9.on{ font-weight:600 }

/* RIGHT-COLUMN ALIGNMENT, properly this time.
   `align-items:start` on the grid was not enough: the two cells are separate
   grid ITEMS, and the left one is taller because it carries a hint line under
   the input. The label of the right cell therefore starts level with the left
   LABEL but its input floats down. Making each cell a column that pushes its
   hint to the bottom keeps both inputs on the same line. */
body .form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:14px 18px; align-items:start }
body .form-grid>.field, body .form-grid>.form-row{
  display:flex; flex-direction:column; gap:6px; min-width:0;
}
body .form-grid>.field>label, body .form-grid>.form-row>.form-row-label{
  min-height:18px; display:flex; align-items:flex-end;
}
/* the hint sits after the control, so a cell without one leaves no gap above */
body .form-grid .field-hint, body .form-grid .form-row-hint{ margin-top:2px }
@media (max-width:760px){ body .form-grid{ grid-template-columns:1fr } }

/* THE TOP-LEFT FADE.
   The scroll container carried a mask/gradient at its top edge that read as a
   dark smear over the first row of the page and the sidebar header. */
body .content::before, body .layout::before, body .sidebar::before{
  content:none!important; background:none!important;
}
body .content, body .layout{ -webkit-mask-image:none!important; mask-image:none!important }

/* RIGHT-COLUMN ALIGNMENT — the actual selector.
   The cells are `label.field-wrap`, not `.field` or `.form-row`, so the previous
   rule never matched them. Measured: left cell top 586, right cell top 606 — a
   20px drop, because the taller left cell (it carries a hint line) was pushing
   the row and the right cell was being baseline-aligned rather than
   top-aligned. Force every cell to start at the row's top edge and carry no
   stray margin, so the two inputs sit on one line. */
body .form-grid>.field-wrap,
body .form-grid>label,
body .form-grid>.field,
body .form-grid>.form-row{
  align-self:start!important;
  margin-top:0!important;
  display:flex!important; flex-direction:column; gap:6px; min-width:0;
}
body .form-grid>.field-wrap>.field-label,
body .form-grid>label>.field-label{
  display:block; min-height:17px; margin:0;
}
body .form-grid>.field-wrap>.input,
body .form-grid>.field-wrap>select,
body .form-grid>.field-wrap>textarea{ margin:0 }
body .form-grid>.field-wrap>.field-hint{ margin:0 }

/* WHOLE-CONTROL FOCUS on the top search pill.
   `:focus-within` needs the pill to be the element carrying the ring; the
   earlier rule was being beaten by the pill's own `:focus-within` declaration
   in design-system.css, which only set a border colour. */
body .tb-search:focus-within{
  border-color:var(--tab-active)!important;
  box-shadow:0 0 0 3px rgba(217,119,87,.34)!important;
}

/* THE FLEET-MAP SEARCH. Its real classes are `.cc-map-search` /
   `.cc-map-search-ic` / `.cc-map-search-inp` — none of which were in the list I
   wrote, so it kept a border on the wrapper AND on the input: the same box-in-a-
   box, still there after I said it was fixed. One control: the wrapper owns the
   box, the icon sits inside it, the input owns nothing. */
body .cc-map-search{
  display:flex; align-items:center; gap:8px;
  border:1px solid var(--line-2)!important; border-radius:10px;
  background:var(--surface); padding:0 12px; height:42px;
  transition:border-color .12s, box-shadow .12s;
}
body .cc-map-search-ic{
  display:flex; align-items:center; flex:none; width:16px; opacity:1; color:var(--muted);
}
body .cc-map-search-ic svg{ width:15px; height:15px }
body .cc-map-search-inp{
  border:0!important; background:transparent!important; box-shadow:none!important;
  outline:0!important; flex:1; min-width:0; padding:0!important; height:100%;
  color:var(--text);
}
body .cc-map-search-inp::placeholder{ color:var(--muted) }
body .cc-map-search:focus-within{
  border-color:var(--tab-active)!important;
  box-shadow:0 0 0 3px rgba(217,119,87,.34)!important;
}

/* A button that is a real button. `secondary` did not exist as a variant, so
   anything that was not the one primary action was written as `ghost` — which
   paints no background at all. On the billing cards that meant "Downgrade to
   Starter", the single action on the card, rendered as plain text. */
body .btn-secondary{
  background:var(--surface-2, var(--surface));
  color:var(--text);
  border:1px solid var(--line-2);
  border-radius:10px;
  font-weight:600;
}
body .btn-secondary:hover:not(:disabled){
  background:var(--surface-3, var(--surface-2, var(--surface)));
  border-color:var(--line-3);
}
body .btn-secondary:active:not(:disabled){ transform:translateY(1px) }
/* Disabled means "this is your current state", not "this failed" — it stays
   legible rather than fading to unreadable. */
body .btn:disabled, body .btn[aria-disabled="true"]{
  cursor:default; opacity:1;
  background:transparent; border-style:dashed; color:var(--muted);
  box-shadow:none;
}
body .plan-cta9{ width:100%; justify-content:center }
body .bill-cancel-cta9{ margin-top:12px }
body .bill-cancel9.is-pending9{ border-color:var(--warn) }
body .bill-cancel-note9{ display:flex; gap:10px; align-items:flex-start }
body .bill-cancel-ic9{ flex:none; color:var(--warn); display:flex; margin-top:2px }
body .bill-cancel-ic9 svg{ width:17px; height:17px }
body .bill-cancel-why9{ color:var(--muted) }
body .bill-contact9{ color:var(--muted) }

/* Shared list row (`histRow`) — used for Open work, activity feeds, history
   panels and more. It had NO horizontal padding, so the icon chip started 1px
   from the row's left edge while the label sat 43px in: the icon looked shoved
   into the corner on every list in the app that uses this helper. */
body .hist-row{ padding:10px 14px }
body .hist-list{ display:flex; flex-direction:column; gap:8px }

/* Any wrapper that holds BOTH an icon and a text input is ONE control: the
   wrapper draws the box, the input draws nothing. Written generically because
   this kept coming back one class at a time — `.cc-search9`, `.cc-map-search`,
   and every search field I had not found yet. */
body :is(div, label, span):has(> input:not([type=checkbox]):not([type=radio])):has(> svg, > span > svg, > i, > .ic) > input{
  border:0!important; background:transparent!important;
  box-shadow:none!important; outline:0!important;
}
body .dw-hint-mut9{ color:var(--muted); margin-top:6px }

/* ── the dark fade in the top-left, and its friends ────────────────────────
   Three separate heavy shadows were stacking up around the top-left corner:
     .topbar    0 8px 24px  rgba(0,0,0,.25)  — a dark band under the header
     .co-logo   0 12px 26px rgba(0,0,0,.20)  — a smudge under the company badge
     .sidebar   10px 0 34px rgba(0,0,0,.12)  — a fade down the sidebar edge
   Together they read as a black gradient smeared across the corner. The app
   separates its chrome with hairlines, so none of these are load-bearing. */
body .sidebar{ box-shadow:inset -1px 0 0 var(--line)!important }
body .sidebar .co-logo{ box-shadow:none!important }
body .topbar{ box-shadow:0 1px 0 var(--line)!important }

/* The off-canvas drawer casts `24px 0 64px rgba(0,0,0,.5)` even when it is
   parked off-screen, so a 64px-blur black haze bled back over the left edge of
   every mobile page. A closed drawer casts nothing. */
@media (max-width:760px){
  body .sidebar{ box-shadow:none!important }
  body .sidebar.open{ box-shadow:24px 0 64px rgba(0,0,0,.38)!important }
}

/* The bar along the bottom of the live map. `.page` gives every page 44px of
   bottom padding so content does not end flush against the viewport — correct
   for a scrolling page, wrong for a full-bleed one. The map stopped 44px short
   and that strip showed the page background through: a white bar in the light
   theme, a black one in the dark theme. The map page fills its pane instead. */
body .page.cc-page{ padding-bottom:0 }
body .cc-page .cc-wrap{ min-height:0 }

/* Map Options sat at the bottom-LEFT of the map, tucked under the vehicle rail
   where it is easiest to miss and furthest from everything else it relates to.
   It belongs on the same side as the rest of the map's own controls. */
body .cc-map-options{ left:auto!important; right:12px!important }
/* …and its panel opens from that corner rather than flying off to the left. */
body .mo-panel9{ right:12px; left:auto }

/* The selected map type had no visual state beyond the dot, so at a glance
   nothing in the panel looked chosen. */
body .mo-radio9.on{ background:color-mix(in srgb, var(--tab-active) 12%, transparent); font-weight:600 }

/* Selects never matched the inputs beside them: 8px text indent against 12px,
   13px type against 14px, 6px corners against 8px. In a two-column form grid
   that puts the label text of adjacent fields — Fuel Type next to Make — on
   different baselines and different left edges. One control, one shape.
   Font size is left to the touch rule further down, which lifts every control
   to 16px on coarse pointers to stop iOS zooming the page on focus. */
body select.input,
body .form-grid select,
body .field-wrap select{
  padding-left:12px; padding-right:32px;
  border-radius:8px;
  font-size:14px;
  min-height:42px;
}

/* FAQ page: the question list is the shared `.land-faq` block, which centres
   itself with auto margins because on the marketing landing page it sits in a
   full-bleed section. Inside the FAQ page's two-column grid that centring left
   it floating 63px to the right of its own section heading — and once the card
   background came off, nothing explained the offset any more. Align it to the
   column it lives in. */
body .faq-group9 .land-faq,
body .faq-body9 .land-faq{
  margin-left:0; margin-right:0;
  width:100%; max-width:none;
}

/* The one selected-state that still failed a contrast check: `solid-warn`
   badges painted near-black text (#2A1D02) on dark amber (#8A5C00) — 2.83:1,
   so "Open" was barely legible. The amber is dark enough to carry white. */
body.light .badge.solid-warn,
body.light .badge-solid-warn{ color:#fff }

/* Dashboard rows ended ragged: 12 widgets whose spans total 40 over a
   12-column grid break as 12 / 12 / 10 / 6, leaving 274px dead on one row and
   821px on the last. Dense packing lets a later narrow widget backfill an
   earlier hole, and the final widget stretches to the end of its row so the
   grid never trails off into empty background. */
body .dash-grid{ grid-auto-flow:row dense }
body .dash-grid > .dw:last-child{ grid-column-end:-1 }

/* Every other widget spans 3 or 6, both of which tile a 12-column row exactly.
   `recent_comments` spanned 4, so any row containing it summed to 10 and left a
   274px hole on the right that no other widget could backfill. Six pairs with
   the two span-3 widgets beside it and the row closes. */
/* !important because polish-v80.css sets span 4 through `:has(.donut)`,
   which outranks a plain attribute selector. */
body .dash-grid > [data-dwk="recent_comments"]{ grid-column:span 6!important }

/* ── sidebar scale ─────────────────────────────────────────────────────────
   The rail read as zoomed-out next to the rest of the app: 232-240px wide with
   36px rows, 14px labels and 16-17px icons, while the content beside it uses
   14px text in 42px controls. Everything here moves up one step so the nav
   matches the density of what it sits next to. Widths are on the variables so
   the collapsed rail, the flyout and the content offset all follow. */
body{ --sidebar-w:272px }
body .sidebar{ width:var(--sidebar-w) }
body .nav-item{
  min-height:42px;
  font-size:15px;
  padding-left:12px; padding-right:12px;
  gap:12px;
}
body .nav-ico{ width:19px; height:19px }
body .nav-ico svg{ width:19px; height:19px }
/* Group headings and the workspace switcher scale with the items under them,
   otherwise the rail just looks like it has bigger gaps. */
body .side-brand, body .side-account{ min-height:64px }
body .side-acct-btn{ font-size:15px }
body .nav-sec, body .nav-label{ font-size:12px; letter-spacing:.04em }
body .nav-kids .nav-item{ font-size:14.5px; min-height:38px }

/* Nav icons: `.nav-ico` fixes its own 16px box in design-system.css, so sizing
   only the svg did nothing. */
body .nav-ico{ width:19px!important; height:19px!important }
body .nav-ico svg{ width:19px!important; height:19px!important }

/* Hover was a 6% tint in the light theme and 9% in the dark one — technically
   present, in practice invisible, which is why hovering a ghost or icon button
   looked like nothing happened. Text contrast was never the problem; the
   surface change was just too small to read. A stronger fill plus a visible
   edge makes the target obvious without shouting. */
body.light .btn-ghost:hover, body.light .icon-btn:hover, body.light .tb-btn:hover{
  background:rgba(23,21,18,.10); border-color:var(--line-3);
}
body:not(.light) .btn-ghost:hover, body:not(.light) .icon-btn:hover, body:not(.light) .tb-btn:hover{
  background:rgba(255,255,255,.16); border-color:var(--line-3);
}
body .nav-item:hover{ background:color-mix(in srgb, var(--text) 9%, transparent) }

/* Icons and labels on brand-coloured buttons were `--on-brand: #1A1917` —
   near-black on the orange. That is why every contrast sweep passed while the
   buttons still read as "black icons": #1A1917 on #D97757 measures 5.63:1, so
   nothing flagged it. White is what was asked for. It measures 3.12:1, which
   clears the 3:1 bar for icons, controls and large/bold labels but is under
   4.5:1 for small body text — so this stays scoped to buttons and chips, and
   their labels are 600-weight. The palette itself is untouched. */
body{ --on-brand:#FFFFFF }
body .btn-primary, body .btn-primary .btn-ic,
body .btn-upgrade, body .btn-upgrade .btn-ic{ color:#fff; font-weight:600 }
body .btn-primary svg, body .btn-upgrade svg{ color:#fff }

/* Selecting a vehicle from the map has to show in the list too: the row was
   the only thing that changed, so the status dot beside it stayed the same
   colour and the selection was easy to miss. */
body .ccu-row.sel, body .ccu-row[aria-selected="true"]{
  background:color-mix(in srgb, var(--tab-active) 14%, transparent);
  box-shadow:inset 3px 0 0 var(--tab-active);
}
body .ccu-row.sel .cc-dot{ box-shadow:0 0 0 3px color-mix(in srgb, var(--tab-active) 45%, transparent) }

/* The parked "unsaved changes" bar left 3px of itself poking above the
   viewport bottom on every settings page. Park it fully off. */
/* It sits at `bottom:22px`, so shifting it by its own height alone still
   left 13px of the bar on screen. Clear the height AND the offset. */
body .setx-dirty:not(.on):not(.show){ transform:translateY(calc(100% + 26px))!important }

/* Audit log controls. */
body .audit-filters9{ display:flex; gap:10px; flex-wrap:wrap; margin:12px 0 8px }
body .audit-filters9 .input{ flex:1 1 260px; min-width:0 }
body .audit-filters9 select{ flex:0 0 auto; min-width:150px }
body .audit-count9{ margin:0 0 10px }
body .audit-list9{ display:flex; flex-direction:column }
