/* ACSL CRM — design system.
   Tokens taken verbatim from https://acsl.io/styles.css so the tool looks like
   it belongs to the company that built it. Dark-first: CRM users live in this
   app all day and a white screen at 7pm is a tax on their eyes. */

:root {
  /* Tells the browser this is a dark interface, so it draws its own controls
     to suit — the date picker's calendar icon, select menus, scrollbars and
     spin buttons.

     Without it those are rendered for a light background: the calendar button
     on a date field is dark grey on near-black and effectively invisible, so
     the field looks like a plain text box and you type the date by hand. The
     picker was always there; nobody could see the way in. */
  color-scheme: dark;

  --bg:         #050506;
  --bg-2:       #0b0c0e;
  --surface:    #111316;
  --surface-2:  #16191d;
  --border:     #23272d;
  --text:       #f4f5f6;
  --muted:      #9aa1a9;
  --dim:        #61686f;
  --accent:     #5ef38c;
  --accent-2:   #34d77d;
  --accent-ink: #04150b;
  --ring:       #5ef38c;

  --radius:    16px;
  --radius-sm: 10px;
  --ease: cubic-bezier(.22,.61,.36,1);

  --font-body:    'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-display: 'Space Grotesk', var(--font-body);

  /* Status colours. Chosen to sit on the near-black surfaces rather than
     borrowed from a light-mode palette — the usual traffic-light greens and
     reds vibrate badly against #111316. */
  --good:     #5ef38c;
  --good-bg:  rgba(94, 243, 140, 0.10);
  --warn:     #f5c26b;
  --warn-bg:  rgba(245, 194, 107, 0.10);
  --bad:      #ff7a7a;
  --bad-bg:   rgba(255, 122, 122, 0.10);
  --info:     #7db8ff;
  --info-bg:  rgba(125, 184, 255, 0.10);

  /* Whoever a task belongs to. One colour for everybody, deliberately: the
     name is written on the chip, so a colour per person carries no information
     and several of them down a list of thirty-four only compete for attention.
     Uniform here is also what lets the amber "Unassigned" chip stand out.

     Green, at Ben's request. Deeper than the accent so a person's name is not
     mistaken for a Won deal or a live retainer, which are the accent's job and
     sit on the same screens. */
  --who:      #3fd18a;
  --who-line: rgba(63, 209, 138, 0.38);
  --who-bg:   rgba(63, 209, 138, 0.13);

  /* Aliases for names the peek panel and diary were written against.

     They were never defined, so every use fell through to a hard-coded
     light-mode fallback — and `--muted-bg` fell back to #f5f6f8, which is why
     the message body in a pipeline card rendered as a large white rectangle
     with white text on it. The text was there the whole time; it was the same
     colour as the box drawn behind it.

     Defined here rather than corrected at each of the thirteen call sites, so
     anything written against these names later is right by default instead of
     inheriting a light theme nobody uses. */
  --muted-bg: var(--surface-2);
  --line:     var(--border);
  --ink:      var(--text);

  --sidebar-w: 232px;
  --header-h: 56px;
}

* { box-sizing: border-box; }

/* `hidden` means hidden, whatever else a class says.
 *
 * The browser's own rule is `[hidden] { display: none }`, and any author rule
 * with a class in it beats it. So `.clientform { display: inline-flex }` made
 * a form marked `hidden` in the template visible anyway — and every task row
 * on the dashboard permanently showed two dropdowns and two Save buttons that
 * were only ever meant to appear when you asked for them. The row was
 * unreadable and the due date was lost in the middle of it.
 *
 * One rule rather than a guard per component: `.sure-backdrop[hidden]` had
 * already been patched by hand, which is the sign of a fault that will keep
 * recurring. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
}

::selection { background: rgba(94, 243, 140, 0.25); }

:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ── Shell ─────────────────────────────────────────────────────────────── */

.shell { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--bg-2);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 18px 18px 14px;
}
.brand .mark {
  width: 26px; height: 26px;
  border-radius: 8px;
  flex: none;
  /* The logo carries its own dark face and green ring, so no gradient tile
     behind it — that would put the mark on a colour it was never drawn for. */
  display: block;
}
.brand .name {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.brand .env {
  font-size: 10px;
  color: var(--dim);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.nav { padding: 6px 10px 18px; }

/* Section headings.
   The pinned links and the grouped ones ran together as one long list — the
   headings were the same weight as everything else and only 4px of padding
   separated a heading from the item above it, so "Records" read as another
   place to click rather than a label for what follows.

   A hairline above each group and more air over the heading than under it does
   the separating. No boxes and no background: the sidebar is read constantly
   and out of the corner of the eye, and every extra edge in it is something to
   look past. */
.nav-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--dim);
  padding: 8px 8px 6px;
  margin-top: 14px;
  border-top: 1px solid var(--border);
}
/* The first heading needs no rule above it — the brand block already ends
   there, and two lines together read as a mistake. */
.nav > .nav-label:first-of-type,
.nav > .nav-group:first-of-type > .nav-label { border-top: 0; margin-top: 10px; }
.nav a {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 13px;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.nav a:hover { background: var(--surface); color: var(--text); }
.nav a.active {
  background: var(--surface-2);
  color: var(--text);
  box-shadow: inset 2px 0 0 var(--accent);
}
.nav a .count {
  margin-left: auto;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  padding: 1px 6px;
  line-height: 1.5;
}
.nav .ico { width: 15px; text-align: center; opacity: .75; flex: none; }

/* Items under a heading sit in from it, so the eye can see where a section
   starts and stops without needing a line down the side. */
.nav-group > a, .nav-label ~ a { padding-left: 14px; }

/* ── The dashboard's left column ───────────────────────────────────────── */

/* Contracts and subscriptions at the top, activity beneath and pushed down so
   the column finishes level with the tasks column beside it, rather than
   stopping short and leaving a ragged gap. */
/* The panes sit one under another and each takes the room it needs.
 *
 * The activity feed used to be pushed to the bottom with `margin-top: auto`,
 * so the column finished level with the one beside it — tidy when the panes
 * above were short, and a stretch of empty space between payments and the
 * feed the rest of the time. Ben asked for the feed to sit directly under
 * payments and let the column end where it ends; as payments fills up, it
 * takes the room and the feed moves down with it. */
.leftstack { display: flex; flex-direction: column; gap: 16px; }

/* The two panes sit side by side and stay equal height even when one is empty
   and the other is not — which, until the first contract is recorded, is the
   normal case. */
.leftstack .grid.cols-2 { align-items: stretch; }
.leftstack .grid.cols-2 > .panel { display: flex; flex-direction: column; }
.leftstack .grid.cols-2 > .panel > .body,
.leftstack .grid.cols-2 > .panel > .empty { flex: 1; }

/* Each pane grows with what is in it, up to a ceiling, and then scrolls
   inside itself.
   A ceiling rather than a fixed height: a pane with two rows should be two
   rows tall, not a tall box mostly full of nothing. And a ceiling at all,
   because forty payments in an uncapped pane would push the campaigns, the
   activity feed and everything else off the bottom of the screen — which is
   the opposite of a dashboard. */
.leftstack .panel > .table-wrap,
.leftstack .grid.cols-2 > .panel > .body { max-height: 320px; overflow-y: auto; }

/* The header stays put while the rows move under it, so the column you are
   reading keeps its label. */
.leftstack .panel > .table-wrap thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-2);
}


/* A denser table, for the dashboard panes where the point is the figures
   rather than the rows. */
.records.compact th, .records.compact td { padding: 6px 10px; font-size: 12.5px; }
/* Figures right-aligned and tabular, so a column of numbers can be read as a
   column rather than compared one at a time. */
.records .num, .records th.num {
  text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum";
}
.records .num.accent, .records .num b.accent { color: var(--accent); }

/* ── Compact layout ────────────────────────────────────────────────────── */

/* The activity feed, capped. It is a reference, not the point of the page, and
   left uncapped it made the dashboard three screens tall. */
.feed-capped { max-height: 420px; overflow-y: auto; }

/* Collapsible sidebar groups. The pinned items above them are deliberately
   outside this — Dashboard, Outreach patterns, Waiting on you and Diary are
   where the day starts and should never be behind a click. */
.nav-group { margin: 0; }
.nav-group > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; gap: 6px;
  user-select: none;
}
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after {
  content: "\203A";                 /* a chevron, rotated when open */
  margin-left: auto; font-size: 13px; color: var(--dim);
  transition: transform .15s var(--ease);
}
.nav-group[open] > summary::after { transform: rotate(90deg); }
.nav-group > summary:hover { color: var(--muted); }

