/* Shared styling for both apps and the operator console.
   No framework, no build step — one stylesheet served from /shared. */

:root {
  --bg: #f5f6f8;
  --panel: #ffffff;
  --panel-2: #fafbfc;
  --ink: #1a1d21;
  --ink-2: #5b6470;
  --ink-3: #8b949e;
  --line: #e3e6ea;
  --line-2: #eef0f3;
  --accent: #2f6feb;
  --accent-ink: #ffffff;
  --accent-bg: #e8f0fe;
  --ok: #1a7f4b;
  --ok-bg: #e6f4ec;
  --warn: #9a6300;
  --warn-bg: #fdf3e0;
  --busy: #6b3fbd;
  --busy-bg: #f0eaff;
  --off: #6b7280;
  --off-bg: #eef0f3;
  --danger: #b3261e;
  --danger-bg: #fdeceb;
  --radius: 8px;
  --shadow: 0 1px 2px rgba(16, 22, 26, 0.06), 0 4px 12px rgba(16, 22, 26, 0.05);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14171a;
    --panel: #1c2024;
    --panel-2: #22262b;
    --ink: #e8eaed;
    --ink-2: #a8b1bb;
    --ink-3: #78828d;
    --line: #2d333a;
    --line-2: #262b31;
    --accent: #5b8def;
    --accent-ink: #0d1117;
    --accent-bg: #18243a;
    --ok: #6ede9d;
    --ok-bg: #14301f;
    --warn: #e8b45c;
    --warn-bg: #33260d;
    --busy: #b79bf5;
    --busy-bg: #241b3b;
    --off: #9aa3ad;
    --off-bg: #262b31;
    --danger: #f2857c;
    --danger-bg: #3a1512;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ---------- header ---------- */

/* One row, always.
 *
 * With `flex-wrap: wrap` the browser prefers wrapping to shrinking, so once the
 * company switcher and the identity control joined the header, the connection
 * pill dropped to a second line on its own and the header looked broken at
 * 1200px. Nothing here should wrap: the legend is the one elastic item, so it
 * takes two lines inside its own box and everything else stays put.
 */
header.top {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: nowrap;
  padding: 12px 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

header.top h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.badge-app {
  font: 600 11px/1 var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 5px 8px;
  border-radius: 5px;
  background: var(--accent);
  color: var(--accent-ink);
}

.spacer { flex: 1 1 auto; }

/* The only header item allowed to give ground. It wraps onto a second line
   inside its own box rather than pushing a control off the row, and below a
   narrow width it goes entirely — an explanation is worth less than knowing
   which company you are looking at. */
header.top .legend { flex: 0 1 auto; min-width: 7rem; }
header.top h1, header.top .badge-app, header.top .session,
header.top .conn, header.top .aistrip { flex: 0 0 auto; }
@media (max-width: 1150px) {
  /* Below this the row genuinely cannot hold everything, and `nowrap` would
     push the AI strip off the side of the page. Drop the explanation and let
     it wrap again — a second row is fine, a horizontal scrollbar is not. */
  header.top { flex-wrap: wrap; }
  header.top .legend { display: none; }
}

.who { display: flex; align-items: center; gap: 6px; }
.who label { color: var(--ink-2); font-size: 12px; }

/* ---------- session: who, and which company ---------- */

/* Two controls, not one. "Which company am I looking at" is a question about
   the data on screen; "who am I" is a question about the session. Merging them
   into one avatar menu is how the company name ends up somewhere you have to
   click to find, and every figure below it belongs to that company. */
.session { display: flex; align-items: center; gap: 8px; }

.ctl {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: 12.5px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--ink);
  cursor: pointer;
  max-width: 260px;
}
.ctl:hover { border-color: var(--ink-3); }
.ctl .ctl-icon { color: var(--ink-3); font-size: 11px; }
.ctl .ctl-main { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl .ctl-sub { color: var(--ink-3); font-size: 11.5px; }
.ctl .ctl-caret { color: var(--ink-3); font-size: 10px; }
.ctl.company { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.ctl.me { background: transparent; }
/* An invitation nobody has acted on. A dot rather than a number: there is
   either something waiting or there is not. */
.ctl-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn); box-shadow: 0 0 0 3px var(--warn-bg);
}

.menu {
  position: fixed;
  z-index: 80;
  min-width: 230px;
  max-width: 320px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow), 0 8px 24px rgba(0, 0, 0, 0.12);
}
.menu-head {
  font-size: 10.5px; font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-3); padding: 6px 8px 4px;
}
.menu-rule { height: 1px; background: var(--line); margin: 5px 0; }
.menu-text { padding: 4px 8px 8px; font-size: 12.5px; color: var(--ink-2); }
.menu-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  text-align: left; font: inherit; font-size: 13px;
  padding: 7px 8px; border: none; border-radius: 6px;
  background: transparent; color: var(--ink); cursor: pointer;
}
.menu-item:hover { background: var(--panel-2); border-color: transparent; }
.menu-item.on { font-weight: 600; }
.menu-tick { width: 12px; color: var(--accent); font-size: 11px; }
.menu-label { flex: 1 1 auto; min-width: 0; }
.menu-fine { display: block; font-size: 11.5px; color: var(--ink-3); font-weight: 400; }

