/* ============================================================
   Reaction ERP — Theme
   Modern visual system on the corporate palette (spec 13.2.3)
   ============================================================ */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../assets/fonts/inter-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Noto Sans Thai';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../assets/fonts/noto-sans-thai-var.woff2') format('woff2');
}
/* The figures' type (drawings, calculation sketches) is IBM Plex Sans Thai,
   shipped in assets/fonts. It used to be declared only by the paper module,
   when a paper was opened — so a figure drawn before that fell back to
   whatever the OS had, and a Windows PC drew it differently from a Mac
   (round 8b, 2026-10-06: "any OS shows much the same look"). Declared here,
   it is the app's own on every page; the browser fetches a weight and a
   script only when a glyph of it is used. */
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-thai-400.woff2') format('woff2'); unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-thai-500.woff2') format('woff2'); unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-thai-600.woff2') format('woff2'); unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-thai-700.woff2') format('woff2'); unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-latin-400.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-latin-500.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-latin-600.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'IBM Plex Sans Thai'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/ibm-plex-sans-thai-latin-700.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; }

:root {
  /* Core palette */
  --c-main: #0070C0;
  --c-main-deep: #005A9E;
  /* The brand blue is a FILL colour: #0070C0 carries white text well, and on
     a dark surface it reads at 3.08:1 as text — a blue word nobody can read.
     So the blue that is TEXT is its own token, redefined per theme, and every
     rule that colours words with the brand blue uses this one. The house
     already had the instinct (`[data-theme="dark"] .more { color: #6fc0ff }`);
     this is that instinct as a token instead of a one-off. */
  --c-main-fg: #005A9E;
  --c-menu: #23374B;
  --c-menu-deep: #1A2A3B;
  --c-accent: #F6C02A;
  --c-navy: #387491;
  --c-blue: #0070C0;
  --c-green: #00B050;
  --c-yellow: #F6C02A;
  --c-orange: #FF964F;
  --c-red: #F05A5A;
  /* The red a delete BUTTON is painted with — darker and less shrill than
     --c-red, which is the red of a warning word or a failed field. White text
     sits on it at 5.9:1. */
  --c-danger-btn: #C0392B; --c-danger-btn-deep: #99271C;
  --c-purple: #7030A0;
  --c-grey: #919191;
  --c-bluegrey: #7B8189;
  --c-darknavy: #23374B;

  /* Surfaces & text */
  color-scheme: light;
  --bg: #f4f6fa;
  --surface: #ffffff;
  --surface-2: #fafbfd;        /* barely raised: table heads, code, footers */
  --surface-3: #eef2f7;        /* fills: secondary buttons, bars, chips     */
  --surface-3-hover: #e5ebf3;
  --row-hover: #f2f7fc;
  --input-bg: #ffffff;
  --border: #e5eaf1;
  /* Row dividers inside a document's detail and a filled form. They used to be
     defined ONLY inside .pdoc (the printed sheet), so on screen every
     var(--line, …) fell through to its near-white fallback and sat on a dark
     surface at full contrast, while the .kv-line card beside it was barely
     there. Same weight as --border now, which is the one the owner pointed at
     (2026-10-13). The paper keeps its own value; it is always light. */
  --line: #e5eaf1;
  --border-strong: #d5dde8;
  --border-hover: #b9c6d6;
  --text: #1c2733;
  /* Captions, table headers, pill text. It was #68788d, which is 4.50:1 on
     white — exactly on the line, and therefore BELOW the line everywhere it
     is actually used: 4.35:1 on --surface-2 (every table header in the
     system) and 4.00:1 on --surface-3 (every pill and chip). A contrast
     checker that measures against the page's white background says that is
     fine; the text is not on the page's background. Darkened to #5F6E82,
     which is 5.20:1 on white, 5.02:1 on --surface-2 and 4.63:1 on
     --surface-3 — the quiet grey it was meant to be, on all three of the
     surfaces it lands on (2026-10-15, audit item 10). */
  --text-dim: #5F6E82;
  --heading: #23374B;
  --star-off: #d8dee8;
  --topbar-bg: rgba(255, 255, 255, .85);

  /* ---------- names that were used before they existed ----------
     Six variables were referenced across the stylesheet — --muted 23 times,
     --danger 19, --accent 17 — and none of them was ever defined. CSS does not
     complain: `color: var(--muted)` with no --muted simply inherits, so a
     caption came out the same colour as body text and a stage badge came out
     white on white. They are aliases of the real tokens, defined in BOTH
     themes so nothing inherits the light palette in the dark one. */
  --accent: var(--c-main);
  --danger: var(--danger-strong);
  --muted: var(--text-dim);
  --success-text: var(--ok-fg);
  --warning-text: var(--warn-fg);
  --font-mono: 'Inter', 'Noto Sans Thai', sans-serif;

  /* State colours as text/background, tuned per theme for contrast */
  --ok-strong: #068348;   --ok-fg: #077a41;
  --danger-strong: #d33f3f; --danger-fg: #b93a3a;
  --warn-strong: #e0762d;  --warn-fg: #9c4e14;
  --gold-strong: #b28908;
  --info-fg: #0b5e9b;
  --pill-pending-fg: #9c4e08;
  --pill-active-fg: #05703d;
  --pill-inactive-fg: #5d6874;
  --pill-cancelled-fg: #b53434;
  --pill-blue-fg: #005A9E;
  --note-warm-bg: #fff8ec; --note-warm-border: #f0dcb4;
  --note-ok-bg: #f1f9f4;   --note-ok-border: #cfe8d8;
  /* One corner, one height, one ring (round 8b, 2026-10-06 — the reui.io
     look the owner pointed at: flat, 8 px corners, 1 px lines from these
     tokens, a clear 2 px focus ring, controls 36 px and 32 px tall). */
  --radius: 8px;
  --radius-sm: 6px;
  --radius-lg: 12px;
  --ctl-h: 36px;            /* a button, a field, a dropdown            */
  --ctl-h-sm: 32px;         /* the small ones: in a row, in a pager      */
  --ring: 0 0 0 2px color-mix(in srgb, var(--c-main) 40%, transparent);
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
  /* ---------- one table look ----------
     table.data (every register), table.table (the compact ones left), the
     portal's table and the editable grids all read these, so a header, a
     row and a total are the same size wherever a table is. */
  --tbl-head-fs: 11.5px;
  --tbl-head-pad: 9px 12px;
  --tbl-cell-pad: 9px 12px;
  --tbl-fs: 13px;
  --tbl-foot-pad: 11px 12px;
  --tbl-foot-fs: 13px;
  --shadow-sm: 0 1px 2px rgba(28, 39, 51, .05), 0 1px 6px rgba(28, 39, 51, .04);
  --shadow: 0 10px 34px rgba(28, 39, 51, .14);
  --shadow-lg: 0 24px 70px rgba(16, 26, 36, .28);
  --font: 'Inter', 'Noto Sans Thai', 'Leelawadee UI', 'Segoe UI', Tahoma, sans-serif;

  /* Single stacking scale — every layered element takes its z-index from here
     so nothing can ever be accidentally covered (spec 13.3.7). */
  --z-sticky: 2;      /* sticky table headers                */
  --z-topbar: 100;    /* topbar above the scrolling view     */
  --z-dropdown: 1800; /* in-form dropdown lists — above modals */
  --z-modal: 800;     /* modal dialogs + backdrop            */
  --z-loader: 1500;   /* full-screen loading curtain         */
  --z-popover: 1900;  /* notifications & quick search        */
  --z-tooltip: 2000;  /* mouse-following tooltip             */
  --z-toast: 2147483000; /* status toasts — always on top    */
}

/* ============================================================
   Dark theme. Same components, same geometry — only the tokens
   move. html[data-theme] is set before first paint (js/i18n.js),
   so there is never a white flash.
   ============================================================ */
[data-theme="dark"] {

  /* ---------- names that were used before they existed ----------
     Six variables were referenced across the stylesheet — --muted 23 times,
     --danger 19, --accent 17 — and none of them was ever defined. CSS does not
     complain: `color: var(--muted)` with no --muted simply inherits, so a
     caption came out the same colour as body text and a stage badge came out
     white on white. They are aliases of the real tokens, defined in BOTH
     themes so nothing inherits the light palette in the dark one. */
  --accent: var(--c-main);
  --danger: var(--danger-strong);
  --muted: var(--text-dim);
  --success-text: var(--ok-fg);
  --warning-text: var(--warn-fg);
  --font-mono: 'Inter', 'Noto Sans Thai', sans-serif;
  color-scheme: dark;
  --bg: #10161d;
  --surface: #1a232e;
  --surface-2: #202b38;
  --surface-3: #263344;
  --surface-3-hover: #2e3d51;
  --row-hover: #212e3d;
  --input-bg: #141c25;
  --border: #2a3646;
  --line: #2a3646;
  --border-strong: #38475c;
  --border-hover: #4a5e77;
  --text: #dde6f0;
  --text-dim: #93a3b8;
  --heading: #dbe7f4;
  --star-off: #3a4a5e;
  --topbar-bg: rgba(19, 27, 36, .85);
  --c-menu: #1d2b3a;
  --c-menu-deep: #131e2a;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3), 0 1px 6px rgba(0, 0, 0, .22);
  --shadow: 0 10px 34px rgba(0, 0, 0, .5);
  --shadow-lg: 0 24px 70px rgba(0, 0, 0, .62);

  --ok-strong: #37c584;   --ok-fg: #64d8a4;
  --danger-strong: #ff7d75; --danger-fg: #ff9b94;
  /* On a dark page the same red reads as brown; lifted a step, still dark
     enough for white text (5.1:1). */
  --c-danger-btn: #CE4034; --c-danger-btn-deep: #A62C22;
  --warn-strong: #ffa161;  --warn-fg: #ffb27f;
  --gold-strong: #ffd34d;
  --info-fg: #7cc4ff;
  --pill-pending-fg: #ffb27a;
  --pill-active-fg: #56d698;
  --pill-inactive-fg: #a8b5c4;
  --pill-cancelled-fg: #ff9d9d;
  --pill-blue-fg: #6fc0ff;
  --c-main-fg: #6FC0FF;
  --note-warm-bg: #2b2517; --note-warm-border: #52431f;
  --note-ok-bg: #16281f;   --note-ok-border: #29523f;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
  font-family: var(--font);
  font-size: 13.5px;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  letter-spacing: -0.011em;
}
.hidden { display: none !important; }
button { font-family: inherit; }
input, select, textarea { font-family: inherit; }
::selection { background: rgba(0, 112, 192, .18); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px; border: none; border-radius: var(--radius);
  font-size: 13px; font-weight: 600; cursor: pointer; letter-spacing: -0.006em;
  /* Thai needs more vertical room than Latin: a vowel above the consonant, a
     tone mark above that, and a vowel below. Left to itself the line box
     grows for it, so the same button was 32px in English and 36px in Thai —
     and every row beneath it sat 4px lower on one language than the other.
     The line box is stated here instead, so a control is the same size in
     both. The marks still draw: they overflow the line box, which is what the
     padding is for. */
  line-height: 16px; min-height: var(--ctl-h);
  color: #fff;
  background: linear-gradient(180deg, var(--c-main), var(--c-main-deep));
  box-shadow: 0 1px 2px rgba(0, 90, 158, .35), inset 0 1px 0 rgba(255,255,255,.16);
  transition: transform .06s, box-shadow .15s, filter .15s;
}
.btn:hover { filter: brightness(1.07); box-shadow: 0 4px 14px rgba(0, 112, 192, .35); }
.btn:active { transform: translateY(1px); }
.btn:disabled { background: var(--c-grey); cursor: not-allowed; filter: none; box-shadow: none; }
.btn.secondary { background: var(--surface-3); color: var(--text); box-shadow: inset 0 0 0 1px var(--border-strong); }
.btn.secondary:hover { background: var(--surface-3-hover); }
.btn.success { background: linear-gradient(180deg, #06bd5c, #049a4b); box-shadow: 0 1px 2px rgba(4,154,75,.35); }
/* Delete is built like Edit — same height, same corner, same lift, white text
   on a filled ground — and differs only in colour (owner, 2026-09-16: "make
   the tone and design match the Edit button, a darker red with white text").
   Round 22 had tried the opposite, red ink on a pale pink ground, so that a
   delete would not look like the main thing to do; beside a solid blue Edit
   it read as a disabled control instead. The distinction it was after now
   comes from the colour alone: this red is dark, not the shrill --c-red, and
   nothing else on the page is painted in it. */
.btn.danger, .btn.secondary.danger {
  background: linear-gradient(180deg, var(--c-danger-btn), var(--c-danger-btn-deep));
  color: #fff; border: none;
  box-shadow: 0 1px 2px rgba(153, 39, 28, .35), inset 0 1px 0 rgba(255, 255, 255, .16);
}
.btn.danger:hover, .btn.secondary.danger:hover {
  color: #fff; box-shadow: 0 4px 14px rgba(192, 57, 43, .38);
}
.btn.danger .icon, .btn.secondary.danger .icon { color: #fff; }
.btn.warning { background: linear-gradient(180deg, #ff9d58, #f07f33); }
.btn.ghost { background: transparent; color: var(--c-main); box-shadow: inset 0 0 0 1px rgba(0,112,192,.45); }
.btn.ghost:hover { background: rgba(0,112,192,.06); }
.btn.small { padding: 5px 11px; font-size: 12.5px; border-radius: var(--radius-sm); line-height: 16px; min-height: var(--ctl-h-sm); }
.btn .icon { width: 15px; height: 15px; }

/* Icon-only row action buttons */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border: none; border-radius: var(--radius-sm);
  background: transparent; cursor: pointer; color: var(--c-bluegrey);
  transition: background .12s, color .12s;
}
.icon-btn:hover { background: rgba(0, 112, 192, .1); color: var(--c-main); }
.icon-btn.danger:hover { background: rgba(192, 57, 43, .12); color: var(--c-danger-btn); }
.icon-btn svg { width: 16px; height: 16px; }

/* ---------- Inputs ---------- */
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: 12px; font-weight: 600; color: var(--text-dim); letter-spacing: .01em; }
/* A required field says so in its label and nowhere else: a bolder name and a
   clear star. There was a red bar down the side of it as well — the owner
   asked for that to go (2026-09-22: "ผมไม่ชอบการมีแถบสีหน้าข้อความ ทำให้ดู
   เหมือนงาน AI"), and the label carries it perfectly well on its own. */
.field label .req { color: var(--c-red); margin-left: 3px; font-weight: 800; font-size: 13px; line-height: 1; }
.field.is-req > label { font-weight: 700; color: var(--heading); }
/* the one line that says what the mark means, above the fields */
.form-req-note {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--muted); margin: -2px 0 2px;
}
.form-req-note b { color: var(--c-red); font-size: 13px; line-height: 1; }
/* A required field the person left empty. Marked on the field itself, because
   a banner at the top of a long form is invisible to somebody who scrolled. */
.field.invalid > label { color: var(--c-red); }
.field.invalid .input,
.field.invalid textarea.input,
.field.invalid select.input { border-color: var(--c-red); background: rgba(240, 90, 90, .05); }
.input, textarea.input, select.input {
  /* 9 + 16 + 9 + the two 1 px lines = --ctl-h (36 px), in Thai as in
     English: the line box is stated, as the button's is */
  padding: 9px 11px; border: 1px solid var(--border-strong); line-height: 16px;
  border-radius: var(--radius); font-size: 13.5px; background: var(--input-bg);
  color: var(--text); outline: none; width: 100%;
  transition: border-color .12s, box-shadow .12s;
}
.input:hover { border-color: var(--border-hover); }
.input:focus { border-color: var(--c-main); box-shadow: var(--ring); }
textarea.input { resize: vertical; min-height: 72px; line-height: 1.5; }

/* ---------- Table ---------- */
.table-wrap {
  /* SIDEWAYS ONLY (owner, 2026-10-13). `overflow-y: clip` does not make this a
     scrolling box, where `auto` and `hidden` both would — the same trick the
     Gantt grid uses a few hundred lines down. Two things the owner asked for
     follow from it: a wheel or a two-finger swipe over a table moves the
     WINDOW, because there is nothing here left to swallow it; and the header's
     `position: sticky` resolves against the page instead of against this box,
     so it freezes at the top of the window.
     `overscroll-behavior: contain` is gone with it. It was written for a box
     that scrolled vertically, and once the height cap went it did active harm:
     a horizontal scrollbar leaves a few pixels of vertical overflow, which was
     enough for the browser to treat the gesture as this box's and never pass
     it to the page — the owner's "ยังไม่เลื่อนหน้าต่าง". */
  /* Not a scrolling box on EITHER axis. `overflow-x: auto` was tried first and
     fails the second half of the request: any overflow value but `visible`
     creates a scrollport, and `position: sticky` then resolves against THIS
     box — which never scrolls vertically, so the header simply rode up the
     page with its rows. The page pane (.view) is the one scroller for both
     directions: the wheel always moves it, a table wider than the pane
     scrolls the pane sideways, and the header freezes against the pane's top
     edge, which is the top of the window. */
  /* SIDEWAYS INSIDE THE BOX, NOT PAST IT (owner, 2026-10-16: "ตอนนี้ตารางงาน
     เลยกรอบ Container box สีขาว ควรจะต้องยึดกรอบสีขาวไว้เป็นหลัก แล้วทำการ
     Scroll ตารางภายในเอาเท่านั้น").
     The 2026-10-13 round said in prose, both here and in
     components/stickytables.js, that a wide table "still scrolls SIDEWAYS
     inside its own box" — but the rule it left behind was `overflow: visible`
     on EVERY wrap, so nothing of the sort happened: a table wider than the
     pane simply drew straight through the white frame and out the other side,
     and the frame became a line across the middle of the data. That is the
     fault being reported.
     `overflow-y: clip` is what keeps the other half of 2026-10-13 true. Only
     the horizontal axis is a scroller, so there is no vertical scrollport here
     to swallow a wheel or a two-finger swipe — the page still moves, which was
     the owner's "ยังไม่เลื่อนหน้าต่าง".
     The cost, stated plainly because it is a real one: a scrollport on either
     axis is the scrollport `position: sticky` resolves against, so a WIDE
     table's header is pinned to a box that never scrolls vertically and rides
     up the page with its rows. A table that fits keeps its frozen header
     against the page — see `.table-wrap.fits` just below, which
     components/stickytables.js puts on by measurement. The frame is worth more
     than the freeze: a clipped header still reads, a table sawn in half by its
     own container does not. */
  overflow-x: auto;
  overflow-y: clip;
  border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface);
  box-shadow: var(--shadow-sm);
  /* A wrap inside a flex or grid cell defaults to min-width:auto, which is its
     CONTENT's width — the cell then stretches to the table and the box grows
     past the card again, scrollbar and all. This is the line that makes the
     card, not the table, decide how wide the box is. */
  min-width: 0;
}
/* A table no wider than the page needs no box at all, so nothing is clipped
   either — a row menu may hang outside it. See components/stickytables.js:
   the class is put on by measurement, never by a view deciding for itself. */
.table-wrap.fits { overflow: visible; max-height: none; }
/* The header freezes at the PAGE's top edge — one pixel higher and the rows
   would show above it. Scoped to a table on a page: inside a dialog the
   scrolling box is .modal-body, whose own top edge is already the right place
   for it, so the offset must not be applied there. */
.view .table-wrap table.data thead th,
.view .table-wrap table.table thead th { top: calc(-1 * var(--view-pad-top, 20px)); }
table.data { border-collapse: collapse; width: 100%; min-width: 600px; }
table.data thead th {
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--surface-2); color: var(--text-dim);
  text-align: left; font-size: var(--tbl-head-fs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  padding: var(--tbl-head-pad); white-space: nowrap; cursor: pointer; user-select: none;
  border-bottom: 1px solid var(--border-strong);
}
table.data thead th:hover, table.data thead th:hover .th-sort { color: var(--c-main-fg); }

/* ---------- a sortable header is a button ----------
   It LOOKS exactly as it always did — the words sit where they sat, in the
   same size, weight and colour — because the change is not a visual one: the
   header has to be reachable by Tab and operable by Enter, which only a real
   button is. So the button inherits everything from the cell and keeps no
   chrome of its own. It fills the cell so the whole header is still the
   target a mouse expects. */
table.data thead th .th-sort {
  all: unset;
  display: block; width: 100%; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  text-align: inherit; white-space: nowrap;
}
table.data thead th .th-sort:focus-visible { outline: 2px solid var(--c-main); outline-offset: -2px; border-radius: 3px; }
/* a numeric column's heading sits right, like its figures */
table.data thead th.num .th-sort { text-align: right; }

/* ---------- words for the ear only ----------
   A sentence that explains a control to a screen reader and would be noise on
   the screen (the header's "press to sort by this column"). Clipped rather
   than display:none, because display:none is not read either. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
table.data thead th .sort-ind { opacity: .9; font-size: 9px; margin-left: 4px; color: var(--c-main-fg); }
table.data tbody td {
  padding: var(--tbl-cell-pad); border-top: 1px solid var(--border);
  font-size: var(--tbl-fs); vertical-align: middle; white-space: nowrap;
  max-width: 340px; overflow: hidden; text-overflow: ellipsis;
}
table.data tbody tr:first-child td { border-top: none; }
table.data tbody tr { transition: background .1s; }
table.data tbody tr:hover { background: var(--row-hover); }
table.data tbody tr.clickable { cursor: pointer; }

/* ---------- a row that can be dragged into place ----------
   The grip lives inside the first cell rather than in a column of its own:
   a whole column of furniture for something used once in a while pushes the
   work off the edge of the screen. It appears on hover and on keyboard focus,
   and the line shows WHERE the row would land, above or below. */
table.data tbody tr.rowdrag .row-grip {
  display: inline-flex; align-items: center; vertical-align: middle;
  width: 14px; margin: 0 5px 0 -4px; color: var(--muted);
  opacity: 0; transition: opacity .12s; cursor: grab;
}
table.data tbody tr.rowdrag:hover .row-grip,
table.data tbody tr.rowdrag:focus-within .row-grip { opacity: .75; }
table.data tbody tr.rowdrag .row-grip i { width: 13px; height: 13px; }
table.data tbody tr.dragging { opacity: .45; }
table.data tbody tr.drop-above td { box-shadow: inset 0 2px 0 0 var(--c-main); }
table.data tbody tr.drop-below td { box-shadow: inset 0 -2px 0 0 var(--c-main); }
@media (prefers-reduced-motion: reduce) {
  table.data tbody tr.rowdrag .row-grip { transition: none; }
}
table.data .empty-row td { text-align: center; color: var(--text-dim); padding: 34px; white-space: normal; }
/* The row that adds the table up. It matched no padding rule at all — only
   `table.data tbody td` had one — so a total sat jammed against the edge in
   type smaller than the figures above it. A total is the line people read
   first; it gets the same gutters and a little more weight. */
table.data tfoot td {
  padding: var(--tbl-foot-pad); font-size: var(--tbl-foot-fs); font-weight: 700;
  color: var(--heading); background: var(--surface-2);
  border-top: 2px solid var(--border-strong); white-space: nowrap;
}
table.data tfoot td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* A number cell in the BODY of a register was never told to sit right — only
   the total row was — so a column of amounts read left-aligned over a
   right-aligned total (owner, 2026-09-16: "the figures do not line up"). */
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.data tfoot tr:last-child td:first-child { border-bottom-left-radius: var(--radius); }
table.data tfoot tr:last-child td:last-child { border-bottom-right-radius: var(--radius); }

/* ---------- Status pills & badges ---------- */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2.5px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 600;
  letter-spacing: 0;
  /* Same reason as .btn: a status pill must not be taller in Thai, or the row
     it sits in is taller too. */
  line-height: 14px; min-height: 19px;
}
.pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .75; }
.pill.pending  { background: rgba(255, 150, 79, .16); color: var(--pill-pending-fg); }
.pill.active   { background: rgba(0, 176, 80, .13); color: var(--pill-active-fg); }
.pill.inactive { background: rgba(123, 129, 137, .14); color: var(--pill-inactive-fg); }
.pill.cancelled{ background: rgba(240, 90, 90, .13); color: var(--pill-cancelled-fg); }
.pill.blue     { background: rgba(0, 112, 192, .1); color: var(--pill-blue-fg); }
/* An icon inside a pill: sized to the pill's text, not to a button's icon
   slot, and carrying the pill's colour. The evidence pill used an emoji,
   which the operating system drew in its own colour at its own weight and
   which nobody could read in a table row (owner, 2026-09-22). */
