/* 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;

  --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;
}
@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;
  }
}

* { 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; }
.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; border-radius:5px 5px 0 0;
}
.panel-body { padding:16px; }

/* ------------------------------------------------------------------- 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); }
.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; }

button.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);
}
button.btn:hover { background:var(--surface-2); border-color:var(--ink-3); }
button.btn.primary {
  background:var(--brand); border-color:var(--brand); color:var(--on-accent); font-weight:600;
}
button.btn.primary:hover { filter:brightness(1.1); background:var(--brand); }
button.btn.primary:disabled { opacity:.45; cursor:not-allowed; filter:none; }
button.btn.danger { color:var(--crit); border-color:var(--crit); background:var(--crit-soft); }
button.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. */
tr.detail-row > td {
  padding:0 0 0 3px; background:var(--brand);
  border-bottom:2px solid var(--rule-strong);
}
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; }

/* 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; }
.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. */
.moneypanel .cell.n { min-width:92px; }
.moneypanel input[type=date].cell { min-width:140px; }
.moneypanel 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 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;
}
.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;
}
.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; }
.booking {
  font-size:12.5px; line-height:1.35; background:var(--surface-2);
  border-left:2px solid var(--brand); 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; }

/* 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); }
.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; }

.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); }
.health .synced.stale { color:var(--warn); font-weight:600; }

/* 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. */
.booking.linked { border-left:2px solid var(--brand); 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; }
