/* ===========================================================================
   RPIT OPS — shared application stylesheet
   Handoff item 22 · docs/RPIT_OPS_Handoff.md §3h

   HAND-WRITTEN. Safe to edit.
   The brand values it builds on are NOT — those live in brand-tokens.css,
   which is generated from rpit_website/style.css by sync_brand_tokens.py.
   Never edit that file; change the website's :root and re-run the sync.

   WHY THIS FILE EXISTS
   Every template used to carry its own inline <style>, including a
   copy-pasted "RPIT mobile-app patch v1" block. Same duplication problem as
   the quotes/inspections code (item 11), expressed in CSS: hub.html even ran
   a completely separate token set (--navy/--blue/--ink) from dashboard.html.

   THE "CALMER" LAYER
   §3h: match the public site's family, but tuned for a tool stared at all
   day rather than a landing page — smaller type, tighter radii, tighter
   rhythm, and none of the marketing motion (marquee, pulse rings, gradient
   hero). Client-facing surfaces (item 24) do NOT get this softening; they
   follow the site closely.

   ORDER OF PRECEDENCE
   Templates still carry their own inline <style> after this link, so legacy
   page styles continue to win until each screen is migrated. That makes
   adoption incremental and non-breaking.
   =========================================================================== */

@import url("brand-tokens.css");

/* ---------------------------------------------------------------- fonts ---
   Item 22 gap, caught by checking rather than assuming: brand-tokens.css
   NAMES the brand faces in --fd/--fb/--fm, but nothing ever served them to the
   browser. The CSS asked for "Plus Jakarta Sans", the browser had never heard
   of it, and every screen silently fell back to the system sans -- so the PDFs
   carried the brand typography and the web app did not.

   Self-hosted, not fetched from Google, for the same reason as everywhere else
   in this codebase: a runtime fetch is what caused the item 4 stall.
   font-display:swap so text is readable immediately rather than invisible
   while a face loads.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("fonts/BricolageGrotesque.ttf") format("truetype");
  font-weight: 200 800;
  font-display: swap;
}
@font-face {
  font-family: "Plus Jakarta Sans";
  src: url("fonts/PlusJakartaSans.ttf") format("truetype");
  font-weight: 200 800;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/JetBrainsMono.ttf") format("truetype");
  font-weight: 100 800;
  font-display: swap;
}



:root {
  /* SAY OUT LOUD THAT THIS IS A LIGHT PAGE.
     Without this a phone set to dark still restyles everything the BROWSER
     draws rather than the stylesheet: number and date inputs, selects, tick
     boxes, scroll bars. On the machine card that is the warranty months box
     and the Set beside it -- a dark control on a light card, on a screen that
     was never asked to be dark. It also tells Android Chrome's "auto dark
     theme" to leave the page alone instead of inverting it by algorithm.
     See ONE PALETTE, LIGHT further down for why there is no dark half. */
  color-scheme: light;

  /* --- calmer scale: deliberate deviations from the site, see §3h --- */
  --r-app: 14px;          /* site --r is 22px — too bubbly at tool density */
  --r-sm: 10px;
  --r-pill: 100px;

  --fs-body: 15px;
  --fs-sm: 13px;
  --fs-xs: 11px;
  --fs-h1: 24px;
  --fs-h2: 19px;
  --fs-h3: 16px;

  --sp-1: 4px;  --sp-2: 8px;   --sp-3: 12px;
  --sp-4: 16px; --sp-5: 24px;  --sp-6: 32px;

  --tap: 44px;            /* iOS minimum touch target */
  --maxw: 720px;

  --shadow-sm: 0 1px 2px rgba(11, 43, 48, .05);
  --shadow-md: 0 8px 24px -12px rgba(11, 43, 48, .22);

  /* --- status colours (item 22, second pass) ---
     brand-tokens.css is GENERATED from the website and must never be
     hand-edited, and the site has no success/warning/danger scale -- it has
     no forms to validate. A tool does. These are defined HERE, derived to sit
     beside the brand hues rather than beside Bootstrap's, so a "paid" pill and
     a "sent" badge belong to the same family as everything else.

     Before this, the same green appeared as #E3F6E8, #157347 and #0A5 in
     three different templates, and the same red as #FEE, #F99, #B42318 and
     #C0392B. That is what 140 hardcoded colours across the app looked like. */
  --ok:      #157347;
  --ok-l:    #E3F6E8;
  /* Avatar hue. Set per row inline (style="--h:262") from the thread, so two
     threads with one client are distinguishable; declared here so it always
     resolves and so verify_agenda's token check can see it. */
  --h: 210;
  --warn:    #B5540B;
  --warn-l:  #FEF0E2;
  --danger:  #B42318;
  --danger-l:#FDECEA;
  --info:    #0B61AF;
  --info-l:  #EAF2FB;

  /* Surfaces used by inputs and inset panels. */
  --surface:   var(--card);
  --surface-2: var(--paper);

  /* --- legacy aliases ---
     hub.html and pattern_review.html each ran their OWN private token set
     (--navy/--blue/--green/--amber/--border/--muted/--bg/--red). When their
     rules were moved into this file the tokens stayed behind, so every moved
     rule referencing one resolved to NOTHING and silently inherited: the
     "sent" badge lost its green, the locked banner lost its colour, the lock
     button fell back to the same teal as the AI button beside it.

     An undefined custom property does not warn — it just quietly does nothing,
     which is why this survived a screenshot review. Mapped here rather than
     hunted through 700 lines, so nothing can still be pointing at a name that
     does not exist. */
  --green:  var(--ok);
  --blue:   var(--info);
  --navy:   var(--signal-d);
  --amber:  var(--warn);
  --red:    var(--danger);
  --border: var(--line);
  --muted:  var(--ink-3);
  --bg:     var(--paper);
}

/* ---------------------------------------------------------------- base --- */
* , *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--fb), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  overflow-x: hidden;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

h1, h2, h3 {
  font-family: var(--fd), var(--fb), sans-serif;
  letter-spacing: -.02em;
  margin: 0;
}
h1 { font-size: var(--fs-h1); font-weight: 800; }
h2 { font-size: var(--fs-h2); font-weight: 700; }
h3 { font-size: var(--fs-h3); font-weight: 700; }

.muted { color: var(--ink-3); }
.small { font-size: var(--fs-sm); }

/* Mono uppercase section label — carried over from the site, one of the
   strongest cues that this is the same product family. */
.eyebrow {
  font-family: var(--fm), ui-monospace, monospace;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--signal-d);
  margin-bottom: var(--sp-2);
}

/* -------------------------------------------------------------- layout --- */
main, .page { max-width: var(--maxw); margin: 0 auto; padding: var(--sp-5) var(--sp-4) 60px; }
.page-wide { max-width: 1080px; }

/* Bare <header> is included: the blueprint base templates use `<header>` with a
   plain <div> of navlinks rather than the .app-header/.navlinks classes. Styling
   both means those bases inherit the shared header without touching their markup. */
header, .app-header {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap;
  background: rgba(251, 252, 252, .88);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  padding: max(var(--sp-3), env(safe-area-inset-top)) var(--sp-4) var(--sp-3);
}
.app-header .brand, header .brand {
  font-family: var(--fd), var(--fb), sans-serif;
  font-weight: 800; font-size: var(--fs-h3); color: var(--ink);
  letter-spacing: -.02em; flex-shrink: 0;
}
/* Nav scrolls horizontally on narrow screens instead of wrapping into a
   second row that eats vertical space. */
.app-header .navlinks, header > div:last-child {
  display: flex; align-items: center; gap: var(--sp-1);
  overflow-x: auto; white-space: nowrap; max-width: 100%;
  scrollbar-width: none;
}
.app-header .navlinks::-webkit-scrollbar,
header > div:last-child::-webkit-scrollbar { display: none; }
.app-header .navlink, header .navlink {
  display: inline-flex; align-items: center; min-height: 40px;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-sm);
  color: var(--ink-2); font-size: var(--fs-sm); font-weight: 600;
  flex-shrink: 0;
}
.app-header .navlink:hover, header .navlink:hover { color: var(--signal-d); background: var(--signal-l); }
.app-header .navlink:active { background: var(--signal-l); }

/* --------------------------------------------------------------- cards --- */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-app);
  padding: var(--sp-4);
  box-shadow: var(--shadow-sm);
}
.card + .card { margin-top: var(--sp-3); }
.card-link { display: flex; align-items: center; gap: var(--sp-4); transition: .18s; }
.card-link:hover { border-color: var(--signal); box-shadow: var(--shadow-md); transform: translateY(-1px); }
.card-link:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

/* Pastel icon chip — site pattern, re-pointed at the real brand tokens.
   Replaces dashboard.html's old ad-hoc .tint-* colours. */