/* ---------- doors: sign in, choose a company, set one up ----------
 *
 * Called doors, not gates, and the distinction is load-bearing. A **gate**
 * further down this file is the small block inside a card explaining why a
 * button is frozen — Send, Post, File, Settle. A **door** is a full-page
 * overlay standing in front of the whole application.
 *
 * They were both called `.gate` for a while. The door's `position: fixed;
 * inset: 0` then applied to every in-card gate too, turning each one into a
 * green rectangle covering the browser window. It survived a screenshot run,
 * because the capture script photographed the rectangle without complaint.
 */

/* Full page, not a modal. None of these is a step in a task you were doing —
   there is nothing behind them to go back to, and dimming a screen full of
   figures the person may not be allowed to see is the wrong picture. */
body.door-open { overflow: hidden; }
.door {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  overflow: auto;
  display: grid;
  place-items: start center;
  padding: 8vh 20px 40px;
}
.door-card {
  width: min(460px, 100%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
  padding: 26px 26px 22px;
}
.door-app { display: flex; align-items: center; gap: 9px; font-size: 13px;
            color: var(--ink-2); margin-bottom: 18px; }
.door-card h1 { margin: 0 0 8px; font-size: 20px; font-weight: 650; letter-spacing: -0.01em; }
.door-why { margin: 0 0 20px; color: var(--ink-2); font-size: 13.5px; line-height: 1.6; }
.door-fine { color: var(--ink-3); font-size: 12px; line-height: 1.55; margin: 8px 0 0; }

button.big { width: 100%; justify-content: center; padding: 11px 16px; font-size: 14px; }

.door-list { display: grid; gap: 8px; }
.door-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  font: inherit; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 9px; background: var(--panel-2); color: var(--ink); cursor: pointer;
}
.door-item:hover:not(:disabled) { border-color: var(--accent); background: var(--accent-bg); }
.door-name { font-weight: 600; flex: 1 1 auto; }
.door-id { display: block; font: 11.5px var(--mono); color: var(--ink-3); font-weight: 400; }
.door-role { color: var(--ink-3); font-size: 12px; }

.door-invites { margin-top: 18px; }
.door-invites h2 { font-size: 11px; font-weight: 650; text-transform: uppercase;
                   letter-spacing: 0.05em; color: var(--ink-3); margin: 0 0 8px; }
.door-invite {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 13px; border: 1px solid var(--warn);
  background: var(--warn-bg); border-radius: 9px; margin-bottom: 8px;
}
.door-invite > div { flex: 1 1 auto; min-width: 0; }

