/* TFS WORKSHOP MANAGER — the standalone instance theme.
 *
 * Loaded ONLY when PILOT_MODE=off. The live pilot service never receives this
 * file, which is what lets the byte harness go on proving that nothing an
 * existing user is served has changed. Every rule here is additive: it overrides
 * style.css rather than editing it, so the two instances share one stylesheet
 * and differ by one <link>.
 *
 * WHAT THIS IS FOR. A DVSA examiner or a fleet manager forms an opinion of a
 * compliance system in about two seconds. Calm, dense, document-like, and
 * obviously built by someone who cares — no gradients for their own sake, no
 * animation that does not carry information.
 *
 * A NOTE ON THE TYPEFACE. The brief asks for Inter and Inter Display. Webfont
 * binaries are NOT shipped here: an extra ~180KB blocking render on a workshop's
 * phone signal costs more than the typeface earns, and a CDN would put a
 * third-party request in front of a compliance record. Inter is used when the
 * device has it; otherwise the stack falls to the system UI faces, which are
 * near-identical in feel at these sizes. Say the word and the binaries go in.
 */

/* ── INTER, SELF-HOSTED ────────────────────────────────────────────────────
 * 6.1 — the board's second law is "Type — Inter, set with intent", and until
 * the font files arrived the stylesheet only ASKED for Inter: a machine with it
 * installed got it and every other machine — which is most of them, including
 * the workshop's phones — got its system font. A typeface you request and do
 * not ship is a preference, not a decision.
 *
 * SELF-HOSTED, from public/fonts. No Google Fonts link, no CDN: this app has no
 * external dependency at runtime and is not acquiring one for a font. It also
 * means the type works in a workshop with no internet, which is the same
 * argument as the paper fallback sheet.
 *
 * THE WEIGHTS ARE THE BOARD'S: 400 body, 600 the heavier body and labels, 800
 * display. Nothing between, and nothing synthesised — a browser asked for 700
 * when only 400 and 800 exist will FAKE it by smearing the 400, which is the
 * blurry heading nobody can quite explain.
 *
 * font-display: swap — text is readable in the system font from the first paint
 * and swaps when Inter lands. A workshop phone on 3G must never be shown a
 * blank screen because a font is in flight.
 */