.sidebar-foot {
  margin-top: auto;
  padding: 14px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--dim);
}
.sidebar-foot .who { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.sidebar-foot .who .name { color: var(--text); font-size: 12px; }
.sidebar-foot .who .role { color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  height: var(--header-h);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 22px;
  border-bottom: 1px solid var(--border);
  background: rgba(5, 5, 6, 0.85);
  backdrop-filter: blur(8px);
  position: sticky;
  top: 0;
  z-index: 40;
}
.topbar h1 { font-size: 16px; }
.topbar .sub { color: var(--dim); font-size: 12px; }
.topbar .spacer { flex: 1; }

.searchbtn {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: var(--radius-sm);
  padding: 5px 10px;
  font-size: 12px;
  cursor: pointer;
  font-family: inherit;
}
.searchbtn:hover { border-color: var(--dim); color: var(--text); }
.searchbtn kbd {
  font-family: var(--font-body);
  font-size: 10px;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 4px;
  color: var(--dim);
}

.content { padding: 22px; flex: 1; }

/* ── Avatars ───────────────────────────────────────────────────────────── */

.avatar {
  width: 24px; height: 24px;
  border-radius: 999px;
  display: grid; place-items: center;
  font-size: 10px; font-weight: 700;
  font-family: var(--font-display);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  flex: none;
}
.avatar.sm { width: 20px; height: 20px; font-size: 9px; }
/* Inside a name chip the initials take the chip's colour rather than the
   person's own accent — otherwise the pebble is green with a blue disc in it.
   The avatar keeps its per-person colour everywhere it stands alone, where
   the colour is the identifier. */
.chip.who .avatar { color: inherit; background: transparent; }

/* ── Cards & panels ────────────────────────────────────────────────────── */

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel > header {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}
.panel > header h2 { font-size: 13px; letter-spacing: 0; }
.panel > header .spacer { flex: 1; }
/* A panel that opens. Same bar as `.panel > header` so a foldable panel and a
   fixed one sit together without one looking half-built — this had no rule at
   all and rendered as bare text against the panel edge. */
.panel-summary {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
  font-size: 13px;
  cursor: pointer;
  list-style: none;
}
.panel-summary::-webkit-details-marker { display: none; }
.panel-summary::after {
  content: "›"; margin-left: auto; color: var(--dim);
  transition: transform .15s ease;
}
details[open] > .panel-summary::after { transform: rotate(90deg); }
.panel-summary:hover { background: var(--surface-2); }

.panel .body { padding: 16px; }
.panel .empty { padding: 22px 16px; color: var(--dim); font-size: 13px; text-align: center; }

.grid { display: grid; gap: 16px; }
.grid.cols-2 { grid-template-columns: 1fr 1fr; }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.grid.split { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
/* Five stats need to break earlier than four do, or the last two get squeezed
   until the figure wraps under its own label. */
@media (max-width: 1300px) {
  .grid.cols-5 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 1100px) {
  .grid.cols-3, .grid.cols-4, .grid.cols-5,
  .grid.split, .grid.cols-2 { grid-template-columns: 1fr; }
}

/* ── Stats ─────────────────────────────────────────────────────────────── */

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 15px 16px;
}
.stat .k {
  font-size: 10px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--dim);
}
.stat .v {
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin-top: 4px;
}
.stat .v.accent { color: var(--accent); }
.stat .v.warn   { color: var(--warn); }
.stat .v.bad    { color: var(--bad); }
.stat .note { font-size: 11px; color: var(--muted); margin-top: 2px; }

/* What is due to leave the account. Deliberately plain: it sits under the
   figures as their working, so it must not compete with them. */
.upcoming { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 13px; }
.upcoming td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--border); }
.upcoming tr:last-child td { border-bottom: 0; }
.upcoming .when { white-space: nowrap; width: 1%; }
.upcoming .when .dim { margin-left: 6px; font-size: 11.5px; }
.upcoming .rhythm { white-space: nowrap; width: 1%; font-size: 11.5px; }
/* Right-aligned and tabular so the column can be read as a column — figures
   that do not line up have to be compared one at a time. */
.upcoming .amt {
  text-align: right; white-space: nowrap; width: 1%;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum";
}

/* ── Buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
  padding: 6px 12px;
  cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease), transform .1s var(--ease);
}
.btn:hover { border-color: var(--dim); }
.btn:active { transform: translateY(1px); }
.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.btn.primary:hover { background: var(--accent-2); border-color: var(--accent-2); }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--bad); border-color: rgba(255,122,122,.35); background: transparent; }
.btn.danger:hover { background: var(--bad-bg); }
.btn.sm { font-size: 11.5px; padding: 4px 9px; }

/* ── Alerts ────────────────────────────────────────────────────────────── */

.alert {
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 13px;
  margin-bottom: 14px;
  border: 1px solid transparent;
}
.alert.good { background: var(--good-bg); border-color: rgba(94,243,140,.3); color: var(--good); }
.alert.warn { background: var(--warn-bg); border-color: rgba(245,194,107,.3); color: var(--warn); }
.alert.bad  { background: var(--bad-bg);  border-color: rgba(255,122,122,.3); color: var(--bad); }
.alert.info { background: var(--info-bg); border-color: rgba(125,184,255,.3); color: var(--info); }

/* ── Tables ────────────────────────────────────────────────────────────── */

table.records { width: 100%; border-collapse: collapse; font-size: 13px; }
table.records th {
  text-align: left;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--dim);
  font-weight: 600;
  padding: 9px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
  white-space: nowrap;
}
table.records th a { color: inherit; }
table.records th a:hover { color: var(--text); }
table.records td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
table.records tbody tr:hover { background: var(--surface-2); }
table.records tbody tr:last-child td { border-bottom: none; }
/* The name a row is about is content, not a signpost.
   Every link inherited the accent, so a dashboard of contracts, subscriptions
   and campaigns came out mostly green — and when everything is the accent
   colour, nothing is: the live-status pills and the figures worth noticing had
   to compete with every title on the page for the same attention.

   So the thing a row *is* reads as text, and turns green under the cursor,
   which is where the affordance actually needs to be. Links that take you
   somewhere else — a company named in a second column, "details", "All" —
   keep the accent, because there the colour is doing real work: it marks the
   cross-reference apart from the row it sits in. */
table.records td .title { font-weight: 500; color: var(--text); }
table.records td .title:hover { color: var(--accent); }
.table-wrap { overflow-x: auto; }

/* ── Chips & badges ────────────────────────────────────────────────────── */

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}
.chip.accent { color: var(--accent); border-color: rgba(94,243,140,.3); background: var(--good-bg); }
.chip.warn   { color: var(--warn);   border-color: rgba(245,194,107,.3); background: var(--warn-bg); }
.chip.bad    { color: var(--bad);    border-color: rgba(255,122,122,.3); background: var(--bad-bg); }
.chip.ai::before { content: "◆"; color: var(--accent); font-size: 9px; }

.stage {
  display: inline-block;
  font-size: 11px;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--muted);
  white-space: nowrap;
}
/* The open stages, coloured as a progression rather than as a set of unrelated
   labels. They all looked identical, so a list of twenty-three prospects gave
   no clue which were still cold and which were a signature away.
   Cool to warm as the deal gets closer to money — grey, blue, violet, amber,
   then the green already used for Won. That order is readable at a glance and
   in a fixed sequence, which a palette of arbitrary colours is not: you learn
   it once rather than looking it up each time. */
.stage.lead {
  color: var(--dim); border-color: var(--border); background: var(--bg-2);
}
.stage.contacted {
  color: #7db8ff; border-color: rgba(125,184,255,.35); background: rgba(125,184,255,.12);
}
/* Cyan, not violet. Violet was the natural next step down the funnel until
   purple became the colour of a company — and on Potential prospects the two
   sit in adjacent columns of the same row, so "Discovery booked" and the client
   token read as the same thing at a glance. A hue can carry one meaning or it
   can carry none. */
.stage.discovery_booked {
  color: #5ee1f3; border-color: rgba(94,225,243,.38); background: rgba(94,225,243,.13);
}
/* Booked and done are the same phase of the conversation, so they share a
   family rather than each taking a hue of their own: teal is cyan a step
   further on. Read together they say "this call is the current business, and
   here is which side of it we are on". */
.stage.discovery_done {
  color: #4fd6c9; border-color: rgba(79,214,201,.38); background: rgba(79,214,201,.13);
}
/* Amber for a proposal out and unanswered. It is the stage that goes stale
   fastest and the one worth chasing, so it is the one that catches the eye. */
.stage.proposal_sent {
  color: #f5c26b; border-color: rgba(245,194,107,.42); background: rgba(245,194,107,.14);
}
/* Deeper amber. Scope sent and Commercial sent are two halves of the same
   conversation, so they share a family the way Discovery booked and done do —
   and the warmer one is the half with money in it. */
.stage.commercial_sent {
  color: #f0a24a; border-color: rgba(240,162,74,.44); background: rgba(240,162,74,.15);
}
/* Lime, one step short of the green of Won — the same relationship Handover
   has to a live retainer on the delivery board. Nearly there, not there. */
.stage.awaiting_signature {
  color: #b8e986; border-color: rgba(184,233,134,.40); background: rgba(184,233,134,.13);
}

/* The delivery board, same idea and now the same palette: blue, cyan, amber,
   lime, then the green of a live retainer. Violet has left this ramp too — a
   project belongs to a company and the two appear side by side on every board
   card, which is the same collision in a smaller space. */
