/* TradeBuilders NewSystem — the look.

   Navy holds it up, orange makes it go.

   The chrome is the management area's -- navy on a soft grey ground,
   white section cards with a hairline shadow -- so a rep switching
   between the staff screens and a member's view is not switching worlds.
   What is new is one rule for the logo orange (#F87800): it is the
   colour of ACTION and of THE MEMBER'S OWN POSITION -- primary buttons,
   the active tab, the tier marker, the brand mark -- and it is never a
   status colour. Red, amber and green are status and never actions, so a
   red button and an orange warning cannot happen.

   White text on the logo orange fails contrast (2.9:1). It is always a
   fill under dark ink; --tb-orange-ink is orange as text (4.6:1).

   One layout, 1K to 4K: the root font size follows the viewport with a
   clamp, everything is in rem, and wide screens get larger type rather
   than wider tables. Phones get a bottom tab bar instead of a menu. */

:root {
  --tb-orange:      #f87800;
  --tb-orange-ink:  #c25a00;
  --tb-orange-soft: #fff1e4;
  --tb-navy:        #0d47a1;
  --tb-navy-2:      #0a3a85;
  --tb-ground:      #f5f6fa;
  --tb-surface:     #ffffff;
  --tb-sunk:        #eef1f6;
  --tb-ink:         #171b22;
  --tb-muted:       #5f6873;
  --tb-faint:       #8b94a1;
  --tb-line:        #e3e7ee;
  --tb-line-2:      #cfd6e1;
  --tb-hover:       #eef3fb;
  --tb-crit:        #b3261e;
  --tb-warn:        #9a6700;
  --tb-good:        #1b6e46;
  --tb-shadow:      0 1px 4px rgba(16, 24, 40, .08);
  --tb-radius:      .5rem;

  /* Bootstrap hooks, so its own components follow the tokens. */
  --bs-body-bg: var(--tb-ground);
  --bs-body-color: var(--tb-ink);
  --bs-body-font-family: Archivo, "Helvetica Neue", Arial, system-ui, sans-serif;
  --bs-link-color: var(--tb-navy);
  --bs-link-hover-color: var(--tb-navy-2);
  --bs-border-color: var(--tb-line);
  --bs-primary: var(--tb-orange);
  --bs-primary-rgb: 248, 120, 0;
  --bs-danger: var(--tb-crit);
  --bs-warning: var(--tb-warn);
  --bs-success: var(--tb-good);
}

/* 15px at 1024, ~16 at 1366, ~17.7 at 1920, 20 from 2560 up. */
html { font-size: clamp(15px, 0.35vw + 11px, 20px); }
body { background: var(--tb-ground); color: var(--tb-ink); -webkit-font-smoothing: antialiased; }
h1, h2, h3, h4, h5 { letter-spacing: -.01em; }
.num, .tabular { font-variant-numeric: tabular-nums; }

/* --- actions --------------------------------------------------------- */
.btn-primary,
.btn-primary:focus {
  --bs-btn-bg: var(--tb-orange); --bs-btn-border-color: var(--tb-orange); --bs-btn-color: #1b1b1b;
  --bs-btn-hover-bg: #e56f00; --bs-btn-hover-border-color: #e56f00; --bs-btn-hover-color: #1b1b1b;
  --bs-btn-active-bg: #d36600; --bs-btn-active-border-color: #d36600; --bs-btn-active-color: #1b1b1b;
  --bs-btn-disabled-bg: #f9b77a; --bs-btn-disabled-border-color: #f9b77a; --bs-btn-disabled-color: #5a3a1a;
  font-weight: 600;
}
.btn-outline-primary {
  --bs-btn-color: var(--tb-orange-ink); --bs-btn-border-color: var(--tb-line-2);
  --bs-btn-hover-bg: var(--tb-orange-soft); --bs-btn-hover-border-color: var(--tb-orange);
  --bs-btn-hover-color: var(--tb-orange-ink); --bs-btn-active-bg: var(--tb-orange-soft);
  --bs-btn-active-border-color: var(--tb-orange); --bs-btn-active-color: var(--tb-orange-ink);
  font-weight: 600;
}
.btn-outline-secondary { --bs-btn-color: var(--tb-muted); --bs-btn-border-color: var(--tb-line-2); --bs-btn-hover-bg: var(--tb-hover); --bs-btn-hover-color: var(--tb-ink); --bs-btn-hover-border-color: var(--tb-line-2); }
.btn:focus-visible, .form-control:focus, .form-check-input:focus, a:focus-visible, button:focus-visible {
  outline: 2px solid var(--tb-orange); outline-offset: 2px; box-shadow: none;
}
.form-control:focus { border-color: var(--tb-orange); }
.form-check-input:checked { background-color: var(--tb-orange); border-color: var(--tb-orange); }
.form-control { border-color: var(--tb-line-2); }
.form-label { font-size: .8rem; font-weight: 600; margin-bottom: .2rem; }
.form-text  { font-size: .75rem; }