@font-face {
  font-family: "Inter";
  src: url("/static/fonts/Inter-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/static/fonts/Inter-SemiBold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/static/fonts/Inter-ExtraBold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}
/* The display cut, for the headings the board sets at 800 — tighter and made
   for size. Named apart so a heading asks for it deliberately. */
@font-face {
  font-family: "Inter Display";
  src: url("/static/fonts/InterDisplay-ExtraBold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Brand, per the design language. Slightly deeper than the old navy and a
     warmer orange — both carried straight from the mock palette. */
  /* 6.1 — THESE ARE THE BOARD'S OWN VALUES, not values near them.
     `reference/TFS_Design_Language_Board.html` arrived at run 4 and was diffed
     token by token against what had been built to its written description. Six
     were close but not equal — a hair off on the line, the muted text, the
     green, the red, the deep ink and the orange's dark step — and seven the
     board defines were missing altogether. "Close enough" in a palette is how
     two screens end up not quite matching and nobody can say why. */
  --navy: #141E27;
  --navy-2: #1D2935;          /* board --navy2 */
  --navy-3: #26343F;
  --orange: #E8720C;
  --orange-dark: #C55F06;     /* board --orange-dk (was #C55F08) */
  --orange-tint: #FDF1E6;

  --ink: #0E151C;             /* board --ink (was #141E27, which is --txt) */
  --txt: #141E27;             /* board --txt: body text, not the deepest ink */
  --ink-2: #3A4753;
  --muted: #5A6873;           /* board --mut (was #64748B) */
  --faint: #8A97A1;           /* board --faint */
  --line: #D8DEE3;            /* board --line (was #E2E7EC) */
  --line-strong: #CBD4DD;
  --bg: #F4F6F8;
  --paper: #F4F6F8;           /* the board's name for it */
  --card: #FFFFFF;

  --ok: #1E7A46;              /* board --ok (was #17794A) */
  --ok-tint: #E9F5EE;
  --warn: #B3421E;            /* board --warn — defect red (was #B45309) */
  --warn-tint: #FDF4E7;
  --amber: #B77400;           /* board --amber — a paused clock, an advisory */
  /* A DARKER STEP OF THE BOARD'S AMBER, for text. `--amber` on `--amber-bg`
     measures 3.33:1, which is below AA — fine for a dot or a rule, not for a
     word. Added rather than changing --amber, because the board's value is
     the board's and this is a use it does not cover. */
  --amber-ink: #8A5700;
  /* ORANGE AS TEXT ON WHITE — James's ruling of 28 Aug 2026, clause 3.
     `--orange` is 3.07:1 on white and `--orange-dark` only 4.19:1, so NEITHER
     is legible as body text; --orange-dark is a HOVER FILL, and it had been
     doing double duty as a link colour since the first stylesheet. This is the
     darker step that actually passes: 5.46:1 on white. */
  --orange-text: #A85105;
  --amber-bg: #FBEEDC;        /* board --amberbg */
  --plate: #FBD341;           /* board --plate — registrations, and nothing else */
  --blue: #8FB4D8;            /* board --blue — secondary text on navy */
  --vor: #C2261C;
  --vor-tint: #FBECEB;

  /* One spacing scale, used everywhere. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  --radius: 12px;
  --radius-sm: 8px;
  --radius-pill: 999px;

  --shadow-card: 0 1px 2px rgba(20, 30, 39, .05), 0 1px 3px rgba(20, 30, 39, .06);
  --shadow-lift: 0 4px 12px rgba(20, 30, 39, .10);

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text",
          "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Inter Display", "Inter", -apple-system, BlinkMacSystemFont,
          "Segoe UI Variable Display", "Segoe UI", Roboto, sans-serif;
}

/* ---- foundations --------------------------------------------------------- */

body {
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 {
  font-family: var(--font-display);
  letter-spacing: -0.015em;
  color: var(--ink);
}
/* THREE WEIGHTS, AND ONLY THREE — 400, 600, 800, per the board's type law and
   Section 8.3. The stylesheet asked for 500, 550, 650 and 700 as well; none of
   them is a font that exists here, so a browser would have SYNTHESISED them by
   smearing the nearest cut. That is the heading that looks slightly wrong and
   nobody can say why. Every declaration below maps onto a cut that ships. */
h1 { font-size: 26px; font-weight: 800; }
h2 { font-size: 18px; font-weight: 600; }
h3 { font-size: 15px; font-weight: 600; }

.wrap { max-width: 1240px; padding: var(--s5) var(--s4) var(--s7); }
.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: 13px; }

/* MONEY AND HOURS LINE UP. Tabular figures mean a column of totals reads as a
 * column, and 11.10 sits under 9.50 rather than drifting left. */
.num, .money, td.num, .reg, .totals td, [data-money], input[type=number] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ---- the top bar --------------------------------------------------------- */

.topbar {
  background: var(--navy);
  border-bottom: 1px solid rgba(255, 255, 255, .07);
  padding: var(--s3) var(--s4);
  gap: var(--s4);
}
.topbar .brand span { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em; }
.topbar .brand small { color: #93A4B5; font-weight: 400; letter-spacing: 0; }

/* THE WHITE CARD BEHIND THE LOGO. The mark is dark artwork; on a navy bar it
 * disappears. A white card is how it appears on the paperwork, so it is how it
 * appears here. */
.topbar .logo-img {
  background: #fff;
  border-radius: var(--radius-sm);
  padding: 5px 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}

.topbar nav a {
  color: #C3CEDA;
  border-radius: var(--radius-pill);
  padding: 7px 13px;
  font-weight: 800;
  font-size: 14px;
  transition: background .12s ease, color .12s ease;
}
.topbar nav a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.topbar nav a.active { background: var(--orange); color: #fff; }
.topbar .who { color: #A8B7C6; font-size: 13px; }

/* ---- cards --------------------------------------------------------------- */

.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s5);
  margin-bottom: var(--s4);
  box-shadow: var(--shadow-card);
}
.panel h2 { margin-bottom: var(--s3); }

/* ---- buttons: one hierarchy, used everywhere ------------------------------ */

.btn {
  font-family: var(--font);
  font-weight: 600;
  font-size: 15px;
  border-radius: var(--radius-sm);
  padding: 11px 18px;
  min-height: 46px;
  background: var(--orange);
  border: 1px solid var(--orange);
  color: #fff;
  box-shadow: var(--shadow-card);
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.btn:hover:not(:disabled) { background: var(--orange-dark); border-color: var(--orange-dark); }
.btn:active:not(:disabled) { box-shadow: none; }

.btn.ghost {
  background: #fff;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  box-shadow: none;
}
.btn.ghost:hover:not(:disabled) { background: #F7F9FB; border-color: var(--muted); }

.btn.danger { background: var(--vor); border-color: var(--vor); color: #fff; }
.btn.danger:hover:not(:disabled) { background: #A81F16; border-color: #A81F16; }

.btn.small { font-size: 13.5px; padding: 8px 13px; min-height: 40px; }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }

.btn:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
}

/* ---- form controls ------------------------------------------------------- */

input, select, textarea {
  font-family: var(--font);
  font-size: 16px;              /* below 16px iOS zooms the page on focus */
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  min-height: 46px;
  color: var(--ink);
  background: #fff;
}
input:focus, select:focus, textarea:focus { border-color: var(--orange); }
label { font-weight: 600; font-size: 13.5px; color: var(--ink-2); margin-bottom: var(--s1); }

/* WebKit renders a natively-styled <select> at its own size and ignores the
 * height CSS asks for — three of them measured 25px against a 44px guideline on
 * the tech job list. Turning the native appearance off puts sizing back under
 * CSS control; the chevron is drawn back on so it still reads as a dropdown. */
select {
  -webkit-appearance: none;
  appearance: none;
  height: 46px;
  padding-right: 34px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='%2364748B' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}
textarea { min-height: 96px; line-height: 1.5; }

/* ---- chips and badges ---------------------------------------------------- */

.chip, .clock-chip, .badge, .sbadge, .pchip {
  font-family: var(--font);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .01em;
  border-radius: var(--radius-pill);
  padding: 4px 11px;
  border: 1px solid transparent;
}
.chip { background: #EEF2F6; color: var(--ink-2); border-color: var(--line); }
.clock-chip { background: #EEF2F6; color: var(--ink-2); border-color: var(--line); }
.clock-chip.ok { background: var(--ok-tint); color: var(--ok); border-color: #BFE3CE; }
.clock-chip.warn { background: var(--warn-tint); color: var(--warn); border-color: #F0DAB4; }

/* ---- tables: document grade ---------------------------------------------- */

table, .grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 14px;
}
th {
  font-family: var(--font);
  font-weight: 600;
  font-size: 11.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-strong);
  background: #FAFBFC;
  white-space: nowrap;
}
td { padding: 11px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: #FAFBFC; }
td.num, th.num { text-align: right; }

/* ---- notices ------------------------------------------------------------- */

.notice, .errors {
  border-radius: var(--radius-sm);
  padding: var(--s3) var(--s4);
  font-size: 14.5px;
  border: 1px solid;
}
.notice { background: var(--ok-tint); border-color: #BFE3CE; color: var(--ok); }
.errors { background: var(--vor-tint); border-color: #F0C4C0; color: var(--vor); }

/* ---- empty states -------------------------------------------------------- */
/* An empty screen should say what it is and what to do, not look broken. */
.panel.muted, .empty {
  color: var(--muted);
  text-align: center;
  padding: var(--s6) var(--s4);
  border-style: dashed;
  border-color: var(--line-strong);
  background: #FBFCFD;
  box-shadow: none;
}

/* ---- the sign-in page ---------------------------------------------------- */

.login-wrap .panel { padding: var(--s6); border-radius: 16px; box-shadow: var(--shadow-lift); }
.login-wrap h1 { font-size: 24px; }
.login-logo { max-height: 52px; }

.namegrid button.nameBtn {
  font-family: var(--font);
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: #fff;
  min-height: 56px;
  transition: border-color .12s ease, background .12s ease;
}
.namegrid button.nameBtn:hover { border-color: var(--muted); background: #F7F9FB; }
.namegrid button.nameBtn.sel {
  border-color: var(--orange);
  background: var(--orange-tint);
  box-shadow: inset 0 0 0 1px var(--orange);
  /* The base stylesheet turns the selected name white for a solid-orange fill.
     This theme fills it with a pale tint instead, so white text vanishes — the
     selected name was unreadable until the colour came back with it. */
  color: var(--ink);
}
.namegrid button.nameBtn.sel .role-tag { color: var(--orange-dark); }
.namegrid .role-tag { color: var(--muted); font-weight: 600; }

.pin-display {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 30px;
  letter-spacing: .28em;
  color: var(--ink);
  background: #F4F6F8;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--s3);
  text-align: center;
}
.pinpad button {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: 21px;
  font-weight: 600;
  min-height: 60px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: #fff;
  color: var(--ink);
}
.pinpad button.digit:hover { background: #F4F6F8; }
.pinpad button.digit:active { background: #E9EEF3; }
.pinpad button.btn { background: var(--orange); border-color: var(--orange); color: #fff; }

/* ---- the phone tech flow -------------------------------------------------- */

@media (max-width: 560px) {
  .wrap { padding: var(--s3) var(--s3) var(--s7); }
  h1 { font-size: 22px; }
  .panel { padding: var(--s4); border-radius: var(--radius); }
  .topbar { padding: var(--s2) var(--s3); }
  .topbar nav a { padding: 9px 12px; }
  /* Nothing a gloved thumb has to hit is under 46px — the board's number, and
     two more pixels than Section 8 worked to. */
  .btn, .btn.small, input, select, textarea, .pinpad button { min-height: 46px; }
}

/* The clock bar is the one piece of chrome that must never be lost. */
.tech-clockbar { box-shadow: 0 2px 14px rgba(20, 30, 39, .30); }
.tech-clockbar-read strong { font-family: var(--font-display); letter-spacing: -0.01em; }

/* ---- the job type picker -------------------------------------------------- */
.jobtype-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--s2);
}
.jobtype {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: #fff;
  cursor: pointer;
  font-weight: 600;
  margin: 0;
  transition: border-color .15s ease, background .15s ease;
}
.jobtype:hover { border-color: var(--muted); background: #F7F9FB; }
.jobtype input {
  width: auto; min-height: 0; margin: 3px 0 0; flex: 0 0 auto;
  accent-color: var(--orange); transform: scale(1.25);
}
.jobtype-body { display: block; min-width: 0; }
.jobtype-name { display: block; font-weight: 600; color: var(--ink); }
.jobtype-lines { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.jobtype:has(input:checked) {
  border-color: var(--orange);
  background: var(--orange-tint);
  box-shadow: inset 0 0 0 1px var(--orange);
}
@media (max-width: 560px) {
  .jobtype-grid { grid-template-columns: 1fr; }
  .jobtype { min-height: 46px; }
}

/* ---- the signature pad ---------------------------------------------------- */
.sig-wrap { position: relative; }
.sig-pad {
  width: 100%;
  height: 240px;
  display: block;
  background: #fff;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  /* Without this a drawing gesture scrolls the page instead of drawing. */
  touch-action: none;
  cursor: crosshair;
}
.sig-baseline {
  position: absolute;
  left: 24px; right: 24px; bottom: 56px;
  border-bottom: 1px dashed var(--line-strong);
  pointer-events: none;
}
@media (max-width: 560px) { .sig-pad { height: 200px; } }

/* ---- print: documents stay documents ------------------------------------- */

@media print {
  .topbar, .btn, form { display: none !important; }
  .panel { border: 0; box-shadow: none; padding: 0; }
  body { background: #fff; }
}

/* ===========================================================================
 * SECTION 8 — APPLE-GRADE REFINEMENT
 *
 * This refines the EXECUTION of the design board. The board is the contract and
 * is unchanged: identity, palette, chips, plate treatment, clock bar and the
 * twelve look-laws are all as they were. Nothing below introduces a colour, a
 * typeface or a component — it adjusts surface, spacing, type and motion so the
 * same design reads calmer and more precise.
 *
 * THE RULE THAT GOVERNS ALL OF IT, and the one to fail on: a workshop phone is
 * read at arm's length, in bad light, by somebody wearing gloves. Every rule
 * here is judged against that first and against elegance second. A 44px target
 * does not become 40px because it looks tidier — and the last block in this
 * file exists to make that impossible.
 *
 * THIS FILE IS STANDALONE-ONLY. It is linked from the header only when
 * `standalone` is true, so the live pilot receives none of it. That is what
 * makes an all-CSS refinement safe: live is byte-identical by construction
 * rather than by inspection.
 * ======================================================================== */

:root {
  /* 8.4 — TWO ELEVATIONS, and only two. A shadow is a statement that something
   * floats above something else; a page with five of them has stopped saying
   * anything. `--shadow-card` is a resting surface, `--shadow-lift` is a
   * surface that has come forward. There is deliberately no third. */
  --elev-rest: 0 1px 2px rgba(20, 30, 39, .04), 0 1px 3px rgba(20, 30, 39, .05);
  --elev-raised: 0 6px 16px rgba(20, 30, 39, .10), 0 2px 4px rgba(20, 30, 39, .04);

  /* 8.4 — a hairline is 1px of the lightest line that still reads as a line.
   * Heavier borders were doing the job of spacing, which is spacing's job. */
  --hairline: rgba(20, 30, 39, .09);

  /* 8.1 — frosted surfaces. The tint sits UNDER the blur, so the surface is
   * still legible when the blur is unavailable. */
  --frost-light: rgba(255, 255, 255, .72);
  --frost-navy: rgba(20, 30, 39, .78);

  /* 8.5 — one duration and one curve. Ease-OUT because a thing arriving should
   * decelerate; ease-in makes an interface feel like it is being dragged. */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --dur: 170ms;

  /* 8.3 — the type scale, tightened. Display sizes get negative tracking
   * because large text set at 0 tracking looks loose; body text does not,
   * because tightening body text at arm's length costs legibility. */
  --tracking-display: -0.021em;
  --tracking-tight: -0.011em;
}

/* ---- 8.1 FROSTED TRANSLUCENCY ------------------------------------------- */
/* Content scrolls BENEATH the header and the clock bar rather than colliding
 * with them. The fallback is not a degraded frost — it is the solid navy the
 * bar has always been, which is why the tint is declared first and the blur
 * only inside @supports. */

.theme-tfs .topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--navy);
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .theme-tfs .topbar {
    background: var(--frost-navy);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
  }
  .theme-tfs .tech-clockbar {
    background: var(--frost-navy);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
  }
}

/* The clock bar keeps its shadow but loses its weight: a frosted surface with a
 * heavy drop shadow reads as two effects arguing. */
.theme-tfs .tech-clockbar {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .06), 0 6px 18px rgba(20, 30, 39, .18);
}

/* ---- 8.2 GROUPED INSET CARDS -------------------------------------------- */
/* The iOS Settings pattern: content sits in inset cards with hairline
 * separators between rows, rather than in boxes with borders. The separator is
 * INSET from the left so it reads as dividing rows within one surface, not as
 * stacking separate surfaces. */

.theme-tfs .panel {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--elev-rest);
  padding: var(--s5);
  margin-bottom: var(--s4);
}

.theme-tfs .panel > h2:first-child {
  margin-top: 0;
  margin-bottom: var(--s3);
}

/* Rows inside a card. The last one loses its rule, because a line under the
 * final row is a line under nothing. */
.theme-tfs .panel > ul > li,
.theme-tfs .panel > .row {
  border-top: 1px solid var(--hairline);
  padding: var(--s3) 0;
}
.theme-tfs .panel > ul > li:first-child,
.theme-tfs .panel > .row:first-child { border-top: 0; padding-top: 0; }
.theme-tfs .panel > ul > li:last-child,
.theme-tfs .panel > .row:last-child { padding-bottom: 0; }

/* Tables inside a card lose their outer borders — the card IS the border. */
.theme-tfs .panel table { border-collapse: collapse; width: 100%; }
.theme-tfs .panel table th,
.theme-tfs .panel table td {
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: var(--s3) var(--s2);
}
.theme-tfs .panel table tr:last-child td { border-bottom: 0; }
.theme-tfs .panel table th {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- 8.3 TYPE ------------------------------------------------------------ */
/* Weights 400 / 600 / 800 only. Five weights on a page is five decisions
 * nobody made deliberately. */

.theme-tfs h1 {
  font-family: var(--font-display);
  font-size: clamp(24px, 4.5vw, 30px);
  font-weight: 800;
  letter-spacing: var(--tracking-display);
  line-height: 1.15;
}
.theme-tfs h2 {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  line-height: 1.25;
}
.theme-tfs h3 { font-size: 15px; font-weight: 600; letter-spacing: var(--tracking-tight); }

/* Body at 15px, and 16px on a phone — where it is read at arm's length and
 * where 16px also stops iOS zooming a focused input. */
.theme-tfs body { font-size: 15px; line-height: 1.5; }
@media (max-width: 760px) {
  .theme-tfs body { font-size: 16px; }
  .theme-tfs input, .theme-tfs select, .theme-tfs textarea { font-size: 16px; }
}

.theme-tfs strong, .theme-tfs b { font-weight: 600; }
.theme-tfs .muted { color: var(--muted); }
.theme-tfs .hint { color: var(--muted); font-size: 13px; line-height: 1.45; }

/* Numbers in a column line up. A price column where the digits do not align is
 * a column that has to be read twice. */
.theme-tfs .num, .theme-tfs td.num, .theme-tfs th.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* The clock bar's own controls are thumb targets like any other — the audit
 * caught "Clock on" at 44px against the board's 46. */
.theme-tfs .tech-clockbar button { min-height: 46px; }

/* ── 6.1 — NUMBERS ARE TABULAR ─────────────────────────────────────────────
 * The board's law: "Numbers are tabular. Money, hours, mm, PSI, efficiencies —
 * always." Applied at the root rather than to a list of classes, because the
 * list is the part that goes stale: the next screen that shows a figure would
 * have to remember to join it, and the one after that would not.
 *
 * Inter ships proper tabular figures, so this costs nothing and makes a column
 * of money, a running clock and a set of tread depths line up under each other
 * — which is the entire point of the law. Standalone only. */
.theme-tfs,
.theme-tfs input,
.theme-tfs button,
.theme-tfs select,
.theme-tfs textarea { font-variant-numeric: tabular-nums; }

/* ── 6.1 — REGISTRATIONS WEAR THE PLATE ────────────────────────────────────
 * The board's law, word for word: "Registrations wear the plate. Yellow,
 * bordered, everywhere a vehicle is named." Until the board arrived this was a
 * NAVY chip with white letters — perfectly tidy, and not what the board says.
 * A number plate is the one thing on a workshop screen that everybody in the
 * building recognises from ten feet away, and it only works if it looks like a
 * plate.
 *
 * The values are the board's own `.plate` rule: plate yellow, a 2px near-black
 * border, 7px radius, 800 weight, .05em tracking. Standalone only — the same
 * `.reg` class is served to the live pilot, where it stays navy. */
.theme-tfs .reg,
.theme-tfs .card .reg,
.theme-tfs table.comp-grid td.regcell,
.theme-tfs table.comp-grid tr.comp-done td.regcell,
.theme-tfs td.regcell {
  display: inline-block;
  background: var(--plate);
  color: #111;
  border: 2px solid #111;
  border-radius: 7px;
  padding: 2px 10px;
  font-weight: 800;
  font-size: 15px;
  letter-spacing: .05em;
  /* A plate is read as characters, not as words: tabular figures keep 0/8 and
     1/7 the same width, which is what stops a reg being misread. */
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}

/* ---- 8.4 COLOUR RESTRAINT ------------------------------------------------ */
/* Colour only where it MEANS something: a state, a warning, an action. A page
 * where everything is coloured is a page where nothing is. */

.theme-tfs .btn.ghost {
  background: transparent;
  border: 1px solid var(--line-strong);
  color: var(--ink);
  box-shadow: none;
}
.theme-tfs .btn.ghost:hover { background: rgba(20, 30, 39, .04); }

/* ---- 8.5 MOTION ---------------------------------------------------------- */

.theme-tfs .btn,
.theme-tfs .tab,
.theme-tfs .nameBtn,
.theme-tfs .digit,
.theme-tfs a.btn {
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

/* A press is a small, immediate acknowledgement — not a performance. */
.theme-tfs .btn:active,
.theme-tfs .digit:active,
.theme-tfs .nameBtn:active { transform: scale(.978); }

.theme-tfs .panel { transition: box-shadow var(--dur) var(--ease); }

/* Skeletons rather than spinners: a spinner says "wait", a skeleton says what
 * is coming. */
.theme-tfs .skeleton {
  background: linear-gradient(90deg, var(--line) 25%, #EEF2F6 37%, var(--line) 63%);
  background-size: 400% 100%;
  animation: tfs-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm);
  min-height: 14px;
}
@keyframes tfs-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* NOTHING PERFORMING. Respected properly: not "shorter", but off. */
@media (prefers-reduced-motion: reduce) {
  .theme-tfs *,
  .theme-tfs *::before,
  .theme-tfs *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .theme-tfs .btn:active { transform: none; }
}

/* ---- 8.6 DETAIL ---------------------------------------------------------- */

/* Focus that is visible on both light cards and the navy bar, pixel-aligned
 * with an offset so it never sits on top of the thing it is marking. */
.theme-tfs :focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.theme-tfs .topbar :focus-visible { outline-color: #fff; }

/* An empty state is a direction, not a shrug. */
.theme-tfs .empty {
  text-align: center;
  padding: var(--s6) var(--s4);
  color: var(--muted);
}
.theme-tfs .empty-emoji { font-size: 28px; line-height: 1; margin-bottom: var(--s3); opacity: .9; }
.theme-tfs .empty p { margin: 0 0 var(--s2); }

/* ---- 8.7 THE LAW THAT OUTRANKS THE REST ---------------------------------- */
/* Refinement never at the cost of clarity or thumb targets.
 *
 * This block is last so it wins, and it is deliberately blunt: no refinement
 * above may shrink a control below 46px, and none may lighten body text below
 * the muted token. If a future tidy-up breaks this, it breaks visibly here
 * rather than quietly on a phone in a workshop. */
.theme-tfs .btn,
.theme-tfs button,
.theme-tfs input[type="submit"],
.theme-tfs .digit,
.theme-tfs .nameBtn,
.theme-tfs a.btn {
  min-height: 46px;
}
.theme-tfs input:not([type="checkbox"]):not([type="radio"]),
.theme-tfs select,
.theme-tfs textarea {
  min-height: 46px;
}
/* Body text never goes lighter than --muted, and never below 13px. */
.theme-tfs .hint, .theme-tfs .muted { font-size: max(13px, .8125rem); }

/* The three controls the 8.7 measurement found still under 44px. They predate
 * this section — the header's search button, and a date input in Settings — but
 * a list of known-too-small thumb targets is a list somebody eventually stops
 * reading, and these were two lines to fix. */
.theme-tfs .navsearch input,
.theme-tfs .navsearch button { min-height: 44px; }
.theme-tfs .navsearch button { padding-inline: var(--s3); }
.theme-tfs input[type="date"],
.theme-tfs input[type="search"] { min-height: 44px; }

/* ── 5.1 — NOTHING RUNS OFF THE SIDE OF A PHONE ────────────────────────────
   Found by walking every screen at 380px and measuring, not by looking: the
   job page, Parts, Settings, System health, the WIP report and the sheet's
   own settings all scrolled sideways, which on a phone hides the column
   somebody came for. Six screens, one cause each, one treatment:

     · a table that must stay a table SCROLLS INSIDE ITSELF rather than taking
       the page with it — the same treatment the compliance matrix got at 3.3,
       and the reason the row's identity stays on screen while the rest moves;
     · a long unbroken string (an error route, a URL, a storage key) wraps;
     · a row of controls wraps instead of pushing the page wider.

   STANDALONE ONLY: .theme-tfs is on the body only when PILOT_MODE=off, so the
   live pilot's rendering cannot be changed by anything in this block. */
@media (max-width: 700px) {
  /* THE TWO-COLUMN GRID IS ONE COLUMN ON A PHONE. `.row` is a hard
     `1fr 1fr` in style.css with no narrow rule, so Settings and the job page
     were laying two columns into 380px and letting the content push the page
     wider. This is where most of the sideways scroll came from — the buttons
     the measurement named were only the deepest thing inside it. */
  .theme-tfs .row,
  .theme-tfs .row.three {
    /* minmax(0, 1fr), NOT 1fr. A grid column will not shrink below the widest
       unbreakable thing inside it, so `1fr` alone left Settings laying out a
       560px column inside a 380px phone — one column, still off the side of the
       screen. The !important is for the job page, which sets its own
       `grid-template-columns` INLINE and would otherwise keep two columns on a
       phone; an inline style is the one thing a stylesheet cannot outrank. */
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .theme-tfs .row > * { min-width: 0; }

  /* Parts and the WIP report build their own <head> and have no <main> — the
     same two views 2.4 and 6.5 found doing it — so the table rule below has to
     reach them by class as well. */
  /* Parts and the WIP report build their own page and have no <main>, so the
     rule reaches them by table class. Both are wide by nature — a parts list
     and a whole workshop's week — so they scroll inside themselves and keep
     their first column on screen. */
  .theme-tfs main table,
  .theme-tfs table.grid,
  .theme-tfs table.pl,
  .theme-tfs table.wip,
  .theme-tfs table.pl,
  .theme-tfs table.wip {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* A table made scrollable still needs its ROWS to keep table layout, or the
     browser reflows every cell into a stack of blocks. */
  .theme-tfs main table > thead,
  .theme-tfs main table > tbody,
  .theme-tfs table.pl > thead,
  .theme-tfs table.pl > tbody,
  .theme-tfs table.wip > thead,
  .theme-tfs table.wip > tbody { display: table; width: max-content; min-width: 100%; }
  /* The sheet's own grids already have a scroll container and a card
     treatment; leave them to it. */
  .theme-tfs main .sheet-scroll table { display: table; }

  .theme-tfs code,
  .theme-tfs .mono,
  .theme-tfs td,
  .theme-tfs th { overflow-wrap: anywhere; }

  .theme-tfs main .flex,
  .theme-tfs main .btnrow,
  .theme-tfs main .row { flex-wrap: wrap; }

  .theme-tfs main img { max-width: 100%; height: auto; }

  /* Nothing here for /parts or /reports/wip: measuring them showed they are A4
     SHEETS rendered on screen (section.sheet, 794px and 1123px), not narrow
     screens that had come loose. Squeezing paper to fit a phone would make what
     comes out of the printer wrong. Recorded at 5.1 instead: both sit in the
     manager's nav and neither is a phone screen. */
}

/* ── 6.6 — THE BOARD'S JOB SCREEN ──────────────────────────────────────────
   Read off the board's second exemplar beside ours at the same width: the
   progress bar over the count, and a line's state carried by a disc down the
   left edge — a green tick when it is done, its number in grey when it is not.
   Both are standalone-only; the pilot's job page is unchanged. */
.theme-tfs .wl-progress {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
  margin: 0 0 var(--s2);
}
.theme-tfs .wl-progress span {
  display: block;
  height: 100%;
  background: var(--orange);
  border-radius: 999px;
  transition: width 180ms ease-out;
}
@media (prefers-reduced-motion: reduce) { .theme-tfs .wl-progress span { transition: none; } }

.theme-tfs .wl-disc {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 14px;
  background: #EEF1F5;
  color: var(--muted);
  border: 1.5px solid var(--line);
}
.theme-tfs .wl-disc.done {
  background: var(--ok);
  border-color: var(--ok);
  color: #fff;
}

/* ---- 6.1 ONE ORANGE PER SCREEN ------------------------------------------- */
/*
 * The board says it twice — in the components panel and again in the laws:
 * "One orange per screen. The primary action wears it; everything else queues
 * behind." It is what makes a screen answerable at a glance: whatever is orange
 * is the thing to do next. Two oranges is two next things, which is none.
 *
 * MEASURED, and the answer was ugly: nine on Settings, six on the tech's job
 * screen — the board's own second exemplar, where it draws exactly one. The
 * cause is that this system has TWO button roles where the board has four. It
 * has `.btn` (orange) and `.btn.ghost` (outline), so every inline action —
 * "Add note", "Upload", "Save note", "Open the inspection sheet" — had nowhere
 * to sit but the primary.
 *
 * THE MISSING ROLE IS SECONDARY, and `.btn.small` already marks it everywhere:
 * a small button in this codebase is, without exception, an action inside a
 * panel rather than the point of the screen. So the role it already plays is
 * given the look it should always have had — the board's `sec2`: navy on paper
 * white, unmistakably a button, unmistakably not the one thing to do next.
 *
 * ONE RULE RATHER THAN A SWEEP OF FORTY TEMPLATES, which also means it cannot
 * be applied to five screens and forgotten on the sixth.
 *
 * STANDALONE-ONLY, and that matters more than usual here: views/job.ejs is
 * served to the LIVE pilot, and this file is not. The pilot's tech screen keeps
 * every byte it has.
 */
.theme-tfs .btn.small {
  background: var(--paper, #fff);
  color: var(--navy, #142030);
  border: 1.5px solid var(--line-strong, #C7CFD8);
  box-shadow: none;
  font-weight: 700;
}
.theme-tfs .btn.small:hover:not(:disabled) {
  background: rgba(20, 30, 39, .04);
  border-color: var(--navy, #142030);
}
/* A destructive action is not secondary — it stays what it is. Same for a
 * control already given the ghost role explicitly. */
.theme-tfs .btn.small.danger {
  background: var(--vor, #B4231A);
  color: #fff;
  border-color: var(--vor, #B4231A);
}

/* ---- 6.1 AND THE ORANGE FOLLOWS THE CHAIN -------------------------------- */
/*
 * James's ruling, 28 Aug 2026, and Amendment 2 on the board: "one orange per
 * screen" is not "pick a panel and stick with it". The orange is the NEXT STEP
 * THAT ADVANCES THE JOB, and it MOVES as the job progresses.
 *
 * The secondary role above closed most of run 5's forty findings. What it could
 * not close was the last eight, all the same shape — a screen of several
 * independent panels, each with its own Save or Add, every one of them a real
 * primary in its own panel and none of them the point of the screen.
 *
 * THIS IS THE OTHER HALF OF THE ROLE, AND IT IS AN INVERSION, not another
 * exception: on a screen that opts in, an unmarked `.btn` is SECONDARY, and the
 * one control `src/lib/primaryAction.js` named for this render wears `.pri`.
 * A screen cannot then grow a second orange by someone adding a panel — the
 * default is quiet, and loudness has to be asked for, once, by a module that can
 * only return one answer.
 *
 * SCOPED BY `chain-orange` ON <body> rather than applied everywhere, and that is
 * a deliberate limit rather than a shortcut. Inverting the default for all
 * forty-odd screens would silently strip the primary off every screen that has
 * exactly ONE today and is correct — a regression nothing would fail on, since
 * "no orange" breaks no law. So the inversion goes where it was measured to be
 * needed, and the LAW is kept honest by MEASUREMENT instead: page-audit.js fails
 * the run on a screen offering more than one, and on a chain screen whose orange
 * is not the step the chain says is next. A ninth screen cannot appear quietly.
 *
 * STANDALONE-ONLY. `views/job.ejs` and `views/board.ejs` are served to the LIVE
 * pilot; this file is not, and the `chain-orange` class is emitted inside the
 * same standalone branch that emits `theme-tfs`. The pilot keeps every byte.
 */
.theme-tfs.chain-orange .btn:not(.pri):not(.ghost):not(.danger) {
  background: var(--paper, #fff);
  color: var(--navy, #142030);
  border: 1.5px solid var(--line-strong, #C7CFD8);
  box-shadow: none;
  font-weight: 700;
}
.theme-tfs.chain-orange .btn:not(.pri):not(.ghost):not(.danger):hover:not(:disabled) {
  background: rgba(20, 30, 39, .04);
  border-color: var(--navy, #142030);
}
/* The one that is the next step. Written out rather than left to the base rule
 * so that it beats the demotion above whatever order the cascade ends up in,
 * and so a `.btn.small.pri` — the shape the tech's job screen needs, where the
 * next step is a capture button inside a work line — is orange like any other
 * primary. A small primary is still the primary. */
.theme-tfs.chain-orange .btn.pri,
.theme-tfs.chain-orange .btn.small.pri {
  background: var(--orange);
  border: 1px solid var(--orange);
  color: #fff;
  font-weight: 700;
}
.theme-tfs.chain-orange .btn.pri:hover:not(:disabled),
.theme-tfs.chain-orange .btn.small.pri:hover:not(:disabled) {
  background: var(--orange-dark);
  border-color: var(--orange-dark);
}
/* THE PARTS SCREEN BUILDS ITS OWN CHROME. It writes its own <head> and its own
 * toolbar with an inline stylesheet, so its Print and Search buttons are orange
 * by their own rule rather than by `.btn` — which is how a print control and a
 * search box came to be counted as two of that screen's three orange actions.
 * They are chrome, not actions: the same argument that excludes the nav's
 * current-page pill, and the board's own app bar has no orange in it. */
.theme-tfs.chain-orange .toolbar button:not(.pri),
.theme-tfs.chain-orange .toolbar a.tbtn:not(.ghost):not(.pri) {
  background: var(--paper, #fff);
  color: var(--navy, #142030);
  border: 1.5px solid var(--line-strong, #C7CFD8);
  font-weight: 700;
}

/* ---- THE JOB CARD'S QUICK-MOVE TABS -------------------------------------- */
/*
 * JAMES'S ACCEPTANCE-RUN FINDING: "each job card's three bottom tabs render
 * white/unlabelled — unreadable until pressed."
 *
 * ROOT CAUSE, and it is not a missing variable. `style.css` has:
 *
 *     .card .movebtns button { … background:#f4f6f9; … }   ← no `color`
 *
 * The rule paints a near-white background and NEVER SAYS WHAT COLOUR THE LABEL
 * IS. So the text colour is whatever the user agent picks for `buttontext` —
 * which follows the browser and the operating system's colour scheme, not this
 * stylesheet. On a device that resolves it light, the label is near-white on a
 * near-white chip: a blank tab, exactly as reported, and readable the instant it
 * is pressed because the :hover rule DOES set a colour (orange with white text).
 *
 * MEASURED, HONESTLY: this could not be reproduced here. Chromium and WebKit,
 * light scheme and dark, all resolve it to black — so what is written down is
 * the cause that is visible in the CSS rather than a repro I do not have. The
 * fix is to remove the class of fault: every one of these controls now states
 * its own colour, so no user agent gets a vote. Whatever James's phone decided,
 * it cannot decide it any more.
 *
 * AND THE 46px FLOOR WAS BEING BEATEN. `.theme-tfs button { min-height: 46px }`
 * sits in the block at the end of this file that says "no refinement above may
 * shrink a control below 46px" — but `.card .movebtns button` in style.css is
 * MORE SPECIFIC (0,2,1 against 0,1,1), so these have been 34px all along and the
 * blunt final block quietly lost. A floor enforced by specificity is not a
 * floor. Measured by the page audit at 380px, which is why it was already on
 * PRE_FLIP_CHECKLIST as "the job page's quick-move buttons are 33px".
 *
 * KEYED ON `title`, WHICH IS THE ONLY HOOK THAT SURVIVES. `public/js/board.js`
 * REBUILDS these buttons after a drag, and that file is served to the live pilot
 * and byte-frozen — so a new class added in the template would be correct on
 * first paint and gone the moment a card moved. Both the server and the rebuild
 * already write `title="Move to <target>"`, identically. Styling off the title
 * needs no markup change, no JS change and no second copy of the mapping, which
 * is why it beats adding a class in two places and forgetting the third.
 *
 * COLOUR CARRIES MEANING, per the board's chip law — tints, not saturated
 * fills, and NONE of them orange: the board's first law is one orange per
 * screen and that orange belongs to "+ Book a Job". The old :hover turned these
 * orange, so a card being pressed put a second primary on the board; the filled
 * state is now the tab's own colour instead.
 *
 * STANDALONE ONLY. `style.css`, `board.ejs` and `board.js` are all served to the
 * live pilot and none of them is touched.
 */
.theme-tfs .card .movebtns,
.theme-tfs .movebtns.detail-moves { gap: 8px; }

.theme-tfs .card .movebtns button,
.theme-tfs .movebtns.detail-moves button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* The board's thumb-first law, and this control is why it needed writing
     twice: it is the most-tapped thing on the busiest screen. */
  min-height: 46px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 700;
  border-radius: 999px;
  /* Stated, not inherited. This single declaration is the fault. */
  color: var(--ink);
  background: var(--paper);
  border: 1.5px solid var(--line-strong);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* THE ICON IS PART OF THE LABEL, NOT INSTEAD OF IT. The board's law is that a
 * control says what it does; an icon-only tab is a control you have to press to
 * find out. Drawn with ::before so the text in the markup is untouched — the
 * label a screen reader announces is still the whole label. */
.theme-tfs .card .movebtns button::before,
.theme-tfs .movebtns.detail-moves button::before {
  font-size: 12px;
  line-height: 1;
}

/* Progress — navy tint. */
.theme-tfs .movebtns button[title="Move to In Progress"] {
  background: #E7ECF1; color: var(--navy); border-color: #C3CEDA;
}
.theme-tfs .movebtns button[title="Move to In Progress"]::before { content: "▶"; }
.theme-tfs .movebtns button[title="Move to In Progress"]:hover,
.theme-tfs .movebtns button[title="Move to In Progress"]:focus-visible,
.theme-tfs .movebtns button[title="Move to In Progress"]:active {
  background: var(--navy); color: #fff; border-color: var(--navy);
}

/* Parts — amber tint. `--amber` (#B77400) on `--amber-bg` is 3.33:1, which is
 * BELOW AA for text, so the tint gets a darker step of the same hue for its ink.
 * The board defines --amber for a mark — a paused clock, an advisory dot — not
 * for text sitting on its own tint. */
.theme-tfs .movebtns button[title="Move to Awaiting Parts"] {
  background: var(--amber-bg); color: var(--amber-ink); border-color: #E4C79A;
}
.theme-tfs .movebtns button[title="Move to Awaiting Parts"]::before { content: "🔧"; }
.theme-tfs .movebtns button[title="Move to Awaiting Parts"]:hover,
.theme-tfs .movebtns button[title="Move to Awaiting Parts"]:focus-visible,
.theme-tfs .movebtns button[title="Move to Awaiting Parts"]:active {
  background: var(--amber-ink); color: #fff; border-color: var(--amber-ink);
}

/* Complete — GREEN tint, and this is a deliberate departure from the brief,
 * recorded rather than done quietly. James's note asked for "the third
 * (history/details) = slate tint" — but he was naming a tab he could not read.
 * The third tab is "Complete". His own instruction is that colour carries
 * audited meaning, and the board's palette says green is "pass, done,
 * clocked-on". Slate is the board's MONITOR colour and would say the opposite of
 * what this button does. Flip it to slate in one line if the call is wrong. */
.theme-tfs .movebtns button[title="Move to Complete"] {
  background: var(--ok-tint); color: var(--ok); border-color: #B9DCC8;
}
.theme-tfs .movebtns button[title="Move to Complete"]::before { content: "✓"; }
.theme-tfs .movebtns button[title="Move to Complete"]:hover,
.theme-tfs .movebtns button[title="Move to Complete"]:focus-visible,
.theme-tfs .movebtns button[title="Move to Complete"]:active {
  background: var(--ok); color: #fff; border-color: var(--ok);
}

/* Ready for MOT — SLATE, the board's monitor colour: a job waiting its turn at
 * a test. It was ORANGE (`.card .movebtns button.mot` in style.css), which put a
 * second orange on the board and broke the first law on the busiest screen. */
.theme-tfs .movebtns button[title="Move to Ready for MOT"],
.theme-tfs .card .movebtns button.mot {
  background: #EDF0F3; color: var(--ink-2); border-color: #C7D0D8;
}
.theme-tfs .movebtns button[title="Move to Ready for MOT"]::before { content: "📋"; }
.theme-tfs .movebtns button[title="Move to Ready for MOT"]:hover,
.theme-tfs .movebtns button[title="Move to Ready for MOT"]:focus-visible,
.theme-tfs .movebtns button[title="Move to Ready for MOT"]:active,
.theme-tfs .card .movebtns button.mot:hover {
  background: var(--ink-2); color: #fff; border-color: var(--ink-2);
}

/* ---- THE SAME FAULT, EVERYWHERE ELSE IT OCCURS --------------------------- */
/*
 * James asked for the failure mode behind the unreadable card tabs to be checked
 * across every screen, and the new contrast measurement in `page-audit.js` found
 * it in two more places. Both are the same thing the tabs were: a label the eye
 * cannot separate from what is behind it.
 *
 *   "Parts: Not Required"  #8593A6 on #F3F6FA  = 2.88:1   (needs 4.5)
 *   the row's ⋯ menu       #97A3B4 on white    = 2.56:1   (needs 4.5)
 *
 * The ⋯ is the worse of the two: it is the ONLY way into delete and archive on a
 * Working Sheet row, and at 2.56:1 it is a control most people would not know
 * was there. It is also the control the browser audit's very first finding was
 * about, back at session 3 — "on a phone the row menu was untappable" — so this
 * is the second time the same three dots have hidden from somebody.
 *
 * Both take the board's own `--muted`, which is the token for exactly this: text
 * that is quieter than body copy and still text. Standalone only — `style.css`
 * and `sheet.css` are served to the live pilot and neither is touched.
 */
.theme-tfs .pchip[data-parts="Not Required"] { color: var(--muted); }
.theme-tfs .sheet-grid td.rowmenu button { color: var(--muted); }

/* ---- THE ORANGE RULES, MADE LAW ------------------------------------------ */
/*
 * James's ruling, 28 Aug 2026, after the contrast sweep measured white on the
 * brand orange at 3.07:1 across 96 primary buttons. The brand keeps its orange;
 * TYPOGRAPHY makes it legible. Three rules, and the board now carries all three:
 *
 *   1. A primary button stays white on orange, and its label must qualify as
 *      LARGE TEXT — at which point the AA floor is 3:1 and 3.07 passes.
 *   2. SMALL text never sits on orange. Where it must, it is navy (5.50:1).
 *   3. Orange used as TEXT on white uses `--orange-text`, not `--orange` and
 *      not `--orange-dark`.
 *
 * ONE CORRECTION TO THE RULING, and it is the whole reason rule 1 works.
 * The ruling defined large text as "SemiBold/Bold at 16px+". WCAG's definition
 * is 18 POINT, or 14 point BOLD — points, not pixels — which at 96dpi is 24px,
 * or 18.66px bold. A 16px SemiBold label is NOT large text: its floor stays 4.5
 * and white-on-orange would still fail at 3.07. So the rule is implemented at
 * the smallest size that genuinely qualifies — 19px at 700 — and the audit
 * enforces the real threshold rather than the stated one. The intent of the
 * ruling is met exactly; only the constant moved, because at 16px the claim
 * "3.07:1 satisfies the large-text standard" is not true.
 *
 * A SIDE EFFECT WORTH HAVING: the one orange action on a screen is now visibly
 * the biggest control on it, which is 6.1's first law drawn rather than argued.
 *
 * SCOPED TO THE ORANGE ONES. `.ghost` is navy on white and `.small` is the
 * secondary role — both are already high-contrast and neither grows. `.danger`
 * is white on #C2261C and passes at any size.
 */
.theme-tfs .btn:not(.ghost):not(.small):not(.danger),
.theme-tfs.chain-orange .btn.pri,
.theme-tfs .pinpad button.btn {
  font-size: 19px;
  font-weight: 700;
  /* 19px at 700 in a 46px control needs the padding back a little, or the label
     touches the edge on a narrow phone. */
  padding: 9px 18px;
  letter-spacing: -0.005em;
}

/* RULE 2 — small text on orange is NAVY, never white.
 * The header's search button is the only one left: a 🔍 at 44px that cannot be
 * made "large text" without turning the app bar into a toolbar. Navy on orange
 * measures 5.50:1, so it passes as body text outright and needs no exemption. */
.theme-tfs .navsearch button { color: var(--navy); }
/* The parts toolbar's buttons are already navy-on-paper under `chain-orange`
 * (the block above), so they need nothing here. Writing a second, UNSCOPED
 * `:not(.pri)` rule for them broke the FROZEN test that requires every rule
 * mentioning `.pri` to be chain-orange scoped — and that test was right: an
 * unscoped `.pri` selector is exactly how a rule meant for one screen ends up
 * restyling all of them. */

/* RULE 3 — orange as TEXT on white takes the darker step.
 * `a { color: var(--orange-dark) }` in style.css means EVERY LINK in the
 * application has been 4.19:1 since the first stylesheet — under AA, and never
 * noticed because nothing measured it. */
.theme-tfs a:not(.btn):not(.tab):not(.nameBtn) { color: var(--orange-text); }
.theme-tfs .pchip[data-parts="On Order"] { color: var(--orange-text); }
.theme-tfs details.adjust > summary { color: var(--orange-text); }
.theme-tfs .sheet-stamp.saving { color: var(--orange-text); }
.theme-tfs .combo-opt.addfree { color: var(--orange-text); }

/* ---- THE HEADER, READ OFF A PHONE --------------------------------------- */
/*
 * James, from his phone: "header nav links are small orange text on the dark
 * header". Measured before it was touched, with the frosted bar composited
 * rather than skipped: every nav link was #A85105 on #454E55 — 1.56:1 — and the
 * ACTIVE tab was orange text on the orange pill at 1.78:1. Against a 4.5 floor.
 *
 * THE CAUSE IS RULE 3, ABOVE, and it is worth naming. That rule paints every
 * link `--orange-text` and its own comment says what it is for: "orange as TEXT
 * ON WHITE takes the darker step". It was written unscoped, so it reached the
 * one surface in the application whose background is dark. A rule that names
 * the background it assumes should say so in its selector.
 *
 * Nav links are WHITE at SemiBold. The ACTIVE tab keeps the orange pill — it is
 * the board's own marker for where you are — and takes NAVY text on it, which is
 * the alternative James allowed: white on that orange is 3.07:1 and would need
 * 19px/700 to be legal, and a 19px label in a nine-link phone header buys the
 * contrast by wrecking the layout.
 *
 * Specificity is written out rather than left to luck: Rule 3 is (0,4,1), so
 * these repeat its :not() chain to land above it. `.theme-tfs` scopes the lot,
 * so the live pilot — which is never served this file — keeps every byte.
 */
.theme-tfs .topbar nav a:not(.btn):not(.tab):not(.nameBtn) {
  color: #FFFFFF;
  font-weight: 600;
}
.theme-tfs .topbar nav a:not(.btn):not(.tab):not(.nameBtn):hover {
  background: rgba(255, 255, 255, .14);
  color: #FFFFFF;
}
.theme-tfs .topbar nav a.active:not(.btn):not(.tab):not(.nameBtn) {
  background: var(--orange);
  color: var(--navy);
}
/* The signed-in line sits on the same dark bar and was the same washed grey. */
.theme-tfs .topbar .who { color: #D7E0E9; }
.theme-tfs .topbar .who b { color: #FFFFFF; }

/*
 * THE PHONE HEADER, LAID OUT ON PURPOSE.
 *
 * Measured at 380px before: 351px tall — nearly half an 820px phone — with the
 * nine links wrapping into rows of 3, 4 and 2, and the search box and the
 * signed-in line falling between them wherever the flex ran out. That is not a
 * layout, it is what is left when nothing decides.
 *
 * So it is a grid with named rows: the brand and the way out on the first, the
 * nav on its own as a SINGLE SCROLLING ROW, then the search, then who you are.
 * A horizontal scroll row is the honest answer to nine links on a phone — every
 * link keeps its full label and its 46px target, and nothing is hidden behind a
 * menu nobody finds.
 */
@media (max-width: 700px) {
  .theme-tfs .topbar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "brand  out"
      "nav    nav"
      "search search"
      "who    who";
    gap: 8px 10px;
    align-items: center;
    padding: 10px var(--s3, 12px);
  }
  .theme-tfs .topbar .brand { grid-area: brand; min-width: 0; }
  .theme-tfs .topbar .brand span { min-width: 0; }
  .theme-tfs .topbar > form[action="/logout"] { grid-area: out; justify-self: end; }
  .theme-tfs .topbar .navsearch { grid-area: search; }
  .theme-tfs .topbar .navsearch input { width: 100%; max-width: none; }
  .theme-tfs .topbar .who { grid-area: who; }
  /* The flex spacer is a layout device for a row that no longer exists. It was
   * rendering as a zero-height 118px box in the middle of the wrap. */
  .theme-tfs .topbar .spacer { display: none; }

  .theme-tfs .topbar nav {
    grid-area: nav;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Bleed to the bar's edges so the row reads as scrollable rather than as a
     * list that happens to be cut off. */
    margin: 0 calc(-1 * var(--s3, 12px));
    padding: 0 var(--s3, 12px);
  }
  .theme-tfs .topbar nav::-webkit-scrollbar { display: none; }
  .theme-tfs .topbar nav a {
    flex: 0 0 auto;
    white-space: nowrap;
    /* The board's floor, on the most-pressed row in the application. */
    min-height: 46px;
    display: inline-flex;
    align-items: center;
  }
}

/* FINDING 2a — THE HANDOFF PANEL.
 *
 * The manager half finished a stage and stopped. This is the panel that says
 * what comes next and carries the one tap that does it — the office's version
 * of the tech screen's next work line, which was never in doubt.
 *
 * Scoped by `chain-orange` for the same reason every rule above it is: that
 * class is emitted only on the standalone, so the live pilot's stylesheet
 * cannot see any of this and its screens cannot move. */
.theme-tfs.chain-orange .chain-next {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  border-left: 4px solid var(--orange, #E8761A);
}
.theme-tfs.chain-orange .chain-next-h {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted, #5A6873);
  flex: 0 0 100%;
}
.theme-tfs.chain-orange .chain-next-what {
  margin: 0;
  font-weight: 700;
  flex: 1 1 160px;
  min-width: 0;
}
/* Over the 46px floor, like every other control that a person in a hurry has to
 * hit. The floor is not a thing to be beaten by specificity again. */
.theme-tfs.chain-orange .chain-next-go { min-height: 46px; }

/* FINDING 2b — THE WAITING-ON-YOU STRIP.
 * Scoped by `chain-orange` like every rule above: emitted on the standalone
 * only, so the live pilot's stylesheet cannot see it. */
.theme-tfs.chain-orange .waiting-strip { border-left: 4px solid var(--orange, #E8761A); }
.theme-tfs.chain-orange .waiting-h {
  margin: 0 0 8px; font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted, #5A6873);
}
.theme-tfs.chain-orange .waiting-list { list-style: none; padding: 0; margin: 0; }
.theme-tfs.chain-orange .waiting-item {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border-top: 1px solid var(--line, #dfe3e8); padding: 10px 0;
}
.theme-tfs.chain-orange .waiting-item:first-child { border-top: 0; }
.theme-tfs.chain-orange .waiting-count {
  font-weight: 800; font-size: 18px; min-width: 1.4em; text-align: center;
}
.theme-tfs.chain-orange .waiting-label { font-weight: 700; flex: 0 1 auto; }
.theme-tfs.chain-orange .waiting-jobs {
  display: flex; gap: 6px; flex-wrap: wrap; flex: 1 1 100%;
}
/* Over the 46px floor — these are the way into the work, not decoration. */
.theme-tfs.chain-orange .waiting-job {
  display: inline-flex; align-items: center; min-height: 46px; padding: 0 10px;
  border: 1px solid var(--line-strong, #C7CFD8); border-radius: var(--radius, 10px);
  font-weight: 700; font-size: 13px; text-decoration: none;
  color: var(--navy, #142030); background: var(--paper, #fff);
}

/* FINDING 2b — THE AGE ON THE STRIP. Debt looks like debt: the chip is quiet
 * while the item is fresh and turns amber once it has been owed a week, which
 * is the point at which a pack nobody sent has stopped being an oversight.
 * Scoped by chain-orange like the rest — the pilot never emits that class. */
.theme-tfs.chain-orange .waiting-age {
  font-size: 12px; font-weight: 700; color: var(--muted, #5A6873);
  border: 1px solid var(--line, #dfe3e8); border-radius: 999px; padding: 2px 8px;
}
.theme-tfs.chain-orange .waiting-age.is-stale {
  color: var(--amber, #B77400); border-color: #f0d5a6; background: #fdf3e3;
}
.theme-tfs.chain-orange .waiting-job { flex-direction: column; align-items: flex-start; padding: 6px 10px; }
.theme-tfs.chain-orange .waiting-job-age {
  display: block; font-size: 11px; font-weight: 600; color: var(--muted, #5A6873);
}
.theme-tfs.chain-orange .waiting-job.is-stale { border-color: #f0d5a6; background: #fdf3e3; }
.theme-tfs.chain-orange .waiting-job.is-stale .waiting-job-age { color: var(--amber, #B77400); }

/* THE BOOKING-REQUEST COUNT IN THE NAVIGATION.
   Standalone only, because theme.css is loaded only on a standalone instance —
   which is why it is here and not in style.css, a file the live pilot serves.
   An unused rule in the pilot's stylesheet would change bytes it has served for
   months for no gain at all. */
.navcount {
  display: inline-block;
  min-width: 20px;
  padding: 1px 6px;
  margin-left: 4px;
  border-radius: 10px;
  background: #E8720C;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  vertical-align: 1px;
}

/* LAW 14 — REACHABLE IS NOT READ. The shared stacked-block pattern.
   A table at 380px puts the column that matters — a due date, a figure, or the
   reason something cannot happen — off the right-hand edge inside a scrolling
   box. Nothing is clipped, so every overflow check passes it; it is simply never
   read, which is the same as not being there.
   So: one block per record, and the facts inside it laid out with auto-fit
   columns. No media query, because the same rule then serves 380px, a tablet in
   the office and a desktop, and there is no breakpoint to get wrong.
   Used by the test radar, the reminder screens, the vehicle and customer files,
   the handoff list, the costing history and the compliance matrix. */
.stack-list { display: grid; gap: 8px; margin-top: 10px; }
.stack-row {
  border: 1px solid #DDE3E8;
  border-radius: 8px;
  padding: 10px 12px;
  background: #fff;
}
.stack-row.is-overdue { border-color: #c9a1a1; background: #fdf5f5; }
.stack-title { font-weight: 800; font-size: 16px; letter-spacing: .02em; }
.stack-sub { color: #5A6873; font-size: 13px; }
.stack-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px 16px;
  margin-top: 10px;
  font-size: 13px;
}
.stack-facts > div > b {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #5A6873;
}
.stack-facts .nowrap { white-space: nowrap; }
.stack-why { margin-top: 8px; font-size: 13px; line-height: 1.5; }

/* A TRAILER'S C NUMBER — its own style, not a number plate.
   A registration keeps the plate treatment it has always had. A C number is a
   different kind of identifier — a fleet number painted on the front, not a
   DVLA-issued mark — and putting one in a yellow plate box reads as a
   registration that has gone wrong. So it gets a chip of its own: same
   monospaced, letter-spaced weight so the two sit together on a line, in the
   navy the rest of the chrome uses.
   Standalone only, because theme.css is loaded only on a standalone instance. */
.cnum {
  display: inline-block;
  padding: 2px 8px;
  margin-left: 6px;
  border: 1px solid #141E27;
  border-radius: 5px;
  background: #141E27;
  color: #fff;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-weight: 700;
  font-size: .9em;
  letter-spacing: .06em;
  vertical-align: 2px;
}
.cnum.solo { margin-left: 0; }

/* ==========================================================================
   05/09 SITE CHECK — THE THINGS THAT WERE WIDER THAN THE SCREEN, AND THE
   HEADER THAT ATE A QUARTER OF IT.
   ==========================================================================
   Everything below is STANDALONE ONLY by construction: this stylesheet is
   linked inside the standalone branch of partials/header.ejs, so the live
   pilot never receives a byte of it.

   ---- 4a. THE DIARY, AT 1288px ------------------------------------------
   `.diary` in style.css is `repeat(8, minmax(150px, 1fr))`. Eight columns of
   at least 150px plus seven 10px gaps is 1270px BEFORE the page's own padding,
   so on a 1288px window the eighth column — "NO DATE YET", the one holding
   everything nobody has scheduled — hung off the right of the document and the
   whole page scrolled sideways.
   The columns keep their floor and the GRID scrolls inside itself instead. */
@media (min-width: 900px) {
  .theme-tfs .diary {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-auto-columns: minmax(150px, 1fr);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
  }
}

/* ---- 4b. THE WORKING SHEET, AT 1288px ---------------------------------
   `.fitgrid` is `width: 100%` with a `min-width: 1180px` and percentage
   columns, which at 1288px left DATE ARRIVE and ADDED BY truncated on the
   left, the PW and INV boxes cut off on the right, and the date cell reading
   "05/09/:" — a date input squeezed narrower than the value inside it.
   Floors in PIXELS for the columns whose contents have a fixed size (a date, a
   tick box, a name), and the header row never truncates. `.sheet-scroll`
   already scrolls; it now has something to scroll. */
/* The per-tab rules in sheet.css are `.fitgrid[data-tab="…"] col.c-…`, which is
   the same specificity as a `.theme-tfs .fitgrid col.c-…` here and is loaded
   after this file on the sheet page — so these carry the tab attribute too and
   win on specificity rather than on order. Measured, not guessed: at 1288px the
   truncated headers needed 100px, 74px and 36px against the 94, 71 and 24 they
   had. */
.theme-tfs table.sheet-grid.fitgrid { min-width: 1360px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-date_arrived { width: 118px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-updated_by,
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-created_by { width: 96px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-night_shift { width: 52px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-tech { width: 110px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-pw_sent,
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-invoiced,
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-passed_mot { width: 62px; }
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-registration { width: 124px; }
.theme-tfs table.sheet-grid thead th {
  white-space: nowrap;
  overflow: visible;
  text-overflow: clip;
}
/* A date input has a value AND a picker button inside it; below about 110px the
   browser truncates the value rather than the control, which is how a cell came
   to read "dd/mm/📅". */
.theme-tfs .sheet-grid input.cin[type="date"] { min-width: 106px; }

/* ---- 5. THE STICKY HEADER, COLLAPSED ON SCROLL ------------------------
   The bar is ~240px tall on the desktop and ~238px at 380px once the nav wraps
   to a third line, and `position: sticky` keeps every pixel of it pinned — a
   quarter of the screen, permanently, on the page a fitter reads one-handed.
   The delete-panel work on 04/09 measured the same 238px eating an anchor.

   ON SCROLL the identity row (logo and workshop name) and the signed-in row
   fold away and only the navigation stays pinned; at the top of the page they
   come back. Nothing is removed from the DOM, so no page's bytes change and the
   keyboard order is untouched — the rows are folded with max-height, which
   transitions, rather than display:none, which jumps.

   The threshold has hysteresis (collapse past 120px, expand under 40px) so a
   page cannot flap between the two states as its own height changes. */
.theme-tfs .topbar .brand,
.theme-tfs .topbar .who,
.theme-tfs .topbar form[action="/logout"] {
  transition: max-height .16s ease, opacity .12s ease;
  max-height: 64px;
  overflow: hidden;
}
.theme-tfs.tfs-collapsed .topbar {
  padding-top: var(--s2);
  padding-bottom: var(--s2);
  row-gap: 0;
}
.theme-tfs.tfs-collapsed .topbar .brand,
.theme-tfs.tfs-collapsed .topbar .who,
.theme-tfs.tfs-collapsed .topbar form[action="/logout"] {
  max-height: 0;
  opacity: 0;
  margin: 0;
  padding: 0;
  border: 0;
  flex-basis: 0;
}
/* COLLAPSED, THE NAV MUST FIT ONE LINE. Twelve links at their full size wrap to
   two rows at 1288px and push the search onto a third, which put the collapsed
   bar at 140px — under the 240 it was, and still over the 100 budget. Tighter
   padding and a shorter search box put the whole thing on one row. Measured at
   1288px: nav 1256px before, and it has to share the row with a 267px search. */
@media (min-width: 900px) {
  .theme-tfs.tfs-collapsed .topbar nav a { padding: 5px 9px; font-size: 13px; }
  .theme-tfs.tfs-collapsed .topbar nav { gap: 2px; }
  .theme-tfs.tfs-collapsed .topbar .navsearch input { max-width: 150px; min-height: 34px; }
  .theme-tfs.tfs-collapsed .topbar .navsearch button { min-height: 34px; }
}

/* On a phone the search box is the third row; the nav alone is what has to stay
   reachable, and 60px is the budget. */
@media (max-width: 899px) {
  .theme-tfs.tfs-collapsed .topbar .navsearch { max-height: 0; opacity: 0; margin: 0; overflow: hidden; }
  .theme-tfs.tfs-collapsed .topbar nav { row-gap: 0; }
  /* The bar's own padding, not the links'. A nav link keeps its 44px touch
     target — that rule is in style.css and it is there for gloves — so the 60px
     budget is met by trimming the bar around it, which was 8px top and bottom
     and put a single row of 44px links at 63. */
  .theme-tfs.tfs-collapsed .topbar { padding-top: 4px; padding-bottom: 4px; }
}
@media (prefers-reduced-motion: reduce) {
  .theme-tfs .topbar .brand,
  .theme-tfs .topbar .who,
  .theme-tfs .topbar form[action="/logout"] { transition: none; }
}

/* ---- the Working Sheet's Job column (6 Sep 2026) --------------------------
   The sheet is a live view of jobs, so every row either IS a job — and links to
   it — or is a line somebody typed that nothing is booked against, and says so.
   Sized to hold "JOB-00032" and the "not booked · Book it" pair without wrapping.
   Standalone only, like everything else in this file. */
.theme-tfs table.sheet-grid.fitgrid[data-tab] col.c-job { width: 132px; }
.theme-tfs .sheet-grid a.joblink { font-weight: 700; text-decoration: none; white-space: nowrap; }
.theme-tfs .sheet-grid .notbooked { display: flex; gap: 6px; align-items: baseline; white-space: nowrap; }
.theme-tfs .sheet-grid .notbooked .chip { font-size: 10.5px; padding: 2px 7px; }
.theme-tfs .sheet-grid .notbooked .bookit { font-size: 11.5px; font-weight: 700; white-space: nowrap; }
/* A job's own fields are the job's record. Quieter than the cells you can type
   in, so the difference is visible before you click one. */
.theme-tfs .sheet-grid .rocell.fromjob { color: var(--ink-2); }
.theme-tfs .sheet-grid .rocell.fromjob.unknown { color: var(--muted); }