.door-form { display: grid; gap: 6px; }
.door-form + .door-form { margin-top: 16px; }
.door-form label { font-size: 12px; color: var(--ink-2); font-weight: 550; }
.door-form button { margin-top: 8px; }
.door-row { display: flex; gap: 8px; }
.door-row input { flex: 1 1 auto; }
.door-row button { margin-top: 0; }

.door-or {
  display: flex; align-items: center; gap: 12px;
  margin: 20px 0; color: var(--ink-3); font-size: 12px;
}
.door-or::before, .door-or::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}

.door-note { font-size: 12.5px; margin-top: 12px; }
.door-note.err { color: var(--danger); }
.door-note.ok { color: var(--ok); }
.door-note:empty { display: none; }

.door-foot {
  display: flex; align-items: center; gap: 10px;
  margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--ink-2);
}
.door-foot span { flex: 1 1 auto; }

/* What accepting an invitation actually grants, next to the role that grants
   it. Warm rather than red: it is a consequence to read, not a refusal. */
.grants {
  margin: -2px 0 14px; padding: 10px 12px;
  background: var(--warn-bg); border-left: 3px solid var(--warn);
  border-radius: 0 6px 6px 0;
  font-size: 12.5px; line-height: 1.55; color: var(--ink-2);
}
.grants:empty { display: none; }

/* One row per person who can see this company. */
.person {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line-2);
}
.person:last-of-type { border-bottom: none; }
.person-who { flex: 1 1 auto; min-width: 0; }
.person.waiting .door-name { color: var(--ink-2); font-weight: 500; }
.person-head {
  margin: 16px 0 2px; font-size: 11px; font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--ink-3);
}

.codebox {
  display: flex; align-items: center; gap: 10px;
  margin: 12px 0; padding: 12px 14px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.codebox code { flex: 1 1 auto; font: 15px var(--mono); word-break: break-all; }

.conn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--ink-2);
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
}
/* The AI strip. Lives in the header because the requirement is that the cost
   is visible *while you work*, not on a page you remember to visit. Three
   states, and the colour is never the only signal — the words change too. */
.aistrip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-family: inherit;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--off-bg);
  color: var(--ink-2);
  cursor: pointer;
}
.aistrip:hover { border-color: var(--accent); }
.aistrip .sparkle { font-size: 13px; filter: grayscale(1); opacity: .6; }
.aistrip.on { background: var(--accent-bg); border-color: var(--accent);
              color: var(--accent); }
.aistrip.on .sparkle { filter: none; opacity: 1; }
.aistrip.capped { background: var(--danger-bg); border-color: var(--danger);
                  color: var(--danger); }
.aistrip b { font-weight: 600; font-variant-numeric: tabular-nums; }
.aistrip .sep { opacity: .45; }

/* A euro figure that is an estimate is written with a leading ≈ everywhere.
   The class exists so it cannot be forgotten in one place and not another. */
.approx::before { content: "≈"; opacity: .6; margin-right: 1px; }

.budgetbar {
  height: 6px; border-radius: 3px; background: var(--off-bg);
  overflow: hidden; margin-top: 6px;
}
.budgetbar > span { display: block; height: 100%; background: var(--accent); }
.budgetbar.warn > span { background: var(--warn); }
.budgetbar.full > span { background: var(--danger); }

/* A proposal, before anybody accepts it. Deliberately not styled like saved
   data: it is dashed and tinted so nothing on screen can be mistaken for a
   figure the business has actually committed to. */
.proposal {
  border: 1px dashed var(--accent);
  background: var(--accent-bg);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-top: 10px;
}
.proposal h4 {
  margin: 0 0 6px; font-size: 12px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--accent);
}
.proposal .caveat { color: var(--ink-3); font-size: 12px; margin-top: 8px; }
.provenance { color: var(--ink-3); font-size: 11.5px; }
.chip.simulated { background: var(--off-bg); color: var(--off); }

