/* NPT — Eurotank house colours.
   Taken from eurotank.co.uk: black ground, lime #99CC00 accent, white text.
   Dark is the brand-faithful default; light is a properly designed alternative
   for anyone who works in a bright office all day. */

:root {
  /* ---- light ---- */
  --ground:#F3F5EE; --surface:#FFFFFF; --surface-2:#ECEFE3; --surface-3:#E1E5D5;
  --ink:#111311; --ink-2:#494D44; --ink-3:#6E7368;
  --rule:#D8DCCC; --rule-strong:#B7BDA8;

  --brand:#99CC00;              /* the lime, unaltered — used as a fill */
  --accent:#99CC00;
  --accent-ink:#55730A;         /* darkened for legible text on light grounds */
  --accent-soft:#EAF3CD;
  --on-accent:#0E1200;          /* black on lime, exactly as the website does it */

  --warn:#8A5A00; --warn-soft:#F7EBD4;
  --crit:#A2352A; --crit-soft:#F8E3E0;
  --ok:#4A6B00;   --ok-soft:#E8F2CF;
  /* The brand lime, taken down to something you can read beside. Same hue
     family, roughly half the saturation, so it still says Eurotank without
     shouting it. Used for the open-row spine and for a check that has passed. */
  --accent-calm:#7A9A33;

  /* ------------------------------------------- HOW FIRM IS THIS (217)
   *
   * One set of five, used everywhere something can be more or less settled: a
   * booking on the week board, a bar on the Timeline, a row on the Sheet. The
   * whole point is that the reader learns them once.
   *
   *   grey   pencilled — somebody expects it. Nothing has confirmed it.
   *   blue   scheduled — it is in the diary. JobWatch has it, or it was booked.
   *   green  confirmed — scheduled AND attached to a project. The money knows.
   *   amber  needs a look — it half agrees. Somebody has to decide.
   *   red    problem — it contradicts something. A clash, a double-booking.
   *
   * Green, amber and red are ALIASES, not new colours: they are the same --ok,
   * --warn and --crit that every chip, banner and flag in NPT already uses, so
   * red means the same thing on this board as it does on a purchase order. A
   * var() inside a custom property is resolved where it is used, so these
   * follow the dark theme without being restated. Only the blue is new, because
   * nothing here had one.
   *
   * Each state sets two things — an edge and a fill — and components read those
   * rather than the colours, which is what lets a chip, a row spine and a tile
   * all take the standard without any of them knowing about the others. */
  --st-pencil:var(--ink-3);  --st-pencil-soft:transparent;
  --st-plan:#2E5E8C;         --st-plan-soft:#E3ECF4;
  --st-live:var(--ok);       --st-live-soft:var(--ok-soft);
  --st-look:var(--warn);     --st-look-soft:var(--warn-soft);
  --st-stop:var(--crit);     --st-stop-soft:var(--crit-soft);

  --mono: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", monospace;
  --sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Arial, sans-serif;
}

/* Dark tokens, applied both by explicit choice and by OS preference. */
:root[data-theme="dark"] {
  --ground:#0A0A0A; --surface:#141414; --surface-2:#1D1D1D; --surface-3:#2D2D2D;
  --ink:#FFFFFF; --ink-2:#B4B7AE; --ink-3:#868A80;
  --rule:#282828; --rule-strong:#3D3D3D;

  --brand:#99CC00;
  --accent:#99CC00;
  --accent-ink:#A9DB1A;
  --accent-soft:#1E2A00;
  --on-accent:#0A0A0A;

  --warn:#E0A93C; --warn-soft:#2E2411;
  --crit:#E8776B; --crit-soft:#301A17;
  --ok:#99CC00;   --ok-soft:#1E2A00;
  --accent-calm:#7E9C42;
  /* The one status colour with no daylight equivalent to inherit. Lifted and
     desaturated so it reads on #141414 the way #2E5E8C reads on white. */
  --st-plan:#7FB6E6; --st-plan-soft:#122334;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:#0A0A0A; --surface:#141414; --surface-2:#1D1D1D; --surface-3:#2D2D2D;
    --ink:#FFFFFF; --ink-2:#B4B7AE; --ink-3:#868A80;
    --rule:#282828; --rule-strong:#3D3D3D;
    --accent-ink:#A9DB1A;
    --accent-soft:#1E2A00;
    --on-accent:#0A0A0A;
    --warn:#E0A93C; --warn-soft:#2E2411;
    --crit:#E8776B; --crit-soft:#301A17;
    --ok:#99CC00;   --ok-soft:#1E2A00;
    --accent-calm:#7E9C42;
    --st-plan:#7FB6E6; --st-plan-soft:#122334;
  }
}

* { box-sizing: border-box; }
html, body { margin:0; padding:0; }
body {
  background: var(--ground); color: var(--ink);
  font-family: var(--sans); font-size: 15px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------- shell measurements
   One gutter, one maximum width, used by the bars and the content alike so they
   line up. The width was 1180px, which is narrower than most of the screens this
   runs on: the projects table needs about 1130px, so at 125% display scaling —
   a 1440px laptop reporting ~1150 CSS pixels — the last column fell off the edge
   into a scroll container with nothing to show it was there. */
:root {
  --shell:1560px; --gutter:clamp(14px, 1.8vw, 30px);
  /* The frozen bars. Anything else that pins itself has to clear them, so the
     height is a token rather than a number repeated in three places. */
  --topbar-h:56px; --tabs-h:41px; --header-h:calc(var(--topbar-h) + var(--tabs-h));
}

/* ------------------------------------------------------------------- topbar */
/* Frozen. A project runs to about three thousand pixels, and the tabs used to
   scroll away within the first screenful — so moving between screens meant
   scrolling back to the top first, every time. z-index above the panels but
   below the toast, which has to be readable over everything. */
.topbar {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:0 var(--gutter); border-bottom:1px solid var(--rule); background:var(--surface);
  min-height:var(--topbar-h);
  position:sticky; top:0; z-index:30;
}
.brand { display:flex; align-items:center; gap:12px; min-width:0; }
.mark {
  font-family:var(--mono); font-weight:700; font-size:13px; letter-spacing:.16em;
  color:var(--on-accent); background:var(--brand); padding:5px 9px; border-radius:3px;
}
.sub { color:var(--ink-2); font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sub b { color:var(--ink); font-weight:600; }
.topright { display:flex; align-items:center; gap:14px; }
.health { font-family:var(--mono); font-size:12px; color:var(--ink-3); white-space:nowrap; }
.health .dot { display:inline-block; width:7px; height:7px; border-radius:50%; margin-right:6px; vertical-align:middle; }
.health.up .dot { background:var(--brand); }
.health.down .dot { background:var(--crit); }
.health.down { color:var(--crit); }
.themetoggle {
  appearance:none; border:1px solid var(--rule-strong); background:var(--surface);
  color:var(--ink-2); border-radius:4px; padding:5px 9px; font:inherit; font-size:12px;
  cursor:pointer; line-height:1;
}
.themetoggle:hover { color:var(--ink); border-color:var(--brand); }

/* --------------------------------------------------------------------- tabs */
.tabs {
  display:flex; gap:2px; padding:0 var(--gutter); border-bottom:1px solid var(--rule);
  background:var(--surface); overflow-x:auto;
  position:sticky; top:var(--topbar-h); z-index:29;
}
.tabs button {
  appearance:none; background:none; border:none; border-bottom:2px solid transparent;
  color:var(--ink-2); font:inherit; font-size:14px; padding:11px 14px; cursor:pointer;
  white-space:nowrap; margin-bottom:-1px;
}
.tabs button:hover { color:var(--ink); }
.tabs button[aria-selected="true"] { color:var(--ink); border-bottom-color:var(--brand); font-weight:600; }
.tabs button:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible {
  outline:2px solid var(--brand); outline-offset:2px; border-radius:3px;
}

/* --------------------------------------------------------------------- main */
main { padding:24px var(--gutter); max-width:var(--shell); margin:0 auto; }
.head { margin:0 0 4px; font-size:20px; font-weight:650; letter-spacing:-.01em; }
.lede { color:var(--ink-2); margin:0 0 20px; max-width:70ch; font-size:14.5px; }
.lede strong { color:var(--ink); }

.panel { background:var(--surface); border:1px solid var(--rule); border-radius:5px; margin-bottom:20px; }
/* .movepanel builds a bare <div> for its card, not a .panel, so the child
   selector below matched nothing inside a dialog: the head rendered as naked
   text hard against the corner of the box while the body kept its padding.
   Listing the dialog here fixes all six modals at once, rather than adding a
   class at six call sites and hoping the seventh remembers. */
.panel > .panel-head,
.movepanel > div > .panel-head {
  padding:11px 16px; border-bottom:1px solid var(--rule); background:var(--surface-2);
  font-family:var(--mono); font-size:11px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--ink-2); font-weight:600; border-radius:5px 5px 0 0;
}
.panel-body { padding:16px; }
/* A dialog sits on top of everything with its own edge a few pixels from the
   text, so the panel-side inset reads as cramped against it. */
.movepanel > div > .panel-body { padding:18px 20px 20px; }

/* ------------------------------------------------------------------- tables */
/* A table wider than its panel still scrolls, but it no longer does it in
   silence: the shadows appear only on the side there is more to see, painted by
   the classic local/scroll background pair rather than by script. Without them a
   clipped column looks exactly like a column that is not there. */
.tablewrap {
  overflow-x:auto; scrollbar-width:thin;
  background:
    linear-gradient(to right, var(--surface) 30%, transparent) left / 28px 100% no-repeat local,
    linear-gradient(to left,  var(--surface) 30%, transparent) right / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.17), transparent) left / 11px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.17), transparent) right / 11px 100% no-repeat scroll;
}
table { border-collapse:collapse; width:100%; font-size:14px; }
th, td { text-align:left; padding:9px 12px; border-bottom:1px solid var(--rule); vertical-align:middle; }
thead th {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600; background:var(--surface-2); white-space:nowrap;
}
tbody tr:last-child td { border-bottom:none; }
tbody tr:hover { background:var(--surface-2); }
td.shrink { width:1%; white-space:nowrap; }
.muted { color:var(--ink-3); }
/* A muted note that is actually a refusal — a rejected JobWatch reference, say.
   Same size and place, so nothing jumps, but not the colour of a quiet aside. */
.muted.crit { color:var(--crit); }
.empty { padding:28px 16px; text-align:center; color:var(--ink-3); font-size:14px; }

/* ------------------------------------------------------------------ controls */
input[type=text], input[type=email], input[type=number], select {
  font:inherit; font-size:14px; color:var(--ink); background:var(--surface);
  border:1px solid var(--rule-strong); border-radius:4px; padding:6px 9px; min-width:0;
}
input:hover, select:hover { border-color:var(--ink-3); }
input:disabled, select:disabled { background:var(--surface-2); color:var(--ink-3); }
select { cursor:pointer; }
label { font-size:13px; color:var(--ink-2); display:block; margin-bottom:4px; }

/* Buttons, and the links that are meant to read as buttons. These rules named
   button.btn only, so <a class="btn"> matched none of them: the Download link
   in Files has been rendering as bare underlined text, and the guide links in
   Help would have done the same. An anchor needs the two extra declarations a
   button gets for free. */
button.btn, a.btn {
  font:inherit; font-size:14px; cursor:pointer; border-radius:4px; padding:7px 13px;
  border:1px solid var(--rule-strong); background:var(--surface); color:var(--ink);
}
a.btn { display:inline-block; text-decoration:none; line-height:normal; }
button.btn:hover, a.btn:hover { background:var(--surface-2); border-color:var(--ink-3); }
button.btn.primary, a.btn.primary {
  background:var(--brand); border-color:var(--brand); color:var(--on-accent); font-weight:600;
}
button.btn.primary:hover, a.btn.primary:hover { filter:brightness(1.1); background:var(--brand); }
button.btn.primary:disabled { opacity:.45; cursor:not-allowed; filter:none; }
button.btn.danger, a.btn.danger { color:var(--crit); border-color:var(--crit); background:var(--crit-soft); }
button.btn.small, a.btn.small { padding:4px 9px; font-size:13px; }

/* The project detail opens as a row inside the table, directly beneath the
   project it belongs to, so the connection between the two is positional and
   needs no explaining. The accent down the left is what carries it. */
/* The spine down an open row uses --accent-calm, not the brand lime.
   #99CC00 at full strength is a signage colour: right for the logo and for the
   one primary button on a screen, wrong for a 3px rule you sit and read beside
   for a minute. Muted and darkened it still reads as "this row is open" without
   pulling the eye off the form inside it. */
tr.detail-row > td {
  padding:0 0 0 3px; background:var(--accent-calm);
  border-bottom:2px solid var(--rule-strong);
  /* A cell is sized by its content, so the widest thing inside an open project
     -- usually the activities table -- was setting the width of the projects
     table around it. Above 1200px that table's scroll container is stood down
     (see .tablewrap:has(tr.detail-row) below), so the excess had nowhere to go
     and ran off the right of the window with nothing to scroll it back.
     max-width:0 against a width:100% table stops the cell reporting its content
     width, which leaves each inner .tablewrap to scroll on its own account. */
  max-width:0;
}
tr.detail-row .detail-wrap {
  background:var(--ground); padding:14px 16px 4px;
}
tr.detail-row .detail-main > .panel:first-child { margin-top:0; }

/* The open project, in two columns.
 *
 * The conversation used to sit under everything else — under the activities,
 * the job references, the purchase orders and the files — about three thousand
 * pixels down. Nobody scrolls the whole of a project to find out whether
 * somebody asked them a question, so in practice nobody read it.
 *
 * It is a column of its own now, and a sticky one: the work scrolls, the
 * conversation stays. Below 1200px there is not room for two, so it goes back
 * under the work rather than being squeezed into something unreadable. */
.detail-grid {
  display:grid; grid-template-columns:minmax(0,1fr) minmax(300px, 370px);
  gap:16px; align-items:start;
}
.detail-main { min-width:0; }              /* or a wide table stretches the column */
.detail-side { position:sticky; top:calc(var(--header-h) + 12px); }

/* position:sticky is inert inside a scroll container, and .tablewrap is one —
   it carries overflow-x:auto so a wide table can scroll rather than be clipped.
   That made the sticky column silently do nothing: it scrolled away with the
   rest, which looks like the rule was never written.
 *
 * While a project is open the table is not scrolling horizontally anyway — the
 * two-column layout only exists above 1200px, and the projects table fits well
 * inside that. So the scroll container is stood down for as long as a detail
 * row is open, and the edge shadows with it, since there is now no edge. */
.tablewrap:has(tr.detail-row) { overflow-x:visible; background:none; }
.detail-side > .panel { margin-bottom:0; }

/* A change that has just been jumped to from the note explaining it. Without
   something marking the landing, the feed simply moves and you are left working
   out which row was meant. */
@keyframes fe-flash { from { background:var(--accent-soft); } to { background:transparent; } }
.fe.flash { animation:fe-flash 1.6s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .fe.flash { animation:none; box-shadow:inset 3px 0 0 var(--brand); }
}

/* The feed takes whatever height is left on screen and scrolls inside itself,
   so the panel never grows past the window and take the compose box with it. */
.detail-side .feed { max-height:calc(100vh - var(--header-h) - 300px); }

/* A 370px column has no room for a row of side-by-side fields, or for the
   timestamp gutter the full-width feed uses. */
.detail-side .formrow { gap:8px; }
.detail-side .formrow > .f { flex:1 1 100%; }
.detail-side .fe { grid-template-columns:1fr; gap:2px 0; padding:9px 14px; }
.detail-side .fe-when { font-size:10.5px; }

@media (max-width:1200px) {
  .detail-grid { grid-template-columns:1fr; }
  .detail-side { position:static; }
  .detail-side .feed { max-height:460px; }
  .tablewrap:has(tr.detail-row) { overflow-x:auto; }
}
/* The row above stays visibly the one this belongs to. */
tr[data-id]:has(+ tr.detail-row) > td { background:var(--surface-2); }

/* Pinning. The control stays out of the way until the chip is approached, and
   is permanently visible once set — a pin you cannot see is a pin you forget
   you made. The app has no icons anywhere, so this is a word like everything
   else on the screen. */
.chip .pin {
  font:inherit; font-size:9.5px; letter-spacing:.06em; text-transform:uppercase;
  margin-left:6px; padding:1px 4px; border-radius:3px; cursor:pointer;
  border:1px solid currentColor; background:none; color:inherit;
  opacity:0; transition:opacity .12s;
}
.chip:hover .pin, .chip .pin:focus-visible { opacity:.65; }
.chip .pin:hover { opacity:1; }
/* Set: never fades, and reads as filled rather than outlined. A fill of
   currentColor would put the label the same colour as its own background, so
   the weight does the work instead. */
.chip.pinned .pin { opacity:1; font-weight:700; }
.chip.pinned { border-width:2px; font-weight:700; }
/* Touch has no hover, so the control cannot hide behind one. */
@media (hover: none) { .chip .pin { opacity:.65; } }

/* Saving. Everything commits as it is changed, which is safer than a button
   but leaves people wondering. This says so, and gives them the button. */
.savebar {
  display:flex; align-items:center; gap:14px; justify-content:space-between;
  flex-wrap:wrap; padding:10px 16px; margin-bottom:20px;
  background:var(--surface); border:1px solid var(--rule);
  border-left:3px solid var(--brand); border-radius:0 5px 5px 0;
}
.savebar.dirty { border-left-color:var(--warn); background:var(--warn-soft); }
.save-state { font-size:13.5px; color:var(--ink-2); }
.savebar.dirty .save-state { color:var(--warn); font-weight:600; }

/* ------------------------------------------------ activities and invoicing */
/* A form opened inside the table it belongs to, so the row it edits stays in
   sight rather than the form appearing somewhere else on the page. */
tr.inline-form > td { background:var(--surface-2); padding:16px; }

/* Recording an invoice against one instalment.
 *
 * It opens inside the instalments table with "Add instalment" directly beneath,
 * and the two read as one wall of boxes. The frame and the accent edge say
 * "this panel opened, and it is about the line you pressed"; the margin below
 * keeps the next form from touching it. */
.billbox {
  background:var(--surface); border:1px solid var(--rule-strong);
  border-left:3px solid var(--accent-calm); border-radius:4px;
  padding:14px 16px; margin:0 0 6px;
  box-shadow:0 1px 0 var(--rule);
}
.billhead {
  margin:0 0 12px; font-size:13.5px; color:var(--ink-2);
  padding-bottom:9px; border-bottom:1px solid var(--rule);
}
.billhead strong { color:var(--ink); }

/* A box that still wants filling in, marked BEFORE the person presses Record
   it — :invalid only styles after a refused submit, which is the moment this
   is trying to avoid. */
.billbox input.needs {
  border-color:var(--warn); background:var(--warn-soft);
}
.billbox input.needs:focus { border-color:var(--brand); background:var(--surface); }
.needtag {
  font-family:var(--mono); font-size:9.5px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--warn);
  border:1px solid var(--warn); border-radius:3px;
  padding:0 4px; margin-left:5px; vertical-align:1px;
}

/* The add-instalment form used to sit open at the foot of the panel, and
   needed a rule above it to stop the two reading as one long form. It opens in
   a dialog now, which separates it on its own; the rule would only draw a line
   across the middle of the dialog. */
.addinst { border-top:0; padding-top:0; margin-top:0; }
.panel-sub {
  font-family:var(--mono); font-size:11px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--ink-2); font-weight:600; margin:0 0 10px;
}
.actform .formrow:last-of-type { align-items:flex-end; }

/* Instalments on the left, the triggers that release them on the right —
   side by side because the question "what has to happen before we can bill
   this" is the relationship between the two. */
/* Stacked by default, side by side only where there is genuinely room.
   This panel now lives inside the LEFT column of the project detail, which is
   about 950px — halve that and the instalment table gets ~460px for five
   columns, so the value field collapses to a sliver you cannot read a number
   in. It fitted while the detail was full width; the two-column layout took
   the room away and the media query never noticed, because a media query
   measures the window and not the column the table is standing in. */
/* Always stacked. Never side by side.
 *
 * It was two columns, which fitted while the project detail was full width.
 * It is not any more: the shell caps at 1560px, the detail splits that with the
 * conversation, and the money panel would split what is left again — so the
 * instalments table gets about 650px however large the monitor is, and it needs
 * 780. The result was the status chip and its buttons sitting on top of the
 * trigger points beside them.
 *
 * A wider breakpoint would not have helped. The constraint is the shell width
 * and two enclosing columns, not the window, so there is no screen big enough
 * to make the second column fit. Stacked, the table gets the whole 1100px and
 * the trigger points read underneath, which is also the order they are used in.
 */
.moneypanel { display:grid; grid-template-columns:1fr; gap:22px; }
.moneypanel table.inner { font-size:13.5px; margin-bottom:10px; background:var(--surface); }
.moneypanel table.inner th, .moneypanel table.inner td { padding:5px 8px; }

/* The status cell carries a chip and up to two buttons. td.shrink makes every
   shrink cell nowrap, which is right for a date and wrong here — it is what
   pushed the whole table past its column rather than wrapping onto a second
   line. */
.moneypanel table.inner td:last-child { white-space:normal; }

/* Belt and braces: if the table ever needs more room than it has, it scrolls
   inside its own panel instead of being drawn over whatever is next to it. */
.mp-scroll { overflow-x:auto; }

/* Edits in place: the field is the record, so it looks like a field and
   confirms itself when it saves. */
.cell {
  width:100%; font:inherit; font-size:13.5px; padding:4px 7px;
  border:1px solid transparent; border-radius:3px;
  background:var(--surface); color:var(--ink);
}
.cell:hover { border-color:var(--rule); }
.cell:focus { border-color:var(--brand); outline:none; }
.cell.n { font-family:var(--mono); text-align:right; }
.cell.saved { border-color:var(--brand); background:var(--ok-soft); }

/* A field in a shrink cell inherits width:100% OF A 1%-WIDE CELL, which is a
   few pixels. These are the floors below which a value stops being readable —
   a money field has to show thousands and a decimal, a date has to show all
   three parts, and a select has to show enough of the option to tell them
   apart. */
/* Scoped to .moneypanel when this was written, which meant the purchase orders
   panel — a different panel with the same shape — collapsed its value field to
   an unusable few pixels. The problem is not a property of one panel: it is
   what width:100% does inside width:1%. So the floors follow the shrink cell
   itself, and any table that grows one of these later is already covered. */
.moneypanel .cell.n,
td.shrink .cell.n { min-width:92px; }
.moneypanel input[type=date].cell,
td.shrink input[type=date].cell { min-width:140px; }
.moneypanel select.cell,
td.shrink select.cell { min-width:150px; }
.moneypanel td:first-child .cell { min-width:130px; }

/* Whether the instalments actually add up to the activity. The spreadsheet
   could not tell you this, and it is the commonest way a job is under-billed. */
.gapnote { font-size:13px; margin:0 0 12px; padding:6px 9px; border-radius:3px; }
.gapnote.under { background:var(--warn-soft); color:var(--warn); }
.gapnote.over  { background:var(--crit-soft); color:var(--crit); }
.gapnote.ok    { background:var(--ok-soft); color:var(--accent-ink); }

.mslist { list-style:none; margin:0 0 12px; padding:0; }
.mslist li {
  display:flex; align-items:flex-start; gap:8px; justify-content:space-between;
  padding:8px 10px; background:var(--surface); border:1px solid var(--rule);
  border-radius:4px; margin-bottom:6px; font-size:13.5px;
}
.mscheck { display:flex; align-items:flex-start; gap:9px; margin:0; cursor:pointer; }
.mscheck input { margin-top:3px; flex:none; }

/* A count standing in for what is hidden. Reads as text, behaves as a button —
   it is a disclosure, not an action with consequences. */
button.btn.quiet {
  border-color:transparent; background:none; color:var(--ink-2);
  font-size:12.5px; padding:2px 6px;
}
button.btn.quiet:hover {
  background:var(--surface-2); border-color:var(--rule); color:var(--ink);
}

/* A link that acts like a button — downloading a file is a navigation, not a
   script, so it stays an anchor and only borrows the look. */
a.btn {
  font:inherit; font-size:14px; cursor:pointer; border-radius:4px; padding:7px 13px;
  border:1px solid var(--rule-strong); background:var(--surface); color:var(--ink);
  display:inline-block; text-decoration:none;
}
a.btn:hover { background:var(--surface-2); border-color:var(--ink-3); }
a.btn.small { padding:4px 9px; font-size:13px; }

/* Who pays, on the project detail. The reference edits in place because it
   belongs to the customer and is read far more often than it is changed. */
.whopays {
  display:flex; flex-wrap:wrap; gap:8px; align-items:center;
  font-size:13.5px; margin:0 0 14px; padding:8px 11px;
  background:var(--surface-2); border:1px solid var(--rule); border-radius:4px;
}
.whopays .inline-ref {
  font-family:var(--mono); font-size:13px; padding:3px 7px; width:15ch;
  background:var(--surface);
}
.whopays .inline-ref:placeholder-shown { border-style:dashed; }

/* A group job reference JobWatch does not recognise. Recorded, not lost, but
   never mistaken for one that reconciles. */
tr.unresolved td { background:var(--warn-soft); }
tr.unresolved .m { opacity:.75; }

.formrow { display:flex; flex-wrap:wrap; gap:12px; align-items:flex-end; }
.formrow .f { flex:1 1 180px; min-width:0; }
.formrow .f input, .formrow .f select { width:100%; }
/* A name on a crew's list.
 *
 * Those rosters were written one week at a time by the board importer and none
 * of them is standing, so a name printed plainly asserts something about today
 * that nobody has confirmed. Normal weight means JobWatch has work for them
 * this week; the other two states are dimmed, because "on the list" and "doing
 * something" are not the same claim. */
.crewname.idle { opacity:.45; }
.crewname.isoff { opacity:.6; font-style:italic; }

/* A crew heading inside the JobWatch schedule. Its own row rather than a column,
   because a crew of six would otherwise repeat its name six times down the side
   of a table that is already narrow at the left. */
tr.crewhead > td {
  background:var(--surface-2); border-top:2px solid var(--rule-strong);
  padding:7px 10px; font-size:13px;
}
tr.crewhead .teamdot { margin-right:7px; }

/* A job JobWatch has somebody on, in a day cell. Small, because a person can
   have five in a day and the point is the shape of the week, not reading each
   site name — the tooltip carries the detail. */
/* A pencilled cell: read out of the JobWatch feed rather than booked here, so
   it is drawn as a sketch of one — dashed and lighter, never solid enough to be
   mistaken for a commitment somebody made on this screen. */
/* A day drawn from the JobWatch feed onto a crew's row. Dashed and quieter than
   a booking somebody made here, because that is exactly what it is not: nobody
   planned it on this board and nothing on this board can move it. A job that
   belongs to a project NPT can name is marked, since that is the one the money
   hangs off. */
/* 217 REPLACED THE DASH WITH THE STANDARD. Until then this said only two
   things — booked here, or read from the feed — and it said them with a border
   style, which left a planner no way to tell a job that is merely in the diary
   from one that is attached to a project and earning. The five states live with
   the tokens at the top of this file and are applied by .st-* below.
   `.fromjob` survives as a hook for "no record of our own behind it", which is
   why these have no bed marker and no vehicle, and nothing more.

   (It also drew a 3px box by accident: `border-style:dashed` on a rule whose
   only width was border-left turns the other three sides on at the CSS initial
   `medium`, so a chip meant to be a faint sketch came out heavier than a real
   booking. Widths are explicit now.) */

/* What a trigger point frees, under its own name. The select sits on its own
   line rather than beside the tick, because the tick is a decision about the
   work and this is a statement about the money — and at 90 characters of
   instalment narrative it would push the delete button off the row. */
/* --------------------------------------------------- the task builder (215)
 *
 * Two columns: a wall of project notes on the left, the chosen one's list on
 * the right. Ops asked for sticky notes because the first version's tiles were
 * fat enough that choosing a project pushed the work off the screen.
 *
 * The left column scrolls on its own so the list on the right stays put — the
 * whole point of two columns is that picking does not move the working area. */
/* Other people's lists on the same project (223). A quiet line, not a panel:
   it is context for the list underneath it, and anything heavier reads as a
   second thing to do rather than as "somebody is already on this". */
.alsohere {
  display:flex; align-items:center; gap:6px; flex-wrap:wrap;
  margin:0 0 12px; padding:6px 8px; font-size:12px;
  background:var(--surface-2); border-left:3px solid var(--st-plan); border-radius:3px;
}
.alsohere .muted { font-size:11px; text-transform:uppercase; letter-spacing:.07em; }

/* Whose list this is, and whether it is the one open (224). A chip that does
   something, beside chips that only report — so it answers the pointer. */
.alsoone { font:inherit; font-size:12px; cursor:pointer; }
.alsoone:hover, .alsoone:focus-visible {
  border-color:var(--st-plan); color:var(--st-plan); outline:none;
}
.alsoone.on {
  background:var(--st-plan-soft); border-color:var(--st-plan); color:var(--st-plan);
  font-weight:620;
}
/* Their steps, set in from your own so the two lists never read as one list. */
.alsolist { margin:0 0 14px; border-left:3px solid var(--st-plan); padding-left:8px; }

/* Who ticked a step. Quiet — it is provenance, not the point of the line — but
   on the line, because once a whole division can tick these, "done" without a
   name is half an answer. */
.tickedby {
  margin-left:auto; flex:0 0 auto; align-self:center;
  font-size:11px; color:var(--ink-3); font-family:var(--mono);
  border:1px solid var(--rule); border-radius:3px; padding:0 4px; white-space:nowrap;
}

.buildgrid {
  display:grid; grid-template-columns:minmax(220px, 300px) 1fr; gap:16px;
  padding:0 16px 16px; align-items:start;
}
.pnotes {
  display:flex; flex-wrap:wrap; gap:8px;
  max-height:62vh; overflow:auto; padding:2px;
}
.pnote {
  flex:1 1 130px; min-width:130px; text-align:left; font:inherit; cursor:pointer;
  display:flex; flex-direction:column; gap:1px;
  background:var(--surface-2); border:1px solid var(--rule);
  border-top:3px solid var(--accent-calm); border-radius:3px;
  padding:7px 9px 8px; line-height:1.25;
}
.pnote:hover, .pnote:focus-visible { border-color:var(--accent-ink); outline:none; }
.pnote.on {
  background:var(--accent-soft); border-color:var(--accent-ink);
  border-top-color:var(--accent-ink);
}
.pnote.done { border-top-color:var(--rule-strong); opacity:.72; }
.pnote .pn-div {
  font-family:var(--mono); font-size:10px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600;
}
.pnote .pn-name { font-size:13px; font-weight:620; color:var(--ink); }
.pnote .pn-client { font-size:11.5px; color:var(--ink-2); }
.pnote .pn-foot { font-size:11px; color:var(--ink-3); margin-top:2px; }
.buildside { min-width:0; }

/* One column on a narrow screen, notes first. */
@media (max-width: 780px) {
  .buildgrid { grid-template-columns:1fr; }
  .pnotes { max-height:38vh; }
}

/* The invoicing week's bar, at the top of the screen with no panel around it.
   It is the heading — which week is being looked at — so it sits closer to the
   h1 than a section would and carries a rule under it instead of a box. */
.weekbar.weektop {
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin:-6px 0 18px; padding:0 0 12px;
  border-bottom:1px solid var(--rule);
}
.weekbar.weektop .wkname { font-size:15px; font-weight:620; }

.msfrees {
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin:4px 0 0 26px; font-size:12.5px;
}
.msfrees select { max-width:280px; }