.stage.scoping  { color: #7db8ff; border-color: rgba(125,184,255,.35); background: rgba(125,184,255,.12); }
.stage.building { color: #5ee1f3; border-color: rgba(94,225,243,.38); background: rgba(94,225,243,.13); }
.stage.uat      { color: #f5c26b; border-color: rgba(245,194,107,.42); background: rgba(245,194,107,.14); }
.stage.handover { color: #b8e986; border-color: rgba(184,233,134,.40); background: rgba(184,233,134,.13); }

.stage.won      { color: var(--accent); border-color: rgba(94,243,140,.35); background: var(--good-bg); }
.stage.lost     { color: var(--bad);    border-color: rgba(255,122,122,.3);  background: var(--bad-bg); }
.stage.retainer { color: var(--accent); border-color: rgba(94,243,140,.35); background: var(--good-bg); }
/* Neither of these is a loss, so neither is red. Muted, because they are
   outcomes to be able to look up rather than numbers to look at. */
.stage.opted_out { color: var(--muted); border-color: var(--border); background: var(--bg-2); }
.stage.declined  { color: var(--muted); border-color: var(--border); background: var(--bg-2); }

/* Contract and subscription states. Signed and active are the good outcomes
   and read as such; the rest stay quiet rather than alarming — a paused
   subscription is a fact, not a problem.

   These two are singled out because they mean something the other statuses do
   not: money is flowing, right now. Everything else on these two panes is a
   record of something that happened or has stopped.

   The prominence comes from a lit dot rather than from a louder green. A
   stronger fill would have read as "urgent" — the language the rest of this
   interface uses amber and red for — and, more practically, twenty active
   subscriptions rendered as twenty solid green blocks is a wall of colour that
   distinguishes nothing. One small lit dot reads as a status light: on, at a
   glance, and still legible when every row has one. */
.stage.signed, .stage.active {
  color: var(--accent);
  border-color: rgba(94,243,140,.45);
  background: rgba(94,243,140,.14);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.stage.signed::before, .stage.active::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  /* A soft halo, so the dot reads as lit rather than merely coloured. */
  box-shadow: 0 0 0 2px rgba(94,243,140,.22);
  flex: none;
}
.stage.draft, .stage.sent, .stage.paused {
  color: var(--muted); border-color: var(--border); background: var(--bg-2);
}
.stage.expired, .stage.terminated, .stage.cancelled {
  color: var(--dim); border-color: var(--border); background: var(--bg-2);
}

/* Payments, where the status is the whole point of the row: has the money
   arrived, is it still coming, or has it not turned up when it should have.
   Three states, three answers, and they map onto the meanings already in use
   here — so nothing new has to be learned to read the pane.

     received     green   it is in
     expected     amber   still to come, nothing wrong
     late/failed  red     it should be here and is not

   `late` is what a standing order that never arrived becomes: the CRM raises
   the expected payment on its due date and marks it late when nothing matches,
   which is the only way a missed standing order is visible rather than silent.
   That is precisely the row worth making red. */
.stage.received {
  color: var(--accent); border-color: rgba(94,243,140,.4); background: rgba(94,243,140,.13);
  font-weight: 600;
}
.stage.expected {
  color: var(--warn); border-color: rgba(245,194,107,.42); background: var(--warn-bg);
}
.stage.late, .stage.failed {
  color: var(--bad); border-color: rgba(255,122,122,.45); background: var(--bad-bg);
  font-weight: 600;
}
/* Written off is a decision that has been taken, not a problem outstanding. */
.stage.written_off {
  color: var(--dim); border-color: var(--border); background: var(--bg-2);
}

/* ── Filter bar ────────────────────────────────────────────────────────── */

.filterbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.filterbar input[type="search"], .filterbar select {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  font: inherit;
  font-size: 12.5px;
}
.filterbar .spacer { flex: 1; }
.viewtabs { display: inline-flex; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.viewtabs a { padding: 5px 12px; font-size: 12px; color: var(--muted); }
.viewtabs a.active { background: var(--surface-2); color: var(--text); }

/* ── Board ─────────────────────────────────────────────────────────────── */

.board {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 10px;
  align-items: flex-start;
}
.board-col {
  flex: 0 0 268px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 210px);
}
.board-col.terminal { opacity: .78; }
.board-col > header {
  padding: 10px 13px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: baseline; gap: 8px;
}
.board-col > header .name { font-size: 12px; font-weight: 600; font-family: var(--font-display); }
.board-col > header .n { font-size: 11px; color: var(--dim); }
.board-col > header .sum { margin-left: auto; font-size: 11px; color: var(--muted); }
.board-cards { padding: 9px; overflow-y: auto; flex: 1; min-height: 70px; display: flex; flex-direction: column; gap: 8px; }
.board-col.dragover { border-color: var(--accent); }
.board-col.dragover .board-cards { background: rgba(94,243,140,.04); }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 11px;
  cursor: grab;
  transition: border-color .15s var(--ease), transform .1s var(--ease);
}
.card:hover { border-color: var(--dim); }
.card.dragging { opacity: .45; }
.card .t { font-size: 13px; font-weight: 500; color: var(--text); display: block; margin-bottom: 3px; }
.card .co { font-size: 11.5px; color: var(--muted); margin-top: 5px; }
/* Tighter on a card, which has less room than a table row and a lot of them
   on screen at once. Same colour, same meaning, less weight. */
.card .co .chip.client { font-size: 10.5px; padding: 1px 7px; font-weight: 600; }
.card .row { display: flex; align-items: center; gap: 7px; margin-top: 8px; }
.card .val { font-family: var(--font-display); font-size: 13px; color: var(--accent); font-weight: 600; }
.card .when { font-size: 10.5px; color: var(--dim); margin-left: auto; }

.conversion {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.conversion .step {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 7px 11px;
  font-size: 11px;
  color: var(--muted);
}
.conversion .step b { color: var(--accent); font-family: var(--font-display); font-size: 13px; }
.conversion .step .lbl { display: block; color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }

/* ── Forms ─────────────────────────────────────────────────────────────── */

.fields { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; }
.fields .field.wide { grid-column: 1 / -1; }
@media (max-width: 760px) { .fields { grid-template-columns: 1fr; } }

.field label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--dim);
  margin-bottom: 5px;
}
.field input, .field select, .field textarea {
  width: 100%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 8px 11px;
  font: inherit;
  font-size: 13px;
  transition: border-color .15s var(--ease);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--accent-2);
  outline: none;
}
.field textarea { min-height: 86px; resize: vertical; line-height: 1.55; }

/* Long-form content gets the room. Named fields rather than every full-width
   textarea, which was too broad and is the reason a deal page opened with a
   half-screen-tall empty "lost reason" box: a field you write one sentence in
   had been given the height of a field you read a whole email in.

   Still draggable — `resize: vertical` above is inherited, so this only
   decides where each starts. */
.field.wide textarea[name="body_text"],
.field.wide textarea[name="transcript"] {
  min-height: clamp(300px, 64vh, 900px);
}
.field.wide textarea[name="summary"],
.field.wide textarea[name="notes"],
.field.wide textarea[name="description"] {
  min-height: clamp(160px, 26vh, 360px);
}
/* A button that reads as a link, wherever it appears.
 *
 * This was written twice as `.whopick .linkish` and `.clientpick .linkish`,
 * both scoped, so the same class on the Drive picker button — which sits in an
 * upload form and is in neither — inherited none of it and rendered as a raw
 * grey browser button. Present in the markup, correct in every test, and not
 * recognisable on screen as the thing it was.
 *
 * The two scoped rules still win where they apply; this is the floor. */
.linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-decoration: underline;
  text-underline-offset: 2px;
}
.linkish:hover { color: var(--text); }

.field .help { font-size: 11px; color: var(--dim); margin-top: 4px; }
.field .err { font-size: 11.5px; color: var(--bad); margin-top: 4px; }
.field.has-error input, .field.has-error select, .field.has-error textarea { border-color: var(--bad); }
.field .check { display: flex; align-items: center; gap: 8px; }
.field .check input { width: auto; }

.form-actions { display: flex; gap: 9px; align-items: center; margin-top: 18px; }
.form-actions .spacer { flex: 1; }

/* ── Timeline ──────────────────────────────────────────────────────────── */

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li {
  display: flex; gap: 11px;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.timeline li:last-child { border-bottom: none; }
.timeline .dot {
  width: 22px; height: 22px; border-radius: 999px; flex: none;
  display: grid; place-items: center;
  font-size: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--muted);
}
.timeline .dot.accent { color: var(--accent); border-color: rgba(94,243,140,.3); }
.timeline .body { flex: 1; min-width: 0; }
.timeline .head { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; }
.timeline .head .who { color: var(--text); font-weight: 500; }
.timeline .head .when { color: var(--dim); font-size: 11px; margin-left: auto; white-space: nowrap; }
.timeline .what { color: var(--muted); }
.timeline .detail {
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--muted);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  white-space: pre-wrap;
}
.timeline .changes { margin-top: 5px; font-size: 12px; color: var(--dim); }
.timeline .changes b { color: var(--muted); font-weight: 500; }

/* ── Tasks ─────────────────────────────────────────────────────────────── */

.tasks { list-style: none; margin: 0; padding: 0; }
.tasks li {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.tasks li:last-child { border-bottom: none; }
.tasks li.done .t { text-decoration: line-through; color: var(--dim); }
/* The tick was 17px, empty, and outlined in the same colour as everything
   else — it worked perfectly and nobody could tell it was there. Bigger, with
   a stronger edge and a tick that previews on hover, so it reads as something
   you press. */
/* Same rule for the list panes: contracts, subscriptions and tasks name the
   thing the row is about, so it is text until the cursor is on it. */
.tasks .t { color: var(--text); }
.tasks .t:hover { color: var(--accent); }

.tasks .tick {
  width: 22px; height: 22px; border-radius: 6px;
  border: 1.5px solid var(--border);
  background: var(--bg-2);
  cursor: pointer;
  flex: none;
  margin-top: 1px;
  display: grid; place-items: center;
  color: transparent;
  font-size: 13px; line-height: 1;
  padding: 0;
  transition: border-color .12s, background .12s, color .12s;
}
.tasks .tick::after { content: "✓"; }
.tasks .tick:hover {
  border-color: var(--accent);
  color: var(--accent);          /* the tick appears faintly under the cursor */
}
.tasks li:hover .tick { border-color: var(--dim); }
.tasks .tick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tasks li.done .tick {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-ink);
}
.tasks li.done .t { text-decoration: line-through; opacity: .6; }