/* The capability table. The allowlist column is the interesting one but it is
   monospace and greedy, so the widths are set rather than negotiated. */
table.caps th:first-child, table.caps td:first-child { width: 34%; }
table.caps th:last-child, table.caps td:last-child { width: 1%; white-space: nowrap; }
table.caps code { font-size: 11px; padding: 1px 3px; }

.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--off); }
.conn.live .dot { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-bg); }
.conn.down .dot { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-bg); }

/* ---------- layout ---------- */

main.cols {
  display: grid;
  /* The entity table carries more columns than the task list, so it gets more room. */
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: 16px;
  padding: 16px 20px 60px;
  align-items: start;
  max-width: 1500px;
  margin: 0 auto;
}

@media (max-width: 1000px) {
  main.cols { grid-template-columns: minmax(0, 1fr); }
}

/* Two panels stacked inside one grid column. */
.stack { display: grid; gap: 16px; align-content: start; min-width: 0; }

section.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.panel > h2 {
  margin: 0;
  padding: 12px 14px;
  font-size: 13px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-2);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
}

.panel-body { padding: 14px; }
.panel-body.flush { padding: 0; }

/* ---------- forms ---------- */

input, select, textarea, button {
  font: inherit;
  color: inherit;
}

input[type="text"], input[type="number"], select, textarea {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 7px 9px;
  width: 100%;
  min-width: 0;
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  outline-offset: -1px;
  border-color: var(--accent);
}
textarea { resize: vertical; min-height: 60px; }

.field { display: grid; gap: 4px; margin-bottom: 10px; }
.field > label { font-size: 12px; color: var(--ink-2); font-weight: 550; }
.row { display: flex; gap: 8px; flex-wrap: wrap; }
.row > * { flex: 1 1 140px; }
.row.tight > * { flex: 0 0 auto; }

button {
  border: 1px solid var(--line);
  background: var(--panel-2);
  border-radius: 6px;
  padding: 7px 12px;
  cursor: pointer;
  font-weight: 550;
  white-space: nowrap;
}
button:hover:not(:disabled) { border-color: var(--ink-3); }
button:disabled { opacity: 0.45; cursor: not-allowed; }

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
button.primary:hover:not(:disabled) { filter: brightness(1.08); }
button.small { padding: 4px 9px; font-size: 12px; }
button.link {
  background: none;
  border: none;
  color: var(--accent);
  padding: 2px 0;
  text-decoration: underline;
}
button.danger { color: var(--danger); }

/* ---------- tables ---------- */

table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left;
  padding: 9px 10px;
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
}
th {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  font-weight: 600;
  background: var(--panel-2);
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--panel-2); }
td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
td.ref { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
.table-wrap { overflow-x: auto; }

/* The free-text column is capped so a long description wraps instead of
   widening the whole table. Without this the layout is data-dependent: one
   verbose voucher description pushes the action buttons off the edge. */
td.text { max-width: 130px; }

td .row { flex-wrap: nowrap; gap: 5px; }
td button.small { padding: 3px 7px; font-size: 11px; }

/* Row actions are glyphs, not words. Four labelled buttons per row pushed the
   table past the edge of its own panel and left Delete reachable only by
   scrolling; four icons fit on one line with room to spare. Each one still
   carries a title and an aria-label. */
td.actions { width: 1%; white-space: nowrap; }

button.icon {
  padding: 3px 7px;
  font-size: 14px;
  line-height: 1.25;
  min-width: 30px;
  text-align: center;
}
button.icon .tally {
  font: 600 10px/1 var(--mono);
  color: var(--ink-3);
  margin-left: 2px;
  vertical-align: 1px;
}
button.icon:hover:not(:disabled) { background: var(--panel); }

/* One arrow opens a record; its weight says whether you can change anything
   once you are in. Deliberately a lightness and border difference rather than a
   hue one — a colour-only signal is invisible to a colour-blind reader — and
   the tooltip says the same thing in words regardless. */
button.icon.open-edit {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}
button.icon.open-edit:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}
button.icon.open-view { color: var(--ink-3); }
/* A shade smaller inside a drawer's tables, where the type is smaller too. */
.detail-block button.icon { font-size: 13px; padding: 2px 6px; min-width: 26px; }
/* The task card's bar mixes one word (the verb) with glyphs beside it. */
.task-actions button.icon { font-size: 13px; }