.chip-icon {
  flex-shrink: 0; width: 42px; height: 42px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--signal-l); color: var(--signal-d);
}
.chip-icon svg { width: 20px; height: 20px; }
.chip-icon.aurora { background: var(--aurora-l); color: var(--aurora); }
.chip-icon.gold   { background: var(--gold-l);   color: #8A5B00; }
.chip-icon.coral  { background: var(--coral-l);  color: #B33A2C; }
.chip-icon.neutral{ background: #EFF3F3;         color: var(--ink-2); }
.chip-icon.wa     { background: #E4F8EC;         color: #128C4A; }

/* ------------------------------------------------------------- buttons --- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: inherit; font-size: var(--fs-body); font-weight: 700;
  cursor: pointer; touch-action: manipulation; transition: .18s;
}
.btn-primary   { background: var(--signal);  color: #fff; }
.btn-primary:hover  { background: var(--signal-d); }
.btn-secondary { background: var(--card); color: var(--ink-2); border-color: var(--line); }
.btn-secondary:hover{ border-color: var(--signal); color: var(--signal-d); }
.btn-wa        { background: var(--wa); color: #fff; }
.btn-danger    { background: var(--coral); color: #fff; }
.btn-ghost     { background: transparent; color: var(--signal-d); }
.btn:disabled  { opacity: .5; cursor: not-allowed; }
.btn-block     { display: flex; width: 100%; }

/* --------------------------------------------------------------- forms --- */
label { display: block; font-weight: 700; font-size: var(--fs-sm); margin-bottom: var(--sp-2); }

input[type=text], input[type=number], input[type=password], input[type=tel],
input[type=email], input[type=date], textarea, select {
  width: 100%;
  /* 16px prevents iOS auto-zoom on focus — do not lower this. */
  font-size: 16px;
  font-family: inherit;
  color: var(--ink);
  padding: var(--sp-3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
input:focus, textarea:focus, select:focus {
  outline: none; border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-l);
}
textarea { min-height: 120px; resize: vertical; line-height: 1.55; }
.field { margin-bottom: var(--sp-4); }
.hint { font-size: var(--fs-sm); color: var(--ink-3); margin-top: var(--sp-2); }

/* ------------------------------------------------- status + feedback ----- */
/* Colour always means something (site discipline): gold = waiting,
   coral = problem, signal = active, neutral = done. */
.pill {
  display: inline-block; border-radius: var(--r-pill);
  padding: 3px 10px; font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .02em; vertical-align: middle;
}
.pill-active  { background: var(--signal-l); color: var(--signal-d); }
.pill-waiting { background: var(--gold-l);   color: #8A5B00; }
.pill-problem { background: var(--coral-l);  color: #B33A2C; }
.pill-done    { background: #EFF3F3;         color: var(--ink-2); }

.flash {
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm); font-weight: 600;
  margin-bottom: var(--sp-4);
  background: var(--gold-l); color: #8A5B00;
  border: 1px solid #F3DFB4;
}
.flash-error { background: var(--coral-l); color: #B33A2C; border-color: #F6CFC9; }
.flash-ok    { background: var(--signal-l); color: var(--signal-d); border-color: #C4EBF0; }

/* --------------------------------------------- notices, by severity --- */
/* "Invoice saved." and "PDF file not found on server." used to render
   identically, so a failure read like a success. The bare .flash above stays
   as the amber default -- anything not classified is still visible. */
.flashes{display:flex;flex-direction:column;gap:8px;margin-bottom:var(--sp-4)}
.flashes .flash{margin-bottom:0;display:flex;align-items:flex-start;gap:9px}
/* THE ICON IS A SHAPE, NOT ONLY A COLOUR. Colour alone fails a colour-blind
   reader, and fails again in a screenshot printed in black and white -- which
   is how a disputed message usually reaches anybody. */
.flash-i{flex:0 0 auto;font-weight:800;line-height:1.35;font-size:13px;
  width:17px;height:17px;border-radius:99px;display:inline-flex;
  align-items:center;justify-content:center;
  background:currentColor;color:#fff;margin-top:1px}
.flash-i::selection{background:transparent}
.flash-error .flash-i{background:#B33A2C}
.flash-warn  .flash-i{background:#8A5B00}
.flash-ok    .flash-i{background:var(--signal-d)}
.flash-info  .flash-i{background:#3C6E77}
.flash-t{min-width:0}
.flash-warn{background:var(--gold-l);color:#8A5B00;border-color:#F3DFB4}
.flash-info{background:var(--surface-2);color:#2B5B63;border-color:rgba(11,43,48,.1)}
/* A failure is the one that should stop the eye. */
.flash-error{box-shadow:0 2px 10px -6px rgba(179,58,44,.65)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* ------------------------------------------------- ONE PALETTE, LIGHT ---
   THERE IS NO DARK MODE HERE, AND NOTHING MAY ADD A PIECE OF ONE.

   Twenty-one rules used to sit in this file -- lifted from the sales-v3
   prototype along with the machine card -- that turned card BACKGROUNDS dark
   when the phone was set to dark. The prototype also carries a full dark
   palette (sales-v3.html, :root and the two dark blocks under it). This
   stylesheet does not, and brand-tokens.css does not either: --ink is
   #0B2B30 and --card is #FFFFFF, in every condition.

   So on a phone in dark mode the page stayed light, the machine cards went
   dark green, and every word on them -- model, price, "12 months left", the
   specs, the serial -- became near-black on near-black. Reported from a
   phone, 29 Aug, with a screenshot of a card that could not be read at all.

   A dark theme is all of the tokens or none of them. Half of one is worse
   than neither, because the half that flips takes its text with it. If dark
   mode is ever wanted, it starts in brand-tokens.css with the whole palette
   and is checked screen by screen -- not by darkening one component.
   ----------------------------------------------------------------------- */

/* ----------------------------------------------- legacy class bridges --- */
/* The blueprint bases (quotes/inspections/messages) and their extending pages
   already use these class names. Defining them here rather than renaming
   markup across ~14 templates keeps the migration low-risk: same HTML, shared
   tokens. New screens should prefer .btn / .card / .pill-* above. */

/* buttons: existing markup says btn-primary/btn-secondary without .btn */
.btn-primary, .btn-secondary, .btn-wa, .btn-danger, button[type=submit] {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--tap); padding: var(--sp-3) var(--sp-5);
  border: 1px solid transparent; border-radius: var(--r-pill);
  font-family: inherit; font-size: var(--fs-body); font-weight: 700;
  cursor: pointer; touch-action: manipulation; transition: .18s;
}
button[type=submit] { background: var(--signal); color: #fff; }

/* two-up field row, collapses on mobile */
.row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
@media (max-width: 600px) { .row { grid-template-columns: 1fr; } }

/* line-item repeater (quote + inspection forms) */
.repeat-row {
  position: relative;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-app); padding: var(--sp-4);
  margin-bottom: var(--sp-3);
}
.add-row-btn {
  background: var(--signal-l); color: var(--signal-d);
  border: 1px dashed var(--signal); border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4); font-weight: 700; font-size: var(--fs-sm);
  cursor: pointer; width: 100%; min-height: var(--tap);
}
.add-row-btn:hover { background: #DBF3F6; }
.remove-row {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  background: transparent; color: var(--ink-3); border: none;
  font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  padding: var(--sp-2); min-height: auto;
}
.remove-row:hover { color: var(--coral); }

/* invoice status pills — colour carries meaning (see .pill-* above) */
.pill.proforma  { background: var(--gold-l);   color: #8A5B00; }
.pill.paid      { background: var(--signal-l); color: var(--signal-d); }
.pill.refunded  { background: #EFF3F3;         color: var(--ink-2); }
.pill.cancelled { background: var(--coral-l);  color: #B33A2C; }

/* -------------------------------------------------------------- tables --- */
/* Wide tables scroll inside their own container so the page body never
   scrolls horizontally on a phone. */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.data { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.data th, table.data td {
  text-align: left; padding: var(--sp-3);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
table.data th {
  font-family: var(--fm), monospace; font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3);
  font-weight: 600;
}

/* Money and case refs in mono so columns align and IDs stay scannable. */
.mono, .money, .caseref { font-family: var(--fm), ui-monospace, monospace; }
.money { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------- responsive --- */
@media (max-width: 600px) {
  :root { --fs-h1: 21px; --fs-h2: 17px; }
  main, .page { padding: var(--sp-4) var(--sp-3) 48px; }
  .app-header { padding-left: var(--sp-3); padding-right: var(--sp-3); }
  .btn { width: 100%; }               /* thumb-friendly full-width actions */
  .btn-inline { width: auto; }        /* opt out where inline is wanted */
  .card { padding: var(--sp-3); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ------------------------------------------------- copy-to-clipboard --- */
/* Item 15. Every message field in this app is copy-pasted into WhatsApp by
   hand, and until now not one screen had a copy button -- the templates only
   contained hint text telling the operator to select-and-drag, which on a
   phone is the highest friction-per-day action in the whole product. */
.copy-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--signal-l); color: var(--signal-d);
  border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 8px 14px; font-size: var(--fs-sm); font-weight: 700;
  cursor: pointer; min-height: 38px; touch-action: manipulation;
}
.copy-btn:hover { background: #DBF3F6; }
.copy-btn.copied { background: var(--wa); color: #fff; }

/* ---------------------------------------------------------------- mobile ---
   A wide table must scroll INSIDE itself. Without this the page body scrolls
   horizontally and the entire layout slides under the thumb -- the classic
   phone break, and the audit found it on the inspection detail and history
   screens, both of which are exactly where a dense table belongs.

   Applied here rather than per-template so a table added later is covered by
   default instead of reintroducing the bug. */
table { width: 100%; border-collapse: collapse; }
.table-scroll, table { display: block; max-width: 100%; overflow-x: auto;
  -webkit-overflow-scrolling: touch; }
@media (min-width: 700px) { table { display: table; } }

/* The body itself must never scroll sideways. If something overflows, this
   makes it obvious in testing instead of silently shifting the layout. */
html, body { max-width: 100%; overflow-x: hidden; }

/* iOS zooms the whole page when a focused input is under 16px, and does not
   zoom back out. Every text-entry control is therefore floored at 16px. */
input, select, textarea, button { font-size: max(16px, 1rem); }

/* hidden MEANS HIDDEN.
   The attribute only sets display:none at user-agent strength, so any rule
   that gives the element a display of its own silently beats it. `.st-set .why
   { display: flex }` did exactly that: the "Why? / Close it" form is written
   `<div class="why" hidden>` and revealed when the status select says closed,
   but it was showing open on EVERY case row, colliding with the status pills
   and the client column. Nothing about the markup was wrong.
   One rule, so it cannot happen again to the next thing written this way. */
[hidden] { display: none !important; }

/* Touch targets. 44px is the Apple HIG minimum and roughly a fingertip. */
button, .btn, .btn-primary, .btn-secondary, input[type="submit"],
select, input[type="search"], input[type="text"], input[type="tel"],
input[type="number"], input[type="date"] { min-height: 44px; }

/* Long refs, emails and URLs must wrap rather than force the page wide. */
.mono, code, pre { overflow-wrap: anywhere; word-break: break-word; }
pre { white-space: pre-wrap; }

/* Keep the last control clear of the home indicator on notched phones. */
main { padding-bottom: max(24px, env(safe-area-inset-bottom)); }

/* item 39 -- payment graphics, reachable from the thread */
.pay-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }
.pay-card { flex: 1 1 190px; display: flex; flex-direction: column; gap: 2px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 16px; text-decoration: none; color: var(--ink); min-height: 44px; }
.pay-card:active { background: var(--signal-l); }
.pay-ico { font-size: 22px; }
.pay-t { font-weight: 700; font-size: 15px; }
.pay-s { font-size: 13px; color: var(--ink-3); }

/* ------------------------------------------------ thread control sizing ---
   Two real defects the operator photographed, both caused by a blanket rule
   further up: `button[type=submit]` is styled as a full pill (44px tall, wide
   padding, teal, pill radius). Anything that submits inherits that, whether or
   not it is a primary action.

   1. The delete "X" on a section is a submit button, so it rendered as a
      GIANT TEAL PILL — visually the loudest control on the page, for the most
      destructive action. Exactly backwards.
   2. In the add-a-message row the <select> had `flex: 1`, which means
      flex-basis 0. Against a button holding its width through padding, the
      select collapsed to about 30px — unreadable and unusable, while "Add"
      took the whole row.

   Fixed by sizing intent, not by nudging pixels: the picker is the important
   control so it gets the room; the button takes only what it needs; and a
   destructive icon button is quiet. */

.pick-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pick-row select {
  /* grow, but never below a width where the option text is readable */
  flex: 1 1 240px;
  min-width: 180px;
}
.pick-row > button[type=submit],
.pick-row .btn-primary { flex: 0 0 auto; padding-left: var(--sp-5); padding-right: var(--sp-5); }
.pick-row textarea { flex: 1 1 100%; min-height: 70px; }
@media (max-width: 460px) {
  /* below this a select and a button cannot share a row legibly */
  .pick-row select, .pick-row > button[type=submit], .pick-row .btn-primary { flex: 1 1 100%; }
}

/* Destructive icon button: quiet until you reach for it. Overrides the
   submit-pill inheritance above. */
.x-btn, button.x-btn[type=submit] {
  background: transparent; color: var(--ink-3);
  border: 1px solid var(--line); border-radius: 10px;
  min-height: 32px; height: 32px; width: 32px; min-width: 32px;
  padding: 0; font-size: 15px; font-weight: 600; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.x-btn:hover, .x-btn:focus-visible {
  color: var(--danger); border-color: var(--danger); background: var(--danger-l);
}

.inbound-fold { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
.inbound-fold > summary { cursor: pointer; list-style: none; font-size: var(--fs-sm);
  font-weight: 700; color: var(--ink-3); min-height: 32px; display: flex; align-items: center; }
.inbound-fold > summary::-webkit-details-marker { display: none; }

/* Secondary/small submit actions. Same root cause as the giant delete X:
   `button[type=submit]` is styled as a full pill, so EVERY submit inherits a
   44px teal pill whatever its importance. The client directory's Duplicate /
   Archive / Restore are .mini and were rendering as loud as the primary
   action next to them. Sizing should follow intent, not element type. */
.mini, button.mini[type=submit] {
  background: var(--card); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  min-height: 36px; padding: 6px 14px;
  font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.mini:hover { border-color: var(--signal); color: var(--signal); }
.mini.wa, a.mini.wa { background: var(--wa); border-color: var(--wa); color: #fff; }
.mini.mini-missing { background: var(--surface-2); color: var(--ink-3);
  border: 1px dashed var(--line); }

/* Inline `style="flex:1"` on inputs has the same collapse problem as the
   select did — a flex-basis of 0 against a padded sibling. */
.pick-row input[type="text"] { flex: 1 1 200px; min-width: 160px; }

/* ------------------------------------------------------ inbox list rows ---
   Standard inbox anatomy: initial, name + time on one line, a PREVIEW of the
   last thing said underneath. A row showing only a phone number tells you
   nothing about which conversation to open, which is what this list was. */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }

a.enq { display:flex; gap:12px; align-items:center; padding:13px 16px;
  border-bottom:1px solid var(--line); text-decoration:none; color:var(--ink);
  min-height:64px; }
a.enq:last-child { border-bottom:none; }
a.enq:active { background:var(--signal-l); }
.enq .avatar { flex:0 0 38px; width:38px; height:38px; border-radius:50%;
  background:var(--signal-l); color:var(--signal-d); font-weight:800;
  display:flex; align-items:center; justify-content:center; font-size:15px; }
.enq .who { flex:1; min-width:0; }
.enq .nm-row { display:flex; align-items:baseline; gap:8px; }
.enq .nm { font-weight:700; font-size:15px; flex:1; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.enq .when { font-size:11px; color:var(--ink-3); flex:0 0 auto; }
/* one line, ellipsised — a preview that wraps to three lines is not a preview */
.enq .ph { font-size:13px; color:var(--ink-3); margin-top:2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Compose is folded: one action among many, not the whole screen. */
.compose-fold > summary { cursor:pointer; list-style:none; font-weight:700;
  color:var(--signal); font-size:15px; min-height:44px; display:flex;
  align-items:center; }
.compose-fold > summary::-webkit-details-marker { display:none; }

/* Section heads read as list headers, not page titles. */
.sec-head { display:flex; align-items:center; gap:8px; margin:22px 0 8px; }
.sec-head h2 { font-size:13px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--ink-3); margin:0; }
.sec-head .count { font-size:11px; font-weight:700; color:var(--ink-3);
  background:var(--surface-2); border-radius:999px; padding:2px 8px; }

/* ---------------------------------------------- pipeline / inbox screen ---
   Moved out of pipeline.html. Inline <style> loads AFTER this stylesheet, so
   anything declared there silently wins — which is how the conversation
   preview stayed monospace through two separate "fixes" here. Control and
   list styling lives in exactly one file now. */
.empty { padding: 18px 16px; color: var(--ink-3); font-size: var(--fs-sm); }

#inbound { width:100%; box-sizing:border-box; font-size:16px; padding:13px;
  border:1px solid var(--line); border-radius:10px; background:var(--surface-2);
  min-height:88px; font-family:inherit; line-height:1.5; }
#cli-phone, #cli-nm, #resume-q, #cli-q {
  width:100%; box-sizing:border-box; font-size:16px; padding:13px;
  border:1px solid var(--line); border-radius:10px;
  background:var(--surface-2); min-height:46px; }

.cli-hit { display:block; width:100%; text-align:left; background:var(--card);
  border:1px solid var(--line); border-radius:12px; padding:12px 14px;
  margin-top:8px; min-height:44px; cursor:pointer; text-decoration:none;
  color:var(--ink); }
.cli-hit:active { background:var(--signal-l); }
.cli-hit .n { font-weight:700; font-size:14px; }
.cli-hit .m { font-size:13px; color:var(--ink-3); margin-top:2px; }
.picked-row { display:flex; align-items:center; justify-content:space-between;
  gap:10px; background:var(--signal-l); border-radius:10px; padding:10px 12px;
  margin-bottom:10px; font-weight:700; font-size:14px; }

/* Section chips: twelve of these rendered as solid primary pills, all
   shouting equally, so nothing looked more likely than anything else. They
   are a MENU, not twelve primary actions — quiet by default, with the icon
   doing the recognising. */
/* Specificity note: `button[type=submit]` above is 0-1-1 and a bare class is
   0-1-0, so a class alone LOSES to it and every submit stayed a solid teal
   pill however it was styled. Matching the element and the attribute is what
   makes intent-based styling actually apply. */
button.grp-choice[type=submit], .grp-choice {
  background: var(--card); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  padding: 9px 15px; font-size: 14px; font-weight: 600;
  min-height: 40px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
}
.grp-choice .ic { font-size: 16px; line-height: 1; }
button.grp-choice[type=submit]:hover, .grp-choice:hover, .grp-choice:focus-visible {
  border-color: var(--signal); color: var(--signal-d); background: var(--signal-l);
}
.grp-choices { display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 14px; }

/* Legend: a reference strip, so it should read as one. */
.legend-labels { display: flex; flex-wrap: wrap; gap: 5px 12px;
  font-size: 12px; color: var(--ink-3); line-height: 1.9; }
.legend-labels .on { color: var(--ok); font-weight: 700; }

/* ------------------------------------------------- conversation identity ---
   "hard to know where I am at which convo, no heading in the convo detail
   page." It sticks, so scrolling a long thread never loses who you are
   talking to. */
.convo-id { position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 12px;
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 12px 16px; margin: 0 calc(var(--sp-4) * -1) 14px; }
.convo-avatar { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 50%;
  background: var(--signal-l); color: var(--signal-d); font-weight: 800;
  display: flex; align-items: center; justify-content: center; font-size: 16px; }
.convo-who { flex: 1; min-width: 0; }
.convo-name { font-weight: 800; font-size: 17px; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.convo-sub { font-size: 12px; color: var(--ink-3); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.convo-tag { display: inline-block; background: var(--signal-l);
  color: var(--signal-d); font-weight: 800; font-size: 10px;
  letter-spacing: .04em; border-radius: 4px; padding: 2px 6px; margin-right: 6px; }

/* advance control — set once, used by every money figure downstream */
.advance-box { padding: 14px 16px; margin-bottom: 12px; }
.adv-head { display:flex; justify-content:space-between; align-items:baseline;
  gap:10px; font-size:14px; margin-bottom:10px; }
.adv-now { font-weight:800; color:var(--ok); font-size:15px; }
.adv-or { color:var(--ink-3); font-size:13px; flex:0 0 auto; }
.advance-box .pick-row input[type=number] { flex:1 1 120px; min-width:100px;
  font-size:16px; padding:11px; border:1px solid var(--line);
  border-radius:10px; background:var(--surface-2); min-height:46px; }

a.count { text-decoration:none; }
a.count:hover { background:var(--signal-l); color:var(--signal-d); }

/* ================================================== THREAD COMPONENT ======
   Moved out of hub.html. The thread markup is shared by the case hub AND the
   conversation screen, but its CSS was sitting in ONE template's inline
   <style> — so the conversation rendered the same component completely
   unstyled. A shared component whose styling is not shared is not shared.
   ========================================================================= */
h2.section-label {
    font-size: 13px; text-transform: uppercase; letter-spacing: .05em;
    color: var(--muted); margin: 24px 4px 10px;
  }
/* -- big pinned cards: case details + scope lock -- */
  .pin-card {
    background: var(--card); border: 2px solid var(--border); border-radius: 18px;
    padding: 4px 18px; margin-bottom: 16px;
  }
.pin-card.locked { border-color: var(--green); }
.pin-card.unlocked { border-color: var(--amber); }
.pin-card > summary {
    padding: 18px 0; font-weight: 800; font-size: 18px; cursor: pointer;
    list-style: none; display: flex; align-items: center; justify-content: space-between;
  }
.pin-card > summary::-webkit-details-marker { display: none; }
.pin-card > summary .chev { color: var(--muted); font-size: 22px; transition: transform .15s; }
.pin-card[open] > summary .chev { transform: rotate(90deg); }
.pin-card .inner { padding-bottom: 20px; }
.status-pill {
    display: inline-block; font-size: 13px; font-weight: 800; padding: 5px 12px;
    border-radius: 999px; margin-left: 8px;
  }
.status-pill.locked { background: var(--ok-l); color: var(--green); }
.status-pill.unlocked { background: var(--warn-l); color: var(--amber); }
.field-row label { display: block; font-weight: 700; font-size: 15px; margin-bottom: 8px; }
/* -- item 13: thread / two-level picker -- */
  .empty-thread { background:var(--card); border:1px dashed var(--border); border-radius:16px;
    padding:22px 18px; color:var(--muted); font-size:15px; margin-bottom:14px; }
.grp { margin-bottom:18px; }
.grp-head { display:flex; align-items:center; gap:10px; margin:0 0 10px; }
.grp-num { flex-shrink:0; width:24px; height:24px; border-radius:999px;
    background:var(--navy); color:var(--card); font-size:12px; font-weight:800;
    display:flex; align-items:center; justify-content:center; }
.grp-label { flex:1; font-size:15px; font-weight:800; letter-spacing:.02em;
    text-transform:uppercase; color:var(--muted); }
.badge.mode { background:var(--info-l); color:var(--blue); }
.doc-note { font-size:12px; font-weight:600; color:var(--green);
    background:var(--ok-l); padding:6px 10px; border-radius:8px;
    display:inline-block; margin-bottom:10px; }
.btn-danger { background:none; border:1px solid var(--danger); color:var(--danger);
    border-radius:12px; padding:10px 14px; font-weight:700; font-size:14px; cursor:pointer; }
.mode-row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:14px; }
details.add-inner, details.add-outer {
    background:var(--card); border:1px dashed var(--border); border-radius:14px;
    padding:0 16px; margin-bottom:12px; }
details.add-inner > summary, details.add-outer > summary {
    padding:14px 0; cursor:pointer; list-style:none; font-weight:700;
    color:var(--blue); font-size:15px; }
details.add-inner > summary::-webkit-details-marker,
  details.add-outer > summary::-webkit-details-marker { display:none; }
details.add-outer > summary { font-size:16px; }
.grp-choices { display:flex; flex-wrap:wrap; gap:8px; padding-bottom:16px; }
.grp-choice { background:var(--info-l); color:var(--blue); border:1px solid var(--info-l);
    border-radius:999px; padding:10px 16px; font-size:14px; font-weight:700; cursor:pointer; }
.grp-choice:hover { background:var(--blue); color:var(--card); }
.inbound { background:var(--surface-2); border-left:3px solid var(--muted);
    border-radius:0 12px 12px 0; padding:12px 14px; margin-bottom:12px; }
.inbound-head { display:flex; justify-content:space-between; align-items:center;
    font-size:12px; font-weight:700; color:var(--muted); margin-bottom:6px; }
.inbound-text { font-size:15px; white-space:pre-wrap; }
.legend { background:var(--card); border:1px solid var(--border); border-radius:16px;
    padding:16px 18px; margin:18px 0; }
.legend-title { font-size:13px; font-weight:800; text-transform:uppercase;
    letter-spacing:.04em; color:var(--muted); margin-bottom:12px; }
.legend-rail { display:flex; gap:6px; align-items:center; margin-bottom:8px; }
.legend-dot { flex:1; height:6px; border-radius:999px; background:var(--border); }
.legend-dot.on { background:var(--green); }
.legend-labels { display:flex; flex-wrap:wrap; gap:6px 10px; font-size:12px;
    color:var(--muted); }
.legend-labels .on { color:var(--green); font-weight:700; }
/* -- gate group + accordion cards -- */
  details.msg-card {
    background: var(--card); border: 1px solid var(--border); border-radius: 16px;
    padding: 0 18px; margin-bottom: 12px;
  }
details.msg-card > summary {
    padding: 18px 0; cursor: pointer; list-style: none;
    display: flex; align-items: center; gap: 14px; min-height: 40px;
  }
details.msg-card > summary::-webkit-details-marker { display: none; }
/* Was a navy pill printing the internal code ("E13"). It now carries the
   group's icon instead -- solid navy behind an emoji reads as a mistake, so
   the pill is a soft tint and the glyph is sized to be the anchor. */
.code-badge {
    flex-shrink: 0;
    background: var(--surface-2); font-size: 20px; line-height: 1;
    border-radius: 10px; padding: 7px 9px; min-width: 42px; text-align: center;
  }
.summary-main { flex: 1; min-width: 0; }
.summary-title { font-size: 17px; font-weight: 700; }
.summary-meta { font-size: 13px; color: var(--muted); margin-top: 3px; }
.badge {
    display: inline-block; font-size: 11px; font-weight: 700; padding: 3px 8px;
    border-radius: 999px; margin-left: 6px; vertical-align: middle;
  }
.badge.draft { background: var(--warn-l); color: var(--amber); }
.badge.locked { background: var(--ok-l); color: var(--green); }
.chevron { flex-shrink: 0; color: var(--muted); font-size: 22px; transition: transform .15s; }
details.msg-card[open] > summary .chevron { transform: rotate(90deg); }
.msg-inner { padding-bottom: 18px; border-top: 1px solid var(--border); padding-top: 16px; }
.stage-note { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
.seed-tag {
    display: inline-block; font-size: 12px; font-weight: 700; color: var(--blue);
    background: var(--info-l); padding: 4px 10px; border-radius: 999px; margin-bottom: 10px;
  }
.note-line { color: var(--amber); font-style: italic; margin-bottom: 10px; font-size: 14px; }
.locked-banner { background: var(--ok-l); color: var(--green); padding: 12px 14px; border-radius: 10px; font-size: 14px; margin-bottom: 14px; font-weight: 600; }
textarea {
    width: 100%; box-sizing: border-box; font-size: 16px; font-family: inherit;
    padding: 14px; border: 1px solid var(--border); border-radius: 12px;
    line-height: 1.5; background: var(--surface-2);
  }
textarea.draft { min-height: 220px; }
textarea.feedback { min-height: 76px; }
label { display: block; font-weight: 700; font-size: 15px; margin: 4px 0 8px; }
.hint { color: var(--muted); font-size: 13px; margin: 6px 0 0; }
.vendor-tag { color: var(--muted); font-size: 13px; margin-top: 8px; }
.action-row { display: flex; gap: 10px; }
.action-row form, .action-row > * { flex: 1; }
.thread-item {
    border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px;
    background: var(--surface-2); font-size: 14px;
  }
.thread-item .meta { color: var(--muted); font-size: 12px; margin-bottom: 4px; }
.thread-item form { margin-top: 8px; }
details.more {
    background: var(--card); border: 1px solid var(--border); border-radius: 14px;
    padding: 4px 16px; margin-bottom: 14px;
  }
details.more summary {
    padding: 14px 0; font-weight: 700; font-size: 15px; cursor: pointer;
    list-style: none;
  }
details.more summary::-webkit-details-marker { display: none; }
details.more summary::after { content: "▾"; float: right; color: var(--muted); }
details.more[open] summary::after { content: "▴"; }
details.more .inner { padding-bottom: 16px; font-size: 14px; }
.context-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px; font-size: 14px; margin-bottom: 8px; }
.history-list { font-size: 14px; color: var(--ink-2); margin: 0; padding-left: 18px; }
.history-list li { margin-bottom: 6px; }
.sub-heading { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 12px 0 6px; }

/* the graphic that belongs to THIS message, inside its card */
.attach-strip { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  background:var(--signal-l); border-radius:12px; padding:10px 12px; margin-top:12px; }
.attach-lbl { font-size:12px; font-weight:700; color:var(--signal-d);
  text-transform:uppercase; letter-spacing:.04em; }
.attach-btn { display:inline-flex; align-items:center; gap:6px;
  background:var(--card); border:1px solid var(--signal); color:var(--signal-d);
  border-radius:var(--r-pill); padding:8px 14px; font-size:14px; font-weight:700;
  text-decoration:none; min-height:40px; }
.attach-btn:active { background:var(--signal); color:#fff; }

/* Locking is a DIFFERENT act from drafting — it commits the message as sent.
   It was `.btn-primary.green`, declared inside hub.html and referencing
   var(--green), a token that has never existed. So it silently fell back to
   the same teal as the AI button on the hub, and on the conversation the rule
   was not loaded at all. Two identical-looking buttons doing opposite things. */
.btn-lock, button.btn-lock[type=submit] {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--ok); border-radius: var(--r-pill);
  background: var(--ok); color: #fff;
  font-family: inherit; font-size: var(--fs-body); font-weight: 700;
  cursor: pointer; touch-action: manipulation;
}
.btn-lock:hover { filter: brightness(1.06); }

/* Equal width, and they stack rather than squeeze on a phone. */
.action-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.action-row > * , .action-row form { flex: 1 1 200px; }

/* Money ACTIONS — one tap drafts the right message with the real figures in
   it. The QR alone was an attachment with nothing to attach it to. */
.money-actions { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px; }
.money-actions form { flex:1 1 200px; display:flex; }
.money-act { flex:1; display:flex; flex-direction:column; align-items:flex-start;
  gap:1px; text-align:left; cursor:pointer;
  background:var(--card); border:1px solid var(--line); border-radius:14px;
  padding:13px 15px; min-height:auto; color:var(--ink); }
.money-act.primary { border-color:var(--signal); background:var(--signal-l); }
.money-act:hover { border-color:var(--signal); }
.money-act .ma-ic { font-size:19px; line-height:1.2; }
.money-act .ma-t { font-weight:700; font-size:15px; }
.money-act .ma-s { font-size:12px; color:var(--ink-3); font-weight:500; }
button.money-act[type=submit] { background:var(--card); color:var(--ink);
  border-radius:14px; padding:13px 15px; }
button.money-act.primary[type=submit] { background:var(--signal-l); }

/* delete sits on the row for a message, same as for a section */
.row-actions { display:inline-flex; align-items:center; flex:0 0 auto; }
.row-actions form { display:inline-flex; }
details.msg-card > summary { align-items:center; }

/* find a message by name — sections are for browsing, this is for finding */
.find-msg { background:var(--card); border:1px solid var(--line);
  border-radius:16px; padding:14px 16px; margin-bottom:12px; }
#find-q { width:100%; box-sizing:border-box; font-size:16px; padding:12px;
  border:1px solid var(--line); border-radius:10px;
  background:var(--surface-2); min-height:46px; }
.find-hit { display:grid; grid-template-columns:auto 1fr; gap:2px 10px;
  width:100%; text-align:left; background:var(--card);
  border:1px solid var(--line); border-radius:12px; padding:11px 13px;
  margin-top:8px; cursor:pointer; min-height:44px; }
.find-hit:hover { border-color:var(--signal); background:var(--signal-l); }
.find-hit .fh-ic { grid-row:1/3; font-size:18px; align-self:center; }
.find-hit .fh-t { font-weight:700; font-size:14px; }
.find-hit .fh-s { font-size:12px; color:var(--ink-3); }

/* ============================================================ app header ===
   One header for every screen. It sticks, because on a long thread the way
   out should not require scrolling to the top. Icon AND label: an icon alone
   is a guess, a word alone is a small target. Labels collapse under 560px so
   the row stays one line on a phone. */
.app-header {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  /* NOWRAP BEATS THE OLDER RULE ABOVE. An earlier `header, .app-header` block
     sets flex-wrap:wrap, so once the ten destinations stopped fitting the bar
     broke onto a second line instead of scrolling -- and the far end still
     could not be reached. The nav scrolls; the header must not wrap for it. */
  flex-wrap: nowrap;
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 8px max(12px, env(safe-area-inset-left)) 8px 12px;
  min-height: 56px;
}
.hdr-left { display: flex; align-items: center; gap: 6px; min-width: 0; }
.hdr-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: 0 0 38px; border-radius: 10px;
  color: var(--ink-2); text-decoration: none;
}
.hdr-back:hover { background: var(--signal-l); color: var(--signal-d); }
.hdr-back svg { width: 20px; height: 20px; }

.hdr-brand {
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; color: var(--ink);
  font-family: var(--fd), var(--fb), sans-serif;
  font-weight: 800; font-size: 16px; letter-spacing: -.01em;
  padding: 6px 8px; border-radius: 10px; min-height: 40px;
}
.hdr-brand { flex: 0 0 auto; }
.hdr-brand:hover { background: var(--surface-2); }
.hdr-brand img { border-radius: 6px; flex: 0 0 26px; }

/* TEN DESTINATIONS, NOT FOUR. The row was a plain flex line, so once the
   items stopped fitting the last ones were simply clipped off the right edge
   with no way to reach them -- "top menu bar is overflowing and unable to
   reach till end". It now scrolls sideways instead of truncating, at every
   width, and a fade on the right edge says there is more. Labels still
   collapse under 860px; the scroll is what makes the widths in between work.
   The scrollbar itself is hidden -- a visible one inside a 56px sticky bar
   eats the row. */
/* IT WRAPS. IT DOES NOT SCROLL.
   "Top menu bar is overflowing and unable to reach till end" -- reported four
   times. Every previous attempt kept it as one scrolling strip and fixed
   something about the scrolling. That was the wrong idea: a menu whose ends he
   cannot see is a menu he has to discover by dragging, and the fade mask made
   a half-scrolled item read as a chopped word -- "Express" as "ess".
   A second row costs 44px on a narrow window. Nothing is ever hidden, nothing
   is ever half-cut, and there is nothing to reach for. */
.hdr-nav {
  display: flex; align-items: center; gap: 2px;
  flex: 1 1 auto; min-width: 0; justify-content: flex-end;
  flex-wrap: wrap; row-gap: 4px;
  overflow: visible;
}
.hdr-left { flex: 0 0 auto; }
/* The header itself grows with the nav rather than clipping it. */
.app-header { align-items: flex-start; padding-top: 8px; padding-bottom: 8px; }
.hdr-left, .hdr-nav { min-height: 40px; }
.hdr-item {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 11px; border-radius: 10px; min-height: 40px;
  text-decoration: none; color: var(--ink-2);
  font-size: 14px; font-weight: 600; white-space: nowrap;
  /* THE ACTUAL CAUSE OF THE HEADER CUTTING OFF, three times reported and
     twice "fixed" on the wrong thing. A flex item shrinks by default, so
     nowrap text got squeezed narrower than itself and clipped -- "Express"
     rendered as "ess". The nav was already scrollable; it never got the
     chance, because the items collapsed first. Do not remove. */
  flex: 0 0 auto;
}
.hdr-item svg { width: 19px; height: 19px; flex: 0 0 19px; }
.hdr-item:hover { background: var(--signal-l); color: var(--signal-d); }
/* the section you are in, so the header answers "where am I" */
.hdr-item.on { background: var(--signal-l); color: var(--signal-d); font-weight: 700; }
.hdr-item.quiet { color: var(--ink-3); }

@media (max-width: 860px) {
  /* Six destinations now, not four. Words go first — the icons still carry
     the meaning, and each keeps a 40px target. */
  .hdr-item span { display: none; }
  .hdr-item { padding: 8px; gap: 0; }
  .hdr-nav { gap: 0; }
}
@media (max-width: 460px) {
  .hdr-brand span { display: none; }
  .app-header { padding-left: 6px; padding-right: 6px; }
  .hdr-item { padding: 8px 6px; }
}

/* "no invoice yet" reads as an instruction, not as an equal action */
.attach-strip:has(.attach-btn.ghost) { background: var(--warn-l); }
.attach-btn.ghost { border-color: var(--warn); color: var(--warn);
  background: var(--card); font-weight: 600; }

/* ---------------------------------------------------- case history log ---
   The chain of what was actually said. Only locked messages appear, so this
   is a record rather than a working area — styled as a transcript, not as
   more cards to act on. */
.case-log { background: var(--card); border: 1px solid var(--line);
  border-radius: 16px; padding: 0 16px; margin: 18px 0; }
.case-log > summary { cursor: pointer; list-style: none; padding: 15px 0;
  font-weight: 800; font-size: 15px; min-height: 44px; display: flex;
  align-items: center; }
.case-log > summary::-webkit-details-marker { display: none; }
.log-inner { padding-bottom: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.log-list { list-style: none; margin: 0; padding: 0; }
.log-item { padding: 10px 0 10px 13px; border-left: 3px solid var(--line);
  margin-bottom: 10px; }
.log-item.us { border-left-color: var(--signal); }
.log-item.client { border-left-color: var(--ink-3); background: var(--surface-2);
  border-radius: 0 10px 10px 0; padding-right: 12px; }
.log-item.evidence { border-left-color: var(--gold); }
.log-meta { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
  font-size: 11px; color: var(--ink-3); margin-bottom: 3px; }
.log-who { font-weight: 800; }
.log-label { font-family: var(--fm), monospace; }
.log-at { margin-left: auto; }
.log-text { font-size: 14px; white-space: pre-wrap; line-height: 1.5; }
.log-item.evidence .log-text { font-style: italic; color: var(--ink-2); }

.said-fold { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
.said-fold > summary { cursor: pointer; list-style: none; font-size: var(--fs-sm);
  font-weight: 700; color: var(--ink-3); min-height: 32px; display: flex; align-items: center; }
.said-fold > summary::-webkit-details-marker { display: none; }
.said-fold textarea { width: 100%; box-sizing: border-box; margin-top: 6px; }

/* drag to reorder — the grip is the handle, so dragging is deliberate rather
   than something that happens while you are trying to select text */
.drag-grip { flex: 0 0 auto; cursor: grab; color: var(--ink-3);
  font-size: 17px; line-height: 1; padding: 6px 4px; user-select: none;
  touch-action: none; }
.drag-grip:active { cursor: grabbing; }
.draggable.is-dragging { opacity: .45; outline: 2px dashed var(--signal);
  outline-offset: 2px; }
.blocks { min-height: 4px; }
/* grip | (head + text) — the head was a flex sibling of the grip, so the
   timestamp and the message competed for the same row and the text got
   squeezed into a narrow column beside a wrapping date. */
.inbound.draggable { display: flex; gap: 8px; align-items: flex-start; }
.inbound-body { flex: 1; min-width: 0; }
.inbound .inbound-head { display: flex; align-items: center; gap: 8px;
  justify-content: space-between; }
.inbound .inbound-head > span:first-child { white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }

.paid-box { padding: 14px 16px; margin-bottom: 12px; border-color: var(--ok); }
.paid-box .pick-row input { flex: 1 1 150px; min-width: 130px; font-size: 16px;
  padding: 11px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-2); min-height: 46px; }

/* ================================================================ quick pay ===
   Small jobs, no invoice. The screen is deliberately blunt: an amount, who,
   and what for. Anything that would slow down a Rs 200 job has been left out. */
.req { font-size: 11px; font-weight: 700; color: var(--danger);
  text-transform: uppercase; letter-spacing: .04em; }
.qp-totals { display: flex; gap: 10px; margin: 16px 0 10px; flex-wrap: wrap; }
.qp-stat { flex: 1 1 120px; background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 14px; }
.qp-stat .n { display: block; font-family: var(--fm), monospace; font-size: 20px;
  font-weight: 800; }
.qp-stat .l { display: block; font-size: 12px; color: var(--ink-3); margin-top: 2px; }

.qp-filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
  margin-bottom: 8px; }
.qp-chip { text-decoration: none; font-size: 13px; font-weight: 600;
  padding: 8px 13px; border-radius: var(--r-pill); min-height: 38px;
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); color: var(--ink-2); background: var(--card); }
.qp-chip.on { background: var(--signal-l); border-color: var(--signal);
  color: var(--signal-d); font-weight: 700; }
.qp-custom { display: flex; gap: 6px; flex-wrap: wrap; }
.qp-custom input { font-size: 14px; padding: 7px 9px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-2); min-height: 38px; }

.qp-row { display: flex; align-items: center; gap: 12px; padding: 13px 16px;
  border-bottom: 1px solid var(--line); text-decoration: none; color: var(--ink);
  min-height: 60px; }
.qp-row:last-child { border-bottom: none; }
.qp-row:active { background: var(--signal-l); }
.qp-amt { font-family: var(--fm), monospace; font-weight: 800; font-size: 15px;
  flex: 0 0 auto; min-width: 76px; }
.qp-who { flex: 1; min-width: 0; }
.qp-who .nm { display: block; font-weight: 700; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qp-who .ds { display: block; font-size: 12px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qp-at { font-size: 11px; color: var(--ink-3); flex: 0 0 auto; }

.qp-share { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.qp-qr { width: 190px; max-width: 100%; border-radius: 12px; border: 1px solid var(--line); }
.qp-actions { display: flex; gap: 10px; flex-wrap: wrap; flex: 1 1 220px; }
.qp-actions form { display: inline-flex; }
.btn-wa { display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--wa); color: #fff; border: 1px solid var(--wa);
  border-radius: var(--r-pill); padding: var(--sp-3) var(--sp-5);
  font-weight: 700; font-size: var(--fs-body); text-decoration: none;
  min-height: var(--tap); }
.qp-msgbox { width: 100%; box-sizing: border-box; font-size: 15px; padding: 13px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2);
  margin: 10px 0 4px; line-height: 1.5; }

/* Reading a payment confirmation — screenshot or pasted text, on every screen
   that records a payment (templates/_payment_fields.html). Two tabs rather
   than two stacked boxes: they are alternatives, and showing both at once
   reads as "do both". */
.pay-read { border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface-2); padding: var(--sp-3); margin-top: var(--sp-3); }
.pay-read-tabs { display: flex; gap: 6px; margin-bottom: 10px; }
.pr-tab { flex: 1 1 0; background: var(--card); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  padding: 9px var(--sp-3); font-weight: 600; font-size: 13px;
  cursor: pointer; min-height: 40px; }
.pr-tab.on { background: var(--signal-l); color: var(--signal);
  border-color: var(--signal); font-weight: 700; }
.pr-pane textarea { width: 100%; box-sizing: border-box; min-height: 84px;
  font-size: 16px; padding: 11px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--card); }
.pr-pane input[type="file"] { width: 100%; font-size: 14px; }
#pr-out:not(:empty) { display: block; margin-top: 9px; padding: 9px 11px;
  background: var(--card); border: 1px solid var(--line); border-radius: 9px;
  line-height: 1.5; }
/* The moment the money moved. Labelled, because "10-Aug-2026" in a bare box
   beside a reference number reads as part of the reference. */
.pr-lbl { display: flex; align-items: center; gap: 8px; flex: 1 1 200px;
  font-size: 13px; color: var(--ink-2); font-weight: 600; }
.pr-lbl input[type="text"] { flex: 1 1 auto; min-width: 110px; }

/* Quick Pay is the one header item that is an ACTION rather than a place. */
.hdr-item.quick { background: var(--gold-l); color: var(--warn); font-weight: 700; }
.hdr-item.quick:hover, .hdr-item.quick.on { background: var(--gold); color: #3a2a00; }

/* "what the client said", beside adding a message — the same act, so the same
   place. It was two folds deep before, which is indistinguishable from absent. */
.said-row { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.said-row input[type="text"] { flex: 1 1 220px; min-width: 180px;
  font-size: 16px; padding: 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-2); min-height: 46px; }
.said-row button { flex: 0 0 auto; }

/* Which build is live, on every screen. Small and quiet -- it is only needed
   the moment a fix looks missing, and at that moment it is the only thing that
   settles the question. */
.hdr-build {
  font-family: var(--fm), monospace; font-size: 10px; color: var(--ink-3);
  align-self: center; padding: 0 6px; letter-spacing: .04em; opacity: .75;
}

/* ------------------------------------------ policy pre-screen modal --- */
/* Playbook Sec.6. A modal rather than a banner: a banner is scrollable past
   on a phone and this is the one message that must not be. No JavaScript --
   it is rendered by the server when the session holds a flag and dismissed
   by an ordinary form post, so a flaky connection cannot trap the operator
   behind an overlay that will not close. */
.pa-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(20, 24, 28, .55);
  display: flex; align-items: center; justify-content: center;
  /* Safe-area insets: every page here sets viewport-fit=cover, so on a
     notched phone the un-inset modal sits under the home indicator and the
     last button is the part that goes under it. */
  padding: calc(var(--sp-4) + env(safe-area-inset-top))
           calc(var(--sp-4) + env(safe-area-inset-right))
           calc(var(--sp-4) + env(safe-area-inset-bottom))
           calc(var(--sp-4) + env(safe-area-inset-left));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
/* dvh, not vh: a mobile URL bar is counted in vh and then slides away, so a
   vh-sized overlay is taller than the screen it is covering. */
@supports (height: 100dvh) { .pa-overlay { max-height: 100dvh; } }
.pa-modal {
  width: 100%; max-width: 460px;
  background: #F4F8FB;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .35);
  margin: auto;
}
.pa-head {
  background: linear-gradient(180deg, #E23B32 0%, #D62F26 100%);
  padding: 26px 0 22px;
  display: flex; justify-content: center;
}
/* The triangle is drawn, not an image file: one less asset to lose. */
.pa-triangle {
  width: 0; height: 0;
  border-left: 44px solid transparent;
  border-right: 44px solid transparent;
  border-bottom: 76px solid #F4F8FB;
  position: relative;
}
.pa-triangle::after {
  content: "!";
  position: absolute; left: -7px; top: 24px;
  font-size: 40px; font-weight: 900; line-height: 1;
  color: #E23B32;
}
.pa-body { padding: var(--sp-4) var(--sp-4) var(--sp-5); text-align: center; }
.pa-title {
  margin: 4px 0 12px;
  font-size: 30px; font-weight: 900; color: #E8443A;
}
.pa-cats { margin: 0 0 12px; }
.pa-cat {
  display: inline-block; margin: 0 4px 6px;
  padding: 5px 12px; border-radius: 999px;
  background: #E23B32; color: #fff;
  font-weight: 700; font-size: var(--fs-sm);
}
.pa-text, .pa-rule, .pa-note {
  margin: 0 0 10px;
  font-size: var(--fs-sm); line-height: 1.55; color: #4A5560;
}
.pa-rule {
  border-top: 1px solid #DCE5EC; padding-top: 10px;
  color: #5A6570;
}
.pa-ask {
  margin: 14px 0 12px;
  font-size: 17px; font-weight: 800; color: #26313B;
}
.pa-buttons {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
}
.pa-buttons form { margin: 0; }
.pa-yes, .pa-no {
  border-radius: 999px; padding: 12px 22px;
  /* 48px, above the 44px this app already holds itself to on every other
     input and button. These two were 40px -- under the minimum, on the one
     choice in the app that should never be mis-tapped. */
  min-height: 48px;
  font-weight: 800; font-size: var(--fs-sm); cursor: pointer;
  border: 2px solid #E23B32;
}
.pa-yes { background: #fff; color: #E23B32; }
.pa-yes:hover { background: #FFF1EF; }
.pa-no  { background: #E23B32; color: #fff; }
.pa-no:hover  { background: #C62A22; }
.pa-note { margin-top: 14px; font-size: 12px; color: #77838E; }

/* On a phone the two answers stop being a pair and become a stack: side by
   side, "No -- not taking this on" wraps to two lines and leaves a lopsided
   row of adjacent targets, which is a mis-tap waiting to happen on a
   consequential choice made with one thumb.
   No is ordered FIRST here (the markup puts Yes first for a mouse and a
   keyboard) so the cautious answer is the one under the thumb and the
   irreversible-feeling one takes a deliberate reach. */
@media (max-width: 460px) {
  .pa-modal { border-radius: 12px; }
  .pa-head { padding: 20px 0 18px; }
  .pa-title { font-size: 26px; }
  .pa-buttons { flex-direction: column-reverse; gap: 8px; }
  .pa-buttons form { width: 100%; }
  .pa-yes, .pa-no { width: 100%; }
}


/* The case-ref rename offer on the job screen. Shown only in response to a
   client edit, never computed on every render -- nagging about a ref the
   operator chose deliberately would be worse than the wrong initials. */
.ref-offer {
  margin-top: 12px;
  padding: 12px;
  border: 1px solid var(--warn);
  border-radius: 8px;
  background: var(--warn-l);
}
.ref-offer-t { font-weight: 700; margin-bottom: 4px; }
.ref-offer-b { margin-bottom: 8px; }
.ref-offer-b code { font-size: 1.02em; }
.ref-offer input[type="text"] { width: 100%; }
.ref-offer-a { margin-top: 8px; }
.ref-offer-a .btn-primary { min-height: 48px; }
/* No dark rule: this stylesheet has a single palette (no prefers-color-scheme
   anywhere), and --warn/--warn-l are the tokens the rest of the app already
   uses for "look at this". Inventing --warn-bg here pointed at nothing, which
   verify_agenda caught. */

/* The blanks panel above a draft. Every [bracket] still in the message, as a
   field, so he can fill them without hunting through twenty lines -- and so a
   forgotten one is countable instead of invisible. */
.blanks { margin: 6px 0 0; }
.blanks:not(.has-blanks) { display: none; }
.blanks-h { font-weight: 700; font-size: 0.92rem; margin-bottom: 6px; }
.blanks-warn { color: var(--warn); }
.blanks-done { color: var(--ok); }
.blanks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px;
  margin-bottom: 8px;
}
.blanks-f { display: flex; flex-direction: column; gap: 2px; font-size: 0.82rem; }
.blanks-f > span { color: var(--muted); text-transform: none; }
.blanks-f input {
  width: 100%;
  min-height: 40px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
}
.blanks-f.blanks-empty input { border-color: var(--warn); background: var(--warn-l); }
@media (max-width: 460px) { .blanks-grid { grid-template-columns: 1fr; } }

/* What's new, running right to left. Dashboard only -- see _ticker.html.
   Pauses on hover and on focus so a link can be clicked rather than chased,
   and stops entirely under prefers-reduced-motion, where it becomes a plain
   scrollable row. */
.ticker {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  padding: 0 12px;
  overflow: hidden;
}
.ticker-tag {
  flex: 0 0 auto;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--muted);
  white-space: nowrap;
}
.ticker-vp { flex: 1 1 auto; overflow: hidden; }
.ticker-run {
  display: flex;
  width: max-content;
  animation: ticker-slide 90s linear infinite;
}
.ticker:hover .ticker-run,
.ticker:focus-within .ticker-run { animation-play-state: paused; }
.ticker-list {
  display: flex;
  align-items: center;
  gap: 26px;
  margin: 0;
  padding: 0 13px;
  list-style: none;
}
.ticker-list li {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 0.83rem;
  line-height: 34px;
}
.ticker-emoji { font-size: 1rem; }
.ticker-list a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--border); }
.ticker-list a:hover, .ticker-list a:focus { color: var(--ok); border-bottom-color: var(--ok); }
.ticker-area {
  font-size: 0.72rem;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 7px;
}
@keyframes ticker-slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .ticker-run { animation: none; }
  .ticker-vp { overflow-x: auto; }
  /* The duplicate run exists only to make the loop seamless; with no loop it
     is just the same news twice. */
  .ticker-list[aria-hidden="true"] { display: none; }
}

/* Deleting an invoice from the history list, and the repeat hint that says
   which ones are worth deleting. */
.pill.repeat {
  background: var(--warn-l);
  color: var(--warn);
  font-size: 0.7rem;
  margin-left: 6px;
}
.row-del { text-align: right; white-space: nowrap; }
.btn-del {
  min-height: 36px;
  padding: 4px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--danger);
  font-size: 0.82rem;
  cursor: pointer;
}
.btn-del:hover, .btn-del:focus { border-color: var(--danger); }

/* A conversation is headed by WHO it is; the number it arrived on stays
   reachable but stops being the headline, and the issue says which of three
   jobs for one client this thread is. */
.sub-num { font-size: 0.76rem; color: var(--muted); font-weight: 400; margin-left: 6px; }
.enq .issue { font-size: 0.78rem; color: var(--muted); margin-top: 2px; }

/* A conversation row is a link WITH actions beside it. The actions are icons:
   a queue of seven rows is scanned, not read, and clearing it should not mean
   opening each one. */
.enq-row { display: flex; align-items: stretch; gap: 4px; }
.enq-row > .enq { flex: 1 1 auto; min-width: 0; }
.enq-acts { display: flex; align-items: center; gap: 2px; padding-right: 6px; }
/* button[type=submit] up at line 322 paints every submit as a full teal pill,
   which turned these two icons into enormous coloured slabs wider than the
   text they sat beside. Matching on button.ico-btn to outrank it, the same way
   the destructive icon button above does -- a class alone loses to a selector
   carrying an attribute. */
button.ico-btn {
  width: 36px;
  min-width: 36px;
  height: 36px;
  min-height: 36px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.45;
}
button.ico-btn:hover, button.ico-btn:focus { opacity: 1; background: var(--surface-2); }
button.ico-btn.danger:hover, button.ico-btn.danger:focus { background: var(--warn-l); }
.st-ico { font-size: 0.95rem; margin-left: 6px; }

/* Case status at a glance. Each state gets its own colour so the queue is
   read by shape rather than by reading every row. */
.pill.st-closed  { background: var(--ok-l); color: var(--ok); }
.pill.st-payment { background: var(--warn-l); color: var(--warn); }
.pill.st-quoted  { background: var(--surface-2); color: var(--muted); }
.pill.st-working { background: var(--surface-2); color: var(--ink); }
.pill.st-open    { background: var(--surface-2); color: var(--muted); }

/* Setting a case status by hand. Narrow, because it sits beside two icon
   buttons on a row that is already dense. */
.st-set { display: inline-flex; }
.pill.st-procuring { background: var(--warn-l); color: var(--warn); }
.pill.st-checking  { background: var(--surface-2); color: var(--ink); }
.pill.st-todeliver { background: var(--ok-l); color: var(--ok); }

/* ---- the queue: tabs, avatars, one status control, line icons ----
   The operator: "icons very weak... this looks totally unprofessional."
   Emoji were the problem -- a washed-out tray glyph sitting next to a real
   control looks like a sticker, and renders differently on every device. */

.q-tabs {
  display: flex;
  gap: 4px;
  margin: 4px 0 10px;
  flex-wrap: wrap;
}
.q-tabs a {
  padding: 7px 13px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  border: 1px solid transparent;
}
.q-tabs a:hover { background: var(--surface-2); }
.q-tabs a.on { background: var(--surface-2); color: var(--ink); border-color: var(--border); }
.q-tabs b { font-weight: 700; opacity: 0.6; margin-left: 3px; }

/* One avatar per client -- the LETTER is who they are, so it is the same on
   every thread they have; the HUE comes from the thread, so two threads with
   one client are still telling apart. Both derived, never random per render:
   an avatar that changes on reload is not worth recognising. */
.enq .avatar {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.9rem;
  background: hsl(var(--h, 210) 62% 93%);
  color: hsl(var(--h, 210) 55% 34%);
}

/* The select IS the status: it shows where the job is and changes it. There
   was a pill AND a dropdown saying the same word next to each other. */
.st-sel {
  appearance: none;
  -webkit-appearance: none;
  min-height: 34px;
  max-width: 150px;
  padding: 5px 26px 5px 11px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.79rem;
  font-weight: 600;
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23555' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 7px center;
  background-size: 13px;
}
.st-sel.st-closed    { background-color: var(--ok-l);      color: var(--ok); }
.st-sel.st-payment   { background-color: var(--warn-l);    color: var(--warn); }
.st-sel.st-procuring { background-color: var(--warn-l);    color: var(--warn); }
.st-sel.st-todeliver { background-color: var(--ok-l);      color: var(--ok); }
.st-sel.st-checking,
.st-sel.st-quoted,
.st-sel.st-working,
.st-sel.st-open      { background-color: var(--surface-2); color: var(--ink); }

.ic { display: block; }
.pill.pill-mode { background: var(--surface-2); color: var(--muted); }

/* Archived, inside the group it belongs to rather than a pile of its own. */
.arch { border-top: 1px solid var(--border); }
.arch > summary {
  cursor: pointer;
  padding: 10px 14px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
}
.arch > summary b { opacity: 0.65; }
.arch .enq-row { opacity: 0.72; }
.arch .enq-row:hover { opacity: 1; }

/* The conversation on the Job screen. Client and us on opposite sides, because
   "what did they last say" is answered by shape before it is read. */
.th-row {
  padding: 8px 11px;
  margin-bottom: 6px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
}
.th-client { border-left: 3px solid var(--ok); }
.th-us { background: var(--surface-2); }
.th-meta {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 0.72rem;
  color: var(--muted);
  margin-bottom: 3px;
}
.th-who { font-weight: 700; color: var(--ink); }
.th-at { margin-left: auto; white-space: nowrap; }
.th-text {
  font-size: 0.86rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* How a thread or a payment request went, kept against the client's number. */
/* THE VERDICT, ON THE CARD. He should not have to open a thread to remember
   why he stopped chasing it: "show that as a status, the final comments and
   the status, not the open one." */
.oc-said {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 3px;
  overflow-wrap: anywhere;
}
.oc-said b { color: var(--ink); font-weight: 700; }
/* An outcome is a finished sentence, so it reads finished -- but "went
   elsewhere" and "declined" are not failures to shout about, so they sit
   quiet rather than red. */
.st-sel.st-oc-paid,
.st-sel.st-oc-resolved      { background-color: var(--ok-l);      color: var(--ok); }
.st-sel.st-oc-follow_up     { background-color: var(--warn-l);    color: var(--warn); }
.st-sel.st-oc-no_response,
.st-sel.st-oc-declined,
.st-sel.st-oc-elsewhere     { background-color: var(--surface-2); color: var(--muted); }

.oc-list { margin: 10px 0 4px; }
.oc-row {
  display: flex;
  gap: 8px;
  align-items: baseline;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.84rem;
}
.oc-label { font-weight: 700; white-space: nowrap; }
.oc-note { color: var(--muted); overflow-wrap: anywhere; }
.oc-at { margin-left: auto; color: var(--muted); font-size: 0.75rem; white-space: nowrap; }
.oc-form { margin-top: 12px; }
.oc-controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.oc-controls select { flex: 0 0 auto; min-height: 44px; }
.oc-controls input[type="text"] { flex: 1 1 220px; min-height: 44px; }
@media (max-width: 520px) {
  .oc-controls select, .oc-controls input[type="text"],
  .oc-controls .btn-primary { flex: 1 1 100%; }
}

/* Something he has sent before, offered as a starting point. */
.prec .prec-q { width: 100%; min-height: 44px; }
.prec-hits { margin-top: 8px; }
.prec-hit {
  padding: 10px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}
.prec-meta { font-size: 0.74rem; color: var(--muted); margin-bottom: 4px; }
.prec-text {
  font-size: 0.83rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 8.5em;
  overflow-y: auto;
  margin-bottom: 8px;
}
.prec-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.prec-acts .btn-secondary { min-height: 40px; }

/* Which message this document needs — offered, with the reason, rather than
   one hardcoded guess. */
.sugg { margin: 10px 0; }
.sugg-why { display: block; font-size: 0.78rem; color: var(--muted); margin-top: 4px; }
.sugg-more { margin-top: 8px; }
.sugg-more > summary { cursor: pointer; font-size: 0.84rem; color: var(--muted); }
.sugg-alt {
  display: block;
  padding: 8px 10px;
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: 8px;
  text-decoration: none;
  color: var(--ink);
}
.sugg-alt:hover { background: var(--surface-2); }
.sugg-t { font-weight: 600; font-size: 0.88rem; }
.sugg-alt .sugg-why { margin-top: 2px; }

/* What the AI says it does not have. Shown ABOVE the draft, because the
   alternative is noticing a dropped sentence after the message has gone. */
.needs {
  margin: 8px 0;
  padding: 10px 12px;
  border: 1px solid var(--warn);
  border-radius: 8px;
  background: var(--warn-l);
}
.needs-h { font-weight: 700; font-size: 0.86rem; color: var(--warn); }
.needs ul { margin: 6px 0 4px; padding-left: 20px; font-size: 0.86rem; }
.needs li { margin-bottom: 3px; }
.needs .hint { margin: 0; }

/* One box: what do you want to do? Sits under the header on every screen. */
.cmd { position: relative; padding: 8px 12px; background: var(--surface); border-bottom: 1px solid var(--border); }
.cmd input[type="search"] {
  width: 100%;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
}
#cmd-hits {
  position: absolute;
  left: 12px;
  right: 12px;
  z-index: 40;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.10);
  max-height: 60vh;
  overflow-y: auto;
}
#cmd-hits:empty { display: none; }
.cmd-hit {
  display: block;
  padding: 9px 12px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
}
.cmd-hit:last-child { border-bottom: 0; }
.cmd-hit.on, .cmd-hit:hover { background: var(--surface-2); }
.cmd-t { display: block; font-weight: 600; font-size: 0.9rem; }
.cmd-s { display: block; font-size: 0.76rem; color: var(--muted); }
/* An action is a verb, not a record -- worth telling apart at a glance. */
.cmd-hit.cmd-action .cmd-t::before { content: "→ "; color: var(--ok); }
.cmd-empty { padding: 10px 12px; font-size: 0.84rem; color: var(--muted); }

/* Why a case is closing, asked at the moment of closing. */
.st-set .why {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 6px;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
}
.st-set .why select, .st-set .why input[type="text"] { min-height: 38px; font-size: 0.8rem; }
.st-set .why input[type="text"] { flex: 1 1 140px; }
.st-set .why .btn-primary { min-height: 38px; }

/* ------------------------------------------------------- talk into a field --
   "in each text box, wherever it is, I don't care, there has to be an audio
   based text option." One button that follows the focused field -- see
   static/voice.js for why it is not welded into all 41 of them. */
.vmic {
  position: absolute;
  z-index: 30;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--muted);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.vmic svg { width: 17px; height: 17px; }
.vmic:hover { color: var(--ink); border-color: var(--muted); }
/* Listening has to be unmistakable from across a room, because the cost of
   thinking it is off when it is on is talking to nobody for a minute. */
.vmic.on {
  background: var(--danger-l);
  border-color: var(--danger);
  color: var(--danger);
  animation: vmic-pulse 1.4s ease-in-out infinite;
}
@keyframes vmic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--danger-l); }
  50%      { box-shadow: 0 0 0 6px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .vmic.on { animation: none; }
}
/* Waiting on the server, on a browser with no engine of its own. The words
   arrive at the END there, not as you speak -- so silence with no explanation
   would read as nothing happening. */
.vmic.busy {
  background: var(--warn-l);
  border-color: var(--warn);
  color: var(--warn);
  animation: none;
}
.vmic-say {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 60;
  max-width: min(92vw, 420px);
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-md);
}

/* ------------------------------------------------------------- express --
   "all can be as simple as single line to that complex shape."

   Everything past the amount is a <details> he opens only when the job has
   it, so the four-second job stays four seconds and nothing sits behind a
   mode chosen before he knows which he needs. */
.ex-form-sec-h,
.sec-h {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  margin: 0 0 10px;
}
.grid2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 10px;
}
.grid2 label, .ex-opts label { display: block; }
.mode-row { display: flex; flex-wrap: wrap; gap: 8px; }
.mode-pick input { position: absolute; opacity: 0; pointer-events: none; }
.mode-pick span {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.mode-pick input:checked + span {
  background: var(--ok-l);
  border-color: var(--ok);
  color: var(--ok);
}
.mode-pick input:focus-visible + span { outline: 2px solid var(--ok); outline-offset: 2px; }

/* One line, or ten. The amount is the widest box because it is the one he
   is actually looking at while a client waits. */
.ex-item {
  display: grid;
  grid-template-columns: 1fr 72px 120px;
  gap: 8px;
  margin-bottom: 8px;
}
.ex-item input { min-height: var(--tap); }
.btn-quiet {
  min-height: var(--tap);
  padding: 8px 14px;
  border: 1px dashed var(--border);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font-weight: 600;
  cursor: pointer;
}
.btn-quiet:hover { color: var(--ink); border-color: var(--muted); }
.ex-opts { margin: 14px 0 4px; display: grid; gap: 8px; }
.ex-opts details {
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  padding: 8px 12px;
}
.ex-opts summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
  min-height: 32px;
  display: flex;
  align-items: center;
}
.ex-opts details[open] summary { margin-bottom: 8px; }
.chk { display: flex; align-items: center; gap: 8px; min-height: var(--tap); }
.chk input { width: 20px; height: 20px; }
.paid-chk { margin-top: 12px; font-weight: 600; }
/* The figure he is about to put in front of a client. It updates as he
   types, because finding out the total was wrong AFTER the PDF is the
   expensive way. */
.ex-total {
  margin-top: 14px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: var(--fs-sm);
}
.ex-total .row { display: flex; justify-content: space-between; gap: 12px; }
.ex-total .row.due { font-weight: 800; font-size: var(--fs-body); color: var(--ink); }
.ex-total .row.off { color: var(--muted); }
.btn-wide { width: 100%; margin-top: 16px; min-height: 52px; font-size: var(--fs-body); }
/* Remove one line. Quiet until you reach for it -- it sits next to the amount,
   and a red X beside a figure reads as an error rather than a control. */
.ex-item { grid-template-columns: 1fr 72px 120px 34px; }
.ex-del {
  min-height: var(--tap);
  width: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.ex-del:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-l); }

/* --------------------------------------------------- the other half of a visit
   A part carried back from a job opens a second case linked to the first. The
   link lived only in the database until now: standing in either case there was
   nothing on screen saying the other existed. */
.lk-card { padding: 0; margin: 16px 0; overflow: hidden; }
.lk-h {
  padding: 10px 14px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.lk-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
  min-height: var(--tap);
}
.lk-row:last-child { border-bottom: 0; }
.lk-row:hover { background: var(--surface-2); }
.lk-b { flex: 1 1 auto; min-width: 0; }
.lk-t { font-weight: 700; font-size: var(--fs-sm); }
.lk-s { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
.lk-ref { flex: 0 0 auto; font-size: 0.74rem; color: var(--muted); }

/* ------------------------------------------------------- the client record
   "how many times a client got refused the work... so that I can decide if to
   entertain it or not." He is reading this to decide whether to quote at all,
   so it sits above everything else and the bad news sorts first. */
.tally { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }
.tally-p {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 600;
}
.tally-p b { font-weight: 800; }
/* Coloured only where it changes a decision. A tally where every pill shouts
   is a tally he stops reading. */
.tp-declined, .tp-elsewhere, .tp-cannot { background: var(--danger-l); color: var(--danger); }
.tp-budget, .tp-no_response, .tp-follow_up { background: var(--warn-l); color: var(--warn); }
.tp-paid, .tp-done, .tp-resolved { background: var(--ok-l); color: var(--ok); }
.money-3 { display: flex; flex-wrap: wrap; gap: 18px; margin: 12px 0; }
.money-3 > div { display: flex; flex-direction: column; gap: 2px; }
.money-3 .m-l { font-size: 0.74rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.money-3 .m-v { font-size: var(--fs-h3); font-weight: 800; color: var(--ink); }
.money-3 .owed .m-v { color: var(--warn); }

/* -------------------------------------------------------------- devices --
   "easy to see how much devices we sold. And what kind of device is this
   sold? that kind of stats." Its own front; the same engine underneath. */
.dev-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}
.dev-row:last-child { border-bottom: 0; }
.dev-b { flex: 1 1 auto; min-width: 0; }
.dev-t { font-weight: 700; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dev-s { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
.dev-w { font-size: 0.78rem; margin-top: 3px; font-weight: 600; }
/* Only the two that change what he does are coloured. "Under warranty until
   next March" is reassurance; "ends in 25 days" is a decision. */
.wr-active  { color: var(--muted); }
.wr-ending  { color: var(--warn); }
.wr-expired { color: var(--danger); }
.wr-unknown { color: var(--muted); }
.dev-a { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.dev-amt { font-weight: 800; }
.dev-a input[type="text"] { min-height: 36px; font-size: 0.8rem; max-width: 170px; }
.tp-dev-sold { background: var(--ok-l); color: var(--ok); }
.tp-dev-sourcing, .tp-dev-checking { background: var(--surface-2); color: var(--ink); }
.tp-dev-ready { background: var(--warn-l); color: var(--warn); }
.tp-dev-returned, .tp-dev-written_off { background: var(--danger-l); color: var(--danger); }
.tp-dev-offered { background: var(--surface-2); color: var(--muted); }
/* The sale, in his order, so the state word on a row means something without
   him holding the sequence in his head. */
.ladder { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 10px 0 14px; }
.lad { font-size: 0.76rem; color: var(--muted); white-space: nowrap; }
.lad.on { color: var(--ok); font-weight: 700; }
.lad-arw { color: var(--border); }
.tp-dev-ordered, .tp-dev-transit { background: var(--surface-2); color: var(--ink); }
/* The record, as written. Preformatted because it is a record, not prose --
   headings and line breaks are how it stays readable six months later. */
.rec {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0 0 10px;
  font-family: inherit;
}
.ph-card { display: flex; gap: 14px; align-items: flex-start; margin-top: 10px; }
.ph-card img {
  width: 120px; height: 120px; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--border); flex: 0 0 auto;
}
.ph-b { flex: 1 1 auto; min-width: 0; }
/* A sale, not a device. The rung it is on and what is owed, because those are
   the two things he wants without opening it. */
.sale-row {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 14px; text-decoration: none; color: var(--ink);
  border-bottom: 1px solid var(--border); min-height: var(--tap);
}
.sale-row:last-child { border-bottom: 0; }
.sale-row:hover { background: var(--surface-2); }
.sale-b { flex: 1 1 auto; min-width: 0; }
.sale-t { font-weight: 700; font-size: var(--fs-sm); }
.sale-who { font-weight: 500; color: var(--muted); }
.sale-s { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
.lad.was { color: var(--ok); }

/* ---- the sales board -------------------------------------------------------
   He opens this to TAKE A DECISION, not to read a list. Five sales rendered as
   five identical cards weigh the same and decide nothing -- that is what made
   every earlier version of this screen unreadable at a glance.

   So the page has exactly one loud thing: what is waiting on HIM. It is set at
   a size nothing else competes with, and it leads with the ACTION rather than
   the client, because the action is what he is deciding about. Everything he
   cannot act on -- a machine with a courier, a client yet to choose -- is true,
   quiet, and one line tall. */
/* THE CONTAINER WAS 640px AND EVERYTHING ELSE WAS FIGHTING IT.
   That is why the sold table would not fit however many columns I removed, and
   why four machine cards stacked one per row on a wide screen: I had built a
   phone-width column and then put desktop tables and a card grid inside it.
   Every dashboard he chose as a reference is drawn on 1400px. This is the fix
   the last four patches were standing in for. */
.sb{max-width:1120px;margin:0 auto;padding:0 24px 64px}
@media (max-width:700px){.sb{padding:0 16px 56px}}
.sb-h{display:flex;align-items:baseline;gap:12px;padding:30px 0 4px}
.sb-h h1{margin:0;font-size:clamp(26px,7vw,34px);font-weight:800;
  letter-spacing:-.04em;line-height:1}
.sb-n{font-size:15px;font-weight:800;color:var(--ink-3);font-variant-numeric:tabular-nums}
.sb-calm{color:var(--ink-3);font-size:15px;margin:8px 0 0}

/* the one thing that needs him */
.act{
  display:block;text-decoration:none;color:inherit;
  background:var(--card);border-radius:22px;padding:22px 24px;margin-top:14px;
  box-shadow:0 1px 2px rgba(11,43,48,.05), 0 10px 30px -14px rgba(11,43,48,.22);
  transition:transform .18s cubic-bezier(.2,.8,.3,1), box-shadow .18s ease;
}
.act:hover{transform:translateY(-2px);
  box-shadow:0 2px 6px rgba(11,43,48,.06), 0 20px 44px -18px rgba(11,43,48,.3)}
.act:focus-visible{outline:2px solid var(--signal);outline-offset:3px}
/* THE VERB LEADS. What he is deciding about is the action, not the client. */
.act-do{font-size:24px;font-weight:800;letter-spacing:-.035em;line-height:1.15;
  color:var(--signal-d)}
.act-who{margin-top:10px;font-size:17px;font-weight:700;letter-spacing:-.02em;line-height:1.25}
.act-what{margin-top:2px;font-size:14px;color:var(--ink-2);line-height:1.45}
.act-what b{color:var(--warn);font-weight:800}
.act-why{margin-top:12px;font-size:13px;color:var(--ink-3);line-height:1.45}

.sb-new{
  display:block;margin-top:18px;padding:15px 20px;border-radius:99px;
  background:var(--signal-l);color:var(--signal-d);
  font-size:15px;font-weight:800;text-decoration:none;text-align:center;
}
.sb-new:hover{background:var(--signal);color:#fff}

/* what he cannot act on: present, quiet, one line */
.sb-sub{display:flex;align-items:baseline;gap:10px;
  margin:38px 0 10px;font-size:11px;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3)}
.sb-all{margin-left:auto;color:var(--ink-3);text-decoration:none;font-weight:800}
.quiet{display:flex;flex-direction:column}
.q-row{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px 14px;
  padding:12px 2px;text-decoration:none;color:inherit;
  border-bottom:1px solid var(--line);
}
.quiet .q-row:last-child{border-bottom:0}
.q-row:hover .q-who{color:var(--signal-d)}
.q-who{font-size:14.5px;font-weight:700;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.q-what{grid-column:1;font-size:12.5px;color:var(--ink-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.q-st{grid-column:2;grid-row:1 / span 2;align-self:center;
  font-size:12px;color:var(--ink-3);white-space:nowrap;font-weight:600}
.q-st.w-active{color:var(--ok)}
.q-st.w-ending{color:var(--warn)}
.q-st.w-expired{color:var(--ink-3);opacity:.65}
@media (prefers-reduced-motion:reduce){.act{transition:none}.act:hover{transform:none}}

/* ============================================================================
   DASHBOARD COMPONENTS
   Built from the references he chose (26 Aug): an inventory ERP, a field-service
   ERP, and an Indian inventory dashboard. What every one of them had in common,
   and what four of my own attempts did not:

     * a KPI card is an ICON, a NUMBER, a DELTA and a SPARKLINE -- not bare type
     * the list is a DENSE TABLE, not tall cards
     * state is a COLOURED PILL, not a grey dot and a word
     * a quantity gets a BAR under it
     * money across states is ONE SEGMENTED BAR with the amounts beneath
     * faces, not initials
     * colour is used, not avoided

   His verdict on the versions that ignored this: "10/100".
   ========================================================================= */

/* ---- KPI card ---- */
.kpis{display:grid;gap:12px;margin:0 0 18px;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,190px),1fr))}
.kpi{background:var(--card);border-radius:16px;padding:15px 16px 12px;
  box-shadow:0 1px 2px rgba(11,43,48,.05),0 6px 18px -12px rgba(11,43,48,.2);
  display:flex;flex-direction:column;gap:2px;min-width:0}
.kpi-t{display:flex;align-items:center;gap:9px}
.kpi-i{width:34px;height:34px;border-radius:11px;flex:0 0 auto;
  display:grid;place-items:center}
.kpi-i svg{width:17px;height:17px}
.kpi-k{font-size:11.5px;font-weight:700;color:var(--ink-3);letter-spacing:.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kpi-v{font-size:25px;font-weight:800;letter-spacing:-.035em;line-height:1.1;
  margin-top:6px;font-variant-numeric:tabular-nums}
.kpi-b{display:flex;align-items:flex-end;justify-content:space-between;gap:8px;
  margin-top:2px}
.kpi-d{font-size:11.5px;font-weight:800;font-variant-numeric:tabular-nums;
  display:inline-flex;align-items:center;gap:2px}
.kpi-d.up{color:var(--ok)}
.kpi-d.down{color:var(--danger)}
.kpi-d.flat{color:var(--ink-3)}
.spark{width:74px;height:26px;flex:0 0 auto;overflow:visible}
.spark path{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.spark .fill{stroke:none;opacity:.13}
/* the four tints -- icon plate, sparkline and delta all take the same hue, so a
   card reads as one object rather than three coloured bits */
.k-blue  .kpi-i{background:var(--info-l);color:var(--info)}
.k-blue  .spark path{stroke:var(--info)} .k-blue .spark .fill{fill:var(--info)}
.k-green .kpi-i{background:var(--ok-l);color:var(--ok)}
.k-green .spark path{stroke:var(--ok)}  .k-green .spark .fill{fill:var(--ok)}
.k-amber .kpi-i{background:var(--warn-l);color:var(--warn)}
.k-amber .spark path{stroke:var(--warn)} .k-amber .spark .fill{fill:var(--warn)}
.k-red   .kpi-i{background:var(--danger-l);color:var(--danger)}
.k-red   .spark path{stroke:var(--danger)} .k-red .spark .fill{fill:var(--danger)}

/* ---- money across states: one bar, amounts beneath ---- */
.mbar{background:var(--card);border-radius:16px;padding:16px;margin-bottom:18px;
  box-shadow:0 1px 2px rgba(11,43,48,.05),0 6px 18px -12px rgba(11,43,48,.2)}
.mbar-h{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.mbar-k{font-size:11.5px;font-weight:700;color:var(--ink-3)}
.mbar-v{font-size:22px;font-weight:800;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.mbar-track{display:flex;height:8px;border-radius:99px;overflow:hidden;
  background:var(--line);margin:12px 0 10px;gap:2px}
.mbar-track i{display:block;height:100%;border-radius:99px}
.mbar-legend{display:grid;gap:10px;
  grid-template-columns:repeat(auto-fit,minmax(min(50%,110px),1fr))}
.mbar-legend div{min-width:0}
.mbar-legend dt{font-size:10.5px;font-weight:700;color:var(--ink-3);
  display:flex;align-items:center;gap:5px}
.mbar-legend dt::before{content:"";width:7px;height:7px;border-radius:99px;
  background:currentColor;flex:0 0 auto}
.mbar-legend dd{margin:2px 0 0;font-size:14.5px;font-weight:800;
  font-variant-numeric:tabular-nums}
.seg-ok{background:var(--ok)}      .lg-ok{color:var(--ok)}
.seg-warn{background:var(--warn)}  .lg-warn{color:var(--warn)}
.seg-bad{background:var(--danger)} .lg-bad{color:var(--danger)}
.seg-info{background:var(--info)}  .lg-info{color:var(--info)}

/* ---- dense table ---- */
.tbl-wrap{background:var(--card);border-radius:16px;overflow:hidden;margin-bottom:18px;
  box-shadow:0 1px 2px rgba(11,43,48,.05),0 6px 18px -12px rgba(11,43,48,.2)}
.tbl-h{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 16px 12px}
.tbl-h h2{margin:0;font-size:15.5px;font-weight:800;letter-spacing:-.02em}
.tbl-scroll{overflow-x:auto}
table.tbl{width:100%;border-collapse:collapse;font-size:13.5px}
table.tbl th{
  text-align:left;font-size:10.5px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3);padding:9px 14px;
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  background:var(--surface-2);white-space:nowrap}
table.tbl td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:middle}
table.tbl tr:last-child td{border-bottom:0}
table.tbl tbody tr:hover{background:var(--surface-2)}
/* the cell contents themselves -- lost once to a careless slice, which is why
   the avatars stopped being circles and the action button fell off the row */
.t-num{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
.t-main{font-weight:700;letter-spacing:-.01em}
.t-sub{font-size:11px;color:var(--ink-3);margin-top:1px;
  font-variant-numeric:tabular-nums;letter-spacing:.02em}
table.tbl td .st + .t-sub{display:inline;margin-left:7px}
.t-who{display:flex;align-items:center;gap:9px;min-width:0}
.t-face{width:28px;height:28px;min-width:28px;border-radius:99px;flex:0 0 auto;
  display:grid;place-items:center;font-size:11px;font-weight:800;color:#fff;
  line-height:1;aspect-ratio:1;
  background:hsl(var(--h,198) 52% 42%)}
.t-act{display:inline-flex;align-items:center;gap:5px;padding:6px 12px;
  border-radius:8px;border:1px solid var(--line);background:var(--card);
  color:var(--ink-2);font-size:12px;font-weight:700;text-decoration:none;
  white-space:nowrap}
.t-act:hover{border-color:var(--signal);color:var(--signal-d);background:var(--signal-l)}
.t-act.go{background:var(--signal);border-color:var(--signal);color:#fff}
.t-act.go:hover{background:var(--signal-d);color:#fff}
/* a quantity with its health under it */
.qty{font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.qbar{height:3px;border-radius:99px;background:var(--line);margin-top:4px;
  overflow:hidden;min-width:64px;display:flex}
.qbar i{display:block;height:100%}

/* NOTHING WRAPS INTO A LADDER. A case ref broken across three lines and a
   model name across two turned the first cut of this table into a column of
   stairs. */
table.tbl td, table.tbl th{white-space:nowrap}
table.tbl .t-main,table.tbl .t-sub{
  display:block;max-width:26ch;overflow:hidden;text-overflow:ellipsis}

/* ---- COLUMNS DROP AND FOLD, THEY DO NOT SHRINK -------------------------
   Seven columns need about 1100px. Every reference he chose fits seven to ten
   comfortably because it is drawn on a 1400px canvas -- I copied the column
   count without copying the canvas, so at 900px it scrolled sideways and cut
   the warranty off at "until 20 Ju...".
   Twice I "fixed" it by removing a column and putting it back. The real rule
   is that a column that will not fit is FOLDED into one that does, and the
   fold happens at a measured width rather than a guessed one.

     >= 1100px   everything
     760-1100    case ref folds under the client, sold date under the machine
     < 760       one block per row, nothing hidden, no sideways scroll
   ---------------------------------------------------------------------- */
.fold{display:none}                      /* the folded copy, shown when needed */

@media (max-width: 1100px){
  table.tbl th.opt, table.tbl td.opt{display:none}
  .fold{display:block}
}

@media (max-width: 760px){
  table.tbl thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  table.tbl,table.tbl tbody,table.tbl tr,table.tbl td{display:block;width:100%}
  table.tbl tr{
    display:grid;
    grid-template-columns:minmax(0,1fr) auto;
    gap:3px 12px;
    padding:14px 15px;
    border-bottom:1px solid var(--line);
  }
  table.tbl tr:last-child{border-bottom:0}
  table.tbl td{padding:0;border:0;white-space:normal;min-width:0}
  table.tbl td:first-child{grid-column:1}
  table.tbl td:last-child{grid-column:2;grid-row:1 / span 3;align-self:start;text-align:right}
  table.tbl td:not(:first-child):not(:last-child){
    grid-column:1;display:inline-flex;flex-wrap:wrap;align-items:center;gap:7px;
    font-size:12.5px}
  table.tbl .t-main,table.tbl .t-sub{display:inline;max-width:none}
  table.tbl .t-num{text-align:left}
  .tbl-scroll{overflow-x:visible}
}

/* ---- pills ---- */
.st{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:99px;
  font-size:11.5px;font-weight:700;white-space:nowrap;max-width:100%;
  overflow:hidden;text-overflow:ellipsis}
.st::before{content:"";width:6px;height:6px;border-radius:99px;background:currentColor}
.st-ok{background:var(--ok-l);color:var(--ok)}
.st-warn{background:var(--warn-l);color:var(--warn)}
.st-bad{background:var(--danger-l);color:var(--danger)}
.st-info{background:var(--info-l);color:var(--info)}
.st-mute{background:var(--surface-2);color:var(--ink-3)}
/* the two halves of Sales */
.stabs{display:flex;gap:6px;margin:0 0 16px}
.stabs a{flex:0 0 auto;padding:8px 16px;border-radius:99px;text-decoration:none;
  background:var(--surface-2);color:var(--ink-2);font-size:13.5px;font-weight:700}
.stabs a.on{background:var(--signal);color:#fff}
.stabs a:hover:not(.on){background:var(--signal-l);color:var(--signal-d)}
.srch{margin:0 0 16px}
.srch input{width:100%;min-height:46px;padding:11px 16px;border-radius:12px;border:0;
  background:var(--card);color:var(--ink);font:inherit;font-size:14.5px;
  box-shadow:0 1px 2px rgba(11,43,48,.05),0 6px 18px -12px rgba(11,43,48,.2)}
.srch input:focus{outline:2px solid var(--signal);outline-offset:1px}
.sb-more{display:block;padding:14px 16px;border-radius:14px;background:var(--surface-2);
  color:var(--ink-2);text-decoration:none;font-size:13.5px;font-weight:700;text-align:center}
.sb-more:hover{background:var(--signal-l);color:var(--signal-d)}

/* ---- one machine, as a card ----------------------------------------------
   He called the previous cut "60% there". The missing 40%, named by looking at
   his reference again instead of guessing at it:

     * a WHITE CIRCULAR ARROW is the action -- every card in that design uses
       one to say "there is more inside"
     * a small WHITE CHIP floats on the tint, the way a rating does
     * the progress bar is HIGH CONTRAST on a pale track, not a tinted sliver
     * the tints are PALE -- pastel, not saturated
     * the card is about HALF the height: picture, name, money, one bar

   The record folds away behind a summary, because five lines of prose on every
   card is what made them tall. */

.case-meta{display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin:2px 0 16px;
  font-size:12.5px;color:var(--ink-3);font-variant-numeric:tabular-nums}
.case-meta span:nth-child(odd){color:var(--ink-2);font-weight:600}
.tally{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:18px}

/* The machine's name is a link now, on every row of the sold list. It has to
   read as one without turning the column into a wall of blue: the colour it
   already had, and the underline only when you are on it. */
.t-open{color:inherit;text-decoration:none;cursor:pointer}
.t-open:hover,.t-open:focus-visible{color:var(--signal);text-decoration:underline;
  text-underline-offset:2px}
.t-open:focus-visible{outline:2px solid var(--signal);outline-offset:2px;
  border-radius:4px}

/* the case ref on a machine card -- quiet, but always there.

   NEVER WRAPPED. On a slider card the column is ~200px and
   "RPIT-DEMO-PC-200726" broke across four lines, which turned a quiet
   reference into the loudest thing on the card. A hyphenated ref has plenty
   of places to break and no good ones. */
.mc-case{margin:3px 0 0;font-size:11px;font-weight:700;letter-spacing:.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.mc-case a{display:block;overflow:hidden;text-overflow:ellipsis}
.mc-case a{color:var(--ink-3);text-decoration:none}
.mc-case a:hover{color:var(--signal);text-decoration:underline;text-underline-offset:2px}
/* a card standing alone is a header, not a grid cell: don't let it stretch */
.mcards-one{grid-template-columns:minmax(0,420px)}

/* "This flag was wrong" -- about the TOOL, not the work, so it sits below the
   decision and reads quieter than it. It is still a real control: a report he
   cannot find is a rate nobody can measure. */
.pa-judge{display:flex;flex-wrap:wrap;gap:8px;align-items:center;
  margin-top:12px;padding-top:12px;
  border-top:1px dashed rgba(11,43,48,.22)}
.pa-judge input[type=text]{flex:1 1 190px;min-width:0;padding:7px 11px;
  border-radius:9px;border:1px solid rgba(11,43,48,.2);
  background:rgba(255,255,255,.65);font:inherit;font-size:12.5px}
.pa-flag{flex:0 0 auto;padding:7px 13px;border-radius:99px;cursor:pointer;
  border:1px solid currentColor;background:transparent;
  font:inherit;font-size:12.5px;font-weight:700;opacity:.8}
.pa-flag:hover{opacity:1;background:rgba(255,255,255,.5)}
.pa-flag:focus-visible{outline:2px solid var(--signal);outline-offset:2px}

/* group headings inside the wording blocks, and the boxes he has changed */
.lbl-group{margin:16px 0 2px;font-size:11px;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3)}
.w-field input.mine{border-color:var(--signal);
  box-shadow:inset 3px 0 0 var(--signal)}
.w-keep{display:flex;align-items:flex-start;gap:10px;margin:16px 0 4px;
  padding:12px 14px;border:1px solid var(--line);border-radius:11px;
  cursor:pointer}
.w-keep input{margin-top:3px;flex:0 0 auto}
.w-keep b{display:block;font-size:13.5px;font-weight:700}
.w-keep i{font-style:normal;font-size:12px;color:var(--ink-3)}
.w-keep:has(input:disabled){opacity:.55;cursor:not-allowed}

/* ============================================================ INCOME PAGE === */
/* Its own screen: what came in, what it cost, what is left -- and every
   invoice behind each number, one tap away. */
.inc-filters{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.inc-filters a{padding:5px 12px;border-radius:99px;text-decoration:none;
  font-size:12.5px;font-weight:700;color:var(--ink-2)}
.inc-filters a:hover{background:var(--surface-2)}
.inc-filters a.on{background:var(--signal-l);color:var(--signal-d)}
.inc-demo{margin-left:6px;display:inline-flex;align-items:center;gap:7px;
  background:var(--surface-2)}
.inc-demo.on{background:var(--gold-l);color:#8A5B00}
.inc-demo i{font-style:normal;font-size:11px;font-weight:800;padding:1px 7px;
  border-radius:99px;background:rgba(11,43,48,.1)}

.kpis{display:grid;gap:12px;margin:18px 0 24px;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,190px),1fr))}
.kpi{padding:16px 18px;border-radius:15px;border:1px solid var(--line);
  background:var(--surface)}
.kpi-k{display:block;font-size:11px;font-weight:800;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-3)}
.kpi-v{display:block;margin:7px 0 3px;font-size:26px;font-weight:800;
  letter-spacing:-.035em;font-variant-numeric:tabular-nums}
.kpi-v.spend{color:#B33A2C}
.kpi-v.earn{color:var(--ok)}
.kpi-v.lose{color:#B33A2C}
.kpi-s{font-size:12px;color:var(--ink-3)}

.pie-wrap{display:grid;gap:22px;margin-bottom:26px;align-items:center;
  grid-template-columns:minmax(190px,240px) 1fr}
.pie-t{margin:0 0 12px;font-size:13px;font-weight:800;letter-spacing:.02em}
.pie{width:100%;max-width:230px;transform:rotate(-90deg)}
.pie .slice{fill:none;stroke-width:6.4}
.pie .s-sales{stroke:var(--signal)}
.pie .s-service{stroke:#5B8DEF}
.pie .s-repair{stroke:var(--warn)}
.pie .s-training{stroke:#9B7BD4}
.pie-n,.pie-c{transform:rotate(90deg);transform-origin:21px 21px;
  text-anchor:middle;fill:var(--ink)}
.pie-n{font-size:4.4px;font-weight:800;letter-spacing:-.1px}
.pie-c{font-size:2.4px;font-weight:700;fill:var(--ink-3);letter-spacing:.3px}

.pie-legend{display:flex;flex-direction:column;gap:8px;min-width:0}
.leg{display:grid;gap:10px;align-items:center;padding:11px 14px;border-radius:12px;
  text-decoration:none;color:var(--ink);border:1px solid var(--line);
  background:var(--surface);
  grid-template-columns:12px 1fr auto auto auto;
  transition:transform .14s ease, box-shadow .14s ease}
.leg:hover{transform:translateY(-1px);box-shadow:0 5px 16px -10px rgba(11,43,48,.5)}
.leg-dot{width:12px;height:12px;border-radius:99px;background:var(--ink-3)}
.v-sales    .leg-dot,.leg.v-sales    .leg-dot{background:var(--signal)}
.v-service  .leg-dot,.leg.v-service  .leg-dot{background:#5B8DEF}
.v-repair   .leg-dot,.leg.v-repair   .leg-dot{background:var(--warn)}
.v-training .leg-dot,.leg.v-training .leg-dot{background:#9B7BD4}
.leg-l{font-size:14px;font-weight:800}
.leg-rev,.leg-exp,.leg-pro{display:flex;flex-direction:column;align-items:flex-end;
  font-variant-numeric:tabular-nums;font-size:13.5px;font-weight:700;min-width:88px}
.leg-exp{color:#B33A2C}
.leg-pro{color:var(--ok);font-weight:800}
.leg i{font-style:normal;font-size:10.5px;font-weight:600;color:var(--ink-3);
  letter-spacing:.02em}
.t-num.earn{color:var(--ok)}
.t-num.lose{color:#B33A2C}
.cost-chip{display:inline-block;margin:1px 4px 1px 0;padding:2px 8px;border-radius:99px;
  background:var(--surface-2);font-size:11px;font-weight:700;white-space:nowrap}
.cost-chip.loss{background:var(--coral-l);color:#B33A2C}
@media (max-width:760px){
  .pie-wrap{grid-template-columns:1fr}
  .pie{max-width:190px;margin:0 auto}
  .leg{grid-template-columns:12px 1fr auto}
  .leg-rev,.leg-exp{display:none}
}

/* ------------------------------------------------------ income by vertical --- */
.inc-strip{display:flex;align-items:baseline;gap:12px;margin:18px 0 4px;
  padding:14px 18px;border-radius:14px;border:1px solid var(--line);
  background:var(--surface);text-decoration:none;color:var(--ink)}
.inc-strip:hover{background:var(--surface-2)}
.inc-go{margin-left:auto;font-size:13px;font-weight:700;color:var(--signal-d)}
.income{margin:22px 0 26px;padding:18px 20px;border-radius:16px;
  border:1px solid var(--line);background:var(--surface)}
.inc-head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:10px;margin-bottom:16px}
.inc-k{display:block;font-size:11px;font-weight:800;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-3)}
.inc-v{font-size:28px;font-weight:800;letter-spacing:-.03em}
.inc-sub{margin-left:9px;font-size:13px;color:var(--ink-3)}
.inc-toggle{display:inline-flex;align-items:center;gap:7px;padding:6px 13px;
  border-radius:99px;text-decoration:none;font-size:12.5px;font-weight:700;
  color:var(--ink-2);background:var(--surface-2);border:1px solid transparent}
.inc-toggle:hover{background:var(--signal-l);color:var(--signal-d)}
.inc-toggle.on{background:var(--gold-l);color:#8A5B00;border-color:#F3DFB4}
.inc-toggle i{font-style:normal;font-size:11px;font-weight:800;padding:1px 7px;
  border-radius:99px;background:rgba(11,43,48,.1)}
.inc-bars{display:flex;flex-direction:column;gap:9px}
.inc-row{display:grid;grid-template-columns:76px 1fr auto 42px;align-items:center;
  gap:11px;font-variant-numeric:tabular-nums}
.inc-l{font-size:13px;font-weight:700}
.inc-bar{height:9px;border-radius:99px;background:var(--surface-2);overflow:hidden}
.inc-bar i{display:block;height:100%;border-radius:99px;background:var(--ink-3)}
.v-sales    .inc-bar i{background:var(--signal)}
.v-service  .inc-bar i{background:#5B8DEF}
.v-repair   .inc-bar i{background:var(--warn)}
.v-training .inc-bar i{background:#9B7BD4}
.inc-n{font-size:13.5px;font-weight:800;letter-spacing:-.02em}
.inc-p{font-size:12px;color:var(--ink-3);text-align:right}
.inc-note{margin:14px 0 0;font-size:12px;color:#8A5B00}
@media (max-width:520px){ .inc-row{grid-template-columns:64px 1fr auto} .inc-p{display:none} }

/* ------------------------------------------------- invoice wording block --- */
/* Closed 95% of the time, and it must look closed -- a quiet line, not a
   section shouting to be filled in. */
.wording{margin:22px 0;border:1px solid var(--line);border-radius:14px;
  background:var(--surface);overflow:hidden}
.wording > summary{list-style:none;cursor:pointer;padding:13px 16px;
  display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px}
.wording > summary::-webkit-details-marker{display:none}
.wording > summary::before{content:"›";font-size:17px;font-weight:800;
  color:var(--ink-3);transition:transform .16s ease;display:inline-block}
.wording[open] > summary::before{transform:rotate(90deg)}
.wording > summary:hover{background:var(--surface-2)}
.w-t{font-weight:800;font-size:14px}
.w-h{font-size:12px;color:var(--ink-3)}
.wording form{padding:4px 16px 18px;border-top:1px solid var(--line)}
.w-grid{display:grid;gap:14px;margin:14px 0 4px;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr))}
.w-field{display:flex;flex-direction:column;gap:5px;min-width:0}
.w-l{font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-2)}
.w-field textarea,.w-field input[type=text]{width:100%;box-sizing:border-box;
  font:inherit;font-size:13.5px;padding:9px 11px;border-radius:9px;
  border:1px solid var(--line);background:var(--surface);color:var(--ink);
  resize:vertical}
.w-field textarea:focus,.w-field input:focus{outline:2px solid var(--signal);
  outline-offset:1px;border-color:transparent}
.w-help{font-size:11.5px;color:var(--ink-3);line-height:1.35}
.w-scope{margin:16px 0 10px;padding:12px 14px;border:1px solid var(--line);
  border-radius:11px}
.w-scope legend{font-size:11px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-2);padding:0 6px}
.w-scope label{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  padding:6px 0;cursor:pointer}
.w-scope label.off{opacity:.5;cursor:not-allowed}
.w-scope b{font-size:13.5px;font-weight:700}
.w-scope i{font-style:normal;font-size:12px;color:var(--ink-3)}
.w-note{margin:10px 0 14px;font-size:12px;color:var(--ink-3);line-height:1.45}

/* ------------------------------------------- the whole card is the link --- */
/* "click on the card will also take me to the individual device card view as
   the arrow (diagonal) one doing."

   Stretched link, not a wrapper: the card holds forms and a photo viewer, and
   <a> may not contain a <button>. The overlay covers the card at z-index 1;
   every real control is lifted above it, so it catches the gaps and nothing
   else. */
/* The card-wide click target. A child of .mcard, so inset:0 covers the CARD --
   the previous version put this on .mc-go, which is itself absolutely
   positioned, so it covered the arrow and nothing else. */
.mc-cover{position:absolute;inset:0;z-index:1;border-radius:22px;
  display:block;text-indent:-9999px;overflow:hidden}
.mcard:has(.mc-cover){cursor:pointer}
/* Everything that must stay pressable, above the overlay.

   Z-INDEX ONLY -- NEVER `position`. The first version of this set
   position:relative on the lot, and .mc-chip and .mc-go are ABSOLUTE: the
   floating "Covered" pill dropped out of its corner into the layout and sat
   across the model name. z-index works on an absolutely positioned element
   already, so there was nothing to gain and a card to break. */
.mcard .mc-strip,
.mcard .mc-f,
.mcard .mc-case a{position:relative;z-index:2}
/* these are positioned already -- lift them without touching how they sit */
.mcard .mc-pic,
.mcard .mc-shot,
.mcard .mc-go,
.mcard .mc-chip{z-index:2}
/* The arrow keeps its own hover, and the card keeps the lift it already had. */
.mcard:has(.mc-cover:hover) .mc-go{background:var(--signal);color:#fff}

/* ------------------------------------------------ machine card slider --- */
/* "make a slider so that we can click left right icon to navigate to all
   device card, dont show all at once."

   A scroller with buttons, not a carousel: the track is an ordinary
   overflow-x element, so it swipes on a phone and works with the JS absent. */
.mslide{position:relative;min-width:0}
/* .mslide > .ms-track, NOT .ms-track. The element carries BOTH classes --
   `class="mcards ms-track"` -- and `.mcards` sets display:grid further down
   this file. Two single-class selectors tie on specificity, so source order
   decided it and the grid won: the cards wrapped into rows instead of running
   along a track, which is exactly what a slider must not do. Out-specifying it
   means the rule holds wherever either block ends up. */
.mslide > .ms-track{
  display:flex;                      /* a TRACK here, not the usual grid */
  gap:14px;
  overflow-x:auto;
  scroll-snap-type:x mandatory;
  scroll-padding-left:2px;
  padding:2px 2px 10px;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;              /* the buttons and the fade say "more" */
}
.mslide > .ms-track::-webkit-scrollbar{display:none}
.mslide > .ms-track > .mcard{
  scroll-snap-align:start;
  flex:0 0 clamp(270px, 31%, 360px); /* two or three across, never a wall */
  min-width:0;
}
/* One machine has nothing to slide: let it sit at a readable width instead of
   stretching across the whole row. */
.mslide > .ms-track > .mcard:only-child{flex-basis:min(100%, 400px)}

.ms-btn{
  position:absolute;top:calc(50% - 22px);z-index:3;
  width:38px;height:38px;border-radius:99px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  border:1px solid rgba(11,43,48,.12);background:var(--surface);color:var(--ink);
  box-shadow:0 4px 16px -6px rgba(11,43,48,.45);
  transition:transform .15s ease, box-shadow .15s ease}
.ms-btn svg{width:19px;height:19px}
.ms-btn:hover{transform:scale(1.07);box-shadow:0 6px 20px -6px rgba(11,43,48,.55)}
.ms-btn:focus-visible{outline:2px solid var(--signal);outline-offset:2px}
.ms-btn[hidden]{display:none}
.ms-prev{left:-6px}
.ms-next{right:-6px}
/* A soft edge on the side that continues -- the honest signal that the row is
   not finished, and the one that still reads with the buttons hidden. */
.mslide::after,.mslide::before{content:"";position:absolute;top:0;bottom:10px;
  width:46px;pointer-events:none;opacity:0;transition:opacity .18s ease}
.mslide::after{right:0;background:linear-gradient(to left,var(--bg),transparent)}
.mslide::before{left:0;background:linear-gradient(to right,var(--bg),transparent)}
.mslide.ms-has-more::after{opacity:1}
.mslide.ms-has-prev::before{opacity:1}
@media (max-width:600px){
  .mslide > .ms-track > .mcard{flex-basis:min(86%, 330px)}
  .ms-prev{left:-2px} .ms-next{right:-2px}
}
@media (prefers-reduced-motion:reduce){
  .mslide > .ms-track{scroll-behavior:auto}
  .ms-btn{transition:none} .ms-btn:hover{transform:none}
}

/* ------------------------------------------- sold: sort + view toolbar --- */
/* "sorting missed, by number, name, recent, old" -- and cards or the table.
   One strip, wrapping on a phone, never a horizontal scroll. */
.sbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;
  margin:0 0 16px;justify-content:space-between}
.sbar-sort{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0}
.sbar-l{font-size:11px;font-weight:800;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-3);margin-right:2px}
.sbar-sort a{padding:5px 11px;border-radius:99px;text-decoration:none;
  font-size:12.5px;font-weight:700;color:var(--ink-2);white-space:nowrap;
  border:1px solid transparent}
.sbar-sort a:hover{background:var(--surface-2)}
.sbar-sort a.on{background:var(--signal-l);color:var(--signal-d);
  border-color:rgba(18,165,184,.35)}
.sbar-sort a:focus-visible,.sbar-view a:focus-visible{
  outline:2px solid var(--signal);outline-offset:2px}
.sbar-view{display:flex;gap:4px;flex:0 0 auto;padding:3px;border-radius:99px;
  background:var(--surface-2)}
.sbar-view a{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;
  border-radius:99px;text-decoration:none;font-size:12.5px;font-weight:700;
  color:var(--ink-2)}
.sbar-view a svg{width:15px;height:15px;flex:0 0 auto}
.sbar-view a.on{background:var(--surface);color:var(--signal-d);
  box-shadow:0 1px 3px rgba(11,43,48,.14)}
@media (max-width:560px){ .sbar-view a span{display:none} }

/* ------------------------------------------------- sold: grouped cards --- */
/* Grouping still means something in card view: each case (or client) keeps a
   heading, and its machines sit under it as the SAME cards the case page, the
   device page and the client page draw. */
.gcards{display:flex;flex-direction:column;gap:26px;margin-bottom:8px}
.gcard{min-width:0}
.gc-h{display:flex;align-items:center;gap:11px;margin:0 0 12px;min-width:0}
.gc-who{min-width:0;flex:1 1 auto}
.gc-nm{display:block;font-size:15px;font-weight:800;letter-spacing:-.01em;
  color:var(--ink);text-decoration:none}
a.gc-nm:hover{color:var(--signal);text-decoration:underline;text-underline-offset:2px}
.gc-sub{display:block;font-size:11.5px;font-weight:600;color:var(--ink-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gc-val{flex:0 0 auto;display:flex;align-items:center;gap:9px;
  font-variant-numeric:tabular-nums}
.gc-val b{font-size:14.5px;font-weight:800;letter-spacing:-.02em}
.tbl-h-loose{padding:0 0 12px;border:0;background:transparent}
@media (max-width:520px){
  .gc-h{flex-wrap:wrap} .gc-val{width:100%;justify-content:flex-start}
}

/* THE TALLY PILLS ARE THE FILTER. A pill that does something has to look like
   it does something -- same shape and same colour as the read-only .st it was
   a moment ago, plus the two things a control needs: it moves under the
   pointer, and the one that is ON is unmistakably on. */
.st-f{text-decoration:none;cursor:pointer;padding:5px 12px;
  border:1px solid transparent;
  transition:transform .14s ease, box-shadow .14s ease, border-color .14s ease}
.st-f:hover{transform:translateY(-1px);border-color:currentColor;
  box-shadow:0 3px 10px -6px rgba(11,43,48,.5)}
.st-f:focus-visible{outline:2px solid var(--signal);outline-offset:2px}
/* ON: the colour it was hinting at, now filled in -- no second vocabulary to
   learn, and it stays legible against its own tint. */
.st-f.on{border-color:currentColor;
  box-shadow:inset 0 0 0 999px rgba(255,255,255,.55), 0 2px 8px -5px rgba(11,43,48,.6)}
.st-f.on::after{content:"\00d7";margin-left:5px;font-size:13px;line-height:1;
  font-weight:800;opacity:.55}
@media (prefers-reduced-motion:reduce){
  .st-f{transition:none} .st-f:hover{transform:none} }

.mcards{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}

.mcard{
  position:relative;
  border-radius:22px;
  padding:16px;
  display:flex;flex-direction:column;min-width:0;
  --sheet-a:#F2F5F5;
  --sheet-b:#E7ECEC;
  /* A GRADIENT, NOT A FLAT WASH. Two stops a few percent apart on the same
     hue: enough that the card has a direction and a light source, not so much
     that it announces itself. A flat tint at this size reads as a coloured
     rectangle; this reads as a surface. */
  background:linear-gradient(155deg, var(--sheet-a) 0%, var(--sheet-b) 100%);
  box-shadow:0 1px 2px rgba(11,43,48,.05), 0 4px 14px -10px rgba(11,43,48,.28);
  transition:transform .22s cubic-bezier(.2,.8,.3,1),
             box-shadow .22s cubic-bezier(.2,.8,.3,1);
}
/* IT LIFTS. A card that rises off the page on hover says it can be opened --
   the affordance every reference he chose uses instead of an outline. */
.mcard:hover{
  transform:translateY(-3px);
  box-shadow:0 2px 5px rgba(11,43,48,.06), 0 18px 38px -16px rgba(11,43,48,.42);
}
.mcard:focus-within{
  transform:translateY(-3px);
  box-shadow:0 2px 5px rgba(11,43,48,.06), 0 18px 38px -16px rgba(11,43,48,.42);
}
@media (prefers-reduced-motion:reduce){
  .mcard{transition:none} .mcard:hover,.mcard:focus-within{transform:none}
}

/* the state, felt rather than announced -- light at the top, settling at the
   bottom, so a column of cards has a rhythm */
.m-active {--sheet-a:#EAF6F0; --sheet-b:#D9EDE3}
.m-ending {--sheet-a:#FDF3E4; --sheet-b:#F8E6CC}
.m-expired,.m-unknown{--sheet-a:#F1F3F4; --sheet-b:#E6EAEB}

/* the floating chip */
.mc-chip{
  position:absolute;top:14px;right:14px;z-index:1;
  background:var(--card);color:var(--ink);
  font-size:10.5px;font-weight:800;letter-spacing:.02em;
  padding:4px 10px;border-radius:99px;white-space:nowrap;
  box-shadow:0 1px 3px rgba(11,43,48,.1);
}
.m-ending .mc-chip{color:var(--warn)}
.m-expired .mc-chip,.m-unknown .mc-chip{color:var(--ink-3)}
.m-active .mc-chip{color:var(--ok)}

.mc-top{display:flex;gap:13px;align-items:center;padding-right:64px}
.mc-img{flex:0 0 auto}
.mc-shot{
  position:relative;display:block;width:84px;height:64px;padding:0;border:0;
  border-radius:14px;overflow:hidden;background:#fff;cursor:pointer;
}
/* CONTAIN, NOT COVER.
   A laptop photograph is wide; forcing it into a square with `cover` crops the
   machine out of its own picture -- his screenshot showed a sliver of a lid and
   nothing else. `contain` fits the whole device in frame, on white, which is
   how a product shot is meant to sit. */
.mc-shot img{width:100%;height:100%;object-fit:contain;display:block;
  background:#fff;padding:5px;
  transition:transform .25s cubic-bezier(.2,.8,.3,1)}
.mc-shot:hover img{transform:scale(1.06)}
.mc-shot:focus-visible{outline:2px solid var(--signal);outline-offset:2px}
.mc-empty{display:grid;place-items:center;color:var(--ink-3);cursor:default}
.mc-empty svg{width:26px;height:26px}
/* how many shots there are, on the picture itself */
.mc-n{position:absolute;right:4px;bottom:4px;background:rgba(11,43,48,.78);color:#fff;
  font-style:normal;font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:99px}
.mc-pic-note{margin:0;font-size:10.5px;line-height:1.45;color:var(--ink-3)}

.mc-id{flex:1 1 auto;min-width:0}
.mc-id h2{margin:0;font-size:15.5px;font-weight:800;letter-spacing:-.03em;line-height:1.2;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mc-sn{margin:2px 0 0;font-size:10px;color:var(--ink-3);letter-spacing:.1em;
  text-transform:uppercase;font-variant-numeric:tabular-nums}
.mc-miss{color:var(--warn);text-transform:none;letter-spacing:0;font-weight:700}
.mc-amt{margin:5px 0 0;font-size:17px;font-weight:800;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;white-space:nowrap}

/* the white circular arrow */
.mc-go{
  position:absolute;right:14px;bottom:14px;width:38px;height:38px;
  display:grid;place-items:center;border-radius:99px;
  background:var(--card);color:var(--ink);text-decoration:none;
  box-shadow:0 1px 3px rgba(11,43,48,.12);
  transition:transform .16s cubic-bezier(.2,.8,.3,1)
}
.mc-go svg{width:16px;height:16px}
.mc-go:hover{transform:translate(1px,-1px)}
.mc-go:focus-visible{outline:2px solid var(--signal);outline-offset:2px}

.mc-spec{margin:10px 0 0;font-size:11.5px;color:var(--ink-2);line-height:1.4;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---- time left, as a ring ----
   The meter he pointed at: an arc with the figure inside it and the sentence
   beside it. A bar says "some"; a ring says how much of the promise is left
   before a word has been read. */
.mc-w{margin-top:16px;display:flex;align-items:center;gap:15px}
.meter{
  /* declared here, not only inline: the sweep's "no CSS variable resolves to
     nothing" check is right to want it, and a ring with no percentage should
     still draw rather than vanish. */
  --pct:0;
  position:relative;width:62px;height:62px;flex:0 0 auto}
.meter svg{width:62px;height:62px;transform:rotate(-90deg)}
.meter circle{fill:none;stroke-width:7;stroke-linecap:round}
.mt-track{stroke:rgba(11,43,48,.09)}
/* THE COLOUR FOLLOWS THE NUMBER: green at 100, red at 0.
   Three fixed colours by state made a ring at 96% and one at 46% look
   identical, which wastes the one thing a ring is for. The hue is computed
   from the percentage itself -- 120deg (green) down to 0deg (red) -- so the
   colour and the arc say the same thing, and a machine sliding towards its
   last month is visibly warming up long before it crosses a threshold. */
.mt-fill{stroke:hsl(calc(var(--pct, 0) * 1.2) 68% 40%)}
.m-expired .mt-fill,.m-unknown .mt-fill{stroke:transparent}
/* finished, not broken: the track completes the circle and the figure recedes */
.m-expired .mt-track{stroke:rgba(11,43,48,.16)}
.m-expired .mt-n{color:var(--ink-3);opacity:.75}
.mt-n{color:hsl(calc(var(--pct, 0) * 1.2) 60% 30%)}
/* THE NUMBER AND ITS UNIT ARE ONE THING.
   This was display:grid with place-items:center, so the text node and the <i>
   each became their own grid ROW and the % sat underneath the figure like a
   fraction. A flex row on the baseline reads as "10%". */
.mt-n{position:absolute;inset:0;display:flex;align-items:baseline;
  justify-content:center;gap:1px;
  font-size:18px;font-weight:800;letter-spacing:-.045em;line-height:1;
  font-variant-numeric:tabular-nums;color:var(--ink)}
.mt-n i{font-style:normal;font-size:9.5px;font-weight:700;opacity:.7;
  letter-spacing:0}
/* the whole label sits on the ring's optical centre, not its box centre --
   baseline alignment pulls the pair up by a couple of pixels */
.meter{display:grid;place-items:center}
.meter svg{grid-area:1/1}
.mt-n{grid-area:1/1;position:static;inset:auto}
.m-expired .mt-n{color:var(--ink-3)}
.mc-wtext{min-width:0}
.mc-big{margin:0;font-size:15px;font-weight:800;letter-spacing:-.025em;line-height:1.2}
.m-ending .mc-big{color:var(--warn)}
.m-expired .mc-big,.m-unknown .mc-big{color:var(--ink-3)}
.mc-until{margin:2px 0 0;font-size:11px;color:var(--ink-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

.mc-strip{display:flex;gap:6px;flex-wrap:wrap;margin-top:12px}
.mc-th{width:52px;height:40px;padding:0;border:0;border-radius:10px;overflow:hidden;
  background:#fff;cursor:pointer;flex:0 0 auto}
.mc-th img{width:100%;height:100%;object-fit:contain;display:block;
  background:#fff;padding:3px}
.mc-th:hover{outline:2px solid var(--signal);outline-offset:1px}
.mc-th:focus-visible{outline:2px solid var(--signal);outline-offset:2px}

/* ---- the facts, ON the card ----
   "Why do I need to click to read the details" -- he should not. They were
   folded behind a summary to keep the card short, which traded the one thing
   the card is FOR against its height. As labelled pairs they cost four lines. */
.mc-facts{display:grid;gap:6px 14px;margin:13px 0 0;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,130px),1fr))}
.mc-facts div{min-width:0}
.mc-facts dt{font-size:9.5px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-3)}
.mc-facts dd{margin:1px 0 0;font-size:12px;font-weight:600;line-height:1.4;
  color:var(--ink);overflow-wrap:anywhere}

/* ---- change the picture, where the picture is ---- */
.mc-pic{position:relative;margin-top:6px}
.mc-pic summary{
  list-style:none;cursor:pointer;width:62px;height:26px;
  display:grid;place-items:center;border-radius:99px;
  background:var(--card);color:var(--ink-2);
  border:1px solid rgba(11,43,48,.14);
}
.mc-pic summary::-webkit-details-marker{display:none}
.mc-pic summary svg{width:13px;height:13px}
.mc-pic summary:hover{color:var(--signal-d);border-color:var(--signal)}
.mc-pic-menu{
  position:absolute;z-index:5;top:32px;left:0;width:246px;
  background:var(--card);border-radius:16px;padding:13px;
  box-shadow:0 4px 10px rgba(11,43,48,.1),0 18px 40px -14px rgba(11,43,48,.4);
  display:flex;flex-direction:column;gap:10px;
}
.mc-pic-up{display:block;cursor:pointer}
.mc-pic-up input{position:absolute;width:1px;height:1px;opacity:0}
.mc-pic-up span{display:block;text-align:center;padding:10px;border-radius:11px;
  background:var(--signal);color:#fff;font-size:12.5px;font-weight:800}
.mc-pic-up:hover span{background:var(--signal-d)}
.mc-pic-h{margin:0;font-size:9.5px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-3)}
.mc-pic-pick{display:flex;flex-wrap:wrap;gap:6px}
.mc-pic-pick button{width:56px;height:44px;padding:0;border:2px solid transparent;
  border-radius:10px;overflow:hidden;cursor:pointer;background:#fff}
.mc-pic-pick img{width:100%;height:100%;object-fit:contain;display:block;padding:3px}
.mc-pic-pick button:hover{border-color:var(--signal)}
.mc-pic-pick button.on{border-color:var(--signal)}
.mc-pic-link{border:0;background:none;padding:4px 0;cursor:pointer;
  font:inherit;font-size:12px;font-weight:700;color:var(--signal-d);text-align:left}
.mc-pic-link:hover{text-decoration:underline}

.mc-f{display:flex;align-items:flex-start;gap:9px;margin-top:14px;
  padding-right:52px;flex-wrap:wrap}
.mc-f form{margin-left:auto}
/* QUIET IS NOT INVISIBLE.
   "Chargeable case" was white on a pale grey card with faded text -- it read as
   a disabled control rather than a secondary one, on the row where he is about
   to charge for a job. A secondary button gets full-strength text and a real
   edge; only its FILL is quiet. */
.mcard .t-act{
  background:var(--card);
  border:1px solid rgba(11,43,48,.16);
  color:var(--ink);
  font-weight:700;
}
.mcard .t-act:hover{border-color:var(--signal);color:var(--signal-d);
  background:var(--signal-l)}
.mcard .t-act.go{background:var(--signal);border-color:var(--signal);color:#fff}
.mcard .t-act.go:hover{background:var(--signal-d);color:#fff}
@media (prefers-reduced-motion:reduce){.mc-shot img,.mc-go{transition:none}}

/* ---- the photo viewer ---- */
.lb{border:0;padding:0;background:transparent;max-width:100vw;max-height:100vh;
  width:100%;height:100%;overflow:hidden}
.lb::backdrop{background:rgba(4,20,23,.9)}
.lb{display:none}
.lb[open]{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px}
.lb-fig{margin:0;display:flex;flex-direction:column;align-items:center;gap:14px;
  min-width:0;max-height:100vh;padding:20px 0}
.lb-fig img{max-width:100%;max-height:74vh;object-fit:contain;border-radius:14px;
  background:#0b2b30}
.lb-fig figcaption{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:10px;max-width:62ch;padding:0 16px;text-align:center}
.lb-count{font-size:12px;font-weight:800;color:rgba(255,255,255,.65);
  font-variant-numeric:tabular-nums}
.lb-stage{font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:#0b2b30;background:rgba(255,255,255,.85);padding:3px 9px;border-radius:99px}
.lb-cap{font-size:13px;line-height:1.55;color:rgba(255,255,255,.9);flex:1 1 100%}
.lb-nav,.lb-x{border:0;cursor:pointer;background:rgba(255,255,255,.12);color:#fff;
  border-radius:99px;display:grid;place-items:center}
.lb-nav{width:48px;height:48px;font-size:30px;line-height:1;margin:0 10px}
.lb-nav:hover,.lb-x:hover{background:rgba(255,255,255,.24)}
.lb-x{position:fixed;top:16px;right:16px;width:42px;height:42px;font-size:26px;z-index:2}
@media (max-width:640px){
  .lb-nav{width:40px;height:40px;font-size:24px;margin:0 4px}
  .lb-fig img{max-height:64vh}
}
@media (prefers-reduced-motion:reduce){.mc-shot img{transition:none}}
/* the standard picture for a kind of thing -- drawn, never fetched */
.mc-kind{display:grid;place-items:center;cursor:default;background:var(--card)}
.kart{width:64px;height:64px;display:block;color:var(--ink-2)}
.kart-bg{fill:var(--card)}
.k-ram .kart-bg,.k-ssd .kart-bg,.k-hdd .kart-bg{fill:var(--card)}

/* ---------------------------------------------------------------------------
   MONEY. Every number on this screen opens the rows it was added up from --
   "these 4 doesn't click and reflect": a figure you cannot open is a figure
   you have to take on trust, and he has no reason to.
   --------------------------------------------------------------------------- */
.kpis a.kpi { text-decoration: none; color: inherit; transition:
  transform .14s ease, box-shadow .14s ease, border-color .14s ease;
  border: 1px solid transparent; }
.kpis a.kpi:hover { transform: translateY(-2px);
  box-shadow: 0 2px 4px rgba(11,43,48,.06), 0 14px 28px -16px rgba(11,43,48,.34); }
.kpis a.kpi.on { border-color: var(--signal); background: var(--signal-l); }
.kpi-k { display: flex; align-items: center; gap: 6px; }
.kpi-k .i { width: 14px; height: 14px; flex: 0 0 14px; opacity: .75; }
.kpi-v.warn { color: var(--warn); }
.kpi-s { font-size: 11.5px; color: var(--ink-3); font-weight: 600; }

.inc-two { display: grid; gap: 18px; grid-template-columns: 1fr;
  align-items: start; margin: 0 0 22px; }
@media (min-width: 1080px) { .inc-two { grid-template-columns: 1.15fr 1fr; } }

/* WHERE THE MONEY WENT. One "what it cost" number answers none of
   "how much I paid to vendor, expenses breakdown, and what left after that". */
.spend-wrap { background: var(--card); border-radius: 16px; padding: 16px 18px 12px;
  box-shadow: 0 1px 2px rgba(11,43,48,.05), 0 6px 18px -12px rgba(11,43,48,.2); }
.spend-list { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; }
.spend-row { display: grid; grid-template-columns: minmax(0,1.15fr) minmax(60px,1fr) auto;
  align-items: center; gap: 12px; padding: 9px 10px; border-radius: 11px;
  text-decoration: none; color: var(--ink); }
a.spend-row:hover { background: var(--surface-2); }
a.spend-row.on { background: var(--signal-l); }
.spend-l { display: flex; align-items: center; gap: 8px; font-size: 13.5px;
  font-weight: 650; min-width: 0; flex-wrap: wrap; }
.spend-l .i { width: 16px; height: 16px; flex: 0 0 16px; color: var(--ink-3); }
.spend-l i { flex-basis: 100%; padding-left: 24px; font-style: normal;
  font-size: 11px; font-weight: 600; color: var(--ink-3); }
.spend-bar { height: 8px; border-radius: 99px; background: var(--surface-2); overflow: hidden; }
.spend-bar i { display: block; height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--warn), #e0654a); }
.spend-n { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--warn); white-space: nowrap; }
.spend-n.earn { color: var(--ok); }
/* WHAT HE STILL OWES. Pinned above the numbers, because every other figure
   on this screen is money that has already moved and this one has not. Gone
   entirely when nothing is open -- a strip that is always there stops being
   read, and he asked for it to disappear once everything is settled. */
.owe { border: 1px solid var(--warn); border-radius: 15px; padding: 13px 15px 9px;
  margin: 0 0 18px; background: var(--warn-l, #fdf4ec); }
.owe-h { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px; }
.owe-t { display: flex; align-items: center; gap: 7px; font-size: 11px;
  font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-3); }
.owe-t .i { width: 15px; height: 15px; flex: 0 0 15px; color: var(--warn); }
.owe-v { font-size: 21px; font-weight: 800; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; color: var(--warn); }
.owe-s { font-size: 11.5px; font-weight: 600; color: var(--ink-3);
  margin-left: auto; }
.owe-list { display: flex; flex-direction: column; }
.owe-row { display: grid; grid-template-columns: minmax(0,1fr) auto auto;
  align-items: center; gap: 10px; padding: 7px 0;
  border-top: 1px solid rgba(0,0,0,.06); }
.owe-tick { display: flex; align-items: center; gap: 9px; min-width: 0;
  cursor: pointer; }
.owe-tick input { width: 17px; height: 17px; flex: 0 0 17px; cursor: pointer;
  accent-color: var(--ok); }
.owe-l { font-size: 13.5px; font-weight: 700; min-width: 0; }
.owe-l i { display: block; font-style: normal; font-size: 11px; font-weight: 600;
  color: var(--ink-3); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.owe-go { font-size: 11.5px; font-weight: 700; color: var(--ink-3);
  text-decoration: none; white-space: nowrap; }
.owe-go:hover { color: var(--signal-d); }
.owe-n { font-size: 14.5px; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--warn); white-space: nowrap; }
.owe-row.is-done .owe-l, .owe-row.is-done .owe-n { color: var(--ink-3);
  font-weight: 700; }
.owe-done { margin-top: 6px; }
.owe-done > summary { cursor: pointer; font-size: 11.5px; font-weight: 700;
  color: var(--ink-3); padding: 5px 0; }
.owe-clear { display: flex; align-items: center; gap: 7px; margin: 0 0 16px;
  font-size: 12.5px; font-weight: 650; color: var(--ink-3); }
.owe-clear .i { width: 15px; height: 15px; flex: 0 0 15px; color: var(--ok); }

/* ONE PAGE AT A TIME, and a plain link so it works with no script. */
.more-row { display: flex; align-items: center; gap: 14px; padding: 14px 4px 6px; }
.more-btn { font-size: 13px; font-weight: 700; text-decoration: none;
  padding: 8px 16px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--surface); color: var(--signal-d); }
.more-btn:hover { border-color: var(--signal); background: var(--signal-l); }
.more-n { font-size: 11.5px; font-weight: 650; color: var(--ink-3); }
.more-all { font-size: 11.5px; font-weight: 700; margin-left: auto; }

/* The share, quietly, under what the job cost. */
.share-sub { color: var(--ink-3); font-weight: 650; opacity: .8; }

/* THIS ONE IS NOT COMING. Quiet until it matters -- only an unpaid balance
   or an existing write-off draws it at all. */
.woff { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 10px; padding: 9px 12px; border-radius: 11px;
  border: 1px dashed var(--line); background: var(--surface-2); }
.woff-l { font-size: 12.5px; font-weight: 700; margin-right: auto; }
.woff-l i { display: block; font-style: normal; font-size: 11px; font-weight: 600;
  color: var(--ink-3); }
.woff.is-off { border-style: solid; border-color: var(--line); opacity: .8; }
.woff.is-off .woff-l { color: var(--ink-3); }

/* WHICH HEADING A LINE COUNTS UNDER. Internal -- it never prints, and it is
   the one thing on this table he can change. */
.lt-h { white-space: nowrap; }
.lt-c { width: 1%; white-space: nowrap; }
.line-tag select { font-size: 12px; padding: 3px 6px; }

/* WHAT HE NEVER GOT BACK. Shut, and at the foot -- "don't keep it in front
   always". A thing he reviews, not a thing he acts on. */
.lost-sec { margin: 22px 0 8px; border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); }
.lost-sec > summary { display: flex; align-items: center; gap: 9px; cursor: pointer;
  padding: 13px 16px; font-size: 13px; font-weight: 700; list-style: none; }
.lost-sec > summary::-webkit-details-marker { display: none; }
.lost-sec > summary .i { width: 16px; height: 16px; flex: 0 0 16px; color: var(--ink-3); }
.lost-sec > summary b { color: #B33A2C; font-variant-numeric: tabular-nums; }
.lost-sec > summary i { font-style: normal; font-size: 11.5px; font-weight: 600;
  color: var(--ink-3); margin-left: auto; text-align: right; }
.lost-sec[open] > summary { border-bottom: 1px solid var(--line); }
.lost-range { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 16px; font-size: 12px; font-weight: 650; color: var(--ink-3); }
.lost-range label { display: flex; align-items: center; gap: 6px; }
.lost-range a { font-weight: 700; }
.lost-t { margin: 0; }
.lost-why { display: block; font-style: normal; font-size: 11px; color: var(--ink-3); }

/* WHAT STILL LEAVES ONE JOB. Same rows as the strip at the top, inside the
   job they belong to -- so "left" is followed by what he actually keeps. */
.outg { margin-top: 4px; }
.outg-row { display: grid; grid-template-columns: minmax(0,1fr) auto;
  align-items: center; gap: 10px; padding: 6px 0;
  border-top: 1px solid var(--line); }
.outg-row.is-done .owe-l, .outg-row.is-done .owe-n { color: var(--ink-3); }
.outg-row.is-waiting { opacity: .62; padding-left: 26px; }
.outg-tot { border-top: 1px solid var(--ink-3); }
.outg-tot .owe-l { font-weight: 800; }
.outg-tot .owe-n { font-size: 15px; }
.outg .owe-n.earn { color: var(--ok); }
.outg .owe-n.lose { color: #B33A2C; }

/* HIS OWN WORDS FOR WHAT HE SPENT -- the list, editable. */
.kinds-list { display: flex; flex-direction: column; gap: 0; margin: 10px 0 0; }
.kind-row { display: grid; grid-template-columns: minmax(0,1fr) auto auto;
  gap: 10px; align-items: center; padding: 9px 0;
  border-top: 1px solid var(--line); }
.kind-row input[type=text] { width: 100%; }
.kind-use { font-size: 11.5px; font-weight: 650; color: var(--ink-3);
  white-space: nowrap; }
.kind-row.is-off { opacity: .55; }
.cost-other { display: none; }
.cost-add.other-on .cost-other { display: inline-block; }

/* THE ROW HE JUST COSTED, held in a list it no longer belongs to. */
tr.openable.just { background: var(--signal-l); }
.just-t { display: block; color: var(--signal-d); font-weight: 700; }

.spend-share { border-top: 1px dashed var(--line); margin-top: 6px;
  padding-top: 12px; }
.spend-share .spend-l .i { color: var(--warn); }
.spend-share .spend-bar i { background: linear-gradient(90deg, #c9a227, #e0b23a); }
.spend-tot { border-top: 1px solid var(--line); margin-top: 2px; padding-top: 12px; }
.spend-tot .spend-l { font-weight: 800; }
.spend-tot .spend-n { font-size: 17px; }

.leg-l { display: flex; align-items: center; gap: 7px; }
.leg-l .i { width: 15px; height: 15px; flex: 0 0 15px; opacity: .8; }
.t-main .i { width: 14px; height: 14px; margin-right: 5px; vertical-align: -2px;
  opacity: .55; }

/* SORTABLE COLUMNS. "these columns doesn't allow me to click to sort." */
th.sortable { padding: 0; }
th.sortable > a { display: flex; align-items: center; gap: 4px; padding: 9px 12px;
  text-decoration: none; color: inherit; font: inherit; white-space: nowrap; }
th.sortable.t-num > a { justify-content: flex-end; }
th.sortable > a:hover { color: var(--signal-d); background: var(--surface-2); }
th.sorted > a { color: var(--signal-d); }
.caret { font-style: normal; font-size: 10px; width: 8px; }
.inc-filters.csort { margin-left: auto; }
.tbl tr.on > td { background: var(--signal-l); }

/* ---------------------------------------------------------------------------
   MONEY, part two: the ladder, the tabs, the range picker.

   THE GLOBAL SIZE RULE COMES FIRST AND IS NOT OPTIONAL. Every .i had its size
   set by whatever container it sat in, so the first one placed somewhere new
   -- the calendar in the range picker -- rendered at its natural size and
   filled the screen. An icon set without a default size is a trap laid for
   the next person to use it.
   --------------------------------------------------------------------------- */
svg.i { width: 16px; height: 16px; flex: 0 0 16px; }

/* WHICH PAGE OF THE BOOK */
.mtabs { display: flex; gap: 4px; margin: 4px 0 20px; overflow-x: auto;
  scrollbar-width: none; }
.mtabs::-webkit-scrollbar { display: none; }
.mtabs a { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  padding: 9px 14px; border-radius: 11px; text-decoration: none;
  color: var(--ink-2); font-size: 13.5px; font-weight: 650;
  border: 1px solid var(--line); background: var(--card); }
.mtabs a:hover { color: var(--signal-d); border-color: var(--signal); }
.mtabs a.on { background: var(--signal-l); border-color: var(--signal);
  color: var(--signal-d); font-weight: 750; }

/* A CUSTOM RANGE */
.range { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 10px 14px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--card); }
.range .i { color: var(--ink-3); }
.range label { font-size: 12px; font-weight: 700; color: var(--ink-3); }
/* A global `input { width:100% }` lives further up for form screens, and it
   turned two date fields into two full-width blocks stacked down the page.
   The picker is a strip, not a form. */
.range input[type="date"] { font: inherit; font-size: 13px; padding: 6px 9px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--surface);
  color: var(--ink); width: auto; min-width: 0; flex: 0 0 auto; margin: 0; }
.range button { font: inherit; font-size: 13px; font-weight: 700; padding: 7px 15px;
  border: 0; border-radius: 9px; background: var(--signal); color: #fff;
  cursor: pointer; }
.range button:hover { background: var(--signal-d); }
.range-clear { font-size: 12.5px; font-weight: 650; color: var(--ink-3);
  text-decoration: none; }

/* THE LADDER. Deductions are indented and red; the rungs you land on are the
   running totals, and the last one is what is actually his. */
.pnl-wrap { background: var(--card); border-radius: 16px; padding: 16px 18px 14px;
  box-shadow: 0 1px 2px rgba(11,43,48,.05), 0 6px 18px -12px rgba(11,43,48,.2); }
/* NOT .ladder -- that class already exists further up for the case-stage
   strip, with flex-wrap:wrap and align-items:center. Reusing the name meant
   inheriting both, so every row shrank to its text and sat centred in the
   card. A colliding class name is not a specificity problem to win; it is the
   wrong name. */
.pnl { display: flex; flex-direction: column; align-items: stretch;
  flex-wrap: nowrap; margin-top: 10px; }
.pnl-row { display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; padding: 9px 10px; border-radius: 10px; text-decoration: none;
  color: var(--ink); }
.pnl-row:hover { background: var(--surface-2); }
.pnl-row.on { background: var(--signal-l); }
.pnl-l { display: flex; flex-direction: column; gap: 1px; min-width: 0;
  font-size: 13.5px; font-weight: 650; }
.pnl-l i { font-style: normal; font-size: 11px; font-weight: 600;
  color: var(--ink-3); }
.pnl-n { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.pnl-out { padding-left: 24px; }
.pnl-out .pnl-l { font-weight: 600; color: var(--ink-2); }
.pnl-out .pnl-n { color: var(--warn); font-size: 14px; }
.pnl-sub { border-top: 1px solid var(--line); margin-top: 3px; padding-top: 11px; }
.pnl-sub .pnl-n { color: var(--ink); }
.pnl-start .pnl-n { font-size: 17px; }
.pnl-end { border-top: 2px solid var(--ink); margin-top: 5px; padding-top: 13px; }
.pnl-end .pnl-l { font-weight: 850; font-size: 15px; }
.pnl-end .pnl-n { font-size: 24px; letter-spacing: -.03em; color: var(--ok); }
.pnl-margin { font-size: 12.5px; color: var(--ink-3); margin: 10px 2px 0;
  font-weight: 600; }
.pnl-margin b { color: var(--ink); }

/* WHAT THE LADDER CANNOT KNOW */
.memo-wrap { display: flex; flex-direction: column; gap: 10px; }
.memo { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px;
  border-radius: 13px; border: 1px solid var(--line); background: var(--card);
  text-decoration: none; color: var(--ink); }
a.memo:hover { border-color: var(--signal); background: var(--signal-l); }
.memo .i { margin-top: 2px; color: var(--ink-3); }
.memo-l { display: flex; flex-direction: column; gap: 3px; font-size: 13.5px;
  font-weight: 700; }
.memo-l i { font-style: normal; font-size: 11.5px; font-weight: 600;
  color: var(--ink-3); line-height: 1.45; }
.memo-warn { border-color: var(--warn); background: var(--gold-l); }
.memo-warn .i { color: var(--warn); }
.memo-quiet { opacity: .72; }

/* NOT MEASURED IS NOT PROFITABLE. Never green, never a number. */
.unknown { color: var(--ink-3); font-weight: 650; font-size: 12.5px;
  font-variant-numeric: normal; }
.t-sub.warn { color: var(--warn); }

/* THE ROW OPENS WHERE IT SITS */
tr.det-row > td { padding: 0; background: var(--surface); }
.det { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 10px; }
.det-h { font-size: 11.5px; font-weight: 750; color: var(--ink-3);
  letter-spacing: .02em; }
.det-lines { display: flex; flex-direction: column; gap: 1px; }
.det-line { display: grid; grid-template-columns: 1fr auto auto;
  gap: 12px; align-items: baseline; padding: 6px 8px; border-radius: 8px;
  font-size: 12.5px; }
.det-line:nth-child(odd) { background: var(--card); }
.det-w { min-width: 0; font-weight: 600; }
.det-t { font-size: 10.5px; font-weight: 700; padding: 2px 7px; border-radius: 99px;
  background: var(--surface-2); color: var(--ink-3); white-space: nowrap; }
.det-a { font-weight: 750; font-variant-numeric: tabular-nums; white-space: nowrap; }
.det-note { font-size: 12px; color: var(--ink-3); line-height: 1.5; }
.det-cost { display: flex; flex-wrap: wrap; gap: 6px; }
tr.openable { cursor: pointer; }
tr.openable:hover > td { background: var(--surface-2); }
.det-caret { display: inline-block; width: 10px; font-size: 10px;
  color: var(--ink-3); transition: transform .15s ease; }
tr.open .det-caret { transform: rotate(90deg); }

/* THE LADDER AS A WATERFALL. Nine figures in a column is dead on the page --
   "this is dead". Each rung's bar is its share of the bill, so the shape of
   the business reads before a single number does: goods eat 59% of everything
   billed, and what is left is the thin green bar at the bottom. */
.pnl-row { display: grid; grid-template-columns: minmax(0,1.25fr) minmax(70px,1fr) auto;
  align-items: center; gap: 14px; }
.pnl-bar { height: 9px; border-radius: 99px; background: var(--surface-2);
  overflow: hidden; }
.pnl-bar i { display: block; height: 100%; border-radius: 99px;
  background: var(--ink-3); }
.pnl-out .pnl-bar i { background: linear-gradient(90deg, var(--warn), #e0654a); }
.pnl-sub .pnl-bar i { background: var(--signal); }
.pnl-start .pnl-bar i { background: var(--ink); }
.pnl-end .pnl-bar i { background: linear-gradient(90deg, var(--ok), #1f9d6b); }
.pnl-n { text-align: right; }
.pnl-n em { display: block; font-style: normal; font-size: 10.5px;
  font-weight: 700; color: var(--ink-3); }
/* THE SHARE. Not a link and not hoverable -- it has no rows behind it to
   open, and a card that lifts under the cursor and then does nothing is a lie
   about what is clickable. */
.kpi-share { border-style: dashed; background: var(--surface); }
.kpi-share .kpi-k .i { opacity: 1; color: var(--warn); }
.kpi-hero { border-color: var(--ok) !important; background: var(--ok-l, #eefaf4); }
.kpi-hero .kpi-v { font-size: 28px; }
@media (max-width: 620px) {
  .pnl-row { grid-template-columns: minmax(0,1fr) auto; }
  .pnl-bar { grid-column: 1 / -1; }
}

/* ENTER THE COST WHERE YOU SEE IT. A strip, not a form page -- the whole point
   is that adding a 500 porter charge should cost fewer keystrokes than
   remembering to come back and do it later. */
.cost-add { display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.cost-add select, .cost-add input[type="text"], .cost-add input[type="date"] {
  font: inherit; font-size: 12.5px; padding: 6px 9px; width: auto; margin: 0;
  border: 1px solid var(--line); border-radius: 8px; background: var(--card);
  color: var(--ink); min-width: 0; }
.cost-add button { font: inherit; font-size: 12.5px; font-weight: 700;
  padding: 7px 14px; border: 0; border-radius: 8px; cursor: pointer;
  background: var(--signal); color: #fff; }
.cost-add button:hover { background: var(--signal-d); }
.cost-add button.ghost { background: var(--surface-2); color: var(--ink-2); }
.cost-add button.ghost:hover { background: var(--line); color: var(--ink); }
.cost-loss { display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 650; color: var(--ink-3); }
.cost-loss input { margin: 0; width: auto; }

/* A CARD THAT FOLDS. Closed it is one line carrying the answer; open it is the
   working behind it. The marker is replaced because the default triangle sits
   at the start of the line and drags the heading off the left edge. */
.fold-card { padding: 0; }
.fold-card > .fold-h { list-style: none; cursor: pointer;
  display: grid; grid-template-columns: minmax(0,1fr) auto auto; gap: 14px;
  align-items: center; padding: 15px 18px; border-radius: 16px; }
.fold-card > .fold-h::-webkit-details-marker { display: none; }
.fold-card > .fold-h:hover { background: var(--surface-2); }
.fold-t { font-family: var(--fd), var(--fb), sans-serif; font-weight: 800;
  font-size: 14.5px; letter-spacing: -.01em; color: var(--ink); }
.fold-s { display: inline-flex; align-items: center; gap: 7px;
  font-size: 16px; font-weight: 800; color: var(--ok);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.fold-s em { font-style: normal; font-size: 11px; font-weight: 650;
  color: var(--ink-3); }
.fold-c { font-style: normal; font-size: 15px; color: var(--ink-3);
  transition: transform .15s ease; }
.fold-card[open] > .fold-h > .fold-c { transform: rotate(90deg); }
.fold-card[open] > .fold-h { border-bottom: 1px solid var(--line);
  border-radius: 16px 16px 0 0; }
.fold-card > .pnl { padding: 6px 18px 4px; margin-top: 4px; }
.fold-card > .pnl-margin { padding: 0 18px 14px; margin-top: 6px; }
@media (max-width: 620px) {
  .fold-card > .fold-h { grid-template-columns: minmax(0,1fr) auto; }
  .fold-s { grid-column: 1 / -1; }
}

/* THE SLICE IS THE TARGET. It looked pressable and was not -- only the legend
   worked. Thickening on hover is the whole affordance a donut can carry. */
.pie a { cursor: pointer; }
.pie a .slice { transition: stroke-width .14s ease, opacity .14s ease; }
.pie a:hover .slice, .pie a:focus-visible .slice { stroke-width: 5.2; }
.pie a:focus-visible { outline: none; }
.pie a:focus-visible .slice { opacity: .82; }
.pie .slice.on { stroke-width: 5.2; }

/* A PRESSED CARD LOOKS PRESSED, and pressing it again puts the view back. */
.memo.on { border-color: var(--signal); background: var(--signal-l); }
.memo.memo-warn.on { border-color: var(--warn); background: var(--gold); }

/* THE DASHBOARD STRIP. Three things he can act on, each a link into the
   filtered list. Turnover was not one of them. */
.inc-strip { display: flex; align-items: stretch; flex-wrap: wrap; gap: 2px;
  margin: 18px 0 4px; padding: 6px; border-radius: 14px;
  border: 1px solid var(--line); background: var(--surface); }
.inc-bit { display: flex; flex-direction: column; gap: 1px; padding: 9px 16px;
  border-radius: 10px; text-decoration: none; color: var(--ink); }
.inc-bit:hover { background: var(--card); }
.inc-bit .inc-k { font-size: 10.5px; font-weight: 750; color: var(--ink-3);
  letter-spacing: .02em; text-transform: uppercase; }
.inc-bit .inc-v { font-size: 19px; font-weight: 800; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; }
.inc-bit.warn .inc-v { color: var(--warn); }
.inc-go { margin-left: auto; align-self: center; padding: 0 12px;
  font-size: 13px; font-weight: 700; color: var(--signal-d);
  text-decoration: none; }

/* SEVERAL COSTS, NOT ONE. A job is routinely parts AND travel AND the
   engineer, each paid on a different day to a different person, so each is its
   own line -- and each can be taken out again when it went in wrong. */
.costs-h { display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; }
.costs-t { font-size: 13px; font-weight: 800; color: var(--warn);
  font-variant-numeric: tabular-nums; }
.cost-chip form { display: inline; }
.chip-x { border: 0; background: none; cursor: pointer; padding: 0 0 0 5px;
  font: inherit; font-size: 13px; line-height: 1; color: var(--ink-3); }
.chip-x:hover { color: var(--warn); }


/* ---------------------------------------------------------------------------
   ON A PHONE. "Check mobile friendliness everywhere -- easy to read, easy to
   write."                                                            28 Aug

   The two things that actually bite standing outside a client's office:

   1. WRITING. iOS zooms the whole page in whenever a field with text under
      16px takes focus, and it does not zoom back out. Entering a porter charge
      then left him scrolled sideways on a page he had to pinch to recover. So
      every control is 16px on a phone -- it costs nothing, because they are
      one per row down here anyway.

   2. READING. 11px sub-lines are the app's quiet detail: the vendor, the date,
      the margin, what a job counts as. On a desktop they are a whisper; on a
      phone at arm's length in daylight they are unreadable. They go up.
   --------------------------------------------------------------------------- */
@media (max-width: 640px) {
  input, select, textarea, button { font-size: 16px; }
  input[type="date"] { min-width: 0; }
  .t-sub, .kpi-s, .memo-l i, .owe-l i, .spend-l i, .det-note, .hint,
  .lost-range, .more-n, .kind-use, .woff-l i { font-size: 12.5px; }
  /* One thing per row: a cost form is five controls, and five across is five
     unusable ones. */
  .cost-add, .lost-range, .woff { flex-direction: column; align-items: stretch; }
  .cost-add > *, .lost-range > *, .woff > * { width: 100%; }
  .cost-add button, .woff button { min-height: 44px; }
  .cost-loss { flex-direction: row; align-items: center; gap: 8px; }
  /* Every tap target big enough to hit while walking. */
  .owe-tick input { width: 22px; height: 22px; flex: 0 0 22px; }
  .chip-x { min-width: 34px; min-height: 34px; }
  .line-tag select { width: 100%; min-height: 40px; }
  /* Nothing may push the page sideways -- tables scroll inside themselves. */
  .tbl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  body { overflow-x: hidden; }
}

/* The fallback button under "Use my own photo" -- present so the upload never
   depends on a script having run. */
.mc-pic-go { margin-top: 6px; width: 100%; min-height: 40px; }


/* ---------------------------------------------------------------------------
   THE PICTURE MENU OPENS INSIDE THE CARD, NOT OVER IT.

   "It's still unclickable." It was drawn and dead, and the reason is the track
   it lives in: the machine cards run along `.ms-track`, which is
   `overflow-x: auto` -- and a box that clips one axis clips the other too. An
   absolutely positioned panel inside it is painted where it overflows but is
   NOT hit-testable there, so every button outside the card's own box took no
   clicks at all.

   A popover cannot work inside a scrolling track. So it does not float: the
   <details> simply expands the card and pushes the rest down. Nothing to clip,
   nothing to sit on top of it, and it behaves the same on a phone.
   --------------------------------------------------------------------------- */
.mc-pic-menu {
  /* IT FLOATS AGAIN -- because clipping was never what broke it.
     The buttons were dead because `.mc-cover`, an <a> stretched over the whole
     card, sat on top of them. Putting the menu in flow made it clickable but
     squeezed the card: it lives in the narrow image column, so opening it cut
     the model name down to "Acer ...". Now it is absolute again, INSIDE the
     card's own bounds so the sliding track has nothing to clip, and it is
     stacked above the cover, which is the thing that actually had to change. */
  position: absolute;
  top: 32px; left: 0;
  width: 246px;
  max-width: 76vw;
  box-sizing: border-box;
  margin-top: 0;
}
.mc-pic-menu > * { max-width: 100%; box-sizing: border-box; }
.mc-pic-up span, .mc-pic-go { width: 100%; box-sizing: border-box; }
/* The card grows with it rather than holding a fixed height. */
.mcard:has(.mc-pic[open]) { height: auto; }

/* The device tick, beside the heading -- two questions, two controls. */
.lt-dev { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-size: 12px; font-weight: 700; color: var(--ink-3); }
.lt-dev input { width: 17px; height: 17px; accent-color: var(--signal-d); }

.mc-inv { margin-left: 8px; font-weight: 700; }
.mc-pic-go[hidden] { display: none; }

/* The file input, plainly visible. No label disguise: it kept posting an
   empty form, and a control that cannot be trusted to open is worse than a
   plain-looking one that always does. */
.mc-pic-file { display: block; width: 100%; box-sizing: border-box;
  font-size: 12.5px; padding: 8px; border: 1px dashed var(--line);
  border-radius: 10px; background: var(--surface); cursor: pointer; }
.mc-pic-file::file-selector-button { font: inherit; cursor: pointer;
  margin-right: 10px; padding: 6px 12px; border-radius: 8px;
  border: 1px solid var(--signal); background: var(--signal-l);
  color: var(--signal-d); font-weight: 700; }

/* CHOOSE A PICTURE. Every candidate, big enough to tell apart. */
.pick-grid { display: grid; gap: 14px; margin-top: 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr)); }
.pick-shot { display: block; width: 100%; padding: 10px; cursor: pointer;
  background: var(--card); border: 2px solid var(--line); border-radius: 14px; }
.pick-shot:hover { border-color: var(--signal); }
.pick-shot img { display: block; width: 100%; height: 150px; object-fit: contain;
  background: #fff; border-radius: 9px; }
.pick-shot span { display: block; margin-top: 8px; font-size: 11px;
  font-weight: 650; color: var(--ink-3); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* the "27 not recorded" line was wrapping and breaking the card */
.kpi-d { white-space: nowrap; }


/* ---------------------------------------------------------------------------
   THE WHOLE-CARD LINK MUST NOT EAT THE PICTURE MENU.

   Clicking the pencil navigated to the device page instead of opening the
   menu: `.mc-cover` is an <a> stretched over the entire card, and every click
   in the menu's area went to it. That is why "none of those buttons work" --
   they were never receiving the click at all.

   The menu and everything in it sits above the cover, decisively.
   --------------------------------------------------------------------------- */
.mcard .mc-pic { z-index: 25; }
.mcard .mc-pic[open] { z-index: 40; }
/* Lift them WITHOUT re-positioning the menu -- this rule used to include
   `.mc-pic-menu` in the `position: relative` list, which quietly cancelled the
   absolute above it and put the menu back in the image column, squeezing the
   model name down to "Acer ...". */
.mcard .mc-pic-menu { z-index: 41; }
.mcard .mc-pic summary,
.mcard .mc-pic-menu > * { position: relative; z-index: 41; }
/* An open menu also switches the cover off, so a stray click beside a button
   cannot navigate away mid-upload. */
.mcard:has(.mc-pic[open]) .mc-cover { pointer-events: none; }

/* Where the warranty is set. Small, in the card, beside what it says. */
.mc-wset { display: flex; align-items: center; gap: 6px; margin-top: 6px;
  position: relative; z-index: 3; }
.mc-wset input { width: 62px; padding: 4px 6px; font-size: 12px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.mc-wset span { font-size: 11px; font-weight: 650; color: var(--ink-3); }
.mc-wset button { font-size: 11px; font-weight: 700; padding: 4px 10px;
  border-radius: 8px; border: 1px solid var(--line); background: var(--surface);
  color: var(--signal-d); cursor: pointer; }
.mc-wset button:hover { border-color: var(--signal); background: var(--signal-l); }

/* The candidate tiles are <button>s, and the app's default button styling was
   turning them into teal circles with the picture spilling out. */
.pick-grid button.pick-shot { border-radius: 14px; background: var(--card);
  min-height: 0; height: auto; box-shadow: none; text-align: left; }
.pick-grid button.pick-shot:hover { background: var(--surface-2); }
.pick-warn { border-color: var(--warn); }

/* Paste or drag a picture -- for a browser that will not open a file dialog. */
.paste-zone { display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 6px; min-height: 150px; padding: 18px;
  border: 2px dashed var(--line); border-radius: 14px; background: var(--surface);
  cursor: pointer; text-align: center; }
.paste-zone:focus { outline: none; border-color: var(--signal);
  background: var(--signal-l); }
.paste-zone.over { border-color: var(--signal); background: var(--signal-l); }
.paste-zone.has { border-style: solid; border-color: var(--ok); }
.paste-zone b { font-size: 14px; }
.paste-zone span { font-size: 12px; color: var(--ink-3); }
.paste-zone img { max-width: 100%; max-height: 220px; border-radius: 10px;
  margin-top: 10px; background: #fff; }

/* A machine card taking a dropped or pasted picture. */
.mcard.drop-over { outline: 3px dashed var(--signal); outline-offset: 3px; }
.mcard.drop-busy { opacity: .55; }

/* This browser cannot open a file window (navigator.webdriver). Say so where
   the button is, and let the ways that work lead. */
.no-file-dialog .mc-pic-file,
.no-file-dialog .photo-file-form { opacity: .45; }
.no-file-dialog .mc-pic-file { pointer-events: none; }
.no-file-dialog .no-dialog-note { display: block; }
.no-dialog-note { display: none; margin: 6px 0 0; padding: 8px 10px;
  border-radius: 9px; background: var(--warn-l, #fdf4ec);
  border: 1px solid var(--warn); font-size: 12px; font-weight: 650;
  color: var(--ink-2); }
.no-file-dialog .paste-first { order: -1; }

/* The missing serial is a thing to fix, so it is a link, not just red text. */
.mc-addser { text-decoration: none; }
.mc-addser:hover { text-decoration: underline; }
.mcard .mc-addser { position: relative; z-index: 3; }

/* Pick a picture, or take it off. */
.pic-one { position: relative; display: inline-block; }
.pic-x { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px;
  min-height: 0; padding: 0; border-radius: 50%; line-height: 1;
  border: 1px solid var(--line); background: var(--card); color: var(--warn);
  font-size: 14px; font-weight: 800; cursor: pointer; z-index: 2; }
.pic-x:hover { background: var(--warn); color: #fff; border-color: var(--warn); }
.ph-rm { float: right; }
.ph-rm .pic-x { position: static; width: auto; height: auto; padding: 4px 10px;
  border-radius: 999px; font-size: 12px; }

/* Cancel an estimate that came to nothing -- kept, but counting nowhere. */
.cancel-form { display: inline-flex; align-items: center; gap: 6px; }
.cancel-form input[type=text] { padding: 6px 9px; font-size: 12.5px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.cancelled-note { color: var(--warn); font-weight: 650; }
.pill.cancelled { background: var(--warn-l, #fdf4ec); color: var(--warn); }

/* "Never recovered" is a fact about one payment, changed on the payment. */
.chip-loss { display: inline; }
.chip-loss button { border: 0; background: none; padding: 0 0 0 2px; cursor: pointer;
  font: inherit; color: var(--ink-3); text-decoration: underline dotted; }
.cost-chip.loss .chip-loss button { color: inherit; text-decoration: none;
  font-weight: 700; cursor: pointer; }
.chip-loss button:hover { color: var(--warn); }

/* Search everything on the Money screen. */
.money-find { display: flex; gap: 8px; align-items: center; margin: 0 0 14px; }
.money-find input[type=search] { flex: 1 1 auto; min-width: 0; padding: 11px 14px;
  font-size: 14px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); }
.money-find input[type=search]:focus { outline: none; border-color: var(--signal);
  background: var(--surface); }
.money-find button { flex: 0 0 auto; padding: 11px 20px; border-radius: 12px;
  border: 1px solid var(--signal); background: var(--signal-l);
  color: var(--signal-d); font-weight: 700; cursor: pointer; }
.money-find-x { flex: 0 0 auto; font-size: 12.5px; font-weight: 700; }

.money-find-more { flex: 0 0 auto; font-size: 12.5px; font-weight: 700;
  color: var(--warn); white-space: nowrap; }

/* A change that saved without reloading the page says so, briefly. */
.saving { opacity: .5; }
.saved { outline: 2px solid var(--ok); outline-offset: 2px; border-radius: 8px; }