/* A hint the first time, on the row itself rather than in a tooltip nobody
   hovers long enough to see. */
.tasks li:hover .tickhint { opacity: 1; }
.tickhint {
  opacity: 0; transition: opacity .12s;
  font-size: 11px; color: var(--dim); white-space: nowrap;
}
/* One line, in a fixed order, separated so the parts are countable.
 *
 * It was a flat run of chips, links, selects and buttons that wrapped wherever
 * it ran out of room — so the due date sat in a different place on nearly
 * every row, which is why it could not be found. Each part is a `bit` that
 * holds together, and a faint divider between them says where one ends. */
.tasks .meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 10px; margin-top: 5px;
  font-size: 11.5px; color: var(--dim);
}
.tasks .meta .bit { display: inline-flex; align-items: center; gap: 6px; }
.tasks .meta .bit + .bit::before {
  content: ""; width: 1px; height: 11px;
  background: var(--border); margin-right: 4px;
}
/* The links live at the end and are pushed there, so a wide pane keeps the
   information left and the ways out right. */
.tasks .meta .bit.actions { margin-left: auto; gap: 10px; }

/* The due date is the question this list exists to answer, so it is the one
   thing in the row that is not grey. */
.tasks .meta .due { color: var(--text); font-weight: 600; white-space: nowrap; }
.tasks .meta .due.overdue { color: var(--bad); font-weight: 700; }
.tasks .meta .due.none { color: var(--dim); font-weight: 400; font-style: italic; }

/* ── Suggestions ───────────────────────────────────────────────────────── */

.suggestion {
  border: 1px solid rgba(94,243,140,.22);
  background: linear-gradient(180deg, rgba(94,243,140,.045), transparent 70%);
  border-radius: var(--radius);
  padding: 13px 15px;
  margin-bottom: 11px;
}
.suggestion .head { display: flex; align-items: center; gap: 9px; margin-bottom: 5px; }
.suggestion .head .k {
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--accent);
}
.suggestion .head .when { margin-left: auto; font-size: 11px; color: var(--dim); }
.suggestion h3 { font-size: 14px; margin-bottom: 4px; }
.suggestion .why { font-size: 12.5px; color: var(--muted); }
.suggestion .draft {
  margin-top: 10px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  font-size: 12.5px;
  color: var(--muted);
  white-space: pre-wrap;
  max-height: 260px;
  overflow-y: auto;
}
.suggestion .draft .subject { color: var(--text); font-weight: 600; display: block; margin-bottom: 7px; }
.suggestion .acts { display: flex; gap: 8px; margin-top: 11px; align-items: center; }
.suggestion .acts .note { font-size: 11px; color: var(--dim); margin-left: auto; }

/* ── Command palette ───────────────────────────────────────────────────── */

.palette-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.6);
  backdrop-filter: blur(3px);
  z-index: 90;
  display: none;
}
.palette-backdrop.open { display: block; }
.palette {
  position: fixed;
  top: 14vh; left: 50%;
  transform: translateX(-50%);
  width: min(620px, 92vw);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 24px 70px rgba(0,0,0,.65);
  z-index: 91;
  overflow: hidden;
  display: none;
}
.palette.open { display: block; }
.palette input {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  padding: 14px 17px;
  outline: none;
}
.palette .results { max-height: 55vh; overflow-y: auto; }
.palette .group-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--dim);
  padding: 10px 17px 4px;
}
.palette .hit {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 17px;
  cursor: pointer;
  font-size: 13px;
}
.palette .hit .sub { color: var(--dim); font-size: 11.5px; margin-left: auto; }
/* The date sits hard right, after the stage or status. `margin-left: auto` is
   on `.sub`, so the pair travels together and the date is last — read as
   "UKCS Group — AI build · won · 10 Aug 2026". A row with no subtitle still
   pushes its date over, hence the auto here too. */
.palette .hit .when {
  color: var(--dim); font-size: 11.5px;
  margin-left: auto; white-space: nowrap; flex: none;
  font-variant-numeric: tabular-nums;
}
.palette .hit .sub ~ .when { margin-left: 0; }
/* The matched sentence, on its own line under the title. Wrapping is what
   makes room for it: the title and subtitle keep the row they had, and the
   passage takes the full width beneath rather than squeezing them. */
.palette .hit { flex-wrap: wrap; }
.palette .hit .passage {
  flex-basis: 100%;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.45;
  /* One line, cut cleanly. A transcript match can run long and ten results
     that each take three lines is a scrollbar instead of an answer. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.palette .hit.sel, .palette .hit:hover { background: var(--surface-2); }
.palette .hint { padding: 9px 17px; font-size: 11px; color: var(--dim); border-top: 1px solid var(--border); }

/* ── Login ─────────────────────────────────────────────────────────────── */

.login-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1000px 500px at 50% -10%, rgba(94,243,140,.07), transparent 65%),
    var(--bg);
}
.login {
  width: min(400px, 100%);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 30px;
}
.login .brand { padding: 0 0 18px; }
.login h1 { font-size: 19px; margin-bottom: 6px; }
.login p.lede { color: var(--muted); font-size: 13px; margin: 0 0 20px; }
.login .field { margin-bottom: 13px; }
.login .btn { width: 100%; justify-content: center; margin-top: 6px; }
.login .foot { margin-top: 18px; font-size: 11px; color: var(--dim); text-align: center; }

/* ── Misc ──────────────────────────────────────────────────────────────── */

.muted { color: var(--muted); }
.dim { color: var(--dim); }
.mono { font-variant-numeric: tabular-nums; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.pagelead { color: var(--muted); font-size: 13px; margin: 0 0 16px; max-width: 68ch; }
.section-gap { margin-top: 18px; }

.toast {
  position: fixed; bottom: 22px; left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease);
  z-index: 95;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.bad { border-color: rgba(255,122,122,.4); color: var(--bad); }

.error-page { max-width: 480px; margin: 12vh auto; text-align: center; }
.error-page .code { font-family: var(--font-display); font-size: 56px; color: var(--dim); letter-spacing: -.03em; }
.error-page p { color: var(--muted); }


/* ── Pipeline card preview ─────────────────────────────────────────────── */

.board .card { cursor: pointer; }
.board .card a { cursor: pointer; }

.peek-wrap { display: none; }
.peek-wrap.open { display: block; }

.peek-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(12, 14, 20, 0.55);
}