.jwjob.pencil {
  border-style: dashed;
  opacity: .88;
  background: transparent;
}
.jwjob {
  font-size:11.5px; line-height:1.3; padding:2px 5px; margin-bottom:2px;
  border-radius:3px; background:var(--surface-2); border:1px solid var(--rule);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* Scheduled across a day NPT records them as off. */
.jwjob.clash { background:var(--crit-soft); border-color:var(--crit); color:var(--crit); }

/* The People / Vehicles switch on Resourcing. A filter over one page, so it sits
   inline above the panels rather than pretending to be a second tab bar — two
   tab bars on one screen is how people lose track of which one they are in. */
.groupbar {
  display:flex; gap:6px; flex-wrap:wrap;
  margin:0 0 16px; padding-bottom:12px;
  border-bottom:1px solid var(--rule);
}

/* A tick box is a control, not a field. Stretched to the column width it leaves
   its own label stranded three hundred pixels away, which is how the Tasks
   checklist first drew: a row of boxes down the left and the words they belong
   to down the right, with nothing between them. */
.formrow .f input[type=checkbox], .formrow .f input[type=radio] { width:auto; }

/* A button for a thing already done: available, not advertised. "Describe" on
   an undescribed customer is the work; "edit" on a described one is a second
   thought, and should not compete with it for attention. */
.btn.tiny { font-size:12px; padding:2px 7px; }
.btn.quiet { border-color:transparent; background:none; color:var(--ink-3); }
.btn.quiet:hover { border-color:var(--rule-strong); background:var(--surface-2); color:var(--ink); }

/* -------------------------------------------------------------------- chips */
.chip {
  display:inline-block; font-family:var(--mono); font-size:10.5px; letter-spacing:.06em;
  text-transform:uppercase; padding:2px 7px; border-radius:3px; font-weight:600;
  border:1px solid var(--rule-strong); background:var(--surface-2); color:var(--ink-2);
  margin:1px 3px 1px 0;
}
/* A check that has PASSED. `.chip.ok` was used in three places on the TIS
   register — the done / PO / quote lights on each row, and the yes/no list
   inside an open job — and was never defined, so a satisfied check rendered as
   a plain grey chip: identical to a neutral label, and the exact opposite of
   the green its `bad` sibling's red implies. Calm rather than the full lime,
   because a row can carry three of these at once. */
.chip.ok { background:var(--ok-soft); color:var(--accent-calm); border-color:var(--accent-calm); }
.chip.primary { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.deputy  { background:var(--warn-soft);  color:var(--warn);  border-color:var(--warn); }
.chip.approve { background:var(--ok-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.off     { opacity:.6; }
/* Chips holding names rather than labels: keep normal case. */
.chip.name { text-transform:none; letter-spacing:0; font-family:var(--sans); font-size:12.5px; font-weight:500; }

/* ------------------------------------------------------------------ org tree */
.tree-title { display:flex; align-items:center; gap:8px; }
.tree-rail { color:var(--rule-strong); font-family:var(--mono); white-space:pre; }
.vacant { color:var(--crit); font-style:italic; }

/* -------------------------------------------------------- multi-select chips */
.picker {
  border:1px solid var(--rule-strong); border-radius:4px; padding:6px;
  background:var(--surface); max-height:150px; overflow-y:auto;
}
.picker label {
  display:flex; align-items:center; gap:8px; margin:0; padding:4px 6px; border-radius:3px;
  font-size:13.5px; color:var(--ink); cursor:pointer;
}
.picker label:hover { background:var(--surface-2); }
.picker input { margin:0; accent-color:var(--brand); }

/* ----------------------------------------------------------------- preview */
.ladder { display:flex; flex-direction:column; gap:0; }
.rung {
  display:grid; grid-template-columns:88px 1fr; gap:0 14px; align-items:baseline;
  padding:9px 14px; border-bottom:1px solid var(--rule); font-size:14px;
}
.rung:last-child { border-bottom:none; }
.rung .when { font-family:var(--mono); font-size:11.5px; color:var(--accent-ink); }
.rung strong { color:var(--ink); }

/* -------------------------------------------------------------------- toast */
.toast {
  position:fixed; left:50%; bottom:24px; transform:translateX(-50%) translateY(12px);
  background:var(--brand); color:var(--on-accent); padding:10px 16px; border-radius:5px;
  font-size:14px; font-weight:600; opacity:0; pointer-events:none;
  transition:opacity .18s, transform .18s;
  max-width:min(560px, 92vw); box-shadow:0 8px 28px -12px rgba(0,0,0,.6); z-index:50;
}
.toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
.toast.err { background:var(--crit); color:#fff; }
@media (prefers-reduced-motion: reduce) { .toast { transition:none; } }

@media (max-width:640px) {
  main { padding:16px; }
  .rung { grid-template-columns:1fr; gap:2px; }
  .sub { display:none; }
}

/* ---------------------------------------------------------------- projects */
.pagehead { display:flex; gap:16px; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; }
.pagehead .btn { flex:none; }
.tabgap { width:1px; background:var(--rule); margin:8px 10px; align-self:stretch; }

.statrow { display:grid; gap:12px; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); margin:20px 0; }
.stat {
  background:var(--surface); border:1px solid var(--rule); border-radius:5px; padding:13px 15px;
  border-top:3px solid var(--rule-strong);
}
.stat.carried { border-top-color:var(--ink-3); }
.stat.created { border-top-color:var(--brand); }
.stat .k {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600;
}
.stat .v { font-size:26px; font-weight:700; line-height:1.15; font-variant-numeric:tabular-nums; margin-top:2px; }
.stat .s { font-size:13px; color:var(--ink-2); font-variant-numeric:tabular-nums; }

td.n, th.n { font-family:var(--mono); font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap; }

.cust { display:flex; align-items:center; gap:11px; min-width:0; }
.logo-img {
  object-fit:contain; border-radius:4px; background:var(--surface-2);
  border:1px solid var(--rule); padding:2px; flex:none;
}
.mono-logo {
  display:inline-flex; align-items:center; justify-content:center; border-radius:4px;
  color:#fff; font-weight:700; letter-spacing:.02em; flex:none; font-family:var(--sans);
}

.chip.carried { background:var(--surface-3); color:var(--ink-2); border-color:var(--rule-strong); }
.chip.created { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.ic      { background:var(--warn-soft); color:var(--warn); border-color:var(--warn); }
.chip.invoiced{ background:var(--ok-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.planned { background:var(--surface-2); color:var(--ink-2); }
.chip.blocked { background:var(--crit-soft); color:var(--crit); border-color:var(--crit); }

input[type=file] { font:inherit; font-size:13px; color:var(--ink-2); }

/* ------------------------------------------------------------- beta notes
 *
 * In the top bar rather than on a tab, so it is reachable from the screen the
 * complaint is about. The badge counts OPEN FAULTS only - ideas and compliments
 * are not a to-do list, and a number that includes them stops meaning anything.
 */
.betabtn { white-space: nowrap; }
.betacount {
  display: inline-block; min-width: 17px; margin-left: 5px; padding: 0 4px;
  font-family: var(--mono); font-size: 11px; line-height: 16px; text-align: center;
  border-radius: 9px; background: var(--crit); color: #fff;
}
/* An empty badge is a red pill saying nothing, and it is 17px wide whether or
   not it has a number in it — enough to wrap "Data cleanse" onto a second line
   and make the whole top bar 40px taller. */
.betacount:empty { display: none; }
/* The top-bar buttons are labels, not paragraphs. Without this the bar grows a
   line whenever a label and its badge do not quite fit. */
.topbar .btn { white-space: nowrap; }
.betapanel { max-width: 860px; width: 100%; }
.betapanel textarea {
  width: 100%; font: inherit; font-size: 14px; padding: 8px 10px;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 3px; resize: vertical;
}
.betapanel textarea:focus { border-color: var(--brand); outline: none; }
.betaveil .tablewrap { max-height: 46vh; overflow-y: auto; }

/* ---------------------------------------------------------- folding panels
 *
 * Resourcing runs to seven panels and People to three, each of them a table
 * that can be hundreds of rows. A panel carrying data-fold collapses to its
 * head, so the screen opens as a list of headings rather than a mile of table.
 *
 * The head has to LOOK like a control, or nobody discovers it: a caret that
 * turns, a pointer cursor, and a hover state. The caret is a pseudo-element so
 * no panel's markup has to know about it.
 */
.panel[data-fold] > .panel-head {
  cursor: pointer;
  display: flex; align-items: center; gap: 9px;
  user-select: none;
}
.panel[data-fold] > .panel-head::before {
  content: ""; flex: 0 0 auto;
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: rotate(90deg);
  transition: transform .12s ease;
  opacity: .55;
}
.panel[data-fold].folded > .panel-head::before { transform: rotate(0deg); }
.panel[data-fold] > .panel-head:hover { background: var(--surface-3); }
.panel[data-fold] > .panel-head:hover::before { opacity: 1; }
.panel[data-fold] > .panel-head:focus-visible {
  outline: 2px solid var(--brand); outline-offset: -2px;
}
/* Everything except the head goes. The head keeps its own bottom border only
   while something is open below it, so a folded panel reads as one bar. */
.panel[data-fold].folded > *:not(.panel-head) { display: none; }
.panel[data-fold].folded > .panel-head { border-bottom: none; }

@media (prefers-reduced-motion: reduce) {
  .panel[data-fold] > .panel-head::before { transition: none; }
}

/* -------------------------------------------------------------- resourcing */
.stat.bad { border-top-color:var(--crit); }
.stat.bad .v { color:var(--crit); }
table.board td.cell { vertical-align:top; padding:6px 8px; min-width:118px; }
/* The chip reads the standard rather than any colour of its own: --st-edge and
   --st-fill are set by whichever .st-* class it carries, and default to a plain
   grey card so an entry whose state nobody has worked out yet still draws. */
.booking {
  font-size:12.5px; line-height:1.35;
  background:var(--st-fill, var(--surface-2));
  border:1px solid transparent;
  border-left:3px solid var(--st-edge, var(--rule-strong));
  border-radius:2px; padding:4px 6px; margin-bottom:3px;
  overflow:hidden; text-overflow:ellipsis; display:block; max-width:190px;
}
.bed {
  display:inline-block; font-family:var(--mono); font-size:9.5px; text-transform:uppercase;
  color:var(--warn); border:1px solid var(--warn); border-radius:2px; padding:0 3px; margin-left:4px;
}
.chip.bad  { background:var(--crit-soft); color:var(--crit); border-color:var(--crit); }
.chip.warn { background:var(--warn-soft); color:var(--warn); border-color:var(--warn); }

/* Temporary stand-in for signing in. */
.actorpick { display:flex; align-items:center; gap:7px; margin:0; }
.actorpick span {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--ink-3);
}
.actorpick select { font-size:13px; padding:4px 7px; max-width:190px; }

/* Lifecycle tiles double as filters. */
.stat.click { cursor:pointer; }
.stat.click:hover { border-color:var(--brand); }
.stat.on { border-color:var(--brand); background:var(--accent-soft); }
.stat.s-in_progress { border-top-color:var(--brand); }
.stat.s-not_started { border-top-color:var(--warn); }
.stat.s-closed      { border-top-color:var(--ink-3); }
.chip.in_progress { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.not_started { background:var(--warn-soft); color:var(--warn); border-color:var(--warn); }
.chip.closed      { background:var(--surface-3); color:var(--ink-3); }
.chip.overdue     { background:var(--crit-soft); color:var(--crit); border-color:var(--crit); }
.selbar {
  display:flex; flex-wrap:wrap; gap:12px; align-items:flex-end;
  background:var(--accent-soft); border:1px solid var(--brand);
  border-radius:5px; padding:12px 14px; margin-bottom:16px;
}

/* ------------------------------------------------------------------- feed */
.feed { max-height:460px; overflow-y:auto; }
.fe {
  display:grid; grid-template-columns:118px 1fr; gap:0 14px;
  padding:10px 16px; border-bottom:1px solid var(--rule); font-size:14px; align-items:baseline;
}
.fe:last-child { border-bottom:none; }
.fe-when { font-family:var(--mono); font-size:11px; color:var(--ink-3); white-space:nowrap; }
.fe-head { display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-bottom:2px; }
.fe.change { background:var(--surface-2); color:var(--ink-2); }
.fe.change .was { text-decoration:line-through; opacity:.7; }
.fe.change .now { color:var(--accent-ink); font-weight:600; }
.fe.question { border-left:3px solid var(--warn); }
/* Arrived from a nudge. The link opens the project, and the point of it is one
   message among thirty — so it is said plainly for a few seconds and then gets
   out of the way, rather than leaving a permanent mark on a conversation. */
.fe-jumped { animation:fejump 4s ease-out; }
@keyframes fejump {
  0%, 55% { background:var(--accent-soft); box-shadow:inset 3px 0 0 var(--brand); }
  100%    { background:transparent; box-shadow:none; }
}
.fe.answer   { border-left:3px solid var(--brand); }
/* A note explaining a change, shown against the change it explains. Indented
   and quieter than the entry above it: it is a footnote, not another event. */
.fe-note {
  margin:4px 0 0 0; padding:4px 0 4px 9px; border-left:2px solid var(--rule-strong);
  font-size:13px; color:var(--ink-2);
}
/* Pinned notes sit above the feed. Tinted so the boundary is visible without a
   heading doing the work, and capped in height so six pins cannot bury the
   conversation they are supposed to introduce. */
.pinned-feed {
  background:var(--surface-2); border-bottom:2px solid var(--rule-strong);
  max-height:40vh;
}
.chip.note     { background:var(--surface-2); color:var(--ink-2); }
.chip.question { background:var(--warn-soft); color:var(--warn); border-color:var(--warn); }
.chip.answer   { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--brand); }
@media (max-width:640px){ .fe { grid-template-columns:1fr; gap:2px; } }

/* ---------------------------------------------------------------- sign in */
.who { display:flex; align-items:center; gap:10px; }
.who-name { font-size:13px; color:var(--ink); font-weight:600; }
.signin-veil {
  position:fixed; inset:0; z-index:100; display:flex; align-items:center; justify-content:center;
  background:var(--ground); padding:24px;
}
.signin {
  width:min(400px,100%); background:var(--surface); border:1px solid var(--rule);
  border-radius:6px; padding:30px 28px; display:flex; flex-direction:column; gap:4px;
  box-shadow:0 20px 60px -30px rgba(0,0,0,.5);
}
.signin-brand { margin-bottom:14px; }
.signin h1 { font-size:22px; font-weight:700; margin:0; letter-spacing:-.02em; }
.signin-sub { color:var(--ink-3); font-size:13.5px; margin:2px 0 20px; }
.signin label { font-size:13px; color:var(--ink-2); margin-bottom:5px; display:block; }
.signin input { width:100%; font-size:15px; padding:9px 11px; margin-bottom:14px; }
.signin input#cd {
  font-family:var(--mono); font-size:26px; letter-spacing:.34em; text-align:center; padding:12px;
}
.btn.wide { width:100%; justify-content:center; margin-bottom:8px; }
/* Quiet secondary action on the sign-in card — present, but not competing
   with "Send me a code". */
.signin button.btn.link {
  border-color:transparent; background:none; color:var(--ink-2); font-size:13.5px;
}
.signin button.btn.link:hover { color:var(--ink); background:none; text-decoration:underline; }
.signin-sent { font-size:13.5px; color:var(--ink-2); margin:0 0 16px; }
/* Said before anything is typed, because the consequence of the obvious action
   is not obvious: asking for a code cancels the one you were handed. */
.signin-warn {
  font-size:13px; line-height:1.45; margin:0 0 16px; padding:9px 11px;
  background:var(--warn-soft); color:var(--warn); border-radius:4px;
}
.signin-err { color:var(--crit); font-size:13.5px; margin:6px 0 0; min-height:1em; }

/* Instalment states. Paid and invoiced read green — money that has landed or
   at least been asked for — against amber for planned and red for blocked. */
.chip.paid     { background:var(--ok-soft); color:var(--accent-ink); border-color:var(--brand); font-weight:700; }
.chip.invoiced { background:var(--ok-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.ready    { background:var(--accent-soft); color:var(--accent-ink); border-color:var(--brand); }
.chip.void     { background:var(--surface-3); color:var(--ink-3); text-decoration:line-through; }
tr.is-paid td:first-child { box-shadow: inset 3px 0 0 var(--brand); }

/* ============================================================== reporting */
/* Division colours, used by both the schedule bars and the timeline so a
   colour means the same thing on every screen. */
.d-EE  { background:#99CC00; }
.d-EI  { background:#3E7CB1; }
.d-EPM { background:#C4622D; }
.d-ESG { background:var(--ink-3); }

.legend { display:flex; align-items:center; gap:16px; flex-wrap:wrap; padding-top:12px; }
.legend .lg { display:inline-flex; align-items:center; gap:6px; font-size:12.5px; color:var(--ink-2); }
.legend .lg i { width:11px; height:11px; border-radius:2px; display:inline-block; }

/* Weekly invoice value. Bars sit on a baseline so the shape of the next few
   weeks reads at a glance, which is the whole job of the schedule sheet. */
.schedule {
  display:flex; align-items:flex-end; gap:6px; height:190px;
  padding:8px 0 0; overflow-x:auto;
}
.sweek { flex:1 1 0; min-width:34px; display:flex; flex-direction:column; align-items:center; height:100%; }
.sbar-wrap { flex:1; width:100%; display:flex; align-items:flex-end; }
.sbar { width:100%; display:flex; flex-direction:column-reverse; border-radius:3px 3px 0 0; overflow:hidden; }
.sseg { min-height:2px; }
.sval {
  font-family:var(--mono); font-size:10.5px; color:var(--ink-2); margin-top:5px;
  font-variant-numeric:tabular-nums; white-space:nowrap;
}
.swk { font-size:10.5px; color:var(--ink-3); white-space:nowrap; }

/* ------------------------------------------------------ invoicing, by week
   The same bars as the schedule sheet, coloured by STATE rather than by
   division. On this screen the question is what can go out and what cannot;
   colouring by division would answer a question nobody asked while hiding the
   one they did. Lime is the brand's own "good"; anything a person has to go
   and unblock is critical red; work that has simply not finished yet is grey,
   because it is not a problem and should not look like one. */
.iv-ready { background:var(--brand); }
.iv-held  { background:var(--crit); }
.iv-wait  { background:var(--rule-strong); }

/* 302. A project line on the week, in the same three colours down its left
   edge, and every check its customer asks for as given ✓, given with something
   still to tidy ~, missing ✗, not yet …, or not known ?. */
tr.wk-ready > td:first-child { box-shadow:inset 4px 0 0 var(--brand); }
tr.wk-held > td:first-child { box-shadow:inset 4px 0 0 var(--crit); }
tr.wk-waiting > td:first-child { box-shadow:inset 4px 0 0 var(--rule-strong); }
ul.reqs { list-style:none; margin:6px 0 0; padding:0; display:flex; flex-direction:column; gap:3px; max-width:62ch; }
ul.reqs li { display:grid; grid-template-columns:18px minmax(0, 1fr); gap:6px; font-size:12.5px; line-height:1.4; color:var(--ink-2); }
ul.reqs li b { font-weight:600; color:var(--ink); }
ul.reqs li i { font-style:normal; font-weight:700; text-align:center; border-radius:3px; font-size:11px; line-height:17px; height:17px; }
ul.reqs .rq-ok i { background:var(--ok-soft); color:var(--ok); }
ul.reqs .rq-part i { background:var(--warn-soft); color:var(--warn); }
ul.reqs .rq-no i { background:var(--crit-soft); color:var(--crit); }
ul.reqs .rq-wait i, ul.reqs .rq-unknown i, ul.reqs .rq-na i { background:var(--surface-2); color:var(--ink-3); }
ul.reqs li.rq-note { display:block; font-style:italic; color:var(--ink-3); }
.chip.plan { background:var(--st-plan-soft); color:var(--st-plan); border-color:var(--st-plan); }

/* 303. Asked: amber down the edge while somebody has been asked and the answer
   is not overdue; each open question with its ticks under the checks. */
tr.wk-asked > td:first-child { box-shadow:inset 4px 0 0 var(--warn); }
.asked { display:flex; flex-direction:column; gap:6px; margin-top:8px; max-width:62ch; }
.askrow { display:flex; flex-wrap:wrap; align-items:center; gap:4px 8px; font-size:12.5px; }
.askrow .ticks { display:flex; flex-wrap:wrap; gap:4px 10px; flex-basis:100%; }
.tk { font-size:12.5px; color:var(--ink-2); }
.tk-yes { color:var(--ok); }
.tk-no { color:var(--crit); }
.tk-later, .tk-wait { color:var(--warn); }

/* The ask and answer panels. */
.billask { display:flex; flex-direction:column; gap:10px; }
.billask textarea { width:100%; font:inherit; font-size:13px; line-height:1.45; padding:8px 10px;
  border:1px solid var(--rule-strong); border-radius:4px; background:var(--surface); color:var(--ink); resize:vertical; }
.askkinds { display:flex; flex-wrap:wrap; gap:6px; }
.askkinds label { display:flex; align-items:center; gap:6px; font-size:13px; padding:4px 10px; border:1px solid var(--rule-strong);
  border-radius:4px; cursor:pointer; }
.askwho { display:flex; flex-direction:column; gap:4px; }
.askwho .person { font-size:13.5px; }
.askteams { display:flex; gap:8px; align-items:flex-start; font-size:13px; color:var(--ink-2); }
.askacts { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
/* 314: the question as they will see it, and its text behind "Change the text". */
.askpreview { font-size:13.5px; line-height:1.55; padding:10px 12px; border:1px solid var(--rule); border-radius:6px; background:var(--surface-2); color:var(--ink); }
.askpreview .need { color:var(--warn); }
.askpreview .have { color:var(--ok); }
.askedit summary { cursor:pointer; font-size:13px; color:var(--ink-2); }
.askedit textarea { margin-top:6px; }
/* 315: a release since this page was opened. */
.newrelease { position:fixed; left:50%; bottom:16px; transform:translateX(-50%); z-index:60; display:flex; gap:10px; align-items:center;
  padding:8px 10px 8px 14px; border:1px solid var(--rule-strong); border-radius:8px; background:var(--surface); color:var(--ink);
  font-size:13.5px; box-shadow:0 4px 16px rgba(0,0,0,.18); }
.asksum { margin:0; font-size:13.5px; }
.askq summary { cursor:pointer; font-size:13px; color:var(--ink-2); }
.askq pre { white-space:pre-wrap; font:inherit; font-size:13px; margin:6px 0 0; padding:8px 10px; background:var(--surface-2); border-radius:4px; }
.answers { display:flex; flex-direction:column; gap:8px; }
.tickblock { border:1px solid var(--rule); border-radius:6px; padding:8px 10px; display:flex; flex-direction:column; gap:6px; font-size:13.5px; }
.tickblock.mine { border-color:var(--warn); background:var(--warn-soft); }
.ansrow { display:flex; flex-wrap:wrap; gap:6px 8px; align-items:center; }
.ansrow input, .ansrow select { font:inherit; font-size:13px; padding:4px 8px; border:1px solid var(--rule-strong); border-radius:4px;
  background:var(--surface); color:var(--ink); min-width:0; }
.ansrow label { display:flex; align-items:center; gap:6px; font-size:13px; }

/* Money expected: this week and next, side by side. */
.expected { display:grid; grid-template-columns:repeat(auto-fit, minmax(280px, 1fr)); gap:12px; }
.expweek { border:1px solid var(--rule); border-radius:6px; padding:10px 12px; display:flex; flex-direction:column; gap:6px; min-width:0; }
.expweek h3 { margin:0; font-size:14px; }
.exprow { display:grid; grid-template-columns:96px auto minmax(0, 1fr); gap:10px; align-items:baseline; font-size:13.5px; }
.exprow b { font-family:var(--mono); font-variant-numeric:tabular-nums; }
.exprow .muted { font-size:12.5px; }
.exprow.go > span:first-child { color:var(--ok); font-weight:600; }
.exprow.stuck > span:first-child { color:var(--crit); font-weight:600; }
.expwhy { margin:2px 0 0; font-size:12.5px; }

/* Today, and the days nobody invoices on. An empty weekend column looks
   identical to a Tuesday nobody has finished anything on; the shading is what
   keeps "quiet" apart from "closed". */
.sweek.today .swk { color:var(--accent-ink); font-weight:600; }
.sweek.today .sbar-wrap { box-shadow:inset 0 -2px 0 var(--brand); }
.sweek.closed .sbar-wrap { background:repeating-linear-gradient(
  45deg, transparent, transparent 4px, var(--surface-2) 4px, var(--surface-2) 8px); }
.sweek.closed .swk { color:var(--ink-3); font-style:italic; }

/* The week's own bar of controls: which week, and the cutoff it was drawn
   with. Wraps rather than scrolls — it sits above everything else on the
   screen and a horizontal scrollbar there is a trap on a laptop. */
.weekbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.weekbar .wkname { font-weight:600; font-size:15px; }

/* Nominal down the side, months across — the shape finance already reads. */
.revtable { font-size:13.5px; }
.revtable tr.divrow th {
  background:var(--surface-2); font-family:var(--mono); font-size:11px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ink-2);
  border-top:2px solid var(--rule-strong);
}
.bar { display:block; height:9px; background:var(--brand); border-radius:2px; min-width:2px; }

/* ================================================================= timeline */
.gantt { overflow-x:auto; padding:0 0 4px; }
.grow { display:grid; grid-template-columns:210px 1fr; border-bottom:1px solid var(--rule); min-width:760px; }
.grow.ghead { border-bottom:1px solid var(--rule-strong); background:var(--surface-2); }
.grow.ghead .gtrack { height:26px; }

.gname { padding:9px 12px; border-right:1px solid var(--rule); min-width:0; }
.gname[data-open] { cursor:pointer; }
.gname[data-open]:hover { background:var(--surface-2); }
.gtitle { font-weight:620; font-size:13.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gsub { font-size:12px; color:var(--ink-3); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

.gtrack { position:relative; min-height:38px; padding:6px 0; }
.gmonth {
  position:absolute; top:5px; font-family:var(--mono); font-size:10.5px;
  letter-spacing:.08em; text-transform:uppercase; color:var(--ink-3);
  padding-left:6px; border-left:1px solid var(--rule-strong); white-space:nowrap;
}
.gline { position:absolute; top:0; bottom:0; width:1px; background:var(--rule); }
.gweekend { position:absolute; top:0; bottom:0; background:var(--surface-2); }

/* The days under the months (247): numbered while they fit, Mondays only across
   a wide window, with a faint line down from each Monday. */
.grow.gdays .gtrack { height:30px; min-height:30px; padding:0; }
.grow.gdays .gdayname { padding:0 12px; align-self:center; font-family:var(--mono); font-size:10px;
  letter-spacing:.06em; text-transform:uppercase; color:var(--ink-3); }
.gday {
  position:absolute; top:0; bottom:0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:10px; line-height:1.15; color:var(--ink-2); white-space:nowrap;
  font-variant-numeric:tabular-nums; border-left:1px solid transparent;
}
.gday .gdw { font-size:8.5px; color:var(--ink-3); }
.gday.we { color:var(--ink-3); }
.gday.mon { border-left-color:var(--rule-strong); }
.gdays.wide .gday { align-items:flex-start; padding-left:3px; overflow:visible; }
.gday.today { color:var(--crit); font-weight:700; box-shadow:inset 0 -2px 0 var(--crit); }
.gwk { position:absolute; top:0; bottom:0; width:1px; background:var(--rule); opacity:.55; }
.gtoday-line { position:absolute; top:0; bottom:0; width:2px; background:var(--crit); z-index:3; }

.gbar {
  position:absolute; height:18px; border-radius:3px; overflow:hidden;
  display:flex; align-items:center; padding:0 6px; z-index:2;
  box-shadow:0 1px 0 rgba(0,0,0,.14);
}
.gbar span {
  font-size:11px; font-weight:600; color:#0E1200; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
}
.gbar.d-EI span, .gbar.d-EPM span { color:#fff; }
.gbar.st-cancelled { opacity:.4; text-decoration:line-through; }
.gbar.st-complete { opacity:.72; }

/* Pencilled in: planned but not firm. Drawn as an outline over a faint wash
   rather than a solid bar, so a glance down the page separates what is agreed
   from what is only intended — which is the whole reason for showing it. */
.gbar.st-planned {
  background:transparent;
  border:1.5px dashed currentColor;
  background-image:repeating-linear-gradient(135deg,
    rgba(0,0,0,.06) 0 4px, transparent 4px 8px);
}
.gbar.st-planned span { opacity:.85; }

/* A folded project: one bar standing for all of its activities. Slightly
   inset and softened, so it does not read as a single very long job. */
.gbar.st-summary { opacity:.9; box-shadow:inset 0 0 0 1px rgba(0,0,0,.18); }

/* The fold caret in a lane title. Deliberately quiet — it is used once per
   lane and then wants to be out of the way. */
.gfold {
  border:none; background:none; cursor:pointer; padding:0 5px 0 0;
  font:inherit; font-size:11px; color:var(--ink-3); line-height:1;
}
.gfold:hover { color:var(--ink); }

.legend i.gw-key { background:var(--surface-3); }
.legend i.gt-key { background:var(--crit); }
/* A bar cut off by the edge of the window says so, rather than looking as
   though the work starts or stops there. */
.gbar.clip-l { border-radius:0 3px 3px 0; border-left:2px dotted rgba(0,0,0,.45); }
.gbar.clip-r { border-radius:3px 0 0 3px; border-right:2px dotted rgba(0,0,0,.45); }
.gbar.clip-l.clip-r { border-radius:0; }

/* Working a weekend. Marked wherever it appears, because it is the exception
   and reads as ordinary if it looks ordinary. */
.chip.weekend { background:var(--warn-soft); color:var(--warn); border-color:var(--warn); }
.weekendbox {
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  flex-wrap:wrap; margin-top:14px; padding:10px 12px;
  background:var(--surface); border:1px solid var(--rule); border-radius:4px;
}
.weekendbox.on { background:var(--warn-soft); border-color:var(--warn-rule, var(--warn)); }

/* Sortable column headers. The direction has to be visible — working it out
   from the data is not a thing anyone should have to do. */
th.sortable { cursor:pointer; user-select:none; white-space:nowrap; }
th.sortable:hover { color:var(--ink); background:var(--surface-3); }
th.sortable.sorted { color:var(--accent-ink); }
.sortmark { display:inline-block; width:1em; font-size:9px; margin-left:3px; }

/* How customers bill. A customer with no profile is the exception worth
   noticing, so it is the row that is marked, not the described one. */
tr.undescribed td { background:var(--warn-soft); }
.note-strip {
  padding:12px 16px; margin:0 0 20px; border-radius:5px; font-size:14px;
  background:var(--warn-soft); color:var(--warn); border:1px solid var(--warn-rule, var(--warn));
}
.docgrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:7px 14px; }
.doccheck { display:flex; align-items:center; gap:8px; margin:0; font-size:13.5px; cursor:pointer; }
.doccheck input { flex:none; }

/* How old the JobWatch data is, next to the connection light. Amber once a
   sync is more than twice overdue — a screen showing yesterday's group jobs
   looks exactly like one showing this morning's. */
.health .sep { opacity:.4; margin:0 4px; }
.health .synced { color:var(--ink-3); }
/* Colour, not weight. Bold and regular are different widths, and this sits in a
   nowrap bar that is redrawn every thirty seconds. */
.health .synced.stale { color:var(--warn); }
/* The sync light. Always present so the row never reflows; lit and breathing
   only while something is actually being read. */
/* A HEADER CONTROL THAT IS PRESENT BUT HAS NOTHING TO SAY.
   Used by "Waiting on you" and "What's new", which used to be hidden outright
   when empty — and reappeared on a timer, moving every control beside them. */
.topright .btn.quiet {
  opacity:.45; border-color:var(--rule); color:var(--ink-3); font-weight:400;
}
.topright .btn.quiet:hover { opacity:1; }
/* Lit and breathing when there IS something, so the eye is drawn without the
   layout changing. The dot reserves no space when unlit because it is not
   rendered at all — and the button's own width does not depend on it, since the
   label and count are what set that. */
.topright .btn.lit::after {
  content:''; display:inline-block; width:6px; height:6px; border-radius:50%;
  margin-left:6px; vertical-align:middle; background:currentColor;
  animation:npt-attn 1.8s ease-in-out infinite;
}
@keyframes npt-attn { 0%, 100% { opacity:.3; } 50% { opacity:1; } }
@media (prefers-reduced-motion: reduce) {
  .topright .btn.lit::after { animation:none; opacity:1; }
}

/* THE TICK THAT KEEPS A SECTION OPEN.
   Sits at the right-hand end of a panel heading, quiet until somebody uses it.
   A star rather than a checkbox: a checkbox inside a clickable heading reads as
   part of the data, and this is about the screen rather than about the row. */
.panel-head .pinfold {
  float:right; margin-left:10px; appearance:none; border:0; background:none;
  cursor:pointer; font-size:15px; line-height:1; padding:0 2px;
  color:var(--ink-3); opacity:.45;
}
.panel-head .pinfold:hover { opacity:1; color:var(--brand); }
.panel-head .pinfold.on { opacity:1; color:var(--brand); }
.panel-head .pinfold:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }

/* PUT AWAY — the same corner, the same quietness, one step further than folding.
   A fold is "not now"; this is "not on my screen". It is never permanent and
   never hidden from the person who did it: everything stowed is listed in a
   section of its own at the foot of the screen, with the way back. */
.panel-head .hidefold {
  float:right; margin-left:6px; appearance:none; border:0; background:none;
  cursor:pointer; font-size:14px; line-height:1; padding:0 2px;
  color:var(--ink-3); opacity:.35;
}
.panel-head .hidefold:hover { opacity:1; color:var(--crit); }
.panel-head .hidefold:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.panel.stowed { display:none !important; }
.stowlist { display:flex; flex-wrap:wrap; gap:8px; }
.stowlist .btn { font-weight:400; }

/* -------------------------------------------------- the settings menu
   Six controls that used to sit loose in the top bar, each of them a word with
   no room to say what it did. In here there is room for the sentence. */
.settings { position:relative; }
.settingsmenu {
  position:absolute; right:0; top:calc(100% + 8px); z-index:60;
  width:min(380px, calc(100vw - 32px));
  /* --surface and --rule, NOT --panel/--line. Those two names do not exist in
     this stylesheet and a var() with no fallback resolves to nothing at all, so
     this menu rendered with no background: the page showed straight through it
     and the text of both sat on top of each other. */
  background:var(--surface); color:var(--ink);
  border:1px solid var(--rule-strong); border-radius:8px;
  box-shadow:0 10px 30px rgba(0,0,0,.28);
  padding:6px;
}
.setrow {
  display:flex; align-items:flex-start; gap:12px;
  padding:10px 10px; border-radius:6px;
}
.setrow:hover { background:var(--surface-2); }
.setrow-txt { flex:1 1 auto; min-width:0; }
.setrow-txt b { display:block; font-size:13.5px; line-height:1.3; }
.setrow-txt span {
  display:block; font-size:12px; line-height:1.45; color:var(--ink-3); margin-top:2px;
}
.setrow > .btn { flex:0 0 auto; min-width:84px; text-align:center; }
.setsep { height:1px; background:var(--rule); margin:6px 10px; }
@media (max-width: 560px) {
  .settingsmenu { position:fixed; right:12px; left:12px; width:auto; }
}

/* THE PARTS OF A DAY, under the tracked figure on the week grid.
   Small, tinted, and deliberately quieter than the number above — three of these
   sit in a cell that already carries a figure, a span, a job count and sometimes
   a chip. Colour separates them; weight and size keep them subordinate. */
.tsparts { font-size:11px; line-height:1.5; margin-top:1px; letter-spacing:.01em; }
/* In the week column the parts sit under a right-aligned total, so they follow
   it rather than starting at the left edge of a narrow cell. */
.tsparts-week { text-align:right; margin-top:3px; }
.tsparts span { white-space:nowrap; }
.tsparts .p-jobs   { color:var(--brand); }
.tsparts .p-travel { color:var(--ink-3); }
/* The clock, in the same grey-and-brackets every provisional figure here uses:
   it is shown to be compared with, never to be counted. */
.tsparts .p-clock  { color:var(--ink-3); opacity:.75; font-style:italic; }

.health .syncdot {
  display:inline-block; width:6px; height:6px; border-radius:50%;
  margin-left:6px; vertical-align:middle; background:var(--rule-strong);
}
.health .syncdot.on { background:var(--brand); animation:npt-syncdot 1.6s ease-in-out infinite; }
@keyframes npt-syncdot { 0%, 100% { opacity:.25; } 50% { opacity:1; } }
@media (prefers-reduced-motion: reduce) {
  .health .syncdot.on { animation:none; opacity:1; }
}

/* How complete each join is. A bar rather than a number, because the shape of
   "nearly all" against "barely any" reads before the digits do. */
.hbar { display:block; height:9px; background:var(--surface-3); border-radius:5px; overflow:hidden; }
.hbar-fill { display:block; height:100%; border-radius:5px; }
.hbar-fill.ok   { background:var(--brand); }
.hbar-fill.warn { background:var(--warn); }
.hbar-fill.bad  { background:var(--crit); }
tr.clickable { cursor:pointer; }
tr.clickable:hover td { background:var(--surface-2); }

/* ------------------------------------------------------------------- search
 *
 * A conversation belongs to a project, which is the right home and the wrong
 * place to look when you cannot remember which project it was. The box lives in
 * the frozen bar, so it is reachable from every screen and from the bottom of a
 * three-thousand-pixel project.
 */
.topsearch { flex:0 1 340px; min-width:120px; margin-left:auto; }
.topsearch input {
  width:100%; font:inherit; font-size:13.5px; padding:5px 10px;
  border:1px solid var(--rule-strong); border-radius:4px;
  background:var(--surface-2); color:var(--ink);
}
.topsearch input:focus { border-color:var(--brand); outline:none; background:var(--surface); }

/* Results overlay the page rather than replacing it. A search is a detour, and
   closing one should put you back exactly where you were. */
.searchpanel {
  position:fixed; top:var(--header-h); right:var(--gutter); z-index:40;
  width:min(560px, calc(100vw - 2 * var(--gutter)));
  max-height:min(70vh, 640px); overflow-y:auto;
  background:var(--surface); border:1px solid var(--rule-strong); border-radius:5px;
  box-shadow:0 14px 40px -14px rgba(0,0,0,.55);
}
.searchpanel .sr {
  display:block; width:100%; text-align:left; padding:10px 14px; border:none;
  border-bottom:1px solid var(--rule); background:none; color:var(--ink);
  font:inherit; font-size:13.5px; cursor:pointer;
}
.searchpanel .sr:hover, .searchpanel .sr:focus-visible { background:var(--surface-2); outline:none; }
.searchpanel .sr:last-child { border-bottom:none; }
.sr-where { font-size:12px; color:var(--ink-3); margin-bottom:2px; }
.sr-hit { background:var(--accent-soft); color:var(--accent-ink); border-radius:2px; padding:0 1px; }
.searchpanel .empty { padding:18px 14px; }

/* --------------------------------------------------------------- the board
 *
 * The crew column showed six full names run together and clipped whatever did
 * not fit, so the end of a roster disappeared without saying so. It is wider
 * now, and past a length a surname becomes an initial rather than being cut in
 * half — see shortName(). The captain is bold.
 */
table.board th.crewcol, table.board td.crewcol {
  min-width:230px; max-width:280px; vertical-align:top;
}
.crewnames {
  font-size:12.5px; color:var(--ink-2); line-height:1.5;
  margin:2px 0 3px; word-break:break-word;
}
.crewnames b { color:var(--ink); font-weight:650; }

/* Weekends read as background, not as working days that happen to be empty. */
table.board th.weekend, table.board td.weekend { background:var(--surface-2); }

/* ------------------------------------------------------------ crew colours
 *
 * A crew is a row tracked across up to forty-two columns (037), and at that
 * width the name at the left edge is too far from the booking at the right to
 * hold in your head. --team is set per row from team.colour (038).
 *
 * Painted as a firm edge and a faint wash, never as a fill. The palette is
 * mid-tone so it reads on both #FFFFFF and #0A0A0A, but at full strength it
 * would compete with the two things on this screen that already carry meaning:
 * the weekend shading, and the lime left border that says a booking is attached
 * to a project. Colour here is for telling rows apart, not for telling you
 * something — so it stays quiet enough to lose an argument with either.
 */
.teamdot {
  display:inline-block; width:9px; height:9px; border-radius:50%;
  background:var(--team, var(--rule-strong)); margin-right:6px;
  vertical-align:baseline; flex:0 0 auto;
}
table.board tr[data-team] td.crewcol { border-left:3px solid var(--team, transparent); }

/* The wash goes on the cells, not the row: a td background paints over a tr
   one, so weekend cells would punch holes in it. Mixing INTO each surface
   instead keeps the weekend reading as a weekend and still tinted. */
table.board tr[data-team] td { background:color-mix(in srgb, var(--team, transparent) 7%, var(--surface)); }
table.board tr[data-team] td.weekend { background:color-mix(in srgb, var(--team, transparent) 7%, var(--surface-2)); }
:root[data-theme="dark"] table.board tr[data-team] td { background:color-mix(in srgb, var(--team, transparent) 13%, var(--surface)); }
:root[data-theme="dark"] table.board tr[data-team] td.weekend { background:color-mix(in srgb, var(--team, transparent) 13%, var(--surface-2)); }

/* Small, because it sits in a table of controls and a colour input at its
   natural size is a slab. */
input[type=color].teamcolour {
  width:30px; height:22px; padding:1px; vertical-align:middle;
  border:1px solid var(--rule); border-radius:3px; background:var(--surface); cursor:pointer;
}
input[type=color].teamcolour::-webkit-color-swatch-wrapper { padding:0; }
input[type=color].teamcolour::-webkit-color-swatch { border:none; border-radius:2px; }
td.nowrap { white-space:nowrap; }

/* Past a fortnight the point is the shape of who is committed where, not
   reading each site name, so the cells give up width. */
table.board.dense td.cell { min-width:62px; padding:4px; font-size:12px; }
table.board.dense .booking { font-size:11.5px; }
table.board.dense .booking .btn { display:none; }

/* A booking attached to a project is the goal state; an unattached one is the
   work still to do, so the marked case is the one that is finished. 217 moved
   that from a lime left border of its own onto the standard — attached is
   `confirmed`, the green — so this no longer sets a colour. It would have won
   the cascade over --st-edge and quietly painted every state lime. */
.booking.linked { padding-left:5px; }

/* Choosing which project a booking belongs to. Overlays, like search: it is a
   decision about one booking, not a screen to be in. */
.movepanel {
  position:fixed; inset:0; z-index:45; display:flex;
  align-items:flex-start; justify-content:center; padding-top:12vh;
  background:rgba(0,0,0,.45);
}
.movepanel > div {
  width:min(620px, 92vw); max-height:70vh; overflow-y:auto;
  background:var(--surface); border:1px solid var(--rule-strong); border-radius:6px;
  box-shadow:0 18px 50px -16px rgba(0,0,0,.6);
}
.movepanel .mv {
  display:block; width:100%; text-align:left; padding:9px 14px; border:none;
  border-bottom:1px solid var(--rule); background:none; color:var(--ink);
  font:inherit; font-size:13.5px; cursor:pointer;
}
.movepanel .mv:hover, .movepanel .mv:focus-visible { background:var(--surface-2); outline:none; }

/* ------------------------------------------- telling one section from the next
 *
 * An open project is eight panels stacked in one column, and every one of them
 * had the same hairline border and the same head against the same ground. Where
 * one section ended and the next began was left to vertical space to say, and
 * vertical space is not very loud. Only inside a project: the rest of the app is
 * one panel per screen, where none of this is a problem to solve.
 */
.detail-main .panel { margin-bottom:24px; border-color:var(--rule-strong); }
.detail-main .panel > .panel-head {
  background:var(--surface-3); color:var(--ink-2); border-bottom-color:var(--rule-strong);
}

/* The strips at the top of a project — who pays, what JobWatch calls it, what
   the customer needs, which currency. Four identical grey boxes in a row read as
   one block with no seams, so the leading word of each is set as a caption and
   the strip gets a spine. No markup changed: the label was always the first
   muted span in there. */
.detail-main .whopays { border-left:3px solid var(--rule-strong); }
.whopays > .muted:first-child {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.09em;
  text-transform:uppercase; color:var(--ink-3);
}

/* A group inside a panel that is its own subject rather than one of its rows.
   The people on a project belong under Costs and margin, and are not a typed
   cost line — same panel, visibly its own thing. */
.costgroup {
  border:1px solid var(--rule); border-left:3px solid var(--accent-calm);
  border-radius:4px; padding:12px 14px; background:var(--surface-2);
}
.over { color:var(--crit); }

/* ---------------------------------------------- the project you are looking at
 *
 * An open project is eight panels in one column. Scroll to the instalments and
 * the customer's name is two screens above — which is how an invoice gets typed
 * against the wrong site. This stays.
 */
.projbar {
  position:sticky; top:0; z-index:6;
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:10px 16px; background:var(--surface-2);
  border-bottom:1px solid var(--rule-strong);
}
.projbar-who { min-width:0; flex:1 1 220px; }
.projbar-name { font-size:16px; font-weight:650; }
.projbar-who .muted { font-size:13px; }

/* The project's name, typed in place (220). It has to read as the name first
   and a box second — a field that looks like a form control on a heading makes
   the heading look unfinished — so it carries only a dashed underline until it
   is hovered or focused, which is the same language the day notes on Resourcing
   use for "this is text you can change". */
.projbar-sub { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
.inline-name {
  font:inherit; font-size:13px; color:var(--ink); background:transparent;
  border:1px solid transparent; border-bottom:1px dashed var(--rule-strong);
  border-radius:3px; padding:1px 4px; min-width:14ch; max-width:44ch; width:auto;
  flex:0 1 auto;
}
.inline-name:hover { border-color:var(--rule); border-bottom-color:var(--ink-3); }
.inline-name:focus {
  outline:none; border-color:var(--accent); border-bottom-style:solid;
  background:var(--surface);
}
.projbar-sub .crit { color:var(--crit); }
.projbar-figs { display:flex; align-items:center; gap:10px; white-space:nowrap; }
.projbar-figs strong { font-variant-numeric:tabular-nums; }

/* A field nobody has filled in. Not an error — nothing is wrong yet — so it is
   ringed in the accent rather than the critical colour, and the button that
   opens the dialog carries the count so the gap is visible without opening it. */
/* ORANGE, NOT GREEN. Asked for directly, and it was the right correction: the
   brand green is what this application uses for "done" everywhere else, so
   ringing an EMPTY box in it said the opposite of what it meant. --warn is the
   amber already used for "somebody has to do something about this". */
.needsfill {
  border-color:var(--warn) !important;
  box-shadow:0 0 0 2px var(--warn-soft);
}
button.btn.needs { border-color:var(--warn); background:var(--warn-soft); color:var(--warn); }

/* The list of what is still blank, and the bar that shrinks as it empties. */
.gapbar {
  height:5px; border-radius:3px; background:var(--warn-soft);
  overflow:hidden; margin:0 0 2px;
}
.gapfill { height:100%; background:var(--accent-calm); transition:width .25s ease; }
.gaplist { list-style:none; margin:0 0 14px; padding:0; display:flex; flex-direction:column; gap:5px; }
.gaplist li {
  display:flex; align-items:baseline; gap:8px; flex-wrap:wrap;
  font-size:13px; padding:5px 9px; border-radius:3px;
  background:var(--warn-soft); border-left:3px solid var(--warn);
}
.gaplist li strong { flex:0 0 auto; }
.gaplist li .muted { flex:1 1 160px; font-size:12.5px; }

/* Where filing the document actually happens, flashed when somebody is sent
   there from the set-up list — otherwise the screen simply scrolls and they are
   looking at a panel with no idea why. */
.flashfor { animation:flashfor 2.2s ease; }
@keyframes flashfor {
  0%, 70% { box-shadow:0 0 0 3px var(--warn-soft); }
  100% { box-shadow:0 0 0 0 transparent; }
}
.setupslot .whopays:last-child { margin-bottom:0; }

/* ------------------------------------------------------------------ timesheets
 *
 * A week is people down the side and seven days across, and the eye is looking
 * for the days that are wrong. So the cell carries the number it is worth in
 * full size, the raw clock underneath it small, and colour only where there is
 * something to do — a settled day and a day that never needed settling look
 * different from a day nobody has decided.
 */
.tsgrid td.tscell { padding:6px 9px; vertical-align:top; min-width:88px; }
.tsgrid .tsh { font-variant-numeric:tabular-nums; font-size:14px; }
.tsgrid .tsw { font-size:11.5px; color:var(--ink-3); font-variant-numeric:tabular-nums; }
.tsgrid td.tscell { cursor:pointer; }
.tsgrid td.tscell:hover { background:var(--surface-3); }
/* Nobody has decided, so it counts as nothing. The one state that needs a
   person, and the only one painted like it. */
.tsgrid td.unsettled { background:var(--crit-soft); }
.tsgrid td.unsettled .tsh { color:var(--crit); font-weight:600; }
.tsgrid td.settled { background:var(--accent-soft); }
.tsgrid td.settled .tsh { font-weight:600; }
.tsgrid td.off .tsh { color:var(--ink-3); }
.tsgrid td.empty { background:transparent; cursor:default; }
.tsgrid td.empty:hover { background:transparent; }
.chip.off { background:var(--surface-3); color:var(--ink-3); }

/* ------------------------------------------------- a grid you can read sideways
 *
 * Fifteen columns and forty people. Scrolling right lost the names, scrolling
 * down lost the day headings, and you could not tell which box you were typing
 * in — which is exactly how a wrong number gets saved against the wrong person.
 * Both are pinned now. The wrapper is what scrolls, so sticky has something to
 * stick to.
 */
.tsgridwrap { max-height:72vh; overflow:auto; }
.tsgrid thead th {
  position:sticky; top:0; z-index:3;
  background:var(--surface-2); box-shadow:0 1px 0 var(--rule-strong);
}
.tsgrid td.tsname, .tsgrid th.tsname {
  position:sticky; left:0; z-index:2;
  background:var(--surface); min-width:190px;
  box-shadow:1px 0 0 var(--rule-strong);
}
.tsgrid thead th.tsname { z-index:4; background:var(--surface-2); }
.tsgrid tbody tr:hover td.tsname { background:var(--surface-2); }
.tsgrid tbody tr.trusted td.tsname { background:var(--ok-soft); }

/* Time on the clock that no job accounts for. The number the whole screen is
   for, so it is the one thing in a cell that is coloured. */
.tsgrid .tsx { font-size:11.5px; color:var(--crit); font-variant-numeric:tabular-nums; }
.termsgrid input.cell.num { width:6ch; }
.termsgrid td { vertical-align:middle; }

/* A dialog that has a table in it needs more than 620px. */
.movepanel.wide > div { width:min(920px, 96vw); max-height:82vh; }

/* ------------------------------------------------------------ tiles, not a list
 *
 * Somebody sitting down to invoice asks three questions in order and used to
 * scroll four screens to answer them. On a wide monitor there is no reason for
 * that: three boxes across the top, each one opening the list behind it. The
 * figure is a button, because it is the thing being questioned.
 */
.bigtiles {
  display:grid; gap:14px; margin:0 0 20px;
  grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));
}
.bigtile {
  text-align:left; font:inherit; cursor:pointer;
  background:var(--surface); border:1px solid var(--rule-strong);
  border-left:4px solid var(--accent-calm); border-radius:6px; padding:16px 18px;
}
.bigtile:hover, .bigtile:focus-visible {
  background:var(--surface-2); border-color:var(--accent-ink); outline:none;
}
.bigtile.bad { border-left-color:var(--crit); }
/* A project tile on Tasks carries a NAME rather than a figure, so it cannot use
   .bt-v — thirty pixels of "Preston Park forecourt refurbishment" is a tile
   three lines tall beside one that is one line tall. And the open one has to be
   visibly the open one, because the builder appears below the row rather than
   inside the tile. */
.bigtile .bt-name {
  font-size:17px; font-weight:620; line-height:1.25; margin:5px 0 3px; color:var(--ink);
}
.bigtile.on {
  border-color:var(--accent-ink); border-left-color:var(--accent-ink);
  background:var(--surface-2);
}
.bigtile .bt-k {
  font-family:var(--mono); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600;
}
.bigtile .bt-v {
  font-size:30px; font-weight:650; line-height:1.15; margin:6px 0 2px;
  font-variant-numeric:tabular-nums; color:var(--ink);
}
.bigtile.bad .bt-v { color:var(--crit); }
.bigtile .bt-s { font-size:13px; color:var(--ink-2); }
.bigtile .bt-more { margin-top:8px; display:flex; gap:6px; flex-wrap:wrap; }

/* The held figure is a way in, so it has to look like one (221). A chip that
   does something and a chip that only reports sit side by side in this row, and
   the only honest difference is that this one answers the pointer. */
.bt-held { font:inherit; font-size:12px; cursor:pointer; }
.bt-held:hover, .bt-held:focus-visible {
  background:var(--warn); color:var(--surface); border-color:var(--warn); outline:none;
}
/* What the headline is made of — project instalments, TIS, or both. Under the
   figure and above the chips, because it describes the figure rather than
   adding anything to it. */
.bigtile .bt-split {
  margin-top:5px; font-size:12.5px; color:var(--ink-2);
  font-variant-numeric:tabular-nums;
}
.bigtile .bt-split i { color:var(--ink-3); font-style:normal; margin:0 5px; }

/* A month is thirty-one narrow cells and seven totals. The day number sits above
   the hours so a column can be found by eye, and the weekend is shaded rather
   than labelled — there is no room for a label and no doubt about which it is. */
.monthgrid td.mcell { padding:3px 4px; min-width:30px; text-align:center; cursor:pointer; }
.monthgrid td.mcell.wknd, .monthgrid th.wknd { background:var(--surface-2); }
.monthgrid .md { font-size:10px; color:var(--ink-3); line-height:1.1; }
.monthgrid .mh { font-size:11.5px; font-variant-numeric:tabular-nums; line-height:1.2; }
.monthgrid .mx { font-size:10px; color:var(--crit); font-weight:700; line-height:1; }
.monthgrid td.unsettled { background:var(--crit-soft); }
.monthgrid td.settled { background:var(--accent-soft); }
.monthgrid td.off .mh { color:var(--ink-3); }
.monthgrid td.mcell:hover { outline:2px solid var(--accent-calm); outline-offset:-2px; }

/* ------------------------------------------------------------ a day is a tile
 *
 * The week grid answers "which day is wrong" and it is right to be dense about
 * it. This is the other question — "what happened on this one" — and it used to
 * be a modal that showed everything to everybody. Seven tiles across instead,
 * each carrying only the rows somebody switched on, and opening in place rather
 * than over the top of the week they were reading.
 *
 * AN OPEN TILE TAKES THE WHOLE ROW. The working behind a day is a paragraph and
 * a form, and neither fits in a column a seventh of the screen wide. Spanning
 * the row keeps every other tile exactly where it was, which a modal never does.
 */
.daytiles {
  display:grid; gap:6px;
  grid-template-columns:repeat(7, minmax(0, 1fr));
}
@media (max-width:1240px) {
  .daytiles { grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); }
}
.daytile {
  margin:5px;
  background:var(--surface); border:1px solid var(--rule);
  border-left:4px solid var(--rule-strong); border-radius:12px; overflow:hidden;
  transition:border-color .12s ease;
}
.daytile.open { border-color:var(--accent-ink); }
.daytile.open .dt-head { background:var(--accent-soft); }
.daytile.wknd { background:var(--surface-2); }
.daytile.blank { opacity:.7; }

/* Colour only where there is something to do — the same rule the grid cell
   follows. Unsettled is the one state that needs a person. */
.daytile.unsettled { border-left-color:var(--crit); }
.daytile.settled { border-left-color:var(--accent-calm); }
.daytile.off { border-left-color:var(--rule-strong); }

.daytile .dt-head {
  position:relative;
  display:flex; align-items:baseline; gap:8px; flex-wrap:wrap;
  width:100%; text-align:left; font:inherit; color:inherit; cursor:pointer;
  background:var(--surface-2); border:0; border-bottom:1px solid var(--rule);
  padding:9px 22px 9px 11px;
}
.daytile .dt-head.plain { cursor:default; }
.daytile .dt-head:hover, .daytile .dt-head:focus-visible {
  background:var(--surface-3); outline:none;
}
.daytile .dt-day { font-weight:650; }
.daytile .dt-date { font-weight:400; color:var(--ink-3); font-size:12.5px; }
.daytile .dt-fig {
  margin-left:auto; font-size:19px; font-weight:650; font-variant-numeric:tabular-nums;
}
.daytile.unsettled .dt-fig { color:var(--crit); }
.daytile .dt-caret {
  position:absolute; top:6px; right:5px;
  font-family:var(--mono); color:var(--ink-3); width:1em; text-align:center;
}

.daytile .dt-body { padding:9px 11px 11px; font-size:13px; }
.daytile .dt-body > * + * { margin-top:6px; }
.daytile .dt-part + .dt-part { margin-top:8px; padding-top:8px; border-top:1px dotted var(--rule); }
.daytile .dt-row { display:flex; gap:8px; align-items:baseline; }
.daytile .dt-v { margin-left:auto; text-align:right; font-variant-numeric:tabular-nums; }
.daytile .dt-k {
  font-family:var(--mono); font-size:10.5px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600;
}
/* The number the whole screen is for, so it is the one thing in a tile that is
   coloured without being asked for. */
.daytile .dt-excess { margin:0; color:var(--crit); font-size:12.5px; font-weight:600; }
.daytile .dt-abs { margin:0; color:var(--ink-3); font-size:12.5px; }
/* A night away nobody wrote down. Amber, not red: it is a question about the
   day rather than a fault in it, and the answer may well be "they did drive
   home". Once somebody has answered it goes quiet and grey — still said, so a
   flag that has been dealt with cannot be mistaken for one that never fired. */
.daytile .dt-away {
  margin:0; font-size:12.5px; font-weight:600; color:var(--warn);
  background:var(--warn-soft); border-radius:6px; padding:4px 7px;
}
.daytile .dt-away.done {
  color:var(--ink-3); background:var(--surface-2); font-weight:400;
}
/* 304. A travel day on the tile: decided, asked, or offered. */
.daytile .dt-travel { display:flex; align-items:center; gap:4px; flex-wrap:wrap; margin:0 10px 6px; font-size:12.5px; }
.daytile .dt-travel > span:first-child { flex:1 1 auto; }
.daytile .dt-travel.on { font-weight:600; color:var(--ok); background:var(--ok-soft); border-radius:6px; padding:4px 4px 4px 7px; }
.daytile .dt-travel.ask { font-weight:600; color:var(--warn); background:var(--warn-soft); border-radius:6px; padding:4px 4px 4px 7px; }
.daytile .dt-travel.no { color:var(--ink-3); font-style:italic; }
.daytile .dt-travel.offer { margin-top:-2px; }
.daytile .dt-travel .btn.tiny { font-size:11.5px; padding:1px 6px; }
td.tscell.travelq { text-align:center; vertical-align:middle; cursor:pointer; }
.daytile .dt-none { margin:0; color:var(--ink-3); font-size:12.5px; font-style:italic; padding:0 10px 8px; }
.daytile .dt-body .dt-none { padding:0; }
.daytile .dt-basis { margin:4px 0 0; color:var(--ink-3); font-size:11.5px; }
.daytile .dt-settled { margin:0; color:var(--ink-2); font-size:12px; }
.daytile .dt-spans { margin:4px 0 0; display:flex; gap:4px; flex-wrap:wrap; }

.daytile .dt-jobs { list-style:none; margin:4px 0 0; padding:0; }
.daytile .dt-jobs li { display:flex; gap:6px; align-items:baseline; padding:2px 0; }
.daytile .dt-jt { font-variant-numeric:tabular-nums; color:var(--ink-3); font-size:12px; white-space:nowrap; }
.daytile .dt-js { flex:1; min-width:0; }

/* The working for one day, opened under the week rather than inside a tile a
   seventh of the screen wide. More than one can be open: a week is worked down,
   not read a day at a time. */
/* NO PADDING ON THE CONTAINER, MARGINS ON THE BLOCKS. Every day has a block and
   most of them are hidden; a padded container with nothing showing left an empty
   band under the week that looked like a panel that had failed to load. A hidden
   block is not laid out at all, so with the inset on the blocks themselves the
   container collapses to nothing on its own. */
.dayworkings { display:grid; }
.dayopen {
  margin:0 21px 16px;
  border:1px solid var(--rule-strong); border-left:4px solid var(--accent-ink);
  border-radius:12px; background:var(--surface-2); padding:12px 14px;
}
.dayopen .dw-head {
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin:0 0 10px; padding-bottom:8px; border-bottom:1px solid var(--rule);
}
/* Prose keeps a reading width even where the box does not — the same rule the
   rest of the app follows. */
.dayopen .note, .dayopen > p { max-width:78ch; }
.daytile .dt-detail .note { margin:0 0 10px; }

/* The switches that say what the tiles carry. They read as pressed rather than
   as six more things to click: the ones that are on take the primary button
   colour, which is the same signal the Week / Month pair already uses. */
.tiletoggles { display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin:14px 0 0; }

/* The rota. The weekend somebody is standing in — twelve weeks of history above
   it and twenty-six below, so the row that answers "am I on this weekend" has to
   find itself rather than being counted to. */
.tsgrid tbody tr.thisweek > td { background:var(--accent-soft); }
.tsgrid tbody tr.thisweek > td:first-child { box-shadow:inset 3px 0 0 var(--accent-ink); }

/* --------------------------------------------------------------- the sheet
 *
 * Twenty-one columns, one row per stage, in the order three divisions have
 * typed for years. Cells are tight and the inputs fill them: this is a grid to
 * be typed across, not a form with labels. The name column and the headings stay
 * pinned, like every other wide table here.
 */
.sheetgrid td { padding:3px 5px; vertical-align:middle; }
.sheetgrid input.cell, .sheetgrid select.cell {
  width:100%; min-width:0; padding:4px 6px; font-size:13px;
}
.sheetgrid input.cell.n { text-align:right; font-variant-numeric:tabular-nums; }
.sheetgrid td.sl { background:var(--surface-2); }
.sheetgrid td.sl.locked { opacity:.72; }
.sheetgrid thead th { font-size:10px; }
.sheetgrid .needtag {
  display:inline-block; margin-top:3px; font-size:11px; color:var(--crit);
  font-family:var(--mono);
}
/* Three little tables of totals, side by side under the rows they total. */
.sumcols { display:grid; gap:18px; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); }
.sumcol table { font-size:13px; }
.sumcol td { padding:4px 8px; }

/* A figure the screen worked out but nobody has settled. Shown, not counted —
   in brackets and lighter, so it can never be mistaken for the week total it is
   deliberately not part of. */
.prov { color:var(--ink-3); font-style:italic; }
.tsgrid td.unsettled .prov { color:var(--crit); font-style:italic; }

/* ------------------------------------------------------------------- width
 *
 * 1,560px on a 1,900px monitor leaves a hand's width of empty ground down each
 * side while a twenty-column grid scrolls sideways in the middle of it. Screens
 * that are mostly a grid take the whole monitor; screens that are mostly prose
 * keep the reading width, because a paragraph two thousand pixels wide is
 * unreadable and no amount of screen makes it otherwise. The button in the top
 * bar overrides both, per browser.
 */
main[data-view="timesheets"],
main[data-view="resourcing"],
main[data-view="gantt"],
main[data-view="invoicing"],
main[data-view="projects"],
main[data-view="tis"],
main[data-view="otherjobs"] { max-width:none; }

:root[data-width="wide"] main { max-width:none; }

/* Prose stays readable however wide the ground under it is. */
main .lede, main .head + p { max-width:78ch; }

/* ------------------------------------------------------------ what a row is
 *
 * A spine down the left, not a wash across the row: colour has to survive being
 * read next to twenty input boxes, and a tinted cell makes a text field look
 * disabled. The legend sits beside the filters, because a colour nobody can
 * read is decoration.
 */
/* 217 PUT THESE ON THE STANDARD. They were four colours chosen for this screen
   alone — a lime spine for running, grey for ahead, amber for owed. Three of
   them already meant what the five states mean, so they now say it in the same
   colours the week board uses: not started is the grey, running is the blue,
   closed with money on it is the amber, closed and settled is the green. A
   reader learns the palette once and it holds wherever they go. */
.sheetgrid tr.t-ahead td.tsname        { box-shadow:inset 3px 0 0 var(--st-pencil); }
.sheetgrid tr.t-running td.tsname      { box-shadow:inset 3px 0 0 var(--st-plan); }
.sheetgrid tr.t-closed_owing td.tsname { box-shadow:inset 3px 0 0 var(--st-look); }
.sheetgrid tr.t-settled td.tsname      { box-shadow:inset 3px 0 0 var(--st-live); opacity:.72; }
.sheetgrid tr.t-settled:hover          { opacity:1; }

.tonekey { display:flex; gap:10px; flex-wrap:wrap; font-size:12px; color:var(--ink-3); }
.tonekey .tk { display:inline-flex; align-items:center; gap:5px; }
.tonekey .tk::before {
  content:''; width:9px; height:9px; border-radius:2px; background:var(--rule-strong);
}
.tonekey .t-ahead::before        { background:var(--st-pencil); }
.tonekey .t-running::before      { background:var(--st-plan); }
.tonekey .t-closed_owing::before { background:var(--st-look); }
.tonekey .t-settled::before      { background:var(--st-live); }

/* ---------------------------------------------------------------- what's new
 *
 * A button that always says the same thing is a button nobody presses. One that
 * carries a number is a question, and the number goes away when it has been
 * answered — per person, so it never nags somebody who has already looked.
 */
.themetoggle.has-new {
  border-color:var(--accent-calm); background:var(--accent-soft); color:var(--accent-ink);
  font-weight:600;
}
.newslist { max-height:58vh; overflow-y:auto; }
.newsitem { padding:10px 0; border-bottom:1px solid var(--rule); }
.newsitem:last-child { border-bottom:none; }
.newsitem.isnew { background:var(--accent-soft); margin:0 -10px; padding:10px; border-radius:4px; }
.ni-head { display:flex; gap:8px; align-items:baseline; flex-wrap:wrap; }
.ni-no {
  font-family:var(--mono); font-size:11px; color:var(--ink-3);
  border:1px solid var(--rule); border-radius:3px; padding:1px 5px;
}
.ni-why { margin:5px 0 0; font-size:13px; color:var(--ink-2); max-width:78ch; }

/* ----------------------------------------------------- a division is a tile
 *
 * Whose people was a dropdown, and a dropdown hid whose screen this actually
 * was: it was built from Europump's workbooks and applies Europump's terms,
 * while 360 of the 386 days on it belong to twenty EE engineers. A tile per
 * division cannot hide that — it carries the count, the days and, above all,
 * whether anybody has written that division's rules down.
 */
.divtiles { grid-template-columns:repeat(auto-fit, minmax(210px, 1fr)); gap:10px; }
.divtile { border-left-color:var(--rule-strong); }
.divtile.on {
  border-color:var(--accent-ink); border-left-color:var(--accent-ink);
  background:var(--accent-soft);
}
/* A division nobody has written rules for is not a problem — it is a fact, and
   it is the one worth seeing before somebody reads a total off this screen. */
.divtile.norule .bt-v { color:var(--ink-2); }
.linky {
  background:none; border:0; padding:0 0 0 4px; cursor:pointer;
  color:inherit; opacity:.6; font:inherit;
}
.linky:hover { opacity:1; color:var(--crit); }


/* ------------------------------------------------------ explanations, folded
 *
 * The prose earns its place the first few times and then stands between
 * somebody and the grid. One button takes all of it away and leaves every
 * figure where it was.
 *
 * BLOCK-LEVEL PROSE ONLY. p.muted is an explanation; span.muted and div.muted
 * inside a cell are data wearing a quiet colour, and hiding those would take
 * facts off the screen. The distinction is the whole reason this is safe.
 */
:root[data-notes="off"] .lede,
:root[data-notes="off"] .note,
:root[data-notes="off"] .panel-body > p.muted,
:root[data-notes="off"] .panel-body > p.empty + p.muted,
:root[data-notes="off"] .dt-basis,
:root[data-notes="off"] .hint { display:none !important; }

/* A panel whose body was nothing BUT explanation should not leave a bar of
   padding behind where its words used to be. */
:root[data-notes="off"] .panel-body:not(:has(> :not(p.muted):not(.note):not(.lede))) {
  display:none;
}


/* --------------------------------------------------------------- working on it
 *
 * A second of nothing reads as a press that missed, and the answer to that is
 * not a spinner over the top of the figures somebody is reading. A bar across
 * the head of the screen, and the pointer says the screen is not taking any
 * more presses — because it is not.
 */
main.working { cursor:progress; }
/* A LIGHT, NOT A SLIDE. This was a gradient travelling 200% across the bar
   every second, pinned to the top of the viewport — directly under the tab strip,
   which made the whole menu look like it was folding sideways. The motion carried
   no information the pill below it does not, and horizontal movement next to text
   somebody is trying to read is the kind that gets noticed. It now breathes in
   place: same colour, same position, nothing moving across. */
/* A DIODE, NOT A LIGHT SHOW — third attempt, and this one was measured rather
   than reasoned about.

   It began as a 3px bar spanning the whole viewport at top:0 with a gradient
   travelling across it every second, plus a "Working…" pill that appeared fixed
   at top:10px dead centre — directly over the header. Timesheets applies this on
   every data load, so switching week or division lit the entire top edge of the
   screen and dropped a pill over the tab strip. Reported, correctly, as the top
   row folding sideways and blinking.

   The first fix stopped the gradient travelling. It was still a full-width bar
   pulsing on and off, so it still blinked — the motion had changed shape, not
   gone. Opening the page and watching it was what settled the question; reading
   the CSS twice had not.

   It is now one 8px dot in the top right, breathing in place. It spans nothing,
   covers nothing, and moves nothing. cursor:progress and aria-busy on the
   main element carry the rest — the pill was decoration, and the accessible
   signal never depended on it. */
main.working::before {
  content:''; position:fixed; top:18px; right:14px; z-index:60;
  width:8px; height:8px; border-radius:50%;
  background:var(--accent);
  animation:npt-working 1.6s ease-in-out infinite;
}
@keyframes npt-working { 0%, 100% { opacity:.3; } 50% { opacity:1; } }
/* Somebody who has asked for less motion gets the bar without the movement. */
@media (prefers-reduced-motion: reduce) {
  main.working::before { animation:none; opacity:1; }
}


/* ------------------------------------------- a tile that fits on the screen
 *
 * Seven of these plus the working underneath ran past the bottom of a laptop,
 * so accepting a day scrolled the next one out of view. Tighter type, tighter
 * rows, and — the part that actually did it — an ordinary day now says one
 * figure instead of three. The clock only appears where it DISAGREES with the
 * work, which is the only time anybody wants to see it.
 */
.daytile .dt-body { padding:7px 9px 9px; font-size:12.5px; }
.daytile .dt-body > * + * { margin-top:4px; }
.daytile .dt-part + .dt-part { margin-top:6px; padding-top:6px; }
.daytile .dt-row { padding:1px 0; }
.daytile .dt-head { padding:7px 20px 7px 10px; }
.daytile .dt-fig { font-size:17px; }
.daytile .dt-jobs li { padding:1px 0; }
.daytile .dt-k { font-size:10px; }

/* Over the contracted day. Amber not red: it is a question about the day, and
   the answer is often "yes, they worked late and it is owed". */
.daytile .dt-over {
  margin:0; font-size:12.5px; font-weight:600; color:var(--warn);
  background:var(--warn-soft); border-radius:6px; padding:4px 7px;
}
.daytile.unsettled .dt-over { color:var(--crit); background:var(--crit-soft); }

/* A site name is one line. "MFG Shell Ketley Service Station (FS446)" wrapped to
   three inside a column a seventh of the screen wide, and three jobs did that
   three times — which is most of why a tile ran off the bottom of a laptop. The
   whole name is on the row's own tooltip, where it costs no height. */
.daytile .dt-jobs li { flex-wrap:nowrap; }
.daytile .dt-js {
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
.daytile .dt-jobs li > .muted { flex:0 0 auto; }


/* ------------------------------------------------ what the figure is made of
 *
 * Three tiles answer the three time-shaped questions. Inside each, a column per
 * stream — because "who do I chase" is a different person for a project
 * instalment and a TIS job, and a one-line split said which without letting
 * anybody act on it. Each column is a button onto that stream's list.
 */
.bigtile .bt-cols {
  display:grid; grid-template-columns:repeat(3, 1fr); gap:1px; margin-top:10px;
  background:var(--rule); border:1px solid var(--rule); border-radius:6px; overflow:hidden;
}
.bt-col {
  display:flex; flex-direction:column; gap:1px; align-items:flex-start;
  background:var(--surface); border:0; padding:6px 8px; cursor:pointer;
  font:inherit; color:inherit; text-align:left; min-width:0;
}
.bt-col:hover, .bt-col:focus-visible { background:var(--surface-3); outline:none; }
.bt-col.empty { cursor:default; opacity:.55; }
.bt-col.empty:hover { background:var(--surface); }
.bt-col .bc-k {
  font-family:var(--mono); font-size:9.5px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--ink-3); font-weight:600; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; max-width:100%;
}
.bt-col .bc-v { font-size:14px; font-weight:650; font-variant-numeric:tabular-nums; }
.bt-col .bc-s {
  font-size:11px; color:var(--ink-3); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; max-width:100%;
}


/* The tile is a box; the headline and each stream column are buttons inside it.
   A button inside a button is invalid and the browser closes the outer one,
   which puts the columns on the page as siblings of the tiles. */
.bigtile { cursor:default; }
.bigtile:hover { background:var(--surface); border-color:var(--rule-strong); }
.bt-main {
  display:flex; flex-direction:column; align-items:flex-start; gap:2px;
  width:calc(100% + 36px); margin:-16px -18px 0; padding:16px 18px 4px;
  background:none; border:0; font:inherit; color:inherit; text-align:left; cursor:pointer;
}
.bt-main:hover, .bt-main:focus-visible { background:var(--surface-2); outline:none; }
.bigtile.bad .bt-main:hover { background:var(--crit-soft); }

/* A heading you can sort by should look like one — and only where it actually
   can, which is why the class is added on hover rather than on render. */
.cansort { cursor:pointer; }
.cansort:hover { background:var(--surface-3); }
th.sortedby { color:var(--accent-ink); }
th.sortedby::after { content:''; font-size:9px; opacity:.8; }
th.sortedby[aria-sort="ascending"]::after { content:' \25B2'; }
th.sortedby[aria-sort="descending"]::after { content:' \25BC'; }

/* A finding handed to somebody is still open — it is just somebody else's. */
tr.assigned > td { background:var(--surface-2); }


/* The stream headings inside an invoicing tile were 9.5px mono in the muted
   ink — legible if you already knew what they said. They name the thing the
   figure underneath belongs to, so they are read FIRST and were the smallest
   text on the tile. */
.bt-col .bc-k {
  font-size: 11.5px;
  letter-spacing: .06em;
  color: var(--ink-2);
  font-weight: 700;
}
.bt-col:hover .bc-k, .bt-col:focus-visible .bc-k { color: var(--accent-ink); }
.bt-col .bc-v { font-size: 15px; }
.bt-col.empty .bc-k { color: var(--ink-3); font-weight: 600; }
/* A little more air, now the heading takes a line of its own more often. */
.bt-col { padding: 7px 9px; }

/* ------------------------------------------------- the week, laid out as 2026
 *
 * The planner everyone uses has the grid on the left and a block down the side
 * naming who is not to be scheduled. Same shape here: the grid takes the room it
 * needs and scrolls on its own, the aside is fixed width and does not.
 *
 * It drops below the grid under 1100px rather than squeezing — a 150px column of
 * wrapped names beside a horizontally scrolling table is worse than either. */
.weekwrap { display:flex; gap:14px; align-items:flex-start; }
.poolaside {
  flex:0 0 208px; border:1px solid var(--rule); border-radius:8px;
  background:var(--surface); max-height:70vh; overflow-y:auto; margin:0 14px 14px 0;
}
.poolaside .panel-head { position:sticky; top:0; z-index:1; }
.poolgroup + .poolgroup { margin-top:12px; padding-top:10px; border-top:1px solid var(--rule); }
.poolhead { font-weight:600; font-size:12.5px; margin-bottom:2px; }
.poolhead.ok   { color:var(--ok); }
.poolhead.warn { color:var(--warn); }
.poolperson {
  font-size:12.5px; padding:3px 6px; border-radius:5px; cursor:default;
  border:1px solid transparent;
}
.poolperson:hover { background:var(--surface-2); border-color:var(--rule); }

@media (max-width:1100px) {
  .weekwrap { display:block; }
  .poolaside { max-width:none; width:auto; max-height:none; margin:12px 14px 14px; }
}

/* The note column the spreadsheet has beside every day. Empty ones stay nearly
   invisible until the cell is hovered — a grid of grey plus signs across eight
   crews and seven days would drown the bookings they sit beside. */
.daynote {
  font-size:11.5px; line-height:1.3; margin-top:3px; padding:2px 4px;
  border-radius:4px; border:1px dashed transparent; color:var(--ink-3);
  cursor:text; white-space:normal; word-break:break-word;
}
.daynote:not(.blank) { border-color:var(--rule); background:var(--surface-2); color:var(--ink); }
.daynote.blank { opacity:0; text-align:center; }
.cell:hover .daynote.blank { opacity:.45; border-color:var(--rule); }
.daynote:hover, .daynote:focus { opacity:1; border-color:var(--accent); outline:none; }

.poolhead.crit { color:var(--crit); }

/* The thick black band between one manager's crews and another's.
 *
 * It is heavy on purpose. On the 2026 sheet this is a solid black row, and it is
 * the single thing that makes sixteen crews readable — without it the grid is
 * one undifferentiated wall and a manager cannot find their own rows. */
tr.groupband > td {
  background:var(--ink); color:var(--ground);
  padding:5px 10px; font-size:12.5px; letter-spacing:.02em;
  border-top:3px solid var(--ink); border-bottom:1px solid var(--ink);
  cursor:pointer; user-select:none; position:sticky; left:0;
}
tr.groupband > td .muted { color:var(--ground); opacity:.62; margin-left:8px; }
tr.groupband > td .chip { margin-left:8px; vertical-align:middle; }
tr.groupband .bandarrow { display:inline-block; width:12px; opacity:.7; }
tr.groupband.shut > td { opacity:.72; }
tr.groupband > td:hover { filter:brightness(1.25); }

/* -------------------------------------------- THE FIVE STATES (147, 217)
 *
 * The classes that carry the standard. They set the two variables and nothing
 * else, so anything that wants to take the standard — a chip here, a bar on the
 * Timeline, a row spine on the Sheet, a tile — only has to read --st-edge and
 * --st-fill. Put the colour in the component instead and the next screen has to
 * be taught the palette again, which is how a standard stops being one.
 *
 * PENCILLED KEEPS ITS DASH as well as its grey. It is the one state that is not
 * a fact about the world but a guess about it, and colour alone is a bad way to
 * carry that: it reads the same to anyone who prints the week in black and
 * white, or who cannot separate grey from olive. Two channels for the one that
 * matters, one channel for the rest. */
.st-pencilled { --st-edge:var(--st-pencil); --st-fill:var(--st-pencil-soft); }
.st-scheduled { --st-edge:var(--st-plan);   --st-fill:var(--st-plan-soft); }
.st-confirmed { --st-edge:var(--st-live);   --st-fill:var(--st-live-soft); }
.st-attention { --st-edge:var(--st-look);   --st-fill:var(--st-look-soft); }
.st-problem   { --st-edge:var(--st-stop);   --st-fill:var(--st-stop-soft); }

.booking.st-pencilled {
  border:1px dashed var(--st-pencil); border-left-width:3px;
  border-left-style:solid; font-style:italic; opacity:.86;
}
/* Pencilled, and JobWatch has since put the crew on that very site. Present,
   superseded, and waiting for somebody to confirm it or rub it out — it is
   never removed on anybody's behalf. */
.booking.pencil.replaced { opacity:.55; }
.booking.pencil.replaced > :first-child { text-decoration:line-through; }

/* The legend. A colour nobody can read is decoration, so every screen that
   uses the standard shows the same key in the same shape, beside the filters
   rather than at the bottom where it would be found by nobody. */
.statuskey { display:flex; gap:12px; flex-wrap:wrap; font-size:12px; color:var(--ink-3); }
.statuskey .sk { display:inline-flex; align-items:center; gap:5px; }
.statuskey .sk::before {
  content:''; width:11px; height:11px; border-radius:2px; flex:0 0 auto;
  background:var(--st-fill); border:1px solid var(--st-edge); border-left-width:3px;
}
.statuskey .st-pencilled::before { border-style:dashed; border-left-style:solid; }

/* What the paperwork folds into. 133 of 356 jobs in a week are toolbox talks,
   inspections and audits; drawn one chip each they bury the work. One press
   opens them, so nothing is hidden — only stacked. */
.adminwrap { margin-bottom:3px; }
.adminwrap > summary { list-style:none; }
.adminwrap > summary::-webkit-details-marker { display:none; }
.adminfold {
  display:inline-flex; align-items:center; gap:5px;
  font-size:11px; font-family:var(--mono); letter-spacing:.04em;
  color:var(--ink-3); background:transparent;
  border:1px dashed var(--rule-strong); border-radius:3px;
  padding:1px 6px; margin-bottom:3px; cursor:pointer;
}
.adminfold:hover, .adminfold:focus-visible { color:var(--ink); border-color:var(--ink-3); outline:none; }
.adminfold[aria-expanded="true"] { border-style:solid; }
.booking.admin {
  font-size:11.5px; opacity:.8; max-width:190px;
  --st-edge:var(--rule-strong); --st-fill:transparent;
}

/* Dragging somebody into a crew (150).
 *
 * The grab cursor is the only thing that says a name can be moved at all — there
 * is no other affordance on a plain span, and a feature nobody can see is a
 * feature nobody uses. */
[data-person][draggable="true"] { cursor:grab; }
[data-person][draggable="true"]:active { cursor:grabbing; }
.crewname[draggable="true"]:hover,
.chip.name[draggable="true"]:hover,
.poolperson[draggable="true"]:hover { outline:1px dashed var(--rule-strong); outline-offset:1px; }
[data-person].dragging { opacity:.4; }

/* The row being dropped onto. Strong enough to be unmistakable while a name is
   hovering over it, because dropping on the wrong crew is silent and annoying to
   undo — it moves somebody's standing crew. */
tr.droptarget > td {
  background:var(--accent-soft);
  box-shadow:inset 0 0 0 2px var(--accent);
}
tr.droptarget > td:first-child { position:relative; }

/* A tile that opens to show its workings (154).
 *
 * The cursor and the lift on hover are the only things that say a figure can be
 * taken apart. Without them it reads as a number on a card and nobody clicks it. */
.stat.opens { cursor:pointer; transition:box-shadow .12s, transform .12s; }
.stat.opens:hover { box-shadow:0 2px 10px rgba(0,0,0,.10); transform:translateY(-1px); }
.stat.opens:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.stat.opens .k::after { content:' ›'; opacity:.45; font-weight:700; }

/* Buttons sitting on the black group band. They inherit nothing useful from the
   band's inverted colours, so they are stated: light text, faint outline, and a
   solid fill on hover so it is obviously a control and not a label. */
tr.groupband > td .btn {
  margin-left:8px; vertical-align:middle;
  background:transparent; color:var(--ground);
  border-color:rgba(255,255,255,.42);
}
tr.groupband > td .btn:hover {
  background:rgba(255,255,255,.16); border-color:rgba(255,255,255,.7);
}

/* ------------------------------------------- the procedures dock (159)
 *
 * Docked right and fixed, so it stays put while the screen behind it scrolls —
 * the point is reading the next step while looking at the job, and a panel that
 * scrolls away with the page is a panel you lose halfway down a project.
 *
 * The body gets padding rather than the dock overlaying: covering the right-hand
 * edge of a table is how you hide the column somebody was reading. */
#procdock {
  position:fixed; top:var(--topbar-h); right:0; bottom:0; width:330px; z-index:40;
  background:var(--surface); border-left:1px solid var(--rule);
  display:flex; flex-direction:column; box-shadow:-2px 0 12px rgba(0,0,0,.06);
}
/* AND THIS LINE IS WHY THE CLOSE BUTTON DID NOTHING.
 *
 * `el.hidden = true` sets the attribute, and the browser's own [hidden] rule is
 * display:none — but that is an attribute selector, and `#procdock` above is an
 * id. The id wins, display:flex stays, and the panel never goes away. It was not
 * that the × failed to fire; it fired every time and the panel ignored it.
 *
 * There is no [hidden] rule anywhere else in this stylesheet, so nothing else
 * was relying on one. An id-plus-attribute selector outranks the id alone. */
#procdock[hidden] { display:none; }
body.has-dock { padding-right:330px; }
#procdock .dockhead {
  padding:10px 12px; font-weight:600; font-size:13.5px;
  border-bottom:1px solid var(--rule); background:var(--surface-2);
  display:flex; align-items:center; justify-content:space-between; gap:8px;
}
#procdock .dockbody { padding:12px; overflow-y:auto; flex:1 1 auto; }
#procdock .docksec {
  font-size:11px; text-transform:uppercase; letter-spacing:.07em;
  color:var(--ink-3); margin:14px 0 6px;
}
#procdock .docksec:first-child { margin-top:0; }
#procdock .dockitem {
  display:block; width:100%; text-align:left; margin-bottom:6px; cursor:pointer;
  padding:8px 10px; border:1px solid var(--rule); border-radius:6px;
  background:var(--surface); font:inherit; font-size:13px; color:inherit;
}
#procdock .dockitem:hover { background:var(--surface-2); border-color:var(--rule-strong); }
#procdock .dockitem .muted { font-size:12px; margin-top:2px; }

#procdock .dockstep { padding:8px 0; border-top:1px solid var(--rule); }
#procdock .dockstep:first-of-type { border-top:none; }
#procdock .dockstep label { display:flex; gap:8px; align-items:flex-start; font-size:13.5px; cursor:pointer; }
#procdock .dockstep.done > label > span { text-decoration:line-through; color:var(--ink-3); }
/* The guidance is why anybody opened the panel, so it is printed, not hidden
   behind a hover. */
#procdock .dockguide {
  margin:6px 0 0 22px; padding:7px 9px; font-size:12.5px; line-height:1.45;
  background:var(--surface-2); border-left:2px solid var(--accent); border-radius:0 4px 4px 0;
}
#procdock .dockask { margin:6px 0 0 22px; }
#procdock .dockask label { display:block; font-size:11.5px; color:var(--ink-3); margin-bottom:3px; }
#procdock .dockask input { width:100%; font-size:13px; }

/* Under 1100px it would leave a strip of screen for the actual work. */
@media (max-width:1100px) {
  #procdock { width:100%; left:0; }
  body.has-dock { padding-right:0; }
}

/* The search at the top of the dock. Full width, because a 330px panel has no
   room for a box that is only part of it. */
#procdock #procq { width:100%; margin-bottom:4px; font-size:13px; }

/* The edit button beside each procedure in the dock. Laid out as a row so the
   big target starts it and the small one edits it — the common action is the one
   that gets the whole card. */
#procdock .dockrow { display:flex; align-items:flex-start; gap:4px; }
#procdock .dockrow .dockitem { flex:1 1 auto; margin-bottom:6px; }
#procdock .dockrow > .btn { flex:0 0 auto; margin-top:8px; }

/* Writing a step. The guidance box is the tall one on purpose: the label is a
   checklist line, the guidance is the knowledge, and equal boxes would say they
   matter equally. */
.stepedit { padding:10px 0; border-top:1px solid var(--rule); }
.stepedit:first-of-type { border-top:none; }
.stepedit input, .stepedit textarea { width:100%; margin-bottom:5px; font-size:13px; }
.stepedit textarea { resize:vertical; min-height:58px; line-height:1.45; }

/* ------------------------------------------------------ the banner (163)
 *
 * IN THE FLOW, NOT OVER IT. A bar that floats above the page covers whatever
 * somebody is reading, and they dismiss it without reading it to get at what is
 * underneath. This sits between the tabs and the screen and pushes.
 *
 * The tone is a left edge and a tint rather than a solid block of colour: a
 * full-width saturated bar reads as an error even when it says the rota is up,
 * and after the second one people stop looking at them. */
#banners { display:flex; flex-direction:column; gap:1px; }
.banner {
  display:flex; align-items:center; gap:12px;
  padding:9px 16px; font-size:13.5px; line-height:1.45;
  background:var(--surface-2); border-bottom:1px solid var(--rule);
  border-left:4px solid var(--ink-3);
  animation:banner-in .22s ease-out;
}
@keyframes banner-in { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce) { .banner { animation:none; } }

.banner-mark {
  flex:0 0 20px; height:20px; border-radius:50%;
  display:inline-flex; align-items:center; justify-content:center;
  font-weight:700; font-size:12px; color:var(--on-accent); background:var(--ink-3);
}
.banner-text { flex:1 1 auto; min-width:0; }
.banner-text strong { margin-right:8px; }
.banner-text a { margin-left:10px; white-space:nowrap; }
.banner-who { flex:0 0 auto; color:var(--ink-3); font-size:12px; white-space:nowrap; }
.banner-shut {
  flex:0 0 auto; border:1px solid var(--rule-strong); background:var(--surface);
  border-radius:5px; padding:3px 10px; font:inherit; font-size:12.5px; cursor:pointer;
}
.banner-shut:hover { background:var(--surface-2); }

.banner.info   { border-left-color:var(--accent); }
.banner.info   .banner-mark { background:var(--accent); }
.banner.good   { border-left-color:var(--ok);   background:var(--ok-soft); }
.banner.good   .banner-mark { background:var(--ok); }
.banner.warn   { border-left-color:var(--warn); background:var(--warn-soft); }
.banner.warn   .banner-mark { background:var(--warn); }
.banner.urgent { border-left-color:var(--crit); background:var(--crit-soft); }
.banner.urgent .banner-mark { background:var(--crit); }
/* Urgent earns a heavier edge and nothing more. Anything louder and the next
   one has to be louder still. */
.banner.urgent { border-left-width:6px; font-weight:500; }

@media (max-width:760px) {
  .banner { flex-wrap:wrap; gap:8px; }
  .banner-who { display:none; }
}

/* A name you can open (165). The cursor is the only thing that says so, and
   without it nobody discovers the menu. */
[data-person]:not(button):not([draggable="true"]) { cursor:pointer; }
[data-person]:not(button):hover { text-decoration:underline dotted; text-underline-offset:2px; }
/* A draggable name keeps the grab cursor — moving crews is the commoner action
   there, and two affordances on one element teaches neither. */
[data-person][draggable="true"] { cursor:grab; }

/* A STEP, CORRECTED IN PLACE.
   The word is the control — no pencil icon, no hover-only affordance — because
   a checklist is read far more often than it is edited and an icon on every
   line would be nine pieces of furniture on a nine-step task. The cue is a
   dotted underline that only appears under the pointer. */
.stepword { cursor:text; border-bottom:1px dashed transparent; }
.stepword:hover { border-bottom-color:var(--ink-3); }
.stepword:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }
.stepedit {
  font:inherit; color:var(--ink); background:var(--surface);
  border:1px solid var(--brand); border-radius:4px;
  padding:1px 5px; width:min(100%, 44ch);
}

/* THE SHEET'S OWN SCROLL BOX — see freezeSheet() for why the height is measured.
   Two header rows: the first spans both, the second sits under it at the height
   the first actually came out as. */
/* Tab has to clear the WHOLE heading, both rows of it — not the first row,
   which left the focused box half under the second. */
.sheetwrap { scroll-padding-top:var(--sheet-head, 68px); scroll-padding-left:200px; }
.sheetgrid thead tr:nth-child(2) th { top:var(--sheet-r1, 34px); z-index:3; }
.sheetgrid thead tr:first-child th { z-index:4; }
.sheetgrid thead th.tsname { z-index:6; }
/* A cell being typed in, visible against a frozen column and a frozen heading. */
.sheetgrid input.cell:focus, .sheetgrid select.cell:focus {
  outline:2px solid var(--brand); outline-offset:-1px; position:relative; z-index:1;
}

/* A tile put away. Same shelf as a folded section — see applyFolds(). */
.bigtile { position:relative; }
.bigtile.stowed { display:none !important; }
.bigtile .tilehide {
  float:none; position:absolute; top:4px; right:6px; margin:0; font-size:13px;
}

/* WORKING. A two-pixel bar across the very top, above everything including the
   header, shown only once a request has been in flight for a quarter of a
   second. It reports that NPT is fetching — it does not pretend to know how far
   along it is, so it slides rather than fills. */
.busybar {
  position:fixed; top:0; left:0; right:0; height:2px; z-index:200;
  background:linear-gradient(90deg, transparent, var(--brand), transparent);
  background-size:40% 100%; background-repeat:no-repeat;
  animation:busyslide 1.1s linear infinite;
}
@keyframes busyslide {
  from { background-position:-40% 0; }
  to   { background-position:140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .busybar { animation:none; background:var(--brand); opacity:.5; }
}

/* ---------------------------------------------------------------- the ghost
   The shape of an answer before the answer arrives. Grey blocks the size of
   what is coming, so the page does not jump when the real thing lands and the
   eye has somewhere to rest. See ghost() in app.js.

   It shimmers rather than pulses: a pulse reads as a warning, a slow sweep
   reads as work in progress. Under prefers-reduced-motion it simply sits
   there, which says the same thing without moving. */
.gh-bar {
  height:10px; border-radius:3px; margin:6px 0;
  background:linear-gradient(90deg, var(--surface-2) 0%, var(--surface-3) 50%, var(--surface-2) 100%);
  background-size:200% 100%;
  animation:ghsweep 1.4s ease-in-out infinite;
}
.gh-head { padding:8px 0 18px; }
.gh-head .gh-bar:first-child { height:20px; }
.gh-row { display:flex; gap:18px; align-items:center; padding:7px 0; border-bottom:1px solid var(--rule); }
.gh-row:last-child { border-bottom:0; }
.panel.gh .panel-head { padding-block:14px; }
.stat.gh { min-height:78px; }
@keyframes ghsweep {
  from { background-position:200% 0; }
  to   { background-position:-200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gh-bar { animation:none; background:var(--surface-2); }
}

/* WHICH OF THE TWO IS IN FORCE, on the sheet.
   A finish date and a number of days say the same thing from opposite ends and
   only one of them can be the commitment. The pinned one is outlined; the one
   worked out FROM it is dimmed, so a glance says which is being obeyed. */
.sheetgrid input.cell.pinned {
  border-color:var(--brand); box-shadow:inset 0 0 0 1px var(--brand);
}
.sheetgrid input.cell.derived { color:var(--ink-3); font-style:italic; }

/* ============================================================ INSTALMENTS
 *
 * The panel used to open five blocks deep: a table, a note, a form for adding
 * an instalment, a list of trigger points and a form for adding one of those.
 * Two activities open meant four open forms, and the heaviest thing on screen
 * was the action taken least often.
 *
 * Everything here reads the five-state standard (217) rather than naming a
 * colour, so amber means on this panel exactly what it means on the week board
 * and on a purchase order, and dark mode follows without being restated.
 */
.chip.money {
  border-color:var(--st-edge); background:var(--st-fill); color:var(--st-edge);
  font-weight:600;
}
.instchips { margin-top:4px; display:flex; flex-wrap:wrap; gap:3px; }

/* The spine on the activity row: the verdict, readable without opening
   anything. Pencilled has no colour of its own and gets none. */
tr.moneyrow > td:first-child { border-left:3px solid transparent; }
tr.moneyrow.st-confirmed > td:first-child,
tr.moneyrow.st-attention > td:first-child,
tr.moneyrow.st-problem   > td:first-child { border-left-color:var(--st-edge); }

.moneypanel { border-left:3px solid var(--st-edge); padding-left:11px; }
.mp-head {
  display:flex; align-items:baseline; justify-content:space-between;
  gap:12px; flex-wrap:wrap; margin:0 0 10px;
}
.mp-verdict { font-size:13px; color:var(--st-edge); }
.moneypanel.st-pencilled .mp-verdict { color:var(--ink-3); }

/* Settled money, read not retyped. */
.mp-summary { list-style:none; margin:0 0 12px; padding:0; display:grid; gap:4px; }
.mp-sum {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--rule);
  border-left:3px solid var(--st-edge); border-radius:0;
  padding:6px 10px; font-size:13px;
}
.mp-sum-name { color:var(--ink); }
.mp-sum-val { font-family:var(--mono); color:var(--ink); margin-left:auto; }

/* A row still wanting a value or a date says so, and only its empty fields
   are emphasised — the rest of the row is not the problem. */
.moneypanel table.inner tr.needs > td:first-child { box-shadow:inset 3px 0 0 var(--st-look); }
.moneypanel table.inner tr.needs input.cell:placeholder-shown,
.moneypanel table.inner tr.needs input.cell[value=""] {
  border-color:var(--st-look); background:var(--st-look-soft);
}

.mp-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.mp-note { font-size:12.5px; margin-left:auto; }

/* ------------------------------------------------------------- the dialog
 * A dialog overlays rather than reflows, so opening one does not push the
 * list somebody is reading down the page. NPT had none before this. */
dialog.npt-dialog {
  border:1px solid var(--rule-strong); border-radius:4px; padding:0;
  background:var(--surface); color:var(--ink);
  width:min(420px, calc(100vw - 32px));
  box-shadow:0 10px 30px rgb(0 0 0 / 22%);
}
dialog.npt-dialog.wide { width:min(560px, calc(100vw - 32px)); }
dialog.npt-dialog::backdrop { background:rgb(0 0 0 / 45%); }
dialog.npt-dialog h3 { margin:0; padding:12px 14px 0; font-size:15px; }
dialog.npt-dialog .dlg-lede { margin:6px 14px 12px; font-size:13px; }
dialog.npt-dialog .dlg-lede .gap { color:var(--warn); font-family:var(--mono); }
dialog.npt-dialog > form:not(.dlg-x) { padding:0 14px 14px; }
dialog.npt-dialog .mslist { margin:0 14px 12px; }
dialog.npt-dialog .dlg-x { position:absolute; top:8px; right:8px; padding:0; }
dialog.npt-dialog .dlg-foot {
  display:flex; gap:8px; justify-content:flex-end; margin-top:14px;
  border-top:1px solid var(--rule); padding-top:12px;
}
dialog.npt-dialog .formrow { display:flex; gap:10px; }
dialog.npt-dialog .formrow .f { flex:1; }

/* Editing one instalment. The invoiced block is framed in the status amber's
   neutral cousin rather than amber itself: being invoiced is not a warning,
   and the panel's amber already means "somebody needs to fill this in". */
dialog.npt-dialog .dlg-check { display:flex; align-items:flex-end; padding-bottom:6px; }
dialog.npt-dialog .dlg-check label { display:flex; gap:6px; align-items:center; margin:0; }
dialog.npt-dialog .dlg-invoiced {
  margin:12px 0 0; padding:10px 12px; background:var(--surface-2);
  border:1px solid var(--rule); border-left:3px solid var(--st-live); border-radius:0;
}
dialog.npt-dialog .dlg-invoiced[hidden] { display:none; }
dialog.npt-dialog .dlg-inv-line { margin:0 0 4px; font-size:13.5px; color:var(--ink); }
dialog.npt-dialog .dlg-inv-note { margin:0 0 10px; font-size:12.5px; }
dialog.npt-dialog .dlg-inv-actions { display:flex; gap:8px; flex-wrap:wrap; }
dialog.npt-dialog [hidden] { display:none !important; }
.mp-sum .btn { margin-left:4px; }

/* Complete (230): invoiced in full. Green because it is the settled state of
   the five-state standard, the same green a confirmed booking carries. */
.chip.complete { border-color:var(--st-live); background:var(--st-live-soft); color:var(--st-live); font-weight:600; }

/* ------------------------------------------- invoiced in full, POs in place (234) */
dialog.npt-dialog .invlines { list-style:none; margin:0 14px 12px; padding:0;
  border:1px solid var(--rule); border-radius:4px; }
dialog.npt-dialog .invlines li { display:flex; gap:12px; align-items:baseline;
  padding:7px 10px; border-top:1px solid var(--rule); font-size:13px; }
dialog.npt-dialog .invlines li:first-child { border-top:0; }
dialog.npt-dialog .invlines-what { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px; }
dialog.npt-dialog .invlines-sum { flex:0 0 auto; font-variant-numeric:tabular-nums; font-family:var(--mono); }
dialog.npt-dialog .invlines-total { background:var(--surface-2); font-weight:600; }
dialog.npt-dialog .invlines-total span:first-child { flex:1 1 auto; }
dialog.npt-dialog .dlg-err { color:var(--crit); font-size:13px; margin:8px 0 0; min-height:1em; }

.poinplace { margin:0 0 14px; padding:10px 14px; border:1px solid var(--rule);
  border-left:4px solid var(--brand); background:var(--ok-soft); border-radius:4px; }
.poinplace.short { border-left-color:var(--warn); background:var(--warn-soft); }
.poinplace-head { margin:0 0 6px; font-size:13.5px; display:flex; flex-wrap:wrap; gap:8px; align-items:baseline; }
.poinplace-list { margin:0; padding-left:18px; font-size:13px; }
.poinplace-list li { margin:2px 0; }
details.pomore { margin:6px 0 0; }
details.pomore > summary { cursor:pointer; font-size:13px; color:var(--ink-2, inherit); padding:6px 0; }
details.pomore[open] > summary { margin-bottom:8px; }

/* ------------------------------------------------------------- month end (238)
 * Four tiles side by side on a wide screen, two on a laptop, one on a phone.
 * Every figure is a button and looks like one only on hover: a tile of
 * outlined buttons reads as a form, and this is a report somebody scans first. */
.metiles { display:grid; grid-template-columns:repeat(auto-fit, minmax(340px, 1fr)); gap:16px; margin-top:18px; }
.metile { background:var(--surface); border:1px solid var(--rule); border-radius:5px; padding:14px 16px; min-width:0; }
.metile h2 { font-size:16px; margin:0 0 10px; display:flex; gap:8px; align-items:center; }
.merow { display:flex; justify-content:space-between; align-items:flex-start; gap:10px; padding:8px 0; border-top:1px solid var(--rule); }
.merow .k { color:var(--ink-2); font-size:13px; padding-top:5px; }
.merow.quiet .mefig .v { font-size:14px; color:var(--ink-2); }
.mefig { appearance:none; border:1px solid transparent; background:none; color:var(--ink); font:inherit; cursor:pointer;
  text-align:right; padding:4px 6px; border-radius:4px; display:flex; flex-direction:column; align-items:flex-end; min-width:0; }
.mefig:hover, .mefig:focus-visible { border-color:var(--rule-strong); background:var(--surface-2); }
.mefig.open { border-color:var(--accent-calm); background:var(--accent-soft); }
.mefig .v { font-family:var(--mono); font-size:15px; font-weight:600; font-variant-numeric:tabular-nums; white-space:nowrap; }
.mefig.big .v { font-size:20px; }
.mefig .s { color:var(--ink-3); font-size:11.5px; line-height:1.3; max-width:22ch; }
.mefig.bad .v { color:var(--crit); }
.metable { width:100%; border-collapse:collapse; margin:4px 0 6px; table-layout:fixed; }
.metable.wide { table-layout:auto; }
.metable th, .metable td { padding:4px 2px; border-bottom:1px solid var(--rule); vertical-align:top; }
.metable:not(.wide) thead th:first-child, .metable:not(.wide) tbody th { width:26%; }
.metable:not(.wide) thead th:not(:first-child) { text-align:right; padding-right:8px; white-space:normal; }
.metable td .mefig .v { font-size:13.5px; }
.metable thead th { background:none; font-size:10px; }
.metable tbody th { font-family:var(--sans); font-size:13px; text-transform:none; letter-spacing:0; color:var(--ink-2);
  background:none; font-weight:600; padding-top:9px; }
.metable td .mefig { width:100%; }
.metable.wide th, .metable.wide td { padding:6px 10px; }
.metable .n { text-align:right; font-family:var(--mono); font-variant-numeric:tabular-nums; }
.metable .m { font-family:var(--mono); }
tr.mebulk > td { background:var(--surface-2); }
tr.mebulkrow > td { font-size:13px; color:var(--ink-2); }
.mesettings { display:grid; gap:22px; }
.mesettings h3 { font-size:15px; margin:0 0 4px; }
.mesettings h4 { font-size:13px; margin:12px 0 4px; color:var(--ink-2); }
.mesettings p.muted { margin:0 0 8px; font-size:13px; max-width:80ch; }
.melist { list-style:none; padding:0; margin:0; display:grid; gap:4px; }
.melist li { display:flex; gap:8px; align-items:center; }


/* --------------------------------------------- easier to read (239)
 * Tabs inside a screen, drawn like the screen tabs above them so they read as
 * the same kind of thing: a quiet row, the open one underlined in the lime. */
.tabbar { display:flex; gap:2px; border-bottom:1px solid var(--rule); margin:0 0 18px; flex-wrap:wrap; }
.tabbtn { appearance:none; border:0; border-bottom:2px solid transparent; background:none; color:var(--ink-2);
  font:inherit; font-size:14.5px; padding:9px 14px; cursor:pointer; margin-bottom:-1px; }
.tabbtn:hover { color:var(--ink); }
.tabbtn.on { color:var(--ink); border-bottom-color:var(--brand); font-weight:600; }
.tabbtn:focus-visible { outline:2px solid var(--accent-calm); outline-offset:2px; }

/* A project, over the list. Below the top bar and the screen tabs, so where you
   are stays on screen; above everything on the page; below dialogs and toasts,
   which can be opened from inside it. */
.projpanel { position:fixed; left:0; right:0; bottom:0; top:var(--header-h); z-index:28; }
.projpanel-veil { position:absolute; inset:0; background:rgba(10,12,8,.45); cursor:pointer; }
.projpanel-box { position:absolute; top:10px; bottom:10px; left:max(10px, calc((100vw - 1500px) / 2));
  right:max(10px, calc((100vw - 1500px) / 2)); overflow:auto; overscroll-behavior:contain;
  background:var(--ground); border:1px solid var(--rule-strong); border-radius:6px;
  box-shadow:0 18px 50px -20px rgba(0,0,0,.55); }
.projpanel .detail-wrap { padding:0 14px 6px; }
.projpanel .detail-wrap > .detail-grid > .detail-main > .panel:first-child,
.projpanel #detail { margin-top:0; border-top:0; border-radius:0 0 5px 5px; }
.projpanel .detail-side { top:64px; }
.projpanel .detail-side .feed { max-height:calc(100vh - var(--header-h) - 340px); }
@media (max-width:700px) {
  .projpanel-box { top:0; bottom:0; left:0; right:0; border-radius:0; border:0; }
}

/* A project from its group job: what will be made, before it is. */
.grcard { border:1px solid var(--rule); border-radius:5px; padding:14px 16px; margin-top:10px; background:var(--surface); }
.grfacts { display:grid; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); gap:6px 18px; margin:0 0 12px; }
.grfacts > div { display:flex; gap:10px; align-items:baseline; min-width:0; }
.grfacts dt { font-family:var(--mono); font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3); min-width:96px; }
.grfacts dd { margin:0; min-width:0; }

/* Questions and answers. */
details.qa { border-bottom:1px solid var(--rule); padding:9px 0; }
details.qa summary { cursor:pointer; font-weight:600; }
details.qa summary:focus-visible { outline:2px solid var(--accent-calm); outline-offset:2px; }
details.qa .qa-a { color:var(--ink-2); margin-top:6px; }
details.qa .qa-a p { margin:0 0 6px; max-width:75ch; }
.qa-h { font-size:11px; font-family:var(--mono); letter-spacing:.11em; text-transform:uppercase; color:var(--ink-3); margin:20px 0 2px; }

/* ------------------------------------------------- the tracker sheet (242)
 * Projects → Sheet, laid out as the FY27 tracker: one tab per division, a
 * project's lines together with their total under them, yellow where something
 * NPT needs is blank (the workbook's own shading), green where an instalment
 * is invoiced (as people coloured it), and the days to the right.
 *
 * Where a project really is — project_now — has one colour each, used on the
 * sheet, its timeline and the list, so it is learnt once. */
:root {
  --trk-miss:#FFF2A0; --trk-miss-edge:#D9BD00;
  --trk-paid:#DCEEC4;
  --lv-bill:#6B4AA0; --lv-bill-soft:#EEE8F7;
  --tl-wk:rgba(17,19,17,.05);
}
:root[data-theme="dark"] {
  --trk-miss:#3B3510; --trk-miss-edge:#A88F12;
  --trk-paid:#1E2D10;
  --lv-bill:#B79BE6; --lv-bill-soft:#241B35;
  --tl-wk:rgba(255,255,255,.045);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --trk-miss:#3B3510; --trk-miss-edge:#A88F12;
    --trk-paid:#1E2D10;
    --lv-bill:#B79BE6; --lv-bill-soft:#241B35;
    --tl-wk:rgba(255,255,255,.045);
  }
}