.pill .pill-ic { width: 13px; height: 13px; vertical-align: -2px; }
/* the dot is what a pill uses to carry its colour when it is only a word.
   A pill with an icon already has a shape, and two marks before one digit
   read as noise — which is what made this one hard to pick out in a row. */
.pill:has(.pill-ic)::before { display: none; }
.pill.purple   { background: rgba(112, 48, 160, .1); color: var(--c-purple); }
/* Status is carried by SHAPE as well as colour, so the four lifecycle states
   stay distinguishable without colour vision: ring = pending, solid = active,
   dash = inactive, diamond = cancelled. */
.pill.pending::before   { background: transparent; box-shadow: inset 0 0 0 2px currentColor; opacity: 1; }
.pill.inactive::before  { width: 9px; height: 2.5px; border-radius: 2px; }
.pill.cancelled::before { border-radius: 1px; transform: rotate(45deg); }

/* ---------- Modal (spec 13.3.4: rounded, dark header + white body) ---------- */
#modal-root { position: fixed; inset: 0; z-index: var(--z-modal); pointer-events: none; }
.modal-backdrop {
  position: absolute; inset: 0; background: rgba(16, 26, 36, .5);
  backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
  pointer-events: auto; padding: 24px;
}
.modal {
  background: var(--surface); border-radius: var(--radius-lg); overflow: hidden;
  width: 660px; max-width: 96vw; max-height: 92vh;
  display: flex; flex-direction: column; box-shadow: var(--shadow-lg);
  animation: modal-in .18s cubic-bezier(.2, .9, .3, 1.1);
}
.modal.wide { width: 1000px; }
/* A dialog takes the focus itself when it opens (ui.js Modal.open), so no
   field is picked for the person and Escape reaches the top dialog only. It is
   a container, not a control: the focus ring would draw a box around the whole
   dialog. */
.modal:focus, .modal:focus-visible { outline: none; }
.modal-head {
  background: linear-gradient(180deg, var(--c-menu), var(--c-menu-deep));
  color: #fff; padding: 14px 20px; font-size: 14.5px; font-weight: 700;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.modal-head .icon-btn { color: #b9c6d3; }
.modal-head .icon-btn:hover { background: rgba(255,255,255,.12); color: #fff; }
/* flex:1 with min-height:0 is what keeps the footer on screen: without it a
   long form grows the body past the modal's max-height and pushes the Save
   button off the bottom of the window. */
/* A dialog is a scroller too, and a table inside one freezes its header
   against the dialog — so it publishes its own top padding for the same
   reason the page does. */
.modal-body { padding: 20px; overflow-y: auto; flex: 1 1 auto; min-height: 0; --view-pad-top: 20px;
  /* a dialog scrolls inside itself and never moves the page behind it */
  overscroll-behavior: contain; }
.modal-foot {
  padding: 13px 20px; border-top: 1px solid var(--border);
  display: flex; justify-content: flex-end; gap: 9px; background: var(--surface-2);
}
@keyframes modal-in { from { transform: translateY(16px) scale(.98); opacity: 0; } to { transform: none; opacity: 1; } }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px 18px; }
.form-grid .span2 { grid-column: span 2; }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } .form-grid .span2 { grid-column: span 1; } }

/* ---------- Alert boxes (Success / Error / Warning / Info) ---------- */
.alert {
  display: flex; gap: 10px; align-items: flex-start;
  border-radius: 11px; padding: 11px 13px; font-size: 13px;
  border: 1px solid transparent; margin-bottom: 10px; line-height: 1.45;
}
.alert svg { width: 17px; height: 17px; flex: none; margin-top: 1px; }
.alert:last-child { margin-bottom: 0; }
.alert.success { background: rgba(0,176,80,.08);  border-color: rgba(0,176,80,.3);  color: var(--ok-fg); }
.alert.error   { background: rgba(240,90,90,.08);  border-color: rgba(240,90,90,.35);  color: var(--danger-fg); }
.alert.warning { background: rgba(255,150,79,.10);  border-color: rgba(255,150,79,.45); color: var(--warn-fg); }
.alert.info    { background: rgba(0,112,192,.07);  border-color: rgba(0,112,192,.3); color: var(--info-fg); }

/* ---------- "the server moved on" bar ----------
   Bottom-LEFT, so it can never sit under the toast stack, and low enough to
   be ignored: the tab is out of date, which is worth saying and not worth
   interrupting anybody for. It reloads only when asked. */
/* a pill that is a button: same shape, and it answers to the hand */
.pill.as-btn { border: 0; cursor: pointer; font: inherit; font-size: 11px; font-weight: 700;
  line-height: inherit; transition: filter .12s; }
.pill.as-btn:hover { filter: brightness(1.12); }
.pill.as-btn:focus-visible { outline: 2px solid var(--c-main); outline-offset: 1px; }

.newbuild-bar {
  position: fixed; left: 18px; bottom: 18px; z-index: var(--z-toast);
  display: flex; align-items: center; gap: 12px;
  max-width: min(460px, calc(100vw - 36px));
  padding: 11px 13px; border-radius: 13px;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 10px 36px rgba(16,26,36,.24), 0 2px 8px rgba(16,26,36,.12);
  animation: nb-in .22s ease-out;
}
.newbuild-bar > i { color: var(--info-fg); flex: none; }
.newbuild-bar .nb-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 12.5px; }
.newbuild-bar .nb-text span { color: var(--muted); font-size: 11.5px; }
@keyframes nb-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .newbuild-bar { animation: none; } }

/* ---------- Toast (spec 13.3.7: bottom-right, stacked, above everything) ---------- */
#toast-root {
  position: fixed; right: 18px; bottom: 18px; z-index: var(--z-toast);
  width: 390px; max-width: calc(100vw - 36px); height: 0; pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  min-width: 270px; max-width: 390px;
  background: var(--surface); border-radius: 13px; padding: 13px 15px;
  box-shadow: 0 10px 36px rgba(16,26,36,.24), 0 2px 8px rgba(16,26,36,.12);
  border: 1px solid var(--border);
  border-left: 4px solid var(--c-blue);
  pointer-events: auto; font-size: 13px;
  position: absolute; right: 0; bottom: 0; width: 100%;
  opacity: 0; transform: translate(var(--tx, 0px), var(--ty, 0px));
  /* the fall: quick, with the smallest settle at the end, the way a thing
     dropped onto a pile behaves */
  transition: transform .34s cubic-bezier(.3, 1.2, .5, 1), opacity .22s linear;
}
.toast.landed { opacity: 1; }
.toast.leaving { opacity: 0; --tx: 28px; }
/* held while it is being read: a quiet sign that it will wait */
.toast.held { box-shadow: var(--shadow-lg, 0 8px 28px rgba(0,0,0,.25)), 0 0 0 2px var(--border-strong); }
.toast.success { border-left-color: var(--ok-strong); }
.toast.error { border-left-color: var(--danger-strong); }
.toast.warning { border-left-color: var(--warn-strong); }
.toast svg { width: 17px; height: 17px; flex: none; margin-top: 1px; }
.toast.success svg { color: var(--ok-strong); }
.toast.error svg { color: var(--danger-strong); }
.toast.warning svg { color: var(--warn-strong); }
.toast.info svg { color: var(--info-fg); }
/* The title repeats the state in words, so colour is never the only cue */
.toast .t-kind { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .09em; }
.toast.success .t-kind { color: var(--ok-strong); }
.toast.error   .t-kind { color: var(--danger-strong); }
.toast.warning .t-kind { color: var(--warn-strong); }
.toast.info    .t-kind { color: var(--info-fg); }
.toast .t-close { margin-left: 6px; align-self: flex-start; width: 22px; height: 22px; }
.toast .t-close svg { width: 13px; height: 13px; color: var(--c-grey); }
.toast .t-title { font-weight: 700; margin-bottom: 2px; }
/* gone, but still holding its place under the toast being read */

/* ---------- Full-screen loader (spec 13.3.8) ---------- */
#loader-root { position: fixed; inset: 0; z-index: var(--z-loader); pointer-events: none; }
.loader-screen {
  position: absolute; inset: 0;
  background: linear-gradient(150deg, rgba(35,55,75,.96), rgba(20,32,45,.97));
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 20px; color: #fff; pointer-events: auto;
}
.spinner {
  width: 46px; height: 46px; border-radius: 50%;
  border: 4px solid rgba(255,255,255,.18); border-top-color: var(--c-accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loader-text {
  font-size: 14.5px; letter-spacing: .01em; opacity: .92;
  white-space: pre-line; text-align: center; line-height: 1.7; max-width: 460px;
}
.loader-text::first-line { font-weight: 600; }
/* The file-upload screen: the same loading screen, plus a bar that follows
   the bytes and a line saying what is happening. */
.busy-bar {
  width: min(360px, 72vw); height: 6px; border-radius: 99px;
  background: rgba(255, 255, 255, .18); overflow: hidden;
}
.busy-bar > i {
  display: block; height: 100%; border-radius: 99px;
  background: var(--c-accent); transition: width .2s ease;
}
.busy-sub { font-size: 13px; opacity: .9; letter-spacing: .01em; max-width: min(420px, 80vw); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.busy-note { font-size: 12.5px; opacity: .72; letter-spacing: .01em; min-height: 16px; }

/* ---------- Custom tooltip (spec 13.3.9: instant, follows mouse) ---------- */
#tooltip-root {
  position: fixed; z-index: var(--z-tooltip); pointer-events: none;
  background: var(--c-menu-deep); color: #fff;
  padding: 6px 11px; border-radius: 8px; font-size: 12px;
  box-shadow: 0 6px 18px rgba(0,0,0,.3);
  max-width: 300px; display: none; white-space: pre-line; line-height: 1.45;
}

/* ---------- Searchable dropdown (spec 13.3.5) ---------- */
.sdd { position: relative; }
/* The list is moved into #popover-root and positioned against the window, so
   no dialog, sticky header or scrollbox can clip it. */
.sdd-list {
  position: fixed; z-index: var(--z-dropdown);
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 11px;
  box-shadow: var(--shadow); max-height: 224px; overflow-y: auto; padding: 4px;
  pointer-events: auto;
}
.sdd-list .sdd-item { padding: 7.5px 10px; cursor: pointer; font-size: 13px; border-radius: 7px; }
.sdd-list .sdd-item:hover, .sdd-list .sdd-item.hl { background: rgba(0,112,192,.09); color: var(--c-main); }
.sdd-list .sdd-item.none { color: var(--text-dim); cursor: default; }
.sdd-list .sdd-item.none:hover { background: transparent; color: var(--text-dim); }
/* The "clear the field" row is a real choice, so it stays clickable */
.sdd-list .sdd-item.clear { cursor: pointer; font-style: italic; }
.sdd-list .sdd-item.clear:hover { background: rgba(0,112,192,.07); color: var(--c-main); }
/* Name first, then what identifies it. The box itself only ever shows the
   name, so a long one is readable; the id lives on the second line here and
   in the caption under the field. */
.sdd-list .sdd-item.two-line { display: flex; flex-direction: column; gap: 1px; padding: 6px 10px; }
.sdd-list .sdd-item .sdd-t { font-weight: 600; }
.sdd-list .sdd-item .sdd-s { font-size: 11px; color: var(--text-dim); }
.sdd-list .sdd-item:hover .sdd-s, .sdd-list .sdd-item.hl .sdd-s { color: var(--c-main); opacity: .8; }
/* The chosen record's id/code, under the field. Empty until something is
   chosen, and it takes no room while it is empty. */
.field .sdd-caption { display: block; font-size: 11.5px; color: var(--text-dim); margin-top: 4px; line-height: 1.4; }

/* A figure the form can work out, offered under the box that holds it
   (owner, 2026-09-17: a typed-over cost needs a way back to the calculation).
   Small and quiet, aligned to the left of the box, and it carries the
   arithmetic as well as the answer — never a bare "recalculate". */
.field .f-calc { align-self: flex-start; margin-top: 2px; font-weight: 600; }
.field .f-calc b { font-weight: 700; }
.field .f-calc.hidden { display: none; }
/* A value the system proposed (form.js, round 8b): green like Quick design's
   proposals, with the words in the label, until the person types over it. */
.field .f-prop-tag { display: none; margin-left: 6px; padding: 0 6px; border-radius: 8px; font-size: 10.5px;
  font-weight: 600; color: var(--c-green); background: rgba(0, 135, 60, .08); vertical-align: 1px; }
.field.f-proposed .f-prop-tag { display: inline-block; }
.field.f-proposed .input { background: rgba(0, 135, 60, .08); border-color: rgba(0, 135, 60, .5); }
.field .f-prop-hint.hidden { display: none; }
.field .sdd-caption:empty { display: none; }
.field .sdd-caption .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero; }

/* ---------- Chips (multi-select values) ---------- */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: rgba(0,112,192,.09); color: var(--c-main);
  border-radius: 999px; padding: 3px 7px 3px 11px; font-size: 12.5px; font-weight: 600;
}
.chip button {
  border: none; background: none; color: var(--c-main); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 0 4px; border-radius: 50%;
}
.chip button:hover { color: var(--c-red); }

/* ---------- Login screen ---------- */
.login-screen {
  position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(900px 500px at 85% -10%, rgba(0,112,192,.35), transparent 60%),
    radial-gradient(700px 500px at -10% 110%, rgba(246,192,42,.14), transparent 55%),
    linear-gradient(150deg, #1d2f42 0%, #16232f 60%, #101a24 100%);
  padding: 20px;
}
.login-card {
  width: 404px; max-width: 94vw; background: var(--surface); border-radius: 22px;
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.login-head {
  background: linear-gradient(160deg, var(--c-main) 0%, var(--c-main-deep) 100%);
  color: #fff; padding: 28px 28px 22px;
  display: flex; flex-direction: column; gap: 5px;
}
.login-head .logo { display: flex; align-items: center; gap: 11px; font-size: 21px; font-weight: 800; letter-spacing: -.02em; }
.login-head .logo .mark {
  width: 38px; height: 38px; border-radius: 12px; flex: none;
  background: rgba(255,255,255,.16); display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.25);
}
.login-head .logo .mark svg { width: 22px; height: 22px; color: var(--c-accent); }
/* The uploaded logo in that frame: contained, never cropped, on white so a
   transparent PNG stays readable against the blue header. */
.login-head .logo .mark img { width: 100%; height: 100%; object-fit: contain; background: #fff;
  border-radius: inherit; display: block; }
.login-head .sub { font-size: 12.5px; opacity: .82; }
.login-body { padding: 26px 28px 28px; display: flex; flex-direction: column; gap: 14px; }
.login-body .btn { justify-content: center; padding: 11px; font-size: 14px; border-radius: 12px; }
.login-alt { text-align: center; font-size: 13px; color: var(--text-dim); }
.login-alt a { color: var(--c-main); font-weight: 600; cursor: pointer; text-decoration: none; }
.login-alt a:hover { text-decoration: underline; }

/* The sidebar mark wearing the organisation's own logo */
/* contain, not cover: a company logo is usually wider than it is tall, and
   cropping it to a square leaves an unrecognisable corner. The white ground
   keeps a transparent PNG readable in dark mode too. */
.sb-org .mark img { width: 100%; height: 100%; object-fit: contain; background: #fff;
  border-radius: inherit; display: block; }

/* The organisation page's logo box */
.org-logo-box { width: 64px; height: 64px; border-radius: 14px; flex: none;
  border: 1.5px dashed var(--border-strong); background: var(--surface-2);
  display: inline-flex; align-items: center; justify-content: center; color: var(--text-dim);
  overflow: hidden; }
.org-logo-box img { width: 100%; height: 100%; object-fit: contain; display: block; background: #fff; }
.org-logo-box svg { width: 22px; height: 22px; }

/* ---------- Toolbar ---------- */
.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.toolbar .spacer { flex: 1; }
/* The toolbar's buttons live in one group pinned to the right. The spacer
   only pushes them right while everything fits on one line — once the
   filters wrap, anything after the spacer fell flush left on the next line.
   margin-left:auto keeps the group on the right of whichever line it lands
   on, and keeps its buttons together. */
.toolbar-actions { display: inline-flex; align-items: center; gap: 10px; margin-left: auto; }
/* Every control on a toolbar stands 32px tall — the search box, a status
   filter, the two date boxes and the This month button were four different
   heights on one row, which reads as clutter rather than a row of controls. */
.toolbar .input { padding-top: 5px; padding-bottom: 5px; min-height: var(--ctl-h-sm); }
.toolbar select.qf-select { width: auto; max-width: 220px; }
.toolbar .btn { min-height: var(--ctl-h-sm); }
.toolbar .date-range .input[type="date"] { min-height: var(--ctl-h-sm); }
.search-box { position: relative; }
.search-box input { padding-left: 32px; width: 250px; }
.search-box svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--c-grey); }
.muted { color: var(--text-dim); }
.mono { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0;
  font-variant-numeric: tabular-nums slashed-zero; }

/* ---------- Tabs ---------- */
.tabs { display: flex; gap: 3px; border-bottom: 1.5px solid var(--border-strong); margin-bottom: 16px; flex-wrap: wrap; }
.tab {
  padding: 9px 15px; cursor: pointer; font-weight: 600; font-size: 13px;
  color: var(--text-dim); border-bottom: 2.5px solid transparent; margin-bottom: -1.5px;
  display: inline-flex; align-items: center; gap: 7px; background: none;
  border-top: none; border-left: none; border-right: none;
  border-radius: 8px 8px 0 0; transition: color .12s, background .12s;
}
.tab svg { width: 15px; height: 15px; }
.tab.on { color: var(--c-main); border-bottom-color: var(--c-main); }
.tab:hover { color: var(--c-main); background: rgba(0,112,192,.05); }

/* ---------- the second tier ----------
   Fifteen tabs across the top made a person read a list before they could
   read a project. The top row is now six groups and this is what is inside
   the one they chose — visually lighter than the row above it, because it is
   subordinate to it and must never be mistaken for another set of top tabs. */
.tabs.grouped { margin-bottom: 0; }
.subtabs { margin: 10px 0 16px; display: flex; flex-direction: column; gap: 9px; }
.subtabs:empty { margin: 0 0 16px; }
.subtab-row { display: flex; gap: 6px; flex-wrap: wrap; }
.subtab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface);
  font-family: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-dim);
  transition: background .12s, color .12s, border-color .12s;
}
.subtab svg { width: 14px; height: 14px; }
.subtab:hover { color: var(--c-main); border-color: var(--c-main); }
.subtab.on { background: var(--c-main); border-color: var(--c-main); color: #fff; }
.subtab:focus-visible { outline: 2px solid var(--c-main); outline-offset: 2px; }

/* Why this group exists, in one line. The distinction it draws — pages you
   keep against pages that only read what you kept — is the whole reason the
   grouping is worth having, so it is said in words rather than implied. */
.tab-note {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 8px 11px; border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--surface-2); font-size: 11.5px; line-height: 1.5; color: var(--text-dim);
}
.tab-note svg { width: 14px; height: 14px; flex: none; margin-top: 1.5px; }
/* the rest of the explanation, a hover away */
.tab-note-more {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 15px; height: 15px; border-radius: 50%; cursor: help;
  font-size: 10px; font-weight: 700; font-style: normal; line-height: 1;
  color: var(--muted); border: 1px solid var(--border-strong); background: var(--surface);
}
.tab-note-more:hover, .tab-note-more:focus { color: var(--c-main); border-color: var(--c-main); outline: none; }
.tab-note b { color: var(--heading); font-weight: 700; }
.tab-note.keep { border-left: 3px solid var(--c-main); }
.tab-note.keep svg { color: var(--c-main); }
.tab-note.tool { border-left: 3px solid var(--c-purple); }
.tab-note.tool svg { color: var(--c-purple); }

/* A chart keeps its natural size and scrolls rather than shrinking: an axis
   label at 5px is not a smaller chart, it is a chart with no axis. */
.chart-scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
.chart-scroll::-webkit-scrollbar { height: 6px; }

/* Something inside this group wants a person's attention. Amber rather than
   red: work nobody has picked up yet is a nudge, not a fault. A mark on a tab
   always means "there IS something in here" — never the reverse. */
.tab-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--warn-strong);
  flex: none; margin-left: 1px; align-self: flex-start; margin-top: 3px;
}

/* How many records are filed under a group. Quiet by design: it is a fact
   about the register, not a summons. Hidden entirely at zero. */
.tab-count {
  flex: none; margin-left: 2px; padding: 0 6px; min-width: 18px;
  border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border);
  font-size: 10.5px; font-weight: 700; line-height: 16px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.tab.on .tab-count { background: rgba(0,112,192,.1); border-color: rgba(0,112,192,.25); color: var(--c-main); }

/* ---------- Cards ---------- */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 14px; }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px;
  box-shadow: var(--shadow-sm);
}
.card h3 {
  font-size: 12px; color: var(--text-dim); font-weight: 700; margin-bottom: 10px;
  display: flex; align-items: center; gap: 8px; text-transform: uppercase; letter-spacing: .05em;
}
.card h3 .chip-ic {
  width: 26px; height: 26px; border-radius: 8px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(0,112,192,.1); color: var(--c-main);
}
.card h3 .chip-ic svg { width: 15px; height: 15px; }
.card h3 > svg { width: 15px; height: 15px; color: var(--c-main); }
.card h3 .chip-ic.green { background: rgba(0,176,80,.1); color: var(--c-green); }
.card h3 .chip-ic.orange { background: rgba(255,150,79,.12); color: var(--warn-strong); }
.card h3 .chip-ic.red { background: rgba(240,90,90,.1); color: var(--c-red); }
.card h3 .chip-ic.purple { background: rgba(112,48,160,.1); color: var(--c-purple); }
.card h3 .chip-ic.yellow { background: rgba(246,192,42,.16); color: var(--gold-strong); }
.card .big { font-size: 27px; font-weight: 800; color: var(--heading); letter-spacing: -.02em; }
.card .sub { font-size: 12.5px; color: var(--text-dim); margin-top: 3px; }