.peek-panel {
  position: fixed; z-index: 61;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(720px, calc(100vw - 2rem));
  max-height: min(80vh, 900px);
  display: flex; flex-direction: column;
  background: var(--surface, #fff);
  border: 1px solid var(--line, #e3e6ec);
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(12, 14, 20, 0.28);
  overflow: hidden;
}

.peek-head {
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 1rem 1.1rem;
  border-bottom: 1px solid var(--line, #e3e6ec);
}
.peek-title { font-weight: 600; font-size: 1.05rem; }
.peek-meta { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.peek-close {
  background: none; border: 0; cursor: pointer;
  font-size: 1.4rem; line-height: 1; padding: 0 .2rem;
  color: var(--dim, #6b7280);
}
.peek-close:hover { color: var(--text, #111); }

.peek-body { padding: 1.1rem; overflow-y: auto; }
.peek-loading { padding: 2.5rem; text-align: center; color: var(--dim, #6b7280); }
.peek-missing {
  padding: .9rem 1rem; border-radius: 8px;
  background: var(--muted-bg, #f5f6f8); color: var(--dim, #6b7280);
}

.peek-warn {
  margin: 0 1.1rem 0; padding: .75rem .9rem;
  border-left: 3px solid #c2410c; border-radius: 0 6px 6px 0;
  background: rgba(194, 65, 12, 0.08); font-size: .9rem;
}
.peek-warn b { color: #9a3412; }

.peek-subject { font-weight: 600; margin-bottom: .4rem; }
.peek-line { display: flex; align-items: center; gap: .5rem; margin-bottom: .7rem; font-size: .88rem; }
.peek-line .spacer { flex: 1; }

/* Bodies are stored as plain text and shown as plain text — deliberately not
   rendered as HTML, since it is third-party content inside our own page. */
.peek-quote {
  margin: 0 0 1rem; padding: .9rem 1rem;
  /* Taller, and draggable. 340px was about eight lines, which for most replies
     meant scrolling a small box inside a page that had room to spare. Bounded
     against the viewport so a long thread still cannot push the buttons under
     it off the screen — the card is a preview, and the actions have to stay
     reachable. */
  max-height: clamp(240px, 48vh, 620px);
  overflow-y: auto;
  resize: vertical;
  /* Background and text set together and explicitly. Setting only the
     background is how this became white text on a white box. */
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font: inherit; font-size: .9rem; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}

/* Who to reply to. Sits above the message because it is the thing you act on;
   the message is the reason. */
.peek-who {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  padding: .6rem .8rem; margin: 0 0 .9rem;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; font-size: .85rem;
}
.peek-who .spacer { flex: 1; }
.peek-who .who-name { font-weight: 600; color: var(--text); }
.peek-who .who-link {
  color: var(--accent-2); text-decoration: none; white-space: nowrap;
  padding: .12rem .45rem; border: 1px solid var(--border); border-radius: 6px;
}
.peek-who .who-link:hover { color: var(--accent); border-color: var(--dim); }

.peek-actions { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.peek-actions .spacer { flex: 1; }
.peek-saved { color: #15803d; font-size: .85rem; }

.peek-form label { display: block; margin-bottom: .8rem; }
.peek-form label > span {
  display: block; margin-bottom: .25rem;
  font-size: .82rem; color: var(--dim, #6b7280);
}
.peek-form input, .peek-form textarea {
  width: 100%; padding: .5rem .6rem;
  border: 1px solid var(--line, #e3e6ec); border-radius: 6px;
  font: inherit; background: var(--surface, #fff); color: inherit;
}
.peek-pair { display: grid; grid-template-columns: 2fr 1fr; gap: .8rem; }
.peek-note {
  margin: 0 0 1rem; font-size: .85rem; color: var(--dim, #6b7280);
}

.chip.in { background: rgba(21, 128, 61, 0.12); color: #15803d; }
.chip.out { background: rgba(37, 99, 235, 0.12); color: #1d4ed8; }

/* Correspondence, one fold per conversation.
   The panel body carries the folds edge to edge, so the padding moves off the
   body and onto the rows — otherwise each fold sits in a 16px inset and the
   dividers stop short of the panel sides, which reads as a mistake. */
.threads { padding: 0 !important; }
.thread { border-bottom: 1px solid var(--border); }
.thread:last-child { border-bottom: none; }

.thread-summary {
  display: flex; align-items: center; gap: .5rem;
  padding: 11px 16px;
  font-size: .9rem;
  cursor: pointer;
  list-style: none;
}
.thread-summary::-webkit-details-marker { display: none; }
.thread-summary:hover { background: var(--surface-2); }
.thread-summary .spacer { flex: 1; }
/* The date and the direction chip hold their own line. Left to wrap, a long
   subject squeezed "10d ago" onto two lines and the row grew a second storey
   — the subject is the thing with an ellipsis, so it is the thing that gives. */
.thread-summary .chip { flex: none; }
.thread-summary .dim { flex: none; white-space: nowrap; }
/* Chevron first, so the subject starts at a consistent left edge whether or
   not the row happens to carry a count chip. */
.thread-summary::before {
  content: "›"; color: var(--dim); flex: none;
  width: .7rem; transition: transform .15s ease;
}
.thread[open] > .thread-summary::before { transform: rotate(90deg); }
.thread[open] > .thread-summary { background: var(--bg-2); }

.thread-subject {
  font-weight: 600;
  /* One line. A forty-word subject must not push the date and the direction
     chip off the row — they are what the collapsed line is for. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}

.thread-body { padding: 14px 16px 4px; }
/* Messages within a conversation, separated by a hairline rather than a gap:
   they are one exchange, and a gap the size of the panel's own reads as
   several. */
.thread-body .peek-email + .peek-email {
  border-top: 1px dashed var(--border);
  padding-top: 14px;
}
.thread-body .peek-email { margin-bottom: 14px; }
.thread-foot { padding-bottom: 12px; }

@media (max-width: 640px) {
  /* The from-address is the first thing worth losing — the direction chip and
     the date carry the row on a narrow screen. */
  .thread-summary .chip:not(.in):not(.out) { display: none; }
}

@media (max-width: 640px) {
  .peek-panel { width: 100vw; height: 100vh; max-height: none; border-radius: 0; top: 0; left: 0; transform: none; }
  .peek-pair { grid-template-columns: 1fr; }
}

.peek-facts { display: grid; grid-template-columns: max-content 1fr; gap: .3rem 1rem; margin: 0 0 1rem; font-size: .9rem; }
.peek-facts dt { color: var(--dim, #6b7280); }
.peek-facts dd { margin: 0; }

/* ── Document upload ───────────────────────────────────────────────────── */
.upload-panel { padding: 1rem 1.1rem; margin-bottom: 1rem; }
.upload-panel summary { cursor: pointer; font-weight: 600; }
.uploadform { margin-top: 1rem; }
.uploadgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: .8rem; margin-bottom: .8rem;
}
.uploadgrid label { display: block; }
.uploadgrid label > span {
  display: block; margin-bottom: .25rem;
  font-size: .82rem; color: var(--dim, #6b7280);
}
.uploadgrid input[type=text], .uploadgrid select, .uploadgrid input[type=file] {
  width: 100%; padding: .45rem .55rem;
  border: 1px solid var(--line, #e3e6ec); border-radius: 6px;
  font: inherit; background: var(--surface, #fff); color: inherit;
}
.uploadgrid label.check { display: flex; align-items: center; gap: .5rem; align-self: end; }
.uploadgrid label.check span { margin: 0; }

/* ── Reply context on a timeline ───────────────────────────────────────── */
.timeline .reply {
  margin-top: .4rem; padding: .55rem .7rem;
  border-left: 3px solid var(--line, #d7dbe0); border-radius: 0 5px 5px 0;
  background: var(--muted-bg, #f5f6f8); font-size: .9rem;
}
.timeline .reply b { display: block; }
/* Colour carries the meaning at a glance: a person wrote back, or a machine
   did, or they asked to be left alone. */
.timeline .reply.genuine { border-left-color: #15803d; background: rgba(21,128,61,.08); }
.timeline .reply.out_of_office { border-left-color: #6b7280; }
.timeline .reply.left_company { border-left-color: #b45309; background: rgba(180,83,9,.08); }
.timeline .reply.unsubscribe { border-left-color: #dc2626; background: rgba(220,38,38,.08); }
.timeline .reply.unverified { border-left-color: #d97706; background: rgba(217,119,6,.07); }
.timeline .reply .unverified {
  margin-top: .35rem; color: #92400e; font-size: .85rem;
}

/* ── People at a company ───────────────────────────────────────────────── */
.addcontact {
  display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .9rem;
  padding-top: .9rem; border-top: 1px solid var(--line, #e3e6ec);
}
.addcontact input {
  flex: 1 1 140px; min-width: 0; padding: .45rem .55rem;
  border: 1px solid var(--line, #e3e6ec); border-radius: 6px;
  font: inherit; background: var(--surface, #fff); color: inherit;
}
/* Someone who must not be approached should read as such at a glance, not
   only when you open their record. */
tr.suppressed td:first-child a { text-decoration: line-through; }
tr.suppressed { opacity: .72; }

/* ── Clickable figures ─────────────────────────────────────────────────── */
a.stat { display: block; text-decoration: none; color: inherit; transition: border-color .12s, transform .12s; }
a.stat:hover { border-color: var(--accent, #10b981); transform: translateY(-1px); }
a.stat .v { text-decoration: none; }

/* ── Diary ─────────────────────────────────────────────────────────────── */
.dayblock { margin-bottom: 1rem; }
.dayblock:last-child { margin-bottom: 0; }
.dayhead {
  display: flex; align-items: baseline; gap: .55rem;
  padding-bottom: .35rem; margin-bottom: .4rem;
  border-bottom: 1px solid var(--line, #e3e6ec); font-size: .92rem;
}
.dayblock.today .dayhead { border-bottom-color: var(--accent, #10b981); }
.dayblock.today .dayhead b { color: var(--accent, #10b981); }
.slot {
  display: flex; align-items: center; gap: .8rem;
  flex-wrap: nowrap;                 /* Join belongs on the row, not under it */
  padding: .5rem .55rem; border-radius: 6px;
  text-decoration: none; color: inherit;
}
.slot:hover { background: var(--muted-bg, #f5f6f8); }
.slot-title { color: inherit; text-decoration: none; }
.slot-title:hover { text-decoration: underline; }
/* Fixed-width time column so the day reads as a schedule rather than a list. */
.slot-time { flex: 0 0 4rem; color: var(--dim, #6b7280); }
.slot-body { flex: 1; min-width: 0; }
.slot-title { display: block; font-weight: 500; }
.slot-meta { display: block; font-size: .86rem; color: var(--dim, #6b7280); }
.slot-meta .chip.client { font-size: 10.5px; padding: 1px 7px; vertical-align: 1px; }
.slot-marks { display: flex; gap: .35rem; flex-shrink: 0; align-items: center; margin-left: auto; }

/* A second clock, shown only to whoever is not on UK time. Quiet enough to
   ignore, present enough to stop a four-hour mistake. */
.slot-time { display: flex; flex-direction: column; line-height: 1.3; }
.slot-alt { font-size: .74rem; color: var(--dim, #6b7280); letter-spacing: .02em; }

/* ── Join the call ─────────────────────────────────────────────────────── */
.btn.join {
  background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); font-weight: 600;
}
.btn.join:hover { filter: brightness(1.08); }
.btn.join.sm { padding: .2rem .6rem; font-size: .78rem; }

/* ── Task meta ─────────────────────────────────────────────────────────── */
/* The client reads as the strongest thing on the line: it is what tells you
   whether a task is yours to worry about this morning. */
/* Which client this row is for. Purple, and prominent.
   Neutral was tried and was wrong: on the open tasks pane this chip is what
   the list is *scanned* by — the eye runs down the column looking for a
   company rather than reading titles — so muting it breaks the one job it has.

   Purple rather than green because green was carrying two unrelated meanings
   at once. It marked what is live *and* which company a row is about, on rows
   that showed both, which is how three greens ended up on a single contract
   line with nothing to tell them apart.

   Now each colour answers one question, and a row can be read by colour alone:

     purple  which client
     blue    which of us is on it
     green   this is live
     amber   nobody has picked it up
     red     something is wrong

   Purple is also the Discovery-booked stage, and that is deliberate rather
   than an oversight: a stage is an outlined pill with a state written in it,
   this is a filled chip with a company name in it, and the two never do the
   same job in the same pane. Sharing a hue between different shapes costs less
   than adding a sixth colour to learn. */
.chip.client {
  background: rgba(196,140,255,.14); color: #c48cff;
  border: 1px solid rgba(196,140,255,.34);
  text-decoration: none; font-weight: 600;
}
.chip.client:hover { background: rgba(196,140,255,.24); }

/* "No client" in the picker, made to stand out among fifty company names —
   it is the option people open the list looking for, and a plain row of text
   in the middle of an alphabetical list is the one thing they will not find.
   Native select options take almost no styling, so this leans on what does
   work everywhere: an optgroup heading, a leading mark, and the words. */
.clientform option.opt-none { font-weight: 700; }
.clientform optgroup { font-weight: 600; color: var(--muted); }
.clientform optgroup option { font-weight: 400; color: var(--text); }
.clientform optgroup option.opt-none { font-weight: 700; color: #c48cff; }

/* A client nobody has stated and none could be worked out. Muted rather than
   absent, because a missing chip looks like a task with no client and an empty
   one invites the correction. */
.chip.client.none {
  background: var(--bg-2); color: var(--dim);
  border: 1px dashed var(--border); font-weight: 500;
}
/* Set by hand. A quiet mark, not a badge — it says "this one was checked",
   which is worth knowing and not worth shouting. */
.chip.client.stated { border-style: solid; box-shadow: inset 0 -2px 0 rgba(196,140,255,.5); }

.whopick { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.whoform { display: inline-flex; align-items: center; gap: 6px; }
.whoform select {
  font-size: 12px; padding: .18rem .4rem; max-width: 12rem;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  color-scheme: dark;
}
.whopick .linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); font-size: 11px; text-decoration: underline;
  text-underline-offset: 2px;
}
.whopick .linkish:hover { color: var(--accent); }

.clientpick { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.clientpick .linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); font-size: 11px; text-decoration: underline;
  text-underline-offset: 2px;
}
.clientpick .linkish:hover { color: var(--accent); }
.clientform { display: inline-flex; align-items: center; gap: 6px; }
.clientform select {
  font-size: 12px; padding: .18rem .4rem; max-width: 15rem;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  /* Native dropdown drawn for a dark background rather than a light one. */
  color-scheme: dark;
}
.btn.small { padding: .18rem .55rem; font-size: 11.5px; }

/* ── Working through the task list ─────────────────────────────────────── */

.taskfilter { display: flex; align-items: center; gap: .6rem; padding-bottom: 0; }
.taskfilter select {
  font-size: 12px; padding: .25rem .5rem; max-width: 20rem; width: 100%;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  color-scheme: dark;
}

.tasks .taskpick { margin-top: .3rem; accent-color: var(--accent); flex: none; }
.tasks li.picked { background: rgba(94,243,140,.06); border-radius: var(--radius-sm); }

.taskbulkbar {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .6rem 0 0; margin-top: .4rem;
  border-top: 1px solid var(--border);
}
.taskbulkbar .spacer { flex: 1; }
.taskbulkbar .check { display: inline-flex; align-items: center; gap: .4rem; font-size: 12px; }
.taskbulkbar .btn[disabled] { opacity: .45; cursor: not-allowed; }

/* Page numbers. Plain and small — they are a way back to the list, not a
   feature of it. */
.pager {
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap;
  padding-top: .7rem; margin-top: .6rem;
  border-top: 1px solid var(--border); font-size: 12px;
}
.pager .spacer { flex: 1; }
.pager a, .pager .here {
  padding: .2rem .5rem; border-radius: var(--radius-sm);
  border: 1px solid transparent; min-width: 1.7rem; text-align: center;
}
.pager a { color: var(--muted); }
.pager a:hover { color: var(--accent); border-color: var(--border); background: var(--surface-2); }
.pager .here {
  color: var(--accent-ink); background: var(--accent); font-weight: 600;
}

/* Present for screen readers, absent on screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* Who is holding a task. Bold and coloured, because "is this mine" is the
   first question a task list is asked and the answer used to be the quietest
   thing on the row. The colour is the person's own, set inline from their
   accent, so it matches their avatar everywhere else. */
/* One blue for every assignee. The name is already written on the chip, so a
   colour per person adds nothing to read and three colours down a long list
   are three things competing for attention. Amber below still means something
   — nobody owns this — and stands out because everything else is uniform. */
.chip.who {
  font-weight: 700; letter-spacing: .01em;
  color: var(--who); border: 1px solid var(--who-line); background: var(--who-bg);
}

/* Nobody has picked it up. Amber rather than red: a gap to close, not
   something that has gone wrong — and eight red rows out of thirty-four
   teaches you to ignore red. */
.chip.who.unassigned {
  color: var(--warn); font-weight: 700;
  border: 1px solid rgba(245,194,107,.4); background: var(--warn-bg);
}
.tasks .chip.open { background: var(--bg-2); color: var(--dim); }
.tasks .chip.done { background: rgba(94,243,140,.14); color: var(--accent); }
.tasks .meta { flex-wrap: wrap; }

/* A tick that is in flight, so a slow network does not read as a dead click. */
.tasks li.saving { opacity: .55; }
.tasks li.saving .tick { border-color: var(--accent); }
/* Completed rows stay in place rather than vanishing: seeing what you just
   ticked is how you notice ticking the wrong one. */
.tasks li.done .t { text-decoration: line-through; opacity: .6; }

.inlineform { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.inlineform input {
  flex: 1 1 300px; padding: .5rem .6rem;
  border: 1px solid var(--border); border-radius: 6px;
  font: inherit; background: var(--bg-2); color: inherit;
}

/* Where the name opens the file, the record is still one click away. */
.records .rec { margin-left: .5rem; font-size: .78rem; }
.records .rec::before { content: "· "; }

/* ── Bulk selection ────────────────────────────────────────────────────── */
.records th.pick, .records td.pick { width: 34px; padding-right: 0; }
.records td.pick input, .records th.pick input {
  width: 16px; height: 16px; cursor: pointer; accent-color: var(--accent);
}
.records tr.picked { background: rgba(94,243,140,.06); }
.bulkbar {
  display: flex; align-items: center; gap: .5rem;
  padding: .6rem .9rem; border-bottom: 1px solid var(--border);
  background: rgba(94,243,140,.06);
}
.bulkbar .spacer { flex: 1; }
.btn.danger { border-color: rgba(255,107,107,.4); color: #ff8f8f; }
.btn.danger:hover { background: rgba(255,107,107,.12); }

/* ── Date and time fields ──────────────────────────────────────────────── */
/* `color-scheme: dark` above is what makes the calendar button visible at all;
   it was being drawn for a light background and vanished into the field.
   Nothing here moves it: stretching the indicator across the input put the
   icon on top of the date. */
input[type="date"], input[type="datetime-local"] { cursor: pointer; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .6;
  transition: opacity .12s;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }

/* ── The bank feed ─────────────────────────────────────────────────────── */
/* Money in reads green and money out reads plain, so the direction is clear
   before any figure is read. A left edge rather than a filled row: tinting
   every outgoing line would make a statement that is mostly outgoings look
   like a page of warnings. */
/* Green means money in, and nothing else on this table — so the date, which
   is the record link everywhere else, is left neutral here. Two meanings for
   one colour on the same screen is worse than no colour at all. */
.records tr.in .title, .records tr.out .title { color: var(--ink); }
.records tr.in .title:hover, .records tr.out .title:hover { color: var(--accent); }

.records tr.in { background: rgba(94,243,140,.05); }
.records tr.in td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.records tr.out td:first-child { box-shadow: inset 2px 0 0 var(--border); }
.records tr.in .mono { color: var(--accent); font-weight: 600; }
.records tr.out .mono { color: var(--dim); }

/* An attempt that never moved money. Struck through and faded, because in a
   plain list it is indistinguishable from a charge — which is how eight
   declined attempts get read as eight payments. */
.records tr.failed td { opacity: .55; }
.records tr.failed td:first-child { box-shadow: inset 2px 0 0 var(--bad); }
.records tr.failed .mono { color: var(--dim); text-decoration: line-through; }
.records tr.failed .title { color: var(--muted); }

/* ── Stage summary, above a breakdown list ─────────────────────────────── */

/* "Seventeen contacted, one discovery booked, five proposals out" answers the
   question faster than any list can, and each one filters the list beneath it.
   Horizontal and small: this is a legend that happens to be clickable, not
   another panel competing with the table. */
.stagebar { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.stagebar a {
  display: inline-flex; align-items: baseline; gap: 7px;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); font-size: 12px;
  transition: border-color .15s var(--ease), background .15s var(--ease);
}
.stagebar a:hover { background: var(--surface-2); }
.stagebar a.on { border-color: currentColor; }
.stagebar .n { font-size: 15px; font-weight: 700; font-family: var(--font-display); }
/* The dot carries the same colour the pill in the table does, so the legend
   and the rows teach each other. */
.stagebar .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; align-self: center;
}
.stagebar a.lead             { color: var(--dim); }
.stagebar a.contacted        { color: #7db8ff; }
.stagebar a.discovery_booked { color: #5ee1f3; }
.stagebar a.discovery_done   { color: #4fd6c9; }
.stagebar a.proposal_sent    { color: #f5c26b; }
.stagebar a.commercial_sent  { color: #f0a24a; }
.stagebar a.awaiting_signature { color: #b8e986; }
.stagebar a.won, .stagebar a.retainer { color: var(--accent); }
.stagebar a.lost             { color: var(--bad); }
.stagebar a.all              { color: var(--text); }

/* ── Legend ────────────────────────────────────────────────────────────── */

/* Says what a colour on this page means, using the colour itself rather than
   describing it. A key that spells out "purple" in grey text is a second thing
   to map; showing the token is the shortest possible explanation. */
.legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  margin: -6px 0 16px; font-size: 12px; color: var(--muted);
}
.legend .chip.client { font-size: 11px; }
.legend-plain {
  color: var(--text); font-weight: 500;
  /* Drawn the way a plain row title is drawn, so the sample matches the thing
     it stands for rather than merely naming it. */
}
.legend-sep { color: var(--dim); }

/* ── Key/value block ───────────────────────────────────────────────────── */

/* Facts about a record that are read rather than edited. A definition list
   rather than a table: there are no columns here, only pairs, and a table
   would draw three lines nobody needs to see. */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin: 0; }
.kv dt { color: var(--dim); font-size: 12px; white-space: nowrap; }
.kv dd { margin: 0; font-size: 13px; }

/* ── Notes on a task ───────────────────────────────────────────────────── */

.notelist { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 10px; }
.notelist li {
  border-left: 2px solid var(--border); padding: 2px 0 2px 10px;
}
.noteby { font-size: 11.5px; display: flex; gap: 8px; align-items: baseline; }
.noteby b { color: var(--text); }
/* Whitespace kept: people paste lists and short paragraphs into these, and
   collapsing them turns a considered note into one long line. */
.notebody { font-size: 13px; white-space: pre-wrap; margin-top: 2px; }

/* ── Changing a stage from a list ──────────────────────────────────────── */

/* The pill is what you read; the select sits invisibly on top of it so the
   whole pill is the control. Two visible controls where one word belongs — a
   pill and a dropdown beside it — is twice the furniture for the same job, and
   in a table column there is no room for it. */
.stagepick { position: relative; display: inline-block; }
.stagepick .stage { cursor: pointer; }
.stagepick .stage::after { content: " ▾"; opacity: .5; font-size: 9px; }
.stagepick .stageform {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
  /* Still a real select, so it keeps the keyboard behaviour and the native
     menu on a phone. */
  appearance: none; border: 0; padding: 0; margin: 0;
}
/* The select follows the pill in the markup, so a sibling selector cannot
   reach back to it. Focus is shown on the wrapper instead. */
.stagepick:focus-within .stage { outline: 2px solid var(--ring); outline-offset: 1px; }
.stagepick .stageform:disabled { cursor: progress; }

/* The tax inside a figure, said under it rather than beside it — a second
   number on the same line reads as a second payment. */
.vatline { font-size: 10.5px; line-height: 1.3; }

/* The first line of a reply, under its subject. Clamped rather than truncated
   with an ellipsis in Python, so the whole thing is still in the page for
   anyone searching it. */
.replysnip { font-size: 11.5px; line-height: 1.4; max-width: 46ch; }
.repliers { text-decoration: none; }
.repliers:hover b { text-decoration: underline; }

/* What kind of reply this was. The same shapes the pipeline uses, and the same
   meanings: green is someone worth answering, red is somebody asking to be left
   alone, grey is a machine. */
.stage.genuine { color: var(--accent); border-color: rgba(94,243,140,.4); background: rgba(94,243,140,.13); font-weight: 600; }
.stage.not_interested { color: var(--muted); border-color: var(--border); background: var(--bg-2); }
.stage.left_company { color: #f5c26b; border-color: rgba(245,194,107,.4); background: var(--warn-bg); }
.stage.unsubscribe { color: var(--bad); border-color: rgba(255,122,122,.42); background: var(--bad-bg); font-weight: 600; }
.stage.out_of_office { color: var(--dim); border-color: var(--border); background: var(--bg-2); }

/* ── Who is on a call ──────────────────────────────────────────────────── */

/* Yellow for anyone outside the company. Amber already means "look at this"
   everywhere else here, and that is exactly right: an external attendee is the
   difference between a catch-up and a meeting you prepare for. Ours are left
   plain, so the eye finds the guest without reading the domains. */
.attendees { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.attendees li {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  padding: 4px 8px; border-radius: var(--radius-sm);
  border: 1px solid transparent;
}
.attendees li.outside {
  background: var(--warn-bg);
  border-color: rgba(245,194,107,.32);
}
.attendees li.outside .who { color: var(--warn); font-weight: 600; }
.attendees .who { font-size: 13px; }
.attendees .addr { font-size: 11.5px; }
.chip.external {
  color: var(--warn); border: 1px solid rgba(245,194,107,.4);
  background: var(--warn-bg); font-weight: 600;
}

/* Anyone outside the company, named in a diary line. Same yellow as the
   attendee list on the meeting itself, so the diary and the record agree about
   what "external" looks like. */
.guests { color: var(--warn); }

/* ── The gate on removing something with records under it ──────────────── */

.sure-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(0,0,0,.62);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.sure-backdrop[hidden] { display: none; }
.sure {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px; width: 100%; max-width: 460px;
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
}
.sure h2 { font-size: 16px; margin-bottom: 4px; }
.sure-name { font-size: 15px; font-weight: 600; margin: 0 0 10px; }
/* The consequence, stated in the one place somebody is definitely reading. */
.sure-what {
  font-size: 13px; margin: 0 0 10px;
  color: var(--bad);
}
.sure .muted { font-size: 12px; margin: 0 0 14px; }
.sure-check {
  display: flex; gap: 9px; align-items: flex-start;
  font-size: 13px; margin-bottom: 12px; cursor: pointer;
}
.sure-check input { margin-top: 2px; }
.sure-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
/* Disabled until both locks are turned, and visibly so — a button that looks
   ready and does nothing is worse than one that says it is not. */
.sure .btn.danger[disabled] { opacity: .45; cursor: not-allowed; }

/* ── A fee offered from the paperwork ──────────────────────────────────── */

.fees { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 12px; }
.fees form { display: flex; align-items: center; gap: 10px; }
.fee-amount { font-size: 16px; font-weight: 700; }
/* The sentence it came from, so the choice can be checked rather than trusted.
   Small, because it is evidence rather than the thing being chosen. */
.feectx { font-size: 11px; line-height: 1.45; margin-top: 3px; max-width: 62ch; }

/* ── The campaigns table on the dashboard ──────────────────────────────── */

/* Names come from Instantly and are whatever somebody typed there: 27
   characters one week, 92 the next, and the long one written as
   "Cleaning,Hygiene,Janitorial,Packaging,…" with no spaces to break at. Left to
   size itself, that single row decides the width of the table and carries the
   figures out of the pane.

   So the columns are fixed and the name wraps to fit them, rather than the
   table stretching to fit the name. The figures are what this pane is for; the
   name is a label on them. */
.records.campaigns { table-layout: fixed; width: 100%; }

.records.campaigns th:first-child,
.records.campaigns td:first-child {
  /* Enough for two or three words a line. The rest divides between six columns
     of figures, which is what decides how much is left here. */
  width: 26%;
  text-align: left;
  /* `anywhere` rather than `break-word`: the offending name has no space in it
     at all, and break-word alone will not split an unbroken run. */
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: normal;
  line-height: 1.35;
}

/* Everything is smaller and tighter in here than in an ordinary table.
   Six columns of figures plus a name in the width of half a dashboard is not
   enough room for 14px of padding either side and letter-spaced capitals — and
   the first thing to go was the last letter of "COMPLETED" and "CONTACTED",
   which is how Ben found it. */
.records.campaigns th, .records.campaigns td { padding: 6px 4px; }
.records.campaigns th {
  font-size: 9px;
  letter-spacing: .02em;      /* .08em over nine columns is a whole letter */
  line-height: 1.25;
  vertical-align: bottom;
  white-space: normal;        /* a heading may wrap; a figure may not */
  overflow-wrap: anywhere;
}
.records.campaigns td { font-size: 12px; }

/* Centred, heading and figure alike, so the two line up with each other.
   Right-alignment reads better down a column of long numbers; these are one to
   four digits in a narrow column, where centring is what makes the heading and
   the figure look like they belong together. */
.records.campaigns th.num,
.records.campaigns td.num {
  text-align: center;
  width: 12.3%;
}
.records.campaigns td.num { white-space: nowrap; }

/* ── Editing a drafted follow-up where it is read ──────────────────────── */

/* The draft used to be a read-only block. A first attempt by a machine that can
   only be corrected somewhere else is one that gets sent uncorrected, or not at
   all — so the fields are the draft, and what is in them when the button is
   pressed is what goes. */
.draftform { margin: 10px 0 6px; display: grid; gap: 8px; }
.draftform .field { margin: 0; }
.draftform textarea {
  font-family: var(--font-body);
  line-height: 1.55;
  min-height: 220px;
  resize: vertical;
}
.draftform .acts { margin-top: 4px; }

/* ── Putting a date on a task ──────────────────────────────────────────── */

/* The date is what you read; the picker sits on top of it, so the whole thing
   is the control and there is no second piece of furniture in a row that
   already carries four. */
.duepick { position: relative; display: inline-block; }
.duepick .due { cursor: pointer; }
.duepick .due.none { color: var(--dim); font-style: italic; }
.duepick .dueform { position: absolute; inset: 0; }
.duepick .dueform input {
  width: 100%; height: 100%; opacity: 0; cursor: pointer;
  border: 0; padding: 0; margin: 0; background: none;
}
.duepick:focus-within .due { outline: 2px solid var(--ring); outline-offset: 1px; }

/* Overdue says so in words as well as in colour. A red date is only red if you
   already knew what the date was for, and colour alone is no use to somebody
   who cannot see it. */
.tasks .meta .due.overdue { color: var(--bad); font-weight: 700; }

/* ── Adding a task to a record ─────────────────────────────────────────── */

/* Title, when and who, on one line where there is room and stacked where there
   is not. All three at the point of adding: a task raised without a date or a
   name is one that has to be corrected afterwards on two more controls, and
   mostly is not. */
.addtask {
  margin-top: 12px; display: flex; gap: 8px; align-items: flex-end;
  flex-wrap: wrap;
}
.addtask .what {
  flex: 1 1 220px; min-width: 0;
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius-sm); padding: 7px 11px; font: inherit; font-size: 13px;
}
.addtask label { display: flex; flex-direction: column; gap: 3px; }
.addtask .lbl { font-size: 10px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; }
.addtask input[type="date"], .addtask select {
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius-sm); padding: 6px 9px; font: inherit; font-size: 12.5px;
}

/* Payment stages on a deal. The amount column is the one people scan down, so
   it gets tabular figures — proportional digits make £4,962.00 and £750.00
   fail to line up at the decimal point, which is the only alignment that
   matters in a column of money. */
.table.stages td.mono { font-variant-numeric: tabular-nums; white-space: nowrap; }
.table.stages .stage { font-size: 11.5px; }
.stage.paid      { color: #15803d; }
.stage.invoiced  { color: var(--warn); }
.stage.due       { color: var(--text); }
.stage.scheduled { color: var(--dim); }
/* The reconciliation line. `.warn` is the whole point of the panel — a
   schedule that does not add up has to look wrong, not merely be wrong. */
.peek-note.warn {
  color: var(--warn);
  border-left: 3px solid var(--warn);
  padding-left: .7rem;
}

/* The campaign attribution table. Nine columns will not fit a laptop at a
   comfortable type size, so it scrolls inside its own panel rather than
   pushing the whole page sideways. */
.scroll-x { overflow-x: auto; }
.table.attribution { width: 100%; border-collapse: collapse; font-size: .9rem; white-space: nowrap; }
.table.attribution th, .table.attribution td { padding: .45rem .9rem .45rem 0; text-align: left; }
.table.attribution th.num, .table.attribution td.num {
  text-align: right; font-variant-numeric: tabular-nums;
}
.table.attribution thead th {
  border-bottom: 1px solid var(--border);
  font-size: .78rem; color: var(--dim); font-weight: 500;
}
.table.attribution tbody td { border-bottom: 1px solid var(--surface-2); }
/* Deals from outside the campaigns: present for honesty, set back so the
   campaign rows still read as the subject of the table. */
.table.attribution tr.aside td { color: var(--dim); font-style: italic; }
.table.attribution tfoot td {
  border-top: 1px solid var(--border); font-weight: 600; padding-top: .6rem;
}

/* What a headline figure leaves out, said on the same line as the figure.
   Amber rather than dim: an unpriced deal is a gap in the number above it,
   and a gap that reads as quiet decoration is a gap nobody closes. */
.stat .note .gap { color: var(--warn); }

/* A quotation drafted from a client's email.
   Shown as it was arrived at rather than as a single number: the questions it
   raises are often worth more to the conversation than the figure, so they are
   not folded away behind a link. */
.suggestion .said {
  margin: .6rem 0 .3rem; padding: .5rem .9rem;
  border-left: 2px solid var(--accent);
  color: var(--text); font-style: italic;
}
.suggestion .small { font-size: .82rem; }
.quote-box {
  margin: .8rem 0 0; padding: .9rem 1rem;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
}
.quote-total { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.quote-total b { font-size: 1.25rem; }
.quote-total .spacer { flex: 1; }
.quote-items { width: 100%; border-collapse: collapse; margin: .7rem 0 0; font-size: .88rem; }
.quote-items td { padding: .25rem .7rem .25rem 0; vertical-align: top; }
.quote-items td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.quote-asks { margin-top: .8rem; font-size: .9rem; }
.quote-asks ul, .quote-assumptions ul { margin: .3rem 0 0; padding-left: 1.1rem; }
.quote-assumptions { margin-top: .7rem; font-size: .86rem; color: var(--dim); }
.quote-assumptions summary { cursor: pointer; }
.quote-note { margin-top: .9rem; font-size: .9rem; }
.quote-note pre {
  margin: .3rem 0 0; padding: .7rem .9rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
  white-space: pre-wrap; word-break: break-word; font: inherit; font-size: .88rem;
  max-height: 22rem; overflow-y: auto;
}

/* Change request outcomes. Four statuses that are not degrees of the same
   thing — they are four different commercial facts, and reading them apart at
   a glance is the whole reason the record exists.

   Approved is money agreed, so it takes the same green as Won and Signed:
   this is billable and an invoice follows from it.

   Absorbed is the one worth seeing. It means the work was done for free, and
   it is the number that says what a fixed fee really cost — £1,250 of it on
   Gracey's against a £4,962 build. Amber, because it is not an error and not
   a success: it is margin leaving quietly, which is exactly the register
   amber carries elsewhere here.

   Proposed is awaiting a decision, so it takes the blue already used for work
   in flight. Not alarming, plainly unfinished.

   Rejected is muted, alongside Lost and Declined. The work was never done, so
   nothing was spent and nothing was earned — a fact to look up rather than a
   number to look at. */
.stage.approved {
  color: var(--accent); border-color: rgba(94,243,140,.35); background: var(--good-bg);
}
.stage.absorbed {
  color: var(--warn); border-color: rgba(245,194,107,.42); background: var(--warn-bg);
}
.stage.proposed {
  color: #7db8ff; border-color: rgba(125,184,255,.35); background: rgba(125,184,255,.12);
}
.stage.rejected {
  color: var(--muted); border-color: var(--border); background: var(--bg-2);
}

/* Commitments, tickets and the last few statuses that were still grey.

   Nothing new is invented here. The meanings already in this file are:

     green   the good outcome — done, in, earning
     amber   still to come, nothing wrong yet
     cyan    being worked on right now
     blue    waiting on somebody else
     red     should have happened and has not
     muted   a decision taken; no longer live

   Commitments are promises made to a client on a call, and the three states
   map straight onto that. Delivered is green, alongside Won and Received: we
   said we would and we did. Open takes the amber of an expected payment,
   which is the same fact in the other direction — something outstanding that
   is not yet a problem, and the whole reason the pane exists is that an
   unrecorded promise is what quietly erodes a fixed fee. Waived is muted
   beside Written off: the client agreed we no longer owe it, which is an
   outcome to look up rather than a number to look at.

   Open is deliberately shared with tickets, where it means the same thing. */
.stage.delivered {
  color: var(--accent); border-color: rgba(94,243,140,.4); background: rgba(94,243,140,.13);
  font-weight: 600;
}
.stage.open {
  color: var(--warn); border-color: rgba(245,194,107,.42); background: var(--warn-bg);
}
.stage.waived {
  color: var(--dim); border-color: var(--border); background: var(--bg-2);
}

/* Support tickets. In progress borrows the cyan of a project being built —
   somebody is on it now — and waiting on client the blue of scoping, because
   the next move is not ours and the clock should not read as our failure.
   Resolved is green; closed is quieter, being the filing rather than the fix. */
.stage.in_progress {
  color: #5ee1f3; border-color: rgba(94,225,243,.38); background: rgba(94,225,243,.13);
}
.stage.waiting_on_client {
  color: #7db8ff; border-color: rgba(125,184,255,.35); background: rgba(125,184,255,.12);
}
.stage.resolved {
  color: var(--accent); border-color: rgba(94,243,140,.35); background: var(--good-bg);
}
.stage.closed {
  color: var(--dim); border-color: var(--border); background: var(--bg-2);
}

/* An accepted proposal is a won deal by another name. An overdue invoice is
   money that should be here and is not, which is what red is for in this file
   — and the only one of these that should catch an eye across a room. */
.stage.accepted {
  color: var(--accent); border-color: rgba(94,243,140,.35); background: var(--good-bg);
}
.stage.overdue {
  color: var(--bad); border-color: rgba(255,122,122,.45); background: var(--bad-bg);
  font-weight: 600;
}

/* The one-click close at the top of Waiting on you. Sits above the cards as a
   summary of them rather than as another card, so it reads as an offer about
   the list and not as one more thing in it. */
.bulkclose {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; padding: 14px 16px; margin-bottom: 14px;
}
.bulkclose .muted { display: block; font-size: 12px; margin-top: 2px; }

/* How a piece of evidence reads on its own. Quiet by design: it sits in the
   source line under a quote, and the colour only has to separate the three
   readings from each other, not compete with the quote above it. */
.reading { font-weight: 600; }
.reading.asserts { color: var(--accent); }
.reading.intends { color: var(--warn); }
.reading.unclear { color: var(--dim); }

/* Sidebar counts are all the one green. Waiting on you and Support tickets are
   both "there is something here for you", and giving them two colours would
   invite reading a difference in urgency that is not there. */

/* One-click status actions on a record page. A row of small buttons rather
   than a menu: there are never more than three, and a menu would hide the
   thing somebody came to the page to do. */
.quickstatus {
  display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;
}

/* A standing term of the deal — "minor iterations are included in the monthly
   subscription" — rather than a promise with a completion. Blue, because it is
   live and ongoing rather than finished, and it should never read as either a
   thing done or a thing owed. */
.stage.standing {
  color: #7db8ff; border-color: rgba(125,184,255,.35); background: rgba(125,184,255,.12);
}