/* A journal subtotal, leading the entries it sums. Tinted and rule-topped so a
   long journal reads as a stack of periods rather than one undifferentiated
   list. */
tr.group-head td {
  background: var(--panel-2);
  border-top: 2px solid var(--line);
  font-weight: 650;
}
tr.group-head td.text { color: var(--danger); font-size: 11px; }

/* Money pointing the other way. Sign *and* colour: the minus carries it for a
   reader who cannot see the tint, and the tint makes a column of mixed
   directions scannable without reading every figure. */
.neg { color: var(--danger); }
.task-meta.shortfall, .shortfall { color: var(--warn); }
tr.group-head:hover td { background: var(--panel-2); }

/* The record number opens the record too, so the primary action never depends
   on recognising a picture. */
button.recordlink {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-family: var(--mono);
  font-size: 12px;
  color: inherit;
  text-align: left;
  border-bottom: 1px dotted var(--ink-3);
  cursor: pointer;
}
button.recordlink:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* Two columns inside a drawer's inline editor, so a date and a counterparty sit
   side by side instead of stacking down the page. */
.editor-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 14px;
}
.editor-fields .field.wide { grid-column: 1 / -1; }
.editor-fields textarea { min-height: 48px; }

/* ---------- chips ---------- */

.chip {
  display: inline-block;
  font: 600 11px/1.6 inherit;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--off-bg);
  color: var(--off);
  white-space: nowrap;
}
.chip.draft { background: var(--off-bg); color: var(--off); }
.chip.sent, .chip.open { background: var(--warn-bg); color: var(--warn); }
.chip.in_progress { background: var(--busy-bg); color: var(--busy); }
.chip.booked, .chip.posted, .chip.done, .chip.paid { background: var(--ok-bg); color: var(--ok); }
.chip.written_off, .chip.cancelled { background: var(--danger-bg); color: var(--danger); }

/* A thin strip under a panel header for filters and switches. */
.panel-strip {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 14px;
  border-bottom: 1px solid var(--line-2);
  background: var(--panel-2);
  font-size: 12px;
}
/* Which client's tasks the panel is showing. Words, not an icon: "all clients"
   and "this client" are different enough that a glyph would have to be learned. */
.scopebar { display: inline-flex; align-items: center; gap: 5px; margin-left: auto; }
.scope-label { color: var(--ink-3); }
button.scope {
  padding: 3px 9px; font-size: 12px; border-radius: 999px;
  background: transparent; color: var(--ink-2); border: 1px solid transparent;
}
button.scope:hover:not(.on) { border-color: var(--line); color: var(--ink); }
button.scope.on {
  background: var(--panel); border-color: var(--line); color: var(--ink); font-weight: 600;
}
button.scope .count { margin-left: 5px; }

/* A task belonging to a client other than the one on screen. Dimmed and
   marked, because it is here to be noticed rather than worked on. */
.task.elsewhere { background: var(--panel-2); }
.task.elsewhere .task-title { color: var(--ink-2); }
.chip.client { background: var(--busy-bg); color: var(--busy); }

.switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-2);
  cursor: pointer;
  user-select: none;
}
.switch input { accent-color: var(--accent); cursor: pointer; margin: 0; }
.switch:hover { color: var(--ink); }

/* ---------- the period filter ---------- */

/* Its own bar above the panels it governs, rather than inside one of them: it
   applies to every view in the app, and a control that lives inside one panel
   reads as if it only filtered that panel. */
.filterbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  font-size: 12px;
}
.filterbar select {
  padding: 3px 6px;
  font-size: 12px;
  min-width: 0;
}
.filterbar .switch > span { color: var(--ink-3); }
.filterbar button { padding: 2px 8px; font-family: var(--mono); }
.filterbar .legend { margin-left: auto; text-align: right; }
.count.late { background: var(--danger-bg); color: var(--danger); }

/* ---------- closed periods ---------- */

/* A figure you can open. The affordance has to be visible before the pointer
   arrives, or nobody discovers the drill-down. */
.tile.drillable { cursor: pointer; }
.tile.drillable:hover, .tile.drillable:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
  outline: none;
}
.tile.drillable .l::after {
  content: " ↗";
  color: var(--ink-3);
  font-weight: 400;
}

/* ---------- tasks ---------- */

.tabs { display: flex; gap: 4px; margin-left: auto; }
.tabs button {
  border: 1px solid transparent;
  background: none;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: none;
  letter-spacing: 0;
}
.tabs button[aria-selected="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.count {
  display: inline-block;
  min-width: 18px;
  text-align: center;
  font: 600 11px/16px var(--mono);
  border-radius: 9px;
  padding: 0 4px;
  margin-left: 4px;
  background: color-mix(in srgb, currentColor 18%, transparent);
}

.task {
  border-bottom: 1px solid var(--line-2);
  padding: 12px 14px;
}
.task:last-child { border-bottom: none; }
.task.fresh { animation: flash 1.6s ease-out; }
@keyframes flash {
  from { background: color-mix(in srgb, var(--accent) 16%, transparent); }
  to { background: transparent; }
}

.task-head { display: flex; align-items: baseline; gap: 8px; }
.task-title { font-weight: 600; flex: 1 1 auto; }
.task-meta { color: var(--ink-3); font-size: 12px; margin-top: 3px; }
/* The note preview under a row title stays one line — notes grow as tasks
   append answers to them, and a growing note must not reflow the table. */
td .task-meta {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* …except where the sub-line is a *reason*, which has to be readable in full.
   A truncated explanation is the same problem as no explanation. */
td .task-meta.wrap { max-width: 34ch; white-space: normal; overflow: visible; }

/* Who is holding a task. A band, not a fragment at the end of a grey line:
   with two accountants on one client this is what stops both of them drafting
   the same voucher, and it only works if it is read. */
.held {
  margin: 8px 0 2px; padding: 7px 10px; border-radius: 6px;
  font-size: 12.5px; background: var(--busy-bg); color: var(--busy);
}
.held.mine { background: var(--ok-bg); color: var(--ok); }
.held.stale { background: var(--warn-bg); color: var(--warn); }

.task-desc { color: var(--ink-2); font-size: 13px; margin-top: 6px; white-space: pre-wrap; }

.kv {
  margin-top: 8px;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 2px 10px;
  font-size: 12px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 8px 10px;
}
.kv dt { color: var(--ink-3); }
.kv dd { margin: 0; font-family: var(--mono); word-break: break-word; }

/* ---------- delivered documents & answers ---------- */

.doc {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 6px;
}
.doc + .doc { margin-top: 6px; }
.doc-thumb {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 4px;
  border: 1px solid var(--line);
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--panel);
  text-decoration: none;
}
.doc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.doc-thumb.doc-file {
  font: 700 11px/1 var(--mono);
  color: var(--ink-2);
  letter-spacing: 0.05em;
}
.doc-meta { min-width: 0; flex: 1 1 auto; }
.doc-meta a { color: inherit; }
.doc-meta a:hover { color: var(--accent); }

.answer {
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--ok-bg);
  border-left: 3px solid var(--ok);
  border-radius: 0 6px 6px 0;
  white-space: pre-wrap;
  font-size: 13px;
}
.answer-label {
  font: 600 10px/1.6 inherit;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ok);
  margin-bottom: 2px;
}