/* Each status sets an edge and a fill; everything else reads those two. */
.lv-active     { --lv:var(--ok);       --lv-soft:var(--ok-soft); }
.lv-oncoming   { --lv:var(--st-plan);  --lv-soft:var(--st-plan-soft); }
.lv-delayed    { --lv:var(--crit);     --lv-soft:var(--crit-soft); }
.lv-to_invoice { --lv:var(--lv-bill);  --lv-soft:var(--lv-bill-soft); }
.lv-not_booked { --lv:var(--warn);     --lv-soft:var(--warn-soft); }
.lv-pencilled  { --lv:var(--st-pencil); --lv-soft:var(--surface-2); }
.lv-on_hold    { --lv:var(--ink-3);    --lv-soft:var(--surface-2); }
.lv-closed     { --lv:var(--ink-3);    --lv-soft:var(--surface-2); }
.lv-cancelled  { --lv:var(--ink-3);    --lv-soft:var(--surface-2); }
.chip.lv { background:var(--lv-soft); color:var(--lv); border-color:var(--lv); font-weight:600; }
.chip.lv.lv-cancelled { text-decoration:line-through; }
.chip.lv.lv-on_hold { border-style:dashed; }
.chip.lv.lv-pencilled { border-style:dashed; background:transparent; }
button.chip.lv { cursor:pointer; font:inherit; font-size:12px; }
button.chip.lv.on { box-shadow:0 0 0 2px var(--lv); }