/* ---------- Detail page header (personnel / project / meeting) ---------- */
.detail-head {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 20px; margin-bottom: 16px;
  box-shadow: var(--shadow-sm);
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
}
.detail-avatar {
  width: 58px; height: 58px; border-radius: 18px; flex: none;
  background: linear-gradient(150deg, var(--c-main), var(--c-navy));
  color: #fff; font-weight: 800; font-size: 21px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 18px rgba(0,112,192,.3);
}
.detail-avatar.project { background: linear-gradient(150deg, var(--c-purple), #4a1f6e); box-shadow: 0 6px 18px rgba(112,48,160,.3); }
.detail-avatar.meeting { background: linear-gradient(150deg, var(--c-orange), #d96a22); box-shadow: 0 6px 18px rgba(255,150,79,.3); }
.detail-avatar svg { width: 26px; height: 26px; }
/* The avatar is 58 px of a photograph. Clicking it opens the whole thing. */
.detail-avatar.clickable { cursor: zoom-in; }
.detail-avatar.clickable:hover { box-shadow: 0 6px 18px rgba(0,112,192,.45), 0 0 0 3px rgba(0,112,192,.25); }

/* ---------- The portrait, large, and where the round one looks ---------- */
.pv-root { display: flex; flex-direction: column; gap: 14px; }
.pv-stage { position: relative; display: flex; justify-content: center;
  background: var(--surface-3); border-radius: 12px; padding: 8px; }
.pv-img { max-width: 100%; max-height: 56vh; border-radius: 8px; display: block;
  cursor: crosshair; touch-action: none; user-select: none; -webkit-user-drag: none; }
/* Where the circle will be centred, drawn on the picture itself. */
.pv-target {
  position: absolute; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 999px; border: 2.5px solid #fff; pointer-events: none;
  box-shadow: 0 0 0 2px rgba(0,0,0,.45), 0 0 10px rgba(0,0,0,.4);
}
.pv-target::after { content: ''; position: absolute; inset: 9px; border-radius: 999px; background: #fff; }
.pv-tools { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.pv-preview-wrap { display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: 11px; }
.pv-preview { width: 74px; height: 74px; border-radius: 999px; background-size: cover;
  border: 1px solid var(--border-strong); box-shadow: var(--shadow-sm); }
.pv-help { flex: 1; min-width: 220px; display: flex; flex-direction: column; gap: 5px; font-size: 12.5px; }
.pv-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }

/* Aim or crop — two jobs on one photograph, so they are two named modes
   rather than a set of controls the reader has to tell apart. */
.pv-modes { display: inline-flex; background: var(--surface-3); border-radius: 10px; padding: 3px; gap: 3px; align-self: flex-start; }
.pv-mode {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border: none; background: none; font-family: inherit; font-size: 12.5px; font-weight: 650;
  color: var(--text-dim); padding: 5px 12px; border-radius: 8px;
}
.pv-mode svg { width: 15px; height: 15px; }
.pv-mode.on { background: var(--surface); color: var(--c-main); box-shadow: var(--shadow-sm); }

/* The square that will be kept. Everything outside it is dimmed by the box's
   own huge shadow, which is cheaper and sharper than a second overlay. */
.pv-crop {
  position: absolute; box-sizing: border-box;
  border: 2px solid #fff; border-radius: 2px; cursor: move; touch-action: none;
  box-shadow: 0 0 0 9999px rgba(12, 20, 30, .55);
}
.pv-crop::before {
  /* the circle the avatar will actually show, inside the square that is cut */
  content: ''; position: absolute; inset: 0; border-radius: 999px;
  border: 1px dashed rgba(255,255,255,.75); pointer-events: none;
}
.pv-handle {
  position: absolute; width: 14px; height: 14px; background: #fff; border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0,0,0,.5); touch-action: none;
}
.pv-handle.nw { left: -8px; top: -8px; cursor: nwse-resize; }
.pv-handle.ne { right: -8px; top: -8px; cursor: nesw-resize; }
.pv-handle.sw { left: -8px; bottom: -8px; cursor: nesw-resize; }
.pv-handle.se { right: -8px; bottom: -8px; cursor: nwse-resize; }
.pv-stage { overflow: hidden; }
.detail-title { flex: 1; min-width: 240px; }
.detail-title h1 { font-size: 20px; font-weight: 800; letter-spacing: -.02em; color: var(--heading); line-height: 27px; }
.detail-title .sub { color: var(--text-dim); font-size: 13px; margin-top: 3px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.detail-actions { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }
.info-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-3); border-radius: 999px; padding: 4px 12px;
  font-size: 12.5px; color: var(--text);
}
.info-chip svg { width: 13px; height: 13px; color: var(--c-bluegrey); }

/* Read-only field display grid on detail pages */

/* ---------- Entity page: LinkedIn-style two-panel layout ---------- */
.entity-grid { display: grid; grid-template-columns: 330px minmax(0, 1fr); gap: 16px; align-items: start; }
/* One column on a narrow screen — and the tabs go FIRST. The identity panel
   is reference (a phone number, a blood group); the tabs are what somebody
   opened the record to read. Stacked in source order a person scrolls past
   twenty rows of "—" to reach their own work. */
@media (max-width: 900px) {
  .entity-grid { grid-template-columns: 1fr; }
  .entity-right { order: 1; }
  .entity-left { order: 2; }
}
.entity-left { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.entity-right { min-width: 0; }
/* Whatever a tab puts in its body, the pieces are separate things and are
   spaced like the panels in the left column. Block layout on purpose: a tab
   whose own sections already carry a margin-bottom collapses with this
   instead of doubling it. */
.tab-body > * + * { margin-top: 14px; }

.kv-line { display: flex; justify-content: space-between; gap: 14px; padding: 6.5px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.kv-line:last-child { border-bottom: none; }
/* The label gives way before the value does: a long skill name wraps, and
   what it is worth stays whole on one line (owner, 2026-10-14). It was
   `flex: none`, so a narrow card squeezed the value instead. */
.kv-line .k { color: var(--text-dim); min-width: 0; overflow-wrap: anywhere; }
.kv-line .v { text-align: right; font-weight: 550; overflow-wrap: anywhere; min-width: 0; }

/* Five stars are ONE thing and never break (owner, 2026-10-14: a row of stars
   must never be left on a line of its own). `.kv-line .v` allows a break
   anywhere so that a long word cannot push a card wider, and that inherits
   into the stars, which then split across two lines in a narrow column. The
   run is therefore its own inline-flex box with nothing in it that may break. */
.stars { display: inline-flex; flex: none; white-space: nowrap; overflow-wrap: normal; word-break: keep-all;
  letter-spacing: 2px; color: var(--star-off); font-size: 13px; }
.stars > span { flex: none; }
.stars .on { color: var(--c-accent); text-shadow: 0 1px 3px rgba(246,192,42,.4); }

/* ---------- Gamified primitives ---------- */
.progress { height: 8px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
.progress-fill {
  height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--c-main), #38a1e8);
  transition: width .4s ease;
}
.progress.green .progress-fill { background: linear-gradient(90deg, #049a4b, #2fd57f); }
.progress.orange .progress-fill { background: linear-gradient(90deg, #f07f33, #ffb066); }
.progress.red .progress-fill { background: linear-gradient(90deg, #e04c48, #ff8a80); }

.ring { --p: 0; --ring-c: var(--c-accent);
  width: 84px; height: 84px; border-radius: 50%; flex: none; position: relative;
  background: conic-gradient(var(--ring-c) calc(var(--p) * 1%), rgba(255,255,255,.18) 0);
  display: flex; align-items: center; justify-content: center;
}
.ring::before { content: ''; position: absolute; inset: 8px; border-radius: 50%; background: var(--surface); }
.ring .ring-hole {
  position: absolute; inset: 8px; border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-weight: 800; font-size: 17px;
}

/* Hero panel (dashboard greeting) */
.hero {
  border-radius: 18px; padding: 22px 24px; color: #fff; margin-bottom: 16px;
  background:
    radial-gradient(600px 300px at 90% -40%, rgba(246,192,42,.25), transparent 60%),
    linear-gradient(140deg, var(--c-main) 0%, var(--c-main-deep) 45%, var(--c-menu) 100%);
  box-shadow: 0 14px 40px rgba(0, 90, 158, .3);
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.hero .ring { background: conic-gradient(var(--ring-c) calc(var(--p) * 1%), rgba(255,255,255,.15) 0); }
.hero .ring::before { background: rgba(0, 62, 105, .6); backdrop-filter: blur(2px); }
.hero .ring .ring-hole { color: #fff; }
.hero .ring .ring-hole small { font-size: 9.5px; font-weight: 600; opacity: .8; }
.hero-avatar {
  width: 56px; height: 56px; border-radius: 18px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 21px; color: #fff;
  box-shadow: 0 6px 18px rgba(0,0,0,.25), inset 0 1px 0 rgba(255,255,255,.25);
}
.hero .who { flex: 1; min-width: 200px; }
.hero .who h1 { font-size: 20px; font-weight: 800; letter-spacing: -.02em; line-height: 27px; }
.hero .who .sub { font-size: 12.5px; opacity: .8; margin-top: 2px; }
.hero-stats { display: flex; gap: 26px; flex-wrap: wrap; align-items: center; }
.hero-stat { min-width: 74px; }
.hero-stat .n { font-size: 24px; font-weight: 800; letter-spacing: -.02em; }
.hero-stat .l { font-size: 11px; opacity: .78; margin-top: 1px; }

/* Quest cards (my tasks) */
.quest {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px;
  background: var(--surface); transition: box-shadow .15s, transform .1s, border-color .15s;
  cursor: pointer;
}
.quest:hover { box-shadow: var(--shadow); transform: translateY(-1px); border-color: rgba(0,112,192,.35); }
.quest .q-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.quest .q-title { font-weight: 650; font-size: 13px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quest .q-meta { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--text-dim); flex-wrap: wrap; }
.quest .q-meta .pct { margin-left: auto; font-weight: 700; color: var(--c-main-fg); }

/* Section container (replaces scattered boxes) */
.section {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow-sm);
  min-width: 0;
}
.section > h2 {
  font-size: 13px; font-weight: 750; color: var(--heading);
  display: flex; align-items: center; gap: 9px; margin-bottom: 12px; letter-spacing: -.01em;
}
.section > h2 .chip-ic { width: 26px; height: 26px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; background: rgba(0,112,192,.1); color: var(--c-main); }
.section > h2 .chip-ic svg { width: 15px; height: 15px; }
/* Only what this place changes: where it sits and how big it is. It used to
   repeat the component's own colour, and because `.section > h2 .more` is
   more specific than `.more:hover`, the hover rule could change the
   background to navy but not the text — the words went invisible on the
   dashboard's "Open approvals" (owner, 2026-10-14). A place may move a
   component; it may not re-state its colours. */
.section > h2 .more { margin-left: auto; font-size: 11.5px; }
.mini-stats { display: flex; gap: 0; flex-wrap: wrap; }
.mini-stat { display: flex; align-items: center; gap: 10px; padding: 6px 18px 6px 0; margin-right: 18px; border-right: 1px solid var(--border); }
.mini-stat:last-child { border-right: none; }
.mini-stat .ic { width: 32px; height: 32px; border-radius: 10px; display: flex; align-items: center; justify-content: center; }
.mini-stat .ic svg { width: 16px; height: 16px; }
.mini-stat .n { font-size: 17px; font-weight: 800; line-height: 1.1; }
.mini-stat .l { font-size: 11px; color: var(--text-dim); }

/* Finance bars */
.money-bar { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.money-bar .row { display: flex; justify-content: space-between; font-size: 12px; gap: 10px; }
.money-bar .row b { white-space: nowrap; }

/* Empty state */
.empty {
  text-align: center; color: var(--text-dim); padding: 26px 10px; font-size: 13px;
}
.empty svg { width: 30px; height: 30px; opacity: .4; margin-bottom: 6px; }

/* ---------- Agenda cards + voting ---------- */
.agenda-card {
  border: 1px solid var(--border); border-radius: 14px; background: var(--surface);
  padding: 16px 18px; margin-bottom: 12px; box-shadow: var(--shadow-sm);
}
.agenda-card .a-head { display: flex; align-items: flex-start; gap: 12px; }
.agenda-no {
  width: 30px; height: 30px; border-radius: 10px; flex: none;
  background: rgba(0,112,192,.1); color: var(--c-main);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px;
}
.agenda-card .a-title { font-weight: 700; font-size: 14px; flex: 1; min-width: 0; }
.agenda-card .a-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.minutes {
  margin-top: 12px; border-left: 3px solid var(--c-accent);
  background: rgba(246,192,42,.07); border-radius: 0 10px 10px 0;
  padding: 10px 13px; font-size: 13px; white-space: pre-line;
}
.minutes .m-label { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em; color: var(--gold-strong); margin-bottom: 3px; }
.vote-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.vote-note { font-size: 11.5px; margin-top: 6px; }
.vote-btn {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1.5px solid var(--border-strong); background: var(--surface); border-radius: 999px;
  padding: 5px 13px; font-size: 12.5px; font-weight: 650; cursor: pointer;
  color: var(--text-dim); transition: all .12s;
}
.vote-btn svg { width: 14px; height: 14px; }
.vote-btn:hover { border-color: var(--c-main); color: var(--c-main); }
.vote-btn.approve.sel { background: rgba(0,176,80,.12); border-color: var(--c-green); color: var(--ok-strong); }
.vote-btn.reject.sel { background: rgba(240,90,90,.12); border-color: var(--c-red); color: var(--danger-strong); }
.vote-btn.abstain.sel { background: rgba(123,129,137,.15); border-color: var(--c-bluegrey); color: var(--pill-inactive-fg); }
.vote-tally { display: flex; height: 8px; border-radius: 99px; overflow: hidden; background: var(--surface-3); margin-top: 10px; }
.vote-tally .t-a { background: var(--c-green); }
.vote-tally .t-r { background: var(--c-red); }
.vote-tally .t-x { background: var(--c-bluegrey); }

/* A figure whose file has gone. Better than the browser's broken-image icon,
   which says only that something is wrong and not what. */
.rte-missing {
  display: inline-block; padding: 6px 11px; border-radius: 8px;
  background: rgba(240,90,90,.08); color: var(--danger-strong);
  border: 1px dashed rgba(240,90,90,.4); font-size: 12px;
}

/* Two plain fields stacked in a dialog get the gap the form grid would have
   given them. Without it the second label sat directly on the first input —
   the "Address" field of the link dialog was the one people noticed. */
.field-stack { display: flex; flex-direction: column; gap: 12px; }
/* The decision an item is asking the meeting for — the question the chair
   reads out before the discussion starts. */
.a-ask {
  display: flex; align-items: flex-start; gap: 7px; margin-top: 10px;
  font-size: 12.5px; color: var(--c-main);
}
.a-ask svg { width: 15px; height: 15px; flex: none; margin-top: 1px; }
.input.tiny { height: 28px; padding: 2px 8px; font-size: 12px; width: auto; min-width: 118px; }

/* ============================================================
   Meeting mode — the screen a meeting is run on.
   Full-screen because during a meeting there is nothing else to look at, and
   built from the same tokens as every other screen so it is the same
   application, not a second one. Three columns on a laptop or a projector;
   one column, in reading order, on anything narrower.
   ============================================================ */
.mm-root {
  position: fixed; inset: 0; z-index: 190; overflow: hidden;
  background: var(--bg); color: var(--text);
  display: flex; flex-direction: column;
}
body.in-meeting { overflow: hidden; }
.mm-loading { display: flex; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--text-dim); flex-direction: column; }

.mm-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 16px; background: var(--c-menu); color: #fff;
}
.mm-bar .mm-id { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex: 1; }
.mm-bar .mm-id b { font-size: 15px; }
.mm-bar .muted { color: rgba(255,255,255,.66); font-size: 12px; }
.mm-live { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,.8); }
.mm-live .dot { width: 8px; height: 8px; border-radius: 99px; background: #ff5f5f; box-shadow: 0 0 0 0 rgba(255,95,95,.7); animation: mm-pulse 1.8s infinite; }
@keyframes mm-pulse { 70% { box-shadow: 0 0 0 7px rgba(255,95,95,0); } 100% { box-shadow: 0 0 0 0 rgba(255,95,95,0); } }
.mm-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.mm-chips .info-chip { background: rgba(255,255,255,.13); color: #fff; border-color: transparent; }
.mm-chips .info-chip.ok { background: rgba(0,176,80,.28); }
.mm-chips .info-chip.warn { background: rgba(255,150,79,.3); }
.mm-acts { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.mm-acts .btn.ghost { color: #fff; }
/* The theme and language switches on the meeting bar, which is dark in both
   themes — the same treatment the login screen gives them on its backdrop. */
.mm-switches { margin-right: 4px; }
.mm-switches .icon-btn { color: #cdd7e1; background: rgba(255,255,255,.08); width: 30px; height: 30px; }
.mm-switches .icon-btn:hover { background: rgba(255,255,255,.18); color: #fff; }
.mm-switches .lang-seg { background: rgba(255,255,255,.1); }
.mm-switches .lang-seg button { color: #cdd7e1; }
.mm-switches .lang-seg button.on { background: rgba(255,255,255,.92); color: var(--c-main); }
/* The topbar hides the language pair on the smallest screens to save a row.
   In meeting mode it is one of the two things the bar is for, and the bar
   wraps rather than crowding, so it stays. */
@media (max-width: 420px) { .mm-switches.tb-switches .lang-seg { display: inline-flex; } }

.mm-grid { flex: 1; min-height: 0; display: grid; grid-template-columns: 270px minmax(0, 1fr) 300px; }
.mm-agenda, .mm-side { overflow-y: auto; background: var(--surface); border-right: 1px solid var(--border); padding: 12px; }
.mm-side { border-right: none; border-left: 1px solid var(--border); }
.mm-main { overflow-y: auto; padding: 20px 26px 40px; }
.mm-h {
  font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-dim); margin: 4px 0 8px;
}
.mm-h.row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.mm-items { list-style: none; margin: 0; padding: 0; }
.mm-items li {
  display: flex; align-items: flex-start; gap: 9px; padding: 8px 9px; border-radius: 10px;
  cursor: pointer; margin-bottom: 3px;
}
.mm-items li:hover { background: var(--surface-2); }
.mm-items li.on { background: rgba(0,112,192,.11); }
.mm-items li.done .tt { color: var(--text-dim); }
.mm-items .n {
  width: 22px; height: 22px; flex: none; border-radius: 7px; font-size: 11.5px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,112,192,.1); color: var(--c-main);
}
.mm-items li.done .n { background: rgba(0,176,80,.14); color: var(--ok-strong); }
.mm-items .tx { min-width: 0; }
.mm-items .tt { display: block; font-size: 13px; font-weight: 600; }
.mm-items .sub { display: block; font-size: 11px; color: var(--text-dim); }
.mm-items .ok { width: 14px; height: 14px; color: var(--ok-strong); margin-left: auto; }
.mm-add { width: 100%; margin-top: 10px; justify-content: center; }

.mm-item-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.mm-no { font-size: 11px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--c-main); }
.mm-main h1 { font-size: 21px; margin: 2px 0 6px; }
.mm-ask { display: flex; gap: 7px; font-size: 13px; color: var(--c-main); margin-bottom: 4px; }
.mm-ask svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.mm-papers { margin: 14px 0; padding: 12px 14px; background: var(--surface-2); border-radius: 12px; }
.mm-label { display: block; margin: 16px 0 5px; font-size: 12px; font-weight: 700; color: var(--text-dim); }
.mm-note { width: 100%; font-size: 14px; line-height: 1.65; resize: vertical; }
.mm-note.short { font-size: 13.5px; }
.mm-res { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 12px; margin-top: 14px; }

.mm-save { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; white-space: nowrap; }
.mm-save svg { width: 14px; height: 14px; }
.mm-save.ok { color: var(--ok-strong); }
.mm-save.dirty { color: var(--gold-strong); }
.mm-save.busy { color: var(--text-dim); }
.mm-save.bad { color: var(--danger-strong); font-weight: 650; }

.mm-actions-box { margin-top: 20px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px; }
.mm-actions { list-style: none; margin: 0; padding: 0; font-size: 13px; }
.mm-actions li { padding: 5px 0; border-bottom: 1px dashed var(--border); display: flex; gap: 10px; justify-content: space-between; }
.mm-actions li:last-child { border-bottom: none; }
.mm-foot { display: flex; gap: 8px; margin-top: 22px; flex-wrap: wrap; }
.mm-empty { text-align: center; color: var(--text-dim); padding: 60px 20px; }
.mm-empty svg { width: 34px; height: 34px; opacity: .4; }

.mm-person { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; border-bottom: 1px dashed var(--border); }
.mm-person:last-of-type { border-bottom: none; }
.mm-person .who b { display: block; font-size: 13px; }
.mm-person .who .muted { font-size: 11px; }
.mm-person.here .who b { color: var(--ok-strong); }

.mm-vote-head { display: flex; gap: 6px; margin-bottom: 8px; }
.mm-tally { display: flex; height: 8px; border-radius: 99px; overflow: hidden; background: var(--surface-3); }
.mm-tally-nums { display: flex; flex-wrap: wrap; gap: 8px; font-size: 11.5px; color: var(--text-dim); margin: 7px 0 10px; }
.mm-tally-nums .ok { color: var(--ok-strong); }
.mm-tally-nums .bad { color: var(--danger-strong); }
.mm-cast { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.mm-vote-acts { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.mm-voters { margin-top: 10px; font-size: 12px; display: flex; flex-direction: column; gap: 4px; }
.mm-voters > div { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

/* The code is read out to a room, sometimes from the back of it. */
.mm-code {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero;
  font-size: 44px; font-weight: 800; letter-spacing: .22em; text-align: center;
  padding: 14px 0 10px; color: var(--c-main);
}
.mm-code-in { font-size: 24px; letter-spacing: .3em; text-align: center; }

@media (max-width: 1100px) {
  .mm-grid { grid-template-columns: 1fr; overflow-y: auto; }
  .mm-agenda, .mm-side { border: none; border-bottom: 1px solid var(--border); max-height: none; }
  .mm-main { padding: 16px; }
  .mm-res { grid-template-columns: 1fr; }
}
@media print { .mm-root { display: none !important; } }

/* ---------- Global quick search (topbar) ---------- */
.gsearch { position: relative; }
.gsearch input { width: 250px; padding-left: 32px; height: var(--ctl-h-sm); min-height: 0; border-radius: var(--radius); }
.gsearch > svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--c-grey); }
.gsearch-panel {
  position: absolute; top: calc(100% + 8px); right: 0; width: 380px; max-width: 84vw;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 14px;
  box-shadow: var(--shadow-lg); padding: 6px; max-height: 420px; overflow-y: auto;
}
.gsearch-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px;
  border-radius: 9px; cursor: pointer;
}
.gsearch-item:hover { background: rgba(0,112,192,.08); }
.gsearch-item .ic { width: 28px; height: 28px; border-radius: 9px; flex: none; display: flex; align-items: center; justify-content: center; background: rgba(0,112,192,.1); color: var(--c-main); }
.gsearch-item .ic svg { width: 14px; height: 14px; }
.gsearch-item .tx { min-width: 0; flex: 1; }
.gsearch-item .t1 { display: block; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gsearch-item .t2 { display: block; font-size: 11px; color: var(--text-dim); line-height: 1.45; }
/* Notification text may wrap over two lines; search results stay single-line */
.notif-item .t1 { white-space: normal; }

/* ---------- Notification bell ---------- */
.bell { position: relative; }
/* A three-character badge ('99+') sat on top of the bell and hid the thing it
   was a badge for. It now hangs off the corner instead of covering it. */
.bell .badge {
  position: absolute; top: -6px; right: -8px;
  min-width: 17px; height: 17px; padding: 0 4px;
  /* --c-red is the red of a warning WORD (see the token block at the top of
     this file); white on it is 3.33:1. --c-danger-btn is the red that was
     defined to carry white text, and is 5.44:1 (2026-10-15, audit item 10). */
  border-radius: 999px; background: var(--c-danger-btn); color: #fff;
  font-size: 10px; font-weight: 800; line-height: 17px; text-align: center;
  box-shadow: 0 0 0 2px var(--surface); pointer-events: none;
}
.notif-panel { width: 420px; }
.notif-list { max-height: 60vh; overflow-y: auto; }
.notif-more { text-align: center; font-size: 11.5px; padding: 8px; }
.notif-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 10px 6px; font-size: 13px;
}
.notif-head .more { border: none; background: none; color: var(--c-main-fg); font-size: 12px; font-weight: 600; cursor: pointer; }
.notif-item.read { opacity: .62; }
/* Repeated headlines collapse into one openable line */
.notif-roll { cursor: pointer; }
/* same reason as .bell .badge above: white text needs the red that was made
   to carry it, not the red of a warning word */
.notif-roll .pill { font-size: 10.5px; font-weight: 800; padding: 1px 7px; border-radius: 999px;
  background: var(--c-danger-btn); color: #fff; flex: none; }
.notif-roll.open .ic { background: rgba(0,112,192,.2); }
.notif-roll-body { padding-left: 14px; border-left: 2px solid var(--border); margin-left: 16px; }
.notif-roll-body[hidden] { display: none; }

.notif-item .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--c-main); flex: none; }

/* ---------- Change history diffs ---------- */
.diff-list { display: flex; flex-direction: column; gap: 4px; }
.diff-row {
  display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  padding: 4px 8px; background: var(--surface-2); border-radius: 8px; flex-wrap: wrap;
}
.diff-row svg { width: 12px; height: 12px; color: var(--c-grey); flex: none; }
.diff-field { font-weight: 700; color: var(--heading); min-width: 110px; }
.diff-before { color: var(--c-red); text-decoration: line-through; overflow-wrap: anywhere; }
.diff-after { color: var(--ok-strong); font-weight: 600; overflow-wrap: anywhere; }

/* ---------- Gantt chart ---------- */
/* The chart scrolls sideways; the names do not. Everything in the first
   column is pinned to the left edge of the scroller, so a long project can be
   read across without losing track of which row is which. */
.sched-scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
.gantt { display: grid; grid-template-columns: 200px 1fr; gap: 0; font-size: 12px; }
.gantt .g-row, .gantt-months .g-scale { position: relative; }
.gantt .g-label {
  padding: 7px 10px 7px 0; border-bottom: 1px solid var(--border);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  position: sticky; left: 0; z-index: 3; background: var(--surface);
  box-shadow: 1px 0 0 var(--border);
}
.gantt .g-label.sc-axis { padding: 0; overflow: visible; border-bottom: none; }
.gantt .g-row { position: relative; border-bottom: 1px solid var(--border); min-height: 30px; }
.gantt .g-grid { position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(--border); }
.gantt .g-bar {
  position: absolute; top: 7px; height: 16px; border-radius: 4px;
  background: var(--surface-3); overflow: hidden; min-width: 4px;
}
.gantt .g-fill { height: 100%; border-radius: 4px 0 0 4px; }
.gantt .g-bar.done .g-fill { background: var(--c-green); }
.gantt .g-bar.active .g-fill { background: var(--c-main); }
.gantt .g-bar.late .g-fill { background: var(--c-red); }
.gantt .g-today { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--c-accent); z-index: 2; }
.gantt-months {
  display: grid; grid-template-columns: 200px 1fr; margin-bottom: 4px;
  position: sticky; top: 0; z-index: 4; background: var(--surface); padding-bottom: 2px;
}
.gantt-months .g-head {
  position: sticky; left: 0; z-index: 5; background: var(--surface);
  font-size: 11px; align-self: end; padding-bottom: 2px; box-shadow: 1px 0 0 var(--border);
}
.gantt-months .g-scale { position: relative; height: 20px; border-bottom: 1px solid var(--border); }
/* Each label owns exactly one month column and is centred in it, so however
   long the project runs the months read cleanly instead of overlapping. */
.gantt-months .g-m {
  position: absolute; top: 3px; font-size: 10px; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: .04em; font-weight: 600;
  text-align: center; overflow: hidden; white-space: nowrap;
  border-left: 1px solid var(--border); padding-left: 4px; box-sizing: border-box;
}
.gantt .g-ms { position: absolute; top: 9px; width: 12px; height: 12px; transform: translateX(-6px) rotate(45deg); background: var(--c-purple); border-radius: 2px; }
.chart-legend { display: flex; gap: 16px; align-items: center; font-size: 12px; color: var(--text-dim); margin-top: 10px; flex-wrap: wrap; }
.chart-legend .key { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend .sw { width: 14px; height: 4px; border-radius: 2px; display: inline-block; }
.chart-legend .sw.dot { width: 10px; height: 10px; border-radius: 50%; }

/* ---------- Icon chips (global) ---------- */
.chip-ic {
  width: 26px; height: 26px; border-radius: 8px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(0,112,192,.1); color: var(--c-main);
}
.chip-ic svg { width: 15px; height: 15px; }
.chip-ic.green { background: rgba(0,176,80,.1); color: var(--c-green); }
.chip-ic.orange { background: rgba(255,150,79,.12); color: var(--warn-strong); }
.chip-ic.red { background: rgba(240,90,90,.1); color: var(--c-red); }
.chip-ic.purple { background: rgba(112,48,160,.1); color: var(--c-purple); }
.chip-ic.yellow { background: rgba(246,192,42,.16); color: var(--gold-strong); }

/* ---------- Popover layer (notifications, quick search) ----------
   Panels are moved into #popover-root so no page content, sticky table
   header or modal can ever cover or clip them. */
#popover-root { position: fixed; inset: 0; z-index: var(--z-popover); pointer-events: none; }
#popover-root > .popover { position: fixed; pointer-events: auto; }

/* ---------- Modal tone (spec keeps the dark header; tone adds a colour cue) ---------- */
.modal-head { position: relative; }
.modal.tone-warning .modal-head::after,
.modal.tone-danger .modal-head::after,
.modal.tone-success .modal-head::after,
.modal.tone-info .modal-head::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
}
.modal.tone-warning .modal-head::after { background: var(--c-orange); }
.modal.tone-danger  .modal-head::after { background: var(--c-red); }
.modal.tone-success .modal-head::after { background: var(--c-green); }
.modal.tone-info    .modal-head::after { background: var(--c-main); }
.modal-head .head-ic { width: 26px; height: 26px; border-radius: 8px; flex: none;
  display: inline-flex; align-items: center; justify-content: center; background: rgba(255,255,255,.13); }
.modal-head .head-ic svg { width: 15px; height: 15px; }
.modal.tone-warning .modal-head .head-ic { background: rgba(255,150,79,.25); color: #ffc999; }
.modal.tone-danger  .modal-head .head-ic { background: rgba(240,90,90,.25); color: #ffb3b3; }
.modal.tone-success .modal-head .head-ic { background: rgba(0,176,80,.25); color: #9de9c1; }
.modal-head .head-tx { flex: 1; min-width: 0; }

/* ---------- Skill catalog & skill page ---------- */
.face {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 700; color: #fff; border: 2px solid var(--surface);
  margin-left: -7px; box-shadow: 0 1px 3px rgba(16,26,36,.18);
}
.face:first-child { margin-left: 0; }
.face.more { background: var(--surface-3); color: var(--text-dim); }

/* Level distribution bars (skill page left panel) */
.lvl-dist { display: flex; flex-direction: column; gap: 6px; }
.lvl-row { display: flex; align-items: center; gap: 9px; font-size: 12px; }
.lvl-row .lv-name { width: 78px; flex: none; color: var(--text-dim); }
.lvl-row .lv-bar { flex: 1; height: 7px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
.lvl-row .lv-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--c-main), #45a9ec); }
.lvl-row .lv-n { width: 18px; text-align: right; font-weight: 700; color: var(--heading); flex: none; }

/* Holder rows on a skill page */
.holder {
  display: flex; align-items: center; gap: 12px; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); margin-bottom: 8px;
  transition: box-shadow .15s, border-color .15s;
}
.holder:hover { box-shadow: var(--shadow-sm); border-color: rgba(0,112,192,.3); }
.holder .h-av { width: 38px; height: 38px; border-radius: 12px; flex: none; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; font-size: 14px; }
.holder .h-tx { flex: 1; min-width: 0; }
.holder .h-name { font-weight: 650; font-size: 13.5px; cursor: pointer; }
.holder .h-name:hover { color: var(--c-main); text-decoration: underline; }
.holder .h-sub { font-size: 11.5px; color: var(--text-dim); display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.holder .h-lvl { text-align: right; flex: none; min-width: 116px; }
.holder .h-lvl .lv-label { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.holder .h-acts { display: flex; gap: 3px; flex: none; }

/* ---------- Star picker (assessment form) ---------- */
.star-pick { display: flex; align-items: center; gap: 10px; }
.star-pick .sp-stars { display: flex; gap: 3px; }
.star-pick .sp-star {
  border: none; background: none; cursor: pointer; padding: 2px; line-height: 0;
  color: var(--star-off); transition: transform .08s, color .12s;
}
.star-pick .sp-star svg { width: 26px; height: 26px; }
.star-pick .sp-star:hover { transform: scale(1.12); }
.star-pick .sp-star.on { color: var(--c-accent); }
.star-pick .sp-star.on svg { fill: var(--c-accent); }
.star-pick .sp-label { font-size: 13px; font-weight: 700; color: var(--heading); }
.star-pick .sp-hint { font-size: 11.5px; color: var(--text-dim); }

/* Segmented control (person / stakeholder toggle, holder filter) */
.seg { display: inline-flex; background: var(--surface-3); border-radius: var(--radius); padding: 3px; gap: 3px; }
.seg button {
  border: none; background: none; cursor: pointer; padding: 6px 13px; border-radius: 8px;
  font-size: 12.5px; font-weight: 650; color: var(--text-dim); font-family: inherit;
  display: inline-flex; align-items: center; gap: 6px;
}
.seg button svg { width: 14px; height: 14px; }
.seg button.on { background: var(--surface); color: var(--c-main-fg); box-shadow: var(--shadow-sm); }

/* Related-records links on a generic record page */
.rel-link {
  display: flex; align-items: flex-start; gap: 9px; padding: 8px 10px; border-radius: 9px;
  cursor: pointer; border: 1px solid var(--border); margin-bottom: 6px; background: var(--surface);
}
.rel-link:hover { border-color: rgba(0,112,192,.4); background: rgba(0,112,192,.04); }
/* The name wraps rather than being cut off or running out of the card. These
   are spans, so the old `text-overflow: ellipsis` never applied to them — an
   inline box does not clip — and a long project name simply left the box.
   A truncated project name is no use to anybody anyway: half of them here
   differ only in the last few words. */
.rel-link .chip-ic, .rel-link > svg, .rel-link > i { margin-top: 1px; }
.rel-link .rl-tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rel-link .rl-t1 { display: block; font-size: 13px; font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
.rel-link .rl-t2 { display: block; font-size: 11px; color: var(--text-dim); line-height: 1.4; overflow-wrap: anywhere; }
.rel-link > svg, .rel-link > b { flex: none; }

/* S-curve sharing the Gantt timeline */
.sc-head {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin: 18px 0 4px; padding-top: 14px; border-top: 1px solid var(--border);
  font-size: 13px;
}
.sc-head .muted { font-size: 11.5px; }

/* ---------- Portraits ---------- */
/* An avatar keeps its gradient + initials until a photo arrives, so the
   layout never jumps and people without a portrait still look deliberate. */
.has-photo { background-size: cover !important; background-position: center; color: transparent !important; }
.avatar-edit { position: relative; display: inline-flex; }
.avatar-edit .cam {
  position: absolute; right: -4px; bottom: -4px; width: 26px; height: 26px;
  border-radius: 50%; border: 2px solid var(--surface); background: var(--c-main);
  color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
  box-shadow: var(--shadow-sm);
}
.avatar-edit .cam svg { width: 13px; height: 13px; }
.avatar-edit .cam:hover { filter: brightness(1.1); }

/* Photo gallery (project photos) */
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 12px; }
.gallery .shot {
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface);
  cursor: pointer; transition: box-shadow .15s, transform .1s;
}
.gallery .shot:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
.gallery .shot img { width: 100%; height: 118px; object-fit: cover; display: block; background: var(--surface-3); }
.gallery .shot .cap { padding: 8px 10px; font-size: 12px; }
.gallery .shot .cap .t2 { font-size: 11px; color: var(--text-dim); }
.lightbox-img { max-width: 100%; max-height: 72vh; display: block; margin: 0 auto; border-radius: 10px; }

/* ---------- Standard-WBS branch picker ---------- */
/* Its own class: this used to be `.wbs-pick`, which the cascading picker in
   the task form also uses — so this box's scrollbar appeared inside that form
   as well. Neither needs one: the dialog body already scrolls, and a panel
   that scrolls inside a page that scrolls is a trap for the reader. */
.wbs-branches { display: flex; flex-direction: column; gap: 6px; }
.wbs-branch {
  display: flex; align-items: flex-start; gap: 11px; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: 10px; cursor: pointer; background: var(--surface);
}
.wbs-branch:hover { border-color: rgba(0,112,192,.4); background: rgba(0,112,192,.03); }
.wbs-branch input { margin-top: 3px; width: 15px; height: 15px; }
.wbs-branch .wb-tx { flex: 1; min-width: 0; }
.wbs-branch .wb-name { display: block; font-size: 13px; }
.wbs-branch .wb-sub { display: block; font-size: 11.5px; color: var(--text-dim); margin-top: 1px; }
.wbs-branch .wb-pct { flex: none; font-size: 12px; font-weight: 700; color: var(--c-main); }

/* Inline "what changed" panel inside an approval card */
.ap-diff { margin-top: 11px; padding-top: 11px; border-top: 1px dashed var(--border-strong); }

/* ---------- Collapsible form sections ---------- */
/* A heading inside the form grid: groups the fields under it without hiding
   them, so a long form reads as three short ones. */
.form-head {
  grid-column: 1 / -1; display: flex; align-items: baseline; gap: 9px;
  margin: 6px 0 -2px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.form-head:first-child { margin-top: 0; }
.form-head span { font-size: 12px; font-weight: 700; color: var(--heading); letter-spacing: .02em; text-transform: uppercase; }
.form-head em { font-style: normal; font-size: 11.5px; color: var(--text-dim); }
@media (max-width: 640px) { .form-head { grid-column: 1 / -1; } }

.form-section { grid-column: 1 / -1; margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.form-section .fs-head {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 2px;
  border: none; background: none; cursor: pointer; font-family: inherit;
  font-size: 12.5px; font-weight: 700; color: var(--heading); text-align: left;
}
.form-section .fs-head svg { width: 15px; height: 15px; color: var(--c-bluegrey); transition: transform .14s; }
.form-section .fs-head.open svg { transform: rotate(90deg); }
.form-section .fs-head:hover { color: var(--c-main); }
.form-section .fs-hint { margin-left: auto; font-size: 11px; font-weight: 500; color: var(--text-dim); }
.form-section .fs-body { margin-top: 10px; }

/* Money reads as money: tabular figures so columns of amounts line up */
.num { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* "Show more" strip under a long table */
.table-more { padding: 12px; text-align: center; border-top: 1px solid var(--border); background: var(--surface-2); }
.table-more .btn { gap: 6px; }

/* Why people voted the way they did — the reason matters more than the tally */
.vote-notes { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.vote-notes .vn-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; background: var(--surface-2); border-radius: 8px; padding: 6px 10px;
}

/* ---------- Photo albums ---------- */
.photo-folder { margin-bottom: 20px; }
.photo-folder .pf-head {
  display: flex; align-items: center; gap: 9px; margin-bottom: 10px;
  font-size: 13px; color: var(--heading);
}
.photo-folder .pf-head svg { width: 16px; height: 16px; color: var(--c-purple); }
.photo-folder .pf-head .muted { font-size: 11.5px; margin-left: 2px; }

/* The schedule is a wide view: give it every pixel the window has, and let it
   scroll only when the project is genuinely longer than the screen. */
.gantt-months, .gantt { min-width: 0; }

/* ---------- Cash flow summary strip ---------- */
.cf-stats { display: flex; flex-wrap: wrap; gap: 0; margin: -2px 0 14px; }
.cf-stat { padding: 4px 20px 4px 0; margin-right: 20px; border-right: 1px solid var(--border); }
.cf-stat:last-child { border-right: none; }
.cf-stat .l { display: block; font-size: 11px; color: var(--text-dim); }
.cf-stat .v { display: block; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cf-stat .v.big { font-size: 18px; }

/* ---------- Submitting and reviewing work ---------- */
.cbx { display: flex; align-items: center; gap: 9px; font-size: 13px; cursor: pointer; color: var(--text); }
.cbx input { width: 16px; height: 16px; }

.rv-head { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.rv-task { font-size: 15px; font-weight: 700; color: var(--heading); }
.rv-facts { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }
.rv-fact { background: var(--surface-3); border-radius: 10px; padding: 7px 12px; min-width: 130px; }
.rv-fact .l { display: block; font-size: 10.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; }
.rv-fact .v { display: block; font-size: 13px; font-weight: 700; }
.rv-fact.late .v { color: var(--c-red); }
.rv-fact.ontime .v { color: var(--ok-strong); }
.rv-summary { background: var(--surface-2); border-left: 3px solid var(--c-main); border-radius: 0 8px 8px 0;
  padding: 9px 12px; font-size: 13px; white-space: pre-line; }

.rv-dim { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.rv-dim-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 9px; font-size: 13px; }
.rv-dim-head .muted { font-size: 11.5px; }
.rv-scale { display: flex; flex-direction: column; gap: 4px; }
/* Each level shows what it actually means, so two supervisors mean the same
   thing by "4" — that is the whole point of an anchored scale. */
.rv-level {
  display: flex; align-items: flex-start; gap: 10px; text-align: left; width: 100%;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface); cursor: pointer;
  padding: 7px 11px; font-family: inherit; font-size: 12.5px; color: var(--text);
  transition: border-color .12s, background .12s;
}
.rv-level:hover { border-color: rgba(0,112,192,.45); background: rgba(0,112,192,.03); }
.rv-level .n {
  flex: none; width: 22px; height: 22px; border-radius: 7px; background: var(--surface-3);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 12px;
  color: var(--text-dim);
}
.rv-level.on { border-color: var(--c-main); background: rgba(0,112,192,.07); box-shadow: 0 0 0 2px rgba(0,112,192,.12); }
.rv-level.on .n { background: var(--c-main); color: #fff; }
.rv-running {
  margin-top: 14px; padding: 9px 12px; background: var(--surface-3); border-radius: 10px;
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 12.5px;
}
.rv-running b { font-size: 15px; color: var(--heading); }
/* The review, read at one rhythm: a head line of facts, then one paragraph
   per thing the reviewer wrote, all on the same spacing. */
.rv-box { line-height: 1.5; white-space: normal; }   /* .minutes is pre-line: template newlines became blank lines */
.rv-box .rv-p { white-space: pre-line; }              /* the reviewer's own line breaks still count */
.rv-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin-bottom: 6px; }
.rv-head .m-label { margin-bottom: 0; }
.rv-head .rv-meta { font-size: 11.5px; color: var(--text-dim); }
.rv-head .rv-meta + .rv-meta::before { content: '·'; margin-right: 10px; color: var(--border-hover); }
.rv-p { margin: 0; font-size: 13px; }
.rv-p + .rv-p { margin-top: 5px; }
.rv-p b { color: var(--heading); font-weight: 650; }
.rv-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.rv-chip { background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 2px 10px; font-size: 11.5px; }
.rv-chip b { color: var(--c-main); }

/* ---------- Measured work rates ---------- */
.nm-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.nm-title { font-size: 14px; font-weight: 700; color: var(--heading); }
.nm-rates { display: flex; flex-wrap: wrap; gap: 0; margin-top: 12px; }
.nm-rate { padding: 4px 22px 4px 0; margin-right: 22px; border-right: 1px solid var(--border); }
.nm-rate:last-child { border-right: none; }
.nm-rate .l { display: block; font-size: 11px; color: var(--text-dim); }
.nm-rate .v { display: block; font-size: 20px; font-weight: 800; color: var(--heading);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.nm-rate .v small { font-size: 11.5px; font-weight: 600; color: var(--text-dim); }
.nm-obs { margin-top: 12px; }
.nm-obs .more { border: none; background: none; color: var(--c-main); font-size: 12px;
  font-weight: 600; cursor: pointer; padding: 0; }
.nm-card { padding: 14px 0; border-top: 1px solid var(--border); }
.nm-card:first-child { border-top: none; padding-top: 6px; }
.nm-act { margin-left: auto; align-self: center; }

/* A table wider than its panel scrolls inside its own box; the page never
   scrolls sideways to reach a column. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* A table that is READ, not browsed: the offer's priced lines and its
   payment phases (owner, 2026-09-17: "they must all be shown without
   scrolling"). Boxing a table so its header can stick is right for a
   register of a thousand rows and wrong for a document somebody is checking
   before it goes to a client — there, half the lines hidden behind a
   scrollbar is exactly how a wrong figure is signed. `data-nofit` keeps the
   auto-fit off it (components/stickytables.js) and this makes sure nothing
   else caps it either, so the box grows to its rows and the PAGE is what
   scrolls. Sideways it still scrolls inside its own box: a phone cannot show
   seven money columns any other way, and a table left to spill over the
   panel would be cut off rather than reachable. */
.table-full { max-height: none !important; }

/* Straight to the page, for a keyboard. Off screen until it is focused. */
.skip-link {
  position: fixed; top: -100px; left: 12px; z-index: 10000;
  background: var(--c-main); color: #fff; padding: 10px 16px; border-radius: 0 0 10px 10px;
  font-weight: 700; font-size: 13.5px; text-decoration: none; transition: top .12s;
}
.skip-link:focus { top: 0; }

/* The rest of the project checklist — what is further down the process and
   what is already behind us — folded behind one line (owner, 2026-09-17). */
.ns-more {
  display: flex; align-items: center; gap: 7px; width: 100%;
  background: none; border: none; cursor: pointer; padding: 7px 2px 2px;
  font-family: inherit; font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.ns-more:hover { color: var(--c-main); }
.ns-more i { width: 15px; height: 15px; }
.ns-rest { border-top: 1px dashed var(--border); margin-top: 2px; padding-top: 4px; }

/* A signature on file, on a person's own record: shown the size it will be
   printed, on the paper-white it will be printed on, so somebody can see
   whether the scan is clean before a client does. */
.sig-card { border: 1.5px solid var(--border-strong); border-radius: 12px; background: #fff;
  min-height: 108px; display: flex; align-items: center; justify-content: center; padding: 12px; }
.sig-card img { max-height: 84px; max-width: 100%; object-fit: contain; mix-blend-mode: multiply; }
.sig-card.empty { background: var(--surface-3); border-style: dashed; }
.sig-card .muted { display: flex; align-items: center; gap: 8px; }

/* the ways payment is taken, on the offer's terms tab */
.qt-pay-ways { margin: 2px 0 8px; padding-left: 18px; }
.qt-pay-ways li { margin: 2px 0; }
.table-scroll table.data { min-width: 520px; }

/* ---------- Cash flow period picker ---------- */
.cf-range {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: -2px 0 14px; padding-bottom: 12px; border-bottom: 1px solid var(--border);
}
.cf-dates { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-dim); }
.cf-dates .input { width: 152px; padding: 5px 9px; font-size: 12px; }
.cf-grain { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-dim); }

/* ---------- Total-cash banner on the accounts list ---------- */
.total-wallet {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background: linear-gradient(135deg, var(--c-darknavy), #35516d);
  color: #fff; border-radius: var(--radius); padding: 16px 20px; margin-bottom: 14px;
}
.total-wallet .tw-ic {
  width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
  background: rgba(255,255,255,.14);
}
.total-wallet .tw-ic i { width: 21px; height: 21px; }
.total-wallet .tw-tx { flex: 1; min-width: 200px; }
.total-wallet .tw-l { font-size: 11.5px; opacity: .8; text-transform: uppercase; letter-spacing: .05em; }
.total-wallet .tw-v { font-size: 27px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.total-wallet .tw-s { font-size: 11.5px; opacity: .82; }
.total-wallet .btn.secondary { background: rgba(255,255,255,.15); border-color: rgba(255,255,255,.3); color: #fff; }
.total-wallet .btn.secondary:hover { background: rgba(255,255,255,.25); }
/* the arrow that opens the total's own page, like a row's arrow */
.total-wallet .tw-open { color: #fff; background: rgba(255,255,255,.15); border-color: rgba(255,255,255,.3); }
.total-wallet .tw-open:hover { background: rgba(255,255,255,.25); color: #fff; }

/* ---------- Fee-share calculator on a WBS line ---------- */
.fee-calc {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin-top: 12px; padding: 10px 12px; background: var(--surface-3); border-radius: 10px; font-size: 12.5px;
}
.fee-calc label { color: var(--text-dim); }
.fee-calc .input { width: 160px; padding: 5px 9px; font-size: 12.5px; }
.fee-calc b { font-size: 15px; color: var(--heading); font-variant-numeric: tabular-nums; }

/* Notification groups: unread first, under their own heading */
.notif-group {
  padding: 9px 14px 5px; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--c-main);
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.notif-group.muted { color: var(--text-dim); }

/* ============ Rich content: agenda papers ============ */
.rich { font-size: 13px; line-height: 1.62; color: var(--text); }
.rich > *:first-child { margin-top: 0; }
.rich > *:last-child { margin-bottom: 0; }
.rich p { margin: 0 0 9px; }
/* A main heading has to look like one beside its sub-heading — 17 against
   14.5, which reads as a level apart without shouting. */
.rich h3 { font-size: 17px; font-weight: 750; color: var(--heading); margin: 18px 0 8px; letter-spacing: -.012em; }
.rich h4 { font-size: 14.5px; font-weight: 700; color: var(--heading); margin: 14px 0 6px; }
.rich ul, .rich ol { margin: 0 0 9px; padding-left: 22px; }
.rich li { margin: 2px 0; }
.rich blockquote {
  margin: 10px 0; padding: 7px 13px; border-left: 3px solid var(--c-main);
  background: var(--surface-2); border-radius: 0 8px 8px 0; color: var(--text-dim);
}
.rich code { background: var(--surface-2); border-radius: 4px; padding: 1px 5px; font-size: 12px; }
.rich pre { background: var(--surface-2); border-radius: 8px; padding: 10px 12px; overflow-x: auto; font-size: 12px; }
.rich a { color: var(--c-main); text-decoration: underline; }
.rich hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }
.rich figure { margin: 12px 0; }
.rich figure img { display: block; max-width: 100%; height: auto; border-radius: 10px; border: 1px solid var(--border); }
/* Captions start at the left edge of the figure, as they do in a report and
   in Word — a centred caption under a left-aligned picture reads as a mistake.
   Anybody who wants it centred can align it like any other block. */
.rich figcaption { margin-top: 5px; font-size: 11.5px; color: var(--text-dim); text-align: left; }
/* Justified text needs its words to be able to break, or a long Thai or
   English word leaves a river down the column. */
.rich [style*="justify"] { text-align: justify; overflow-wrap: break-word; hyphens: auto; }
/* A figure sized to less than the column stays where the writer put it. */
.rich figure { display: table; }
/* A figure or a table that has been PLACED (its margins written by the align
   buttons) stops filling the column, or there is nothing for the margins to
   centre it in. Matched on the inline rule the editor writes, because that is
   the only signal that survives into stored content. */
.rich figure[style*="margin"], .rich table[style*="margin"] {
  display: table; width: auto; max-width: 100%;
}
/* Filling the column ("justify" on an object) needs display:table as well as
   the width: a table set to display:block lays its rows out in an anonymous
   table that shrinks to its contents, so width:100% on the block moves
   nothing. That is also why a table only scrolls sideways while it is at
   rest — a placed one is sized by the person who placed it. */
.rich table[style*="width: 100%"] { display: table; width: 100%; }
.rich figure[style*="width: 100%"] { width: 100%; }
.rich figure figcaption { display: table-caption; caption-side: bottom; }
.rich img { max-width: 100%; height: auto; border-radius: 8px; }
.rich table {
  border-collapse: collapse; margin: 11px 0; font-size: 12.5px;
  width: 100%; max-width: 100%; display: block; overflow-x: auto;
}
.rich table th, .rich table td { border: 1px solid var(--border-strong); padding: 6px 9px; vertical-align: top; min-width: 60px; }
.rich table th { background: var(--surface-3); font-weight: 700; text-align: left; color: var(--heading); }
.rich table tr:nth-child(even) td { background: var(--surface-2); }

/* ============ The editor ============ */
.rte { border: 1px solid var(--border-strong); border-radius: var(--radius); overflow: hidden;
  background: var(--input-bg); position: relative; }

/* ---------- sizing a figure ---------- */
/* Drawn OVER the editor rather than inside it: anything inside a
   contenteditable is content, and content is what gets saved. */
.rte-sizer { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.rte-sizer-box {
  position: absolute; box-sizing: border-box;
  border: 1.5px solid var(--c-main); border-radius: 3px;
  box-shadow: 0 0 0 3px rgba(0,112,192,.14);
}
.rte-sizer-grip {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px;
  background: var(--c-main); border: 2px solid var(--surface); border-radius: 4px;
  cursor: nwse-resize; pointer-events: auto; touch-action: none;
}
.rte-sizer-bar {
  position: absolute; display: inline-flex; align-items: center; gap: 2px;
  background: var(--c-menu); border-radius: 8px; padding: 3px; pointer-events: auto;
  box-shadow: var(--shadow-lg);
}
.rte-sizer-bar button {
  border: none; background: none; color: rgba(255,255,255,.78); cursor: pointer;
  font-family: inherit; font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 6px;
}
.rte-sizer-bar button:hover { background: rgba(255,255,255,.16); color: #fff; }
.rte-sizer-now { color: rgba(255,255,255,.55); font-size: 11px; padding: 0 6px 0 3px; }
/* The pointer says the picture is a thing you can pick up and size. */
.rte-body img { cursor: pointer; }
.rte-bar {
  display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
  padding: 5px 7px; background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.rte-btn {
  width: 28px; height: 28px; border: none; background: none; border-radius: 7px;
  display: grid; place-items: center; cursor: pointer; color: var(--text-dim);
}
.rte-btn:hover { background: rgba(0,112,192,.12); color: var(--c-main); }
.rte-btn i { width: 15px; height: 15px; }
.rte-sep { width: 1px; height: 18px; background: var(--border-strong); margin: 0 5px; }
.rte-body { padding: 12px 14px; max-height: 46vh; overflow-y: auto; outline: none; }
.rte-body:empty::before, .rte-body > p:only-child:empty::before {
  content: attr(data-placeholder); color: var(--text-dim);
}
.rte-body:focus { box-shadow: inset 0 0 0 2px rgba(0,112,192,.12); }
.rte-body table th, .rte-body table td { min-width: 48px; }
.rte-foot {
  display: flex; gap: 10px; align-items: center; justify-content: space-between;
  padding: 5px 10px; background: var(--surface-2); border-top: 1px solid var(--border); font-size: 11px;
}
.rte-uploading { color: var(--text-dim); font-style: italic; }
.rte-tablepick .field { margin-bottom: 10px; }

/* ============ Agenda: closed items you open ============ */
.agenda-card.collapsible { padding: 0; overflow: hidden; }
.a-toggle {
  width: 100%; display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 12px 14px; background: none; border: none; cursor: pointer; font: inherit;
}
.a-toggle:hover { background: var(--surface-2); }
.a-tx { flex: 1; min-width: 0; }
.a-tx .a-title { display: block; }
.a-tx .a-sub { display: block; }
.a-chev { width: 16px; height: 16px; color: var(--c-grey); transition: transform .15s; flex: none; }
.agenda-card.collapsible.open .a-chev { transform: rotate(180deg); }
.agenda-card.collapsible.open .a-toggle { border-bottom: 1px solid var(--border); background: var(--surface-2); }
.a-body:not(:empty) { padding: 13px 14px 14px; }
.a-lead { font-size: 12.5px; color: var(--text-dim); margin-bottom: 10px; }
.a-content:not(:empty) { margin-bottom: 12px; }

/* ============ Invitations, replies and the register ============ */
.rsvp-banner { display: flex; align-items: center; gap: 12px; }
.rsvp-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.rsvp-bar { display: flex; height: 9px; border-radius: 999px; overflow: hidden; background: var(--border); margin: 2px 0 9px; }
.rsvp-bar span { display: block; }
.s-a { background: var(--ok-strong); } .s-x { background: var(--c-orange); }
.s-w { background: var(--c-bluegrey, #7d93a8); } .s-r { background: var(--danger-strong); }
.s-u { background: var(--border-strong); }
.rsvp-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 11px; color: var(--text-dim); margin-bottom: 10px; }
.rsvp-legend .sw { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 5px; }
.chk {
  width: 26px; height: 26px; border-radius: 8px; border: 1px solid var(--border-strong);
  background: var(--surface); cursor: pointer; display: grid; place-items: center; color: var(--c-grey);
}
.chk i { width: 14px; height: 14px; }
.chk.on { background: var(--ok-strong); border-color: var(--ok-strong); color: #fff; }
.chk:hover { border-color: var(--c-main); }
.chip .x {
  border: none; background: none; cursor: pointer; color: var(--text-dim);
  font-size: 14px; line-height: 1; padding: 0 0 0 4px;
}
.chip .x:hover { color: var(--c-red); }

/* ---------- Choosing a piece of work, one level at a time ---------- */
/* Deliberately unbounded: it grows to whatever the choice needs and the form
   around it scrolls, exactly like every other field. */
.wbs-pick { display: grid; gap: 8px; }
.wbs-lvl { display: grid; grid-template-columns: 150px 1fr; gap: 10px; align-items: center; }
.wbs-lvl > label { font-size: 12px; font-weight: 600; color: var(--heading); }
.wbs-lvl > label .muted { display: block; font-size: 10.5px; font-weight: 500; }
.wbs-trail {
  margin-top: 2px; padding: 8px 11px; background: var(--surface-3); border-radius: 9px;
  font-size: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.wbs-trail .mono { font-weight: 700; color: var(--heading); }
.sw-dot { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
@media (max-width: 720px) { .wbs-lvl { grid-template-columns: 1fr; } }

/* ---------- How work flows here ---------- */
.flow-stage { display: flex; gap: 14px; padding: 14px 0; border-top: 1px solid var(--border);
  /* `empty` here only dims the stage (below); it must NOT borrow the centred
     text-align of the global .empty placeholder — the stage still holds real
     left-reading description text whether or not any records exist yet. */
  text-align: left; }
.flow-stage:first-child { border-top: none; }
.flow-stage.empty { opacity: .72; }
.fs-n { flex: none; }
.fs-n span {
  width: 30px; height: 30px; border-radius: 10px; display: grid; place-items: center;
  background: var(--c-menu); color: #fff; font-weight: 800; font-size: 13px;
}
.flow-stage.empty .fs-n span { background: var(--c-grey); }
.fs-body { flex: 1; min-width: 0; }
.fs-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.fs-head b { font-size: 14px; color: var(--heading); }
.fs-head .muted { font-size: 11.5px; }
.fs-head .spacer { flex: 1; }
.fs-what { font-size: 12.5px; color: var(--text); margin: 6px 0 7px; }
.fs-does { margin: 0 0 9px; padding-left: 18px; font-size: 12px; color: var(--text-dim); }
.fs-does li { margin: 2px 0; }
/* The money map: one line per fact, the count on the right, a lock under
   the level saying what it may not change. */
.mm-rows { display: grid; gap: 2px; margin: 8px 0 10px; }
.mm-row {
  display: flex; align-items: center; gap: 12px; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface); font-size: 12.5px;
}
.mm-row .mm-l { flex: 1; min-width: 0; }
.mm-row .mm-v { flex: none; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mm-row .mm-v.none { color: var(--warn-strong); font-weight: 600; }
.mm-row .mm-v b { font-size: 14px; }
.mm-frozen {
  display: flex; gap: 9px; align-items: flex-start; padding: 9px 11px; border-radius: 8px;
  background: var(--surface-2); border-left: 3px solid var(--c-purple); font-size: 12px; line-height: 1.5;
}
.mm-frozen svg { width: 14px; height: 14px; flex: none; margin-top: 2px; color: var(--c-purple); }
@media (max-width: 640px) {
  .mm-row { flex-wrap: wrap; }
  .mm-row .mm-l { flex-basis: 100%; }
}

.fs-foot { display: flex; gap: 7px; align-items: center; flex-wrap: wrap; font-size: 11.5px; }

/* ---------- What happens next, on a project ---------- */
.ns-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; cursor: pointer; border-radius: 8px; }
.ns-head:hover .ns-chev { color: var(--c-main); }
.ns-head:focus-visible { outline: 2px solid var(--c-main); outline-offset: 3px; }
.ns-head .ns-chev { width: 16px; height: 16px; color: var(--text-dim); flex: none; }
.ns-body[hidden] { display: none; }
.ns-bar { flex: 1; height: 7px; border-radius: 999px; background: var(--border); overflow: hidden; }
.ns-bar span { display: block; height: 100%; background: var(--ok-strong); border-radius: 999px; }
.ns-next {
  display: flex; align-items: center; gap: 11px; padding: 11px 13px;
  background: var(--note-warm-bg); border: 1px solid var(--note-warm-border); border-radius: 10px; font-size: 12.5px;
}
.ns-next.done { background: var(--note-ok-bg); border-color: var(--note-ok-border); }
.ns-next .muted { font-size: 11.5px; }
.ns-next .btn { margin-left: auto; }
.ns-list { display: grid; gap: 2px; margin-top: 11px; }
/* `display: grid` above beats the browser's own [hidden] rule, so the folded
   tail stayed on screen and pressing the toggle did nothing at all — the same
   trap .ns-body, .am-dot and .notif-roll-body each had to be told about. */
.ns-list[hidden] { display: none; }
.ns-row {
  display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  padding: 4px 0; color: var(--text-dim);
}
.ns-row i { width: 15px; height: 15px; color: var(--c-grey); flex: none; }
.ns-row.done { color: var(--text); }
.ns-row.done i { color: var(--ok-strong); }
.ns-row .more { margin-left: auto; }

/* ---------- Quotations ---------- */
/* The money, laid out so the eye lands on the total payable and the net —
   the two figures a client and an accountant respectively look for. */
.qt-totals { padding: 16px 18px; }
.qt-sum { display: flex; flex-wrap: wrap; gap: 10px 26px; align-items: flex-end; }
.qt-cell { display: flex; flex-direction: column; gap: 3px; min-width: 128px; }
.qt-lab { font-size: 11.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .3px; }
.qt-val { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.qt-cell.minus .qt-val { color: var(--c-orange); }
.qt-cell.strong .qt-val { font-size: 17px; }
.qt-cell.grand { padding-left: 20px; border-left: 2px solid var(--c-main); }
.qt-cell.grand .qt-val { font-size: 21px; color: var(--c-main); }
.qt-cell.note .qt-val { font-size: 13px; font-weight: 500; color: var(--text-dim); }
.qt-group td { background: var(--bg); font-weight: 600; font-size: 12px;
  text-transform: uppercase; letter-spacing: .3px; color: var(--heading); }
.qt-group .muted { float: right; font-weight: 600; text-transform: none; letter-spacing: 0; }
.qt-optional td { opacity: .72; }
/* the shared `table.data tfoot td` rule now carries this */
td.row-actions { white-space: nowrap; text-align: right; }

/* A hint under any form field, not only the star picker */
.field .sp-hint { display: block; font-size: 11.5px; color: var(--text-dim); margin-top: 4px; line-height: 1.4; }
/* A yes/no that reads as a sentence rather than a bare box */
label.check { display: flex; align-items: flex-start; gap: 8px; font-size: 13px;
  line-height: 1.45; cursor: pointer; padding-top: 6px; }
label.check input { margin-top: 2px; flex: none; }

/* ---------- "Open" buttons ----------
   The small jump from a summary row to the record itself. One component,
   used by the clause map, project checklists and section footers — it was a
   bare native <button> before, which is why it looked like Windows 95. */
.more {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px; border: none; border-radius: 999px;
  background: rgba(0, 112, 192, .09); color: var(--c-main);
  font-family: inherit; font-size: 12px; font-weight: 650; letter-spacing: -.005em;
  line-height: 1.45; cursor: pointer; white-space: nowrap;
  transition: background .13s, color .13s, box-shadow .13s, transform .1s;
}
.more svg { width: 12.5px; height: 12.5px; flex: none; transition: transform .13s; }
.more:hover { background: var(--c-main); color: #fff; box-shadow: 0 3px 10px rgba(0, 112, 192, .35); }
.more:hover svg { transform: translate(1.5px, -1.5px); }
.more:active { transform: translateY(1px); }
.more:focus-visible { outline: 2px solid var(--c-main); outline-offset: 2px; }
[data-theme="dark"] .more { background: rgba(90, 176, 255, .13); color: #6fc0ff; }
[data-theme="dark"] .more:hover { background: var(--c-main); color: #fff; }

/* ---------- Theme & language switches (topbar) ---------- */
.tb-switches { display: flex; align-items: center; gap: 7px; }
.lang-seg {
  display: inline-flex; background: var(--surface-3); border-radius: 9px;
  padding: 2.5px; gap: 2px;
}
.lang-seg button {
  border: none; background: none; cursor: pointer; padding: 3.5px 9px;
  border-radius: 7px; font-size: 11.5px; font-weight: 700; color: var(--text-dim);
  font-family: inherit; letter-spacing: .01em; line-height: 1.5;
  transition: background .12s, color .12s;
}
.lang-seg button.on { background: var(--surface); color: var(--c-main-fg); box-shadow: var(--shadow-sm); }
.lang-seg button:focus-visible { outline: 2px solid var(--c-main); outline-offset: 1px; }

/* The same switches on the login screen, floating on its dark backdrop */
.login-switches {
  position: absolute; top: 16px; right: 18px;
  display: flex; align-items: center; gap: 7px;
}
.login-switches .icon-btn { color: #cdd7e1; background: rgba(255, 255, 255, .08); width: 32px; height: 32px; }
.login-switches .icon-btn:hover { background: rgba(255, 255, 255, .16); color: #fff; }
.login-switches .lang-seg { background: rgba(255, 255, 255, .1); }
.login-switches .lang-seg button { color: #b9c6d3; }
.login-switches .lang-seg button.on { background: rgba(255, 255, 255, .92); color: var(--c-main-deep); }

/* Login screen needs its own positioning context for the switches */
.login-screen { position: fixed; }

/* ---------- Risk register: score chip ---------- */
.risk-score {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 34px; height: 22px; padding: 0 8px; border-radius: 7px;
  font-weight: 800; font-size: 12px; font-variant-numeric: tabular-nums;
}
.risk-score.low    { background: rgba(0, 176, 80, .14);  color: var(--pill-active-fg); }
.risk-score.medium { background: rgba(246, 192, 42, .2); color: var(--gold-strong); }
.risk-score.high   { background: rgba(255, 150, 79, .18); color: var(--pill-pending-fg); }
.risk-score.extreme{ background: rgba(240, 90, 90, .16);  color: var(--pill-cancelled-fg); }

/* ---------- Live estimate panel inside a form ---------- */
.form-note {
  background: var(--surface-3); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 13px; font-size: 12.5px; line-height: 1.55;
}
.form-note b { color: var(--heading); }
.form-note .est-figure { font-size: 15px; font-weight: 800; color: var(--c-main); font-variant-numeric: tabular-nums; }
.form-note .muted { display: block; margin-top: 3px; }

/* ---------- "There is more to the right" ----------
   Wide tables scroll inside their own box, but macOS hides scrollbars until
   the moment you scroll — so a clipped last column reads as a broken layout
   rather than as something you can reach. These shadows appear only while
   there is content beyond the edge, and fade out as you arrive at it. */
.table-wrap, .table-scroll, .sched-scroll {
  background-image:
    linear-gradient(to right, var(--surface) 30%, rgba(0, 0, 0, 0)),
    linear-gradient(to left,  var(--surface) 30%, rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 0 50%, rgba(16, 26, 36, .16), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 100% 50%, rgba(16, 26, 36, .16), rgba(0, 0, 0, 0));
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 42px 100%, 42px 100%, 15px 100%, 15px 100%;
  background-attachment: local, local, scroll, scroll;
  background-color: var(--surface);
}
[data-theme="dark"] .table-wrap,
[data-theme="dark"] .table-scroll,
[data-theme="dark"] .sched-scroll {
  background-image:
    linear-gradient(to right, var(--surface) 30%, rgba(0, 0, 0, 0)),
    linear-gradient(to left,  var(--surface) 30%, rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0));
}
/* The sticky header must not let the shadow bleed over it */
table.data thead th { background-clip: padding-box; }

/* ---------- Password field with a reveal button ---------- */
.pw-field { position: relative; display: flex; }
.pw-field .input { padding-right: 40px; }
.pw-eye {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; border: none; background: none; cursor: pointer;
  display: grid; place-items: center; border-radius: 8px;
  color: var(--text-dim); transition: color .12s, background .12s;
}
.pw-eye:hover { color: var(--c-main); background: rgba(0, 112, 192, .09); }
.pw-eye:focus-visible { outline: 2px solid var(--c-main); outline-offset: 1px; }
.pw-eye svg { width: 16px; height: 16px; }

/* ---------- Record page: the facts, laid out to be read ----------
   The wide column gets the full field list in a responsive grid, grouped
   exactly as the form groups it, so a record reads like the form that made
   it rather than like a column of a hundred rows. */
.rec-group + .rec-group { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
.rec-group-h {
  font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em;
  color: var(--c-main); margin-bottom: 10px;
}
.rec-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 12px 20px; }
.rec-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rec-cell .k { font-size: 11px; color: var(--text-dim); }
.rec-cell .v { font-size: 13.5px; font-weight: 550; overflow-wrap: anywhere; }
.rec-long { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }

/* ---------- One timeline: what was done, and what changed ---------- */
.tl-item { display: flex; gap: 12px; padding: 12px 0; border-top: 1px solid var(--border); }
.tl-item:first-child { border-top: none; }
.tl-ic {
  width: 30px; height: 30px; border-radius: 10px; flex: none; display: grid; place-items: center;
  background: rgba(0, 112, 192, .1); color: var(--c-main);
}
.tl-ic svg { width: 15px; height: 15px; }
.tl-ic.move { background: rgba(255, 150, 79, .14); color: var(--warn-strong); }
.tl-ic.change { background: rgba(123, 129, 137, .14); color: var(--text-dim); }
.tl-body { flex: 1; min-width: 0; }
.tl-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.tl-head b { font-weight: 650; }
.tl-when { margin-left: auto; font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.tl-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }

/* ---------- The total line of a roll-up ----------
   A total that touches the last row reads as one more row; and sitting on the
   scroller's own edge it collided with the scrollbar. It gets a rule above it,
   a little air, and its own background so it stays readable while the table
   scrolls sideways underneath. */
.rollup-scroll { padding-bottom: 4px; }
/* The total row is the shared `table.data tfoot td` — the same as the
   billing table's (owner, 2026-09-17: "make it like Billing and receipts,
   especially the last row"); the plainer rule of 2026-09-16 is gone. */
/* the roll-ups are `table.table.sm` like the billing table (2026-09-17) */
table.rollup tbody tr.rollup-gap { display: none; }

/* A unit sitting inside a number box: % or a currency, so the figure and what
   it means are read together rather than from a label three inches away. */
.in-suffix {
  position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  font-size: 12px; color: var(--text-dim); pointer-events: none;
}
.pw-field .input[type="number"] { padding-right: 42px; }

/* ============================================================
   Project controls — the programme, the network, the site record
   ============================================================ */
:root {
  --link: #7B8189;            /* dependency arrows: present, never shouting  */
  --tag-link: #1a5fd6;        /* a tag on the board reads as a link (Facebook-style), 5.9:1 on white */
  --wknd: rgba(123,129,137,.07);
  --bar-tx: #ffffff;
  --pill-orange-fg: #9c4e14;
  --pill-green-fg: #05703d;
  --pill-red-fg: #b53434;
}
[data-theme="dark"] {
  --link: #7f8a99;
  --tag-link: #6ea8ff;
  --wknd: rgba(255,255,255,.035);
  --bar-tx: #0d1520;
  --pill-orange-fg: #ffb877;
  --pill-green-fg: #6ede9f;
  --pill-red-fg: #ff9a9a;
}

.pill.orange { background: rgba(255,150,79,.16); color: var(--pill-orange-fg); }
.pill.green  { background: rgba(0,176,80,.13);   color: var(--pill-green-fg); }
.pill.red    { background: rgba(240,90,90,.13);  color: var(--pill-red-fg); }

/* Dense card row for the control dashboards: more of them, smaller */
.cards.tight { grid-template-columns: repeat(auto-fit, minmax(184px, 1fr)); gap: 11px; }   /* auto-fit: three cards fill a wide row instead of huddling left (owner, 2026-09-17) */
/* The three billing cards: each at least as wide as its own title on one
   line — no more, no less (owner, 2026-09-17: "the minimum is the width at
   which the first line does not break in two") — sharing the row equally,
   a card that does not fit beside the others taking the next row. The
   title alone sets that minimum: the figure and the note beneath it are
   given no intrinsic width (width 0, min-width 100 %), so a long note
   cannot widen the card, it wraps. */
.money-flow .cards.tight { display: flex; flex-wrap: wrap; }
.money-flow .cards.tight .card { flex: 1 1 0; min-width: max-content; }
.money-flow .cards.tight .card h3 { white-space: nowrap; }
.money-flow .cards.tight .card > :not(h3) { width: 0; min-width: 100%; }
.cards.tight .card .big { font-size: 22px; }
.cards.evm-cards { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 11px; }
.evm-card h3 { font-size: 11.5px; letter-spacing: .04em; }
.evm-card .big { font-size: 19px; }
.evm-card .sub { font-size: 11px; line-height: 1.35; }

/* Compact report table — denser than the module list, and it carries totals */
table.table { border-collapse: collapse; width: 100%; background: var(--surface); }
table.table thead th {
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--surface-2); color: var(--text-dim);
  text-align: left; font-size: var(--tbl-head-fs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; padding: var(--tbl-head-pad); white-space: nowrap;
  border-bottom: 1px solid var(--border-strong);
}
table.table tbody td { padding: var(--tbl-cell-pad); border-top: 1px solid var(--border); font-size: var(--tbl-fs); vertical-align: middle; }
table.table tbody tr:hover { background: var(--row-hover); }
table.table td.num, table.table th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.table tfoot td {
  padding: var(--tbl-foot-pad); border-top: 2px solid var(--border-strong);
  background: var(--surface-2); font-size: var(--tbl-foot-fs);
}
table.table tfoot td.num { text-align: right; font-variant-numeric: tabular-nums; }
table.cert-table td { border-top: 1px solid var(--border); }
table.cert-table tr.total td { border-top: 2px solid var(--border-strong); font-size: 14px; }

/* Key/value grid used by the dialogs */
.detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 9px 18px; }
.detail-grid .dk { display: block; font-size: 11px; font-weight: 700; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: .05em; }
.detail-grid .dv { display: block; font-size: 13px; color: var(--text); margin-top: 1px; }

/* Popover menu (export, and anything else offered from a button) */
.pop-menu { background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: var(--shadow-lg, 0 12px 32px rgba(20,32,48,.18)); padding: 6px; min-width: 288px; overflow: auto; }
.pop-item { display: flex; gap: 10px; align-items: flex-start; width: 100%; text-align: left;
  padding: 9px 10px; border: none; background: none; border-radius: 9px; cursor: pointer; color: var(--text); }
.pop-item:hover { background: var(--row-hover); }
.pop-item svg { width: 17px; height: 17px; color: var(--c-main); flex: none; margin-top: 1px; }
.pop-item b { display: block; font-size: 13px; font-weight: 650; }
.pop-item small { display: block; font-size: 11.5px; color: var(--text-dim); line-height: 1.35; margin-top: 1px; }

/* ---------- Gantt ---------- */
.sched .gt-toolbar { display: flex; flex-direction: column; gap: 9px; margin-bottom: 10px; }
.gt-tools { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; }
.gt-toggles { display: flex; gap: 5px; flex-wrap: wrap; }
.gt-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; cursor: pointer; color: var(--text-dim);
  background: var(--surface-3); border: 1px solid transparent;
}
.gt-chip svg { width: 12px; height: 12px; }
.gt-chip.on { background: rgba(0,112,192,.1); color: var(--c-main); border-color: rgba(0,112,192,.3); }
.gt-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.btn.sm { padding: 5px 11px; font-size: 12px; border-radius: var(--radius-sm); line-height: 15px; min-height: var(--ctl-h-sm); }
.btn.sm svg { width: 13px; height: 13px; }

.gt-wrap {
  display: grid;
  grid-template-columns: var(--label-w) var(--chart-w);
  grid-template-rows: auto auto;
  width: max-content; min-width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
  /* `clip`, not `hidden`.
     The name column and the corner are position:sticky so they stay put while
     the timeline scrolls under them — but `overflow: hidden` here made THIS
     element the scrollport they stick to, and this element never scrolls. The
     declaration was present and had no effect: drag the chart sideways and the
     task names slid off the left edge with it.
     `overflow: clip` clips the corners the same way without creating a scroll
     container, so the names now stick to .sched-scroll, which is the thing
     that actually moves. */
  overflow: clip;
}
.gt-corner {
  position: sticky; left: 0; z-index: 4; background: var(--surface-2);
  border-right: 1px solid var(--border-strong); border-bottom: 1px solid var(--border-strong);
  padding: 7px 10px; display: flex; flex-direction: column; justify-content: center; gap: 1px;
  font-size: 11.5px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em;
}
.gt-corner-sub { font-size: 10.5px; font-weight: 500; text-transform: none; letter-spacing: 0; }
.gt-headwrap { border-bottom: 1px solid var(--border-strong); background: var(--surface-2); }
.gt-head { position: relative; height: 40px; }
.gt-band, .gt-ticks { position: relative; height: 20px; }
.gt-band span {
  position: absolute; top: 0; height: 20px; line-height: 20px; padding-left: 6px;
  font-size: 11px; font-weight: 700; color: var(--heading);
  border-left: 1px solid var(--border-strong); overflow: hidden; white-space: nowrap;
}
.gt-ticks span {
  position: absolute; top: 0; height: 20px; line-height: 20px; text-align: center;
  font-size: 10px; color: var(--text-dim); border-left: 1px solid var(--border);
  overflow: hidden; white-space: nowrap;
}
.gt-ticks span.wknd { background: var(--wknd); }
.gt-ticks span i { font-style: normal; opacity: .55; margin-left: 2px; font-size: 9px; }

.gt-labels { position: sticky; left: 0; z-index: 3; background: var(--surface); border-right: 1px solid var(--border-strong); }
.gt-label {
  height: 30px; display: flex; align-items: center; gap: 6px; padding: 0 8px;
  border-bottom: .5px solid var(--border); font-size: 12px; cursor: pointer; overflow: hidden;
}
.gt-label:hover { background: var(--row-hover); }
.gt-wbs { font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero; font-size: 10px; color: var(--text-dim); flex: none; }
.gt-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gt-name.late { color: var(--danger-fg); font-weight: 600; }
.gt-who { margin-left: auto; font-size: 10.5px; color: var(--text-dim); flex: none; }
.gt-dia { width: 9px; height: 9px; background: var(--c-purple); transform: rotate(45deg); border-radius: 1.5px; flex: none; }
.gt-chart { position: relative; }
.gt-barg:hover rect { filter: brightness(1.08); }
/* The head of the name column over the payment lane (schedule.js cashLane):
   the same height as the lane in the chart, border included, so row one of
   the names still meets bar one. */
.gt-paylane {
  display: flex; align-items: center; box-sizing: border-box; padding: 0 8px; overflow: hidden;
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
  color: var(--text-dim); background: var(--surface-2); border-bottom: 1px solid var(--border-strong);
}

/* the S-curve reuses the same two-column frame */
.gt-wrap.sc { grid-template-rows: auto; margin-top: 10px; }
.gt-labels.sc-axis { border-right: 1px solid var(--border-strong); }

/* ---------- AON network ---------- */
.aon-scroll {
  overflow: auto; max-height: 62vh; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); margin-top: 12px;
}
.aon-node:hover rect:first-of-type { stroke-width: 2.4; }

/* ---------- Weather strip ---------- */
.wx-strip {
  display: flex; gap: 2px; align-items: flex-end; overflow-x: auto; padding: 8px 2px 4px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
}
.wx-day {
  position: relative; width: 15px; min-width: 15px; height: 46px; border-radius: 4px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  cursor: pointer; background: var(--surface-3);
}
.wx-day.ok { background: rgba(0,176,80,.16); }
.wx-day.partial { background: rgba(255,150,79,.22); }
.wx-day.stopped { background: rgba(240,90,90,.22); }
.wx-day.none { background: var(--surface-3); opacity: .55; cursor: default; }
.wx-day:hover { outline: 1.5px solid var(--c-main); }
.wx-ic { font-size: 9px; line-height: 12px; }
.wx-rain { position: absolute; bottom: 0; width: 7px; border-radius: 2px 2px 0 0; background: var(--c-main); opacity: .75; }
.wx-eot { position: absolute; top: 1px; right: 1px; width: 4px; height: 4px; border-radius: 50%; background: var(--warn-strong); }
.chart-legend .sw.wx-sw { width: 12px; height: 12px; border-radius: 3px; }
.chart-legend .sw.wx-sw.ok { background: rgba(0,176,80,.4); }
.chart-legend .sw.wx-sw.partial { background: rgba(255,150,79,.5); }
.chart-legend .sw.wx-sw.stopped { background: rgba(240,90,90,.5); }
.chart-legend .sw.wx-sw.none { background: var(--surface-3); }

/* ---------- The earned-value verdict ---------- */
.evm-verdict { margin-top: 8px; }
.evm-big {
  display: flex; gap: 12px; align-items: center; padding: 13px 15px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--border);
}
.evm-big svg { width: 26px; height: 26px; flex: none; }
.evm-big b { display: block; font-size: 15px; }
.evm-big span { display: block; font-size: 12.5px; color: var(--text-dim); margin-top: 2px; line-height: 1.45; }
.evm-big.ok { border-color: rgba(0,176,80,.4); } .evm-big.ok svg { color: var(--c-green); }
.evm-big.warn { border-color: rgba(255,150,79,.5); } .evm-big.warn svg { color: var(--warn-strong); }
.evm-big.bad { border-color: rgba(240,90,90,.45); } .evm-big.bad svg { color: var(--c-red); }

/* ---------- Print ----------
   The browser is the PDF writer. What it prints is the report, not the
   application around it — and never the dark theme, which is a screen
   choice, not a paper one. */
@media print {
  /* Every print is an A4 sheet with a rule around the page. position:fixed
     repeats on each printed page, which is what makes it a frame rather
     than a box around the first page's content. Papers that want landscape
     say so themselves (the progress report, the schedule). */
  /* 25 mm on the left is the binding margin — blank, the frame starts
     after it (owner's rule for every printed page, 2026-09-14). */
  @page { size: A4 portrait; margin: 12mm 12mm 12mm 25mm; }
  /* Paper is white whatever the screen theme was, and the page is only as
     tall as its content — html/body at height:100% printed a spare dark
     page after every paper in dark mode. */
  html, body { height: auto !important; background: #fff !important; }
  body::after { content: ''; position: fixed; inset: 0;
    border: 1.2px solid #23374B; pointer-events: none; }
  /* Dark mode is a screen choice, not a paper one: printing an app page in
     dark mode painted ink-heavy navy tables. The dark tokens are re-declared
     as the light palette for the printer. */
  [data-theme="dark"] {
    color-scheme: light;
    --bg: #fff; --surface: #fff; --surface-2: #f4f6fa; --surface-3: #eef2f7;
    --row-hover: #fff; --input-bg: #fff; --border: #d5dde8; --border-strong: #b9c6d6;
    --text: #1c2733; --text-dim: #5d6874; --heading: #23374B; --topbar-bg: #fff;
    --star-off: #d8dee8;
  }
  .sidebar, .topbar, .tabs, .nav-scrim, .gt-toolbar, .detail-actions,
  .btn, .icon-btn, .table-more, .toolbar { display: none !important; }
  #app-root { display: block !important; height: auto !important; }
  /* 4mm of air between the content and the page frame */
  .main, .view { display: block !important; overflow: visible !important; padding: 4mm !important; }
  .section, .card, .gt-wrap, .table-scroll { break-inside: avoid; box-shadow: none !important; }
  .sched-scroll, .aon-scroll, .table-scroll, .table-wrap {
    overflow: visible !important; max-height: none !important;
  }
  .gt-labels, .gt-corner { position: static !important; }
}

/* A progress photograph carries what it is evidence of, on the picture */
.shot { position: relative; }
.shot-pct {
  position: absolute; top: 7px; right: 7px; z-index: 1;
  padding: 2px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 700; color: #fff;
  background: var(--c-main); box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.shot-pct.done { background: var(--c-green); }
.shot-pct.mid { background: var(--c-main); }
.shot-pct.early { background: var(--warn-strong); }
.shot .cap .t2 { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.shot .cap .t2 svg { width: 11px; height: 11px; }
.shot-task { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* ---------- Portfolio health: one bar, two facts ----------
   The fill is the work actually done; the notch is where the plan says it
   should be. The gap between them is the whole message. */
.ph-bar { position: relative; height: 8px; border-radius: 999px; background: var(--surface-3); overflow: visible; }
.ph-fill { height: 100%; border-radius: 999px; background: var(--c-green); transition: width .25s; }
.ph-fill.warn { background: var(--warn-strong); }
.ph-fill.bad { background: var(--c-red); }
.ph-mark { position: absolute; top: -3px; width: 2px; height: 14px; background: var(--heading); border-radius: 1px; }
[data-theme="dark"] .ph-mark { background: #e7edf6; }

/* ---------- Earned value: the reading, and the arithmetic ---------- */
.evm-actions { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.evm-actions li { display: flex; gap: 8px; align-items: flex-start; font-size: 12.5px; line-height: 1.5; color: var(--text); }
.evm-actions svg { width: 14px; height: 14px; flex: none; margin-top: 2px; color: var(--c-main); opacity: .8; }
.evm-card { cursor: pointer; transition: box-shadow .12s, transform .12s; }
.evm-card:hover { box-shadow: 0 0 0 2px rgba(0,112,192,.25); }
.evm-card:focus-visible { outline: 2px solid var(--c-main); outline-offset: 2px; }
.evm-formula {
  display: inline-block; font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero; font-size: 11.5px;
  background: var(--surface-3); padding: 3px 7px; border-radius: 6px; color: var(--heading);
  white-space: normal; line-height: 1.6;
}
.evm-formula.block { display: block; padding: 9px 11px; font-size: 12px; }
.evm-verdict-line {
  font-size: 12.5px; line-height: 1.5; margin-top: 5px; padding: 6px 9px; border-radius: 8px;
  border-left: 3px solid var(--border-strong); background: var(--surface-2);
}
.evm-verdict-line.ok { border-left-color: var(--c-green); }
.evm-verdict-line.warn { border-left-color: var(--warn-strong); }
.evm-verdict-line.bad { border-left-color: var(--c-red); }
table.evm-method tbody td { vertical-align: top; padding: 11px; }
.evm-metric-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.evm-metric-value { font-size: 26px; font-weight: 800; color: var(--heading); }

/* The approval row: what it is on the left, what you can do on the right.
   The stacking below 680px is in responsive.css. */
.agenda-card .a-body { flex: 1; min-width: 0; }
.agenda-card .a-actions { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }

/* ============================================================
   The progress report — a document, not a screen
   ------------------------------------------------------------
   Deliberately its own visual language: white paper, black text, thin rules,
   no rounded cards. It is read on paper or as a PDF by somebody who is not a
   user of this system, so it borrows nothing from the interface except the
   data. Always light, whatever the reader's theme.
   ============================================================ */
.report-root {
  position: fixed; inset: 0; z-index: 200; overflow: auto;
  background: #6b7684;
  color: #1c2733;
  -webkit-font-smoothing: antialiased;

  /* The sheet is white whatever the reader's theme — so anything inside it
     that reads a theme token must read the PAPER's tokens, not the
     application's. Without this, a minute typed in the rich editor came out
     as `.rich { color: var(--text) }` — pale grey ink on white paper, which
     in dark mode was very nearly invisible and printed the same way. */
  --text: #1c2733;
  --heading: #23374B;
  --text-dim: #5b6b7f;
  --surface: #ffffff;
  --surface-2: #f6f8fb;
  --surface-3: #eef2f7;
  --border: #e5eaf1;
  --border-strong: #d7dfe9;
  --input-bg: #ffffff;
}
body.reporting { overflow: hidden; }

.pdoc table tbody tr:hover,
.pdoc table.table tbody tr:hover,
.pdoc table.data tbody tr:hover { background: transparent; }
.pdoc thead th { position: static !important; z-index: auto !important; }
.pdoc table, .pdoc td, .pdoc th { cursor: default; }
.pdoc details > summary { list-style: none; cursor: default; }
.pdoc details > summary::-webkit-details-marker { display: none; }

.report-sheet {
  background: #fff; color: #1c2733;
  width: 297mm; max-width: 100%; min-height: 210mm;
  margin: 18px auto 40px; padding: 16mm 14mm;
  box-shadow: 0 12px 40px rgba(0,0,0,.35);
  font-size: 11px; line-height: 1.5;
}
.report-sheet h1 { font-size: 22px; margin: 2px 0 2px; letter-spacing: -.02em; color: #23374B; }
.report-sheet h2 {
  font-size: 13px; margin: 20px 0 8px; color: #23374B;
  border-bottom: 1.5px solid #23374B; padding-bottom: 4px; letter-spacing: -.01em;
}
.report-sheet h3 { font-size: 11.5px; margin: 12px 0 5px; color: #3d4d60; }
.rp-foot {
  display: flex; justify-content: space-between; gap: 16px; margin-top: 22px; padding-top: 8px;
  border-top: 1px solid #d7dee8; font-size: 9.5px; color: #68788d;
}
table.rp-pages > thead { display: none; }

/* A module whose name no longer says what it covers explains itself, in the
   authority table where the question is actually being asked. */
.am-mod .am-note { display: block; font-size: 10.5px; color: var(--text-dim);
  line-height: 1.4; margin-top: 2px; max-width: 46ch; }

/* The งวด's actions: one line, equal widths, in the order the paperwork
   happens. Buttons that size themselves to their label make a ragged column
   that reads as three unrelated controls rather than one row of choices. */
/* Thirteen columns do not fit a laptop, and the one that gets pushed past the
   edge is the last one — which is where Certify lives, the button that starts
   the whole certify → invoice → receipt run. An action you cannot see is an
   action nobody takes, so the column stays pinned to the right-hand edge and
   the rest of the table scrolls underneath it. */
/* the action column is sticky to the right, UNDER the sticky header — at
   the header's own z-index a row's buttons rode over it as the box scrolled
   (owner, 2026-09-17); the corner cell sits above both */
td.pay-actions, th.pay-actions {
  position: sticky; right: 0; background: var(--surface); z-index: 1;
  box-shadow: -10px 0 10px -10px rgba(16, 26, 36, .35);
}
td.pay-actions { white-space: nowrap; text-align: right; }
th.pay-actions { z-index: 3; }
td.pay-actions .pay-btn {
  min-width: 108px; justify-content: center; white-space: nowrap;
}
td.pay-actions .pay-btn + .pay-btn { margin-left: 6px; }

/* ---------- Papers for the client ---------- */
/* Four cards, because a paper is a thing you send rather than a row action
   hiding at the right-hand end of a thirteen-column table. */
.paper-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.paper-card {
  display: flex; align-items: center; gap: 11px; text-align: left; width: 100%;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: border-color .12s, background .12s, transform .08s;
}
.paper-card:hover:not(.empty) { border-color: var(--c-main); background: var(--row-hover); }
.paper-card:active:not(.empty) { transform: translateY(1px); }
.paper-card.empty { cursor: default; opacity: .62; }
.paper-ic { display: flex; align-items: center; justify-content: center; flex: none;
  width: 34px; height: 34px; border-radius: 10px; background: rgba(112,48,160,.12); color: var(--c-purple); }
.paper-ic svg { width: 17px; height: 17px; }
.paper-card.empty .paper-ic { background: var(--surface-2); color: var(--text-dim); }
.paper-tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.paper-tx b { font-size: 13.5px; color: var(--heading); }
.paper-tx span { font-size: 11.5px; color: var(--text-dim); line-height: 1.35; }
.paper-n { flex: none; font-size: 12px; font-weight: 700; color: var(--c-purple);
  background: rgba(112,48,160,.12); border-radius: 999px; padding: 1px 9px; }

/* Choosing which งวด a paper is for */
.pick-list { display: flex; flex-direction: column; gap: 7px; }
.pick-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 14px; width: 100%;
  text-align: left; padding: 10px 13px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text); cursor: pointer; }
.pick-row:hover { border-color: var(--c-main); background: var(--row-hover); }
.pick-row b { font-size: 13px; color: var(--heading); }
.pick-row span { font-size: 11.5px; color: var(--text-dim); }
.pick-row .num { grid-row: 1 / 3; align-self: center; font-size: 14px; font-weight: 700;
  color: var(--heading); font-variant-numeric: tabular-nums; }

/* ---------- The organisation page's live preview ---------- */
/* What is being typed, drawn the way the client will read it — the same block
   the papers print, so a missing field is visible as a missing field. */
.org-preview { border: 1px solid var(--border); border-radius: 11px; padding: 16px 18px;
  background: var(--surface-2); max-width: 460px; }
.org-role { font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }
.org-name { font-weight: 700; font-size: 15px; color: var(--heading); margin-top: 2px; }
.org-party { font-size: 12.5px; line-height: 1.6; }
.org-gap { color: var(--warn-fg); font-style: italic; }

/* ---------- How much room there is to bargain ---------- */
/* The margin is the only part of the price the practice can give away, so it
   is drawn as a quantity that gets used up rather than described in prose. */
.qt-bargain {
  display: flex; gap: 11px; align-items: flex-start; margin-top: 12px;
  padding: 11px 13px; border-radius: 11px; font-size: 12.5px; line-height: 1.5;
  background: rgba(0,176,80,.07); border: 1px solid rgba(0,176,80,.3); color: var(--ok-fg);
}
.qt-bargain svg { width: 17px; height: 17px; flex: none; margin-top: 1px; }
.qt-bargain b { display: block; font-size: 13.5px; }
.qt-bargain span { color: var(--text-dim); }
.qt-bargain.spent { background: rgba(240,90,90,.08); border-color: rgba(240,90,90,.35); color: var(--danger-fg); }
.qt-bargain.none { background: var(--surface-2); border-color: var(--border); color: var(--text); }
.qt-bargain-bar { height: 5px; border-radius: 3px; background: rgba(0,0,0,.10); margin: 6px 0 5px; overflow: hidden; }
.qt-bargain-bar span { display: block; height: 100%; background: currentColor; opacity: .55; }

/* The project brief is a portrait paper — it is read, not scanned across */
.brief-root .report-sheet { width: 210mm; min-height: 297mm; }
.rb-signs { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-top: 26px; }
.rb-sign { display: flex; flex-direction: column; gap: 2px; font-size: 10.5px; }
.rb-sign-line { height: 44px; border-bottom: 1px solid #23374B; margin-bottom: 6px; }
.rb-sign-hint { color: #68788d; font-size: 9.5px; }

/* On paper: the sheet IS the page */
@media print {
  /* Orientation is each paper's own decision (the commercial papers and the
     brief inject portrait, the progress report injects landscape); forcing
     landscape here printed every portrait paper sideways. */
  /* The id selector on the generic print rules (#app-root) outguns a bare
     child-combinator hider, so the application printed as three junk pages
     in front of every report. Name it explicitly. */
  body.reporting > *:not(.report-root), body.reporting #app-root { display: none !important; }
  .report-root { position: static; overflow: visible; background: #fff; }
  .rp-bar, .no-print { display: none !important; }
  /* .brief-root .report-sheet outguns a bare .report-sheet, so the screen
     min-height of 297mm survived into print — and one full page of content
     plus 24mm of margins is a guaranteed blank second page. */
  .report-sheet, .brief-root .report-sheet {
    width: auto; margin: 0; padding: 6mm; box-shadow: none; min-height: 0; }
  .rp-section { break-inside: avoid-page; }
  .rp-break { break-before: page; }
  table.rp-table { break-inside: auto; }
  table.rp-table tr { break-inside: avoid; }
  table.rp-pages > thead { display: table-header-group; }
  table.rp-pages > tfoot { display: table-footer-group; }
  .rp-foot { margin-top: 10px; }
}

/* ---------- Workload grid: people down, weeks across ---------- */
table.wl-grid td, table.wl-grid th { padding: 5px 7px; }
.wl-person { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.wl-face {
  width: 24px; height: 24px; border-radius: 8px; flex: none; color: #fff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700;
}
.wl-cell { cursor: pointer; font-variant-numeric: tabular-nums; min-width: 40px; text-align: center !important; }
.wl-cell.under { background: rgba(0,176,80,.13); color: var(--pill-green-fg); }
.wl-cell.fit   { background: rgba(0,112,192,.14); color: var(--pill-blue-fg); }
.wl-cell.over  { background: rgba(240,90,90,.20); color: var(--pill-red-fg); font-weight: 800; }
.wl-cell:hover { outline: 2px solid var(--c-main); outline-offset: -2px; }
b.under { background: rgba(0,176,80,.13); color: var(--pill-green-fg); }
b.fit   { background: rgba(0,112,192,.14); color: var(--pill-blue-fg); }
b.over  { background: rgba(240,90,90,.20); color: var(--pill-red-fg); }
.chart-legend .sw.wl-sw { width: 14px; height: 12px; border-radius: 3px; }
.chart-legend .sw.wl-sw.under { background: rgba(0,176,80,.4); }
.chart-legend .sw.wl-sw.fit { background: rgba(0,112,192,.4); }
.chart-legend .sw.wl-sw.over { background: rgba(240,90,90,.5); }
.wl-cell.unknown { background: repeating-linear-gradient(135deg, var(--surface-3) 0 5px, transparent 5px 10px); }
.wl-unknown { font-size: 9px; color: var(--warn-strong); font-weight: 800; margin-left: 2px; }

/* ---------- what somebody wrote when they handed work in ---------- */
/* Label above value, wrapping into as many columns as the width allows. All
   of this was captured and then never shown to anybody, which is how a form
   teaches people that filling it in does not matter. */
.sub-detail {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px 18px; margin: 10px 0 4px; padding: 10px 0; border-top: 1px solid var(--border);
}
.sub-detail .dk { display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.sub-detail .dv { font-size: 13px; font-weight: 600; }
.agenda-card .a-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }

/* Earlier attempts, folded away. A reviewer about to write the same comment
   for the third time should be able to see that they are. */
.rv-earlier { margin-top: 10px; }
.rv-earlier > summary {
  cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--c-main); padding: 4px 0;
}
.rv-earlier > summary:hover { text-decoration: underline; }
.rv-earlier-row {
  padding: 8px 11px; margin-top: 6px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--border); font-size: 12.5px;
}

/* ---------- recovery instructions ---------- */
/* Numbered, spaced, with the command on its own line. Somebody reading this
   is having a bad day and should not have to parse a paragraph. */
.recover { margin: 12px 0 0; padding-left: 22px; font-size: 12.5px; line-height: 1.6; }
.recover li { margin-bottom: 10px; }
.recover code.block {
  display: block; margin-top: 6px; padding: 8px 11px; border-radius: 8px;
  background: var(--surface-3); border: 1px solid var(--border);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero; font-size: 11.5px;
  overflow-x: auto; white-space: nowrap;
}

/* ---------- the periodic evaluation ---------- */
/* The judgement questions are sentences, not a scale. A radio row of numbers
   invites the middle answer; a row of statements makes somebody pick the one
   that is actually true, which is the whole point of asking this way. */
.ev-q { padding: 11px 0; border-bottom: 1px solid var(--border); }
.ev-q:last-child { border-bottom: none; }
.ev-q-head { display: flex; align-items: center; gap: 7px; font-size: 13px; margin-bottom: 8px; }
.ev-q-head svg { width: 15px; height: 15px; color: var(--c-main); }
.ev-q-head .muted { font-size: 11px; font-weight: 500; margin-left: auto; }
.ev-opts { display: flex; flex-direction: column; gap: 5px; }
.ev-opt {
  display: flex; align-items: center; gap: 10px; text-align: left; width: 100%;
  padding: 8px 11px; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface);
  font-family: inherit; font-size: 12.5px; color: var(--text);
  transition: border-color .12s, background .12s;
}
.ev-opt:hover { border-color: var(--c-main); }
.ev-opt:focus-visible { outline: 2px solid var(--c-main); outline-offset: 2px; }
.ev-opt.on { border-color: var(--c-main); background: rgba(0,112,192,.07); font-weight: 600; }
.ev-opt.clear { opacity: .72; font-style: italic; }
.ev-pts {
  flex: none; width: 22px; height: 22px; border-radius: 6px; display: grid; place-items: center;
  background: var(--surface-3); font-weight: 800; font-size: 11.5px; color: var(--text-dim);
}
.ev-opt.on .ev-pts { background: var(--c-main); color: #fff; }

.ev-mix { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; font-size: 12.5px; }
.ev-mix .nui-range-wrap { flex: 1; min-width: 160px; }
.ev-mix b { font-variant-numeric: tabular-nums; min-width: 40px; }

.ev-result {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  padding: 13px 15px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2);
}
.ev-big { display: flex; align-items: center; gap: 11px; }
.ev-num { font-size: 30px; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.ev-parts { display: flex; gap: 22px; flex-wrap: wrap; margin-left: auto; }
.ev-parts .dk { display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.ev-parts .dv { font-size: 13px; font-weight: 600; }
.ev-split { display: flex; gap: 24px; flex-wrap: wrap; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.ev-split .dk { display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.ev-split .dv { font-size: 13px; font-weight: 600; }
.ev-summary { cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--c-main); }
.ev-summary:hover { text-decoration: underline; }

@media (max-width: 680px) {
  .ev-parts, .ev-split { margin-left: 0; gap: 14px; }
  .ev-num { font-size: 25px; }
}

/* ---------- choosing a colour ---------- */
/* Big enough to see the colour and to hit with a thumb; the tick appears on
   the chosen one rather than a border, because a border changes the swatch's
   apparent colour and this field is about judging colours. */
.colorpick { display: flex; flex-direction: column; gap: 10px; }
.cp-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 6px; }
.cp-sw {
  position: relative; aspect-ratio: 1; min-height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(0,0,0,.18); background: var(--sw, var(--surface-3));
  display: flex; align-items: center; justify-content: center; padding: 0;
  transition: transform .1s, box-shadow .1s;
}
.cp-sw:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.cp-sw:focus-visible { outline: 2px solid var(--c-main); outline-offset: 2px; }
.cp-sw svg { width: 15px; height: 15px; color: #fff; opacity: 0; filter: drop-shadow(0 1px 2px rgba(0,0,0,.55)); }
.cp-sw.on svg { opacity: 1; }
.cp-sw.none { background: var(--surface-2); border-style: dashed; aspect-ratio: auto; width: 34px; height: 30px; flex: none; }
.cp-sw.none svg { color: var(--text-dim); opacity: .8; }
.cp-sw.none.on { border-color: var(--c-main); }
.cp-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cp-name { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; }
.cp-hexwrap { display: inline-flex; align-items: center; gap: 7px; margin-left: auto; font-size: 11.5px; }
.cp-hex { width: 104px; font-family: var(--font-mono); font-variant-numeric: tabular-nums slashed-zero; font-size: 12px; text-transform: uppercase; }
@media (max-width: 680px) {
  .cp-grid { grid-template-columns: repeat(5, 1fr); gap: 8px; }
  .cp-hexwrap { margin-left: 0; width: 100%; }
}

/* A candidate for a piece of work. The tint is the verdict, so a planner
   scanning the list sees who is worth reading before reading anybody. */
tr.cand.ok  td:first-child { box-shadow: inset 3px 0 0 var(--c-green); }
tr.cand.warn td:first-child { box-shadow: inset 3px 0 0 var(--warn-strong); }
tr.cand.bad td:first-child { box-shadow: inset 3px 0 0 var(--border-strong); }
tr.cand.bad { opacity: .72; }
tr.cand:hover { background: var(--row-hover); }
tr.cand td { vertical-align: middle; }

/* The period bar sits above a page rather than inside a panel, so it needs
   its own breathing room where .cf-range alone would sit flush. */
.period-bar { flex-wrap: wrap; row-gap: 8px; }
.chart-legend .sw.wl-sw.unknown { background: repeating-linear-gradient(135deg, var(--surface-3) 0 4px, transparent 4px 8px); box-shadow: inset 0 0 0 1px var(--border-strong); }

/* ---------- What needs attention ---------- */
.att-list { display: flex; flex-direction: column; gap: 7px; }
.att-item {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 11px;
  border: 1px solid var(--border); border-left-width: 3px; border-radius: 10px;
  background: var(--surface); cursor: pointer; transition: background .12s;
}
.att-item:hover { background: var(--row-hover); }
.att-item.bad { border-left-color: var(--c-red); }
.att-item.warn { border-left-color: var(--warn-strong); }
.att-item.info { border-left-color: var(--c-bluegrey); }
.att-ic { width: 26px; height: 26px; border-radius: 8px; flex: none; display: inline-flex;
  align-items: center; justify-content: center; background: var(--surface-3); color: var(--text-dim); }
.att-item.bad .att-ic { background: rgba(240,90,90,.12); color: var(--c-red); }
.att-item.warn .att-ic { background: rgba(255,150,79,.14); color: var(--warn-strong); }
.att-ic svg { width: 14px; height: 14px; }
.att-tx { flex: 1; min-width: 0; }
.att-main { display: block; font-size: 13px; line-height: 1.45; }
.att-hint { display: block; font-size: 11.5px; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att-go { width: 15px; height: 15px; color: var(--text-dim); flex: none; margin-top: 5px; }

/* ---------- The daily site report: one sheet, filled in standing up ---------- */
.dsr-h { font-size: 12.5px; font-weight: 700; color: var(--heading); margin: 16px 0 8px;
  display: flex; align-items: baseline; gap: 8px; }
.dsr-h .muted { font-size: 11.5px; font-weight: 500; }
.dsr-weather { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.dsr-wx {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 10px; min-width: 74px; border-radius: 11px; cursor: pointer;
  border: 1.5px solid var(--border); background: var(--surface);
  font-size: 11px; color: var(--text-dim); font-weight: 600;
}
.dsr-wx:hover { border-color: var(--border-hover); }
.dsr-wx.on { border-color: var(--c-main); background: rgba(0,112,192,.07); color: var(--c-main); }
.dsr-wx-ic { font-size: 20px; line-height: 1.1; }
.dsr-lines input.sm, .dsr-lines select.sm { height: 32px; padding: 4px 8px; font-size: 12.5px; }
.dsr-lines input.num { text-align: right; }
.dsr-lines td { padding: 4px 6px; }
@media (max-width: 680px) {
  .dsr-wx { min-width: 62px; padding: 7px 6px; }
  .dsr-lines { min-width: 520px; }
}

/* ---------- Look-ahead: the three weeks in front of the site ---------- */
.la-weeks { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.la-week { border: 1px solid var(--border); border-radius: 12px; padding: 10px; background: var(--surface); }
.la-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid var(--border); font-size: 12.5px; }
.la-head .muted { font-size: 11px; }
.la-item { display: flex; gap: 8px; align-items: flex-start; padding: 7px 8px; border-radius: 9px;
  cursor: pointer; border-left: 3px solid var(--border-strong); margin-bottom: 6px; background: var(--surface-2); }
.la-item:hover { background: var(--row-hover); }
.la-item.late { border-left-color: var(--c-red); }
.la-item.blocked { border-left-color: var(--warn-strong); }
.la-item.milestone { border-left-color: var(--c-purple); }
.la-ic { width: 22px; height: 22px; border-radius: 7px; flex: none; display: inline-flex;
  align-items: center; justify-content: center; background: var(--surface-3); color: var(--text-dim); }
.la-ic svg { width: 12px; height: 12px; }
.la-tx { min-width: 0; flex: 1; }
.la-title { display: block; font-size: 12.5px; font-weight: 600; }
.la-sub { display: block; font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.la-block { display: block; font-size: 11px; color: var(--warn-fg); margin-top: 3px; }
.la-empty { font-size: 11.5px; color: var(--text-dim); padding: 8px 4px; }
@media (max-width: 900px) { .la-weeks { grid-template-columns: 1fr; } }

/* ---------- the offer, checked against the work behind it ---------- */
.qt-undercost { display: flex; gap: 10px; align-items: flex-start; margin-top: 12px;
  padding: 12px 14px; border-radius: 10px;
  background: color-mix(in srgb, var(--danger) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--border)); }
.qt-undercost > i { flex: none; width: 18px; height: 18px; color: var(--danger); margin-top: 2px; }
.qt-undercost b { display: block; font-size: 13.5px; color: var(--danger); margin-bottom: 3px; }
.qt-undercost span { font-size: 12px; color: var(--text); line-height: 1.5; }
.qt-undercost-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.qt-undercost-list span { font-size: 11.5px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 20px; padding: 2px 9px; }
.qt-lines tbody tr.qt-below { box-shadow: inset 3px 0 0 var(--danger); }
.qt-lines td.qt-cost { color: var(--muted); }
/* the work as a tree on the offer (2026-09-17): headings bold, tasks smaller,
   a picked row tinted, a row covered by a picked heading faded */
.qt-work tr.covered td { opacity: .45; }
.qt-work tr.task td { font-size: 12.5px; }
.qt-work tr.on > td { background: color-mix(in srgb, var(--c-main) 7%, transparent); }
.qt-work .qt-offer { width: 132px; text-align: right; }
.qt-work select.input.sm { max-width: 220px; }
.qt-tree-bar { flex-wrap: wrap; }
.qt-pct { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.qt-pct .input { width: 72px; }
.qt-short { font-size: 10.5px; font-weight: 700; color: var(--danger); }
.qt-gapblock { margin-bottom: 14px; }
.qt-gapblock b { display: block; font-size: 13px; color: var(--heading); }
.qt-gapblock > .muted { font-size: 11.5px; line-height: 1.45; display: block; margin-bottom: 5px; }
.qt-gapblock ul { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.7; }
.qt-workbar { height: 4px; border-radius: 3px; background: var(--surface-3, var(--border));
  overflow: hidden; margin-top: 5px; max-width: 130px; }
.qt-workbar span { display: block; height: 100%; background: var(--accent); }

/* ---------- which work a งวด bills ---------- */
.alloc-note { display: flex; gap: 9px; align-items: flex-start; margin-bottom: 12px;
  font-size: 12px; color: var(--muted); line-height: 1.5; }
.alloc-note i { flex: none; width: 16px; height: 16px; margin-top: 1px; color: var(--accent); }
.alloc-table tbody tr.alloc-full { opacity: .55; }
.alloc-table input.alloc-pct { width: 84px; text-align: right; padding: 4px 8px; }
.alloc-table td.alloc-amt { font-weight: 600; }

/* ---------- how work is priced ---------- */
.cost-rule { padding: 12px 14px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid var(--border); }
.cost-rule b { display: block; font-size: 13.5px; color: var(--heading); margin-bottom: 4px; }
.cost-rule span { font-size: 12px; color: var(--muted); line-height: 1.55; }
.cost-input { display: flex; align-items: center; gap: 7px; }
.cost-input input { flex: 1 1 auto; text-align: right; }
.cost-input span { font-size: 12.5px; color: var(--muted); min-width: 34px; }
.cost-chain { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; margin-top: 16px; }
.cost-chain > div { background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 9px; padding: 9px 13px; display: flex; flex-direction: column; gap: 1px; min-width: 132px; }
.cost-chain > div.op { background: none; border: none; align-items: center; justify-content: center;
  min-width: 0; padding: 0 3px; font-size: 13px; font-weight: 700; color: var(--muted); }
.cost-chain span { font-size: 11.5px; color: var(--muted); }
.cost-chain b { font-size: 16px; color: var(--heading); }
.cost-chain em { font-size: 10.5px; color: var(--muted); font-style: normal; }
.cost-chain .grand { border-color: var(--accent); }
.cost-chain .grand b { color: var(--c-main-fg); }
.cost-chain .bargain { margin-left: auto; border-style: dashed; }
.qt-lines td.qt-kind { font-size: 12px; color: var(--muted); max-width: 260px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qt-lines td.qt-kind .mono { color: var(--text); }

/* ============================================================
   The service catalogue: the WBS read commercially.

   The stage headings used to sit directly on top of their table, so a
   fifteen-stage page read as one long list with words in it. Each stage is
   now a card with a banner of its own, a clear gap between it and the next,
   and the table inset inside it — so the eye lands on "which stage am I in"
   before it lands on a row.
   ============================================================ */
.cat-head { padding-bottom: 18px; }
.cat-strip { display: flex; flex-wrap: wrap; gap: 10px; }
.cat-stat { flex: 1 1 170px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px; display: flex; flex-direction: column; gap: 2px; }
.cat-stat .cat-num { font-size: 28px; font-weight: 700; color: var(--heading); line-height: 1.05; }
.cat-stat .cat-lab { font-size: 12.5px; font-weight: 600; color: var(--text); }
.cat-stat .cat-sub { font-size: 11.5px; color: var(--muted); line-height: 1.35; }

.cat-formula { margin-top: 16px; padding: 14px 16px; border-radius: 12px;
  background: var(--surface-2); border: 1px solid var(--border); }
.cat-formula-lead { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 14px; }
.cat-formula-lead i { flex: none; width: 18px; height: 18px; color: var(--accent); margin-top: 2px; }
.cat-formula-lead b { display: block; font-size: 13.5px; color: var(--heading); margin-bottom: 3px; }
.cat-formula-lead span { font-size: 12px; color: var(--muted); line-height: 1.55; }
.cat-chain { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; }
.cat-chain > div { background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 13px; display: flex; flex-direction: column;
  gap: 2px; min-width: 128px; justify-content: center; }
.cat-chain > div.op { background: none; border: none; align-items: center; justify-content: center;
  min-width: 0; padding: 0 2px; font-size: 15px; font-weight: 700; color: var(--muted); }
.cat-chain span { font-size: 11.5px; color: var(--muted); }
.cat-chain b { font-size: 16px; color: var(--heading); }
.cat-chain .grand { border-color: var(--accent); }
.cat-chain .grand b { color: var(--c-main-fg); font-size: 18px; }
.cat-chain em { font-size: 10.5px; color: var(--muted); font-style: normal; }
.cat-pct > div { display: flex; align-items: baseline; gap: 3px; }
.cat-pct input { width: 74px; padding: 3px 6px; font-size: 16px; font-weight: 700;
  text-align: right; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface-2); color: var(--heading); }
.cat-pct input:disabled { background: none; border-color: transparent; padding-left: 0; }
.cat-pct em { font-size: 13px; font-style: normal; color: var(--muted); }
.cat-save { background: none !important; border: none !important; flex-direction: row !important;
  align-items: center; gap: 9px !important; margin-left: auto; }
.cat-save .muted { font-size: 11.5px; max-width: 240px; line-height: 1.4; }

.cat-controls { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.cat-controls #cat-q { min-width: 260px; flex: 1 1 260px; }
.cat-controls select { max-width: 220px; }

.cat-body { display: flex; flex-direction: column; gap: 18px; margin-top: 18px; }
.cat-stage { background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; overflow: hidden; box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,.04)); }
.cat-stage-head { display: flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 14px 18px; background: var(--surface-2);
  border-bottom: 1px solid var(--border); user-select: none; }
.cat-stage-head:hover { background: color-mix(in srgb, var(--accent) 6%, var(--surface-2)); }
.cat-stage-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cat-stage-no { flex: none; min-width: 30px; height: 30px; padding: 0 8px; border-radius: 8px;
  background: var(--accent); color: #fff; font-weight: 700; font-size: 14px;
  display: flex; align-items: center; justify-content: center; }
.cat-stage-name { font-size: 15px; font-weight: 700; color: var(--heading); }
.cat-stage-meta { margin-left: auto; text-align: right; display: flex; flex-direction: column; gap: 1px; }
.cat-stage-meta b { font-size: 12.5px; color: var(--text); }
.cat-stage-meta span { font-size: 11.5px; color: var(--muted); }
.cat-stage-chev { flex: none; width: 18px; height: 18px; color: var(--muted); transition: transform .15s; }
.cat-stage.shut .cat-stage-chev { transform: rotate(-90deg); }
.cat-stage.shut .cat-stage-head { border-bottom: none; }
.cat-stage.shut .cat-stage-body { display: none; }
.cat-stage-body { padding: 4px 6px 6px; }
.cat-table tbody tr { cursor: pointer; }
.cat-table tbody tr.cat-below { box-shadow: inset 3px 0 0 var(--danger); }
.cat-grade { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.cat-set { font-size: 10.5px; color: var(--accent); font-weight: 600; }
.cat-loss { color: var(--danger); font-weight: 700; }
.cat-gap { display: flex; align-items: center; gap: 4px; font-size: 11px;
  color: var(--warning-text, #a86400); margin-top: 3px; }
.cat-gap i { width: 12px; height: 12px; }
.cat-drift { font-size: 11px; font-weight: 700; }
.cat-drift.over { color: var(--danger); }
.cat-drift.under { color: var(--success-text, #17794f); }

/* ---------- one service, priced ---------- */
.pricing { display: flex; flex-direction: column; gap: 14px; }
/* Two panels side by side while there is room for both, stacked when there is
   not. The old rule asked the VIEWPORT how wide it was — but this grid usually
   renders inside a dialog, which is far narrower than the window, so on a
   1400px laptop the right-hand panel (the whole price build-up, including the
   control that adopts a price) sat past the edge of a box that scrolls
   sideways without saying so. auto-fit asks the container instead. */
.pricing-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; }
.pricing-box { min-width: 0; }
/* Stacked panels need air between them. The three history boxes below the
   build-up are siblings of the two-column grid rather than children of it, so
   they inherited no gap and their 1px borders met — two panels reading as one
   box with a line through it. The grid's own children keep their column gap
   and must NOT take this margin, or the right-hand panel drops half a row. */
.pricing-box + .pricing-box,
.pricing-grid + .pricing-box { margin-top: 14px; }
.pricing-grid > .pricing-box + .pricing-box { margin-top: 0; }
.pricing-box table.pricing-parts { width: 100%; }
/* The build-up table has six columns and lives in one half of a grid. When
   the box is narrower than the table, the table has to scroll INSIDE it —
   left to itself it simply drew past the groupbox border, which reads as a
   broken panel rather than as content continuing. */
.pricing-box .parts-scroll { overflow-x: auto; max-width: 100%; }
/* `table.data` carries min-width:600px and nowrap cells — right for a register
   that owns a whole page, wrong for a build-up table sharing a row with the
   price panel. In a ~510px box it was cut off mid-column: the Rate header read
   "R.", the amounts were sliced down the middle, and the total lost its
   figure. It reads as a table drawn past its own groupbox, which is what it
   was. Here the table fits the box: descriptions wrap, the numbers stay whole,
   and the padding tightens rather than the content disappearing. */
.pricing-box .parts-scroll table.pricing-parts { min-width: 0; width: 100%; }
.pricing-box .pricing-parts thead th,
.pricing-box .pricing-parts tbody td,
.pricing-box .pricing-parts tfoot td { padding-left: 8px; padding-right: 8px; }
.pricing-box .pricing-parts tbody td { white-space: normal; max-width: none; overflow-wrap: anywhere; }
.pricing-box .pricing-parts thead th { white-space: normal; }
.pricing-box .pricing-parts td.num, .pricing-box .pricing-parts th.num { white-space: nowrap; }
.pricing-box .pricing-parts td.row-actions { padding-left: 2px; padding-right: 4px; }
/* The Kind cell holds one short word beside an icon. Left to wrap-anywhere it
   broke "Material" into "Materia/l", which looks like a rendering fault. */
.pricing-box .pricing-parts tbody td:first-child { white-space: nowrap; overflow-wrap: normal; }
.pricing-box { background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px 16px; }
.pricing-box h3 { display: flex; align-items: center; gap: 7px; margin: 0 0 10px;
  font-size: 13px; color: var(--heading); }
.pricing-box h3 i { width: 15px; height: 15px; color: var(--accent); }
.pricing-box h3 .muted { font-weight: 400; font-size: 11.5px; margin-left: auto; }
.pricing-parts { background: var(--surface); }
.pricing-parts td i { width: 13px; height: 13px; vertical-align: -2px; margin-right: 4px; color: var(--muted); }
.pricing-add { display: flex; gap: 8px; margin-top: 11px; flex-wrap: wrap; }
.ask-build { display: flex; flex-wrap: wrap; align-items: stretch; gap: 6px; margin-bottom: 14px; }
.ask-build > div { background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; padding: 8px 11px; display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; }
.ask-build > div.op { background: none; border: none; align-items: center; justify-content: center;
  flex: 0 0 auto; padding: 0; font-size: 14px; color: var(--muted); }
.ask-build span { font-size: 11px; color: var(--muted); line-height: 1.3; }
.ask-build b { font-size: 15px; color: var(--heading); }
.ask-build .built { border-color: var(--accent); }
.ask-build .built b { color: var(--accent); }
.ask-mode { display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  color: var(--text); margin-bottom: 9px; cursor: pointer; }
.ask-own, .ask-hours { display: flex; flex-direction: column; gap: 6px; }
.ask-hours { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.ask-hours > label { font-size: 12px; font-weight: 600; color: var(--text); }
.ask-verdict { display: flex; gap: 6px; align-items: flex-start; font-size: 12px; line-height: 1.45; }
.ask-verdict i { flex: none; width: 14px; height: 14px; margin-top: 2px; }
.ask-verdict.ok { color: var(--success-text, #17794f); }
.ask-verdict.thin { color: var(--warning-text, #a86400); }
.ask-verdict.bad { color: var(--danger); font-weight: 600; }
.ask-actions { display: flex; gap: 8px; margin-top: 14px; }
.pricing-measured { display: flex; gap: 9px; align-items: center; padding: 11px 14px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border);
  font-size: 12.5px; line-height: 1.5; }
.pricing-measured i { flex: none; width: 16px; height: 16px; color: var(--accent); }
.pricing-measured.thin { color: var(--muted); }
.pricing-measured.thin i { color: var(--muted); }

/* ---------- a rate, and every version of it ---------- */
.rate-used { display: flex; gap: 9px; align-items: flex-start; margin-bottom: 12px;
  font-size: 12px; color: var(--muted); line-height: 1.5; }
.rate-used i { flex: none; width: 15px; height: 15px; margin-top: 2px; color: var(--accent); }
.rate-hist tbody tr.rate-now { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.rate-delta { font-weight: 600; }
.rate-delta.up { color: var(--danger); }
.rate-delta.down { color: var(--success-text, #17794f); }
.rate-delta div { font-size: 10.5px; font-weight: 400; opacity: .8; }
.rate-actions { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.measured-lead { display: flex; gap: 10px; align-items: center; padding: 11px 14px;
  margin-bottom: 12px; border-radius: 10px; font-size: 12.5px; line-height: 1.5;
  background: var(--surface); border: 1px solid var(--border); }
.measured-lead i { flex: none; width: 16px; height: 16px; }
.measured-lead > div { flex: 1 1 auto; }
.measured-lead.ok i { color: var(--success-text); }
.measured-lead.over i { color: var(--danger); }
.measured-lead.under i { color: var(--warning-text); }
.measured-lead.thin { color: var(--muted); }
.measured-lead.thin i { color: var(--muted); }
.pricing-parts td.row-actions { white-space: nowrap; text-align: right; }
.pricing-parts td.row-actions .muted i { width: 13px; height: 13px; opacity: .5; }

/* ---------- Date-range narrowing on a register toolbar ---------- */
.date-range { display: inline-flex; align-items: center; gap: 5px; }
.date-range .input[type="date"] { width: 140px; padding: 5px 7px; font-size: 12px; }
.date-range .dash { color: var(--text-dim); }
@media (max-width: 760px) {
  .date-range { flex-wrap: wrap; }
  .date-range .input[type="date"] { width: calc(50% - 10px); }
}

/* An inline "use this number" offer inside a field hint */
.linkish { border: none; background: none; padding: 0; font: inherit; color: var(--c-main);
  font-weight: 600; cursor: pointer; text-decoration: underline dotted; }
.linkish:hover { text-decoration: underline solid; }

/* The counts on the dashboard's meetings box: only the ones that are not
   zero are drawn, because a row of zeroes is a row nobody reads. */
.dash-meet-wait { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.chip-count {
  display: inline-flex; align-items: baseline; gap: 5px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim);
  border-radius: 999px; padding: 3px 11px; font-size: 11.5px; font-family: inherit;
}
.chip-count:hover { border-color: var(--c-main); }
.chip-count b { font-size: 13px; font-weight: 800; color: var(--text); }
.chip-count.green b { color: var(--ok-strong); }
.chip-count.orange b { color: var(--gold-strong); }
.chip-count.red b { color: var(--danger-strong); }

/* ============================================================
   The meetings hub — the strip above the meetings register.
   Counts of things waiting for the person reading it; a zero is drawn quiet
   so the eye lands on the numbers that are not zero.
   ============================================================ */
.mh-strip { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.mh-card {
  flex: 1 1 140px; min-width: 130px; text-align: left; cursor: pointer;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  padding: 11px 13px; display: flex; flex-direction: column; gap: 2px;
  box-shadow: var(--shadow-sm); transition: border-color .12s, transform .12s;
}
.mh-card:hover { border-color: var(--c-main); transform: translateY(-1px); }
.mh-card svg { width: 16px; height: 16px; color: var(--text-dim); }
.mh-card .n { font-size: 22px; font-weight: 800; line-height: 1.1; }
.mh-card .l { font-size: 11.5px; color: var(--text-dim); }
.mh-card.quiet { opacity: .62; }
.mh-card.blue .n, .mh-card.blue svg { color: var(--c-main); }
.mh-card.green .n, .mh-card.green svg { color: var(--ok-strong); }
.mh-card.orange .n, .mh-card.orange svg { color: var(--gold-strong); }
.mh-card.red .n, .mh-card.red svg { color: var(--danger-strong); }

.mh-next { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
.mh-row {
  display: flex; align-items: center; gap: 12px; text-align: left; cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
  padding: 8px 12px; font-size: 13px;
}
.mh-row:hover { border-color: var(--c-main); }
.mh-row.today { border-left: 3px solid var(--c-main); }
.mh-row .when { font-weight: 700; min-width: 128px; }
.mh-row .what { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mh-row .who { font-size: 11.5px; }
@media (max-width: 700px) {
  .mh-row { flex-wrap: wrap; gap: 4px 10px; }
  .mh-row .what { flex: 1 1 100%; }
}

/* The proposer's own words, quoted while their proposal is answered — so the
   answer is written against what was asked, not against a summary of it. */
.prop-quote {
  border-left: 3px solid var(--c-main); background: rgba(0,112,192,.06);
  border-radius: 0 10px 10px 0; padding: 10px 13px; font-size: 13px;
}
.prop-quote .q { margin-top: 6px; white-space: pre-line; }


/* ---------- Switching between light and dark ----------
   Only while `theme-anim` is on the root — see setTheme() in js/i18n.js.

   It has to be one rule over EVERYTHING, and it has to win. Naming surfaces
   individually left every element that carries its own transition fading on
   its own beat: the sticky first column (.12s), the sidebar items (.15s),
   the buttons, the rows. The owner saw exactly that — "the first column is
   different from the rest" — because it was, by 140 milliseconds.

   So during the switch every element is given the SAME duration on the SAME
   properties, with !important to override the per-element transitions, and
   the properties are named rather than `all`: transitioning `all` would
   animate transform, width and opacity too and the page would appear to
   rebuild itself instead of changing colour. The class comes off after
   320ms and every element goes back to its own transition. */
html.theme-anim, html.theme-anim *,
html.theme-anim *::before, html.theme-anim *::after {
  transition-property: background-color, background-image, color, border-color,
                       box-shadow, outline-color, fill, stroke !important;
  transition-duration: .26s !important;
  transition-timing-function: ease !important;
  transition-delay: 0s !important;
}
@media (prefers-reduced-motion: reduce) {
  html.theme-anim, html.theme-anim * { transition: none !important; }
}

/* ---------- The money of a job, as four steps (2026-09-16) ---------- */
.money-step { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.money-n {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%; font-size: 13px; font-weight: 700;
  color: #fff; background: linear-gradient(180deg, var(--c-main), var(--c-main-deep));
}
/* Which of the four steps is the one to do next. A step already done wears a
   tick and steps back; the one in hand is the only place on the page with a
   filled button on it. Nothing is hidden and nothing is disabled — a person
   who knows what they are doing can still work in any order. */
.money-step.done .money-n { background: var(--c-green, #06bd5c); }
.money-step:not(.at):not(.done) .money-n { background: var(--surface-3); color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--border-strong); }
.money-step.at .money-n { box-shadow: 0 0 0 4px rgba(0, 112, 192, .18); }
.money-step.at { font-weight: 700; }
.step-now {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--c-main); background: rgba(0, 112, 192, .1);
  border: 1px solid rgba(0, 112, 192, .3); border-radius: 999px; padding: 2px 8px;
}

.money-value { padding: 6px 2px 2px; }
.money-big { font-size: 24px; font-weight: 700; letter-spacing: -.01em; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.money-vat { font-size: 12.5px; font-weight: 500; color: var(--text-dim); }
.money-hint {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 10px;
  padding: 9px 12px; border-radius: 10px; font-size: 12.5px;
  background: color-mix(in srgb, var(--c-accent) 12%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-accent) 40%, var(--border));
}
.money-hint > i, .money-hint > svg { width: 16px; height: 16px; flex: none; color: var(--warn-strong); }
.money-sum { display: flex; align-items: center; gap: 10px; margin: 6px 0 8px; font-size: 12.5px; color: var(--text-dim); }

/* ============================================================
   Ours, not the operating system's (owner, 2026-09-17)
   ------------------------------------------------------------
   The controls a browser hands to the OS to draw — the dropdown list, the
   calendar, the colour wheel, the slider, the tick in a checkbox, the little
   arrows on a number box — are replaced by the app's own in
   components/nativeui.js and painted here. The originals stay in the DOM as
   the value; they are never seen.
   ============================================================ */
.nui-hidden { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; clip-path: inset(50%); }
/* `select.input` (element + class) outranks `.nui-hidden` (class), so a
   replaced select on the quick-design and standards pages kept its 100 %
   width off-screen and the page scrolled sideways by exactly that (found by
   allroutes, 2026-09-23). Say it at the same specificity, later in the file. */
select.nui-hidden, input.nui-hidden { width: 1px; height: 1px; }
/* The hidden original is absolutely placed, and nothing above it was
   positioned, so its box was laid out against the page itself: a select far
   down a long Quick design panel sat 450 px below the window and made the
   DOCUMENT taller than the screen. A wheel turned over anything that does not
   scroll of its own (the name box at the bottom-left) then scrolled the whole
   page, menu and all (owner, 2026-10-04). The holder — or whatever element
   holds a hidden original — is its positioning box now, so the original sits
   inside it and is clipped with the view like everything else. */
.nui-holder, :has(> .nui-hidden) { position: relative; }
.nui-holder { display: inline-flex; align-items: center; min-width: 0; }
.nui-holder, .nui-dd { width: 100%; }
.nui-holder.sm, .nui-holder.tiny { width: auto; }
.nui-dd.tiny .input { height: 28px; padding: 3px 26px 3px 8px; font-size: 12px; }
.nui-dd.sm .input { height: 30px; font-size: 12.5px; }

/* ---------- a date ---------- */
.nui-date { display: inline-flex; align-items: center; gap: 6px; width: 100%; min-width: 0; }
.nui-date .nui-date-text { flex: 1 1 auto; min-width: 0; }
.nui-date-btn {
  flex: none; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--c-main); cursor: pointer;
}
.nui-date-btn:hover { background: rgba(0,112,192,.07); border-color: rgba(0,112,192,.4); }
.nui-date-btn svg { width: 16px; height: 16px; }
/* #popover-root gives a panel its place, not its paint — without a surface
   of its own the calendar was see-through over the form (owner, 2026-09-19). */
.nui-cal, .nui-colors {
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: var(--shadow); overflow-y: auto;
}
.nui-cal { padding: 10px; min-width: 268px; }
.nui-colors { padding: 10px; }
.nui-cal-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
/* the month arrows are the calendar's main controls, so they look like buttons */
.nui-cal-head .icon-btn {
  width: 32px; height: 32px; border: 1px solid var(--border-strong); background: var(--surface-2, var(--surface)); color: var(--c-main);
}
.nui-cal-head .icon-btn:hover { background: rgba(0,112,192,.1); border-color: var(--c-main); }
.nui-cal-head .icon-btn svg { width: 18px; height: 18px; stroke-width: 2.4; }
.nui-cal-head b { font-size: 13.5px; color: var(--heading); }
.nui-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.nui-cal-w { text-align: center; font-size: 10.5px; font-weight: 700; color: var(--text-dim); padding-bottom: 4px; text-transform: uppercase; }
.nui-cal-d {
  height: 32px; border: none; background: none; border-radius: 8px; cursor: pointer;
  font-size: 12.5px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums;
}
.nui-cal-d:hover:not(:disabled) { background: rgba(0,112,192,.1); color: var(--c-main); }
.nui-cal-d.out { color: var(--text-dim); opacity: .6; font-weight: 500; }
.nui-cal-d.today { box-shadow: inset 0 0 0 1.5px var(--border-strong); }
.nui-cal-d.on { background: var(--c-main); color: #fff; }
.nui-cal-d:disabled { opacity: .3; cursor: not-allowed; }
.nui-cal-foot { display: flex; gap: 6px; justify-content: space-between; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }

/* ---------- a colour ---------- */
.nui-swatch {
  width: 30px; height: 26px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border-strong);
  background-image: linear-gradient(45deg, var(--surface-3) 25%, transparent 25%, transparent 75%, var(--surface-3) 75%),
                    linear-gradient(45deg, var(--surface-3) 25%, transparent 25%, transparent 75%, var(--surface-3) 75%);
  background-size: 10px 10px; background-position: 0 0, 5px 5px;
}
.nui-color-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 3px; }
.nui-color-grey { display: grid; grid-template-columns: repeat(9, 1fr); gap: 3px; margin-top: 6px; }
.nui-color { width: 20px; height: 20px; border-radius: 5px; border: 1px solid rgba(0,0,0,.12); cursor: pointer; padding: 0; }
.nui-color:hover { outline: 2px solid var(--c-main); outline-offset: 1px; }
.nui-color-hex { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 11.5px; }
.nui-color-hex .input { width: 110px; }

/* ---------- a slider ---------- */
.nui-range-wrap { flex: 1; min-width: 160px; }
.nui-range { position: relative; height: 22px; width: 100%; cursor: pointer; touch-action: none; }
.nui-range::before { content: ''; position: absolute; left: 0; right: 0; top: 9px; height: 4px; border-radius: 3px; background: var(--surface-3); box-shadow: inset 0 0 0 1px var(--border); }
.nui-range-fill { position: absolute; left: 0; top: 9px; height: 4px; border-radius: 3px; background: var(--c-main); }
.nui-range-thumb {
  position: absolute; top: 3px; width: 16px; height: 16px; margin-left: -8px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--c-main); box-shadow: var(--shadow-sm);
}
.nui-range:focus-visible { outline: none; }
.nui-range:focus-visible .nui-range-thumb { box-shadow: 0 0 0 3px rgba(0,112,192,.28); }

/* ---------- a tick, a dot, a number, a fold ---------- */
/* The box and the tick are drawn here; the OS draws neither. `accent-color`
   only tints what the OS draws, which is why it is gone. */
input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none; flex: none;
  width: 16px; height: 16px; margin: 0; cursor: pointer; position: relative;
  border: 1.5px solid var(--border-strong); background: var(--surface);
  border-radius: 5px; transition: background .12s, border-color .12s;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:hover, input[type="radio"]:hover { border-color: var(--c-main); }
input[type="checkbox"]:checked, input[type="radio"]:checked { background: var(--c-main); border-color: var(--c-main); }
input[type="checkbox"]:checked::after {
  content: ''; position: absolute; left: 4px; top: 0.5px; width: 4px; height: 9px;
  border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(42deg);
}
input[type="radio"]:checked::after { content: ''; position: absolute; inset: 3px; border-radius: 50%; background: #fff; }
input[type="checkbox"]:indeterminate { background: var(--c-main); border-color: var(--c-main); }
input[type="checkbox"]:indeterminate::after { content: ''; position: absolute; left: 3px; right: 3px; top: 6px; height: 2px; background: #fff; border: none; transform: none; }
input[type="checkbox"]:disabled, input[type="radio"]:disabled { opacity: .45; cursor: not-allowed; }
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible { outline: 2px solid rgba(0,112,192,.45); outline-offset: 2px; }
/* the browser's stepper arrows and its password eye are its own furniture */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input::-ms-reveal, input::-ms-clear { display: none; }
/* a fold opens with our chevron, not the OS triangle */
summary { list-style: none; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
summary::before {
  content: ''; display: inline-block; width: 6px; height: 6px; margin-right: 8px; vertical-align: middle;
  border: solid currentColor; border-width: 0 1.6px 1.6px 0; transform: rotate(-45deg); transition: transform .15s;
}
details[open] > summary::before { transform: rotate(45deg); }

/* ---------- the scrollbars, as far as a page may paint them ---------- */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ---------- what the job is worth: the value screen (2026-09-17) ---------- */
.pv-bar { flex-wrap: wrap; }
.pv-grid { display: grid; grid-template-columns: minmax(300px, 1.1fr) minmax(280px, .9fr); gap: 14px; margin-top: 14px; align-items: start; }
@media (max-width: 860px) { .pv-grid { grid-template-columns: 1fr; } }
.pv-inputs { margin: 0; }
/* whose mark-up the figures are: the practice's, or this project's own */
.pv-markup { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 12px; margin: -2px 0 8px; }
.pv-markup .icon { width: 14px; height: 14px; }
.pv-lump { display: flex; width: fit-content; max-width: 100%; }
.pv-lump button { white-space: normal; text-align: left; }
/* the figure everyone is here for stays in view while the rest scrolls */
.pv-out { padding: 14px 16px; position: sticky; top: 0; }
.pv-line { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; padding: 5px 0; font-size: 13px; }
.pv-line .k { color: var(--text-dim); }
.pv-line .v { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.pv-line.minus .v { color: var(--c-red); }
.pv-line.sub { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 8px; }
.pv-line.grand { border-top: 2px solid var(--border-strong); margin-top: 6px; padding-top: 10px; }
.pv-line.grand .k b { color: var(--heading); font-size: 14px; }
.pv-line.grand .k .muted { font-size: 11px; margin-left: 6px; }
.pv-line.grand .v { font-size: 19px; font-weight: 800; color: var(--c-main); }
.pv-note { font-size: 11.5px; padding: 2px 0 6px; }
/* the same build-up, one line, on the Money tab */
.pv-strip { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.pv-chip { display: inline-flex; align-items: baseline; gap: 6px; padding: 4px 10px; border-radius: 999px;
  background: var(--surface-3); font-size: 12px; white-space: nowrap; }
.pv-chip .k { color: var(--text-dim); }
.pv-chip .v { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--heading); }

/* A date field in a row of controls is as wide as a date, not as wide as the
   row: our own date box replaced an `<input type=date>` that had sized itself,
   and the buttons beside it were squeezed to their padding (owner,
   2026-09-17 — the "This month" and "All dates" buttons in every register). */
.toolbar .nui-date, .date-range .nui-date, .toolbar .nui-holder, .money-flow .nui-date { width: auto; flex: 0 0 auto; }
.toolbar .nui-date .nui-date-text, .date-range .nui-date .nui-date-text { width: 132px; flex: 0 0 132px; }
.date-range { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.date-range .btn { flex: none; white-space: nowrap; }
.toolbar .btn.ghost.small, .toolbar .btn.small { flex: none; white-space: nowrap; }

/* the computed price is a button in all but name: clicking it offers that
   figure (owner, 2026-09-17) */
.qt-work td.pv-take { cursor: pointer; }
.qt-work td.pv-take:hover { color: var(--c-main); text-decoration: underline; text-underline-offset: 3px; }

/* a register that totals its money: the sum sits under the first column and
   the money columns, in the table's own footer (2026-09-17) */
table.data tfoot td.col-stick { position: sticky; left: 0; z-index: 1; background: var(--surface-2); }

@media (prefers-reduced-motion: reduce) {
  .toast { transition: opacity .2s linear; }
}

/* ============================================================
   One table (round 8b, 2026-10-06)
   ------------------------------------------------------------
   Every list in the app is components/table.js now — registers, roll-ups,
   the tables inside a tab or a dialog — so these finish the few things a
   roll-up needs that a register did not.
   ============================================================ */
/* a column of sentences wraps instead of being cut short */
table.data tbody td.wrap { white-space: normal; max-width: 420px; overflow: visible; }
/* a table whose order is its meaning: the headers are labels, not buttons */
table.data.nosort thead th { cursor: default; }
table.data.nosort thead th:hover { color: var(--text-dim); }
table.data th.th-actions { cursor: default; }
table.data th.th-actions:hover { color: var(--text-dim); }
/* the actions column takes the room its icons need and no more */
table.data td.cell-actions { width: 1%; white-space: nowrap; }
.rt-table > .tbl-top:empty { display: none; }
/* "Show [25] per page" reads as one line wherever it stands — above a
   roll-up it has no toolbar to size it, and the dropdown took the width */
.al-size { white-space: nowrap; }
.al-size .nui-holder { width: auto; flex: 0 0 auto; }
.al-size .nui-dd { width: 76px; }
.rt-table + .rt-table, .rt-table + .muted { margin-top: var(--sp-3); }
/* the ranked candidates keep their coloured edge while the table scrolls */
.x-scrolled table.data tr.cand.ok td:first-child { box-shadow: inset 3px 0 0 var(--c-green); }
.x-scrolled table.data tr.cand.warn td:first-child { box-shadow: inset 3px 0 0 var(--warn-strong); }
.x-scrolled table.data tr.cand.bad td:first-child { box-shadow: inset 3px 0 0 var(--border-strong); }

/* ---------- the app's own fold (components/ui/collapsible.js) ----------
   Where the browser's <details> was: the same chevron and spacing on every
   OS, read as a link-coloured line rather than a form section. */
.ui-fold { margin-top: 10px; border-top: none; padding-top: 0; }
.ui-fold > .fs-head { padding: 4px 0; font-size: 12.5px; font-weight: 600; color: var(--c-main); }
.ui-fold > .fs-head svg { color: var(--c-main); }
.ui-fold > .fs-head:hover .ui-col-title { text-decoration: underline; }
.ui-fold > .fs-body { margin-top: 8px; }