.clip {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- per-row task signal ---------- */

/* Something the other side did that has not been read. The row itself carries
   the signal, so it is visible while scanning the list rather than only after
   reading each badge. */
tr.row-new > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.row-new { background: color-mix(in srgb, var(--accent) 7%, transparent); }
tr.row-new:hover { background: color-mix(in srgb, var(--accent) 11%, transparent); }

/* Sent and still waiting on the other side — present, but quiet. */
tr.row-waiting > td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

.badge-new {
  animation: pulse 2.2s ease-in-out 3;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 0%, transparent); }
}
.badge-mine { border-color: var(--warn); color: var(--warn); }
.badge-waiting { color: var(--ink-3); }

.headline {
  font-size: 11px;
  color: var(--accent);
  margin-top: 3px;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#unseen {
  font: 600 11px/1 inherit;
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  white-space: nowrap;
}

/* ---------- activity list ---------- */

.act {
  padding: 10px 0;
  border-bottom: 1px solid var(--line-2);
}
.act:last-child { border-bottom: none; }
.act-new {
  margin: 0 -8px;
  padding: 10px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.act-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
  justify-content: space-between;
  font-weight: 600;
  font-size: 13px;
}

/* ---------- gates: why a button is frozen ----------
   Small, inside a card. Not to be confused with the doors above. */

.gate {
  margin-bottom: 10px;
  padding: 8px 10px;
  border-radius: 6px;
  border-left: 3px solid var(--off);
  background: var(--off-bg);
  font-size: 12px;
}
.gate-ok { border-left-color: var(--ok); background: var(--ok-bg); }
.gate-blocked { border-left-color: var(--warn); background: var(--warn-bg); }
.gate-head { font-weight: 600; margin-bottom: 3px; }
.gate-ok .gate-head, .gate-ok .gate-line { color: var(--ok); }
.gate-blocked .gate-head { color: var(--warn); }
.gate-list { margin: 0; padding-left: 16px; color: var(--warn); }
.gate-list li { margin: 1px 0; }

/* A frozen action explains itself in place rather than only on hover. The width
   is capped so a long reason wraps instead of stretching its column. */
.blockers {
  margin: 0;
  padding-left: 15px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--warn);
  list-style: disc;
  /* Wide enough that a reason like "VAT code is missing on 2 row(s)" reads in
     two lines rather than five. The list itself is capped to two reasons in
     Shell.blockerList, which is what keeps the row from growing instead. */
  max-width: 24ch;
}
.blockers li { margin: 1px 0; }
.gate-list { max-width: 60ch; }

.kindbar { display: flex; gap: 4px; margin-left: 8px; }
.kindbar button {
  border: 1px solid transparent;
  background: none;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: none;
  letter-spacing: 0;
}
.kindbar button[aria-selected="true"] {
  background: var(--ink-2);
  border-color: var(--ink-2);
  color: var(--panel);
}

tr.frozen td:first-child::before {
  content: "";
}
/* Capped, so a wrapped list of reasons cannot widen the whole table. */
td.gatecell { min-width: 110px; max-width: 155px; }

input[type="file"] {
  width: 100%;
  font-size: 12px;
  padding: 6px;
  background: var(--panel-2);
  border: 1px dashed var(--line);
  border-radius: 6px;
}

.task-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; align-items: center; }
.exec-form { margin-top: 10px; padding: 10px; border: 1px dashed var(--line); border-radius: 6px; }
.hidden { display: none !important; }

.empty { padding: 26px 14px; text-align: center; color: var(--ink-3); font-size: 13px; }

.history { margin-top: 8px; font-size: 12px; border-left: 2px solid var(--line); padding-left: 10px; }
.history li { margin-bottom: 4px; list-style: none; }
.history ul { margin: 0; padding: 0; }
.history .t { font-family: var(--mono); color: var(--ink-3); }

/* ---------- modal ---------- */