/* The division colours of the workbook's title rows. */
.dv-EE  { --dv:#92D050; }
.dv-EI  { --dv:#FFC000; }
.dv-EPM { --dv:#F2E600; }

.sheettabs { display:flex; align-items:flex-end; gap:4px; flex-wrap:wrap; margin:6px 0 0; }
.sheettab {
  font:inherit; font-weight:700; font-size:14px; cursor:pointer;
  border:1px solid var(--rule-strong); border-bottom:0; border-radius:6px 6px 0 0;
  background:var(--surface-2); color:var(--ink-2); padding:6px 18px;
}
.sheettab .n { font-weight:400; color:var(--ink-3); margin-left:4px; font-variant-numeric:tabular-nums; }
.sheettab.on { background:var(--surface); color:var(--ink); box-shadow:inset 0 4px 0 var(--dv); }
.sheettab:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
/* Resourcing's division tabs (247): the Sheet's tabs, with "All" marked in the accent. */
.divtabs { margin:0 0 14px; border-bottom:1px solid var(--rule-strong); }
.divtabs .sheettab:not([class*="dv-"]) { --dv:var(--accent); }
.divtabs .divnote { margin-left:auto; align-self:center; font-size:12.5px; padding-bottom:4px; }
.poolnodiv { margin-top:10px; border-top:1px solid var(--rule); padding-top:8px; }
.poolnodiv > summary { cursor:pointer; font-size:12.5px; color:var(--ink-2); }
.sheettitle {
  margin-left:auto; align-self:center; background:var(--dv); color:#111311;
  font-weight:800; letter-spacing:.06em; font-size:12.5px; padding:4px 12px; border-radius:4px;
}
.sheetpanel { margin-top:0; border-top-left-radius:0; }
.sheetbar { display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px; }
.sheetbar .f { margin:0; }
.sheetbar .f.newgr { flex:0 1 270px; }
.sheetbar .inl, .trkdlg .inl { display:flex; gap:6px; align-items:center; }
.sheetbar .inl input { flex:1; min-width:0; }
.sheetbar .legend { display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
.sheetbar .tick, .trkdlg .tick { display:inline-flex; align-items:center; gap:5px; font-size:13px; cursor:pointer; margin:0; }
.sheetbar .zoom { display:flex; gap:4px; margin-left:auto; }
.sheetbar .zoom [data-zoom="0"] { min-width:56px; font-variant-numeric:tabular-nums; }
.grprev { margin:0 14px 12px; padding:9px 12px; border:1px dashed var(--rule-strong); border-radius:5px;
  display:flex; flex-direction:column; gap:8px; align-items:flex-start; font-size:13.5px; }
.grprev .chip.dv-EE, .grprev .chip.dv-EI, .grprev .chip.dv-EPM { background:var(--dv); color:#111311; border-color:transparent; }
.grprev .pencilmatch { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.grprev .pencilmatch > span { width:100%; color:var(--ink-2); }
.grprev .pencilmatch .btn { border-style:dashed; }
.sheetfoot { font-size:12.5px; margin:8px 2px 0; }

.sheetwrap2 { overflow:auto; position:relative; border-top:1px solid var(--rule); scroll-padding-left:360px; scroll-padding-top:70px; }
table.trk { border-collapse:separate; border-spacing:0; width:max-content; font-size:12.5px; line-height:1.3; }
table.trk th {
  position:sticky; top:0; z-index:3; background:var(--surface-2); color:var(--ink-2);
  font-size:11px; font-weight:700; text-align:left; vertical-align:bottom; line-height:1.2;
  padding:5px 6px; border-right:1px solid var(--rule); border-bottom:1px solid var(--rule-strong);
  max-width:120px; white-space:normal; text-transform:none; letter-spacing:0; font-family:var(--sans);
}
table.trk thead tr:nth-child(2) th { top:var(--trk-r1, 24px); }
table.trk td {
  background:var(--surface); padding:2px 4px; vertical-align:middle; white-space:nowrap;
  border-right:1px solid var(--rule); border-bottom:1px solid var(--rule);
}
table.trk .sticky0 { position:sticky; left:0; z-index:2; width:150px; min-width:150px; max-width:150px; }
table.trk .sticky1 { position:sticky; left:150px; z-index:2; width:210px; min-width:210px; max-width:210px;
  box-shadow:2px 0 0 var(--rule-strong); }
table.trk thead .sticky0, table.trk thead .sticky1 { z-index:6; }
table.trk td.cu, table.trk td.pj, table.trk td.gr, table.trk td.qb, table.trk td.st {
  vertical-align:top; white-space:normal; padding:6px;
}
table.trk td.cu { box-shadow:inset 5px 0 0 var(--lv); background:var(--lv-soft); padding-left:11px; }
table.trk td.cu strong { font-size:13px; }
table.trk td.pj .site { font-size:11.5px; margin:1px 4px 0; }
table.trk td.pj .pjbtns { margin-top:4px; }
table.trk td.gr { min-width:92px; }
table.trk td.gr .chip { margin:0 2px 2px 0; }
table.trk td.qb { min-width:74px; }
table.trk td.st { min-width:98px; }
table.trk td.st .src { font-size:10.5px; color:var(--ink-3); margin-top:3px; }

table.trk .cell {
  width:100%; border:1px solid transparent; border-radius:3px; background:transparent;
  font:inherit; color:inherit; padding:3px 4px;
}
table.trk .cell:hover:not(:disabled) { border-color:var(--rule-strong); }
table.trk .cell:focus { outline:2px solid var(--accent); outline-offset:-1px; background:var(--surface); }
table.trk .cell.n { text-align:right; font-variant-numeric:tabular-nums; }
/* Duration Days: up to three digits. At 5ch the browser's up and down arrows
   took the room of a digit and 25 read as "2" (Dominik, 22/10/2026). Room for
   999 and no arrows; the arrow keys still count up and down. */
table.trk input.cell.days { width:calc(3ch + 14px); min-width:calc(3ch + 14px); -moz-appearance:textfield; appearance:textfield; }
table.trk input.cell.days::-webkit-inner-spin-button,
table.trk input.cell.days::-webkit-outer-spin-button { -webkit-appearance:none; margin:0; }
table.trk .cell.saved { background:var(--ok-soft); transition:background .2s; }
table.trk .cell.pname { font-weight:600; }
table.trk td.sc { min-width:250px; }
table.trk td.sc .seq { display:inline-block; width:18px; color:var(--ink-3); font-size:11px; text-align:right; margin-right:3px; }
table.trk td.sc .cell { width:calc(100% - 24px); }
table.trk td.tm { min-width:130px; box-shadow:inset 4px 0 0 var(--c, transparent); }
table.trk td.sl { background:var(--surface-2); }
table.trk td.sl .cell { min-width:72px; }
table.trk td.sl input[type="date"] { min-width:118px; }
table.trk td.sl.paid { background:var(--trk-paid); }
table.trk td.miss, table.trk td.sl.miss { background:var(--trk-miss); }
table.trk td.miss .cell { border-color:var(--trk-miss-edge); }
table.trk td.n { text-align:right; font-variant-numeric:tabular-nums; }
table.trk td.cost .cell { color:var(--ink-3); font-style:italic; }
table.trk .costnote { font-size:11px; font-weight:400; margin-left:6px; }
table.trk td.dt { font-variant-numeric:tabular-nums; color:var(--ink-2); min-width:52px; }
table.trk td.dt.pinned { color:var(--ink); font-weight:600; }
table.trk input[type="date"].cell { min-width:118px; }
table.trk .jwtag, table.trk .jwdays { font-size:11px; color:var(--st-plan); font-weight:600; padding:0 4px; }
table.trk tr.cancelled td:not([rowspan]) { opacity:.5; text-decoration:line-through; }

/* A project is a block: a rule above it, its total under its lines. */
table.trk tbody.pblock > tr:first-child > td { border-top:3px solid var(--rule-strong); }
table.trk tr.srow > td { background:var(--surface-2); border-bottom:1px solid var(--rule-strong); padding:4px 6px; }
table.trk tr.srow td.sumlabel { display:table-cell; }
table.trk tr.srow td.sumlabel > span:first-child { font-weight:700; margin-right:8px; }
table.trk tr.srow td.sum { font-weight:700; font-size:13px; }
table.trk tr.srow td.invcell { white-space:nowrap; }
table.trk tr.srow .invsum { margin-right:8px; }
table.trk tr.srow .invsum strong { font-variant-numeric:tabular-nums; }
table.trk tr.srow .chip, table.trk tr.srow .btn { margin-right:6px; }
.chip.missing { background:var(--trk-miss); border-color:var(--trk-miss-edge); color:var(--ink); cursor:pointer; font:inherit; font-size:12px; }
table.trk .linkish, .trkdlg .linkish {
  background:none; border:0; padding:0 2px; font:inherit; font-size:12px; color:var(--accent-ink);
  text-decoration:underline; cursor:pointer;
}
table.trk .missbtn.linkish { color:var(--warn); }
table.trk .ghostbtn { background:transparent; border-color:transparent; color:var(--ink-3); }
table.trk .ghostbtn:hover { border-color:var(--rule-strong); color:var(--ink); }
table.trk tbody.flash td { animation:trkflash 2.4s ease-out; }
@keyframes trkflash { from { background:var(--accent-soft); } }
@media (prefers-reduced-motion: reduce) { table.trk tbody.flash td { animation:none; } }

/* The days. One background stripe per weekend; the red line is today. */
table.trk th.tlth { padding:4px 0 0; max-width:none; vertical-align:bottom; }
table.trk td.tlc { padding:0; }
.tlhead { position:relative; height:34px; text-transform:none; letter-spacing:0; }
.tlhead .tlm { position:absolute; top:0; font-size:11px; font-weight:700; color:var(--ink); padding-left:3px;
  border-left:1px solid var(--rule-strong); line-height:14px; }
.tlhead .tlw { position:absolute; top:17px; font-size:10px; font-weight:500; color:var(--ink-3); padding-left:2px;
  border-left:1px solid var(--rule); white-space:nowrap; line-height:14px; }
.tlhead .tltoday { position:absolute; top:0; bottom:-5px; width:2px; background:var(--crit); }
.tl {
  position:relative; height:26px;
  background-image:repeating-linear-gradient(90deg, transparent 0 calc(var(--px) * 5),
    var(--tl-wk) calc(var(--px) * 5) calc(var(--px) * 7));
}
.tl::after { content:''; position:absolute; top:0; bottom:0; left:var(--todayx); width:2px; background:var(--crit); opacity:.45; }
.tl .bar { position:absolute; top:6px; height:14px; border-radius:3px; background:var(--c, var(--ink-3)); }
.tl .bar.jw { box-shadow:inset 0 0 0 2px rgba(17,19,17,.45); }
.tl .bar.late { box-shadow:0 0 0 2px var(--crit); }
.tl .bar.past { opacity:.6; }
.tl .bar.shut { opacity:.35; }
.tl.sumtl { height:18px; }
.tl .pspan { position:absolute; top:7px; height:4px; border-radius:2px; background:var(--lv); }

/* Pencilled (246): drawn as the Timeline draws it — an outline over a faint
   hatch rather than a solid bar — so a glance down the sheet separates what is
   agreed from what is only expected. */
table.trk tbody.lv-pencilled .tl .bar {
  background:transparent; border:1.5px dashed var(--c, var(--st-pencil));
  background-image:repeating-linear-gradient(135deg, rgba(0,0,0,.06) 0 4px, transparent 4px 8px);
}
table.trk tbody.lv-pencilled .tl .pspan { background:transparent; height:0; border-radius:0; border-top:2px dashed var(--lv); }
table.trk tbody.lv-pencilled .cell.pname { font-style:italic; }
table.trk td.st .pencilacts { display:flex; gap:2px; align-items:baseline; flex-wrap:wrap; }
table.trk td.st .pencilacts .linkish { font-size:11px; }
.stat.pencilled { border-style:dashed; }

/* The boxes: what is missing, everything else about a line, invoiced. */
dialog.trkdlg { width:min(860px, calc(100vw - 32px)); }
dialog.trkdlg .mlist { margin:0 14px 14px; display:flex; flex-direction:column; gap:10px; }
dialog.trkdlg .mrow { display:grid; grid-template-columns:170px 1fr; gap:10px; align-items:start;
  padding-top:10px; border-top:1px solid var(--rule); }
dialog.trkdlg .mrow:first-child { border-top:0; padding-top:0; }
dialog.trkdlg .mrow > label { font-weight:600; font-size:13px; padding-top:5px; }
dialog.trkdlg .inl.wrap { flex-wrap:wrap; align-items:flex-end; }
dialog.trkdlg .inl label { display:flex; flex-direction:column; gap:2px; font-size:12px; color:var(--ink-2); margin:0; }
dialog.trkdlg .cell { font:inherit; font-size:13px; padding:4px 6px; border:1px solid var(--rule-strong);
  border-radius:4px; background:var(--surface); color:var(--ink); }
dialog.trkdlg textarea.cell { width:100%; resize:vertical; }
dialog.trkdlg .cell.saved { background:var(--ok-soft); }
dialog.trkdlg .hint { font-size:12px; margin-top:5px; }
dialog.trkdlg table.insts { border-collapse:collapse; font-size:13px; }
dialog.trkdlg table.insts th { font-size:11px; text-align:left; color:var(--ink-3); padding:2px 4px; }
dialog.trkdlg table.insts td { padding:2px 4px; }
dialog.trkdlg table.insts tr.paid td { background:var(--trk-paid); }
dialog.trkdlg table.insts .cell.n { width:110px; text-align:right; }
dialog.trkdlg .invform { padding:0 14px 14px; }
@media (max-width:700px) {
  dialog.trkdlg .mrow { grid-template-columns:1fr; }
  .sheetbar .zoom { margin-left:0; }
  .sheettitle { margin-left:0; }
}

/* The list (242): the same colours as a spine on each row. */
tr.lvrow > td:first-child { box-shadow:inset 4px 0 0 var(--lv); }
.livekey { display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin:-4px 0 12px; font-size:12px; }

/* ------------------------------------------- the short version of a release
 * One plain line per change, with the screen it is on; a click opens two or
 * three sentences and a Show me. The same list opens once on coming back after
 * a release and heads What's new. */
dialog.digestcard { width:min(660px, calc(100vw - 32px)); }
dialog.digestcard h3 .muted { font-weight:400; }
ul.digest { list-style:none; margin:0 14px 12px; padding:0; border-top:1px solid var(--rule); }
ul.digest li { border-bottom:1px solid var(--rule); }
ul.digest summary {
  display:grid; grid-template-columns:14px 1fr; column-gap:8px; row-gap:1px; align-items:baseline;
  padding:9px 4px; cursor:pointer; list-style:none;
}
ul.digest summary::-webkit-details-marker { display:none; }
ul.digest summary::before {
  content:'›'; grid-row:span 2; color:var(--accent-ink); font-weight:700; font-size:16px; line-height:1;
  display:inline-block; text-align:center; transition:transform .15s ease;
}
ul.digest details[open] > summary::before { transform:rotate(90deg); }
ul.digest summary:hover .dg-t { color:var(--accent-ink); }
ul.digest summary:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; border-radius:3px; }
ul.digest .dg-t { grid-column:2; font-weight:600; font-size:14px; color:var(--ink); text-wrap:balance; }
ul.digest .dg-w { grid-column:2; font-size:12px; color:var(--ink-3); }
ul.digest .dg-d { padding:2px 4px 12px 26px; font-size:13.5px; color:var(--ink-2); }
ul.digest .dg-d p { margin:0 0 7px; max-width:64ch; }
ul.digest .dg-d .btn { margin-top:2px; }
.movepanel .digestwrap {
  margin:0 0 18px; padding:12px 14px 2px; border:1px solid var(--rule-strong); border-radius:6px;
  background:var(--surface-2);
}
.movepanel .digestwrap .dg-head { margin-bottom:8px; font-size:14px; }
.movepanel .digestwrap ul.digest { margin:0 0 10px; }
@media (prefers-reduced-motion: reduce) { ul.digest summary::before { transition:none; } }

/* ------------------------------------------------- overtime and payroll (243)
 * The dates the report covers: a payroll period (dates changeable) or one week,
 * and for EI whether overtime is worked out over the period or week by week. */
.otrange { display:flex; flex-wrap:wrap; gap:10px 14px; align-items:flex-end; margin-top:12px; }
.otrange .f { display:flex; flex-direction:column; gap:3px; font-size:12px; color:var(--ink-2); margin:0; }
.otrange .seg { display:inline-flex; gap:4px; }
details.otrates { padding:10px 14px 14px; border-top:1px solid var(--rule); }
details.otrates summary { cursor:pointer; font-weight:600; font-size:13.5px; }
details.otrates p { font-size:12.5px; margin:8px 0; max-width:80ch; }
table.otratetable { font-size:13px; }
table.otratetable th, table.otratetable td { padding:4px 8px; text-align:left; }

/* ------------------------------------------------------- working (05/10/2026)
 * The button that was pressed shows it is working: it cannot be pressed again
 * at once, and after a fifth of a second it carries a spinner. Anything slow
 * also puts "Working…" in the bottom corner until the last request is back. */
.is-pressed { pointer-events:none; }
.is-busy { cursor:progress; }
.is-busy::after {
  content:''; display:inline-block; width:.85em; height:.85em; margin-left:.45em;
  border:2px solid currentColor; border-right-color:transparent; border-radius:50%;
  vertical-align:-.1em; animation:np-spin .7s linear infinite;
}
.worknote {
  position:fixed; right:16px; bottom:16px; z-index:190;
  display:flex; align-items:center; gap:8px; padding:8px 14px;
  background:var(--surface); color:var(--ink); border:1px solid var(--rule-strong); border-radius:6px;
  box-shadow:0 6px 18px rgb(0 0 0 / 22%); font-size:13px;
  opacity:0; transform:translateY(6px); pointer-events:none;
  transition:opacity .2s ease, transform .2s ease;
}
.worknote.on { opacity:1; transform:none; }
.worknote .spin {
  width:14px; height:14px; border:2px solid var(--accent); border-right-color:transparent;
  border-radius:50%; animation:np-spin .7s linear infinite;
}
@keyframes np-spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .is-busy::after, .worknote .spin { animation-duration:2s; }
  .worknote { transition:none; }
}

/* ---------------------------------------- extra hours, EPM's rate switch (245) */
.otextra { margin:0 0 12px; padding:10px 12px; border:1px solid var(--rule); border-radius:6px;
  background:var(--surface-2); font-size:13px; }
.otextra ul { margin:6px 0 8px; padding-left:18px; }
.otextra li { margin:3px 0; }
.otextraform { display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-top:6px; }
.otratetable .seg { display:inline-flex; gap:4px; }
.otratetable .gbp { display:inline-flex; align-items:center; gap:3px; font-size:13px; margin:0; }
.otratetable .gbp input { width:6em; }

/* 248: Beta notes replies — what was done, said back on the note itself. */
.betareply { font-size:12.5px; margin-top:5px; padding:5px 8px; border-left:3px solid var(--ok);
  background:var(--ok-soft); border-radius:0 4px 4px 0; white-space:pre-wrap; color:var(--ink); }
.betareplyform { margin-top:6px; }
#betalist td:nth-child(2) { min-width:320px; }
.movepanel .betapanel { width:min(980px, calc(100vw - 32px)); max-width:none; }
.betareplyform textarea { width:100%; font:inherit; font-size:13px; padding:6px 8px; border:1px solid var(--rule-strong);
  border-radius:4px; background:var(--surface); color:var(--ink); resize:vertical; }

/* 248: a JobWatch job on Resourcing — what it is, its category, then where. */
.jwwhat { font-weight:600; }
.jwcat { display:block; font-size:10.5px; opacity:.75; font-weight:400; }
.jwplace { display:block; font-size:11px; font-weight:400; }

/* 248: on the Timeline, work still going after its dates ran out — drawn on to
   today with a hatched red end, so running late is the thing you see. */
.gbar.running { box-shadow:0 0 0 2px var(--crit); }
.gbar.running::after { content:''; position:absolute; right:0; top:0; bottom:0; width:10px;
  background:repeating-linear-gradient(135deg, var(--crit) 0 3px, transparent 3px 6px); }

/* 251: Tasks — what comes with a library item, and what is still to order. */
.kitparts { font-size:11.5px; color:var(--ink-3); margin-top:2px; }
.kitparts .linkish, .orderbar .linkish, .partsdlg .linkish { background:none; border:0; padding:0; font:inherit;
  color:var(--accent-ink); text-decoration:underline; cursor:pointer; }
.orderbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:0 0 8px; padding:7px 10px;
  border-radius:5px; background:var(--warn-soft); border:1px solid var(--warn); font-size:13px; }
.orderbar.done { background:var(--ok-soft); border-color:var(--ok); color:var(--ink-2); }
.orderbar > span { margin-right:auto; }
table.inner tr.toorder > td { background:var(--warn-soft); }
table.inner tr.toorder > td:first-child { box-shadow:inset 3px 0 0 var(--warn); }
table.inner tr.partrow > td:nth-child(2) { padding-left:22px; font-size:13px; }
.partof { color:var(--ink-3); margin-right:4px; }
.chip.ordchip { cursor:pointer; font:inherit; font-size:11.5px; border-style:dashed; background:transparent; }
.chip.ordchip.on { border-style:solid; background:var(--ok-soft); border-color:var(--ok); color:var(--ok); }
dialog.partsdlg { width:min(560px, calc(100vw - 32px)); }
dialog.partsdlg .partstable { width:calc(100% - 28px); margin:0 14px; }
dialog.partsdlg .partstable .cell { width:100%; font:inherit; font-size:13px; padding:4px 6px;
  border:1px solid var(--rule-strong); border-radius:4px; background:var(--surface); color:var(--ink); }
dialog.partsdlg p { margin-left:14px; margin-right:14px; }

/* 252: a Sheet line whose JobWatch people are not its crew. */
table.trk td.tm.warned { position:relative; }
table.trk td.tm .crewwarn { position:absolute; right:3px; top:50%; transform:translateY(-50%);
  color:var(--warn); font-size:13px; cursor:help; }

/* ======================================================== the 2026 planner (252)
   The sheet's shape: status, crew, a row per person with their van, the plant,
   then every day as the work and the stay beside it. Colours are the sheet's
   own families, mixed onto the theme, and quieter in the dark. */
:root {
  --pl-head:color-mix(in srgb, #FFFF00 40%, var(--surface-2));
  --pl-crew:color-mix(in srgb, #FDE9D9 60%, var(--surface));
  --pl-who:color-mix(in srgb, #FDE9D9 35%, var(--surface));
  --pl-stay:color-mix(in srgb, #FDE9D9 30%, var(--surface));
  --pl-off:color-mix(in srgb, #FFC7CE 55%, var(--surface)); --pl-off-ink:#9C0006;
  --pl-train:color-mix(in srgb, #FF0000 30%, var(--surface)); --pl-stay-ink:#0B6FA4;
}
:root[data-theme="dark"] {
  --pl-head:color-mix(in srgb, #FFFF00 16%, var(--surface-2));
  --pl-crew:color-mix(in srgb, #FDE9D9 14%, var(--surface));
  --pl-who:color-mix(in srgb, #FDE9D9 8%, var(--surface));
  --pl-stay:color-mix(in srgb, #FDE9D9 6%, var(--surface));
  --pl-off:color-mix(in srgb, #FFC7CE 16%, var(--surface)); --pl-off-ink:#FF9AA6;
  --pl-train:color-mix(in srgb, #FF0000 28%, var(--surface)); --pl-stay-ink:#6CC4F0;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --pl-head:color-mix(in srgb, #FFFF00 16%, var(--surface-2));
    --pl-crew:color-mix(in srgb, #FDE9D9 14%, var(--surface));
    --pl-who:color-mix(in srgb, #FDE9D9 8%, var(--surface));
    --pl-stay:color-mix(in srgb, #FDE9D9 6%, var(--surface));
    --pl-off:color-mix(in srgb, #FFC7CE 16%, var(--surface)); --pl-off-ink:#FF9AA6;
    --pl-train:color-mix(in srgb, #FF0000 28%, var(--surface)); --pl-stay-ink:#6CC4F0;
  }
}
.weekwrap.withplan { align-items:flex-start; }
.planaside { flex:0 0 190px; align-self:flex-start; border:1px solid var(--rule); border-radius:8px;
  background:var(--surface); font-size:12px; overflow:hidden; }
.planaside .pa-head { padding:6px 10px; font-weight:700; font-size:11.5px; letter-spacing:.03em; text-transform:uppercase; }
.planaside .pa-off { background:var(--pl-off); color:var(--pl-off-ink); }
.planaside .pa-train { background:var(--pl-train); color:var(--ink); margin-top:6px; }
.planaside .pa-row { display:flex; flex-direction:column; padding:4px 10px; border-top:1px solid var(--rule); }
.planaside .pa-row span { color:var(--ink-2); }
.planaside .pa-course strong::after { content:' · course'; font-weight:400; color:var(--ink-3); }
.planaside .pa-none, .planaside .pa-src { margin:6px 10px; font-size:11.5px; }
.planaside .pa-train-body { padding:8px 10px; white-space:pre-wrap; cursor:text; min-height:40px; }
.planaside .pa-train-body.blank { color:var(--ink-3); }

/* Frozen like the sheet: the wrapper scrolls both ways, the day headings stay on
   top and the status, crew and names stay on the left. The crew board tints
   every cell see-through with the crew's colour; the frozen ones must be solid,
   hence the longer selectors. */
.weekwrap.withplan > .tablewrap { max-height:78vh; overflow:auto; }
table.board.planner { border-collapse:separate; border-spacing:0; font-size:11.5px; }
table.board.planner thead { position:sticky; top:0; z-index:4; }
table.board.planner th { background:var(--pl-head); color:var(--ink);
  font-size:11px; text-transform:uppercase; letter-spacing:.02em; white-space:nowrap; }
table.board.planner tr.pl-sub th { font-weight:500; text-transform:none; background:var(--surface-2); color:var(--ink-3); }
table.board.planner td { vertical-align:top; border-bottom:1px solid var(--rule); border-right:1px solid var(--rule); padding:3px 5px; }
table.board.planner tr.pl-first > td { border-top:2px solid var(--rule-strong); }
table.board.planner .pl-status, table.board.planner .pl-crew, table.board.planner .pl-who { position:sticky; z-index:2; }
table.board.planner th.pl-status, table.board.planner th.pl-crew, table.board.planner th.pl-who { z-index:5; }
table.board.planner .pl-status { left:0; min-width:76px; max-width:76px; text-align:center; }
table.board.planner .pl-crew { left:76px; min-width:112px; max-width:112px; font-weight:700; text-transform:uppercase; }
table.board.planner .pl-who { left:188px; min-width:140px; max-width:140px; box-shadow:1px 0 0 var(--rule-strong); }
table.board.planner tbody tr[data-team] td.pl-status { background:var(--surface); }
table.board.planner tbody tr[data-team] td.pl-crew { background:var(--pl-crew); box-shadow:inset 4px 0 0 var(--team, transparent); }
table.board.planner tbody tr[data-team] td.pl-who { background:var(--pl-who); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
table.board.planner tbody tr[data-team] td.pl-stay { background:var(--pl-stay); }
table.board.planner .pl-who .crewname.isoff { text-decoration:line-through; color:var(--ink-3); }
table.board.planner .pl-van { width:70px; font-family:var(--mono); font-size:11px; white-space:nowrap; }
table.board.planner .pl-plant { width:110px; }
table.board.planner .pl-job { min-width:150px; max-width:190px; }
table.board.planner .pl-stay { min-width:92px; max-width:120px; }
table.board.planner td.weekend { min-width:70px; }
table.board.planner .pl-stay .daynote { font-size:11px; color:var(--pl-stay-ink); font-weight:600; white-space:pre-wrap; }
table.board.planner .pl-stay .daynote.blank { color:var(--ink-3); font-weight:400; }
table.board.planner .booking[class*="wk-"] { background:color-mix(in srgb, var(--wk) 22%, var(--surface));
  border-left:4px solid var(--wk); }
.pl-plantbox { white-space:pre-wrap; cursor:text; min-height:18px; font-size:11.5px; }
.pl-plantbox.blank { color:var(--ink-3); }
.pl-flag { display:inline-block; font-size:10px; font-weight:800; letter-spacing:.03em; padding:2px 5px; border-radius:3px; }
.pl-flag.pl-added { background:color-mix(in srgb, #92D050 55%, var(--surface)); color:var(--ink); }
.pl-flag.pl-sent { background:#00B050; color:#fff; }
.pl-flag.pl-missing { background:var(--warn-soft); color:var(--warn); border:1px solid var(--warn); }

.pl-entry { --wk:#A6A6A6; display:flex; flex-direction:column; gap:1px; margin:0 0 4px; padding:3px 5px 3px 7px;
  border-radius:3px; background:color-mix(in srgb, var(--wk) 28%, var(--surface)); border-left:4px solid var(--wk);
  font-size:11.5px; line-height:1.25; }
.pl-entry .pe-site { font-weight:700; text-transform:uppercase; }
.pl-entry .pe-pc { font-family:var(--mono); font-size:10.5px; }
.pl-entry .pe-what { color:var(--ink-2); }
.pl-entry .pe-who { font-size:10.5px; color:var(--ink-3); }
.pl-entry .pe-warn { font-size:10.5px; color:var(--warn); font-weight:600; }
.pl-entry .pe-btns { display:flex; gap:3px; margin-top:2px; }
.pl-entry.fromproject { border-left-style:dashed; background:transparent;
  outline:1px dashed color-mix(in srgb, var(--wk) 70%, transparent); outline-offset:-1px; }
.pl-entry.warned { box-shadow:0 0 0 1px var(--warn); }
.pl-entry.st-problem { box-shadow:0 0 0 2px var(--crit); }
.wk-lining { --wk:#FFC000; } .wk-vr { --wk:#00B0F0; } .wk-testing { --wk:#7030A0; }
.wk-cleaning { --wk:#AFF540; } .wk-interceptor { --wk:#8497B0; } .wk-disposal { --wk:#A08C64; }
.wk-training { --wk:#FFE79B; } .wk-transport { --wk:#BFBFBF; } .wk-reactive { --wk:#00B050; } .wk-other { --wk:#A6A6A6; }
.pl-key { display:flex; flex-wrap:wrap; gap:6px; margin:8px 0 2px; }
.pl-key .pl-entry { flex-direction:row; margin:0; font-size:11px; padding:2px 7px; }
.crewpick select { font-size:11px; max-width:150px; }
.staynights { margin-top:3px; font-size:11px; width:100%; }

/* ============================================ the EI schedule, stays, the Archive (253, 254)
   EI's sheet colours by customer at full strength on white; in the dark the
   same colours are mixed into the surface so text stays readable. The driver
   is green as on the 2026 sheet. */
:root { --cu-mix:100%; --cu-ink:#1F2933; --pl-driver:color-mix(in srgb, #92D050 70%, var(--surface)); }
:root[data-theme="dark"] { --cu-mix:38%; --cu-ink:var(--ink); --pl-driver:color-mix(in srgb, #92D050 30%, var(--surface)); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --cu-mix:38%; --cu-ink:var(--ink); --pl-driver:color-mix(in srgb, #92D050 30%, var(--surface)); }
}
table.board.planner .crewname.driver { background:var(--pl-driver); padding:0 4px; border-radius:3px; }

.staycell { min-height:16px; }
.stayown { font-size:11px; color:var(--pl-stay-ink); cursor:pointer; white-space:normal; }
.stayown:hover, .staycell:hover, .ei-stay:hover { text-decoration:underline dotted; }
.stayedit { display:flex; flex-direction:column; gap:3px; }
.stayedit select { font-size:11px; width:100%; }

.weekwrap.schedule-wrap { align-items:flex-start; }
.weekwrap > .tablewrap:has(> table.eisched) { max-height:78vh; overflow:auto; }
table.board.eisched { border-collapse:separate; border-spacing:0; font-size:11.5px; }
table.board.eisched thead { position:sticky; top:0; z-index:4; }
table.board.eisched th { background:var(--surface-2); color:var(--ink); font-size:11px; white-space:nowrap; text-align:left; }
table.board.eisched th .muted { font-weight:400; }
table.board.eisched td { vertical-align:top; border-bottom:1px solid var(--rule); border-right:1px solid var(--rule); padding:3px 5px; }
table.board.eisched tr.ei-first > td { border-top:2px solid var(--rule-strong); }
table.board.eisched .ei-team, table.board.eisched .ei-name { position:sticky; z-index:2; }
table.board.eisched th.ei-team, table.board.eisched th.ei-name { z-index:5; }
table.board.eisched .ei-team { left:0; min-width:92px; max-width:92px; font-weight:700; }
table.board.eisched .ei-name { left:92px; min-width:150px; max-width:150px; box-shadow:1px 0 0 var(--rule-strong); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
table.board.eisched tbody tr[data-team] td.ei-team,
table.board.eisched tbody tr[data-team] td.ei-name { background:var(--surface); }
table.board.eisched .ei-skill { min-width:110px; max-width:150px; color:var(--ink-2); }
table.board.eisched .ei-tickets { min-width:110px; max-width:160px; font-size:10.5px; color:var(--ink-2); overflow-wrap:anywhere; }
table.board.eisched .ei-van { min-width:70px; font-family:var(--mono); font-size:11px; white-space:nowrap; }
table.board.eisched .ei-cell { min-width:120px; max-width:160px; }
table.board.eisched td.weekend { min-width:60px; }
.ei-entry { --cu:#E8E8E8; display:block; margin:0 0 3px; padding:2px 5px; border-radius:3px; font-size:11.5px; line-height:1.25;
  background:color-mix(in srgb, var(--cu) var(--cu-mix), var(--surface)); color:var(--cu-ink); }
.ei-entry.fromproject { outline:1px dashed color-mix(in srgb, var(--ink) 35%, transparent); outline-offset:-1px; }
.ei-entry.fromjob { box-shadow:inset 3px 0 0 color-mix(in srgb, var(--ink) 30%, transparent); }
.ei-off { --cu:#3A7D22; color:#FFFFFF; background:#3A7D22; }
.ei-harvest { --cu:#FFD96D; } .ei-eg { --cu:#CCFF99; } .ei-gilmore { --cu:#FF9900; } .ei-bp { --cu:#FFB9B9; }
.ei-shell { --cu:#FFFF00; } .ei-essar { --cu:#FBE3D6; } .ei-moto { --cu:#D0DFE6; } .ei-mfg { --cu:#ECD5E9; }
.ei-asda { --cu:#DCEDD5; } .ei-hws { --cu:#8694A0; } .ei-pricewatch { --cu:#CFECF7; } .ei-other { --cu:#E8E8E8; }
.ei-stay { font-size:11px; color:var(--pl-stay-ink); font-weight:600; cursor:pointer; min-height:14px; white-space:pre-wrap; }
.ei-stay.fromcrew { font-weight:400; opacity:.75; }
.ei-stay.blank { color:var(--ink-3); font-weight:400; }
.ei-key { display:flex; flex-wrap:wrap; gap:6px; margin:8px 0 2px; }
.ei-key .ei-entry { display:inline-block; margin:0; font-size:11px; }

/* 304. Travel days beside the day's work: decided, asked, or offered. */
.wk-travel { display:flex; align-items:center; gap:3px; flex-wrap:wrap; margin:2px 0 3px; font-size:11px; line-height:1.25; }
.wk-travel.on { padding:2px 2px 2px 6px; border-radius:3px; font-weight:700; letter-spacing:.03em;
  background:var(--ok-soft); color:var(--ok); border:1px solid color-mix(in srgb, var(--ok) 45%, transparent); }
.wk-travel.no { color:var(--ink-3); font-style:italic; }
.wk-travel.ask { padding:2px 2px 2px 6px; border-radius:3px; font-weight:600;
  background:var(--warn-soft); color:var(--warn); border:1px dashed color-mix(in srgb, var(--warn) 55%, transparent); }
.wk-travel .btn.tiny { font-size:10.5px; padding:0 5px; line-height:1.5; }
.wk-travel.on .btn.quiet, .wk-travel.no .btn.quiet { margin-left:auto; }
.wk-travel.offer { opacity:0; transition:opacity .12s; }
td:hover > .wk-travel.offer, .wk-travel.offer:focus-within { opacity:1; }
@media (hover:none) { .wk-travel.offer { opacity:1; } }

/* The Archive: the same drawings, nothing on them that changes anything. */
.weekwrap.readonly .daynote, .weekwrap.readonly .ei-stay, .weekwrap.readonly .stayown,
.weekwrap.readonly .pl-plantbox, .weekwrap.readonly .pa-train-body { cursor:default; text-decoration:none; }
.archost { min-height:120px; }
.archnote { margin:0 0 10px; font-size:13px; }

/* ======================================== the Sheet: why, what it was, a note (256) */
table.trk .was { display:block; font-size:10.5px; line-height:1.1; color:var(--ink-3); text-align:right; padding:0 4px 1px; }
table.trk td.changed input.cell { text-decoration:underline dotted var(--ink-3); }
table.trk td.sl.paid input:disabled { pointer-events:none; }
table.trk td.sl.due { box-shadow:inset 0 -2px 0 var(--warn); }
table.trk td.sl.due input.cell[type="date"] { color:var(--warn); font-weight:600; }
table.trk tr.srow .readywhy { margin-left:10px; font-size:11.5px; color:var(--warn); white-space:nowrap; cursor:help; }
table.trk tr.srow .readywhy.ready { color:var(--ok); font-weight:600; }
table.trk td.sc { position:relative; }
table.trk td.sc button.seq { padding:0; border:0; background:none; font:inherit; font-size:11px; color:var(--ink-3); cursor:pointer; }
table.trk td.sc button.seq:hover, table.trk td.sc button.seq:focus-visible { color:var(--ink); text-decoration:underline; }
table.trk td.sc .addnote { position:absolute; right:4px; top:4px; visibility:hidden; border:0; border-radius:3px;
  background:var(--surface-2); color:var(--ink-3); font-size:10.5px; padding:1px 5px; cursor:pointer; }
table.trk tr.arow:hover td.sc .addnote, table.trk td.sc .addnote:focus-visible { visibility:visible; }
table.trk td.sc .lnote { margin:2px 0 1px 21px; padding:2px 6px; max-width:340px; border-radius:3px; cursor:pointer;
  font-size:11.5px; line-height:1.3; white-space:normal; color:var(--warn); background:var(--warn-soft); }
table.trk td.sc .noteedit { margin:4px 0 2px 21px; display:flex; flex-direction:column; gap:4px; max-width:360px; }
table.trk td.sc .noteedit textarea { width:100%; font:inherit; font-size:12px; }

/* ======================================== the Sheet: a cancelled line, by customer (257) */
table.trk tr.arow.cancelled td.sc input.cell { text-decoration:line-through; color:var(--ink-3); }
table.trk tr.arow.cancelled td { background:color-mix(in srgb, var(--ink) 4%, transparent); }
table.trk .offtag { display:inline-block; font-size:10.5px; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
  color:var(--ink-3); padding:1px 5px; border:1px solid var(--rule-strong); border-radius:3px; }
table.trk tr.arow.cancelled td .was { color:var(--ink-2); }
table.trk td.sc .noteedit .offtick { font-size:12px; display:flex; gap:6px; align-items:center; }
/* By customer: one customer's sites read as one group — a lighter rule between
   them and the name said quietly after the first. */
table.trk tbody.pblock.samecust > tr:first-child > td { border-top-width:1px; }
table.trk tbody.pblock.samecust td.cu strong { color:var(--ink-3); font-weight:500; }
/* ------------------------------------------------- billed phase by phase (265)
 * Each invoice phase is a row under its line: green invoiced (and the whole
 * line once all of it is), orange due to bill this week or next, red a date
 * gone by. Under each group job, the customer's PO — red, orange or green. A
 * project with more than one group job draws each one's lines in a frame. */
:root {
  --trk-soon:#FFE2B8; --trk-soon-edge:#D9861A;
  --trk-late:#F6D6D0; --trk-late-edge:#A2352A;
  --trk-frame:#4F7BA8;
}
:root[data-theme="dark"] {
  --trk-soon:#3A2810; --trk-soon-edge:#E0A93C;
  --trk-late:#3A1C18; --trk-late-edge:#E8776B;
  --trk-frame:#7FB6E6;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --trk-soon:#3A2810; --trk-soon-edge:#E0A93C;
    --trk-late:#3A1C18; --trk-late-edge:#E8776B;
    --trk-frame:#7FB6E6;
  }
}
table.trk td.ph { background:var(--surface-2); }
table.trk td.ph .cell { min-width:72px; }
table.trk td.ph input[type="date"] { min-width:118px; }
table.trk td.ph.paid, table.trk td.lpaid { background:var(--trk-paid); }
table.trk td.ph.paid input:disabled { pointer-events:none; }
table.trk td.ph.soon { background:var(--trk-soon); }
table.trk td.ph.soon input.cell[type="date"] { font-weight:600; }
table.trk td.ph.late { background:var(--trk-late); }
table.trk td.ph.late input.cell[type="date"] { color:var(--crit); font-weight:600; }
table.trk td.ph.miss { background:var(--trk-miss); }
table.trk td.phinv { min-width:150px; white-space:nowrap; }
table.trk td.phinv .phdone { font-weight:600; color:var(--accent-ink); font-variant-numeric:tabular-nums; }
table.trk td.phinv .jwok, table.trk td.phinv .jwbad, table.trk td.phinv .jwwait { font-size:10.5px; margin-left:6px; cursor:help; }
table.trk td.phinv .jwok { color:var(--ok); }
table.trk td.phinv .jwbad { color:var(--crit); font-weight:600; }
table.trk td.phinv .jwwait { color:var(--ink-3); }
table.trk td.phinv .warnbtn { border:0; background:none; color:var(--warn); cursor:pointer; font-size:14px; line-height:1;
  padding:0 4px; margin-left:2px; }
table.trk td.phinv .warnbtn:hover, table.trk td.phinv .warnbtn:focus-visible { color:var(--crit); }
table.trk td.phinv .addph { font-size:11px; margin-left:8px; color:var(--ink-3); visibility:hidden; }
/* Shown on the project under the pointer, so twenty lines do not each carry one. */
table.trk tbody.pblock:hover td.phinv .addph, table.trk td.phinv .addph:focus-visible { visibility:visible; }
table.trk td.phinv .addph:hover { color:var(--ink); }
/* Removing a phase added by mistake: there on the project under the pointer, like + phase. */
table.trk td.phinv .delph { border:0; background:none; color:var(--ink-3); cursor:pointer; font-size:14px; line-height:1;
  padding:0 4px; margin-left:4px; visibility:hidden; }
table.trk tbody.pblock:hover td.phinv .delph, table.trk td.phinv .delph:focus-visible { visibility:visible; }
table.trk td.phinv .delph:hover, table.trk td.phinv .delph:focus-visible { color:var(--crit); }
table.trk tr.phrow.cancelled td { opacity:.5; }
/* The frame round one group job's lines: from Scope to the Invoiced column. */
table.trk td.gxt { border-top:2px solid var(--trk-frame); }
table.trk td.gxb { border-bottom:2px solid var(--trk-frame); }
table.trk td.gxl { border-left:2px solid var(--trk-frame); }
table.trk td.gxr { border-right:2px solid var(--trk-frame); }
table.trk td.sc .gtag { display:flex; gap:6px; align-items:baseline; margin:0 0 2px 21px; font-size:11px; color:var(--trk-frame); }
table.trk td.sc .gtag span { color:var(--ink-3); overflow:hidden; text-overflow:ellipsis; max-width:150px; }
/* The PO box under each group job. */
table.trk td.gr { min-width:150px; vertical-align:top; padding-top:6px; }
table.trk td.gr .grone + .grone { margin-top:8px; padding-top:6px; border-top:1px dashed var(--rule-strong); }
table.trk td.gr .addgr { display:block; margin-top:6px; font-size:11px; }
table.trk .pobox { margin-top:4px; padding:3px 5px 4px; border:1px solid var(--rule-strong); border-radius:3px;
  display:flex; flex-direction:column; gap:2px; font-size:11.5px; max-width:160px; background:var(--surface); }
table.trk .pobox .pohead { display:flex; gap:6px; align-items:baseline; }
table.trk .pobox .postate { font-weight:700; font-size:11px; letter-spacing:.02em; }
table.trk .pobox .cell.po { width:100%; font-variant-numeric:tabular-nums; }
table.trk .pobox .poacts { display:flex; gap:8px; flex-wrap:wrap; }
table.trk .pobox .poacts .linkish, table.trk .pobox .pohead .linkish { font-size:11px; }
table.trk .pobox .podoc { font-size:11px; }
table.trk .pobox .podiff { font-size:10.5px; color:var(--warn); cursor:help; }
table.trk .pobox .poasked { font-size:10.5px; color:var(--ink-3); cursor:help; }
table.trk .pobox.po-red { background:var(--trk-late); border-color:var(--trk-late-edge); }
table.trk .pobox.po-red .postate { color:var(--crit); }
table.trk .pobox.po-orange { background:var(--trk-soon); border-color:var(--trk-soon-edge); }
table.trk .pobox.po-orange .postate { color:var(--warn); }
table.trk .pobox.po-green { background:var(--trk-paid); border-color:var(--ok); }
table.trk .pobox.po-green .postate { color:var(--ok); }
table.trk .pobox.po-ask { border-style:dashed; background:var(--surface-2); }
table.trk .pobox.po-ask .postate { color:var(--ink-2); }
table.trk .pobox.po-not_needed { border-color:transparent; background:transparent; padding-left:0; }
table.trk .pobox.po-not_needed .postate { color:var(--ink-3); font-weight:400; }
/* To invoice this week, and next week's list. */
.sheetbar .duebar { display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
.sheetbar .chip.due { cursor:pointer; font:inherit; font-size:12px; background:var(--trk-soon); border-color:var(--trk-soon-edge); color:var(--ink); }
.sheetbar .chip.due.ready { background:var(--trk-paid); border-color:var(--ok); }
.sheetbar .chip.due.gap { background:var(--trk-miss); border-color:var(--trk-miss-edge); }
.sheetbar .chip.due.on { box-shadow:0 0 0 2px var(--trk-soon-edge); font-weight:700; }
.sheetbar .chip.due:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
dialog.trkdlg.weekbox { width:min(1080px, calc(100vw - 32px)); }
dialog.trkdlg .wkwrap { overflow-x:auto; margin:0 14px 12px; }
dialog.trkdlg table.wk { width:100%; font-size:12.5px; }
dialog.trkdlg table.wk td { border-top:1px solid var(--rule); vertical-align:top; }
dialog.trkdlg table.wk td.n, dialog.trkdlg table.wk th.n { text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; }
dialog.trkdlg table.wk tr.gap td { background:color-mix(in srgb, var(--trk-miss) 45%, transparent); }
dialog.trkdlg table.wk .gapword { color:var(--warn); }
dialog.trkdlg table.wk .okword { color:var(--ok); font-weight:600; }
dialog.trkdlg .wkh { margin:4px 14px 6px; font-size:13px; }
dialog.trkdlg .wkfoot { display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap; }
dialog.trkdlg .wknote { margin:0 14px 12px; font-size:12px; }
dialog.trkdlg .gaplist { margin:0 14px 10px 32px; padding:0; font-size:13px; }
dialog.trkdlg .gaplist li { margin:2px 0; }

/* ------------------------------------------------- supplier spend (266)
 * Reporting → Supplier spend: the whole amount, then the divisions in their
 * chart colours (.d-EE lime, .d-EI blue, .d-EPM orange — the invoice schedule's),
 * then the years. */
.ss-ask .ss-find { margin-bottom:6px; }
.ss-ask .ss-filters { align-items:flex-end; gap:10px 14px; margin:10px 0 0; }
.ss-ask .ss-filters .f { margin:0; }
.ss-ask .ss-filters input[type="number"] { width:11ch; }
.ss-ask .ss-go { display:flex; gap:8px; margin-left:auto; }
.ss-divs { display:flex; gap:12px; padding-top:4px; }
.ss-key { display:inline-block; width:10px; height:10px; border-radius:2px; margin-right:5px; vertical-align:baseline; }
.ss-matches { display:flex; flex-direction:column; gap:4px; padding:6px 0 4px; }
.ss-match { display:flex; align-items:baseline; gap:8px; font-size:13.5px; cursor:pointer; flex-wrap:wrap; }
.ss-match .muted { font-size:12.5px; font-variant-numeric:tabular-nums; }
.ss-none { margin:4px 0 0; font-size:13px; }
.ss-chip { display:inline-block; padding:0 6px; border-radius:3px; font-size:11px; font-weight:700; line-height:1.6;
  color:#111311; letter-spacing:.02em; }
.ss-chip.d-EI, .ss-chip.d-EPM { color:#fff; }

.ss-hero { margin:18px 2px 16px; display:flex; flex-direction:column; gap:4px; }
.ss-who { font-size:13px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--ink-2); }
.ss-total { font-size:40px; font-weight:700; line-height:1.05; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
.ss-total span { font-size:14px; font-weight:400; color:var(--ink-2); margin-left:10px; letter-spacing:0; }
.ss-sub { font-size:13.5px; color:var(--ink-2); font-variant-numeric:tabular-nums; }

.ss-split { display:flex; height:30px; border-radius:4px; overflow:hidden; gap:2px; }
.ss-seg { display:flex; align-items:center; padding:0 10px; min-width:3px; font-size:12px; font-weight:700;
  color:#111311; white-space:nowrap; overflow:hidden; }
.ss-seg.d-EI, .ss-seg.d-EPM { color:#fff; }
.ss-divrows { display:flex; flex-direction:column; margin-top:12px; }
.ss-divrow { display:grid; grid-template-columns:minmax(150px, 1.2fr) minmax(110px, auto) 60px 1.6fr; gap:12px;
  align-items:baseline; padding:7px 0; border-top:1px solid var(--rule); font-variant-numeric:tabular-nums; }
.ss-divrow:first-child { border-top:0; }
.ss-divrow.none { opacity:.55; }
.ss-dname { font-weight:600; }
.ss-dval { text-align:right; font-weight:700; }
.ss-dshare { text-align:right; color:var(--ink-2); }
.ss-dmeta { font-size:12.5px; }

.ss-chart .sval { margin:0 0 3px; }
.ss-chart.dense .sweek { min-width:26px; }
.ss-chart.dense .sval { font-size:9.5px; }

.ss-two { display:grid; grid-template-columns:repeat(auto-fit, minmax(340px, 1fr)); gap:0 16px; }
.ss-what { font-size:12px; max-width:420px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ss-more { margin:6px 14px 12px; font-size:12.5px; }
.ss-lines { margin:0 0 10px; font-size:13.5px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.ss-orders { max-height:520px; overflow:auto; }
tr.ss-cancelled td { text-decoration:line-through; color:var(--ink-3); }
.ss-foot { font-size:12.5px; margin:4px 2px 24px; max-width:90ch; }
@media (max-width:640px) {
  .ss-divrow { grid-template-columns:1fr auto; }
  .ss-dmeta { grid-column:1 / -1; }
  .ss-total { font-size:32px; }
  .ss-ask .ss-go { margin-left:0; }
}

/* ------------------------------------------------- seasonal looks (14/10/2026)
 * Dominik: "add switchable autumn and halloween themes to npt just for fun".
 * Then (15/10/2026): "improve the themes to look graphically more pleasing and
 * to show less of the theme objects so they don't get all the attention."
 *
 * A season is a second attribute, data-season, laid OVER dark or light rather
 * than replacing them: every screen's own dark-theme adjustments (the board's
 * crew colours, the Sheet's frozen cells) key on data-theme and keep working.
 * A season changes the house colours only — the grounds, the ink, the rules and
 * the lime that marks Eurotank. What a colour MEANS is left alone: red, amber
 * and green for problems, warnings and passes, the tracker's yellow and green,
 * the divisions' colours on the charts.
 *
 * QUIET BY DESIGN. The grounds are near the house neutrals with a little warmth,
 * and the accent is a soft copper or pumpkin rather than a saturated orange, so
 * the work stays the loudest thing on the screen. What dresses up: the logo
 * (still, not moving), the tab icon, and the signs that only show while
 * something is loading. Leaves or bats cross once, the first time NPT opens
 * that day. Nothing moves while somebody works.
 *
 * Black on the accent, as on the lime: every accent below is light enough to
 * carry dark text. Set from Settings → Seasonal look, kept per machine. */

/* Autumn, on light: warm paper and soft copper. */
:root[data-season="autumn"] {
  --ground:#F7F4EF; --surface:#FFFFFF; --surface-2:#F3EEE6; --surface-3:#E9E1D5;
  --ink:#2B211A; --ink-2:#5C4E43; --ink-3:#877869;
  --rule:#E7DFD3; --rule-strong:#CFC2B0;
  --brand:#CF8A52; --accent:#CF8A52;
  --accent-ink:#8E4F22; --accent-soft:#F6E8DA; --on-accent:#1E1208;
  --accent-calm:#A07A55;
}
/* Autumn, on dark: wood smoke, with a low ember for the accent. */
:root[data-season="autumn"][data-theme="dark"] {
  --ground:#16120F; --surface:#1E1915; --surface-2:#27211C; --surface-3:#372E27;
  --ink:#F2EBE3; --ink-2:#C9BBAD; --ink-3:#998A7C;
  --rule:#2F2721; --rule-strong:#4A3E34;
  --brand:#D99A66; --accent:#D99A66;
  --accent-ink:#E9B386; --accent-soft:#36261A; --on-accent:#1A0F06;
  --accent-calm:#B88F68;
}
/* Halloween, on light: bone, a breath of violet, and pumpkin. */
:root[data-season="halloween"] {
  --ground:#F6F4F1; --surface:#FFFFFF; --surface-2:#F2EFF4; --surface-3:#E6E1EC;
  --ink:#1F1A26; --ink-2:#4B4456; --ink-3:#766E83;
  --rule:#E4DFE9; --rule-strong:#C9C0D3;
  --brand:#E88A3C; --accent:#E88A3C;
  --accent-ink:#A24B12; --accent-soft:#FBE8D8; --on-accent:#1A0A00;
  --accent-calm:#7D62A8;
}
/* Halloween, on dark: night violet and a lit pumpkin. */
:root[data-season="halloween"][data-theme="dark"] {
  --ground:#0F0D13; --surface:#17141C; --surface-2:#201C27; --surface-3:#2E2838;
  --ink:#F1EDF6; --ink-2:#BDB4C9; --ink-3:#8C8399;
  --rule:#29232F; --rule-strong:#40384B;
  --brand:#F0954A; --accent:#F0954A;
  --accent-ink:#F6AE70; --accent-soft:#3A220F; --on-accent:#1A0A00;
  --accent-calm:#9E86C8;
}

/* The top bar's rule takes a little of the season's colour — a hint, not a stripe. */
:root[data-season] .topbar { border-bottom-color:color-mix(in srgb, var(--brand) 40%, var(--rule)); }

/* Leaves falling, or bats crossing — a few, once a day (seasonFx in app.js),
   then gone. Not drawn at all where motion is turned down. */
.season-fx { position:fixed; inset:0; pointer-events:none; z-index:150; overflow:hidden; }
.season-fx i { position:absolute; display:block; width:var(--s); height:var(--s); will-change:transform; }
.season-fx i svg { display:block; width:100%; height:100%; }
.season-fx.leaves i { top:-30px; opacity:0; }
.season-fx.bats i { top:0; left:0; opacity:0; }
.season-fx.bats i svg { animation:fx-flap var(--flap, .24s) ease-in-out infinite alternate; transform-origin:50% 45%; }
/* On the dark ground a dark bat is no bat at all: a faint glow from below. */
:root[data-theme="dark"] .season-fx.bats i { filter:drop-shadow(0 0 2px rgb(255 150 60 / 60%)); }
@keyframes fx-flap { from { transform:scaleY(1); } to { transform:scaleY(.45); } }
@media (prefers-reduced-motion: reduce) { .season-fx { display:none; } }

.setrow > select.seasonpick { flex:0 0 auto; min-width:110px; width:auto; font:inherit; font-size:13px;
  padding:5px 8px; border:1px solid var(--rule-strong); border-radius:4px; background:var(--surface); color:var(--ink); }

/* The few pictures the seasons use, NPT's own (drawn on a 24-unit grid, flat
   colour, held here as SVG so they are the same on every computer). */
:root {
  --ico-lantern:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11.3 5.6c-.2-1.8.5-3.2 2.1-4l1.2 1.2c-1.1.6-1.6 1.5-1.6 2.8z' fill='%234F7A2B'/%3E%3Cellipse cx='12' cy='14.2' rx='10.6' ry='8.6' fill='%23D9620E'/%3E%3Cellipse cx='8.1' cy='14.2' rx='5.8' ry='8.3' fill='%23EE7518'/%3E%3Cellipse cx='15.9' cy='14.2' rx='5.8' ry='8.3' fill='%23EE7518'/%3E%3Cellipse cx='12' cy='14.2' rx='4.1' ry='8.6' fill='%23F88A2A'/%3E%3Cpath d='M6.4 12.4l2.3-3 2.3 3zM13 12.4l2.3-3 2.3 3z' fill='%23FFD24A'/%3E%3Cpath d='M6.3 15.3c2.8 3.6 8.6 3.6 11.4 0-.9.5-1.8.6-2.5.5l-.7 1.3-1-1.1-1.5 1.4-1.5-1.4-1 1.1-.7-1.3c-.7.1-1.6 0-2.5-.5z' fill='%23FFD24A'/%3E%3C/svg%3E");
  --ico-bat:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 16'%3E%3Cpath fill='%233A2152' stroke='%231E0F2C' stroke-width='.5' stroke-linejoin='round' d='M16 4.4c.5-.9 1.1-1.3 1.6-1.3l.5-1.5.5 1.7c1.3.3 2.3 1 3.1 2.1 1.4-.8 3.1-.8 4.9.1 1.3.6 2.7.9 4.4.8-1.9.9-3.1 2.2-3.6 3.9-1.3-1.1-3-1.2-4.3-.3-.7-1.1-2-1.5-3.1-.9-.7 1.2-1.7 2-2.6 2.6l-1.4.7-1.4-.7c-.9-.6-1.9-1.4-2.6-2.6-1.1-.6-2.4-.2-3.1.9-1.3-.9-3-.8-4.3.3C4.1 8.2 2.9 6.9 1 6c1.7.1 3.1-.2 4.4-.8 1.8-.9 3.5-.9 4.9-.1.8-1.1 1.8-1.8 3.1-2.1l.5-1.7.5 1.5c.5 0 1.1.4 1.6 1.3z'/%3E%3Ccircle cx='15.1' cy='4.9' r='.45' fill='%23FFB347'/%3E%3Ccircle cx='16.9' cy='4.9' r='.45' fill='%23FFB347'/%3E%3C/svg%3E");
  --ico-maple:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 23v-7' stroke='%237A3A12' stroke-width='1.2' stroke-linecap='round'/%3E%3Cpath d='M12 1.8l1.6 3.4 2.3-1-.6 4.4 3.2-2.6.5 2.2 3-.4-1.9 3.2 1.6 1.2-4.6 3.1.6 1.6-4.3-.6-.8 2.7h-1.2l-.8-2.7-4.3.6.6-1.6-4.6-3.1 1.6-1.2-1.9-3.2 3 .4.5-2.2 3.2 2.6-.6-4.4 2.3 1z' fill='%23D2601E' stroke='%23B14A12' stroke-width='.7' stroke-linejoin='round'/%3E%3Cpath d='M12 6v10.5M12 12l4.2-3.4M12 12L7.8 8.6M12 14.6l4.4-.6M12 14.6l-4.4-.6' stroke='%238E3A0C' stroke-width='.7' stroke-linecap='round' opacity='.7'/%3E%3C/svg%3E");
  --ico-maple-ink:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 23v-7' stroke='%233A1404' stroke-width='1.3' stroke-linecap='round'/%3E%3Cpath d='M12 1.8l1.6 3.4 2.3-1-.6 4.4 3.2-2.6.5 2.2 3-.4-1.9 3.2 1.6 1.2-4.6 3.1.6 1.6-4.3-.6-.8 2.7h-1.2l-.8-2.7-4.3.6.6-1.6-4.6-3.1 1.6-1.2-1.9-3.2 3 .4.5-2.2 3.2 2.6-.6-4.4 2.3 1z' fill='%233A1404'/%3E%3Cpath d='M12 6v10.5M12 12l4.2-3.4M12 12L7.8 8.6M12 14.6l4.4-.6M12 14.6l-4.4-.6' stroke='%23E39A52' stroke-width='.7' stroke-linecap='round' opacity='.75'/%3E%3C/svg%3E");
  --ico-lantern-ink:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11.3 5.6c-.2-1.8.5-3.2 2.1-4l1.2 1.2c-1.1.6-1.6 1.5-1.6 2.8z' fill='%232D4A14'/%3E%3Cellipse cx='12' cy='14.2' rx='10.6' ry='8.6' fill='%232A0E00'/%3E%3Cellipse cx='12' cy='14.2' rx='4.1' ry='8.6' fill='%233E1704'/%3E%3Cpath d='M6.4 12.4l2.3-3 2.3 3zM13 12.4l2.3-3 2.3 3z' fill='%23FFD24A'/%3E%3Cpath d='M6.3 15.3c2.8 3.6 8.6 3.6 11.4 0-.9.5-1.8.6-2.5.5l-.7 1.3-1-1.1-1.5 1.4-1.5-1.4-1 1.1-.7-1.3c-.7.1-1.6 0-2.5-.5z' fill='%23FFD24A'/%3E%3C/svg%3E");
}

:root[data-season] .mark { white-space:nowrap; }
/* The costumed logo is wider than the pill; on a crowded bar the brand column
   shrinks to nothing and the logo would sit over the search box. */
:root[data-season] .topbar .brand { min-width:88px; }

/* Autumn: a small maple leaf hangs from the logo and stirs — app.js moves it as
   a breeze would: small uneven swings, now and then a gust. */
.mark .deco-leaf { display:inline-block; width:13px; height:13px; margin-left:6px; vertical-align:-2px; opacity:.85;
  background:var(--ico-maple-ink) center / contain no-repeat; transform-origin:50% 0; }

/* Halloween (Dominik, 15/10/2026: "I don't like the pumpkin … leave the logo in
   the natural colours just with spiders running over it and web and pumpkins
   rolling, maybe random ghost going around the screen"). The NPT pill in its own
   lime, whatever the season has done to the accent, with a cobweb on its corner.
   The spider, the pumpkins and the ghost are moved by app.js (seasonDeco), each
   on a path and at a pace of its own. */
:root[data-season="halloween"] .mark { position:relative; background:#99CC00; color:#0E1200; }
.mark .deco-web { position:absolute; right:-5px; top:-5px; width:17px; height:17px; pointer-events:none; }
.mark .deco-spider { position:absolute; left:0; top:0; width:11px; height:11px; pointer-events:none; will-change:transform; }
/* Its legs go while it walks. */
.mark .deco-spider.walking svg { animation:fx-legs .11s steps(2, jump-none) infinite; }
@keyframes fx-legs { from { transform:scaleX(.88) rotate(-4deg); } to { transform:scaleX(1.06) rotate(4deg); } }
.mark .deco-spider svg, .mark .deco-web svg, .season-deco i svg { display:block; width:100%; height:100%; }
.season-deco.pumpkins { position:absolute; left:var(--gutter, 16px); bottom:0; width:min(560px, 45vw); height:14px;
  pointer-events:none; overflow:hidden; }
.season-deco.pumpkins i { position:absolute; left:0; bottom:1px; opacity:0; will-change:transform; }
/* The ghost: faint, drifting where it pleases, and gone; never in the way of a click. */
.season-ghost { position:fixed; left:0; top:0; width:34px; height:40px; z-index:150; pointer-events:none; opacity:0;
  will-change:transform; }
.season-ghost svg { display:block; width:100%; height:100%; animation:fx-bob var(--bob, 2.4s) ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .mark .deco-spider.walking svg { animation:none; }
  .season-deco.pumpkins, .season-ghost { display:none; }
}

/* A pressed button that is working spins a jack-o'-lantern, or a maple leaf. */
:root[data-season] .is-busy::after {
  border:0; border-radius:0; width:1.15em; height:1.15em; vertical-align:-.22em;
  background:var(--ico-lantern) center / contain no-repeat; animation:np-spin 1.1s linear infinite;
}
:root[data-season="autumn"] .is-busy::after { background-image:var(--ico-maple); }
/* On an accent button the coloured ones vanish: the dark ones, as its text. */
:root[data-season="halloween"] .btn.primary.is-busy::after { background-image:var(--ico-lantern-ink); }
:root[data-season="autumn"] .btn.primary.is-busy::after { background-image:var(--ico-maple-ink); }

/* "Working…" in the corner: a bat, or a leaf (the words are the season's too —
   workNote in app.js). */
:root[data-season] .worknote .spin { border:0; border-radius:0; width:auto; height:auto; animation:none; display:inline-flex; }
:root[data-season="halloween"] .worknote .spin::before { content:''; display:inline-block; width:26px; height:13px; background:var(--ico-bat) center / contain no-repeat; animation:fx-flutter .2s ease-in-out infinite alternate; }
:root[data-season="autumn"] .worknote .spin::before { content:''; display:inline-block; width:16px; height:16px; background:var(--ico-maple) center / contain no-repeat; transform-origin:50% 0; animation:fx-sway 1.2s ease-in-out infinite; }
@keyframes fx-flutter { from { transform:scale(1.06, .7); } to { transform:scale(.94, 1.06); } }
@keyframes fx-sway { 0%, 100% { transform:rotate(-10deg); } 21% { transform:rotate(9deg); } 36% { transform:rotate(3deg); } 58% { transform:rotate(13deg); } 81% { transform:rotate(-5deg); } }

/* The dot top right while a screen loads: a bat bobbing, or a leaf turning. */
:root[data-season] main.working::before { border-radius:0; top:12px; background-color:transparent; }
:root[data-season="halloween"] main.working::before { content:''; display:inline-block; width:24px; height:12px; background:var(--ico-bat) center / contain no-repeat; animation:fx-bob .9s ease-in-out infinite; }
:root[data-season="autumn"] main.working::before { content:''; display:inline-block; width:15px; height:15px; background:var(--ico-maple) center / contain no-repeat; animation:np-spin 2.4s linear infinite; }
@keyframes fx-bob { 0%, 100% { transform:translateY(-2px); opacity:.8; } 28% { transform:translateY(1px); } 55% { transform:translateY(3px); opacity:1; } 79% { transform:translateY(0); opacity:.9; } }
/* A dark bat on a dark ground is no bat at all: a faint glow, as the flying ones have. */
:root[data-season="halloween"][data-theme="dark"] .worknote .spin::before,
:root[data-season="halloween"][data-theme="dark"] main.working::before { filter:drop-shadow(0 0 2px rgb(255 150 60 / 70%)); }

@media (prefers-reduced-motion: reduce) {
  :root[data-season] .worknote .spin::before, :root[data-season] main.working::before { animation:none; }
  :root[data-season] .is-busy::after { animation-duration:3s; }
}

/* Company, now and then (22/10/2026, season.js): a zombie or its hand up out of
   the ground, bats out of a click, the witch on her broom, a ghost's BOO; in
   autumn leaves kicked up, a pumpkin spice latte and a squirrel. The foot of
   the window is the ground — above the phone's bar, where there is one. None of
   it takes a click; none of it is drawn where motion is turned down. */
.season-ground { position:fixed; left:0; right:0; bottom:var(--phonebar-h, 0px); height:180px; z-index:150;
  pointer-events:none; overflow:hidden; }
.season-ground > i { position:absolute; left:0; bottom:0; display:block; will-change:transform; }
.season-ground svg, .season-witch svg, .season-burst svg { display:block; width:100%; height:100%; overflow:visible; }
.season-ground .clod { border-radius:50%; background:#6E4E32; }
.season-ground .boo-ghost { width:30px; height:35px; opacity:0; }
.season-ground .boo-ghost svg { animation:fx-bob 2.2s ease-in-out infinite; }
.zombie, .zhand, .latte, .squirrel { transform-origin:50% 100%; }
/* Each part turns about its own joint: hips, shoulders, neck, wrist, the root
   of a tail, the brim of a hat. */
.season-ground g, .season-witch g { transform-box:fill-box; }
.zb-legb, .zb-legf { transform-origin:50% 0; }
.zb-armb, .zb-armf { transform-origin:0 50%; }
.zb-head, .zh-hand, .wt-hat, .wt-cat { transform-origin:50% 100%; }
.sq-tail { transform-origin:88% 92%; }
.sq-head { transform-origin:15% 85%; }
.wt-cape { transform-origin:100% 0; }
.wt-hair { transform-origin:100% 20%; }
.latte .wisp { position:absolute; bottom:86%; width:9px; height:26px; color:var(--ink-3); opacity:0; pointer-events:none; }

.season-witch { position:fixed; left:0; top:0; z-index:150; pointer-events:none; will-change:transform; }
.season-witch .tilt, .season-witch .face { display:block; width:100%; height:100%; }
.season-witch .face { transition:transform .18s ease-in-out; }
.season-burst, .season-sparks { position:fixed; inset:0; z-index:150; pointer-events:none; overflow:hidden; }
.season-burst i, .season-sparks i { position:absolute; left:0; top:0; display:block; width:var(--s); height:var(--s);
  opacity:0; will-change:transform; }
.season-burst.bats i svg { animation:fx-flap var(--flap, .2s) ease-in-out infinite alternate; transform-origin:50% 45%; }
.season-sparks i { background:var(--c); clip-path:polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%); }
/* Dark figures on the dark ground: the faint glow the bats have. */
:root[data-theme="dark"] .season-witch, :root[data-theme="dark"] .season-burst.bats i { filter:drop-shadow(0 0 2px rgb(255 150 60 / 60%)); }

/* "BOO!", "Gotcha!" — and "!" over the one who jumped. */
.season-says { position:fixed; z-index:151; pointer-events:none; padding:2px 8px; border-radius:10px;
  background:var(--surface); color:var(--ink); border:1.5px solid var(--ink); box-shadow:0 3px 8px rgb(0 0 0 / 20%);
  font-size:12.5px; font-weight:900; letter-spacing:.03em; white-space:nowrap; opacity:0; }
.season-says::after { content:""; position:absolute; left:calc(50% - 4px); bottom:-5px; width:8px; height:8px;
  background:var(--surface); border-right:1.5px solid var(--ink); border-bottom:1.5px solid var(--ink); transform:rotate(45deg); }
.season-says.bang { padding:0; background:none; border:0; box-shadow:none; color:var(--accent-ink); font-size:22px; font-weight:900;
  text-shadow:0 0 3px var(--surface), 0 0 1px var(--surface); }
.season-says.bang::after { content:none; }
@media (prefers-reduced-motion: reduce) {
  .season-ground, .season-witch, .season-burst, .season-sparks, .season-says { display:none; }
}

/* ---------------------------------------------------------------- Ask NPT (267)
 *
 * Down the right-hand side like the procedures dock, and over it: the point is
 * to press Open and see the screen change behind it with the answer still there.
 * It covers the right edge rather than pushing the page, because it is open for
 * a question or two, not for the length of a job. */
.askbtn { border-color:var(--brand); }
.askbtn[aria-expanded="true"] { background:var(--accent-soft); color:var(--accent-ink); }
.askpanel {
  position:fixed; top:var(--topbar-h); right:0; bottom:0; width:min(400px, 100vw); z-index:42;
  display:flex; flex-direction:column;
  background:var(--surface); border-left:1px solid var(--rule-strong);
  box-shadow:-10px 0 30px -18px rgba(0,0,0,.45);
}
.ask-head {
  display:flex; align-items:baseline; gap:8px; padding:10px 12px 10px 16px;
  border-bottom:1px solid var(--rule);
}
.ask-head strong { font-size:14.5px; }
.ask-head .muted { font-size:12.5px; flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ask-log { flex:1; overflow-y:auto; padding:14px 16px; display:flex; flex-direction:column; gap:10px; }
.ask-empty { color:var(--ink-2); margin:4px 0 2px; }
.ask-try { display:flex; flex-direction:column; align-items:flex-start; gap:6px; }
.ask-try .btn { text-align:left; white-space:normal; }
.ask-q {
  align-self:flex-end; max-width:88%; padding:8px 11px; border-radius:10px 10px 2px 10px;
  background:var(--accent-soft); color:var(--ink); font-size:13.5px; white-space:pre-wrap; overflow-wrap:anywhere;
}
.ask-a {
  align-self:flex-start; max-width:96%; padding:10px 12px; border-radius:10px 10px 10px 2px;
  background:var(--surface-2); border:1px solid var(--rule); font-size:13.5px; line-height:1.5;
  overflow-wrap:anywhere;
}
.ask-a p { margin:0 0 8px; }
.ask-a p:last-of-type { margin-bottom:0; }
.ask-a ol { margin:0 0 8px; padding-left:20px; }
.ask-a .ask-note { font-size:12.5px; color:var(--warn); }
.ask-go { display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; }
/* What it found, as a heading over the answer; other Help questions as links. */
.ask-a .ask-found { font-weight:600; color:var(--ink); margin-bottom:6px; }
.ask-also { display:flex; flex-direction:column; align-items:flex-start; gap:3px; margin-top:10px; font-size:12.5px; }
.ask-also .muted { font-size:11.5px; text-transform:uppercase; letter-spacing:.04em; }
.ask-also button { border:0; background:none; padding:0; font:inherit; color:var(--accent-ink); text-align:left; cursor:pointer; }
.ask-also button:hover, .ask-also button:focus-visible { text-decoration:underline; }
.ask-more { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:8px; font-size:12px; min-height:24px; }
.ask-more .btn.quiet { margin-left:auto; font-size:12px; }
.ask-a.pending { display:flex; gap:5px; padding:13px 14px; }
.ask-a.pending span {
  width:7px; height:7px; border-radius:50%; background:var(--ink-3);
  animation:askdot 1.1s ease-in-out infinite;
}
.ask-a.pending span:nth-child(2) { animation-delay:.15s; }
.ask-a.pending span:nth-child(3) { animation-delay:.3s; }
@keyframes askdot { 0%, 80%, 100% { opacity:.25; transform:translateY(0); } 40% { opacity:1; transform:translateY(-3px); } }
@media (prefers-reduced-motion: reduce) { .ask-a.pending span { animation:none; opacity:.6; } }
.ask-form { display:flex; gap:8px; align-items:flex-end; padding:10px 12px 6px; border-top:1px solid var(--rule); }
.ask-form textarea {
  flex:1; resize:none; min-height:44px; max-height:140px; font:inherit; font-size:13.5px;
  padding:8px 10px; border:1px solid var(--rule-strong); border-radius:6px; background:var(--surface); color:var(--ink);
}
.ask-form textarea:focus { border-color:var(--brand); outline:none; }
.ask-foot { margin:0; padding:0 12px 10px; font-size:11.5px; color:var(--ink-3); }
.ask-tour { margin-top:10px; }
.ask-tour .btn { white-space:normal; text-align:left; }

/* ============================================ two minutes a day (305)
 * Green beside the name, the day's card in the corner, the tour above Ask
 * NPT's answers, the light that shows the part of the screen, the stamp and the
 * passport. Season colours only on the stamp; everything else is the house set. */
.greendot {
  width:9px; height:9px; border-radius:50%; flex:0 0 auto;
  background:var(--warn); box-shadow:0 0 0 2px var(--surface);
  transition:background .3s;
}
.greendot.on { background:var(--brand); box-shadow:0 0 0 2px var(--surface), 0 0 0 4px var(--accent-soft); }
.passbtn { display:inline-flex; align-items:center; gap:5px; }
.passbtn svg { width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; }
.passbtn #passcount:empty { display:none; }
.passbtn #passcount { font-variant-numeric:tabular-nums; font-size:12px; color:var(--ink-2); }

.tourcard {
  position:fixed; right:18px; bottom:18px; z-index:44; width:min(330px, calc(100vw - 32px));
  padding:12px 14px 10px; border-radius:12px;
  background:var(--surface); border:1px solid var(--rule-strong); border-top:3px solid var(--brand);
  box-shadow:0 14px 34px -14px rgba(0,0,0,.5);
  opacity:0; transform:translateY(14px); transition:opacity .3s, transform .3s cubic-bezier(.2,1.2,.4,1);
}
.tourcard.on { opacity:1; transform:none; }
.tourcard .tc-head { display:flex; align-items:center; gap:8px; }
.tourcard .tc-mark { width:20px; height:20px; color:var(--accent-ink); display:inline-flex; }
.tourcard .tc-mark svg { width:100%; height:100%; }
.tourcard .tc-eyebrow { flex:1; font-size:11.5px; font-weight:600; letter-spacing:.05em; text-transform:uppercase; color:var(--ink-2); }
.tourcard .tc-x { padding:0 7px; font-size:16px; line-height:1.4; }
.tourcard h3 { margin:8px 0 2px; font-size:16px; text-wrap:balance; }
.tourcard p { margin:0 0 10px; font-size:13.5px; color:var(--ink-2); }
.tourcard .tc-go { display:flex; gap:8px; align-items:center; }
.tourcard .tc-foot { display:flex; justify-content:space-between; gap:10px; margin-top:10px; padding-top:8px;
  border-top:1px solid var(--rule); font-size:11.5px; color:var(--ink-3); }
.tourcard .linkish, .tourblock .linkish { border:0; background:none; padding:0; font:inherit; color:var(--ink-3); cursor:pointer; text-decoration:underline; }

.tourblock { padding:12px 14px 10px 16px; border-bottom:1px solid var(--rule); background:var(--accent-soft); }
.tourblock .tb-head { display:flex; gap:10px; align-items:center; }
.tourblock .tb-clock { position:relative; width:42px; height:42px; flex:0 0 auto; }
.tourblock .tb-clock svg { width:42px; height:42px; transform:rotate(-90deg); }
.tourblock .tb-track { fill:none; stroke:var(--rule-strong); stroke-width:3; }
.tourblock .tb-left { fill:none; stroke:var(--accent-ink); stroke-width:3; stroke-linecap:round; transition:stroke-dashoffset 1s linear; }
.tourblock .tb-time { position:absolute; inset:0; display:grid; place-items:center; font-size:11px; font-weight:600;
  font-variant-numeric:tabular-nums; color:var(--ink); }
.tourblock .tb-title { display:flex; flex-direction:column; min-width:0; }
.tourblock .tb-eyebrow { font-size:11px; letter-spacing:.05em; text-transform:uppercase; color:var(--ink-2); }
.tourblock .tb-title strong { font-size:15px; text-wrap:balance; }
.tourblock .tb-say { margin:10px 0 0; font-size:14px; line-height:1.5; color:var(--ink); }
.tourblock .tb-say.try b, .tourblock .tb-say b { color:var(--accent-ink); }
.tourblock .tb-btns { display:flex; gap:6px; align-items:center; margin-top:12px; }
.tourblock .tb-gap { flex:1; }
.tourblock .tb-foot { margin-top:8px; font-size:11.5px; color:var(--ink-3); }
.tourblock .tb-quiz { display:flex; flex-direction:column; gap:6px; margin-top:10px; }
.tourblock .tb-quiz .btn { text-align:left; white-space:normal; justify-content:flex-start; }
.tourblock .tb-quiz .btn.right { border-color:var(--ok); background:var(--ok-soft); color:var(--ok); opacity:1; }
.tourblock .tb-quiz .btn.wrong { border-color:var(--crit); background:var(--crit-soft); color:var(--crit); opacity:1; }
.tourblock .tb-stamped { display:flex; gap:12px; align-items:center; }
.tourblock .tb-stamped strong { display:block; font-size:15px; }
.tourblock .tb-stamped .muted { font-size:12.5px; }
.tourblock .tb-chapter { margin:10px 0 0; font-weight:600; color:var(--accent-ink); }
.tourblock .tb-next { display:flex; flex-direction:column; gap:6px; margin-top:6px; }
.tourblock .tb-next .btn { display:flex; flex-direction:column; align-items:flex-start; text-align:left; white-space:normal; gap:1px; }
.tourblock .tb-next .btn span { font-size:12px; color:var(--ink-2); font-weight:400; }
.tourblock .tb-next .btn.chosen { border-color:var(--accent-ink); background:var(--surface); }

.stamp {
  width:52px; height:52px; flex:0 0 auto; display:grid; place-items:center; border-radius:50%;
  border:2px dashed currentColor; transform:rotate(-12deg); color:var(--accent-ink);
  animation:npt-stamp .5s cubic-bezier(.2,1.6,.4,1);
}
.stamp svg { width:30px; height:30px; }
.stamp.autumn { color:#B5561B; }
.stamp.halloween { color:#E06A1B; }
:root[data-theme="dark"] .stamp.autumn { color:#E08A4B; }
@keyframes npt-stamp { 0% { transform:scale(1.9) rotate(-30deg); opacity:0; } 70% { transform:scale(.92) rotate(-10deg); opacity:1; } 100% { transform:scale(1) rotate(-12deg); } }

/* The light: a hole in a dimmed page round the part being talked about. It
   lets every click through — "Try it" means the real screen. Inside a pop-up
   (Timesheets' week) it is only the ring, since the pop-up sits above it. */
.tourspot {
  position:fixed; z-index:41; pointer-events:none; border-radius:8px;
  border:2px solid var(--brand); box-shadow:0 0 0 9999px rgba(0,0,0,.42);
  transition:left .25s, top .25s, width .25s, height .25s;
  animation:npt-spot .4s ease-out;
}
@keyframes npt-spot { from { opacity:0; } to { opacity:1; } }
.tourspot.inpop { z-index:47; box-shadow:0 0 0 4px var(--accent-soft); }

.passveil { position:fixed; inset:0; z-index:90; display:grid; place-items:center; padding:16px; background:rgba(0,0,0,.45); }
.passport {
  width:min(760px, 100%); max-height:88vh; overflow-y:auto; border-radius:14px;
  background:var(--surface); border:1px solid var(--rule-strong); box-shadow:0 24px 60px -24px rgba(0,0,0,.6);
}
.passport .pp-head { display:flex; gap:12px; align-items:center; padding:14px 16px; border-bottom:1px solid var(--rule);
  position:sticky; top:0; background:var(--surface); }
.passport .pp-head > div { flex:1; display:flex; flex-direction:column; }
.passport .pp-head strong { font-size:16px; }
.passport .pp-head .muted { font-size:12.5px; }
.passport .pp-mark { width:26px; height:26px; color:var(--accent-ink); }
.passport .pp-mark svg { width:100%; height:100%; }
.passport .pp-pages { display:grid; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); gap:12px; padding:14px 16px 18px; }
.passport .pp-page { border:1px solid var(--rule); border-radius:10px; padding:10px 12px; background:var(--surface-2); }
.passport .pp-page.full { border-color:var(--accent-ink); }
.passport .pp-page h3 { margin:0 0 8px; font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-2); }
.passport .pp-page ul { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px; }
.passport .pp-page li { display:flex; gap:8px; align-items:center; }
.passport .pp-stamp { width:26px; height:26px; flex:0 0 auto; border-radius:50%; display:grid; place-items:center;
  border:1.5px dashed var(--rule-strong); color:var(--accent-ink); }
.passport li.done .pp-stamp { border-color:currentColor; transform:rotate(-12deg); }
.passport .pp-stamp svg { width:16px; height:16px; }
.passport .pp-what { flex:1; min-width:0; display:flex; flex-direction:column; font-size:13px; }
.passport .pp-what span { font-size:11.5px; color:var(--ink-3); }
.passport li.never { opacity:.55; }
@media (prefers-reduced-motion: reduce) {
  .tourcard, .tourspot, .stamp { transition:none; animation:none; }
}
@media (max-width:640px) {
  /* 100vw counts the scrollbar, which pushed the panel off the left edge. */
  .askpanel { left:0; width:auto; border-left:none; }
}

/* ------------------------------------------- a new project, as a line (267)
 * New project… and Pencil in… put a line at the top of the Sheet and a guide at
 * the foot of the screen that asks for one box at a time (draftBlock and
 * draftGuide in web/views/sheet.js). The line is marked out — an accent wash and
 * a dashed edge — so it is plain it is not made yet; the box being asked for has
 * a ring. Pencilled, it is grey, as pencilled work is on the sheet. */
table.trk tbody.newdraft td { background:var(--accent-soft); vertical-align:top; }
table.trk tbody.newdraft.pencil td { background:var(--surface-2); }
table.trk tbody.newdraft tr { outline:2px dashed var(--brand); outline-offset:-2px; }
table.trk tbody.newdraft .dcell { width:100%; min-width:6ch; background:var(--surface); }
table.trk tbody.newdraft .dsite { margin-top:4px; font-size:12px; }
table.trk tbody.newdraft .dcell.dcur { outline:2px solid var(--brand); outline-offset:0; }
.draftguide {
  position:fixed; left:50%; bottom:16px; transform:translateX(-50%); z-index:41;
  width:min(680px, calc(100vw - 32px)); display:grid; gap:8px; padding:12px 16px;
  background:var(--surface); color:var(--ink); border:1px solid var(--rule-strong); border-radius:8px;
  box-shadow:0 16px 40px -16px rgba(0,0,0,.5); font-size:13.5px;
}
.draftguide.pencil { border-style:dashed; }
.draftguide .dg-top { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.draftguide .dg-top [data-dcancel] { margin-left:auto; }
.draftguide .dg-mode { display:inline-flex; gap:4px; margin-left:6px; }
.draftguide .dg-mode .chip { cursor:pointer; font:inherit; font-size:11.5px; }
.draftguide .dg-mode .chip.on { background:var(--accent-soft); border-color:var(--brand); color:var(--accent-ink); }
.draftguide .dg-ask { line-height:1.45; }
.draftguide .dg-n { display:inline-block; min-width:5.5ch; color:var(--ink-3); font-size:12px; font-variant-numeric:tabular-nums; }
.draftguide .dg-btns, .draftguide .dg-rem { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
.draftguide .dg-rem label { display:inline-flex; gap:6px; align-items:center; }
.draftguide .dg-rem select, .draftguide .dg-rem input { font:inherit; font-size:13px; padding:3px 6px;
  border:1px solid var(--rule-strong); border-radius:4px; background:var(--surface); color:var(--ink); }
.draftguide .dg-dots { display:flex; gap:5px; }
.draftguide .ddot { width:9px; height:9px; padding:0; border-radius:50%; border:1px solid var(--rule-strong);
  background:var(--surface-2); cursor:pointer; }
.draftguide .ddot.full { background:var(--brand); border-color:var(--brand); }
.draftguide .ddot.on { outline:2px solid var(--ink-2); outline-offset:1px; }
.draftguide .ddot:focus-visible { outline:2px solid var(--brand); outline-offset:1px; }
@media (max-width:640px) { .draftguide { bottom:8px; padding:10px 12px; } }

/* 268. THE JOBWATCH AGE IS A BUTTON NOW — a click lists what the screen is made
   of, with Read now. It keeps the look of the plain label it replaced: same
   font, same colours, no box, so the header does not move. */
.health button.synced {
  font:inherit; color:var(--ink-3); background:none; border:0; padding:0; margin:0;
  cursor:pointer; white-space:nowrap;
}
.health button.synced.stale { color:var(--warn); }
.health button.synced:hover { text-decoration:underline; text-underline-offset:2px; }
.health button.synced:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:3px; }
.freshpanel {
  position:absolute; z-index:43; width:min(360px, calc(100vw - 16px));
  background:var(--surface); border:1px solid var(--rule-strong); border-radius:8px;
  box-shadow:0 12px 30px -16px rgba(0,0,0,.45); padding:10px 12px 8px; font-size:13px;
}
.freshpanel-head { font-weight:600; margin-bottom:6px; }
.freshpanel table { width:100%; border-collapse:collapse; }
.freshpanel td { padding:6px 0; border-top:1px solid var(--rule); vertical-align:middle; }
.freshpanel td.shrink { width:1%; white-space:nowrap; padding-left:10px; text-align:right; }
.freshpanel td .muted { font-size:12px; }
.freshpanel p { margin:8px 0 0; font-size:12px; line-height:1.45; }

/* 270. PORTALS — MFG's Verisae and Premoni orders. Quiet by design: the screen
   is read for what is waiting, so colour is kept for what needs a person. */
.ptbanner { border:1px solid var(--rule); background:var(--surface-2); border-radius:8px; padding:10px 14px; margin:0 0 16px; font-size:14px; line-height:1.5; }
.ptbanner .muted { display:block; font-size:13px; }
.ptcount { display:inline-block; min-width:18px; padding:0 5px; margin-left:4px; border-radius:9px; background:var(--accent-soft); color:var(--ink); font-size:12px; font-weight:600; text-align:center; }
.pttiles { display:grid; grid-template-columns:repeat(auto-fit, minmax(140px, 1fr)); gap:10px; margin:0 0 16px; }
.pttile { border:1px solid var(--rule); border-radius:8px; padding:10px 12px; background:var(--surface); display:flex; flex-direction:column; gap:2px; }
.pttile b { font-size:22px; font-variant-numeric:tabular-nums; }
.pttile span { color:var(--ink-2); font-size:13px; }
.pttile.warn { border-color:var(--warn); }
.pttile.bad { border-color:var(--crit); }
.ptsteps { display:flex; flex-wrap:wrap; gap:6px; }
.ptsteps b { margin-left:4px; font-variant-numeric:tabular-nums; }
.ptfilters { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 10px; }
.ptnotes { list-style:none; margin:0; padding:0; }
.ptnotes li { display:flex; gap:10px; align-items:baseline; padding:7px 0; border-top:1px solid var(--rule); font-size:14px; }
.ptnotes li > span:nth-of-type(2) { flex:1; }
.ptnotes li.pt-warning { box-shadow:inset 3px 0 0 var(--warn); padding-left:8px; }
.ptnotes li.pt-error { box-shadow:inset 3px 0 0 var(--crit); padding-left:8px; }
.ptnotes li.pt-done { opacity:.6; }
.ptwatch { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 10px; }
.ptstrip { display:inline-flex; gap:3px; vertical-align:middle; }
.ptstrip i { width:10px; height:10px; border-radius:50%; background:var(--rule-strong); }
.ptstrip i.pt-done { background:var(--brand); }
.ptstrip i.pt-current { background:var(--surface); box-shadow:inset 0 0 0 2px var(--warn); }
.ptsearch { display:flex; gap:8px; align-items:center; margin:0 0 10px; }
.ptsearch input { flex:1; max-width:420px; }
.ptcheck { display:flex; gap:6px; align-items:center; margin:0 0 10px; font-size:14px; }
.ptcols { display:grid; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); gap:16px; }
.ptcols h4 { margin:8px 0 4px; }
.ptlist { margin:0; padding-left:18px; }
.ptcode pre { font-family:var(--mono); font-size:16px; background:var(--surface-2); border:1px solid var(--rule); border-radius:6px; padding:8px 12px; display:inline-block; }
.ptdrawer { position:fixed; top:var(--header-h); right:0; bottom:0; width:min(520px, 100vw); z-index:40; display:flex; flex-direction:column;
  background:var(--surface); border-left:1px solid var(--rule-strong); box-shadow:-10px 0 30px -18px rgba(0,0,0,.45); }
.ptdrawer-head { display:flex; gap:8px; align-items:baseline; padding:12px 16px; border-bottom:1px solid var(--rule); }
.ptdrawer-head .muted { flex:1; }
.ptdrawer-body { overflow-y:auto; padding:12px 16px 24px; font-size:14px; line-height:1.5; }
.ptdrawer-body h4 { margin:16px 0 6px; }
.ptsteplist { list-style:none; margin:8px 0; padding:0; display:flex; flex-wrap:wrap; gap:4px 12px; }
.ptsteplist li { font-size:13px; color:var(--ink-3); }
.ptsteplist li::before { content:'\25CB  '; }
.ptsteplist li.pt-done { color:var(--ink-2); }
.ptsteplist li.pt-done::before { content:'\25CF  '; color:var(--brand); }
.ptsteplist li.pt-current { color:var(--ink); font-weight:600; }
.ptsteplist li.pt-current::before { content:'\25C9  '; color:var(--warn); }
.pttimeline { list-style:none; margin:0; padding:0; }
.pttimeline li { padding:4px 0; border-top:1px solid var(--rule); }

/* Portals, phase 3 (273): deciding a JobWatch flag step in the drawer. */
.ptdecide { display:flex; flex-wrap:wrap; gap:8px 10px; align-items:flex-end; margin-top:14px; padding-top:12px; border-top:1px solid var(--rule); }
.ptreject { display:grid; gap:3px; font-size:13px; color:var(--ink-2, inherit); flex:1 1 180px; }
.ptdrawer-body table { width:100%; border-collapse:collapse; margin:8px 0; font-size:13.5px; }
.ptdrawer-body th, .ptdrawer-body td { text-align:left; padding:4px 6px; border-bottom:1px solid var(--rule); vertical-align:top; }
/* Accepting on Verisae (274): the ETA and comment the approver gives. */
.ptaccept { display:grid; grid-template-columns:1fr 1fr; gap:8px 12px; margin:12px 0 4px; padding:8px 12px 12px; border:1px solid var(--rule); border-radius:6px; }
.ptaccept legend { padding:0 4px; font-size:13px; font-weight:600; }
.ptaccept label { display:grid; gap:3px; font-size:13px; color:var(--ink-2, inherit); }
.ptaccept .ptwide { grid-column:1 / -1; }
.ptaccept textarea { resize:vertical; font:inherit; }

/* Portals LEDs (274) — Dominik, 16/10/2026: "I like the led lights of what steps are
   done on each order". Green for each step done, a pulsing blue for the one the order
   is on (red when it is on hold), dark for those to come. Later rules win over the
   first .ptstrip ones above. */
.ptstrip { display:inline-flex; gap:4px; align-items:center; vertical-align:middle; }
.ptstrip i, .ptstepper i { display:block; width:12px; height:12px; border-radius:50%; flex:none;
  background:var(--rule-strong); box-shadow:inset 0 1px 2px rgba(0,0,0,.35); }
.ptstrip i.pt-done, .ptstepper li.pt-done i {
  background:radial-gradient(circle at 35% 35%, #e4ff9c 0, #8fc700 45%, #4c7000 100%); box-shadow:0 0 5px rgba(143,199,0,.65); }
.ptstrip i.pt-current, .ptstepper li.pt-current i {
  background:radial-gradient(circle at 35% 35%, #d2ecff 0, #3394f0 45%, #165aa6 100%);
  box-shadow:0 0 0 3px rgba(51,148,240,.18), 0 0 7px rgba(51,148,240,.75); animation:ptled 2.4s ease-in-out infinite; }
.pt-hold i.pt-current, .ptstepper.pt-hold li.pt-current i {
  background:radial-gradient(circle at 35% 35%, #ffd6cf 0, #e5483a 45%, #8f1e14 100%);
  box-shadow:0 0 0 3px rgba(229,72,58,.18), 0 0 7px rgba(229,72,58,.75); animation-name:ptledhold; }
@keyframes ptled { 50% { box-shadow:0 0 0 3px rgba(51,148,240,.06), 0 0 3px rgba(51,148,240,.35); } }
@keyframes ptledhold { 50% { box-shadow:0 0 0 3px rgba(229,72,58,.06), 0 0 3px rgba(229,72,58,.35); } }
.ptstepper { list-style:none; margin:10px 0 14px; padding:0; display:flex; flex-wrap:wrap; row-gap:12px; }
.ptstepper li { position:relative; display:flex; flex-direction:column; align-items:center; gap:6px; width:78px;
  font-size:12px; line-height:1.25; text-align:center; color:var(--ink-3); }
.ptstepper li + li::before { content:''; position:absolute; top:5px; right:calc(50% + 10px); width:calc(100% - 20px);
  height:2px; background:var(--rule); }
.ptstepper li.pt-done + li::before { background:#8fc700; }
/* Done only because a later step proves it (19/10/2026): a hollow green ring. */
.ptstrip i.pt-done.pt-implied, .ptstepper li.pt-done.pt-implied i {
  background:var(--surface); box-shadow:inset 0 0 0 2px #8fc700; }
.ptstepper li.pt-done, .ptstepper li.pt-current { color:var(--ink); }
.ptstepper li.pt-current span { font-weight:600; }
.ptwait::before { content:''; display:inline-block; width:10px; height:10px; margin-right:8px; border-radius:50%;
  vertical-align:-1px; background:#3394f0; animation:ptled 1.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .ptstrip i.pt-current, .ptstepper li.pt-current i, .ptwait::before { animation:none; }
}

/* Portals: one frame per customer's portals (MFG: Verisae and Premoni), picked from this strip. */
.ptgroups { display:flex; gap:8px; flex-wrap:wrap; margin:0 0 12px; }
.ptgroup { appearance:none; border:1px solid var(--rule); background:var(--surface); color:var(--ink-2); border-radius:8px;
  padding:8px 16px 9px; text-align:left; cursor:pointer; font:inherit; min-width:140px; }
.ptgroup strong { display:block; color:var(--ink); font-size:15px; letter-spacing:.02em; }
.ptgroup span { font-size:12px; }
.ptgroup:hover { border-color:var(--ink-3); }
.ptgroup.on { border-color:var(--brand); box-shadow:inset 0 -3px 0 var(--brand); }
.ptgroup:focus-visible { outline:2px solid var(--accent-calm); outline-offset:2px; }
.ptframe { border-top:1px solid var(--rule); padding-top:12px; }

/* Portals by stage (299): whose orders, a tile per step, and the window a tile opens.
   Dominik, 22/10/2026: "grouped more by stage (led lights) ... tiles and pop up windows
   with option to search order in every tab/tile". The lights are the orders' own LEDs. */
.ptkinds { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 12px; }
.ptkind { appearance:none; display:inline-flex; align-items:baseline; gap:6px; min-height:40px; padding:7px 12px;
  border:1px solid var(--rule); border-radius:999px; background:var(--surface); color:var(--ink); font:inherit; font-size:14px; cursor:pointer; }
.ptkind b { font-weight:600; color:var(--ink-2); font-variant-numeric:tabular-nums; }
.ptkind span { color:var(--ink-3); font-size:12.5px; }
.ptkind:hover, .ptneed:hover, .ptstage:hover { border-color:var(--ink-3); }
.ptkind.on { border-color:var(--brand); box-shadow:inset 0 0 0 1px var(--brand); }
.ptkind:focus-visible, .ptneed:focus-visible, .ptstage:focus-visible, .ptpoprow:focus-visible, .ptfound-list button:focus-visible {
  outline:2px solid var(--accent-calm); outline-offset:2px; }
.ptk-iad { --ptk:#8b5cf6; } .ptk-vr1 { --ptk:#0891b2; } .ptk-reactive { --ptk:#ea7a0c; } .ptk-lifeliner { --ptk:#c026d3; } .ptk-tis { --ptk:#65a30d; } .ptk-ei { --ptk:#0f766e; } .ptk-undecided { --ptk:#8a8f85; }
.ptk-dot { flex:none; align-self:center; width:9px; height:9px; border-radius:50%; background:var(--ptk); }
.chip.ptk { box-shadow:inset 3px 0 0 var(--ptk); padding-left:8px; white-space:nowrap; }
/* Portals' search boxes are type=search, which the inputs' own rule (above) does not name: drawn here in NPT's look. */
.ptsearch input, .ptfind input, .ptpop-tools input { font:inherit; font-size:14px; color:var(--ink); background:var(--surface);
  border:1px solid var(--rule-strong); border-radius:6px; padding:6px 10px; }
.pteyebrow { margin:18px 0 8px; font-family:var(--mono); font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-2); }
.ptfind { position:relative; max-width:560px; }
.ptfind input { width:100%; min-height:44px; box-sizing:border-box; }
.ptfound:empty { display:none; }
.ptfound { position:absolute; left:0; right:0; top:calc(100% + 4px); z-index:20; padding:4px; background:var(--surface);
  border:1px solid var(--rule-strong); border-radius:8px; box-shadow:0 10px 28px -12px rgba(0,0,0,.5); }
.ptfound p { margin:8px 10px; }
.ptfound-list { list-style:none; margin:0; padding:0; }
.ptfound-list button { width:100%; display:flex; gap:10px; align-items:center; padding:8px 10px; border:0; border-radius:6px;
  background:none; color:var(--ink); font:inherit; text-align:left; cursor:pointer; }
.ptfound-list button:hover { background:var(--surface-2); }
.ptfound-list button > span:nth-child(2) { flex:1; min-width:0; }
.ptneeds { display:grid; grid-template-columns:repeat(auto-fit, minmax(190px, 1fr)); gap:10px; }
.ptneed { appearance:none; display:flex; align-items:center; gap:12px; padding:12px 14px; border:1px solid var(--rule); border-radius:10px;
  background:var(--surface); color:var(--ink); font:inherit; text-align:left; cursor:pointer; }
.ptneed b { font-size:26px; line-height:1; font-variant-numeric:tabular-nums; }
.ptneed span:last-child { color:var(--ink-2); }
.ptstages { display:grid; grid-template-columns:repeat(auto-fit, minmax(128px, 1fr)); gap:10px; }
.ptstage { appearance:none; display:flex; flex-direction:column; gap:4px; min-height:156px; padding:12px; border:1px solid var(--rule);
  border-radius:10px; background:var(--surface); color:var(--ink); font:inherit; text-align:left; cursor:pointer; }
.ptstage.empty { opacity:.72; }
.ptstage-head { display:flex; align-items:center; gap:8px; }
.ptstage-n { font-size:30px; line-height:1.05; font-variant-numeric:tabular-nums; }
.ptstage-who, .ptstage-old { color:var(--ink-2); font-size:12.5px; line-height:1.35; }
.ptstage-chips { display:flex; flex-wrap:wrap; gap:4px; min-height:22px; }
.ptstage-old { margin-top:auto; }
.ptstrip i.pt-current.pt-calm { animation:none; }
.ptpop { position:fixed; inset:0; z-index:39; display:flex; justify-content:center; align-items:flex-start;
  padding:calc(var(--header-h) + 12px) 16px calc(16px + var(--phonebar-h, 0px)); background:rgba(8,10,6,.55); }
.ptpop-box { width:min(980px, 100%); max-height:100%; display:flex; flex-direction:column; background:var(--surface); color:var(--ink);
  border:1px solid var(--rule-strong); border-radius:12px; box-shadow:0 24px 60px -20px rgba(0,0,0,.6); }
.ptpop-head { display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 12px; padding:14px 16px 10px; border-bottom:1px solid var(--rule); }
.ptpop-head h2 { flex:1 1 auto; margin:0; font-size:18px; }
.ptpop-head > .muted { font-size:13px; }
.ptpop-tools { display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:10px 16px; }
.ptpop-tools input { flex:1 1 260px; min-height:40px; box-sizing:border-box; }
.ptpop-tools .ptfilters { margin:0; }
.ptpop-list { overflow-y:auto; padding:0 8px 10px; }
.ptpop-none { margin:12px; }
.ptpoplist { list-style:none; margin:0; padding:0; }
.ptpoplist > li { display:flex; align-items:center; gap:8px; border-top:1px solid var(--rule); }
.ptpoprow { appearance:none; flex:1; min-width:0; display:grid; grid-template-columns:auto 92px minmax(0, 1fr) auto 52px; align-items:center; gap:12px;
  padding:10px 8px; border:0; background:none; color:var(--ink); font:inherit; font-size:14px; text-align:left; cursor:pointer; }
.ptpoprow:hover { background:var(--surface-2); }
.ptpop-wo { font-family:var(--mono); font-variant-numeric:tabular-nums; }
.ptpop-site { display:flex; flex-direction:column; min-width:0; }
.ptpop-site .muted { font-size:12.5px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ptpop-age { color:var(--ink-2); text-align:right; font-variant-numeric:tabular-nums; }
.ptpop-act { flex:none; padding-right:8px; }
.ptpop-whose { font-size:12.5px; }
@media (max-width: 640px) {
  .ptpop { padding:8px 8px calc(8px + var(--phonebar-h, 0px)); }
  .ptpoplist > li { flex-direction:column; align-items:stretch; gap:0; padding-bottom:10px; }
  .ptpoprow { grid-template-columns:minmax(0, 1fr) auto; grid-template-areas:'led led' 'wo age' 'site site' 'kind kind'; gap:4px 10px; }
  .ptpop-led { grid-area:led; } .ptpop-wo { grid-area:wo; } .ptpop-age { grid-area:age; }
  .ptpop-site { grid-area:site; } .ptpop-kind { grid-area:kind; }
  .ptpop-act { padding:0 8px; }
}
/* What NPT Portals is doing, step by step as it does it (299). */
.ptlive { list-style:none; margin:10px 0 0; padding:0; display:grid; gap:5px; font-size:13.5px; }
.ptlive li { display:flex; gap:8px; align-items:baseline; }
.ptlive li::before { content:''; flex:none; width:9px; height:9px; border-radius:50%; background:#8fc700; transform:translateY(1px); }
.ptlive li .muted { flex:none; font-variant-numeric:tabular-nums; font-size:12px; }
.ptlive li.pt-done { color:var(--ink-2); }
.ptlive li.pt-now::before { background:#3394f0; }
.ptlive li.pt-now.pt-last::before { box-shadow:0 0 0 3px rgba(51,148,240,.25); animation:ptled 1.2s ease-in-out infinite; }
.ptlive li.pt-wait::before { background:#e0a100; }
.ptlive li.pt-wait { font-weight:600; }
.ptlive li.pt-bad::before { background:#e5483a; }
@media (prefers-reduced-motion: reduce) { .ptlive li.pt-now.pt-last::before { animation:none; } }

/* ================================================== phones and tablets (Phase 0)
 * The mobile plan "NPT on phones and tablets", started 19/10/2026. Dominik: a
 * phone app with most of NPT in it, used mostly for deciding, Tasks and
 * approvals first. phone.js wires what is here.
 *
 * EVERY RULE SITS IN A WIDTH QUERY. From 1600 px NPT is exactly as it was: the
 * elements added for the small sizes (.menubtn, .screenname, .phonebar,
 * .appmenu) are display:none there, and nothing else is touched outside a
 * query. Four sizes:
 *   1600 px and up   as before
 *   1024-1599 px     laptop: the tabs stay; the top bar lets five things go to Menu
 *   600-1023 px      tablet: the tabs and most of the top bar fold into Menu
 *   under 600 px     phone: as the tablet, plus a bar of five at the foot
 */
:root { --phonebar-h:0px; }
.menubtn, .screenname, .phonebar, .setdone { display:none; }

/* Every screen and tool, sliding in from the right. Built by phone.js each time
   it opens, from the tabs and controls as they stand, so a hidden tab stays
   hidden here too. */
.appmenu {
  position:fixed; inset:0; z-index:70; display:flex; justify-content:flex-end;
  background:rgb(0 0 0 / 45%);
}
.appmenu[hidden] { display:none; }
body.appmenu-open { overflow:hidden; }
.appmenu-card {
  width:min(420px, 100vw); height:100%; display:flex; flex-direction:column;
  background:var(--surface); color:var(--ink); border-left:1px solid var(--rule-strong);
  box-shadow:-18px 0 40px -20px rgba(0,0,0,.6);
}
.appmenu-head {
  display:flex; align-items:center; gap:10px; min-height:var(--topbar-h);
  padding:calc(8px + env(safe-area-inset-top)) 14px 8px; border-bottom:1px solid var(--rule);
}
.appmenu-who { flex:1; min-width:0; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.appmenu-body { flex:1; overflow-y:auto; padding:4px 14px calc(18px + env(safe-area-inset-bottom)); }
.appmenu-body h3 {
  margin:18px 2px 8px; font-family:var(--mono); font-size:11px; font-weight:600;
  letter-spacing:.11em; text-transform:uppercase; color:var(--ink-3);
}
.appmenu-screens { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:6px; }
.appmenu-screen {
  appearance:none; font:inherit; font-size:15px; text-align:left; cursor:pointer;
  display:flex; align-items:center; justify-content:space-between; gap:6px;
  min-height:48px; padding:10px 12px; border-radius:6px;
  color:var(--ink); background:var(--surface-2); border:1px solid var(--rule);
}
.appmenu-screen[aria-current="page"] { border-color:var(--brand); box-shadow:inset 3px 0 0 var(--brand); font-weight:600; }
.appmenu-tools { display:flex; flex-direction:column; }
.appmenu-tool {
  appearance:none; font:inherit; font-size:15px; text-align:left; cursor:pointer;
  min-height:56px; padding:9px 2px; color:var(--ink);
  background:none; border:none; border-bottom:1px solid var(--rule);
}
.appmenu-tool .muted { font-size:13px; }
.appmenu-foot { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:18px; font-size:12.5px; }

/* An explanation shown by touch (phone.js, wireTips): the title text of what
   was held, in a bubble above it. */
.tipbubble {
  position:fixed; z-index:90; max-width:min(320px, calc(100vw - 16px));
  padding:8px 11px; border-radius:6px; font-size:13.5px; line-height:1.45;
  background:var(--ink); color:var(--surface); pointer-events:none;
  box-shadow:0 10px 30px -10px rgba(0,0,0,.6);
}
@media (hover: none) { [title] { -webkit-touch-callout:none; } }

/* ------------------------------------------------------- laptops */
/* Dominik, 19/10/2026: "Fix the desktop top bar". It needs about 1,550 px, so
   below that it ran past the right edge (to 1,363-1,431 px at 1024-1440) and
   squeezed the NPT mark to nothing below 1,440. Here the tabs stay; the
   strapline, How we do this, ?, Beta notes and the name with Sign out go
   behind Menu, and the health line keeps its light and the JobWatch age. */
@media (min-width:1024px) and (max-width:1599px) {
  .brand { flex-shrink:0; }
  .sub { display:none; }
  .menubtn { display:inline-flex; align-items:center; }
  #procbtn, #qabtn, #betabtn, .topright > .who { display:none; }
  .health .hl-db, .health .hl-db + .sep { display:none; }
}

/* ---------------------------------------------------- tablets and phones */
@media (max-width:1023px) {
  /* The tabs fold into Menu, so the frozen header is the top bar alone, and
     everything pinned under it (--header-h) moves up with it. */
  :root { --tabs-h:0px; }
  .tabs { display:none; }
  .topbar { gap:10px; }
  .topright { gap:8px; }
  .sub { display:none; }
  .screenname {
    display:block; min-width:0; font-size:15px; font-weight:600;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .menubtn { display:inline-flex; align-items:center; }
  /* Offered in Menu instead. Hidden, not removed: Menu presses these. */
  #askbtn, #procbtn, #qabtn, #betabtn, #cleansebtn, #settingsbtn, .topright > .who, .topright > .health { display:none; }
  .topsearch { flex:0 1 240px; min-width:0; }
  /* Settings opens from Menu, so it hangs from the top bar's corner, covers
     most of the screen and closes with its own Done. */
  .settingsmenu {
    position:fixed; top:calc(var(--topbar-h) + 6px); right:8px;
    max-height:calc(100dvh - var(--topbar-h) - var(--phonebar-h) - 20px); overflow-y:auto;
  }
  .setdone { display:flex; justify-content:flex-end; padding:4px 4px 0; }
  /* The procedures dock covers the page instead of pushing it aside: there is
     no room beside a 768 px page for a 330 px column. */
  body.has-dock { padding-right:0; }
}

/* A finger needs 44 px. Touch screens below 1024 px only: a touch laptop at
   desktop width keeps the desktop's sizes (the plan's rule that the desktop
   does not change). Small buttons inside table rows get 40 px for now; each
   screen's own phase lays its rows out for touch. */
@media (max-width:1023px) and (pointer: coarse) {
  button.btn, a.btn, button.btn.small, a.btn.small { min-height:44px; padding-left:14px; padding-right:14px; }
  a.btn { display:inline-flex; align-items:center; }
  button.btn.tiny, a.btn.tiny { min-height:40px; padding-left:11px; padding-right:11px; }
  /* 16 px stops an iPhone zooming the page every time a box is tapped. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea { min-height:44px; font-size:16px; }
  input[type="checkbox"], input[type="radio"] { width:22px; height:22px; }
}

/* ------------------------------------------------------------------ phones */
@media (max-width:599px) {
  :root { --phonebar-h:calc(58px + env(safe-area-inset-bottom)); }
  .phonebar {
    display:flex; position:fixed; left:0; right:0; bottom:0; z-index:50;
    height:var(--phonebar-h); padding-bottom:env(safe-area-inset-bottom);
    background:var(--surface); border-top:1px solid var(--rule-strong);
  }
  .phonebar button {
    flex:1 1 0; min-width:0; position:relative; cursor:pointer;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
    appearance:none; background:none; border:none; color:var(--ink-2); font:inherit; font-size:11.5px;
  }
  .phonebar svg { width:24px; height:24px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
  .phonebar button[aria-current="page"], .phonebar button[aria-pressed="true"] { color:var(--ink); font-weight:600; }
  .phonebar button[aria-current="page"]::before {
    content:''; position:absolute; top:0; left:28%; right:28%; height:3px;
    border-radius:0 0 3px 3px; background:var(--brand);
  }
  .phonebar .betacount { position:absolute; top:5px; left:calc(50% + 7px); margin:0; }

  /* Room at the foot for the bar, and the toast above it. */
  main { padding:14px 12px calc(var(--phonebar-h) + 24px); }
  .toast { bottom:calc(var(--phonebar-h) + 12px); }

  /* The top bar keeps the mark and the section's name; Waiting, Notices and
     Menu are on the bar or in More. Searching is the magnifier in the top bar,
     which opens Jump to over the whole screen (proposal 9). */
  .menubtn, #waitingbtn, #newsbtn { display:none; }
  .topsearch { display:none; }

  /* Panels pinned to the screen's edges stop above the bar and take its width. */
  .askpanel, .ptdrawer, .projpanel, #procdock { bottom:var(--phonebar-h); }
  #procdock { width:100vw; }

  /* Dialogs take the whole screen: each has its own close (.dlg-x). */
  dialog.npt-dialog, dialog.npt-dialog.wide {
    width:100vw; max-width:100vw; height:100dvh; max-height:100dvh; margin:0; border-radius:0; border:none;
  }
  /* Panels laid over the page (Waiting on you, Beta notes, procedures, what's
     new) fill it below the top bar; the strip of veil left over the top bar is
     what closes them, as a tap beside the box does on a computer. */
  .movepanel { padding:var(--topbar-h) 0 0; align-items:stretch; }
  .movepanel > div, .movepanel.wide > div, .movepanel .betapanel {
    width:100vw; max-width:100vw; max-height:none; height:calc(100dvh - var(--topbar-h));
    border-radius:0; border-left:none; border-right:none;
  }
  .appmenu-card { width:100vw; border-left:none; }
  .settingsmenu { left:8px; right:8px; width:auto; }

  /* The Sheet's new-project row puts its buttons under the box instead of
     pushing the page sideways. The Sheet itself is Phase 2. */
  .sheetbar .f.newgr { flex:1 1 100%; }
  .sheetbar .inl { flex-wrap:wrap; }
  .sheetbar .inl input { flex:1 1 100%; }

  /* PHASE 1, TASKS AND APPROVALS FIRST (Dominik, 19/10/2026). A list marked
     table.stack becomes a card per row: the first cell on its own lines, the
     small cells side by side under it, each with its column's name above it
     (data-label). The To do list on Tasks, Waiting on you, and Portals'
     Proposals so far. Tablets and computers keep the table. */
  table.stack > thead { display:none; }
  table.stack, table.stack > tbody, table.stack > tbody > tr, table.stack > tbody > tr > td { display:block; width:auto; }
  table.stack > tbody > tr { padding:12px 4px 14px; border-bottom:1px solid var(--rule); }
  table.stack > tbody > tr > td { border:none; padding:2px 0; text-align:left; }
  table.stack > tbody > tr > td.shrink { display:inline-block; vertical-align:top; margin:6px 16px 0 0; }
  table.stack > tbody > tr > td[data-label]::before {
    content:attr(data-label); display:block; margin-bottom:2px;
    font-family:var(--mono); font-size:10.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-3);
  }
  table.stack > tbody > tr > td:empty { display:none; }
  .tablewrap:has(> table.stack) { background:none; }

  /* A notice across the top takes three lines on a phone; a tap on it opens the
     rest (phone.js). It used to fill the first screen of every page. */
  .banner-text { display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; cursor:pointer; }
  .banner.open .banner-text { display:block; -webkit-line-clamp:unset; }
  .banner-who { display:none; }
}

/* PHASE 1, A PROJECT ON A PHONE (20/10/2026). What a Teams link opens: the
   header wraps instead of running off the side, the work comes first and the
   conversation straight after it (detail-main's panels and detail-side share
   one column, ordered), the editing rows fold (phone.js), and tables are cards
   (phone.js marks them table.stack). */
@media (max-width:599px) {
  .projpanel .detail-wrap { padding:0 8px 6px; }
  .projpanel .projbar { flex-wrap:wrap; gap:8px 10px; }
  .projpanel .projbar-who { flex:1 1 calc(100% - 60px); }
  .projpanel .projbar-sub .inline-name { width:100%; }
  .projpanel .projbar-figs { flex:1 1 100%; flex-wrap:wrap; white-space:normal; gap:6px 10px; }
  .projpanel .projbar-figs .chip { white-space:normal; }
  .projpanel .projbar > .btn { flex:0 0 auto; }

  .projpanel .detail-grid { display:flex; flex-direction:column; gap:12px; }
  .projpanel .detail-main { display:contents; }
  .projpanel .detail-main > * { order:4; }
  .projpanel .detail-main > :nth-child(1) { order:1; }
  .projpanel .detail-main > :nth-child(2) { order:2; }
  .projpanel .detail-side { order:3; position:static; }
  .projpanel .detail-side .feed { max-height:70vh; }

  details.phone-more { margin:4px 0 10px; }
  details.phone-more > summary { cursor:pointer; padding:10px 2px; color:var(--ink-2); font-size:14px; }
  details.phone-more[open] > summary { color:var(--ink); }

  /* Cards made from a table the screen did not mark itself. */
  table.stack-auto > tbody > tr > td { white-space:normal; }
  table.stack-auto > tbody > tr > td[data-label="#"]::before { display:inline; margin-right:2px; }
  table.stack-auto > tbody > tr > td .btn { margin:4px 6px 0 0; }
  table.stack-auto > tbody > tr > td input:not([type="checkbox"]):not([type="radio"]), table.stack-auto > tbody > tr > td select { max-width:100%; }
  .projpanel .tablewrap, .wkwrap { overflow-x:visible; }
  /* Help: beside two buttons a guide's description was a word a line. */
  main[data-view="help"] .panel table tr { display:block; border-bottom:1px solid var(--rule); }
  main[data-view="help"] .panel table td { display:block; border:0; }
  main[data-view="help"] .panel table td.shrink { padding-top:0; }
}

/* PHASE 2, THE SHEET ON A TOUCH SCREEN (20/10/2026). A tablet keeps the grid:
   what showed only under a mouse (note, + phase, ×) is always there, and the
   boxes are big enough for a finger. */
@media (hover:none) {
  table.trk td.sc .addnote { visibility:visible; position:static; margin-left:4px; }
  table.trk td.phinv .addph, table.trk td.phinv .delph { visibility:visible; }
}
@media (pointer:coarse) and (min-width:600px) {
  table.trk input.cell, table.trk select.cell { min-height:34px; }
  table.trk td.phinv .delph, table.trk td.phinv .warnbtn { min-width:30px; min-height:30px; }
}

/* PHASE 2, THE SHEET ON A PHONE. A card per project (sheet.js phoneCard): the
   customer and status, the project's name as its own box, the group job and
   PO, then each line with its phases — orange due this week or next, red late,
   green invoiced, as on the grid — and the total with the project's Invoiced…. */
@media (max-width:599px) {
  .sheetpanel.phone .zoom, .sheetpanel.phone label.tick:has(#shmoney) { display:none; }
  .sheetpanel.phone .sheetbar { gap:10px; }
  .sheetpanel.phone .legend { display:flex; flex-wrap:wrap; gap:6px; }
  .sheetpanel.phone .chip { min-height:30px; }
  .phonesheet { display:flex; flex-direction:column; gap:12px; padding:10px 0 4px; }
  .phonesheet > .empty { margin:8px 4px; }

  .pcard { border:1px solid var(--rule-strong); border-left:5px solid var(--lv, var(--rule-strong)); border-radius:6px;
    background:var(--surface); display:flex; flex-direction:column; min-width:0; }
  .pcard.pencilled { border-style:dashed; border-left-style:solid; }
  .pcard.flash { animation:trkflash 2.4s ease-out; }
  .pcard > * { padding:0 12px; }
  .pc-head { padding-top:10px; display:flex; flex-direction:column; gap:4px; background:var(--lv-soft, transparent); padding-bottom:8px; }
  .pc-who { display:flex; gap:8px; align-items:center; justify-content:space-between; }
  .pc-cust { font-weight:700; overflow-wrap:anywhere; }
  .pc-head .pname { width:100%; font-size:16px; font-weight:600; }
  .pc-site { font-size:12.5px; }
  .pc-gr { padding-top:8px; display:flex; flex-direction:column; gap:6px; }
  .pc-gr.miss { background:var(--trk-miss); padding-bottom:8px; }
  .pc-gr .cell { width:100%; }
  .pc-gr .grone { display:flex; flex-direction:column; gap:4px; }
  .pc-gr .addgr { align-self:flex-start; }
  .pc-pencil, .pc-bar { padding-top:8px; display:flex; gap:8px; flex-wrap:wrap; align-items:center; }

  /* The customer's PO under the group job, coloured as on the grid. */
  .pcard .pobox { padding:6px 8px; border:1px solid var(--rule-strong); border-radius:4px; display:flex; flex-direction:column; gap:4px;
    font-size:13px; background:var(--surface); }
  .pcard .pobox .pohead { display:flex; gap:10px; align-items:baseline; flex-wrap:wrap; }
  .pcard .pobox .postate { font-weight:700; font-size:12px; letter-spacing:.02em; }
  .pcard .pobox .poacts { display:flex; gap:14px; flex-wrap:wrap; }
  .pcard .pobox .podiff { color:var(--warn); }
  .pcard .pobox .poasked { color:var(--ink-3); font-size:12px; }
  .pcard .pobox.po-red { background:var(--trk-late); border-color:var(--trk-late-edge); }
  .pcard .pobox.po-red .postate { color:var(--crit); }
  .pcard .pobox.po-orange { background:var(--trk-soon); border-color:var(--trk-soon-edge); }
  .pcard .pobox.po-orange .postate { color:var(--warn); }
  .pcard .pobox.po-green { background:var(--trk-paid); border-color:var(--ok); }
  .pcard .pobox.po-green .postate { color:var(--ok); }
  .pcard .pobox.po-ask { border-style:dashed; background:var(--surface-2); }
  .pcard .pobox.po-not_needed { border-color:transparent; background:transparent; padding-left:0; }
  .pcard .pobox.po-not_needed .postate { color:var(--ink-3); font-weight:400; }

  /* A line, its phases, and its boxes under "Change this line". */
  .pc-lines { padding:0; margin-top:10px; border-top:1px solid var(--rule); }
  .pline { padding:10px 12px; border-bottom:1px solid var(--rule); display:flex; flex-direction:column; gap:6px; }
  .pline.lpaid { background:var(--trk-paid); }
  .pline.cancelled .pl-scope { text-decoration:line-through; color:var(--ink-3); }
  .pl-head { display:flex; gap:8px; align-items:baseline; }
  .pl-head .seq { border:0; background:none; padding:0 2px; font:inherit; font-size:12px; color:var(--ink-3); min-width:22px; min-height:24px; text-align:left; }
  .pl-scope { flex:1 1 auto; min-width:0; font-weight:600; overflow-wrap:anywhere; }
  .pl-sum { font-variant-numeric:tabular-nums; white-space:nowrap; }
  .pl-meta { font-size:12.5px; color:var(--ink-2); }
  .pl-meta .jwtag { font-size:11px; color:var(--st-plan); font-weight:600; }
  .pl-miss { display:inline-block; margin-left:4px; padding:0 5px; border-radius:3px; background:var(--trk-miss); border:1px solid var(--trk-miss-edge); color:var(--ink); }
  .pline .lnote { padding:4px 8px; border-radius:3px; font-size:12.5px; color:var(--warn); background:var(--warn-soft); }
  .pline .noteedit { display:flex; flex-direction:column; gap:6px; }
  .pline .noteedit textarea { width:100%; font:inherit; font-size:15px; }
  .pline .noteedit .offtick { display:flex; gap:8px; align-items:center; font-size:13px; }
  .pline .offtag { font-size:11px; font-weight:700; text-transform:uppercase; color:var(--ink-3); }

  .pphases { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
  .pph { display:grid; grid-template-columns:1fr auto; gap:2px 10px; align-items:center; padding:6px 8px; border-radius:4px;
    background:var(--surface-2); border:1px solid var(--rule); }
  .pph.soon { background:var(--trk-soon); border-color:var(--trk-soon-edge); }
  .pph.late { background:var(--trk-late); border-color:var(--trk-late-edge); }
  .pph.late .pph-when { color:var(--crit); font-weight:600; }
  .pph.soon .pph-when { font-weight:600; }
  .pph.paid { background:var(--trk-paid); border-color:var(--ok); }
  .pph-what { overflow-wrap:anywhere; }
  .pph-sum { font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap; }
  .pph-when { font-size:12.5px; color:var(--ink-2); }
  .pph-act { display:flex; gap:6px; align-items:center; justify-content:flex-end; flex-wrap:wrap; }
  .pph-act .phdone { font-weight:600; color:var(--accent-ink); }
  .pph-act .jwok { color:var(--ok); font-size:11.5px; }
  .pph-act .jwbad { color:var(--crit); font-weight:600; font-size:11.5px; }
  .pph-act .jwwait { color:var(--ink-3); font-size:11.5px; }
  .pph-act .warnbtn, .pph-act .delph { border:0; background:none; font-size:18px; line-height:1; min-width:34px; min-height:34px; cursor:pointer; }
  .pph-act .warnbtn { color:var(--warn); }
  .pph-act .delph { color:var(--ink-3); }
  .pl-acts { display:flex; gap:16px; }
  .pl-acts .linkish { min-height:30px; }

  .pl-change > summary { cursor:pointer; color:var(--accent-ink); font-size:13.5px; min-height:32px; display:flex; align-items:center; }
  .pl-form { display:flex; flex-direction:column; gap:10px; padding:6px 0 4px; }
  .pl-form label, .pslot label { display:flex; flex-direction:column; gap:3px; font-size:12.5px; color:var(--ink-2); }
  .pl-form label.miss { background:var(--trk-miss); padding:4px 6px; border-radius:4px; }
  .pl-form .cell { width:100%; font-size:16px; min-height:40px; }
  .pslot { border:1px solid var(--rule-strong); border-radius:4px; padding:6px 8px 8px; margin:0; display:flex; flex-direction:column; gap:8px; }
  .pslot.miss { background:var(--trk-miss); border-color:var(--trk-miss-edge); }
  .pslot legend { font-size:12px; color:var(--ink-3); padding:0 4px; }
  .pl-jw { margin:0; font-size:12.5px; }
  .pl-more { display:flex; justify-content:flex-end; }

  .pc-foot { padding-top:10px; padding-bottom:12px; display:flex; flex-direction:column; gap:6px; }
  .pc-total { display:flex; gap:10px; align-items:baseline; }
  .pc-total strong { font-variant-numeric:tabular-nums; }
  .pc-total .linkish { margin-left:auto; min-height:30px; }
  .pc-inv { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:13.5px; }
  .pc-inv .readywhy { font-size:12.5px; color:var(--warn); flex-basis:100%; }
  .pc-inv .readywhy.ready { color:var(--ok); font-weight:600; }

  /* A new project: one box at a time over the whole screen, the guide under it. */
  .phonedraft { position:fixed; inset:0; z-index:60; overflow:auto; background:var(--surface);
    display:flex; flex-direction:column; gap:14px; padding:calc(var(--topbar-h, 56px) * .4) 16px calc(16px + env(safe-area-inset-bottom)); }
  .phonedraft.pencil { background:var(--surface-2); }
  .phonedraft .pd-fields { display:flex; flex-direction:column; gap:12px; }
  .phonedraft .dfield { display:flex; flex-direction:column; gap:6px; font-size:14px; font-weight:600; }
  .phonedraft .dfield:not(:has(.dcur)) { display:none; }
  .phonedraft .dcell { width:100%; font-size:17px; min-height:46px; font-weight:400; }
  .phonedraft .draftguide { position:static; transform:none; width:auto; box-shadow:none; border:0; padding:0; background:transparent; font-size:15px; }
  .phonedraft .draftguide .dg-btns .btn { min-height:44px; padding:0 16px; }
  .phonedraft .draftguide .dg-top [data-dcancel] { min-height:40px; }
  .phonedraft .draftguide .ddot { width:12px; height:12px; }
  .phonedraft .draftguide .dg-rem { flex-direction:column; align-items:stretch; }
  .phonedraft .draftguide .dg-rem label { display:flex; flex-direction:column; align-items:stretch; }
  .phonedraft .draftguide .dg-rem select, .phonedraft .draftguide .dg-rem input { font-size:16px; min-height:42px; }
  body:has(.phonedraft) { overflow:hidden; }
  .pcard .linkish { background:none; border:0; padding:0 2px; font:inherit; font-size:14px; color:var(--accent-ink);
    text-decoration:underline; cursor:pointer; min-height:32px; }
  /* A box's ✕ sits in the corner: its heading keeps clear of it, so the line under starts below it (dlg-x clears). */
  dialog.trkdlg > h3 { min-height:48px; padding-right:56px; }
}

/* THE WEEK IN A POP-UP (Timesheets, 20/10/2026). A person's week over the grid,
   the week three ways for approving it, and the grid's Hours-from switch. */
.movepanel.tspop { padding-top:3vh; z-index:46; }
.movepanel.tspop > .tspop-box { width:min(1280px, 97vw); max-height:93vh; overflow-y:auto; }
/* Not a .panel: app.js folds every .panel by its heading, and a pop-up that
   opens shut is no pop-up. The panel's look, without the fold. */
.tspop-panel { background:var(--surface); }
.tspop-panel > .panel-head { padding:11px 16px; border-bottom:1px solid var(--rule); background:var(--surface-2);
  font-family:var(--mono); font-size:11px; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-2); font-weight:600; }
.tspop-panel > .panel-head { display:flex; align-items:center; gap:12px; position:sticky; top:0; z-index:3; }
.tspop-panel > .panel-head .tspop-who { font-family:inherit; text-transform:none; letter-spacing:0; font-weight:400; color:var(--ink-3); font-size:12.5px; }
.tspop-panel > .panel-head .tspop-x { margin-left:auto; }
body:has(.movepanel.tspop) { overflow:hidden; }
.tsapprove { margin:14px 0 4px; border:1px solid var(--rule); border-radius:6px; overflow:hidden; }
.tsapprove .ap-head { padding:8px 12px; background:var(--surface-2); font-size:13px; }
.tsapprove table.ap-table { width:100%; font-size:13px; border-collapse:collapse; }
.tsapprove .ap-table th, .tsapprove .ap-table td { padding:6px 10px; border-top:1px solid var(--rule); vertical-align:top; text-align:left; }
.tsapprove .ap-table .n { text-align:right; font-variant-numeric:tabular-nums; }
.tsapprove .ap-table .ts-span { font-size:11.5px; font-weight:400; }
.tsapprove .ap-table tr.ap-unsettled > * { background:var(--warn-soft); }
.tsapprove .ap-table tr.ap-none > * { color:var(--ink-3); }
.tsapprove .ap-table tfoot th, .tsapprove .ap-table tfoot td { border-top:2px solid var(--rule-strong); }
.tsapprove .ap-act { white-space:nowrap; text-align:right; }
.tsapprove .ap-foot { padding:10px 12px; display:flex; gap:10px; align-items:center; flex-wrap:wrap; border-top:1px solid var(--rule); font-size:13px; }
.tsfrom { display:flex; gap:6px; align-items:center; flex-wrap:wrap; padding-top:10px; padding-bottom:10px; }
.tsfrom button[aria-pressed="true"] { background:var(--accent-soft); border-color:var(--brand); color:var(--accent-ink); font-weight:600; }
.tsgrid td.tscell .tsdiff { font-size:11.5px; }
.tsgrid td.tscell .tsdiff.up { color:var(--warn); }
.tsgrid td.tscell .tsdiff.down { color:var(--ink-3); }
.tsgridwrap.catching-up { opacity:.55; transition:opacity .2s; }
.tspop-box.loading { pointer-events:none; }
.tspop-box.loading > * { opacity:.6; transition:opacity .15s; }
@media (max-width:599px) {
  .movepanel.tspop { z-index:55; padding:var(--topbar-h) 0 0; }
  .movepanel.tspop > .tspop-box { width:100vw; max-height:none; height:calc(100dvh - var(--topbar-h)); border-radius:0; border:0; }
  .tspop-panel > .panel-head { flex-wrap:wrap; }
}

/* ================================================== PROPOSAL 9 (21/10/2026)
 *
 * "NPT, easier to use", approved by Dominik from the mock-up: five sections
 * instead of twenty-one tabs, one line of introduction, badges red only for
 * what is yours, Tasks with the list first, small rewards, and Ctrl+K.
 *
 * THE ROW UNDER THE TOP BAR carries the sections on the left, the screens of
 * the one that is on beside them, and Setup at the right-hand end. The same
 * height as the tab strip it replaces (--tabs-h), so everything pinned under
 * it stays where it was. */
.tabs { align-items:center; gap:0; scrollbar-width:thin; }
.tabs .sections {
  display:flex; gap:4px; align-items:center; flex:none;
  padding-right:12px; margin-right:8px; border-right:1px solid var(--rule);
}
.tabs .sections button {
  appearance:none; margin:0; border:1px solid transparent; border-radius:5px; background:none;
  color:var(--ink-2); font:inherit; font-size:14px; font-weight:600; padding:5px 13px;
  white-space:nowrap; cursor:pointer;
}
.tabs .sections button:hover { color:var(--ink); background:var(--surface-2); }
.tabs .sections button[aria-current] { background:var(--brand); color:var(--on-accent); }
/* The section that is on shows its screens, and Tasks there carries the count. */
.tabs .sections button[aria-current] .betacount { display:none; }
/* Never squeezed: where the row is too narrow it scrolls sideways (sections.js
   keeps the screen that is on in view), rather than the gear landing on a tab. */
.subtabs { display:flex; gap:2px; flex:none; }
.subtabs button.offsec { display:none; }
.tabs .setupbtn {
  appearance:none; margin:0 0 0 auto; flex:none; display:inline-flex; align-items:center; gap:6px;
  border:1px solid transparent; border-radius:5px; background:none; color:var(--ink-3);
  font:inherit; font-size:13px; padding:5px 10px; white-space:nowrap; cursor:pointer;
}
.tabs .setupbtn svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.tabs .setupbtn:hover { color:var(--ink); background:var(--surface-2); }
.tabs .setupbtn[aria-current] { color:var(--on-accent); background:var(--brand); }

/* RED ONLY FOR WHAT IS YOURS. A count that is everybody's (Data cleanse's
   unowned items) is grey; "3 yours" beside it is the red one. */
.betacount.quietcount { background:var(--surface-3); color:var(--ink-2); }
.betacount + .betacount { margin-left:4px; }

/* ONE LINE OF INTRODUCTION, AND "MORE" (app.js trimIntro). */
/* Both aligned by their tops: an inline-block that hides its overflow takes
   its bottom edge as its baseline, which lifted "more" above the line. */
.lede.intro {
  display:inline-block; vertical-align:top; max-width:min(70ch, calc(100% - 4.5em));
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.lede.intro * { white-space:nowrap; }
.intro-more {
  appearance:none; vertical-align:top; margin:0 0 20px 6px; padding:0 2px; border:none; background:none;
  color:var(--accent-ink); font:inherit; font-size:14.5px; line-height:1.55; cursor:pointer;
  text-decoration:underline; text-underline-offset:2px;
}
.intro-more:hover { color:var(--ink); }
/* Beside a heading in a row (Projects, the Sheet), the line has no gap to keep. */
.lede.intro[style*="margin-bottom:0"] + .intro-more { margin-bottom:0; }

/* THE BOX IN THE TOP BAR opens Jump to; it says so, and says Ctrl K. */
.topsearch { position:relative; }
.topsearch input { padding-right:58px; cursor:pointer; }
.topsearch-key {
  position:absolute; right:7px; top:50%; transform:translateY(-50%); pointer-events:none;
  font-family:var(--mono); font-size:10.5px; line-height:17px; padding:0 5px;
  color:var(--ink-3); border:1px solid var(--rule-strong); border-radius:3px; background:var(--surface);
}
.jumpbtn { display:none; }
.jumpbtn svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; }

/* ------------------------------------------------------------ Jump to */
.jumpveil {
  position:fixed; inset:0; z-index:80; display:flex; justify-content:center; align-items:flex-start;
  padding:12vh 16px 16px; background:rgb(0 0 0 / 45%);
}
body.jump-open { overflow:hidden; }
.jump {
  width:min(720px, 100%); max-height:76vh; display:flex; flex-direction:column; overflow:hidden;
  background:var(--surface); color:var(--ink); border:1px solid var(--rule-strong); border-radius:10px;
  box-shadow:0 24px 60px -20px rgba(0,0,0,.6);
}
.jump-in { display:flex; align-items:center; gap:10px; padding:12px 14px; border-bottom:1px solid var(--rule); }
.jump-in svg { width:20px; height:20px; flex:none; fill:none; stroke:var(--ink-3); stroke-width:2; stroke-linecap:round; }
.jump-in input {
  flex:1; min-width:0; border:none; outline:none; background:none; color:var(--ink);
  font:inherit; font-size:18px; padding:4px 0;
}
.jump-in input::-webkit-search-cancel-button { display:none; }
.jump-list { overflow-y:auto; padding:6px 0 8px; }
.jump-group {
  padding:10px 16px 4px; font-family:var(--mono); font-size:10.5px; font-weight:600;
  letter-spacing:.11em; text-transform:uppercase; color:var(--ink-3);
}
.jump-it {
  appearance:none; display:flex; align-items:baseline; gap:10px; width:100%; text-align:left;
  padding:8px 16px; border:none; border-left:3px solid transparent; background:none;
  color:var(--ink); font:inherit; font-size:14.5px; cursor:pointer;
}
.jump-it strong { font-weight:600; flex:none; max-width:60%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.jump-it span { color:var(--ink-3); font-size:13px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.jump-it[aria-selected="true"] { background:var(--accent-soft); border-left-color:var(--brand); }
.jump-it[aria-selected="true"]::after {
  content:'Enter'; margin-left:auto; flex:none; font-family:var(--mono); font-size:11px; color:var(--accent-ink);
}
.jump-foot {
  display:flex; gap:18px; flex-wrap:wrap; padding:8px 16px; border-top:1px solid var(--rule);
  font-size:12.5px; color:var(--ink-3);
}
.jump kbd, .jump-foot kbd {
  font-family:var(--mono); font-size:11px; padding:0 4px; border:1px solid var(--rule-strong);
  border-radius:3px; color:var(--ink-2); background:var(--surface-2);
}

/* ------------------------------------------------------- small rewards */
.toast.cheer {
  display:flex; align-items:center; gap:12px; padding:12px 18px 12px 14px;
  background:var(--surface); color:var(--ink); border:1px solid var(--brand); font-weight:400;
}
.toast.cheer b { display:block; font-weight:600; }
.toast .cheer-sub { display:block; color:var(--ink-2); font-size:13px; }
.cheer-tick {
  flex:none; width:30px; height:30px; border-radius:50%; background:var(--brand);
  display:inline-flex; align-items:center; justify-content:center;
}
.cheer-tick svg { width:18px; height:18px; fill:none; stroke:var(--on-accent); stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round; }
.toast.cheer.show .cheer-tick { animation:npt-pop .45s cubic-bezier(.2,1.6,.4,1); }
@keyframes npt-pop { 0% { transform:scale(.4); } 100% { transform:scale(1); } }
.topright .btn.allclear {
  opacity:1; background:var(--accent-soft); border-color:var(--brand); color:var(--accent-ink); font-weight:600;
}
.topright .btn.allclear::before { content:'✓ '; }
@media (prefers-reduced-motion: reduce) { .toast.cheer.show .cheer-tick { animation:none; } }

/* ------------------------------------------------- Tasks: the list first */
.quickadd {
  margin:0 0 16px; border:1px solid var(--rule-strong); border-radius:8px; background:var(--surface);
}
.quickadd:focus-within { border-color:var(--brand); }
.qa-line { display:flex; align-items:center; gap:8px; padding:6px 6px 6px 14px; }
.qa-plus { color:var(--ink-3); font-size:22px; line-height:1; }
.qa-line input {
  flex:1; min-width:0; border:none; outline:none; background:none; color:var(--ink);
  font:inherit; font-size:16px; padding:8px 0;
}
.qa-more { padding:4px 14px 14px; border-top:1px solid var(--rule); }
.taskchips { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin:0 0 14px; }
.fchip {
  appearance:none; font:inherit; font-size:13.5px; padding:5px 13px; border-radius:16px; cursor:pointer;
  border:1px solid var(--rule-strong); background:var(--surface); color:var(--ink-2);
}
.fchip:hover { color:var(--ink); border-color:var(--ink-3); }
.fchip[aria-pressed="true"] { background:var(--accent-soft); border-color:var(--brand); color:var(--accent-ink); font-weight:600; }
.fchip.due:not([aria-pressed="true"]) { border-color:var(--crit); color:var(--crit); }
.fchip-tick { display:inline-flex; align-items:center; gap:6px; margin-left:6px; font-size:13.5px; color:var(--ink-2); cursor:pointer; }

/* A laptop: Work's eight screens, the four sections and Setup fit from about
   1,030 px with the gear on its own and the tabs a little closer. */
@media (max-width:1279px) {
  .tabs .setupbtn span { display:none; }
  .subtabs button { padding-left:10px; padding-right:10px; }
}

/* -------------------------------------------- tablets and phones (< 1024) */
@media (max-width:1023px) {
  /* The row comes back: sections and the current section's screens, scrolling
     sideways if they do not fit. Menu has every screen as well. */
  :root { --tabs-h:41px; }
  /* No bar under it, so it keeps its height: a finger or a trackpad scrolls it,
     the screen that is on is kept in view, and Menu has every screen. */
  .tabs { display:flex; scrollbar-width:none; }
  .tabs::-webkit-scrollbar { display:none; }
  .topsearch-key { display:none; }
  .topsearch input { padding-right:10px; }
}

/* ------------------------------------------------------- phones (< 600) */
@media (max-width:599px) {
  /* The sections are the bar at the foot, so the row is the current section's
     screens as chips — as the mock-up has it. */
  :root { --tabs-h:46px; }
  /* A finger scrolls it; no bar needed. */
  .tabs { gap:6px; padding:6px 12px; min-height:var(--tabs-h); scrollbar-width:none; }
  .tabs::-webkit-scrollbar { display:none; }
  .tabs .sections, .tabs .setupbtn { display:none; }
  .subtabs { gap:6px; }
  .subtabs button {
    flex:none; border:1px solid var(--rule-strong); border-radius:16px; margin:0;
    padding:5px 13px; font-size:13.5px; background:var(--surface);
  }
  .subtabs button[aria-selected="true"] { background:var(--accent-soft); border-color:var(--brand); color:var(--accent-ink); }
  .jumpbtn { display:inline-flex; align-items:center; justify-content:center; margin-left:auto; min-width:44px; }
  .phonebar button[aria-current="true"] { color:var(--ink); font-weight:600; }
  .phonebar button[aria-current="true"]::before {
    content:''; position:absolute; top:0; left:28%; right:28%; height:3px;
    border-radius:0 0 3px 3px; background:var(--brand);
  }
  .jumpveil { padding:0; align-items:stretch; }
  .jump { width:100vw; max-height:none; height:100dvh; border-radius:0; border:none; }
  .jump-foot { display:none; }
  .jump-it { padding:12px 16px; flex-wrap:wrap; gap:2px 10px; }
  .jump-it strong { max-width:100%; }
  .jump-it[aria-selected="true"]::after { display:none; }
  .qa-line { flex-wrap:wrap; padding:6px 10px; }
  .qa-line input { flex:1 1 calc(100% - 40px); }
  .qa-line .btn.primary { margin-left:auto; }
}

/* ------------------------------------------------ THE CLASSIC LAYOUT (21/10/2026)
 *
 * Settings → Layout → Classic (sections.js): NPT as it was before proposal 9.
 * Every tab along the row, the divider before the setting-up screens, no
 * section buttons or gear, the box searching what was said, and below 1024 px
 * the tabs folded into Menu again. */
.subtabs .tabgap { display:none; }
html[data-layout="classic"] .subtabs .tabgap { display:block; }
html[data-layout="classic"] .tabs .sections,
html[data-layout="classic"] .tabs .setupbtn,
html[data-layout="classic"] .topsearch-key,
html[data-layout="classic"] .jumpbtn { display:none; }
html[data-layout="classic"] .subtabs button { padding-left:14px; padding-right:14px; }
html[data-layout="classic"] .topsearch input { padding-right:10px; cursor:auto; }
.phonebar .classic-only { display:none; }
html[data-layout="classic"] .phonebar .p9-only { display:none; }
@media (max-width:1023px) {
  html[data-layout="classic"] { --tabs-h:0px; }
  html[data-layout="classic"] .tabs { display:none; }
}
@media (max-width:599px) {
  html[data-layout="classic"] .phonebar .classic-only { display:flex; }
  html[data-layout="classic"] .phonebar button[aria-current="page"] { color:var(--ink); font-weight:600; }
  html[data-layout="classic"] body.phone-search .topsearch {
    display:block; position:fixed; top:var(--topbar-h); left:0; right:0; z-index:31;
    margin:0; padding:8px 12px; background:var(--surface); border-bottom:1px solid var(--rule);
  }
  html[data-layout="classic"] body.phone-search .topsearch input { width:100%; }
}

/* 295. THE WEEK THREE WAYS, PRESSED (21/10/2026). The van, clock and estimate
   figures are buttons that take the day from that record; what was taken is
   lit green and fixed; the week's summary sits above the table. */
.tsapprove .ap-pick {
  appearance:none; width:100%; display:block; text-align:right; cursor:pointer;
  padding:3px 6px; margin:-3px -6px; border:1px solid transparent; border-radius:5px;
  background:none; color:inherit; font:inherit;
}
.tsapprove .ap-pick:hover { border-color:var(--brand); background:var(--accent-soft); }
.tsapprove .ap-pick:focus-visible { outline:2px solid var(--brand); outline-offset:1px; }
.tsapprove td.ap-taken { background:var(--ok-soft); }
.tsapprove .ap-pick.on { border-color:var(--brand); background:var(--ok-soft); }
.tsapprove .ap-pick.on:disabled { cursor:default; }
.tsapprove .ap-tick {
  display:block; margin-top:2px; font-family:var(--mono); font-size:10.5px; font-weight:600;
  letter-spacing:.04em; color:var(--accent-ink);
}
.tsapprove .ap-table tr.ap-fixed > th:first-child { box-shadow:inset 3px 0 0 var(--brand); }
.tsapprove .ap-table tr.ap-bulk > * { border-top:1px dashed var(--rule-strong); background:var(--surface-2); }
.tsapprove .ap-table tr.ap-bulk th { font-weight:400; color:var(--ink-3); }
.tsapprove .ap-sum {
  display:flex; gap:6px 16px; flex-wrap:wrap; align-items:baseline;
  padding:9px 12px; border-top:1px solid var(--rule); background:var(--warn-soft); font-size:13.5px;
}
.tsapprove .ap-sum.done { background:var(--ok-soft); }
.tsapprove .ap-sum.done strong { color:var(--accent-ink); }
.tsapprove .ap-sum .crit { color:var(--crit); }

/* 296. Inactive in JobWatch with no job in 60 days: listed for a look, not changed. */
.rsreview { margin:-8px 0 16px; font-size:13px; }
.rsreview summary { cursor:pointer; }
.rsreview ul { margin:6px 0 0; padding-left:18px; columns:2 220px; }

/* 297. Stays: somewhere to sleep near the work. */
.stbar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:0 0 14px; }
.stlinks { display:grid; gap:8px; grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); }
.stlink { display:flex; flex-direction:column; gap:2px; padding:9px 12px; border:1px solid var(--rule-strong);
  border-radius:8px; background:var(--surface); color:var(--ink); text-decoration:none; }
.stlink:hover, .stlink:focus-visible { border-color:var(--brand); background:var(--accent-soft); }
.stlink strong { font-size:14px; }
.stlink span { font-size:12px; color:var(--ink-3); }
.sttable td a, .stdetail a { color:var(--brand); }
.stbest { border-color:var(--accent-calm); }
.stbest > .panel-head { background:var(--ok-soft); }
.stwhy { list-style:none; margin:0; padding:0; display:grid; gap:4px; font-size:13.5px; }
.stwhy li { padding-left:22px; position:relative; }
.stwhy li::before { position:absolute; left:2px; top:0; font-weight:700; }
.stwhy li.ok::before { content:'✓'; color:var(--ok); }
.stwhy li.warn::before { content:'!'; color:var(--warn); left:6px; }
.stsorts { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.sttable tr.stmiss td { color:var(--ink-3); }
.stdetail { display:grid; gap:18px; grid-template-columns:repeat(auto-fit, minmax(260px, 1fr)); padding:6px 2px 10px; }
.stdetail h4 { margin:0 0 6px; font-size:13.5px; }
.stcrime { width:100%; font-size:13px; }
.stcrime td, .stcrime th { padding:2px 6px; }
.streview { margin:8px 0 0; padding:6px 10px; border-left:3px solid var(--warn); background:var(--surface-2); font-size:12.5px; }
.ststars { display:inline-flex; gap:4px; }
.ststars label { display:inline-flex; }
.ststars input { position:absolute; opacity:0; width:1px; height:1px; }
.ststars span { padding:4px 8px; border:1px solid var(--rule-strong); border-radius:6px; cursor:pointer; font-size:13px; }
.ststars input:checked + span { background:var(--brand); color:#fff; border-color:var(--brand); }
.ststars input:focus-visible + span { outline:2px solid var(--brand); outline-offset:2px; }
.stextras { border:0; padding:0; margin:0; display:flex; flex-wrap:wrap; gap:4px 12px; align-items:center; }
.stextras legend { font-size:12.5px; color:var(--ink-2); padding:0; margin-bottom:2px; width:100%; }
.stextras label { font-size:13px; display:inline-flex; gap:4px; align-items:center; }