/* --- app chrome ------------------------------------------------------ */
.app-navbar { background: var(--tb-navy); padding-top: .5rem; padding-bottom: .5rem; }
.app-navbar .navbar-brand { color: #fff; font-weight: 600; font-size: 1.02rem; letter-spacing: -.01em; }
.app-navbar .nav-link {
  color: rgba(255,255,255,.82); font-size: .9rem; border-radius: .3rem; padding: .3rem .7rem;
  position: relative;
}
.app-navbar .nav-link:hover { color: #fff; background: rgba(255,255,255,.1); }
.app-navbar .nav-link.is-active { color: #fff; font-weight: 600; background: rgba(255,255,255,.12); }
.app-navbar .nav-link.is-active::after {
  content: ""; position: absolute; left: .7rem; right: .7rem; bottom: .1rem; height: 2px;
  background: var(--tb-orange); border-radius: 1px;
}
.app-navbar .nav-link.is-disabled { color: rgba(255,255,255,.4); pointer-events: none; }
.app-navbar .navbar-toggler { border-color: rgba(255,255,255,.3); }
.app-navbar .btn-outline-light { --bs-btn-border-color: rgba(255,255,255,.35); font-size: .82rem; }

/* The brand mark: an orange square with the logo's own weight, so the
   wordmark can be plain white type. */
.tb-mark {
  width: 1.25rem; height: 1.25rem; border-radius: .3rem; background: var(--tb-orange);
  display: inline-block; flex: 0 0 auto; position: relative;
}
.tb-mark::after {
  content: ""; position: absolute; left: 28%; top: 28%; width: 44%; height: 44%;
  background: var(--tb-navy); border-radius: .1rem;
}
.brand-tag {
  font-size: .62rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  padding: .12rem .4rem; border-radius: .25rem;
  background: rgba(255,255,255,.18); color: #fff; align-self: center;
}

/* Widths: one container that grows with the screen, not three layouts. */
.app-container { max-width: 120rem; margin-inline: auto; padding-inline: 1rem; }
@media (min-width: 1200px) { .app-container { padding-inline: 1.5rem; } }
.app-main { padding-block: 1.25rem 2rem; }

/* --- the breach banner ------------------------------------------------ */
.pwned-banner {
  background: #fff8e6; border: 1px solid #f3e2b8; border-left: 3px solid var(--tb-warn);
  border-radius: .4rem; padding: .6rem .9rem; font-size: .86rem; margin-bottom: 1rem;
  display: flex; gap: .6rem; align-items: flex-start;
}
.pwned-banner i { color: var(--tb-warn); margin-top: .1rem; }

/* --- cards ----------------------------------------------------------- */
.section-card { background: var(--tb-surface); border-radius: var(--tb-radius); box-shadow: var(--tb-shadow); }
.card-heading {
  font-size: .76rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--tb-muted); margin-bottom: .75rem;
}

/* --- entity bar (mirrors the staff screens' record bar) --------------- */
.entity-bar {
  background: var(--tb-navy); color: #fff; padding: .7rem 1rem; border-radius: .375rem;
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
}
.entity-bar a { color: #cfe0ff; }
.entity-name { font-weight: 600; font-size: 1.05rem; line-height: 1.25; }
.entity-sub  { font-size: .82rem; opacity: .85; }
.entity-bar .chip-orange {
  background: var(--tb-orange); color: #1b1b1b; font-weight: 600; font-size: .74rem;
  padding: .15rem .55rem; border-radius: .25rem;
}

/* --- Day Sheet: tiles ------------------------------------------------- */
.tile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: .75rem; }
.tile {
  background: var(--tb-surface); border-radius: var(--tb-radius); padding: .8rem 1rem;
  box-shadow: var(--tb-shadow); border-left: 3px solid transparent;
  text-decoration: none; color: inherit; display: block;
}
a.tile:hover { background: var(--tb-hover); }
/* Only a tile with something in it earns colour. A row of coloured
   zeroes reads as alarm when nothing is wrong. */
.tile.is-live.tile-danger  { border-left-color: var(--tb-crit); }
.tile.is-live.tile-warning { border-left-color: var(--tb-warn); }
.tile.is-live.tile-info    { border-left-color: var(--tb-navy); }
.tile.is-live.tile-success { border-left-color: var(--tb-good); }
.tile-n {
  display: block; font-size: 1.6rem; font-weight: 700; line-height: 1.05;
  font-variant-numeric: tabular-nums; color: var(--tb-ink);
}
.tile:not(.is-live) .tile-n { color: var(--tb-faint); }
.tile-k {
  display: block; margin-top: .2rem; font-size: .68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em; color: var(--tb-muted);
}

/* --- the tier bar ------------------------------------------------------ */
/* The member's own position: the one place orange is used as a mark
   rather than a button. Thresholds come from ref_list_items. */
.tier-card { padding: .9rem 1rem 1.9rem; }
.tier-head { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; }
.tier-head .card-heading { margin-bottom: 0; }
.tier-now { font-weight: 700; color: var(--tb-orange-ink); font-size: .9rem; }
.tier-hint { margin-left: auto; font-size: .78rem; color: var(--tb-muted); }
.tierbar { position: relative; height: .6rem; border-radius: .3rem; background: var(--tb-sunk); margin: 1.5rem .4rem 0; }
.tierbar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: .3rem; background: var(--tb-orange); }
.tierbar .mark { position: absolute; top: -.35rem; width: 2px; height: 1.3rem; background: var(--tb-line-2); }
.tierbar .mark.hit { background: var(--tb-orange-ink); }
.tierbar .mark em {
  position: absolute; top: 1.45rem; left: 50%; transform: translateX(-50%);
  font-style: normal; font-size: .68rem; color: var(--tb-muted); white-space: nowrap; text-align: center; line-height: 1.25;
}
.tierbar .mark em b { display: block; color: var(--tb-ink); font-size: .74rem; }
.tierbar .mark:first-of-type em { transform: none; left: 0; text-align: left; }
.tierbar .mark:last-of-type  em { transform: none; left: auto; right: 0; text-align: right; }
.tierbar .you { position: absolute; top: -.15rem; width: 0; }
.tierbar .you i {
  display: block; width: .9rem; height: .9rem; border-radius: 50%;
  background: #fff; border: 3px solid var(--tb-orange); margin-left: -.45rem;
}
.tierbar .you em {
  position: absolute; bottom: 1.15rem; left: 50%; transform: translateX(-50%);
  font-style: normal; font-variant-numeric: tabular-nums; font-size: .72rem; font-weight: 700;
  color: var(--tb-orange-ink); white-space: nowrap; background: var(--tb-surface);
  padding: .05rem .4rem; border-radius: .2rem; border: 1px solid var(--tb-line-2);
}

/* --- Day Sheet: the queue ----------------------------------------------- */
.queue { display: flex; flex-direction: column; }
.queue-row {
  display: flex; align-items: center; gap: .75rem;
  padding: .7rem 1rem .7rem 0; border-top: 1px solid var(--tb-line);
  text-decoration: none; color: inherit; position: relative;
}
.queue-row:first-child { border-top: none; }
.queue-row:hover { background: var(--tb-hover); }
.queue-stripe { width: 3px; align-self: stretch; border-radius: 0 2px 2px 0; flex: 0 0 3px; }
.queue-main { flex: 1; min-width: 0; }
.queue-title { display: block; font-weight: 600; font-size: .92rem; }
.queue-sub {
  display: block; font-size: .8rem; color: var(--tb-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.queue-meta { display: flex; flex-direction: column; align-items: flex-end; gap: .2rem; flex: 0 0 auto; }
.queue-when { font-size: .72rem; color: var(--tb-muted); font-variant-numeric: tabular-nums; }
.badge.text-bg-light { background: var(--tb-sunk) !important; color: var(--tb-muted) !important; font-weight: 600; }

/* --- Job Desk shell --------------------------------------------------- */
.desk { display: grid; grid-template-columns: 17rem minmax(0, 1fr); gap: 1rem; align-items: start; }
.desk-main { min-width: 0; }
@media (min-width: 1600px) { .desk { grid-template-columns: 18rem minmax(0, 1fr); } }

.rail { background: var(--tb-surface); border-radius: var(--tb-radius); box-shadow: var(--tb-shadow); overflow: hidden; }
.rail-search { padding: .6rem; border-bottom: 1px solid var(--tb-line); }
.rail-list { max-height: 62vh; overflow-y: auto; }
.rail-item {
  display: flex; align-items: center; gap: .55rem; padding: .55rem .75rem;
  border-bottom: 1px solid var(--tb-line); text-decoration: none; color: inherit;
}
.rail-item:last-child { border-bottom: none; }
.rail-item:hover { background: var(--tb-hover); }
.rail-item.is-active { background: var(--tb-hover); box-shadow: inset 3px 0 0 var(--tb-orange); }
.rail-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; background: var(--tb-line-2); }
.rail-dot.is-crit { background: var(--tb-crit); }
.rail-dot.is-live { background: var(--tb-navy); }
.rail-body { flex: 1; min-width: 0; }
.rail-name { display: block; font-size: .88rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-sub  { display: block; font-size: .75rem; color: var(--tb-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-badge { flex: 0 0 auto; font-size: .68rem; }
.rail-new {
  display: block; padding: .6rem .75rem; border-top: 1px solid var(--tb-line);
  font-size: .82rem; font-weight: 600; text-decoration: none; color: var(--tb-orange-ink);
}
.rail-new:hover { background: var(--tb-orange-soft); }

.desk-tabs { border-bottom: 1px solid var(--tb-line-2); }
.desk-tabs .nav-link { font-size: .88rem; font-weight: 600; color: var(--tb-muted); border: none; padding: .5rem .9rem; }
.desk-tabs .nav-link:hover { color: var(--tb-ink); }
.desk-tabs .nav-link.active { color: var(--tb-ink); background: transparent; box-shadow: inset 0 -2px 0 var(--tb-orange); }
.tab-body { margin-top: 1rem; }

/* --- tables ------------------------------------------------------------ */
.tb-table th {
  white-space: nowrap; font-size: .7rem; text-transform: uppercase;
  letter-spacing: .05em; color: var(--tb-muted); font-weight: 700; border-bottom-color: var(--tb-line-2);
}
.tb-table td { font-size: .875rem; vertical-align: middle; }
.tb-table tbody tr:hover { background: var(--tb-hover); }
.tb-table a.row-link { text-decoration: none; font-weight: 600; color: var(--tb-ink); }
.tb-table a.row-link:hover { color: var(--tb-navy); }
.tb-table .sub { display: block; font-size: .76rem; color: var(--tb-muted); }

/* --- lead thread -------------------------------------------------------- */
.msg { border-radius: .4rem; padding: .6rem .8rem; margin-bottom: .6rem; border: 1px solid var(--tb-line); }
.msg-head { font-size: .75rem; font-weight: 700; color: var(--tb-muted); display: flex; gap: .5rem; }
.msg-when { margin-left: auto; font-weight: 400; }
.msg-body { font-size: .9rem; margin-top: .25rem; }
.msg-me   { background: var(--tb-hover); border-color: #cfe0ff; }
.msg-them { background: var(--tb-surface); }
.msg-tb   { background: #fff8e6; border-color: #f3e2b8; }
.msg-sys  { background: var(--tb-sunk); }
.msg-note { font-size: .78rem; color: var(--tb-muted); padding: .35rem 0 .35rem .8rem; border-left: 2px solid var(--tb-line); margin-bottom: .6rem; }

/* --- misc --------------------------------------------------------------- */
.kv { display: grid; grid-template-columns: auto 1fr; gap: .3rem .8rem; font-size: .85rem; }
.kv dt { color: var(--tb-muted); font-weight: 600; white-space: nowrap; }
.kv dd { margin: 0; }

.chip {
  display: inline-flex; align-items: center; gap: .4rem; padding: .25rem .6rem;
  background: var(--tb-surface); border: 1px solid var(--tb-line); border-radius: 1rem;
  font-size: .8rem; text-decoration: none; color: inherit;
}
.chip:hover { background: var(--tb-hover); border-color: #cfe0ff; }
.chip-n { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--tb-navy); }
.chip.is-on { background: var(--tb-navy); border-color: var(--tb-navy); color: #fff; font-weight: 600; }
.chip.is-on:hover { background: var(--tb-navy-2); border-color: var(--tb-navy-2); }
.chip.is-on .chip-n { color: #fff; }

.stage-preview { display: flex; flex-wrap: wrap; gap: .4rem; }
.stage-chip { padding: .3rem .7rem; background: var(--tb-ground); border: 1px dashed var(--tb-line-2); border-radius: .3rem; font-size: .8rem; color: var(--tb-muted); }
.back-link { display: inline-block; font-size: .82rem; text-decoration: none; margin-bottom: .6rem; }

/* --- sign in ------------------------------------------------------------- */
/* A split page: navy on the left carrying the mark and three lines of
   what the network is for; the form on the right. Stacks on a phone. */
.plain-body { background: var(--tb-ground); min-height: 100vh; margin: 0; }
.plain-main { min-height: 100vh; display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
.login-side {
  background: var(--tb-navy); color: #fff; padding: clamp(2rem, 6vw, 5rem) clamp(1.25rem, 5vw, 4.5rem);
  display: flex; flex-direction: column; justify-content: space-between; gap: 3rem;
  position: relative; overflow: hidden;
}
.login-side::before {
  /* the orange band from the membership card, at the edge */
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: .45rem; background: var(--tb-orange);
}
.login-logo { display: flex; align-items: center; gap: .7rem; }
.login-logo img { height: 2.2rem; width: auto; display: block; }
.login-logo .tb-word { font-weight: 700; font-size: 1.25rem; letter-spacing: -.01em; }
.login-side h1 { font-size: clamp(1.7rem, 3.2vw, 2.6rem); font-weight: 700; line-height: 1.12; letter-spacing: -.02em; margin: 0 0 1rem; text-wrap: balance; }
.login-side p.lead-line { font-size: 1.02rem; opacity: .88; max-width: 34ch; margin: 0; }
.login-points { list-style: none; padding: 0; margin: 1.6rem 0 0; display: grid; gap: .8rem; max-width: 36ch; }
.login-points li { display: flex; gap: .7rem; align-items: flex-start; font-size: .92rem; opacity: .92; }
.login-points li::before { content: ""; flex: 0 0 .55rem; height: .55rem; margin-top: .45rem; border-radius: 50%; background: var(--tb-orange); }
.login-foot { font-size: .78rem; opacity: .7; }
.login-form-wrap { display: flex; align-items: center; justify-content: center; padding: clamp(1.5rem, 5vw, 4rem) 1rem; }
.login-card { width: 100%; max-width: 26rem; padding: 1.75rem 1.75rem 1.5rem; }
.login-card h2 { font-size: 1.25rem; font-weight: 700; margin: 0 0 .25rem; }
.login-card .login-sub { color: var(--tb-muted); font-size: .88rem; margin-bottom: 1.25rem; }
.login-card .form-control { padding: .6rem .75rem; }
.login-card .btn-primary { padding: .65rem; font-size: .98rem; }
.login-remember { display: flex; align-items: center; gap: .5rem; font-size: .86rem; margin: .75rem 0 1rem; }
.login-remember .form-check-input { margin: 0; }
.login-remember small { display: block; color: var(--tb-muted); font-size: .76rem; }
.login-other { font-size: .8rem; color: var(--tb-muted); text-align: center; margin: 1.1rem 0 0; }
.login-other a { color: var(--tb-orange-ink); }
.brand-mark {
  width: 3.2rem; height: 3.2rem; margin: 0 auto; border-radius: .7rem;
  background: var(--tb-navy); color: #fff; display: flex; align-items: center;
  justify-content: center; font-size: 1.5rem; position: relative;
}
.brand-mark::after { content: ""; position: absolute; left: -.25rem; top: 20%; bottom: 20%; width: .3rem; border-radius: 2px; background: var(--tb-orange); }

/* --- role chooser ----------------------------------------------------------- */
.role-picker { display: flex; flex-direction: column; gap: .6rem; }
.role-option {
  display: flex; align-items: center; gap: .9rem; width: 100%; text-align: left;
  padding: .85rem 1rem; border: 1px solid var(--tb-line); border-radius: .5rem;
  background: var(--tb-surface); color: inherit; cursor: pointer;
}
.role-option:hover  { background: var(--tb-orange-soft); border-color: var(--tb-orange); }
.role-icon { font-size: 1.4rem; color: var(--tb-navy); flex: 0 0 auto; }
.role-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.role-what { font-weight: 600; font-size: .95rem; }
.role-who  { font-size: .82rem; color: var(--tb-muted); }
.role-no   { font-size: .72rem; color: var(--tb-faint); }
.role-go   { color: var(--tb-faint); flex: 0 0 auto; }

/* --- the supplier side ------------------------------------------------------ */
/* Same product, same chrome; the bar colour is the one visual difference,
   and it earns its place -- a rep with both areas open in two tabs needs
   to know which one they are typing a reply into. Teal, not a second
   navy: two navies are the same navy across a room. */
:root { --tb-supplier: #00695c; --tb-supplier-2: #00564b; }
.app-navbar--supplier { background: var(--tb-supplier); }
body:has(.app-navbar--supplier) .entity-bar { background: var(--tb-supplier); }
body:has(.app-navbar--supplier) .chip-n { color: var(--tb-supplier); }
body:has(.app-navbar--supplier) .chip.is-on { background: var(--tb-supplier); border-color: var(--tb-supplier); }
body:has(.app-navbar--supplier) .chip.is-on:hover { background: var(--tb-supplier-2); border-color: var(--tb-supplier-2); }
body:has(.app-navbar--supplier) .chip.is-on .chip-n { color: #fff; }
body:has(.app-navbar--supplier) .login-side { background: var(--tb-supplier); }

/* --- phone: bottom tab bar ----------------------------------------------------- */
.tabbar {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 1030;
  background: var(--tb-surface); border-top: 1px solid var(--tb-line-2);
  padding: .35rem .25rem calc(.5rem + env(safe-area-inset-bottom, 0px));
  grid-template-columns: repeat(5, 1fr);
}
.tabbar a {
  display: flex; flex-direction: column; align-items: center; gap: .15rem;
  font-size: .66rem; font-weight: 600; color: var(--tb-muted); text-decoration: none; padding: .2rem 0;
}
.tabbar a i { font-size: 1.25rem; line-height: 1; }
.tabbar a.is-active { color: var(--tb-orange-ink); }
.tabbar a.is-disabled { opacity: .4; pointer-events: none; }

@media (max-width: 767.98px) {
  .tabbar { display: grid; }
  body:has(.tabbar) .app-main { padding-bottom: 5rem; }
  .app-navbar .navbar-collapse-links { display: none; }
  .plain-main { grid-template-columns: 1fr; min-height: auto; }
  .login-side { padding: 1.5rem 1.25rem; gap: 1.25rem; }
  .login-side h1 { font-size: 1.5rem; }
  .login-points { display: none; }
  .login-card { box-shadow: none; padding: 1.25rem .25rem; }
}
@media (max-width: 991.98px) {
  .desk { grid-template-columns: 1fr; }
  .rail-list { max-height: 240px; }
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* --- P9: card ------------------------------------------------------------------
   The digital membership card, and the public page a scan of it lands on.

   Built at 400px and allowed to grow, not shrunk down from a desktop
   layout: this is opened one-handed at a trade counter and turned round
   to be shown to somebody. So the card is a fixed aspect panel that caps
   at 26rem and centres, and the two things read across a counter -- the
   member number and the verdict -- are the biggest type on either page.

   Navy is the card because navy is structure here, and a membership card
   IS the structure. The orange band is the member's own mark, a fill
   under nothing, which is the only way #F87800 is allowed to be used at
   this size. The QR sits on white with its full four-module quiet zone;
   a QR tinted to match the card, or bled into the navy, does not scan. */

.mcard-wrap { max-width: 26rem; margin-inline: auto; }

.mcard {
  background: linear-gradient(160deg, var(--tb-navy) 0%, var(--tb-navy-2) 100%);
  color: #fff;
  border-radius: .9rem;
  overflow: hidden;
  box-shadow: 0 6px 20px rgba(13, 71, 161, .28);
}

.mcard-top {
  display: flex; align-items: center; gap: .5rem;
  padding: .9rem 1.1rem .75rem;
}
.mcard-mark {
  width: 1.1rem; height: 1.1rem; border-radius: .28rem;
  background: var(--tb-orange); flex: none;
}
.mcard-brand { font-weight: 700; letter-spacing: -.01em; }
.mcard-tier {
  margin-left: auto;
  background: var(--tb-orange); color: #1b1b1b;   /* dark ink on orange, never white */
  font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: .2rem .55rem; border-radius: 999px;
}

/* The band. Four pixels of the member's own colour, edge to edge. */
.mcard-band { height: .3rem; background: var(--tb-orange); }

.mcard-body { padding: 1.1rem 1.1rem 1.25rem; }

.mcard-label {
  font-size: .68rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: rgba(255, 255, 255, .62);
}
.mcard-business {
  font-size: 1.25rem; font-weight: 700; line-height: 1.2;
  margin: .15rem 0 1rem; overflow-wrap: anywhere;
}

.mcard-no-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem; margin-bottom: 1.1rem;
}
.mcard-no {
  font-size: 2.1rem; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.mcard-valid { text-align: right; }
.mcard-date { font-weight: 600; font-size: .95rem; margin-top: .25rem; }

/* Lapsed: amber, because a lapsed card is a renewal conversation and not
   an error. The card stays navy -- recolouring the whole thing red would
   make a member think their account had been cancelled. */
.mcard.is-lapsed .mcard-date { color: #ffcf6b; }
.mcard.is-lapsed .mcard-band { background: #e9a23b; }

.mcard-qr {
  background: #fff; border-radius: .6rem;
  padding: .55rem; width: 12.5rem; max-width: 100%;
  margin: 0 auto;
}
.mcard-qr-svg { display: block; width: 100%; height: auto; }
.mcard-qr-missing {
  color: var(--tb-ink); font-size: .8rem; text-align: center; padding: 2rem .5rem;
}
.mcard-scan {
  text-align: center; margin-top: .6rem;
  font-size: .78rem; color: rgba(255, 255, 255, .72);
}

.mcard-notes { margin-top: 1.1rem; }
.mcard-notes p { margin-bottom: .6rem; }
.mcard-warn {
  background: #fff8e6; border: 1px solid #f3e2b8; border-left: 3px solid var(--tb-warn);
  border-radius: var(--tb-radius); padding: .6rem .8rem; font-size: .9rem;
}
.mcard-warn i { color: var(--tb-warn); margin-right: .35rem; }

/* --- the public verify page ---------------------------------------------------
   One card on the bare layout. The verdict is a WORD and an icon as well
   as a colour: red/green colour blindness is one man in twelve, and this
   is read by a stranger in a hurry. Green, amber and grey are status
   colours here and that is exactly what they are for. */

.vcheck-wrap {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 1.5rem 1rem;
}
.vcheck {
  background: var(--tb-surface); border-radius: var(--tb-radius);
  box-shadow: var(--tb-shadow); border-top: .35rem solid var(--tb-faint);
  width: 100%; max-width: 24rem; padding: 1.5rem 1.35rem 1.1rem; text-align: center;
}
.vcheck-verdict {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  font-size: 2rem; font-weight: 700; line-height: 1.1; letter-spacing: -.02em;
}
.vcheck-verdict i { font-size: 1.7rem; }
.vcheck-line { color: var(--tb-muted); font-size: .92rem; margin: .5rem 0 1.1rem; }

.vcheck--valid   { border-top-color: var(--tb-good); }
.vcheck--valid   .vcheck-verdict { color: var(--tb-good); }
.vcheck--lapsed  { border-top-color: var(--tb-warn); }
.vcheck--lapsed  .vcheck-verdict { color: var(--tb-warn); }
.vcheck--unknown { border-top-color: var(--tb-line-2); }
.vcheck--unknown .vcheck-verdict { color: var(--tb-muted); }

.vcheck-facts {
  margin: 0; padding-top: 1rem; border-top: 1px solid var(--tb-line);
  text-align: left;
}
.vcheck-facts dt {
  font-size: .68rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--tb-faint);
}
.vcheck-facts dd { margin: .1rem 0 .8rem; font-weight: 600; }
.vcheck-business { font-size: 1.15rem; line-height: 1.25; overflow-wrap: anywhere; }
.vcheck-no { font-size: 1.35rem; font-variant-numeric: tabular-nums; }

.vcheck-notice {
  background: var(--tb-sunk); border-radius: var(--tb-radius);
  padding: .55rem .7rem; font-size: .85rem; color: var(--tb-muted);
}
.vcheck-foot {
  margin: .6rem 0 0; font-size: .72rem; color: var(--tb-faint);
}

/* Anything narrower than a 2016 phone: give the number the whole row
   rather than letting it and the expiry squeeze each other. */
@media (max-width: 359.98px) {
  .mcard-no-row { flex-direction: column; align-items: flex-start; gap: .75rem; }
  .mcard-valid { text-align: left; }
  .mcard-no { font-size: 1.9rem; }
}
/* --- P6-P8 --- */
/* The Ledger and the tier benefit columns. Tiles, tables, chips and the
   tier bar itself are the classes that were already here; these are the
   two things that had none -- the three benefit columns under the bar,
   and the money figure in a tile, which is wider than a count. */
.tier-benefits { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: .75rem; margin-top: .6rem; }
.tier-col { border: 1px solid var(--tb-line); border-radius: var(--tb-radius); padding: .7rem .8rem; }
.tier-col.is-mine { border-color: var(--tb-navy); box-shadow: inset 0 0 0 1px var(--tb-navy); }
.tier-col-name { font-weight: 700; font-size: .92rem; display: flex; align-items: center; gap: .4rem; }
.tier-you { background: var(--tb-orange); color: #1b1b1b; font-size: .68rem; font-weight: 700; padding: .05rem .35rem; border-radius: .2rem; }
.tier-col-when { font-size: .74rem; color: var(--tb-muted); margin: .1rem 0 .45rem; font-variant-numeric: tabular-nums; }
.tier-col ul { margin: 0; padding-left: .9rem; }
.tier-col li { font-size: .78rem; line-height: 1.35; margin-bottom: .25rem; }
.ledger-head { border-bottom: 1px solid var(--tb-line); }
.tile .tile-n { font-size: clamp(1.1rem, 2.4vw, 1.5rem); }

/* --- P14 --- */
/* The one line the notifications control says back. It lives inside the
   account dropdown, so it must wrap rather than widen the menu, and it is
   muted: nothing here is a status the member has to act on. */
.push-note { padding: .1rem 1rem .5rem; font-size: .76rem; line-height: 1.3;
             color: var(--tb-muted); max-width: 16rem; white-space: normal; }
/* --- P10-P11: bill --------------------------------------------------------------
   The bill of work on the Job Desk: the template picker on an empty job,
   then the lines with their four money columns.

   BUILT AT 400px FIRST. A bill has six columns of which four are money,
   and four money columns will not fit beside a description on a phone.
   So under 576px the table stops being a table: each row becomes a block
   and each cell a labelled line under the description, with the label
   coming out of data-label rather than a second copy of the markup. The
   header row is hidden there, not duplicated.

   Colour: nothing here is orange except the Add line button and the save
   tick on a budget cell, which are the two actions. The money columns
   are ink, never red or green -- a budget that is smaller than the
   actual is a fact for the variance column (P13) to have an opinion
   about, not something a bill of work should shout at somebody while
   they are typing. */

.tpl-list { display: flex; flex-direction: column; gap: .5rem; }
.tpl {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  border: 1px solid var(--tb-line); border-radius: var(--tb-radius); padding: .7rem .85rem;
}
.tpl:hover { border-color: var(--tb-line-2); background: var(--tb-hover); }
.tpl-main { flex: 1; min-width: 12rem; }
.tpl-name { font-weight: 600; font-size: .95rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.tpl-sub  { font-size: .82rem; color: var(--tb-muted); }
.tpl-meta { font-size: .74rem; color: var(--tb-faint); font-variant-numeric: tabular-nums; margin-top: .15rem; }
.tpl-go   { flex: 0 0 auto; }

/* The toolbar. The three actions, then the two numbers that matter. */
.bill-bar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.bill-bar-tot { display: flex; align-items: baseline; gap: .4rem; font-variant-numeric: tabular-nums; }
.bill-tot-k { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint); }
.bill-tot-v { font-weight: 700; font-size: .95rem; margin-right: .4rem; }

/* <details> used as a button that opens a small form. The marker is
   dropped so the summary can be a .btn without a stray triangle. */
.bill-details > summary { list-style: none; cursor: pointer; }
.bill-details > summary::-webkit-details-marker { display: none; }
.bill-details-inline { position: relative; }
.bill-name-form {
  position: absolute; z-index: 20; top: calc(100% + .35rem); left: 0; width: min(22rem, 90vw);
  background: var(--tb-surface); border: 1px solid var(--tb-line-2);
  border-radius: var(--tb-radius); box-shadow: var(--tb-shadow); padding: .75rem;
}

.bill-stage-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap; padding: .75rem 1rem .25rem;
}
.bill-stage-tot { display: flex; gap: .9rem; font-size: .82rem; font-variant-numeric: tabular-nums; }
.bill-stage-tot .k { color: var(--tb-faint); font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; }

.bill-table th { white-space: nowrap; }
.bill-table td { vertical-align: middle; }
.bill-table tfoot th { background: var(--tb-sunk); font-size: .8rem; }
.bill-desc { font-weight: 600; }
.bill-group .bill-desc { font-weight: 700; }
.bill-group > td { background: var(--tb-sunk); }
.bill-child > td:first-child { padding-left: 1.6rem; }

/* The editable cell. The tick is quiet until the row is being used. */
.bill-budget { display: flex; align-items: center; gap: .25rem; justify-content: flex-end; }
.bill-budget .form-control { width: 6.5rem; }
.bill-save {
  border: none; background: none; padding: .1rem .2rem; line-height: 1;
  color: var(--tb-faint); opacity: .35; cursor: pointer;
}
.bill-table tr:hover .bill-save, .bill-budget:focus-within .bill-save { opacity: 1; color: var(--tb-orange-ink); }

.bill-acts { white-space: nowrap; text-align: right; }
.bill-acts-h { width: 7rem; }
.bill-act {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border: none; background: none; padding: 0;
  color: var(--tb-muted); text-decoration: none; border-radius: .25rem;
}
.bill-act:hover { background: var(--tb-hover); color: var(--tb-ink); }
.bill-act-del:hover { color: var(--tb-crit); }

/* The edit row. Rendered only for ?edit={id}, so it is simply there. */
.bill-edit > td { background: var(--tb-hover); }
.bill-act.is-on { background: var(--tb-orange-soft); color: var(--tb-orange-ink); }
.bill-cancel { display: inline-block; font-size: .78rem; margin-top: .4rem; }

.bill-form-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem .6rem; align-items: end; }
.bill-f { display: block; margin: 0; }
.bill-f-stage  { grid-column: span 3; }
.bill-f-desc   { grid-column: span 9; }
.bill-f-qty    { grid-column: span 2; }
.bill-f-unit   { grid-column: span 2; }
.bill-f-cat    { grid-column: span 4; }
.bill-f-budget { grid-column: span 2; }
.bill-f-go     { grid-column: span 2; }

.bill-job-total {
  display: flex; align-items: baseline; gap: 1.25rem; flex-wrap: wrap;
  font-variant-numeric: tabular-nums;
}
.bill-tot-name { font-weight: 700; margin-right: auto; }
.bill-tot-cell { display: flex; flex-direction: column; }
.bill-tot-cell .k { font-size: .68rem; text-transform: uppercase; letter-spacing: .07em; color: var(--tb-faint); }
.bill-tot-cell b { font-size: 1.05rem; }

@media (max-width: 767.98px) {
  .bill-form-grid { grid-template-columns: repeat(6, 1fr); }
  .bill-f-stage  { grid-column: span 6; }
  .bill-f-desc   { grid-column: span 6; }
  .bill-f-qty    { grid-column: span 2; }
  .bill-f-unit   { grid-column: span 2; }
  .bill-f-budget { grid-column: span 2; }
  .bill-f-cat    { grid-column: span 4; }
  .bill-f-go     { grid-column: span 2; }
}

/* 400px: the table becomes a list of rows, each money column a labelled
   line under the description. */
@media (max-width: 575.98px) {
  .bill-table thead { display: none; }
  .bill-table, .bill-table tbody, .bill-table tfoot, .bill-table tr, .bill-table td, .bill-table th { display: block; width: auto; }
  .bill-table tr { border-bottom: 1px solid var(--tb-line); padding: .5rem .25rem; }
  .bill-table td { border: none; padding: .15rem .5rem; text-align: left !important; }
  .bill-table td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    display: inline-block; min-width: 5.5rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint);
  }
  .bill-table td[data-label="Line"]::before { display: none; }
  .bill-budget { justify-content: flex-start; }
  .bill-save { opacity: 1; }
  .bill-acts { text-align: left; padding-top: .35rem !important; }
  .bill-child > td:first-child { padding-left: 1.25rem; }
  .bill-table tfoot tr { background: var(--tb-sunk); }
  .bill-table tfoot th { padding: .15rem .5rem; }
  .bill-stage-tot { gap: .75rem; }
}

/* --- P15: contacts and the People tab -------------------------------------
   The address book, and who is on a job.

   ONE NEW THING ONLY: the kind chip. Everything else here is
   .section-card, .tb-table, .chip and .btn-primary as they already are,
   because a contact list that looked different from the Ledger would be
   a second application.

   The kind chip is a LABEL, not a status and not an action, so it is
   never orange and never red/amber/green: it is navy, grey or the
   muted ink already in the tokens. Orange on this page is the
   "Add a contact" button and nothing else.

   BUILT AT 400px. Both tables lose their header under 576px and each
   row becomes a block with its cells labelled from data-label -- the
   same mechanism the bill uses, and for the same reason: five columns
   of contact details do not fit beside a name on a phone, and a phone
   is where somebody looks up the certifier's number. */

.chip-kind {
  display: inline-block; padding: .1rem .45rem; border-radius: .25rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  border: 1px solid var(--tb-line-2); color: var(--tb-muted); background: var(--tb-ground);
  white-space: nowrap;
}
.chip-kind.is-client  { border-color: #b9cdf0; color: var(--tb-navy);   background: var(--tb-hover); }
.chip-kind.is-trade   { border-color: var(--tb-line-2); color: var(--tb-ink); background: var(--tb-sunk); }
.chip-kind.is-council { border-color: #d5cdbe; color: #6b5a3a;          background: #faf6ee; }

.contacts-head { border-bottom: 1px solid var(--tb-line); }
.contacts-search { min-width: 14rem; max-width: 22rem; flex: 1 1 14rem; }
.contacts-table td { vertical-align: top; }
.contacts-table .sub { margin-top: .1rem; }

/* The kind picker on the form: four radios that read as buttons. The
   underlying <input> stays in the DOM and keeps the keyboard and the
   screen reader working -- it is moved out of sight, not display:none. */
.kind-picker { display: flex; gap: .4rem; flex-wrap: wrap; }
.kind-opt { margin: 0; }
.kind-opt input {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.kind-opt span {
  display: inline-block; padding: .35rem .8rem; border-radius: .3rem; cursor: pointer;
  border: 1px solid var(--tb-line-2); background: var(--tb-surface);
  font-size: .85rem; font-weight: 600; color: var(--tb-muted);
}
.kind-opt span:hover { background: var(--tb-hover); }
.kind-opt input:checked + span { background: var(--tb-navy); border-color: var(--tb-navy); color: #fff; }
.kind-opt input:focus-visible + span { outline: 2px solid var(--tb-orange); outline-offset: 2px; }

/* The contact's details, as a two-column list that collapses to one. */
.contact-dl { display: grid; grid-template-columns: 7rem 1fr; gap: .25rem .75rem; margin: 0; }
.contact-dl dt { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
                 color: var(--tb-faint); font-weight: 700; align-self: center; }
.contact-dl dd { margin: 0; font-size: .9rem; }

/* --- the People tab --- */
.people-head { border-bottom: 1px solid var(--tb-line); }
.people-table td { vertical-align: top; }
.people-table .sub { margin-top: .15rem; }
.people-acts   { white-space: nowrap; text-align: right; }
.people-acts-h { width: 3rem; }

.people-form-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem .6rem; align-items: end; }
.people-f { display: block; margin: 0; }
.people-f-who  { grid-column: span 4; }
.people-f-role { grid-column: span 3; }
.people-f-note { grid-column: span 3; }
.people-f-go   { grid-column: span 2; }

/* <details> as a quiet second option, not a second form. */
.people-new > summary { cursor: pointer; font-size: .82rem; color: var(--tb-navy); }
.people-new > summary:hover { color: var(--tb-navy-2); }

@media (max-width: 991.98px) {
  .people-f-who  { grid-column: span 6; }
  .people-f-role { grid-column: span 6; }
  .people-f-note { grid-column: span 8; }
  .people-f-go   { grid-column: span 4; }
}

@media (max-width: 575.98px) {
  .people-form-grid { grid-template-columns: repeat(4, 1fr); }
  .people-f-who, .people-f-role, .people-f-note, .people-f-go { grid-column: span 4; }

  .contacts-search { max-width: none; }
  .contact-dl { grid-template-columns: 5.5rem 1fr; }

  .contacts-table thead, .people-table thead { display: none; }
  .contacts-table, .contacts-table tbody, .contacts-table tr, .contacts-table td,
  .people-table, .people-table tbody, .people-table tr, .people-table td { display: block; width: auto; }
  .contacts-table tr, .people-table tr { border-bottom: 1px solid var(--tb-line); padding: .5rem .25rem; }
  .contacts-table td, .people-table td { border: none; padding: .15rem .5rem; text-align: left !important; }
  .contacts-table td[data-label]:not([data-label=""])::before,
  .people-table   td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    display: inline-block; min-width: 4.5rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint);
  }
  .contacts-table td[data-label="Name"]::before,
  .contacts-table td[data-label="Job"]::before,
  .people-table   td[data-label="Who"]::before { display: none; }
  .people-acts { text-align: left; padding-top: .35rem !important; }
}

/* --- P18: scorecard ---------------------------------------------------------
   The supplier's own report card. The tiles, the table and the badges are
   the classes that were already here; what had none is the two lines that
   turn a figure into something a rep can act on -- the comparison and the
   sentence under it.

   So .tile grows a body. A scorecard tile is not a Day Sheet tile: the
   Day Sheet's job is to be counted at a glance, this one's is to be
   understood once. The wider minimum column keeps the sentence from
   setting two words to a line, and the tiles stack on a phone rather than
   shrinking.

   Colour: green and red only where a figure is plainly good or plainly
   not, from the same --tb-good / --tb-crit the buckets use. Nothing on
   this page is orange -- orange is the action colour and there is no
   action here. */

.sc-tiles { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); align-items: stretch; }
.sc-tiles .tile { display: flex; flex-direction: column; }
.sc-tiles .tile-n { font-size: 1.9rem; }

/* The comparison. Tabular figures so "22h" under one tile lines up with
   "3.5 days" under the next, and never wider than the sentence below it. */
.sc-cmp {
  display: block; margin-top: .35rem;
  font-size: .78rem; font-weight: 600; color: var(--tb-ink);
  font-variant-numeric: tabular-nums;
}

/* What moves it. Quiet, and it wraps -- it is prose, not a label. */
.sc-why {
  display: block; margin-top: .45rem;
  font-size: .76rem; line-height: 1.4; color: var(--tb-muted);
}

/* An aside about the figures rather than a status about the business, so
   it is a rule and a sentence, not a coloured banner. */
.sc-note {
  font-size: .8rem; color: var(--tb-muted);
  border-left: 2px solid var(--tb-line); padding: .1rem 0 .1rem .7rem;
}

.sc-table td { vertical-align: top; }
.sc-table .badge { font-weight: 600; }

/* 576px: the same treatment the bill gets. Five columns of which two are
   dates and one is money will not sit beside a member's name on a phone,
   so each row becomes a block and each cell a labelled line. The labels
   come out of data-label rather than a second copy of the markup. */
@media (max-width: 575.98px) {
  .sc-table thead { display: none; }
  .sc-table, .sc-table tbody, .sc-table tr, .sc-table td { display: block; width: auto; }
  .sc-table tr { border-bottom: 1px solid var(--tb-line); padding: .5rem .25rem; }
  .sc-table td { border: none; padding: .15rem .5rem; text-align: left !important; }
  .sc-table td[data-label]:not([data-label="Member"])::before {
    content: attr(data-label);
    display: inline-block; min-width: 6rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint);
  }
  .sc-table .sub { display: inline; margin-left: .35rem; }
  .sc-table td[data-label="Member"] .sub { display: block; margin-left: 0; }
}

/* --- P12: request prices ---------------------------------------------------
   The tick column on the bill, the "N asked" chip on a line that is out
   with somebody, and the picker page.

   The picker is a form, not a table of suppliers: each row is a large
   label wrapping its own checkbox, so the whole row is the hit target on
   a phone and the reasons -- categories matched, service committed --
   sit under the name where they are read rather than in columns that
   would wrap into nonsense at 400px.

   Colour: the chip is navy-on-tint, not orange and not amber. It is a
   fact about the line ("this is out with two suppliers"), not an action
   and not a warning. Orange stays on Send. */

.bill-tick-h { width: 2.2rem; }
.bill-tick   { width: 2.2rem; text-align: center; }
.bill-tick .form-check-input { margin: 0; cursor: pointer; }

.chip-asked {
  display: inline-block; margin-top: .15rem;
  font-size: .7rem; line-height: 1.5; text-decoration: none;
  padding: 0 .4rem; border-radius: 1rem;
  background: var(--tb-hover); color: var(--tb-navy);
  border: 1px solid var(--tb-line-2);
}
.chip-asked:hover { background: var(--tb-sunk); color: var(--tb-navy-2); }

/* The lines table, shared by the picker and both lead pages. */
.req-lines td, .req-lines th { vertical-align: middle; }
.req-lines .sub { display: block; }
.req-price { color: var(--tb-faint); }

.req-why { font-size: .85rem; color: var(--tb-muted); max-width: 70ch; }
.req-sub-head {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--tb-faint); margin: .9rem 0 .35rem;
}

.req-sup {
  display: flex; align-items: flex-start; gap: .7rem;
  border: 1px solid var(--tb-line); border-radius: var(--tb-radius);
  padding: .65rem .8rem; margin-bottom: .5rem; cursor: pointer;
}
.req-sup:hover { border-color: var(--tb-line-2); background: var(--tb-hover); }
.req-sup:focus-within { border-color: var(--tb-orange); }
.req-sup .form-check-input { margin-top: .15rem; flex: 0 0 auto; }
.req-sup-main { display: block; min-width: 0; }
.req-sup-name { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-weight: 600; }
.req-sup-cats { display: block; font-size: .82rem; color: var(--tb-muted); }
.req-sup-sla  { display: block; font-size: .74rem; color: var(--tb-faint); font-variant-numeric: tabular-nums; }

.req-go { display: flex; gap: .5rem; flex-wrap: wrap; }

/* 400px: the lines table becomes a list, the same way the bill does. */
@media (max-width: 575.98px) {
  .req-lines thead { display: none; }
  .req-lines, .req-lines tbody, .req-lines tr, .req-lines td { display: block; width: auto; }
  .req-lines tr { border-bottom: 1px solid var(--tb-line); padding: .5rem .25rem; }
  .req-lines td { border: none; padding: .15rem .5rem; text-align: left !important; }
  .req-lines td[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    display: inline-block; min-width: 5.5rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint);
  }
  .req-lines td[data-label="Line"]::before { display: none; }
  .bill-tick { text-align: left; }
}

/* --- P13: compare quotes ---------------------------------------------------
   The comparison panel, the two chips on a bill line, and the
   selected/not-selected badge on the member's lead page.

   ONE CARD PER SUPPLIER, not one column per supplier: a table with a
   column each reads well at two quotes and falls apart at four, and at
   400px it falls apart at two. The card is the unit of the decision, so
   the price, the promise, the scorecard and the button live in it
   together.

   Colour: orange is on "Select this quote" and nowhere else on the page.
   The cheapest price is deliberately NOT green -- cheapest is not best,
   and a screen that colours it is a screen with an opinion about which
   business a member should use. The only green here is the flag on the
   quote the member themselves chose, which is a statement of fact about
   the bill and not a recommendation. */

.cmp-card {
  border: 1px solid var(--tb-line);
  border-radius: var(--tb-radius);
  padding: .8rem .9rem;
  margin-bottom: .75rem;
  background: var(--tb-surface);
}
.cmp-card:last-child { margin-bottom: 0; }
.cmp-card.is-chosen { border-color: var(--tb-good); background: var(--tb-surface); }

.cmp-head { display: flex; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.cmp-who  { min-width: 0; margin-right: auto; }
.cmp-name { font-weight: 600; text-decoration: none; }
.cmp-name:hover { text-decoration: underline; }
.cmp-status { display: block; font-size: .78rem; color: var(--tb-muted); }

.cmp-price   { text-align: right; white-space: nowrap; }
.cmp-figure  { display: block; font-size: 1.25rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.cmp-when    { display: block; font-size: .72rem; color: var(--tb-faint); }
.cmp-nofigure { font-size: .85rem; color: var(--tb-faint); }

.cmp-chosen-flag {
  margin-top: .5rem; font-size: .78rem; font-weight: 600; color: var(--tb-good);
}

.cmp-facts {
  display: flex; flex-wrap: wrap; gap: .15rem .9rem;
  margin: .6rem 0 0; font-size: .8rem;
}
.cmp-facts dt { color: var(--tb-faint); font-weight: 400; }
.cmp-facts dt::after { content: ":"; }
.cmp-facts dd { margin: 0 .6rem 0 0; color: var(--tb-ink); }

.cmp-score {
  margin-top: .55rem; padding-top: .55rem; border-top: 1px dashed var(--tb-line);
  display: flex; flex-wrap: wrap; gap: .2rem 1.1rem; font-size: .8rem;
}
.cmp-score-bit { display: inline-flex; gap: .35rem; align-items: baseline; }
.cmp-score-k { color: var(--tb-faint); }
.cmp-score-v { font-weight: 600; font-variant-numeric: tabular-nums; }
.cmp-score-note { flex-basis: 100%; font-size: .72rem; color: var(--tb-faint); }

.cmp-lines { margin-top: .6rem; }
.cmp-lines > summary { font-size: .8rem; color: var(--tb-navy); cursor: pointer; }
.cmp-lines > summary:hover { color: var(--tb-navy-2); }
.cmp-lines .table { margin-top: .5rem; }

.cmp-act {
  margin-top: .7rem; padding-top: .7rem; border-top: 1px solid var(--tb-line);
  display: flex; align-items: flex-end; gap: .5rem; flex-wrap: wrap;
}
.cmp-act-field { min-width: 15rem; }
.cmp-act .form-control, .cmp-act .form-select { min-width: 12rem; }
.cmp-act .btn { white-space: nowrap; }

.cmp-note { margin-top: .6rem; font-size: .78rem; color: var(--tb-muted); }

/* The bill chips. "N quotes" is the same navy chip as "N asked" with a
   heavier border -- a line with answers waiting is a louder fact than a
   line that is merely out, but it is still a fact and not an action. */
.chip-quoted { border-color: var(--tb-navy); font-weight: 600; }
.chip-chosen { color: var(--tb-good); border-color: var(--tb-good); background: var(--tb-surface); }
.chip-chosen:hover { color: var(--tb-good); background: var(--tb-hover); }

/* The lead page's badge. Green only for the one that was chosen. */
.cmp-badge-on  { background: var(--tb-good); color: #fff; font-weight: 600; }
.cmp-badge-off { background: var(--tb-sunk); color: var(--tb-muted); font-weight: 500; }

@media (max-width: 575.98px) {
  .cmp-head { flex-direction: column; gap: .25rem; }
  .cmp-price { text-align: left; }
  .cmp-act { flex-direction: column; align-items: stretch; }
  .cmp-act .btn, .cmp-act .form-control, .cmp-act .form-select { width: 100%; min-width: 0; }
}

/* --- P17: off-network suppliers --- */
/* The "ask somebody else" block sits under the network picker and is
   deliberately quieter than it: a sunk panel, not a second offer of
   equal weight. The network is what a member pays for; this is the door
   beside it. Existing tokens only. */
.offnet { background: var(--tb-sunk); }
.offnet .form-check-label { font-weight: 500; }

/* "Outside the network" beside a lead's name. It reuses .chip-kind --
   navy/grey/sand, never orange and never red-amber-green -- because it
   is a fact about who was asked, not an action and not a status. */
.chip-kind.chip-offnet {
  border-color: var(--tb-line-2); color: var(--tb-muted); background: var(--tb-surface);
  vertical-align: middle; margin-left: .35rem;
}
/* --- P18b --- */
/* Two windows on the scorecard: recent form beside track record.
   A P18 tile was one figure with a comparison under it. A P18b tile is a
   MEASURE with two of those inside it, because the comparison that
   matters most is the measure against itself a year and a half ago -- a
   supplier at 40% this quarter and 85% over two years has a problem this
   quarter, and two separate cards would have made a reader hold one
   number in their head while they found the other.

   So the measure's name moves to the top of the tile, the two windows
   sit side by side under it, and the sentence that says what moves the
   figure stays at the bottom where it belongs to both.

   Colour moves INWARD with them: the border is on the window now, not
   the tile, for exactly the same reason -- one border could only have
   told half the story. Same --tb-good / --tb-crit as everywhere else,
   same rule that the middle stays grey, and still nothing orange.

   The months are never written here. They come from the row, so this
   file does not need editing when scorecard.windows moves. */

.sc-measures { grid-template-columns: repeat(auto-fit, minmax(19.5rem, 1fr)); }
.sc-measure > .tile-k { margin-top: 0; margin-bottom: .5rem; }

/* The two windows. A grid rather than a flex row so both columns are the
   same width whatever is in them -- "100%" beside "—" must not make the
   empty window a third of the size of the one with a figure in it. */
.sc-windows {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: .35rem 1rem; align-items: start;
}
.sc-win {
  min-width: 0;
  border-left: 3px solid var(--tb-line);
  padding-left: .6rem;
}
.sc-win.is-live.tile-success { border-left-color: var(--tb-good); }
.sc-win.is-live.tile-danger  { border-left-color: var(--tb-crit); }

/* "Last 3 months". Quieter than the measure's own name above it, and it
   never wraps mid-number. */
.sc-win-k {
  display: block; margin-bottom: .1rem;
  font-size: .66rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--tb-faint); white-space: nowrap;
}

/* The figure. Smaller than P18's single tile number -- there are two of
   them now and they are read against each other, not across the page --
   and always ink, because the status is the border. The rule it is
   overriding (.tile:not(.is-live) .tile-n) greys the number on a tile
   with no colour, which was right when the tile WAS the figure. */
.sc-measure .tile-n { font-size: 1.5rem; }
.sc-measure .sc-win .tile-n { color: var(--tb-ink); }
.sc-measure .sc-win .tile-n.sc-none { color: var(--tb-faint); font-weight: 600; }
.sc-none-k { color: var(--tb-faint); font-weight: 500; }

.sc-win .sc-cmp { margin-top: .2rem; font-size: .74rem; }

/* Won / lost / decided under the win rate, and the date under a figure
   when the two windows were cut on different nights (which means a run
   failed, so it is worth saying). */
.sc-win-sub, .sc-win-date {
  display: block; margin-top: .25rem;
  font-size: .72rem; color: var(--tb-muted);
  font-variant-numeric: tabular-nums;
}
.sc-win-date { color: var(--tb-faint); }

/* The "only N decided" caution, per window. It was one note under the
   whole card in P18; with two windows it has to belong to the figure it
   is about, because the three-month one will carry it for months after
   the two-year one has stopped needing it. Still a sentence and a rule,
   not a coloured banner. */
.sc-win-note {
  display: block; margin-top: .35rem;
  font-size: .72rem; line-height: 1.35; color: var(--tb-muted);
}

.sc-measure .sc-why { margin-top: .6rem; }

/* 576px: stack the two windows. Two columns inside one tile inside a
   400px phone leaves about 170px a column, which sets "category median"
   three words to a line and makes the pair harder to compare than one
   under the other. Stacked, each window keeps its own coloured rule down
   the left and the labels carry the months, so nothing is ambiguous. */
@media (max-width: 575.98px) {
  .sc-measures { grid-template-columns: 1fr; }
  .sc-windows { grid-template-columns: 1fr; gap: .7rem; }
  .sc-win-k { white-space: normal; }
}

/* --- P21: the counter -------------------------------------------------------
   Scan a card, check it, record the sale. A phone held one-handed with a
   customer waiting, so the camera is big, the answer is one word in one
   colour, and the only orange on the page is the Record button.

   The three states are the public /verify page's three states and wear
   the same colours for the same reason: green is current, AMBER (never
   red) is lapsed because a lapsed membership is a renewal conversation
   and not a fraud alert, and grey is "we do not know who this is".
   Status colours, never actions.

   "Scanned" / "Not scanned" reuse .chip-kind -- a fact about the
   evidence, not a status and not a warning. Typing a member number is
   allowed and normal; colouring it like a failure would teach counter
   hands to ignore it. */

.scan-stage {
  position: relative; width: 100%; max-width: 28rem; margin: 0 auto;
  aspect-ratio: 1 / 1; background: var(--tb-ink); border-radius: var(--tb-radius); overflow: hidden;
}
.scan-stage video { width: 100%; height: 100%; object-fit: cover; display: block; }
.scan-frame {
  position: absolute; inset: 15%; border: 2px solid rgba(255, 255, 255, .85);
  border-radius: var(--tb-radius); box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .25);
}

.scan-result {
  display: flex; align-items: center; gap: .75rem; margin-top: .75rem;
  padding: .75rem; border: 1px solid var(--tb-line); border-radius: var(--tb-radius);
  background: var(--tb-sunk);
}
.scan-state {
  flex: 0 0 auto; padding: .35rem .6rem; border-radius: .3rem;
  font-weight: 700; font-size: .78rem; letter-spacing: .06em; text-transform: uppercase;
  border: 1px solid var(--tb-line-2); color: var(--tb-muted); background: var(--tb-surface);
}
.scan-state.is-valid   { border-color: var(--tb-good); color: var(--tb-good); background: #eaf5ef; }
.scan-state.is-lapsed  { border-color: var(--tb-warn); color: var(--tb-warn); background: #fdf5e3; }
.scan-state.is-unknown { border-color: var(--tb-line-2); color: var(--tb-muted); background: var(--tb-surface); }
.scan-business { font-weight: 600; }

/* The day's list. A voided row is still readable -- nothing is deleted,
   and a row struck through in grey says "this did not count" without
   hiding what was recorded or who took it back. */
tr.is-voided td { color: var(--tb-faint); text-decoration: line-through; }
tr.is-voided td:last-child { text-decoration: none; }

.chip-kind.chip-scanned { border-color: var(--tb-line-2); color: var(--tb-ink); background: var(--tb-surface); }

/* "Counter" beside a supplier's name on the member's Ledger. A fact
   about how the row came to exist -- scanned at a trade counter rather
   than reported at the end of the month -- so it is .chip-kind and not a
   colour anybody could read as good or bad news. */
.chip-kind.chip-counter {
  border-color: var(--tb-line-2); color: var(--tb-muted); background: var(--tb-surface);
  vertical-align: middle; margin-left: .35rem;
}

/* --- P19: purchase orders --------------------------------------------------
   Two chips and one card, all on existing tokens.

   "PO 4154-2656-1" is navy like every other bill chip: it is a FACT about
   the line -- something has been ordered against it -- and the door to the
   document that says so. It is not green, because a committed figure is
   not an achievement, and it is not orange, because orange is an action.

   "Raise PO" IS an action, and it is the only chip in the bill that is:
   it appears only on a line that carries a selected quote and no order
   yet, which is exactly the moment the member has decided and not yet
   acted. Orange text on the surface, never a fill -- the fill belongs to
   the real buttons. */
.chip-po    { border-color: var(--tb-navy); font-weight: 600; }
.chip-raise { color: var(--tb-orange-ink); border-color: var(--tb-orange-ink); }
.chip-raise:hover { color: var(--tb-orange-ink); background: var(--tb-hover); }

/* The order card on the supplier's lead page. A left edge in navy, which
   is what the rest of the app uses for structure, so an order reads as
   part of the lead rather than as an alert about it. */
.po-card { border-left: 3px solid var(--tb-navy); }

/* The timeline on the member's order page: four facts, one per line, the
   ones that have not happened yet said in words rather than left out. */
.po-timeline li { padding: .3rem 0; border-bottom: 1px dashed var(--tb-line); }
.po-timeline li:last-child { border-bottom: 0; }
.po-timeline strong { display: inline-block; min-width: 8.5rem; color: var(--tb-faint); font-weight: 500; }

/* The order form's line table. The figure column is the one that matters,
   so it is the one that is tabular. */
.po-lines input.text-end { font-variant-numeric: tabular-nums; }

/* --- P23: variations -------------------------------------------------
   The register on the Job Desk, and the form that writes it.

   IT BORROWS THE BILL'S FURNITURE ON PURPOSE. The toolbar, the row
   actions and the phone layout are .bill-bar, .bill-act and the same
   data-label collapse the bill uses, because a variation is the same
   kind of object on the same screen and a second visual language for it
   would be two things to learn. What is new here is only what is new in
   the data: a number column, a status badge, and one total.

   COLOUR. The status badge is the only colour on the tab, and it is
   status colour -- amber out for sign-off, green approved, red declined,
   grey draft and withdrawn -- never orange. Orange stays on the two
   actions (New variation, Send for approval), which is the rule the
   whole app follows. */

.var-bar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.var-bar-tot { display: flex; align-items: baseline; gap: .4rem; font-variant-numeric: tabular-nums; }
.var-tot-k { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint); }
.var-tot-v { font-weight: 700; font-size: .95rem; margin-right: .4rem; }

.var-table td { vertical-align: middle; }
.var-table tfoot th { background: var(--tb-sunk); font-size: .8rem; }
.var-no-h { width: 3.5rem; }
.var-no { font-weight: 700; color: var(--tb-muted); }
.var-title { font-weight: 600; }
.var-price { font-weight: 600; white-space: nowrap; }
.var-dates .sub { display: block; }
.var-reason { font-style: italic; }
.var-acts-h { width: 7rem; }
.var-acts { white-space: nowrap; text-align: right; }
.var-is-editing > td { background: var(--tb-hover); }

/* The note under the register: where the money actually went. Quiet, and
   a sentence rather than a legend, because it is explaining a decision
   (cost on the bill, price on the variation) and not labelling a
   control. */
.var-foot-note {
  padding: .75rem 1rem; border-top: 1px solid var(--tb-line);
  font-size: .82rem; color: var(--tb-muted);
}

/* The line grid. Four narrow columns that have to survive a phone, so
   they collapse the same way the bill's do -- see the media query at the
   end of the bill block for the mechanism. */
.var-lines-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap; margin-bottom: .35rem;
  font-weight: 600;
}
.var-line-table th { white-space: nowrap; font-size: .78rem; }
.var-line-table td { vertical-align: middle; padding: .3rem .4rem; }
.var-line-table td:first-child, .var-line-table th:first-child { padding-left: 0; }
.var-l-qty  { width: 6rem; }
.var-l-unit { width: 8rem; }
.var-l-rate { width: 8rem; }

/* The send block is a second card under the form, sunk, because it is
   the step AFTER saving and should not read as a second equal offer --
   the same argument .offnet makes on the request picker. */
.var-send { background: var(--tb-sunk); }
.var-newclient > summary { cursor: pointer; font-size: .88rem; color: var(--tb-navy); }

@media (max-width: 575.98px) {
  .var-table thead, .var-line-table thead { display: none; }
  .var-table tr, .var-line-table tr { display: block; border-bottom: 1px solid var(--tb-line); padding: .5rem 0; }
  .var-table td, .var-line-table td { display: flex; gap: .75rem; border: 0; padding: .2rem 0; }
  .var-table td::before, .var-line-table td::before {
    content: attr(data-label); flex: 0 0 6.5rem;
    font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--tb-faint);
  }
  .var-table td.var-acts, .var-acts { text-align: left; }
  .var-l-qty, .var-l-unit, .var-l-rate { width: auto; }
}
/* --- NS-20 ---------------------------------------------------------------
   The upload form on the Files tab, and the Attach picker on a lead.

   The grid is P15's people-form-grid, cloned rather than shared: the two
   forms want different column widths (a file input needs room, a role
   dropdown does not) and one grid bent to serve both is how a layout
   stops being editable. Everything else here is existing tokens --
   .bill-act-del does the remove button, .form-text does the caps line,
   and the Attach button is the ordinary .btn-primary.

   No new colour. An upload is an action, so its button is orange like
   every other action, and the remove is P10's red-on-hover cross. */
.ns20-form-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem .6rem; align-items: end; }
.ns20-f      { display: block; margin: 0; }
.ns20-f-file { grid-column: span 5; }
.ns20-f-kind { grid-column: span 3; }
.ns20-f-desc { grid-column: span 2; }
.ns20-f-go   { grid-column: span 2; }

/* The actions column on the Files table: as narrow as its one button. */
.ns20-acts-h { width: 2.5rem; }
.ns20-acts   { text-align: right; white-space: nowrap; }

/* The Attach picker sits inside the attachments card, under the list, so
   it needs a rule above it rather than a heading of its own. */
.ns20-attach { border-top: 1px dashed var(--tb-line); padding-top: .6rem; }

@media (max-width: 991.98px) {
  .ns20-f-file { grid-column: span 12; }
  .ns20-f-kind { grid-column: span 5; }
  .ns20-f-desc { grid-column: span 4; }
  .ns20-f-go   { grid-column: span 3; }
}

@media (max-width: 575.98px) {
  .ns20-form-grid { grid-template-columns: repeat(4, 1fr); }
  .ns20-f-file, .ns20-f-kind, .ns20-f-desc, .ns20-f-go { grid-column: span 4; }
}
/* --- end NS-20 --- */
/* --- P20: budget -------------------------------------------------------------
   The Budget tab: the bill of work read as money. Four tiles, the stage
   roll-up with a bar per stage, and what moved this week.

   ALMOST NOTHING NEW. The tiles are the Day Sheet's .tile-row / .tile,
   the week is the Day Sheet's .queue / .queue-row, the stage table is
   the bill's .tb-table and its 400px block layout, and every colour is
   an existing token. What is added here is a third line on a tile, the
   bar, and the two variance colours.

   RED IS OVER BUDGET AND NOTHING ELSE. --tb-crit on the figure, never as
   a background and never on a control: a variance is a status, and the
   only orange on this page is Save. --tb-good marks a line that came in
   UNDER, which is the same fact with the other sign and deserves to be
   as easy to see.

   Every block below is closed where it is opened, including the media
   query at the end -- three earlier packets left one open and took the
   desktop layout with it. */

/* The tile grows a third line: the tile itself says the figure, the key
   says what it is, and this says what it is OF. Same idea as the
   scorecard's tile body (.sc-tiles), which is why the row shares its
   minimum column width -- four money figures do not fit in 8.5rem. */
.budget-tiles { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); align-items: stretch; }
.budget-tiles .tile { display: flex; flex-direction: column; }
.budget-tiles .tile-n { font-size: clamp(1.15rem, 2.2vw, 1.55rem); }
.budget-tile-sub {
  margin-top: .3rem; font-size: .73rem; line-height: 1.3; color: var(--tb-muted);
}
.budget-tile-sub a { color: var(--tb-navy); }

/* Over its budget by more than this job's tolerance, and under it. */
.budget-over  { color: var(--tb-crit); font-weight: 700; }
.budget-under { color: var(--tb-good); }

/* The three numbers the member tells the tab. A form, laid out as a row
   that wraps, because it is three short fields and a button rather than
   a page of settings. */
.budget-set { display: flex; align-items: flex-end; gap: .6rem; flex-wrap: wrap; }
.budget-f { display: block; margin: 0; flex: 0 0 9rem; }
.budget-f .form-control { font-variant-numeric: tabular-nums; }
.budget-f-go { flex: 0 0 7rem; }
.budget-set-note {
  flex: 1 1 100%; font-size: .75rem; color: var(--tb-muted); max-width: 70ch;
}

/* The stage roll-up. */
.budget-table th { white-space: nowrap; }
.budget-table td { vertical-align: middle; }
.budget-table tfoot th { background: var(--tb-sunk); font-size: .8rem; }
.budget-bar-h { width: 9rem; }

/* % invoiced. The bar is capped at 100 in the template -- it cannot draw
   past full -- and the figure beside it is not, so "142%" still reads as
   142%. Navy is structure, which is what a progress bar is; it turns red
   only when the stage it belongs to is over budget. */
.budget-bar {
  display: inline-block; vertical-align: middle;
  width: 4.5rem; height: .45rem; border-radius: .25rem;
  background: var(--tb-sunk); overflow: hidden;
}
.budget-bar .fill { display: block; height: 100%; background: var(--tb-navy); border-radius: .25rem; }
.budget-bar .fill.is-over { background: var(--tb-crit); }
.budget-bar-n {
  margin-left: .4rem; font-size: .76rem; color: var(--tb-muted); font-variant-numeric: tabular-nums;
}

/* What moved this week. The Day Sheet's queue, with the money on the
   right where the due date sits there. */
.budget-moved .queue-row { text-decoration: none; color: inherit; }
.budget-moved .queue-meta .badge { font-variant-numeric: tabular-nums; }

/* 400px: the tiles stack two-up and the table becomes blocks, each money
   column a labelled line -- exactly what the bill does, because it is
   the same table read by the same thumb. */
@media (max-width: 575.98px) {
  .budget-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .budget-tiles .tile-n { font-size: 1.1rem; }

  .budget-f { flex: 1 1 8rem; }
  .budget-f-go { flex: 1 1 100%; }

  .budget-table thead { display: none; }
  .budget-table, .budget-table tbody, .budget-table tfoot,
  .budget-table tr, .budget-table td, .budget-table th { display: block; width: auto; }
  .budget-table tr { border-bottom: 1px solid var(--tb-line); padding: .5rem .25rem; }
  .budget-table td, .budget-table th { border: none; padding: .15rem .5rem; text-align: left !important; }
  .budget-table td[data-label]:not([data-label=""])::before,
  .budget-table th[data-label]:not([data-label=""])::before {
    content: attr(data-label);
    display: inline-block; min-width: 5.5rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint);
  }
  .budget-table td[data-label="Stage"]::before { display: none; }
  .budget-table tfoot tr { background: var(--tb-sunk); }
  .budget-bar { width: 6rem; }
}
/* --- P22: rebates -----------------------------------------------------------
   Three chips, one banner, one dimmed row. All on existing tokens; nothing
   here invents a colour.

   THE KIND CHIPS ARE GREY, all three of them. "Counter", "Order" and
   "Reported" are three ways the same fact -- a member bought something --
   reached this system. None of them is good news or bad news, so none of
   them is red, amber or green, and none is orange because orange is an
   action and there is no action on a chip. This is P21's rule for
   "Not scanned", applied to the same kind of fact. */
.chip-kind.chip-rb-counter,
.chip-kind.chip-rb-po,
.chip-kind.chip-rb-reported {
  border-color: var(--tb-line-2); color: var(--tb-muted); background: var(--tb-surface);
  vertical-align: middle; margin-left: .2rem;
}

/* "Not on your report" IS amber, because it IS a status -- a sale
   TradeBuilders watched happen that the supplier's own figures do not
   appear to carry. Amber and not red: a sale late in the period that went
   onto next month's report reads exactly like this and is not a fault. */
.chip-kind.chip-rb-unreported {
  border-color: #e4cf9a; color: var(--tb-warn); background: #fff8e6;
  vertical-align: middle; margin-left: .2rem;
}

/* The signal above the tables. Same treatment as .pwned-banner, which is
   the app's existing "you should know this before you go on" shape. */
.rb-signal {
  background: #fff8e6; border: 1px solid #f3e2b8; border-left: 3px solid var(--tb-warn);
  border-radius: .4rem; padding: .6rem .9rem; font-size: .88rem;
  display: flex; gap: .6rem; align-items: flex-start;
}
.rb-signal i { color: var(--tb-warn); margin-top: .15rem; }
.rb-signal .small { color: var(--tb-muted); margin-top: .25rem; }

/* A line the supplier has taken off the report. Struck through and faded
   rather than hidden: it is still one of the invoices we found, and a row
   that vanished would leave them wondering what they had just done. */
.rb-lines tr.rb-off td { color: var(--tb-faint); }
.rb-lines tr.rb-off .num { text-decoration: line-through; }

/* The money columns on both tables are the point of the page. */
.rb-table .num, .rb-lines .num { font-variant-numeric: tabular-nums; }

/* "What we saw" leads the eye on the period list -- it is the column that
   is new. A weight, not a colour. */
.rb-table .rb-platform { font-weight: 600; }

/* The confirm card. A navy left edge, which is what this app uses for
   structure, so the one action on the page sits on something that reads as
   part of the report rather than as an alert about it. */
.rb-confirm { border-left: 3px solid var(--tb-navy); }

/* --- P-LOG --- */
/* The contact log, the site diary and escalation. Built on what is
   already here: the Day Sheet's .queue for the rows, the address book's
   .chip for the filters, the People tab's form grid for the form. The
   only genuinely new shapes are the thread and the entry card. */

/* The chip row and the search box, the same head the Contacts list has.
   At 400px the search drops under the chips and takes the full width --
   an input that stayed beside seven chips would be forty pixels wide. */
.log-head { row-gap: .5rem; }
.log-search { min-width: 14rem; }
@media (max-width: 575.98px) {
  .log-search { margin-left: 0 !important; width: 100%; }
}

/* THE KIND CHIP IS GREY, ALWAYS. A call, a council RFI and a defect are
   three things that happened and none of them is good or bad news --
   P22's rule for the Counter / Order / Reported chips, for the same
   reason. What IS coloured is the stripe and the status badge, because
   those say where the work is. */
.log-kind { vertical-align: middle; margin-right: .15rem; }

/* "With TradeBuilders" is navy -- structure, not alarm. An escalation is
   not a fault, it is a hand-over, and red would read as one. */
.chip-kind.log-escalated {
  border-color: #b9cdf0; color: var(--tb-navy); background: var(--tb-hover);
  vertical-align: middle; margin-left: .2rem;
}

/* A log row carries more than a lead row does -- a kind, who it is
   about, the job, and what was decided -- so its second line is allowed
   to wrap rather than being cut off mid-sentence by the ellipsis the
   Day Sheet's own rows use. */
.log-queue .queue-sub { white-space: normal; }
.log-decided { display: block; color: var(--tb-muted); }

/* Two queues in one card (the Day Sheet, where the overdue log items sit
   above the leads) must not lose the line between them. */
.queue + .queue .queue-row:first-child { border-top: 1px solid var(--tb-line); }

/* The form. THE PHONE IS THE POINT: one column at 400px, the kind first,
   the line second, the button under them. On a desk it is three columns
   so the whole thing is one glance. */
.log-form-grid {
  display: grid; gap: .5rem; align-items: end;
  grid-template-columns: minmax(10rem, 1fr) minmax(0, 2fr) 7rem;
}
.log-form-more { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); margin-top: .5rem; }
.log-f { display: block; min-width: 0; }
.log-f .form-label { margin-bottom: .15rem; font-size: .78rem; font-weight: 600; }
.log-f-detail { grid-column: 1 / -1; }
.log-f-wide { grid-column: span 2; }
@media (max-width: 575.98px) {
  .log-form-grid, .log-form-more { grid-template-columns: 1fr; }
}
/* A field the chosen kind has no use for is hidden by app.js. Bootstrap
   sets display on labels, so [hidden] needs to win. */
.log-form-card [hidden] { display: none !important; }

/* The entry itself: the same left edge the queue's stripe draws, so an
   entry opened from a red row is still red. */
.log-entry { border-left: 3px solid var(--tb-line-2); }
.log-entry-danger    { border-left-color: var(--tb-crit); }
.log-entry-warning   { border-left-color: var(--tb-warn); }
.log-entry-info      { border-left-color: var(--tb-navy); }
.log-entry-secondary { border-left-color: var(--tb-line-2); }
.log-detail { white-space: pre-line; }
.log-resolution {
  background: var(--tb-sunk); border-radius: .35rem; padding: .5rem .7rem;
  font-size: .9rem;
}
.log-photos { display: flex; flex-wrap: wrap; gap: .5rem; }
.log-photo {
  display: inline-flex; align-items: center; gap: .3rem; font-size: .82rem;
  border: 1px solid var(--tb-line-2); border-radius: .3rem; padding: .25rem .5rem;
  text-decoration: none; color: inherit; background: var(--tb-surface);
}
.log-photo:hover { background: var(--tb-hover); }

/* The thread. Navy ticks down the left, which is the shape a
   conversation already has on the lead page. */
.log-thread { display: flex; flex-direction: column; gap: .6rem; }
.log-event { border-left: 2px solid var(--tb-line-2); padding-left: .7rem; }
.log-event-status .log-event-body, .log-event-resolution .log-event-body { color: var(--tb-muted); }
.log-event-head { font-size: .8rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.log-event-body { font-size: .9rem; white-space: pre-line; }
.log-follow { display: flex; gap: .5rem; }
.log-follow .form-control { flex: 1; }

/* Settling it: the resolution, the note and the button in one row on a
   desk, stacked on a phone. */
.log-resolve-grid {
  display: grid; gap: .5rem; align-items: end;
  grid-template-columns: minmax(10rem, 1fr) minmax(0, 2fr) 8rem;
}
@media (max-width: 575.98px) { .log-resolve-grid { grid-template-columns: 1fr; } }

/* The escalation card. Orange is the ACTION colour in this application
   and the button is already orange; the card around it is navy-edged
   structure, because the words are what matter before it is pressed. */
.log-escalate { border-left: 3px solid var(--tb-navy); }
/* --- end P-LOG --- */
/* --- P26-P27: schedule -------------------------------------------------
   The Schedule tab, the chart on it, and "This week on site" on the Day
   Sheet.

   THE CHART IS A CSS GRID AND NOTHING ELSE. `.sch-track` has one column
   per DAY of the job's span (--sch-days, set inline from PHP), the week
   headings span seven of them, and a bar is placed with `grid-column`
   worked out from its own dates. No library, no canvas, no script: five
   bars do not justify 90 KB of somebody else's code, and this prints.

   COLOUR IS STATUS, NEVER ACTION, which is the app's rule and on this
   tab it is the whole picture: navy is work that is booked, navy with a
   tick is work a trade has confirmed, RED is nobody booked or a trade
   who said no -- the thing a builder opens this tab to find -- and green
   is done. Every one of them is an existing token; nothing here invents
   a colour, and the only orange on the tab is on the buttons.

   AT 400px THE CHART IS NOT DRAWN AT ALL. Below 576px the grid is
   display:none and the tables collapse to labelled rows the way the bill
   and the variations register already do. A Gantt chart on a phone is a
   smear; "Tue: Dave, pour slab" is not. */

.sch-top { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.sch-legend { display: flex; gap: .75rem; flex-wrap: wrap; font-size: .75rem; color: var(--tb-muted); }
.sch-legend span { display: inline-flex; align-items: center; gap: .3rem; }
.sch-key { width: .85rem; height: .55rem; border-radius: .15rem; display: inline-block; }

/* --- the chart --- */
.sch-chartwrap { overflow-x: auto; }
.sch-chart { min-width: 30rem; }
.sch-row { display: grid; grid-template-columns: 9rem 1fr; align-items: center; gap: .5rem; }
.sch-row + .sch-row { margin-top: .25rem; }
.sch-name {
  font-size: .8rem; color: var(--tb-ink); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sch-task .sch-name { color: var(--tb-muted); padding-left: .75rem; }
.sch-track {
  display: grid; grid-template-columns: repeat(var(--sch-days, 28), 1fr);
  min-height: 1.35rem; align-items: center;
  background: repeating-linear-gradient(
    to right, var(--tb-sunk) 0, var(--tb-sunk) 1px, transparent 1px,
    transparent calc(100% / var(--sch-days, 28) * 7));
}
.sch-head .sch-track { background: none; min-height: 1.1rem; }
.sch-week {
  font-size: .7rem; color: var(--tb-faint); border-left: 1px solid var(--tb-line);
  padding-left: .2rem; white-space: nowrap; overflow: hidden;
}
/* Today, as a hairline down the chart. Amber would be a warning and navy
   would be a bar, so it is the faint rule -- a position, not a status. */
.sch-today { grid-row: 1; border-left: 2px solid var(--tb-line-2); height: 100%; }

.sch-bar {
  grid-row: 1; height: 1.05rem; border-radius: .2rem; min-width: .5rem;
  display: flex; align-items: center; gap: .25rem; padding: 0 .3rem;
  overflow: hidden;
}
.sch-bar-stage { height: 1.2rem; }
.sch-bar-t { font-size: .68rem; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sch-tick { font-size: .6rem; color: #fff; flex: 0 0 auto; }
.sch-nodate { grid-column: 1 / -1; font-size: .7rem; color: var(--tb-faint); }

.sch-bar.is-planned,    .sch-key.is-planned    { background: var(--tb-navy); }
.sch-bar.is-confirmed,  .sch-key.is-confirmed  { background: var(--tb-navy-2); box-shadow: inset 0 0 0 1px #fff3; }
.sch-bar.is-unassigned, .sch-key.is-unassigned { background: var(--tb-crit); }
.sch-bar.is-declined                           { background: var(--tb-crit); opacity: .75; }
.sch-bar.is-done,       .sch-key.is-done       { background: var(--tb-good); }
.sch-bar.is-cancelled { background: var(--tb-line-2); }
.sch-bar.is-cancelled .sch-bar-t { color: var(--tb-muted); text-decoration: line-through; }
.sch-bar-stage.is-planned { background: var(--tb-navy); opacity: .55; }

/* --- a stage and its tasks --- */
.sch-stagehead {
  display: flex; align-items: flex-end; gap: .75rem; flex-wrap: wrap;
  padding: .75rem 1rem; border-bottom: 1px solid var(--tb-line);
}
.sch-dates { display: flex; align-items: flex-end; gap: .5rem; flex-wrap: wrap; }
.sch-f { display: block; }
.sch-f .form-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--tb-faint); margin-bottom: .1rem; }
.sch-f-dur input { width: 5rem; }
/* The shift checkbox. Deliberately plain and deliberately unticked every
   time the page renders: it is the only control in this application that
   moves a date nobody typed, and it should read as a decision rather than
   as a setting that might be on. */
.sch-shift { display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem; color: var(--tb-muted); padding-bottom: .35rem; }

.sch-table td { vertical-align: middle; }
.sch-title { font-weight: 600; }
.sch-none { color: var(--tb-crit); font-weight: 600; }
.sch-reason { font-style: italic; }
.sch-acts-h { width: 11rem; }
.sch-acts { white-space: nowrap; text-align: right; }
.sch-is-editing > td { background: var(--tb-hover); }
.sch-off td { color: var(--tb-faint); }
.sch-off .sch-title { text-decoration: line-through; }
.sch-formrow > td { background: var(--tb-sunk); }

/* The status badge is the only colour in the table, and it is status
   colour -- grey planned, navy confirmed, red declined, green done. */
.sch-st { font-weight: 600; }
.sch-st-planned   { background: var(--tb-sunk); color: var(--tb-muted); }
.sch-st-confirmed { background: var(--tb-navy); color: #fff; }
.sch-st-declined  { background: #fdecea; color: var(--tb-crit); }
.sch-st-done      { background: #e4f2e6; color: var(--tb-good); }
.sch-st-cancelled { background: var(--tb-sunk); color: var(--tb-faint); }

/* --- the add/edit form --- */
.sch-form { padding: .75rem 0 0; }
.sch-form-head { font-weight: 600; margin-bottom: .4rem; }
.sch-form-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: .5rem .6rem; align-items: end; }
.sch-f-title { grid-column: span 4; }
.sch-f-trade { grid-column: span 3; }
.sch-f-date  { grid-column: span 2; }
.sch-f-dur   { grid-column: span 1; }
.sch-f-item  { grid-column: span 4; }
.sch-f-notes { grid-column: span 6; }
.sch-f-go    { grid-column: span 2; }
.sch-newtrade > summary { cursor: pointer; font-size: .88rem; color: var(--tb-navy); }

/* --- the Day Sheet card --- */
.site-week .site-none { color: var(--tb-crit); font-weight: 600; }

@media (max-width: 575.98px) {
  /* The chart goes entirely. What is left is the same rows as a list. */
  .sch-chartwrap { display: none; }
  .sch-stagehead { align-items: stretch; }
  .sch-dates { width: 100%; }
  .sch-table thead { display: none; }
  .sch-table tr { display: block; border-bottom: 1px solid var(--tb-line); padding: .5rem 0; }
  .sch-table td { display: flex; gap: .75rem; border: 0; padding: .2rem 0; }
  .sch-table td::before {
    content: attr(data-label); flex: 0 0 5.5rem;
    font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--tb-faint);
  }
  .sch-table td.sch-acts { text-align: left; }
  .sch-form-grid { grid-template-columns: repeat(2, 1fr); }
  .sch-f-title, .sch-f-trade, .sch-f-item, .sch-f-notes, .sch-f-go { grid-column: span 2; }
  .sch-f-date, .sch-f-dur { grid-column: span 1; }
  .sch-f-dur input { width: 100%; }
}
/* --- end P26-P27 --- */
/* --- P28: claims -------------------------------------------------------
   The Claims tab on the Job Desk, and the client's read-only statement
   at /c/{token}.

   IT BORROWS THE BILL'S AND THE VARIATION REGISTER'S FURNITURE ON
   PURPOSE. The toolbar, the row actions, the table and the phone
   collapse are .bill-act, .tb-table and the same data-label mechanism,
   because a claim is the same kind of object on the same screen and a
   second visual language for it would be one more thing to learn.

   COLOUR. The status badge is the only colour in the list -- amber sent,
   green paid, red overdue, grey draft and cancelled -- and red appears
   once more, on an outstanding total that has something overdue in it.
   Orange stays on the two actions (Raise claim, Send), which is the rule
   the whole app follows. */

.clm-bar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.clm-bar-tot { display: flex; align-items: baseline; gap: .4rem; font-variant-numeric: tabular-nums; }
.clm-tot-k { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint); }
.clm-tot-v { font-weight: 700; font-size: .95rem; margin-right: .4rem; }
.clm-over { color: var(--tb-crit); }

/* The schedule's own table: an editable grid, so the cells are inputs and
   the rows have to stay short enough to read on a phone. */
.clm-table td { vertical-align: middle; }
.clm-table .sub { display: block; }
.clm-pct-h { width: 6rem; }
.clm-pct-in { width: 5rem; }
.clm-amt { white-space: nowrap; font-weight: 600; }
.clm-done { color: var(--tb-good); font-weight: 600; }
.clm-row-claimed > td { background: var(--tb-hover); }

/* The running total of the percentages. It goes amber when it is not
   100 -- a warning, never a refusal: these are the percentages in the
   builder's own contract and this screen is not a party to it. */
.clm-pct-total { display: flex; align-items: baseline; gap: .4rem; padding-bottom: .4rem; }
.clm-pct-total.is-off .clm-tot-v { color: var(--tb-warn); }

/* The note under the schedule form: whose percentages these are. A
   paragraph rather than a legend, because it is explaining a decision
   and not labelling a control. */
.clm-note { flex: 1 1 18rem; font-size: .82rem; color: var(--tb-muted); }
.clm-foot-note {
  padding: .75rem 1rem; border-top: 1px solid var(--tb-line);
  font-size: .82rem; color: var(--tb-muted);
}

.clm-list td { vertical-align: middle; }
.clm-no-h { width: 3.5rem; }
.clm-no { font-weight: 700; color: var(--tb-muted); }
.clm-title { font-weight: 600; }
.clm-total { font-weight: 700; white-space: nowrap; }
.clm-dates .sub { display: block; }
.clm-acts-h { width: 9rem; }
.clm-acts { white-space: nowrap; text-align: right; }
.clm-send, .clm-paid { display: inline-block; vertical-align: top; min-width: 8rem; }

/* The variations waiting to be claimed, inside the Raise form. Sunk,
   because it is a list of facts about the claim being raised rather than
   a second form. */
.clm-vars { background: var(--tb-sunk); border: 1px solid var(--tb-line); border-radius: .4rem; padding: .75rem .9rem; }
.clm-vars-head { font-weight: 600; display: block; margin-bottom: .35rem; }
.clm-vars-list { list-style: none; padding: 0; margin: 0 0 .35rem; }
.clm-vars-list li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .2rem 0; font-size: .88rem; color: var(--tb-muted);
}

@media (max-width: 575.98px) {
  .clm-table thead, .clm-list thead { display: none; }
  .clm-table tr, .clm-list tr { display: block; border-bottom: 1px solid var(--tb-line); padding: .5rem 0; }
  .clm-table td, .clm-list td { display: flex; gap: .75rem; border: 0; padding: .2rem 0; }
  .clm-table td::before, .clm-list td::before {
    content: attr(data-label); flex: 0 0 6.5rem;
    font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--tb-faint);
  }
  .clm-table td.clm-acts, .clm-list td.clm-acts, .clm-acts { text-align: left; }
  .clm-pct-in { width: 100%; }
}

/* --- the client's statement at /c/{token} ---
   Read on a phone more often than not, by somebody who will see this page
   once or twice. Sized for that: one column, large type, the arithmetic
   above one figure. The colours are literals rather than tokens for the
   same reason the sign-off page's are -- this page is served with the
   plain layout to somebody who has never seen the app, and it must not
   change appearance if a token moves. */
.cview { max-width: 42rem; margin: 0 auto; padding: 1.25rem 1rem 4rem; }
.cview-head { margin-bottom: 1.5rem; }
.cview-brand { font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: #7d8899; }
.cview-builder { font-weight: 600; color: #1c2333; margin-top: .15rem; }
.cview-title { font-size: 1.35rem; font-weight: 700; margin: .5rem 0 .25rem; line-height: 1.25; }
.cview-where { color: #5a6478; font-size: .9rem; }
.cview-card .card-body { padding: 1.1rem 1.15rem; }
.cview-lines-head { font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: #7d8899; margin-bottom: .4rem; }
.cview-lines { list-style: none; padding: 0; margin: 0 0 1rem; }
.cview-lines li { padding: .5rem 0; border-bottom: 1px solid #eef0f4; display: flex; gap: .75rem; align-items: baseline; }
.cview-lines li:last-child { border-bottom: 0; }
.cview-line-d { flex: 1 1 auto; }
.cview-line-n { display: block; color: #5a6478; font-size: .85rem; }
.cview-line-v { white-space: nowrap; font-variant-numeric: tabular-nums; }
.cview-total {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  border-top: 2px solid #1c2333; padding-top: .8rem; margin-top: .4rem;
}
.cview-total-k { font-weight: 600; }
.cview-due { display: block; font-weight: 400; color: #5a6478; font-size: .85rem; }
.cview-total-v { font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.cview-pay { background: #f5f6fa; }
.cview-bank { display: grid; grid-template-columns: 7rem 1fr; gap: .3rem 1rem; margin: 0; }
.cview-bank dt { font-weight: 400; color: #5a6478; }
.cview-bank dd { margin: 0; font-weight: 600; }
/* --- end P28 --- */

/* --- P-FIX1 ----------------------------------------------------------------
   NS-55. "Order 4154-2656-1" beside a supplier's name on the Ledger: the
   same kind of chip as P21's "Counter", and for the same reason -- a fact
   about how the row came to exist, never a status and never an action. It
   borrows .chip-counter's shape and takes the navy edge .chip-po already
   uses for a purchase order in the bill, so an order reads as an order
   wherever it appears. */
.chip-kind.chip-fromorder {
  border-color: var(--tb-navy); color: var(--tb-navy); background: var(--tb-surface);
  font-weight: 600; vertical-align: middle; margin-left: .35rem;
}
/* --- end P-FIX1 --- */
/* --- P28b ---------------------------------------------------------------
   The retention pot on the Claims tab: what the client is holding out of
   the claims they have paid, what has been asked back, and the release
   forms that ask for it.

   IT IS A PANEL ON THE SAME TAB AND USES THE SAME FURNITURE. .tb-table,
   .clm-tot-k / .clm-tot-v and the data-label phone collapse are P28's,
   reused rather than restated: a builder should not have to learn a
   second table on the same screen. Everything here is layout and one
   piece of emphasis.

   COLOUR. None that is new. "Still held" is the member's own money and
   takes the orange INK token (never white on orange, and never orange as
   a status); a cancelled release is greyed and struck through, which is
   the same thing the Ledger does to an excluded line. Orange stays on
   the action buttons, which Bootstrap's .btn-primary already carries. */

.rtn-card .rtn-tot { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; font-variant-numeric: tabular-nums; }
.rtn-left { color: var(--tb-orange-ink); }

.rtn-table td { vertical-align: middle; }
.rtn-table .sub { display: block; }

.rtn-sub-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--tb-faint); margin-bottom: .4rem; }

.rtn-releases { padding: .85rem 1rem; border-top: 1px solid var(--tb-line); }
.rtn-rel-list { list-style: none; margin: 0; padding: 0; }
.rtn-rel-list > li { padding: .4rem 0; border-bottom: 1px dashed var(--tb-line); }
.rtn-rel-list > li:last-child { border-bottom: 0; }
.rtn-rel-head { display: flex; justify-content: space-between; gap: 1rem; font-weight: 600; }
.rtn-rel-void { color: var(--tb-faint); }
.rtn-rel-void .rtn-rel-head { text-decoration: line-through; }

.rtn-terms { padding: .85rem 1rem; border-top: 1px solid var(--tb-line); background: var(--tb-hover); }
.rtn-terms .form-label { font-size: .8rem; margin-bottom: .2rem; }

.rtn-raise { padding: .85rem 1rem; border-top: 1px solid var(--tb-line); }
.rtn-form { border: 1px solid var(--tb-line); border-radius: .5rem; padding: .75rem; height: 100%; }
.rtn-form-head { font-weight: 600; margin-bottom: .2rem; }
.rtn-form .sub { display: block; font-size: .8rem; color: var(--tb-muted); }

/* "Send again" sits under Mark paid in the actions cell. It is a link,
   not a second orange button: re-sending a copy of something is not the
   action the row is for. */
.rtn-again { padding: 0; font-size: .8rem; text-decoration: none; }
/* --- end P28b --- */
/* --- P11b --- */
/* The template register (NS-39) and the draft review queue (NS-38).
   Everything here leans on the `.tpl*` rows P10 already drew for the
   picker on an empty bill and on the `.bill-*` form grid from P11: the
   same list in two places should not be two lists. What is new is the
   action column on a register row, the read-only note, and the outline
   a draft is read through. */
.tpl-reg { align-items: flex-start; }
.tpl-reg .tpl-name a { color: inherit; text-decoration: none; }
.tpl-reg .tpl-name a:hover { text-decoration: underline; }
.tpl-acts { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; flex: 0 0 auto; }
.tpl-acts form { margin: 0; }
.tpl-ro { font-size: .74rem; color: var(--tb-faint); }
.tpl-unused { color: var(--tb-faint); font-style: italic; }
.tpl-nocat { color: var(--tb-faint); }
.tpl-crumb { font-size: .78rem; color: var(--tb-muted); margin-bottom: .1rem; }
.tpl-crumb a { color: var(--tb-muted); }

/* Read-only, and why. Ground rather than a status colour: this is not a
   warning, it is how the screen works. */
.tpl-ro-note {
  display: flex; align-items: flex-start; gap: .2rem;
  background: var(--tb-ground); font-size: .86rem; color: var(--tb-muted);
}
.tpl-ro-note strong { color: var(--tb-ink); }

/* The editor's table: no money columns, so the description takes the
   room the four of them had on the bill. */
.tpl-table td, .tpl-table th { vertical-align: middle; }
.tpl-form-grid, .tpl-group-grid { align-items: end; }
.tpl-addrow td { background: var(--tb-ground); }
.tpl-addline {
  cursor: pointer; font-size: .8rem; font-weight: 600; color: var(--tb-orange-ink);
  list-style: none; padding: .15rem 0;
}
.tpl-addline::-webkit-details-marker { display: none; }
.tpl-addline:hover { text-decoration: underline; }

/* Reading a draft. Indented plain lists, because the question being
   asked of this page is "are these the right lines", and a table of one
   column is a list wearing a table's clothes. */
.tpl-draft-read { margin-top: -.25rem; }
.tpl-draft-stage { margin-top: .6rem; }
.tpl-draft-grp { font-size: .82rem; font-weight: 600; margin: .35rem 0 .1rem; }
.tpl-draft-lines { margin: 0 0 .3rem; padding-left: 1.1rem; font-size: .82rem; color: var(--tb-muted); }
.tpl-draft-lines li { padding: .05rem 0; }
.tpl-draft-lines .sub { font-size: .76rem; color: var(--tb-faint); }

@media (max-width: 575.98px) {
  .tpl-acts { width: 100%; }
  .tpl-ro { width: 100%; }
}
/* --- end P11b --- */

/* --- P-NOTIFY -------------------------------------------------------------
   The bell in both navbars, the notifications page, and the preferences
   table.

   COLOUR. One new use of orange and no new tokens. The unread mark is
   ORANGE because an unread notification is THE MEMBER'S OWN POSITION --
   the same rule the tier marker and the active tab follow -- and it is
   never red, because nothing here is a fault. Everything else is navy
   for structure and grey for what has been dealt with; the kind chip
   reuses .chip-kind exactly as it is, grey always (P22, P-LOG).

   The bell sits in the top bar on BOTH sides. It is not a sixth tab: the
   phone tab bar is full, and a bell is a state of the app rather than a
   section of it. It inherits the navbar's own white-on-navy, so the
   supplier's teal bar needs no override. */

.navbell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border-radius: .35rem; flex: 0 0 auto;
  color: rgba(255,255,255,.82); text-decoration: none; font-size: 1.05rem;
}
.navbell:hover, .navbell:focus-visible { color: #fff; background: rgba(255,255,255,.1); }
.navbell.has-unread { color: #fff; }

/* The count rides on the bell rather than beside it, so the control is
   one target at 32px however many digits it carries. Dark ink on the
   orange, never white -- white on #F87800 is 2.9:1. */
.navbell-n {
  position: absolute; top: -.15rem; right: -.25rem;
  min-width: 1.05rem; padding: 0 .22rem; border-radius: .6rem;
  background: var(--tb-orange); color: var(--tb-ink);
  font-size: .62rem; font-weight: 700; line-height: 1.05rem; text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 1.5px var(--tb-navy);
}
body:has(.app-navbar--supplier) .navbell-n { box-shadow: 0 0 0 1.5px var(--tb-supplier); }

/* The same number in the account menu, where the background is white. */
.notif-menu-n {
  display: inline-block; margin-left: .3rem; padding: 0 .35rem; border-radius: .6rem;
  background: var(--tb-orange); color: var(--tb-ink);
  font-size: .68rem; font-weight: 700; font-variant-numeric: tabular-nums;
}

/* --- the list --- */
.notif-list { list-style: none; margin: 0; padding: 0; }
.notif-row {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .7rem 1rem; border-bottom: 1px solid var(--tb-line);
}
.notif-row:last-child { border-bottom: 0; }
.notif-row:hover { background: var(--tb-hover); }
.notif-row.is-unread { background: var(--tb-orange-soft); }
.notif-row.is-unread:hover { background: #ffe9d4; }

/* A dot in the gutter, so the unread state survives a screenshot, a
   colour-blind reader and the row being read out. It keeps its space
   when the row is read, which is what stops the whole list shifting
   left as things are opened. */
.notif-dot {
  width: .5rem; height: .5rem; border-radius: 50%; flex: 0 0 auto; margin-top: .45rem;
  background: transparent;
}
.notif-row.is-unread .notif-dot { background: var(--tb-orange); }

.notif-main { min-width: 0; flex: 1 1 auto; }
.notif-head { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
.notif-title { font-weight: 600; color: var(--tb-ink); text-decoration: none; }
.notif-title:hover { color: var(--tb-navy); text-decoration: underline; }
.notif-body { font-size: .9rem; color: var(--tb-muted); }
.notif-meta { font-size: .76rem; color: var(--tb-faint); margin-top: .15rem; }
.notif-state-new { color: var(--tb-orange-ink); font-weight: 600; }
.notif-state-old { color: var(--tb-faint); }
.notif-state-err { color: var(--tb-crit); }

/* "Mark read" is a link and not a button: the ACTION on this row is
   opening the thing it points at, and a second orange button beside the
   title would argue with it. */
.notif-mark { flex: 0 0 auto; }
.notif-mark-btn { padding: 0; font-size: .8rem; text-decoration: none; white-space: nowrap; }

@media (max-width: 575.98px) {
  .notif-row { flex-wrap: wrap; }
  .notif-mark { width: 100%; padding-left: 1.1rem; }
  .notif-mark-btn { padding-left: 0; }
}

/* --- preferences --- */
/* The quiet-hours note. Navy-edged structure, not an amber warning: it
   explains how the system behaves, it is not telling anybody off. */
.notif-quiet {
  display: flex; gap: .7rem; align-items: flex-start;
  padding: .85rem 1rem; border-left: 3px solid var(--tb-navy);
  font-size: .9rem;
}
.notif-quiet .bi { color: var(--tb-navy); font-size: 1.1rem; line-height: 1.3; }

.notif-prefs td { vertical-align: middle; }
.notif-prefs-ch { width: 1%; white-space: nowrap; }
.notif-off { border-color: var(--tb-line-2); color: var(--tb-faint); margin-left: .35rem; }

/* Four radios that read as a segmented control -- the same trick the
   P-LOG kind picker uses, and for the same reason: the <input> stays in
   the DOM and keeps the keyboard and the screen reader working, it is
   moved out of sight rather than display:none. */
.notif-choices { display: inline-flex; gap: .25rem; flex-wrap: wrap; }
.notif-choice { margin: 0; }
.notif-choice input {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.notif-choice span {
  display: inline-block; cursor: pointer; user-select: none;
  padding: .2rem .55rem; border: 1px solid var(--tb-line-2); border-radius: .3rem;
  font-size: .8rem; color: var(--tb-muted); background: var(--tb-surface);
}
.notif-choice span:hover { border-color: var(--tb-navy); color: var(--tb-navy); }
.notif-choice input:checked + span {
  background: var(--tb-navy); border-color: var(--tb-navy); color: #fff; font-weight: 600;
}
.notif-choice input:focus-visible + span { outline: 2px solid var(--tb-orange); outline-offset: 1px; }

/* "off" checked is grey and not navy: switching something off is not a
   selection to be pleased about, it is the absence of one. */
.notif-choice input[value="off"]:checked + span {
  background: var(--tb-faint); border-color: var(--tb-faint);
}

@media (max-width: 575.98px) {
  .notif-prefs-ch { width: auto; white-space: normal; }
}
/* --- end P-NOTIFY --- */
/* --- P-FIX2 -----------------------------------------------------------------
   Two small marks on the schedule, and neither is an action.

   .sch-clash  AMBER. The trade on this task is standing on another of the
               member's jobs on an overlapping day. Amber because it is a
               status and a warning -- red is "nobody is booked", which is a
               hole, and this is not: two half-days on two sites is a normal
               Tuesday, and only the builder knows which it is. It sits inside
               .sub on both screens it appears on (the Schedule tab's Trade
               cell and the Day Sheet's "this week on site" row), so it takes
               that block's size and spacing and only restates the colour.

   .sch-delivered  GREEN. The supplier has said the material for this line
               landed. The same green every "it happened" reads in, and the
               same shape as the bill's chips so a fact about an order looks
               like a fact about an order wherever it turns up. */
.sch-clash {
  color: var(--tb-warn);
  display: block;
}
.sch-clash .bi { margin-right: .2rem; }
.queue-sub .sch-clash { display: inline-block; margin-left: .35rem; font-size: .78rem; }
.sch-delivered {
  display: inline-block;
  margin-left: .35rem;
  padding: 0 .35rem;
  border: 1px solid var(--tb-good);
  border-radius: 999px;
  background: var(--tb-surface);
  color: var(--tb-good);
  font-size: .72rem;
  font-weight: 600;
  vertical-align: middle;
  white-space: nowrap;
}
.sch-delivered .bi { margin-right: .2rem; }
/* --- end P-FIX2 --- */
/* --- P24-CSV ----------------------------------------------------------------
   Three things, one block: the files a price request carries (NS-46), the
   two NS-49 additions to the comparison panel, and the Xero download.

   Nothing here is a new colour. The attach rows borrow .req-sup's shape
   because they are the same kind of thing one card lower -- a tick box, a
   name and a fact about it -- and the cancel-the-rest card borrows the
   alert's amber because it is a STATUS (somebody is still waiting on you)
   and not an action; the one orange on it is the button, which is the
   .btn-primary the rest of the application already owns.
   -------------------------------------------------------------------------- */

.req-files .card-heading { margin-bottom: .35rem; }

.req-file {
  display: flex; align-items: flex-start; gap: .7rem;
  border: 1px solid var(--tb-line); border-radius: var(--tb-radius);
  padding: .55rem .8rem; margin-bottom: .4rem; cursor: pointer;
}
.req-file:hover { background: var(--tb-hover); }
.req-file-main { display: flex; flex-direction: column; min-width: 0; }
.req-file-name { font-weight: 600; word-break: break-word; }
.req-file-meta { font-size: .78rem; color: var(--tb-muted); }

/* The per-supplier line. Closed, it is one quiet row under the supplier;
   open, it is a box. A <details> rather than a permanently visible
   textarea because eight suppliers would otherwise be eight boxes
   nobody asked for. */
.req-sup-note { margin: -.2rem 0 .7rem 2.1rem; }
.req-sup-note > summary {
  font-size: .8rem; color: var(--tb-navy); cursor: pointer;
  list-style: none; padding: .15rem 0;
}
.req-sup-note > summary::-webkit-details-marker { display: none; }
.req-sup-note > summary::before { content: "+ "; color: var(--tb-faint); }
.req-sup-note[open] > summary::before { content: "\2212 "; }
.req-sup-note > textarea { margin-top: .35rem; }

/* NS-49: which lines a quote is being taken for. */
.cmp-pick-lines { margin-bottom: .5rem; }
.cmp-pick {
  display: flex; align-items: flex-start; gap: .5rem;
  padding: .2rem 0; cursor: pointer; font-size: .9rem;
}
.cmp-pick input { margin-top: .2rem; flex: 0 0 auto; }

/* NS-49: the offer to cancel what is still out. */
.cmp-cancel {
  border-left: 3px solid var(--tb-warn);
  background: #fffaf0;
}
.cmp-cancel .card-heading { color: var(--tb-warn); }

/* The Xero download, both places it appears. */
.xero-export .form-text { max-width: 68ch; margin-top: .5rem; }
.xero-export-link { white-space: nowrap; }

@media (max-width: 575.98px) {
  .req-sup-note { margin-left: 0; }
  .xero-export-link { width: 100%; }
}
/* --- end P24-CSV --- */

/* --- P-CLIENT --- */

/* The Client panel on the People tab, and the share toggle on Files. */
.clink-card .clink-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .8rem;
}
.clink-fresh {
  background: var(--tb-orange-soft);
  border-top: 1px solid var(--tb-line);
  border-bottom: 1px solid var(--tb-line);
}
.clink-table .clink-acts-h { width: 2.5rem; }
.clink-table .clink-acts { text-align: right; white-space: nowrap; }
.clink-dead td { color: var(--tb-faint); }
.clink-dead .row-link, .clink-dead .num { color: var(--tb-faint); }
.clink-new .clink-f { flex: 1 1 14rem; min-width: 10rem; }
.clink-new .form-text { margin-top: .5rem; }
.clink-files-note { max-width: 72ch; }

/* The toggle is a STATE, not an action, so it is navy and grey rather
   than orange -- orange is the member's own next move and this is a
   fact about a row. */
.clink-toggle {
  display: inline-flex; align-items: center; gap: .3rem;
  border: 1px solid var(--tb-line-2); background: var(--tb-surface);
  color: var(--tb-muted); border-radius: 999px;
  padding: .1rem .55rem; font-size: .78rem; line-height: 1.6;
  cursor: pointer;
}
.clink-toggle:hover { background: var(--tb-hover); border-color: var(--tb-navy); color: var(--tb-navy); }
.clink-toggle.is-on {
  background: #e8f1ff; border-color: var(--tb-navy); color: var(--tb-navy); font-weight: 600;
}

/* ------------------------------------------------------------------
   The client's own page. It reuses .cview-brand from P28 and nothing
   else: that page is one document on a phone and this one is a whole
   job, so the rhythm is sections rather than a single card.
   ------------------------------------------------------------------ */
.cjob { max-width: 44rem; margin: 0 auto; padding: 1.25rem 1rem 4rem; }
.cjob-head { margin-bottom: 1rem; }
.cjob-title { font-size: 1.5rem; font-weight: 700; margin: .35rem 0 .2rem; line-height: 1.2; }
.cjob-where { color: var(--tb-muted); font-size: .95rem; }
.cjob-builder { color: var(--tb-muted); font-size: .9rem; margin-top: .4rem; }
.cjob-intro {
  color: var(--tb-muted); font-size: .9rem; max-width: 60ch;
  border-left: 3px solid var(--tb-line-2); padding-left: .75rem; margin-bottom: 1.75rem;
}

.cjob-sec { margin-bottom: 2rem; }
.cjob-h {
  font-size: .78rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--tb-faint); font-weight: 600; margin-bottom: .6rem;
}
.cjob-empty { color: var(--tb-muted); font-size: .92rem; margin: 0; }
.cjob-now { font-size: .95rem; margin-bottom: .75rem; }
.cjob-sum { font-size: .9rem; color: var(--tb-muted); margin: .75rem 0 0; max-width: 60ch; }

/* The stage list. Green is a state and never an action, per the house
   rule, so a finished stage is --tb-good and the current one is navy. */
.cjob-stages { list-style: none; padding: 0; margin: 0; }
.cjob-stage { display: flex; gap: .6rem; align-items: baseline; padding: .4rem 0; }
.cjob-stage-mark { flex: 0 0 1.1rem; color: var(--tb-line-2); }
.cjob-stage-d { flex: 1 1 auto; }
.cjob-stage-n { display: block; color: var(--tb-muted); font-size: .84rem; }
.cjob-stage.is-complete .cjob-stage-mark { color: var(--tb-good); }
.cjob-stage.is-complete .cjob-stage-d { color: var(--tb-muted); }
.cjob-stage.is-current .cjob-stage-mark { color: var(--tb-navy); }
.cjob-stage.is-current .cjob-stage-d { font-weight: 700; }
.cjob-stage.is-upcoming .cjob-stage-d { color: var(--tb-faint); }

/* Claims and variations share a row. */
.cjob-rows { list-style: none; padding: 0; margin: 0; }
.cjob-row {
  display: flex; gap: .75rem; align-items: baseline;
  padding: .6rem 0; border-bottom: 1px solid var(--tb-line);
}
.cjob-row:last-child { border-bottom: 0; }
.cjob-row-d { flex: 1 1 auto; }
.cjob-row-n { display: block; color: var(--tb-muted); font-size: .84rem; }
.cjob-row-v { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
.cjob-tag {
  display: block; font-size: .72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; margin-top: .1rem;
}
.cjob-tag.is-paid  { color: var(--tb-good); }
.cjob-tag.is-owing { color: var(--tb-warn); }
.cjob-tag.is-dead  { color: var(--tb-faint); }

.cjob-files { list-style: none; padding: 0; margin: 0; }
.cjob-files li { border-bottom: 1px solid var(--tb-line); }
.cjob-files li:last-child { border-bottom: 0; }
.cjob-file {
  display: flex; gap: .6rem; align-items: baseline;
  padding: .6rem 0; text-decoration: none; color: var(--tb-navy);
}
.cjob-file:hover { color: var(--tb-navy-2); }
.cjob-file-d { flex: 1 1 auto; }
.cjob-file-n { display: block; color: var(--tb-muted); font-size: .84rem; }

.cjob-foot {
  margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--tb-line);
  color: var(--tb-muted); font-size: .88rem;
}
.cjob-foot-n { color: var(--tb-faint); font-size: .8rem; }

@media (max-width: 575.98px) {
  .cjob-row { flex-wrap: wrap; }
  .cjob-row-v { text-align: left; }
  .cjob-tag { display: inline; margin-left: .4rem; }
  .clink-new form { flex-direction: column; align-items: stretch; }
  .clink-new .btn { width: 100%; }
}
/* --- end P-CLIENT --- */
/* --- P-MEMBERSHIP -----------------------------------------------------------
   NS-29's grace period and NS-18's tier badge.

   TWO IDEAS, AND THEY USE DIFFERENT COLOUR FAMILIES ON PURPOSE.

   The grace period is a STATE, so it is amber -- the same amber the
   lapsed card and the pwned-password banner already use. Red/amber/green
   are status on this system and never actions. Grace is told apart from
   lapsed by the WORD and by the ICON, never by the shade: nobody can
   tell two ambers apart, and one man in twelve cannot tell amber from
   green at all.

   The tier badge is NOT a status and must not borrow those three. It is
   three MUTED METALLICS -- a warm brown, a cool grey and a soft ochre --
   each one desaturated enough to sit beside orange without arguing with
   it. Orange #F87800 is the action colour and the member's own position
   on the tier bar; a gold chip that shouted would read as a button. The
   ink in each chip is dark enough on its own tint to pass AA, because
   these appear at .74rem on a phone held at arm's length across a trade
   counter.

   A tier the reference list grows later gets `--plain`, which is the
   muted grey every other neutral chip on this system uses -- a new tier
   renders as a chip rather than as nothing. */

.tier-badge {
  display: inline-flex; align-items: center; gap: .3rem;
  font-size: .78rem; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; white-space: nowrap;
  padding: .2rem .55rem; border-radius: 999px;
  border: 1px solid transparent;
}
.tier-badge.is-small { font-size: .7rem; padding: .12rem .45rem; }
.tier-badge i { font-size: .8em; opacity: .75; }

.tier-badge--bronze { background: #f7efe5; border-color: #ddc7a9; color: #7d5a2e; }
.tier-badge--silver { background: #eef1f5; border-color: #c9d1dc; color: #56606d; }
.tier-badge--gold   { background: #fbf4dd; border-color: #e3d2a0; color: #7d6416; }
.tier-badge--plain  { background: var(--tb-sunk); border-color: var(--tb-line-2); color: var(--tb-muted); }

/* On the member's own card the chip sits on navy, so it keeps its light
   fill and loses the border, which would read as a seam against the
   gradient. The card's own orange band is still the brand mark. */
.mcard-tier { background: none; padding: 0; }
.mcard-tier .tier-badge { border-color: transparent; }

/* --- the grace period -------------------------------------------------------
   The card, within grace: the date goes amber like a lapsed one, but the
   BAND stays orange. The band is the brand mark; recolouring it would
   say "this card has stopped", and it has not. */
.mcard.is-grace .mcard-date { color: #ffcf6b; }
.mcard-warn.is-grace { border-left-color: #b07d16; }
.mcard-warn.is-grace i { color: #b07d16; }

/* The public verify page's fourth verdict. Amber, like lapsed -- see the
   header. The tick in the icon and the words "renewal due" are what tell
   them apart, and the verdict wraps rather than shrinking, because it is
   read at arm's length. */
.vcheck--grace  { border-top-color: var(--tb-warn); }
.vcheck--grace  .vcheck-verdict { color: var(--tb-warn); }
.vcheck--grace  .vcheck-verdict span { font-size: .78em; line-height: 1.15; }

/* The counter's chip. Same amber as LAPSED, different word. */
.scan-state.is-grace { border-color: var(--tb-warn); color: var(--tb-warn); background: #fdf5e3; }

/* --- the amber bar in the member chrome -------------------------------------
   Above the flashes, below the breach banner. The same construction as
   .pwned-banner, which is the one other thing on this system that is
   neither a flash nor page content -- so the two stack without arguing
   about what a full-width amber strip means. */
.renew-banner {
  display: flex; align-items: flex-start; gap: .6rem; flex-wrap: wrap;
  background: #fff8e6; border: 1px solid #f3e2b8; border-left: 3px solid var(--tb-warn);
  border-radius: var(--tb-radius); padding: .7rem .9rem; margin-bottom: 1rem;
  font-size: .92rem;
}
.renew-banner > i { color: var(--tb-warn); margin-top: .15rem; font-size: 1.05rem; }
.renew-banner-t { flex: 1 1 18rem; min-width: 0; }
.renew-banner-s { color: var(--tb-muted); font-size: .85rem; margin-top: .15rem; }
.renew-banner-f { margin: 0; flex: none; }
.renew-banner-x { margin: 0; flex: none; }
.renew-banner-x .btn-close { font-size: .7rem; opacity: .5; }
.renew-banner-x .btn-close:hover { opacity: .9; }

/* "Renewal requested 18/09/2026" -- what replaces the button for seven
   days. GREEN, and not a disabled button: a greyed-out button is
   something to keep pressing, and a sentence saying it is done is not. */
.renew-done {
  display: inline-flex; align-items: center; gap: .35rem; flex: none;
  color: var(--tb-good); font-weight: 600; font-size: .88rem; white-space: nowrap;
}

/* The sentence above the Renew button on the account page. */
.renew-note { font-size: .88rem; color: var(--tb-muted); margin-bottom: .75rem; }
.renew-note.is-grace  { color: var(--tb-warn); }
.renew-note.is-lapsed { color: var(--tb-warn); }

/* NS-18. What is left on the Ledger where the three benefit columns
   were: one line, the tracked-spend sentence and the free-membership
   rule, which is the only one of those benefits that was ever real. */
.tier-note { font-size: .88rem; color: var(--tb-muted); margin: -.25rem 0 1rem; }
.tier-note strong { color: var(--tb-ink); }

@media (max-width: 575.98px) {
  /* The button goes full width under the words rather than being
     squeezed beside them -- it is the one thing on the bar to press. */
  .renew-banner-f { flex: 1 1 100%; }
  .renew-banner-f .btn { width: 100%; }
  .renew-banner-x { order: -1; margin-left: auto; }
}
/* --- end P-MEMBERSHIP --- */

/* --- P-SCOREMIX --- */
/* NS-47: every comparison on the scorecard now says WHAT IT IS AGAINST,
   and a comparison with fewer than `scorecard.min_group` suppliers
   behind it is replaced by the sentence saying so. Both are the same
   slot on the tile -- one line, under the figures, in the same place on
   every window of every measure -- because a reader has to be able to
   find "what am I being compared with" without hunting for it, and
   because the absence of a comparison has to be as visible as its
   presence. A blank where the line normally sits reads as a page that
   failed to load. */
.sc-vs {
  display: block; margin-top: .25rem;
  font-size: .7rem; line-height: 1.3;
  color: var(--tb-muted);
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
}

/* The not-enough line. Fainter and italic: it is an absence, and it must
   not be mistaken at a glance for a figure. Never red -- there is
   nothing wrong with being the only supplier in your trade, and red on
   this card means a measure that needs attention. */
.sc-vs-thin { color: var(--tb-faint); font-style: italic; }

/* The within-trade line sits under the network one on the win rate, so
   it gets a little air and a rule to say it is a second thing and not a
   continuation of the first. */
.sc-vs-trade {
  margin-top: .3rem; padding-top: .3rem;
  border-top: 1px solid var(--tb-line);
}

@media (max-width: 575.98px) {
  /* Stacked windows are already narrow; the comparison line wraps to two
     lines there and needs the room between it and the next window. */
  .sc-vs { margin-top: .3rem; }
}
/* --- end P-SCOREMIX --- */
/* --- P-GST -----------------------------------------------------------------
   GST, NS-64. Every figure this system stores is ex-GST; the documents
   that leave it -- a client's claim statement, a variation's sign-off
   page, a supplier's purchase order -- add GST as a line.

   THIS BLOCK ADDS NO COLOUR AND NO NEW TOKEN. A GST line is not a
   status, so it never takes red, amber or green; it is not an action, so
   it never takes orange. It is working: the arithmetic under the figure
   that matters, in the muted ink the rest of the application already
   uses for a subtitle. Four rules, two of which are one word wide.
   ------------------------------------------------------------------------ */

/* The subtotal / GST rows above a big total -- on the client's claim
   statement and the variation sign-off page, both of which sit on
   layout/plain and carry their own light palette rather than the app
   tokens (see .cview-* and .vsign-* above, which hard-code the same
   greys). Same row shape as .cview-total, one step down in weight, so
   the eye runs past them to the figure it is looking for. */
.gst-sub {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  font-size: .92rem; color: #5a6478; padding: .15rem 0;
}
.gst-sub:first-of-type { border-top: 1px solid #e3e6ee; padding-top: .6rem; margin-top: .5rem; }
.gst-sub-v { font-variant-numeric: tabular-nums; }

/* "ex $48,640.00 · GST $4,864.00" under an inc-GST figure, inside a
   table cell or a definition list. Reuses .sub's meaning -- a fact about
   the line above -- and only adds the tabular figures, because these are
   numbers a member reads down a column. */
.gst-work {
  display: block; font-weight: 400; font-size: .8rem; color: var(--tb-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* "All figures ex GST", once per screen. Deliberately the quietest thing
   on the page: it is a caption for forty numbers, not a warning about
   one, and a member should read it the first time and never again. */
.gst-page-note {
  font-size: .82rem; color: var(--tb-muted); margin: -.25rem 0 .75rem;
}
.gst-page-note.is-inline { display: inline; margin: 0; }
.gst-page-note strong, .gst-page-note em { color: var(--tb-ink); }

/* A two-line column heading -- "Total / inc GST" -- so the Claims tab
   can say which side of GST its money column is on without a wider
   table. */
.clm-th-sub { display: block; font-weight: 400; font-size: .75rem; color: var(--tb-muted); }

/* The inc-GST row of a purchase order's footer is the one a supplier
   checks against their invoice, so it carries the rule the plain total
   used to. */
.po-total-inc th { border-top: 2px solid var(--tb-line); }
/* --- end P-GST --- */

/* --- P-TAXINV --- */
/* The compliant header on the claim statement (/c/{token}) and the two
   lines the account page shows of it. Tokens and greys as .cview-*
   above, one step quieter: this is a letterhead, not the figure the
   client is reading for. */
.taxinv-word {
  font-size: .8rem; font-weight: 700; letter-spacing: .14em;
  color: var(--tb-navy, #1c2333); margin: .35rem 0 .6rem;
}
.taxinv-word-sub { font-weight: 400; letter-spacing: .04em; color: #5a6478; margin-left: .4rem; }
.taxinv-abn { color: #1c2333; font-size: .9rem; }
.taxinv-line { display: block; color: #5a6478; font-size: .85rem; }
/* Invoice / date of issue / to, in the same two-column grid the bank
   details use, so the two cards read as one document. */
.taxinv-meta { display: grid; grid-template-columns: 9rem 1fr; gap: .35rem 1rem; margin: 0; }
.taxinv-meta dt { font-weight: 400; color: #5a6478; }
.taxinv-meta dd { margin: 0; font-weight: 600; }
/* The ATO's words, under the total they are about. */
.taxinv-incgst {
  margin-top: .5rem; text-align: right; color: #5a6478; font-size: .85rem;
}
/* /account/business: what the claim header will say, read-only. */
.taxinv-head-note { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--tb-line, #eef0f4); }
.taxinv-head-dl { display: grid; grid-template-columns: 7rem 1fr; gap: .3rem 1rem; margin: .4rem 0 0; }
.taxinv-head-dl dt { font-weight: 400; color: #5a6478; }
.taxinv-head-dl dd { margin: 0; }
@media (max-width: 420px) {
  .taxinv-meta, .taxinv-head-dl { grid-template-columns: 1fr; gap: 0 0; }
  .taxinv-meta dd, .taxinv-head-dl dd { margin-bottom: .45rem; }
}
/* --- end P-TAXINV --- */

/* --- P-RENEW --- */
/* A member paying their own renewal, on pages they reach with NO LOGIN:
   the chrome-less layout/plain, a phone in a ute, often at night. Every
   rule below is for those five pages and the two buttons that lead to
   them; nothing here restyles anything that existed before. */

/* The Renew button that used to be a submit and is now a link. Sized
   like the form it replaced so the amber bar does not shift, and full
   width on a phone where it is the thing being aimed at. */
.renew-go { flex: none; }
@media (max-width: 575.98px) {
  .renew-go { width: 100%; }
}

/* The page itself. Narrow, centred and capped: this is one decision with
   one number on it, and a wide column would make it look like a form to
   fill in. */
.renew-wrap {
  max-width: 34rem; margin: 0 auto; padding: 1.5rem 1rem 3rem;
}
.renew-card { padding: 1.35rem 1.4rem; }
.renew-biz {
  font-weight: 600; color: var(--tb-ink); margin-bottom: 1rem;
}
.renew-biz .sub { font-weight: 400; color: var(--tb-muted); font-size: .85rem; }
.renew-kv { margin-bottom: 1rem; }

/* The three money lines. Tabular figures so the cents line up under each
   other -- a fee table where the decimal points wander is the one a
   bookkeeper does not trust -- and a rule above the total, which is the
   number that will appear on the statement. */
.renew-fee {
  width: 100%; margin: 0 0 1.1rem; font-size: .95rem;
  font-variant-numeric: tabular-nums;
}
.renew-fee th {
  text-align: left; font-weight: 400; color: var(--tb-muted);
  padding: .3rem 0;
}
.renew-fee td { text-align: right; padding: .3rem 0; white-space: nowrap; }
.renew-fee-total th, .renew-fee-total td {
  border-top: 1px solid var(--tb-line); padding-top: .5rem;
  font-weight: 700; color: var(--tb-ink); font-size: 1.05rem;
}
.renew-fee-total th { color: var(--tb-ink); }
.renew-fee-total .sub { font-weight: 400; font-size: .8rem; color: var(--tb-muted); }

/* Two ways to pay, side by side on a desktop and stacked on a phone.
   Card is the orange action; transfer is an outline BESIDE it, not a
   lesser thing said in grey -- the office is happy with either. */
.renew-actions { display: flex; flex-wrap: wrap; gap: .6rem; }
.renew-actions form { margin: 0; flex: 1 1 13rem; }
.renew-actions .btn { width: 100%; }

/* The transfer reference. The largest thing on the deposit page, in a
   box of its own, because a transfer that arrives without it costs
   somebody an afternoon of matching. Monospace and letter-spaced so it
   can be copied by eye off a phone onto a banking app. */
.renew-ref {
  border: 1px dashed var(--tb-line); border-radius: var(--tb-radius);
  background: var(--tb-sunk); padding: .7rem .9rem; margin-bottom: 1rem;
}
.renew-ref-l {
  display: block; font-size: .78rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--tb-muted); margin-bottom: .15rem;
}
.renew-ref-v {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.15rem; letter-spacing: .04em; color: var(--tb-ink);
  word-break: break-all;
}

/* The verdict on the way home. A word as well as a colour -- one man in
   twelve cannot tell the green from the amber -- the same rule the
   membership card's verify page follows. */
.renew-verdict {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  font-size: 1.35rem; font-weight: 700; color: var(--tb-good);
  margin-bottom: .75rem;
}
.renew-verdict i { font-size: 1.5rem; }
.renew-verdict--quiet { color: var(--tb-muted); }

/* And the answer to the question they came with: the new expiry date. */
.renew-newexp {
  text-align: center; border: 1px solid var(--tb-line);
  border-radius: var(--tb-radius); background: var(--tb-sunk);
  padding: .8rem; margin-bottom: 1rem;
}
.renew-newexp-l {
  display: block; font-size: .8rem; color: var(--tb-muted);
  text-transform: uppercase; letter-spacing: .04em;
}
.renew-newexp-v { font-size: 1.5rem; color: var(--tb-ink); }
/* --- end P-RENEW --- */

/* --- P-STAFF-1 ---
   The staff area.

   ONE RULE DECIDES EVERY COLOUR HERE, and it is the one at the top of
   this file: navy is STRUCTURE, orange is ACTION and the member's own
   position, red/amber/green are STATUS and never actions. The staff
   area is the system looking at itself, so its bar is navy with NO
   orange mark -- the member bar is navy plus orange, the supplier bar
   is teal, and the one with no customer colour in it is the one that
   can change everybody's records. Somebody with three tabs open can
   tell them apart without reading the tab.

   Nothing below restyles a shared class outright. Where a staff screen
   needs a shared element to sit differently (.section-card has no
   padding of its own, because the member templates add Bootstrap's),
   the rule is scoped under `body:has(.app-navbar--staff)` so the member
   and supplier pages are untouched. */

.app-navbar--staff { background: var(--tb-navy); }
.brand-tag--staff {
  background: rgba(255,255,255,.16); color: #fff;
  border: 1px solid rgba(255,255,255,.3);
}

/* The staff templates use .section-card bare. Padding here rather than a
   p-3 on forty elements, and scoped so nothing else moves. */
body:has(.app-navbar--staff) .section-card { padding: 1rem 1.15rem; }
body:has(.app-navbar--staff) .entity-bar { background: var(--tb-navy); }
body:has(.app-navbar--staff) .login-side { background: var(--tb-navy); }

.page-head { margin-bottom: 1rem; }
.page-head h1 { font-size: 1.45rem; font-weight: 700; margin: 0; }
.page-sub { color: var(--tb-muted); font-size: .9rem; margin: .2rem 0 0; }

.section-title {
  font-size: .95rem; font-weight: 700; margin: 0 0 .6rem;
  display: flex; align-items: center; gap: .45rem;
}
.form-subhead {
  font-size: .78rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--tb-muted); margin: .4rem 0 0;
}
.empty-line { color: var(--tb-muted); font-size: .92rem; margin: 0; }
.table-wrap { overflow-x: auto; }

/* Two columns at desk width, one on a phone. Two and not four: these are
   labelled fields somebody reads down, and a four-column form of thirty
   boxes is a wall. */
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .65rem .9rem; }
.form-grid > .span-2 { grid-column: 1 / -1; }
@media (max-width: 767.98px) { .form-grid { grid-template-columns: 1fr; } }

/* --- the desk ------------------------------------------------------- */
.staff-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: .75rem; }
.staff-tile {
  background: var(--tb-surface); border-radius: var(--tb-radius); box-shadow: var(--tb-shadow);
  padding: .85rem 1rem; text-decoration: none; color: inherit; display: block;
  border-left: 3px solid var(--tb-navy);
}
.staff-tile:hover { background: var(--tb-hover); }
.staff-tile-n { display: block; font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.staff-tile-l { display: block; font-size: .82rem; color: var(--tb-muted); }

/* The quick search. The result list is positioned over the page rather
   than pushing it down: a list that moves the form while somebody is
   typing into it is how a click lands on the wrong row. */
.staff-quick-wrap { position: relative; }
.staff-quick-results {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + .2rem);
  margin: 0; padding: .25rem; list-style: none; max-height: 22rem; overflow-y: auto;
  background: var(--tb-surface); border: 1px solid var(--tb-line-2);
  border-radius: var(--tb-radius); box-shadow: 0 6px 20px rgba(16,24,40,.14);
}
.staff-quick-hit {
  display: flex; align-items: baseline; gap: .55rem; padding: .35rem .55rem;
  border-radius: .3rem; text-decoration: none; color: inherit;
}
.staff-quick-hit:hover { background: var(--tb-hover); }
.staff-quick-hit.is-inactive { color: var(--tb-muted); }
.staff-quick-kind {
  flex: 0 0 auto; font-size: .68rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--tb-muted); min-width: 4.4rem;
}
.staff-quick-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.staff-quick-no { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--tb-muted); font-size: .84rem; }

/* --- the record summary strip --------------------------------------- */
.staff-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .75rem; }
.staff-stat {
  background: var(--tb-surface); border-radius: var(--tb-radius); box-shadow: var(--tb-shadow);
  padding: .7rem .9rem;
}
.staff-stat-l { display: block; font-size: .74rem; font-weight: 700; letter-spacing: .04em;
                text-transform: uppercase; color: var(--tb-muted); }
.staff-stat-n { display: block; font-size: 1.25rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.staff-stat-s { display: block; font-size: .78rem; color: var(--tb-muted); }

/* --- filters -------------------------------------------------------- */
.staff-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: .6rem .8rem; align-items: end; }
.staff-filters .grow { grid-column: span 2; }
.staff-filters-actions { grid-column: span 2; align-self: end; }
@media (max-width: 575.98px) {
  .staff-filters { grid-template-columns: 1fr; }
  .staff-filters .grow, .staff-filters-actions { grid-column: span 1; }
}

.staff-table th a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--tb-line-2); }
.staff-pager { display: flex; align-items: center; gap: .75rem; margin-top: .75rem; }

/* --- status pills ----------------------------------------------------
   STATUS COLOURS, so red / amber / green and never orange. `active` is
   green because it is the healthy state; `inactive` is red because the
   business is gone; `prospective`, `limbo`, `subcontracting` and
   `retired` are amber or grey -- they are neither, and colouring them
   red would make a third of the supplier list look broken. */
.pill {
  display: inline-block; font-size: .72rem; font-weight: 600; line-height: 1.5;
  padding: .1rem .5rem; border-radius: 1rem; border: 1px solid transparent;
}
.pill--active         { background: #e7f5ee; color: var(--tb-good); border-color: #bfe3d0; }
.pill--inactive       { background: #fdeceb; color: var(--tb-crit); border-color: #f5c9c6; }
.pill--prospective,
.pill--limbo,
.pill--subcontracting { background: #fdf4e0; color: var(--tb-warn); border-color: #ecd9a8; }
.pill--retired,
.pill--soft           { background: var(--tb-sunk); color: var(--tb-muted); border-color: var(--tb-line-2); }

/* --- contacts ------------------------------------------------------- */
.contact-row {
  border: 1px solid var(--tb-line); border-radius: var(--tb-radius);
  padding: .75rem .85rem; margin-bottom: .6rem;
}
.contact-row.is-off { background: var(--tb-sunk); }
.contact-row-foot { display: flex; flex-wrap: wrap; gap: 1.25rem; align-items: center; }

.cat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .25rem .75rem; }

/* --- saving --------------------------------------------------------- */
.staff-save-bar {
  position: sticky; bottom: 0; z-index: 20;
  display: flex; align-items: center; gap: .85rem; flex-wrap: wrap;
  background: var(--tb-surface); border-top: 1px solid var(--tb-line);
  border-radius: var(--tb-radius); box-shadow: 0 -2px 10px rgba(16,24,40,.06);
  padding: .7rem 1rem;
  padding-bottom: calc(.7rem + env(safe-area-inset-bottom, 0px));
}

/* The status box is bordered in the crit colour because of what it does
   -- it signs every person at a supplier out -- and the BUTTON inside it
   is an outline-danger for the same reason. That is the one place in
   this application where red is on a control, and it is deliberate: the
   colour is describing the consequence, which is a status. */
.status-card { border-left: 3px solid var(--tb-crit); }
.status-form { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
.status-form .form-control { max-width: 26rem; }

/* --- the activity list ---------------------------------------------- */
.staff-log { list-style: none; margin: 0; padding: 0; font-size: .88rem; }
.staff-log li {
  display: flex; gap: .6rem; padding: .3rem 0;
  border-bottom: 1px solid var(--tb-line); flex-wrap: wrap;
}
.staff-log li:last-child { border-bottom: 0; }
.staff-log-when { flex: 0 0 auto; color: var(--tb-muted); font-variant-numeric: tabular-nums; min-width: 8.5rem; }
.staff-log-who  { flex: 0 0 auto; font-weight: 600; min-width: 8rem; }
.staff-log-what { flex: 1 1 14rem; min-width: 0; }

/* --- the sign-in page ----------------------------------------------- */
.plain-main--narrow { grid-template-columns: 1fr; place-items: center; padding: 2rem 1rem; }
.plain-main--narrow .login-form-wrap { width: 100%; max-width: 27rem; }
/* --- end P-STAFF-1 --- */