.veil {
  position: fixed;
  inset: 0;
  background: rgba(10, 12, 15, 0.45);
  display: grid;
  place-items: center;
  z-index: 60;
  padding: 20px;
}
.modal {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
  width: min(520px, 100%);
  max-height: 90vh;
  overflow: auto;
}
.modal.wide { width: min(920px, 100%); }
.modal h3 { margin: 0; padding: 14px 16px; border-bottom: 1px solid var(--line); font-size: 15px; }

.detail-block + .detail-block { margin-top: 18px; }
.detail-block > h4 {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
}
.detail-block table { font-size: 13px; }
.detail-block td, .detail-block th { padding: 6px 8px; }

.cross {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px dashed color-mix(in srgb, var(--accent) 50%, transparent);
}
.cross:hover { border-bottom-style: solid; }

.kv-inline { display: flex; flex-wrap: wrap; gap: 4px 18px; font-size: 12px; color: var(--ink-2); }
.kv-inline strong { color: var(--ink); font-variant-numeric: tabular-nums; }

tr.unbalanced td { background: color-mix(in srgb, var(--danger) 8%, transparent); }
td.dr, td.cr { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.modal .body { padding: 16px; }
.modal .foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}
.hint { font-size: 12px; color: var(--ink-3); margin-top: -4px; margin-bottom: 10px; }

/* ---------- toasts ---------- */

#toasts {
  position: fixed;
  right: 16px;
  bottom: 16px;
  display: grid;
  gap: 8px;
  z-index: 80;
  max-width: min(380px, calc(100vw - 32px));
}
.toast {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  box-shadow: var(--shadow);
  padding: 10px 12px;
  font-size: 13px;
  animation: slide 0.2s ease-out;
}
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--danger); }
.toast .h { font-weight: 600; margin-bottom: 2px; }
.toast .b { color: var(--ink-2); font-size: 12px; }
@keyframes slide { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }

/* The totals strip under the books header. */
#books-totals { flex-wrap: wrap; gap: 4px 16px; color: var(--ink-2); }
#books-totals strong { color: var(--ink); font-variant-numeric: tabular-nums; }
#books-totals .pending { color: var(--warn); }

/* ---------- figure tiles ---------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.tile {
  padding: 9px 11px;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  background: var(--panel-2);
  min-width: 0;
}
.tile .l {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile .n {
  font-size: 19px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  margin-top: 1px;
}
.tile .s { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
.tile.good .n { color: var(--ok); }
.tile.owed .n { color: var(--warn); }
.tile.bad .n { color: var(--danger); }
.tile.wide { grid-column: 1 / -1; }

.note-line {
  margin-top: 10px;
  padding: 8px 10px;
  border-radius: 6px;
  border-left: 3px solid var(--warn);
  background: var(--warn-bg);
  font-size: 12px;
  color: var(--warn);
}

/* Deliberately not the warn palette a plain .note-line uses. A closed period is
   not a warning that something is missing — it is a settled fact, and it reads
   better as definite than as alarming. */
.locked-note {
  border-left-color: var(--off);
  background: var(--off-bg);
  color: var(--ink-2);
}
.locked-note strong { color: var(--ink); }

/* ---------- console ---------- */

.log { font-family: var(--mono); font-size: 12px; }
.log tbody tr td { padding: 6px 10px; }
.ev { font-weight: 600; }
.ev-TaskCreated { color: var(--accent); }
.ev-TaskClaimed { color: var(--busy); }
.ev-TaskReleased { color: var(--warn); }
.ev-TaskCompleted { color: var(--ok); }
.ev-TaskCancelled { color: var(--danger); }
.ev-TaskCommented { color: var(--ink-2); }

.stats { display: flex; gap: 18px; flex-wrap: wrap; }
.stat .n { font-size: 22px; font-weight: 650; font-variant-numeric: tabular-nums; }
.stat .l { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-3); }

.legend { font-size: 12px; color: var(--ink-2); }
.legend code {
  font-family: var(--mono);
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: 1px 4px;
}
