/* ==========================================================================
   Irada Hub — the Irada Foundation board workspace design system
   (forked from MarkerOps): neutral surfaces, a PER-MEMBER accent, dark +
   light modes, and the Irada brand as a touch — navy #1D2D5E on the sign-in
   screen and a pink → purple → cyan gradient line under the top bar.

   ---------------------------------------------------------------- theming
   Every colour comes from a custom property. Two blocks define them all:

       :root                 the DARK palette (the default)
       [data-theme="light"]  the same names, light values

   Nothing below those two blocks mentions a mode. If a component needs to
   differ between them, the fix is a new token, not a second rule.

   light-dark() would express the same thing in one declaration per token, but
   plain overrides are used instead: they need no color-scheme plumbing, they
   work on older engines, and a token can hold any value type (the two native
   control filters and the select arrow are not colours).

   <html data-theme> is always "dark" or "light" by the time anything paints:
   index.php resolves 'auto' in a pre-paint inline script and app.js keeps it
   resolved, following the OS while the member stays on auto.

   ----------------------------------------------------------------- accent
   --accent is the member's raw hex, written inline on <html> by index.php
   (and by app.js when they pick a new one). Everything else is DERIVED:

       --accent        raw hex — solid fills ONLY, always under --accent-ink
       --accent-ink    readable text on top of --accent (auto black/white)
       --accent-fg     accent as a FOREGROUND: text, icons, focus rings
       --accent-line   accent as a 1–3px stroke
       --accent-soft   translucent accent wash for backgrounds

   This split is the whole reason light mode is legible: lime, amber and mint
   sit near 1.1:1 on white untouched, so --accent-fg and --accent-line clamp
   the OKLCH lightness (L<=.48 / L<=.62) before the accent is ever used as
   ink. Dark mode floors it instead (L>=.70 / .62) so a deep accent cannot
   vanish into the panel. Measured across all six accents in both modes:
   4.9–16:1 as text, 3.3:1+ as a stroke, 7.7:1+ for ink on the accent.

   app.js also writes --accent INLINE on .tcard/.ccard/.cprog/.client-row/
   .cal-chip to carry a CLIENT's colour. Derived tokens are computed where
   they are declared, so a :root token would ignore that — anything that must
   follow the client accent is written at its use site (search "client
   stripe").

   Layers, in order:
     1  tokens + reset
     2  primitives      buttons, inputs, chips, pills, avatars, bars
     3  shell           topbar, sidenav, view frame
     4  auth            splash, login, forced PIN
     5  dashboard
     6  board           + drag and drop
     7  drawer          task detail
     8  clients
     9  members + account
    10  overlays        modals, toasts, empty/skeleton states
    11  theme menu
    12  notifications
    13  timers
    14  calendar
    15  workload
    16  inline-colour repairs for app.js-generated markup
    17  motion
    18  responsive
   ========================================================================== */

/* ------------------------------------------------------------ 1. tokens */

/* Mode-independent: geometry, type, motion. */
:root {
  --r-sm: 10px;
  --r:    14px;
  --r-lg: 18px;
  --r-xl: 24px;

  /* Inter is the default (self-hosted under assets/fonts/), so a page with no
     data-font attribute (and no inline stack from index.php) paints in it. */
  --f-head: 'Inter', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'Inter', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, Roboto, sans-serif;

  /* The Irada brand. Navy is a touch (sign-in, the top-bar band), never a
     surface the whole app sits on; the gradient is a line, never a fill. */
  --brand-navy: #1D2D5E;
  --brand-pink: #E94B9A;
  --brand-purple: #8B5CF6;
  --brand-cyan: #22D3EE;
  --brand-grad: linear-gradient(90deg, #E94B9A 0%, #8B5CF6 50%, #22D3EE 100%);
  --f-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --topbar-h: 62px;
  --nav-w:    212px;

  --ease: cubic-bezier(.22,.61,.36,1);
}

/* ------------------------------------------------------------- font system */
/*
 * The member picks a face; the choice rides on <html data-font>. index.php also
 * writes the resolved stack INLINE on <html>, because the first painted frame
 * has to be right before any stylesheet is parsed.
 *
 * That inline declaration outranks everything below, so these rules are NOT how
 * a face gets switched at runtime — app.js rewrites --f-head/--f-body directly
 * in applyTheme(), exactly as it does for --accent. What data-font still drives
 * is the per-face optical trim further down (a couple of the faces run larger
 * or tighter than the others), and it keeps the app correct if that inline
 * style is ever dropped.
 *
 * Every face is self-hosted (assets/fonts/<key>.css — no CDN), and every stack
 * ends in the same system fallbacks, so a missing font file degrades to the OS
 * UI face rather than to Times. "custom" IS the OS face.
 */
html[data-font="jakarta"] {
  --f-head: 'Plus Jakarta Sans', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'Plus Jakarta Sans', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
}
html[data-font="inter"] {
  --f-head: 'Inter', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'Inter', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
}
html[data-font="grotesk"] {
  --f-head: 'Space Grotesk', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'Space Grotesk', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
}
html[data-font="plex"] {
  --f-head: 'IBM Plex Sans', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: 'IBM Plex Sans', ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
}
html[data-font="custom"] {
  --f-head: ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
  --f-body: ui-sans-serif, 'Segoe UI', system-ui, -apple-system, sans-serif;
}

/*
 * Optical compensation, scoped to the one face that needs it. The type scale is
 * tuned for Jakarta; two of the alternates set to a different apparent size on
 * the same em, and the place that shows first is the dense grids.
 *
 *   grotesk  sets LARGE and its numerals are wide — a money column gains ~6%
 *            width, which is what pushes a table into its scroller early. Half a
 *            point off the cell, a touch less tracking on the header.
 *   plex     has a smaller x-height — the 10.5–12px captions go muddy. They get
 *            it back; the headline scale is left alone.
 *
 * jakarta and inter are deliberately untouched.
 */
html[data-font="grotesk"] .tt-cell,
html[data-font="grotesk"] .fin-table td,
html[data-font="grotesk"] .inv-table td { font-size: 13px; }
html[data-font="grotesk"] .tt-th,
html[data-font="grotesk"] .fin-table th,
html[data-font="grotesk"] .inv-table th { letter-spacing: .06em; }

html[data-font="plex"] .tt-cell,
html[data-font="plex"] .fin-table td,
html[data-font="plex"] .inv-table td { font-size: 14px; }
html[data-font="plex"] .hint,
html[data-font="plex"] .stat-foot,
html[data-font="plex"] .cr-nums,
html[data-font="plex"] .guide-step::before { font-size: 12.5px; }

/* ---------------------------------------------------------------- DARK */
/* The dark palette is the default: :root carries it, and [data-theme="light"]
   overrides the same names below. Nothing else in the file knows about modes. */
:root {
  color-scheme: dark;

  --ink:    #0B0B0C;
  --panel:  #131317;
  --panel2: #191920;
  --panel3: #202028;
  --line:   #26262c;
  --line2:  #33333c;
  --paper:  #F7F7F5;
  --mut:    #9a9aa2;
  /* --faint is still real text (timestamps, counts, hints), so it is held at
     ~5:1 on its own panel rather than the 3.6:1 a softer grey would give. */
  --faint:  #8A8A93;

  --topbar-bg: rgba(11,11,12,.86);
  --nav-bg:    rgba(19,19,23,.4);
  --col-bg:    rgba(19,19,23,.6);
  --scrim:     rgba(5,5,6,.72);
  --scroll:    #2f2f38;
  --scroll-h:  #3d3d48;
  --well:      #0B0B0C;
  --ring:      rgba(255,255,255,.05);
  --toast-bg:  var(--panel3);

  --btn-hover-bg:   #2a2a33;
  --btn-hover-line: #43434f;
  --focus-bg:       var(--panel3);
  --on-status:      var(--ink);

  /* A surface that must read as LIFTED above --panel2 (the selected chip in a
     segmented control). Dark lifts by going lighter, light by going whiter —
     the one direction the panel ramp cannot express with a single name. */
  --lift:           var(--panel3);

  /* Dark gets lift from glow, not from cast shadows, so cards carry none. */
  --sh-c1:   rgba(0,0,0,.5);
  --sh-c2:   rgba(0,0,0,.35);
  --sh-c3:   rgba(0,0,0,.6);
  --sh-card: none;
  --bloom1:  color-mix(in srgb, var(--accent) 7%, transparent);
  --bloom2:  color-mix(in srgb, var(--accent) 5%, transparent);
  --auth-glow: color-mix(in srgb, var(--accent) 16%, transparent);

  --danger: #fb7185;
  --warn:   #fbbf24;
  --info:   #38bdf8;
  --danger-ink:    #ffd0d8;   /* text on a danger-tinted surface */
  --danger-on:     #2b0a12;   /* text on a solid danger fill     */
  --danger-hover:  #ff8ea0;
  --warnbox-ink:   #ffc3cd;

  --st-backlog:   #6b7280;
  --st-progress:  #22D3EE;
  --st-review:    #8B5CF6;
  --st-client:    #fbbf24;
  --st-revisions: #E94B9A;
  --st-done:      #34D399;
  --st-hold:      #94a3b8;
  --st-cancel:    #4b5563;

  /* Status colours used as small label TEXT. The fills are tuned for bars and
     dots (3:1); as small text several of them missed 4.5:1 — backlog at 3.7
     and canceled at 2.5 on a dark panel. Flooring L at .70 fixes both while
     keeping the hue. Light mode caps instead (see the light block). */
  --st-backlog-ink:  var(--st-backlog);
  --st-backlog-ink:  oklch(from var(--st-backlog) max(l, .70) c h);
  --st-progress-ink: var(--st-progress);
  --st-progress-ink: oklch(from var(--st-progress) max(l, .70) c h);
  --st-review-ink:   var(--st-review);
  --st-review-ink:   oklch(from var(--st-review) max(l, .70) c h);
  --st-client-ink:   var(--st-client);
  --st-client-ink:   oklch(from var(--st-client) max(l, .70) c h);
  --st-revisions-ink:var(--st-revisions);
  --st-revisions-ink:oklch(from var(--st-revisions) max(l, .70) c h);
  --st-done-ink:     var(--st-done);
  --st-done-ink:     oklch(from var(--st-done) max(l, .70) c h);
  --st-hold-ink:     var(--st-hold);
  --st-hold-ink:     oklch(from var(--st-hold) max(l, .70) c h);
  --st-cancel-ink:   var(--st-cancel);
  --st-cancel-ink:   oklch(from var(--st-cancel) max(l, .70) c h);

  --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%239a9aa2' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  --pick-invert: invert(.65);
}

/* --------------------------------------------------------------- LIGHT */
/*
 * Same names, light values. Overriding custom properties on :root inherits
 * and invalidates correctly, so app.js can switch themes live just by
 * rewriting <html data-theme> — verified by flipping both ways and measuring
 * every text node in both modes.
 *
 * 'auto' is never seen here: index.php resolves it to a concrete mode in a
 * pre-paint inline script, and app.js keeps it resolved (and follows the OS
 * while the member stays on auto). With JS off the app does not render at all,
 * so there is nothing to theme.
 */
[data-theme="light"] {
  color-scheme: light;

  --ink:    #F6F7F5;
  --panel:  #FFFFFF;
  --panel2: #F1F2EF;
  --panel3: #E7E9E3;
  --line:   #E3E5DF;
  --line2:  #CFD2CA;
  --paper:  #111214;
  --mut:    #5C6060;
  --faint:  #6B706A;

  --topbar-bg: rgba(255,255,255,.84);
  --nav-bg:    rgba(255,255,255,.55);
  --col-bg:    rgba(241,242,239,.75);
  --scrim:     rgba(30,33,28,.42);
  --scroll:    #CFD2CA;
  --scroll-h:  #B4B8AE;
  --well:      #F1F2EF;
  --ring:      rgba(17,18,20,.05);
  --toast-bg:  var(--panel);

  --btn-hover-bg:   #DCDFD7;
  --btn-hover-line: #BFC3B9;
  --focus-bg:       var(--panel);
  --on-status:      #FFFFFF;

  /* see the dark block: light lifts by going whiter, not darker */
  --lift:           var(--panel);

  /* Light mode earns its depth the other way round: real cast shadows. */
  --sh-c1:   rgba(17,18,20,.07);
  --sh-c2:   rgba(17,18,20,.09);
  --sh-c3:   rgba(17,18,20,.20);
  --sh-card: var(--sh-sm);
  --bloom1:  color-mix(in srgb, var(--accent) 12%, transparent);
  --bloom2:  color-mix(in srgb, var(--accent) 8%, transparent);
  --auth-glow: color-mix(in srgb, var(--accent) 30%, transparent);

  /* These three are text far more often than fill, so they sit a step deeper
     than the status ramp (6.0–6.6:1 on white). */
  --danger: #BE123C;
  --warn:   #92400E;
  --info:   #0369A1;
  --danger-ink:   #9F1239;
  --danger-on:    #FFF5F7;
  --danger-hover: #9F1239;
  --warnbox-ink:  #9F1239;

  --st-backlog:   #4B5563;
  --st-progress:  #0E7490;
  --st-review:    #7C3AED;
  --st-client:    #B45309;
  --st-revisions: #C0267A;
  --st-done:      #047857;
  --st-hold:      #64748B;
  --st-cancel:    #6B7280;

  /* Clamping L to .48 lifts each one past 4.5:1 on its own pale tint while
     keeping its hue, so a "Done" label still reads as the done colour. */
  --st-backlog-ink:   oklch(from var(--st-backlog)   min(l, .48) c h);
  --st-progress-ink:  oklch(from var(--st-progress)  min(l, .48) c h);
  --st-review-ink:    oklch(from var(--st-review)    min(l, .48) c h);
  --st-client-ink:    oklch(from var(--st-client)    min(l, .48) c h);
  --st-revisions-ink: oklch(from var(--st-revisions) min(l, .48) c h);
  --st-done-ink:      oklch(from var(--st-done)      min(l, .48) c h);
  --st-hold-ink:      oklch(from var(--st-hold)      min(l, .48) c h);
  --st-cancel-ink:    oklch(from var(--st-cancel)    min(l, .48) c h);

  --sel-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235C6060' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  --pick-invert: none;
}

/* composed shadows — colour comes from the tokens above */
:root {
  --sh:    0 1px 2px var(--sh-c1), 0 6px 20px var(--sh-c2);
  --sh-sm: 0 1px 2px var(--sh-c1);
  --sh-lg: 0 24px 70px var(--sh-c3);
  --glow:  0 0 0 1px var(--accent-soft2), 0 10px 34px -12px var(--accent-glow);
}

/* ---------------------------------------------------------------- accent */
/*
 * --accent is the member's raw hex, written inline on <html> by index.php and
 * by app.js. Everything else is derived from it, so six accents x two modes
 * need no per-accent CSS.
 *
 * Each derived token is declared twice: a safe literal first (kept by any
 * engine without relative colour syntax), then the computed version.
 */
:root {
  --accent: #7C3AED;

  /*
   * Text ON the accent. clamp(0, (.62 - L) * 100, 1) is 0 above L .62 (black
   * ink) and 1 below it (white ink). Measured >=7.7:1 for all six accents in
   * both modes, and 10:1 on a deep navy.
   *
   * !important on purpose: app.js and index.php also write --accent-ink
   * inline (WCAG 0.179 crossover). The two agree for every shipped accent;
   * this keeps the stylesheet the final word if they ever drift.
   */
  --accent-ink: #FFFFFF;
  --accent-ink: oklch(from var(--accent) clamp(0, (.62 - l) * 100, 1) min(c, .04) h) !important;

  /* Accent as a foreground (text, icons, focus rings). Dark mode floors L at
     .70 so a deep accent cannot vanish into the panel — a no-op for all six
     shipped accents, whose darkest, violet and rose, already sit at L .70. */
  --accent-fg: var(--accent);
  --accent-fg: oklch(from var(--accent) max(l, .70) c h);

  /* Accent as a 1–3px stroke. Non-text UI needs 3:1 (WCAG 1.4.11), not 4.5. */
  --accent-line: var(--accent);
  --accent-line: oklch(from var(--accent) max(l, .62) c h);

  --accent-hover: #8B5CF6;
  --accent-hover: oklch(from var(--accent) calc(l + .05) c h);

  /*
   * NOTE — why there is no --accent-mark token.
   *
   * A derived token is computed WHERE IT IS DECLARED. Anything defined on
   * :root therefore resolves var(--accent) against :ROOT's accent, i.e. the
   * signed-in member's theme. app.js also writes --accent inline on .tcard,
   * .ccard, .cprog, .client-row and .cal-chip to carry the CLIENT's colour —
   * a root-level token would ignore that and paint every client stripe in the
   * member's accent instead.
   *
   * So the client-stripe clamp is written at each use site (search "client
   * stripe"), where var(--accent) picks up the inherited value.
   */

  --accent-soft:  rgba(124,58,237,.14);
  --accent-soft:  color-mix(in srgb, var(--accent) 15%, transparent);
  --accent-soft2: rgba(124,58,237,.30);
  --accent-soft2: color-mix(in srgb, var(--accent) 30%, transparent);
  --accent-glow:  rgba(124,58,237,.40);
  --accent-glow:  color-mix(in srgb, var(--accent) 40%, transparent);

  /*
   * COMPATIBILITY ALIAS. Three places in app.js paint with var(--lime) — a token
   * that never existed, so the declaration was invalid at computed-value time
   * and the colour silently fell back to inherited body text ("client-visible"
   * vs "internal" on a client file became literally indistinguishable). Defining
   * it as the accent foreground makes those three correct today; when app.js
   * switches to --accent-fg this alias goes unused rather than wrong.
   */
  --lime: var(--accent-fg);
}

/*
 * Light mode caps the accent instead of flooring it: lime, amber and mint sit
 * around 1.1:1 on white untouched. The cap is .48 rather than .52 because the
 * accent is often read on a tinted surface, not pure white — at .52 mint
 * measured 4.34:1 there.
 */
[data-theme="light"] {
  --accent-fg:    oklch(from var(--accent) min(l, .48) c h);
  --accent-line:  oklch(from var(--accent) min(l, .62) c h);
  --accent-hover: oklch(from var(--accent) calc(l - .06) c h);
}

/* ----------------------------------------------------------------- reset */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--f-body);
  font-size: 14.5px;
  font-weight: 400;
  line-height: 1.55;
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  /* a very soft accent bloom behind everything, so the page never reads flat */
  background-image:
    radial-gradient(900px 520px at 82% -12%, var(--bloom1), transparent 62%),
    radial-gradient(700px 460px at -8% 108%, var(--bloom2), transparent 60%);
  background-attachment: fixed;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}

h1, h2, h3, h4, .num, .f-head {
  font-family: var(--f-head); font-weight: 700; letter-spacing: -.02em;
}

button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
a { color: var(--accent-fg); text-decoration: none; }
a:hover { text-decoration: underline; }
/*
 * PROSE links carry a persistent underline. mdToHtml() turns every bare URL in a
 * comment or a brief into an anchor, and an anchor differs from the sentence
 * around it by hue alone — the accent measures ~1.1:1 against --paper, so a
 * colour-blind reader cannot see it at all (WCAG 1.4.1 wants 3:1 when colour is
 * the only differentiator). Scoped to the places that hold member-written prose;
 * the chrome anchors already read as controls and are left alone.
 */
.rich a, .brief-box a, .guide-sec a, .guide-body a, .req-body a,
.update-body a, .view-desc a, .hint a, .cmt-text a {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 2px;
}
.rich a:hover, .brief-box a:hover, .guide-sec a:hover, .guide-body a:hover,
.req-body a:hover, .update-body a:hover, .view-desc a:hover, .hint a:hover,
.cmt-text a:hover { text-decoration-color: currentColor; }
img { max-width: 100%; display: block; }
::placeholder { color: var(--faint); opacity: 1; }
.lime { color: var(--accent-fg); }
[hidden] { display: none !important; }

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

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 9px; }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-h); }
::-webkit-scrollbar-track { background: transparent; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* -------------------------------------------------------- 2. primitives */

.inp, .sel, .txa {
  width: 100%;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 10px 13px;
  color: var(--paper);
  transition: border-color .16s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
}
/* The outline is only suppressed for POINTER focus. A blanket `outline: none`
   here used to beat the global :focus-visible rule (same specificity, later in
   the file), which left keyboard users with no indicator anywhere the custom
   ring was also stripped — the drawer's meta grid, for one. */
.inp:focus:not(:focus-visible),
.sel:focus:not(:focus-visible),
.txa:focus:not(:focus-visible) { outline: none; }
.inp:hover, .sel:hover, .txa:hover { border-color: var(--line2); }
/* background-COLOR, never the shorthand: the shorthand resets background-image
   and a focused <select> would lose its arrow glyph. */
.inp:focus, .sel:focus, .txa:focus {
  border-color: var(--accent-line);
  background-color: var(--focus-bg);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/*
 * Disabled controls. `color: inherit` on the reset above undoes the UA's own
 * greying, so without this a field that cannot be typed into looks identical to
 * one that can — including the in-flight disable app.js applies to every
 * [data-patch] field while a save is running.
 */
.inp:disabled, .sel:disabled, .txa:disabled {
  color: var(--mut); background: var(--panel); border-color: var(--line);
  opacity: .6; cursor: not-allowed;
}
.inp:disabled:hover, .sel:disabled:hover, .txa:disabled:hover { border-color: var(--line); }
.txa { resize: vertical; min-height: 96px; line-height: 1.6; }
/*
 * A textarea holding a pipe table.
 *
 * Padded columns only line up in a MONOSPACE face; in the body font the
 * padding is wasted and the pipes still stagger. app.js adds this class while
 * the field holds a table and takes it off when it does not, so a brief of
 * plain prose is never made to look like code.
 *
 * Lines still WRAP — the same choice GitHub makes. Switching to `pre` would
 * keep a wide table row on one line, but every prose paragraph in the same
 * field would run off the side with it, which is the worse trade. A row too
 * wide to fit is what Preview is for.
 *
 * Declared HERE, in the primitives, and not down with the other table styles:
 * section 27's touch-input floor has to be able to raise this back to 16px on
 * a phone, and a media query adds no specificity — only source order settles
 * it. See the note at the top of that block.
 */
.txa.is-grid { font-family: var(--f-mono); font-size: 12.4px; line-height: 1.65; tab-size: 2; }
.inp[readonly] { color: var(--mut); background: var(--panel); }

.sel {
  appearance: none;
  padding-right: 34px;
  background-image: var(--sel-arrow);
  background-repeat: no-repeat;
  background-position: right 12px center;
}
.sel option { background: var(--panel2); color: var(--paper); }

.inp-pin {
  letter-spacing: .5em; font-family: var(--f-head); font-weight: 700;
  font-size: 18px; text-align: center; padding: 12px;
}

label, .lbl {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 6px;
}
.field { margin-bottom: 15px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* A pair whose other half was flag-gated away is not a pair. Without this the
   surviving field renders at half width with a blank column beside it, breaking
   the form's rhythm (the task form's "Counts as" when Publishing is off). */
.field-row:has(> :only-child) { grid-template-columns: minmax(0, 1fr); }
.field-row:empty { display: none; }
.hint { font-size: 12px; color: var(--faint); margin-top: 6px; line-height: 1.5; }

/* checkbox / switch */
.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 42px; height: 24px; border-radius: 999px; flex: none;
  background: var(--panel3); border: 1px solid var(--line2);
  position: relative; transition: background .18s var(--ease), border-color .18s var(--ease);
}
.switch-track::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%; background: var(--mut);
  transition: transform .2s var(--ease), background .2s var(--ease);
}
.switch input:checked + .switch-track { background: var(--accent-soft); border-color: var(--accent-line); }
.switch input:checked + .switch-track::after { transform: translateX(18px); background: var(--accent-line); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent-fg); outline-offset: 2px; }
/* app.js disables the checkbox for the whole round-trip on the portal and the
   client-feature toggles; without this the switch looks live while it is not. */
.switch:has(input:disabled) { cursor: progress; }
.switch input:disabled + .switch-track { opacity: .5; }
.switch-label { font-size: 13.5px; color: var(--paper); font-weight: 500; text-transform: none; letter-spacing: 0; }
.switch-label small { display: block; color: var(--faint); font-size: 12px; font-weight: 400; }

/* buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border-radius: var(--r-sm); padding: 10px 18px;
  font-family: var(--f-head); font-weight: 600; font-size: 14px;
  background: var(--panel3); color: var(--paper);
  border: 1px solid var(--line2);
  white-space: nowrap;
  transition: transform .12s var(--ease), background .16s var(--ease),
              border-color .16s var(--ease), box-shadow .18s var(--ease), color .16s var(--ease);
}
.btn:hover {
  background: var(--btn-hover-bg);
  border-color: var(--btn-hover-line);
  transform: translateY(-1px);
  box-shadow: var(--sh-sm);
}
.btn:active { transform: translateY(0) scale(.985); }
.btn:disabled { opacity: .5; cursor: default; transform: none; box-shadow: none; }

/* The accent button is a FILL — never accent-coloured text. This is what
   keeps lime, amber and mint usable on a white page. */
.btn-lime {
  background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); font-weight: 700;
}
.btn-lime:hover {
  background: var(--accent-hover); border-color: var(--accent-hover);
  color: var(--accent-ink); box-shadow: var(--glow);
}
.btn-ghost { background: transparent; border-color: var(--line); color: var(--mut); }
.btn-ghost:hover { background: var(--panel2); color: var(--paper); border-color: var(--line2); }
.btn-danger { background: transparent; border-color: color-mix(in srgb, var(--danger) 40%, transparent); color: var(--danger); }
.btn-danger:hover {
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: var(--danger);
  color: var(--danger-ink);
}
.btn-solid-danger {
  background: var(--danger); border-color: var(--danger);
  color: var(--danger-on); font-weight: 700;
}
.btn-solid-danger:hover {
  background: var(--danger-hover); border-color: var(--danger-hover);
}
.btn-sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
.btn-block { width: 100%; }
.btn-icon {
  width: 36px; height: 36px; padding: 0; border-radius: 10px;
  background: transparent; border-color: transparent; color: var(--mut); font-size: 15px;
}
.btn-icon:hover { background: var(--panel3); color: var(--paper); border-color: var(--line); transform: none; box-shadow: none; }
.btn-icon.danger:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }

.btn-spin { position: relative; color: transparent !important; pointer-events: none; }
.btn-spin::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  color: var(--accent-ink); animation: spin .7s linear infinite;
}
.btn-ghost.btn-spin::after, .btn:not(.btn-lime).btn-spin::after { color: var(--paper); }

/* chips, pills, dots */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 3px 10px;
  font-size: 11.5px; font-weight: 600; font-family: var(--f-head);
  border: 1px solid var(--line2); color: var(--mut); background: var(--panel2);
  max-width: 100%;
}
.chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; }
.chip-client { border-color: transparent; }
.chip-client .dot { box-shadow: 0 0 0 3px var(--ring); }
.chip-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
  border: 1px solid transparent;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

.prio { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600; font-family: var(--f-head); }
.prio-pip { width: 8px; height: 8px; border-radius: 2px; transform: rotate(45deg); flex: none; }

/* Priority as a word on cards and lists. Normal renders nothing; Urgent is a
   red pill that reads at a glance, Low a quiet outlined one. */
.prio-tag {
  display: inline-flex; align-items: center; flex: none;
  padding: 1px 7px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  white-space: nowrap;
}
.prio-tag[data-prio="3"] {
  color: #fff; background: var(--st-revisions-ink, #fb7185);
}
.prio-tag[data-prio="1"] {
  color: var(--mut); background: transparent; border: 1px solid var(--line2);
}
/* The priority sash across a card's top-left corner. The card is
   position:relative with overflow:hidden, so the band is clipped to the
   corner triangle; the top row steps right to stay clear of it. */
.prio-ribbon {
  position: absolute; top: 10px; left: -28px; width: 104px; z-index: 2;
  transform: rotate(-45deg); transform-origin: center; pointer-events: none;
  text-align: center; font-family: var(--f-head); font-size: 9px; font-weight: 800;
  line-height: 17px; letter-spacing: .08em; text-transform: uppercase;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}
.prio-ribbon[data-prio="3"] { background: var(--st-revisions-ink, #fb7185); color: #fff; }
.prio-ribbon[data-prio="1"] { background: var(--panel3); color: var(--mut); border-top: 1px solid var(--line2); border-bottom: 1px solid var(--line2); }
.tcard.has-ribbon .tcard-top { padding-left: 30px; }

/* "New" — the same pulse the dot had, now with a word beside it. */
.new-tag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 1px 7px 1px 6px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  color: var(--accent-fg); background: var(--accent-soft); white-space: nowrap;
}
.new-tag i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: pulse 2.4s infinite; }

.avatar {
  position: relative; overflow: hidden;   /* the photo overlays the initials */
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  color: #0b0b0c; letter-spacing: .02em;
}
.avatar.lg { width: 38px; height: 38px; font-size: 13.5px; }
.avatar.ghost { background: var(--panel3) !important; color: var(--faint); border: 1px dashed var(--line2); }
/* A profile PHOTO fills the round .avatar when avatarHTML(name, id) has one to
   emit (behind the "profiles" flag, resolved server-side). It rides on EVERY
   avatar size — .lg, .who-av, .watch-av, the mention menu, the standup — by
   filling the box and covering, so a photo is never distorted and a bare name
   with no id/photo still paints initials, exactly as before. */
.avatar img, .avatar-img {
  position: absolute; inset: 0;            /* stack over the initials fallback */
  width: 100%; height: 100%; border-radius: inherit;
  object-fit: cover; object-position: center; display: block;
}

/* The little "new activity" pip.
   Named -dot deliberately: this used to be a bare `.unread`, which also matched
   every ROW that carries `unread` as a state modifier — `.notif.unread` and
   `.tt-row.unread`. Those rows inherited a dot's 8px box, so an unread
   notification became an 8px-tall lime pill with its title, body and timestamp
   spilling out below it, and an unread list row got painted solid accent and
   set pulsing. A utility class and a state modifier must never share a name. */
.unread-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent-line);
  animation: pulse 2.4s infinite;
}

/* progress bars */
.bar { height: 7px; border-radius: 999px; background: var(--panel3); overflow: hidden; }
.bar-fill {
  height: 100%; border-radius: 999px; width: 0;
  background: linear-gradient(90deg, var(--accent-line), var(--accent));
  transition: width .95s var(--ease);
}
.bar-fill.full { box-shadow: 0 0 14px -2px var(--accent-glow); }
.bar.sm { height: 5px; }

/* cards & panels */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
}
.panel-head {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px; border-bottom: 1px solid var(--line);
}
.panel-head h2 { font-size: 15px; }
.panel-head .grow { flex: 1; }
.panel-body { padding: 18px; }

.sec-title {
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--faint);
  margin-bottom: 12px;
}

/*
 * The task drawer's collapsible Activity log.
 *
 * The summary IS the .sec-title, so it keeps the same type and spacing as
 * every other section heading — collapsing the thread should not make the
 * drawer look like a different page.
 */
.act-log-sum {
  display: flex; align-items: center; gap: 7px;
  min-height: 36px;
  margin-bottom: 0; padding: 2px 0;
  cursor: pointer; list-style: none;
  transition: color .16s var(--ease);
}
.act-log-sum::-webkit-details-marker { display: none; }
.act-log-sum:hover { color: var(--mut); }
.act-log-sum:focus-visible {
  outline: 2px solid var(--accent-line); outline-offset: 3px; border-radius: 6px;
}
.act-log-caret {
  display: inline-block; font-size: 9px; color: var(--faint);
  transition: transform .16s var(--ease);
}
.act-log[open] > .act-log-sum > .act-log-caret { transform: rotate(90deg); }
.act-log[open] > .act-log-sum { margin-bottom: 12px; }

.divider { height: 1px; background: var(--line); margin: 18px 0; border: 0; }

/* ------------------------------------------------------------- 3. shell */

.app { min-height: 100vh; }

.topbar {
  position: sticky; top: 0; z-index: 40;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px;
  background: var(--topbar-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}
.topbar .grow { flex: 1; }

.brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
.brand-logo { height: 22px; width: auto; }
.brand-fallback { font-family: var(--f-head); font-weight: 800; font-size: 17px; letter-spacing: -.03em; }
.brand-sep { width: 1px; height: 22px; background: var(--line2); }
.brand-app {
  font-family: var(--f-head); font-weight: 700; font-size: 13px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent-fg);
}

/*
 * Logo pair. The top bar carries BOTH Irada marks — navy for light surfaces,
 * white for dark — and shows the one that reads on the current mode. The
 * sign-in card's single white mark sits on its navy band (see .auth-brand).
 */
.brand-logo--dark { display: none; }
[data-theme="dark"] .brand .brand-logo--light { display: none; }
[data-theme="dark"] .brand .brand-logo--dark { display: block; }

.period-pick {
  display: flex; align-items: center; gap: 8px;
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 6px 4px 14px;
}
.period-pick .lbl { margin: 0; font-size: 10.5px; }
.period-pick input {
  background: none; border: none; color: var(--paper);
  font-family: var(--f-head); font-weight: 600; font-size: 13px; width: 132px;
}
/* The ring goes on the PILL, not the bare input: an outline on the input would
   cut across the rounded chrome it sits inside. Drawn once — the input's own
   outline is suppressed so the two do not stack. */
.period-pick:focus-within { border-color: var(--accent-line); box-shadow: 0 0 0 3px var(--accent-soft); }
.period-pick input:focus, .period-pick input:focus-visible { outline: none; }
.period-pick input::-webkit-calendar-picker-indicator { filter: var(--pick-invert); cursor: pointer; }

.userchip {
  display: flex; align-items: center; gap: 9px;
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 14px 4px 5px;
  transition: border-color .16s var(--ease), background .16s var(--ease);
}
.userchip:hover { border-color: var(--line2); background: var(--panel3); }
/* Stack the name over the role, so it reads as "Ali Hilli" / "Admin" instead of
   the cramped inline "Ali HilliADMIN". */
.userchip-id { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.12; min-width: 0; }
.userchip-name { font-family: var(--f-head); font-weight: 600; font-size: 13px; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A quiet caption, not a lime shout — it's a label, not a badge. */
.userchip-role { font-size: 9.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .1em; font-weight: 600; margin-top: 1px; }

.layout { display: flex; align-items: stretch; min-height: calc(100vh - var(--topbar-h)); }

/*
 * The rail SCROLLS ITSELF. It is sticky under a sticky topbar, so its height is
 * bounded by what is left of the viewport; without an overflow of its own a full
 * nav (19 rows for a superadmin with every feature on) simply grew past the
 * bottom of the window and the whole PAGE had to be scrolled to reach Settings —
 * which pushed the topbar away at the same time. Now the page never moves for
 * the nav's sake.
 *
 * The scrollbar is thin and only paints on hover/focus: a permanent gutter on a
 * 212px rail reads as a second border.
 */
.sidenav {
  width: var(--nav-w); flex: none;
  border-right: 1px solid var(--line);
  padding: 12px 9px 16px;
  position: sticky; top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
  display: flex; flex-direction: column; gap: 2px;
  background: var(--nav-bg);
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
}
.sidenav:hover, .sidenav:focus-within { scrollbar-color: var(--scroll) transparent; }
.sidenav::-webkit-scrollbar { width: 6px; }
.sidenav::-webkit-scrollbar-thumb { background: transparent; }
.sidenav:hover::-webkit-scrollbar-thumb, .sidenav:focus-within::-webkit-scrollbar-thumb { background: var(--scroll); }

/* Denser than a content row on purpose: 19 of these have to read as GROUPS, and
   the eye only sees groups when the rows inside one are tighter than the space
   between them (2px between rows, 14px + a rule between groups). */
.nav-item {
  display: flex; align-items: center; gap: 10px;
  /* flex:none is load-bearing: without it the rows would compress to fit the
     bounded rail instead of overflowing it, and the scroll above would never
     engage. */
  flex: none;
  width: 100%; padding: 8px 11px; border-radius: var(--r-sm);
  font-family: var(--f-head); font-weight: 600; font-size: 13.5px;
  color: var(--mut); text-align: left;
  position: relative; overflow: hidden;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.nav-item .ico { width: 18px; text-align: center; font-size: 14px; flex: none; }
/* The label truncates rather than clipping mid-glyph — the rail is fixed-width
   and a long destination name must not push the count off the row. */
.nav-item > span:not(.ico):not(.nav-count):not(.nav-badge) {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nav-item:hover { background: var(--panel2); color: var(--paper); }
.nav-item.active { background: var(--accent-soft); color: var(--accent-fg); }
.nav-item.active::before {
  content: ''; position: absolute; left: 0; top: 7px; bottom: 7px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--accent-line);
}
.nav-count {
  margin-left: auto; flex: none;
  background: var(--panel3); color: var(--mut);
  border-radius: 999px; padding: 1px 8px; font-size: 11px; font-weight: 700;
}
.nav-item.active .nav-count { background: var(--accent-soft2); color: var(--accent-fg); }
/* The spacer may collapse when the rail scrolls, but never below a gap that
   keeps the foot off the last row. */
.nav-spacer { flex: 1 1 auto; min-height: 14px; }
.nav-foot { flex: none; padding: 12px 11px 0; margin-top: 4px; border-top: 1px solid var(--line);
            font-size: 11px; color: var(--faint); line-height: 1.5; }

.view { flex: 1; min-width: 0; padding: 26px 26px 90px; }

/*
 * The 90px bottom inset is load-bearing everywhere EXCEPT the kanban: it keeps
 * the last row clear of the floating .bulkbar in List view, and below 720px it
 * clears .sidenav, which is a fixed bottom bar at that width.
 *
 * The board is the one view tall enough that a reader routinely scrolls to its
 * very bottom — to reach the horizontal scrollbar, which sits at the foot of a
 * column that can now run past the viewport — and lands on 90px of nothing
 * under the hint. The board has no bulkbar, so it does not need the clearance.
 *
 * Scoped to >=721px so the mobile bottom-nav clearance is untouched, and :has()
 * is used ALONE: an engine without it drops this rule and keeps today's padding
 * rather than discarding a whole selector list.
 */
@media (min-width: 721px) {
  .view:has(.board) { padding-bottom: 28px; }
}
/*
 * Nothing in a view may be wider than the view. `overflow-x: hidden` on the body
 * is the production backstop, but it HIDES an overflow instead of preventing it
 * — the amputated part is simply unreachable. Capping the children means the
 * page cannot be pushed sideways in the first place. Wide content still scrolls
 * inside its own container; the deliberate edge-to-edge bleeds opt out.
 */
.view > * { min-width: 0; max-width: 100%; }

/*
 * FEATURE-OFF RESILIENCE. Every one of these containers can end up with nothing
 * in it once a feature flag removes its contents, and each carries a border, a
 * padding or a background that would otherwise paint an empty box. Collapsing
 * them here is the guarantee; app.js gating the markup is the mechanism. Note
 * that gap collapses on its own, which is why the layouts above use gap rather
 * than margins on children.
 */
.toolbar:empty, .list-toolbar:empty, .cc-toolbar:empty, .cal-toolbar:empty,
.fin-filters:empty, .fin-head:empty, .fin-cards:empty, .fin-tabs:empty,
.viewtabs:empty, .stat-grid:empty, .settings-grid:empty, .status-picker:empty,
.meta-grid:empty, .panel-head:empty, .panel-body:empty, .drawer-foot:empty,
.modal-foot:empty, .chg-list:empty, .lineitems:empty { display: none; }

.view-head {
  display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
  margin-bottom: 22px;
}
/* A header that carries only a back button, with the real page title in the
   .view-head directly below it. Without this it keeps the full 22px title gap
   and pushes that title down out of line with every other page. */
.view-head--bare { margin-bottom: 10px; }

.view-head h1 { font-size: 26px; line-height: 1.15; font-weight: 800; }
.view-head .sub { color: var(--mut); font-size: 13.5px; margin-top: 3px; }
.view-head .grow { flex: 1; }

/* -------------------------------------------------------------- 4. auth */

.boot {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 18px;
}
.boot-mark { display: flex; gap: 6px; }
.boot-mark span {
  width: 10px; height: 26px; border-radius: 3px; background: var(--accent-line);
  animation: bounceBar 1s var(--ease) infinite;
}
.boot-mark span:nth-child(2) { animation-delay: .12s; opacity: .75; }
.boot-mark span:nth-child(3) { animation-delay: .24s; opacity: .5; }
.boot-text { color: var(--faint); font-size: 13px; letter-spacing: .04em; }

.auth {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; padding: 24px;
  position: relative; overflow: hidden;
}
.auth-glow {
  position: absolute; width: 620px; height: 620px; border-radius: 50%;
  background: radial-gradient(circle, var(--auth-glow), transparent 66%);
  top: -180px; right: -160px; pointer-events: none;
  animation: drift 16s ease-in-out infinite alternate;
}
.auth-card {
  position: relative; z-index: 1;
  width: 100%; max-width: 420px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: 34px 30px 30px;
  box-shadow: var(--sh-lg);
  animation: riseIn .5s var(--ease) both;
}
.auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 26px; }
.auth-brand .brand-logo { height: 26px; }
.auth-pane h1 { font-size: 23px; margin-bottom: 6px; font-weight: 800; }
.auth-sub { color: var(--mut); font-size: 13.5px; margin-bottom: 22px; line-height: 1.55; }
.auth-pane label { margin-top: 15px; }
.auth-pane label:first-of-type { margin-top: 0; }
.auth-error { color: var(--danger); font-size: 13px; min-height: 20px; margin: 12px 0 4px; font-weight: 500; }
.auth-pane .btn-block + .btn-block { margin-top: 10px; }
.auth-foot { color: var(--faint); font-size: 12px; text-align: center; margin-top: 16px; }
.auth-legal { position: relative; z-index: 1; color: var(--faint); font-size: 11.5px; margin-top: 22px; letter-spacing: .05em; }

/* --------------------------------------------------------- 5. dashboard */

.stat-grid {
  display: grid; gap: 14px; margin-bottom: 26px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.stat {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 18px;
  position: relative; overflow: hidden; text-align: left; width: 100%;
  box-shadow: var(--sh-card);
  transition: transform .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.stat::after {
  content: ''; position: absolute; inset: auto -30% -70% auto;
  width: 150px; height: 150px; border-radius: 50%;
  background: radial-gradient(circle, var(--accent-soft), transparent 70%);
  opacity: 0; transition: opacity .3s var(--ease);
}
.stat:hover { transform: translateY(-3px); border-color: var(--line2); box-shadow: var(--sh); }
.stat:hover::after { opacity: 1; }
/* A tile that is a FIGURE, not a button (Profitability's headline row). The lift
   and the accent bloom are an affordance — a tile that does nothing when pressed
   must not advertise one. */
.stat.static { cursor: default; }
.stat.static:hover { transform: none; border-color: var(--line); box-shadow: var(--sh-card); }
.stat.static:hover::after { opacity: 0; }
.stat-label {
  font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--mut); display: flex; align-items: center; gap: 7px;
}
.stat-num {
  font-family: var(--f-head); font-size: 38px; font-weight: 800;
  line-height: 1.1; margin-top: 8px; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.stat-foot { font-size: 12px; color: var(--faint); margin-top: 4px; }
.stat.alert .stat-num { color: var(--danger); }
.stat.warn .stat-num { color: var(--warn); }
.stat.accent .stat-num { color: var(--accent-fg); }

.statusbar { display: flex; height: 10px; border-radius: 999px; overflow: hidden; background: var(--panel3); }
.statusbar span { transition: flex-grow .8s var(--ease); }
.status-legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 14px; }
.status-legend .lg-item { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--mut); }
.status-legend b { color: var(--paper); font-family: var(--f-head); }

.dash-cols { display: grid; grid-template-columns: 1.55fr 1fr; gap: 18px; align-items: start; }

/*
 * View-level vertical rhythm. .panel declares no margin (it is used inside
 * .stack, .dash-cols and grids that supply their own gap), so two siblings
 * dropped straight into a view touched at 0px — the Dashboard's "Work in flight"
 * panel against .dash-cols, and Settings' agency grid against the Features
 * panel. The rhythm belongs to the VIEW, not to the wrapper, so it is declared
 * once here on adjacency: no trailing margin, and nothing to keep in sync when a
 * feature flag removes a block.
 */
#view > .panel + .panel,
#view > .panel + .dash-cols,
#view > .dash-cols + .panel,
#view > .settings-grid + .panel,
#view > .panel + .settings-grid,
#view > .stack + .panel,
#view > .panel + .stack { margin-top: 18px; }
/* A block whose whole contents were flag-gated away collapses completely — no
   border, no padding, no rhythm of its own. */
#view > .panel:empty, #view > .stack:empty,
#view > .dash-cols:empty, #view > .settings-grid:empty { display: none; }

/* --- by-stage bar (stats.overview -> by_stage) ------------------------- */
.stage-bar {
  display: flex; width: 100%; height: 14px; border-radius: 999px;
  overflow: hidden; background: var(--panel3);
  box-shadow: inset 0 0 0 1px var(--line);
}
.stage-seg {
  min-width: 0; flex-grow: 0; flex-basis: 0;
  position: relative; cursor: default;
  transition: flex-grow .9s var(--ease), filter .16s var(--ease), opacity .16s var(--ease);
}
.stage-seg + .stage-seg { box-shadow: inset 1px 0 0 var(--panel); }
.stage-bar:hover .stage-seg { opacity: .55; }
.stage-bar .stage-seg:hover { opacity: 1; filter: brightness(1.12); }
.stage-seg[data-status="backlog"]       { background: var(--st-backlog); }
.stage-seg[data-status="in_progress"]   { background: var(--st-progress); }
.stage-seg[data-status="review"]        { background: var(--st-review); }
.stage-seg[data-status="client_review"] { background: var(--st-client); }
.stage-seg[data-status="revisions"]     { background: var(--st-revisions); }
.stage-seg[data-status="done"]          { background: var(--st-done); }
.stage-seg[data-status="on_hold"]       { background: var(--st-hold); }
.stage-seg[data-status="canceled"]      { background: var(--st-cancel); }

/* --- who-row / who-av: a member's name line and their avatar ----------- */
/* .who-av is a size modifier on .avatar; .who-row is the name + client chip
   line used inside .active-row. */
.who-av { width: 30px; height: 30px; font-size: 11.5px; }
.who-row { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; }

/* --- client-row: per-client progress line on the dashboard ------------- */
.client-rows { display: flex; flex-direction: column; gap: 2px; }
.client-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%; text-align: left;
  padding: 11px 10px; border-radius: 12px;
  border: 1px solid transparent;
  transition: background .15s var(--ease), border-color .15s var(--ease), transform .15s var(--ease);
}
.client-row:hover { background: var(--panel2); border-color: var(--line); transform: translateX(2px); }
/* .cr-mark carries its client's accent as an inline background. Its ink is
   re-derived from the inherited --accent (set inline on .client-row) rather
   than trusting the inline colour — same reason as --accent-ink above. */
.cr-mark {
  width: 34px; height: 34px; border-radius: 10px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--f-head); font-weight: 700; font-size: 12.5px;
  color: #0B0B0C;
  color: oklch(from var(--accent) clamp(0, (.62 - l) * 100, 1) min(c, .04) h) !important;
}
.cr-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.cr-top { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.cr-name {
  font-family: var(--f-head); font-weight: 600; font-size: 13.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cr-nums { margin-left: auto; font-size: 11.5px; color: var(--faint); white-space: nowrap; }
.cr-nums b { font-family: var(--f-head); color: var(--paper); font-size: 13px; }
.cr-side { flex: none; text-align: right; }
.cr-pct {
  display: block; font-family: var(--f-head); font-weight: 700; font-size: 14.5px;
  color: var(--accent-fg); font-variant-numeric: tabular-nums;
}
.cr-open { display: block; font-size: 11px; color: var(--faint); white-space: nowrap; }

.client-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); }
.cprog {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 16px; text-align: left; width: 100%; position: relative; overflow: hidden;
  box-shadow: var(--sh-card);
  transition: transform .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.cprog::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent);
  /* client stripe — see the note in the accent block */
  background: var(--accent);
  opacity: .9;
}
.cprog:hover { transform: translateY(-3px); border-color: var(--line2); box-shadow: var(--sh); }
.cprog-top { display: flex; align-items: center; gap: 9px; margin-bottom: 4px; }
.cprog-name { font-family: var(--f-head); font-weight: 600; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cprog-pct { margin-left: auto; font-family: var(--f-head); font-weight: 700; font-size: 15px; color: var(--accent-fg); }
.cprog-meta { font-size: 12px; color: var(--faint); margin-bottom: 11px; }

.duelist { display: flex; flex-direction: column; }
.duerow {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 11px 4px; border-bottom: 1px solid var(--line);
  transition: background .15s var(--ease), padding-left .15s var(--ease);
}
.duerow:last-child { border-bottom: none; }
.duerow:hover { background: var(--panel2); padding-left: 9px; }
.duerow-title { flex: 1; min-width: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.duerow-sub { font-size: 11.5px; color: var(--faint); }
.due-tag { font-family: var(--f-head); font-size: 11.5px; font-weight: 600; padding: 2px 9px; border-radius: 999px; flex: none; }
.due-tag.over { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
.due-tag.today { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.due-tag.soon { background: var(--panel3); color: var(--mut); }

/* ------------------------------------------------------------- 6. board */

.toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 11px 13px; margin-bottom: 18px;
  box-shadow: var(--sh-card);
}
.toolbar .sel, .toolbar .inp { width: auto; min-width: 132px; padding: 8px 12px; font-size: 13.5px; }
.toolbar .sel { padding-right: 30px; }
.search-wrap { position: relative; flex: 1; min-width: 180px; }
.search-wrap .inp { width: 100%; padding-left: 34px; }
.search-wrap .ico { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--faint); font-size: 13px; pointer-events: none; }
.toggle-mine {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  transition: all .16s var(--ease);
}
.toggle-mine:hover { border-color: var(--line2); color: var(--paper); }
.toggle-mine.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-fg); }

.board {
  display: flex; gap: 14px; align-items: flex-start;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 14px; scroll-snap-type: x proximity;
}
.bcol {
  flex: 0 0 286px; max-width: 286px;
  background: var(--col-bg); border: 1px solid var(--line);
  border-radius: var(--r); scroll-snap-align: start;
  display: flex; flex-direction: column;
  /*
   * A column shows at least 10 cards before it grows its own scrollbar.
   * Card height is content-driven, NOT viewport-driven, so the floor has to be
   * a pixel value. Measured on the live board: a card is 115px and the flex gap
   * is 9px, so ten of them plus the 44px head and 20px of padding need
   * 44 + 20 + 10x115 + 9x9 + 2 = 1297. 1320 leaves room for a slightly taller
   * card without ever fitting an eleventh ((1320-66+9)/124 = 10.18).
   * On a tall screen the viewport term wins and the column grows past 10.
   * The consequence is deliberate: a full column is taller than the viewport,
   * so the PAGE scrolls to reach the rest of it instead of the old cramped
   * ~5-card inner box.
   */
  max-height: max(calc(100vh - var(--topbar-h) - 190px), 1320px);
  /*
   * Now that a column CAN be taller than the viewport, the Dashboard's stage
   * legend — which calls scrollIntoView({block:'nearest'}) on a column — aligns
   * the column's top edge with the top of the page instead of doing nothing,
   * parking the header under the 62px sticky topbar. Same fix .guide-sec uses.
   */
  scroll-margin-top: calc(var(--topbar-h) + 16px);
  transition: border-color .18s var(--ease), background .18s var(--ease), box-shadow .18s var(--ease);
}
.bcol-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 13px; border-bottom: 1px solid var(--line);
  font-family: var(--f-head); font-size: 12.5px; font-weight: 700;
  letter-spacing: .02em; position: sticky; top: 0;
  background: var(--panel); border-radius: var(--r) var(--r) 0 0; z-index: 2;
}
.bcol-head .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.bcol-head .count {
  margin-left: auto; background: var(--panel3); color: var(--mut);
  border-radius: 999px; padding: 1px 8px; font-size: 11px; font-weight: 700;
}
.bcol-cards { padding: 10px; display: flex; flex-direction: column; gap: 9px; overflow-y: auto; }
.bcol-empty { color: var(--faint); font-size: 12.5px; text-align: center; padding: 20px 8px; line-height: 1.6; }

.tcard {
  /* .bcol-cards is a column flex box that scrolls. Without this, a column
     holding more cards than fit would shrink every card instead of scrolling,
     and each card's `overflow: hidden` would slice its own title — the same
     failure the guide index had. A scrolling list must never compress its rows. */
  flex: none;
  /* A keyboard move (Alt+arrow) re-focuses the card, and the browser's focus
     scroll would otherwise slide it up under the sticky topbar — reachable now
     that a column can run past the bottom of the window. */
  scroll-margin-top: calc(var(--topbar-h) + 12px);
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px; width: 100%; text-align: left;
  position: relative; overflow: hidden;
  box-shadow: var(--sh-card);
  transition: transform .16s var(--ease), border-color .16s var(--ease),
              box-shadow .16s var(--ease), opacity .16s var(--ease);
}
.tcard::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent);
  /* client stripe — see the note in the accent block */
  background: var(--accent);
  opacity: .9;
}
.tcard:hover { transform: translateY(-2px); border-color: var(--line2); box-shadow: var(--sh); }
.tcard.urgent { border-color: color-mix(in srgb, var(--danger) 32%, transparent); }
.tcard-top { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; }
.tcard-title {
  font-size: 13.5px; font-weight: 500; line-height: 1.42;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  margin-bottom: 10px; padding-left: 4px;
}
.tcard-foot {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding-left: 4px; font-size: 11.5px; color: var(--faint);
}
.tcard-foot .grow { flex: 1; }
.tcard-count { display: inline-flex; align-items: center; gap: 3px; }
.tcard-hold {
  margin: 0 0 9px 4px; padding: 7px 9px; border-radius: 8px;
  background: color-mix(in srgb, var(--st-hold) 12%, transparent);
  border-left: 2px solid var(--st-hold);
  font-size: 11.5px; color: var(--mut); line-height: 1.45;
}
.tcard-due { font-family: var(--f-head); font-weight: 600; }
.tcard-due.over { color: var(--danger); }
.tcard-due.today { color: var(--warn); }

/* --- drag and drop ----------------------------------------------------- */
/*
 * Pointer drag is an ENHANCEMENT. The drawer's .status-picker is the
 * always-available fallback, and .tcard.kb-active is the picked-up state for
 * a keyboard move (grab with Space/Enter, arrow to a column, drop).
 */
.tcard[draggable="true"] { cursor: grab; }
.tcard[draggable="true"]:active { cursor: grabbing; }

.tcard.dragging {
  opacity: .45;
  transform: scale(.98) rotate(-1deg);
  border-color: var(--accent-line);
  box-shadow: 0 12px 30px -10px var(--accent-glow);
  cursor: grabbing;
}
/* Hook for a keyboard "picked up" state. app.js does not emit it yet — the
   drawer's .status-picker is today's no-mouse route — but the style is here
   so adding the interaction needs no CSS change. */
.tcard.kb-active {
  outline: 2px solid var(--accent-fg); outline-offset: 2px;
  box-shadow: 0 10px 26px -12px var(--accent-glow);
  transform: translateY(-2px);
}

/* calendar: chips drag between days to move a deadline */
.cal-chip[draggable="true"] { cursor: grab; }
.cal-chip.dragging { opacity: .45; cursor: grabbing; }
.cal-day.drag-over {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 0 0 0 2px var(--accent-line);
}
.bcol.drag-over, .col.drag-over {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-soft2);
}
.bcol.drag-over .bcol-head, .col.drag-over .bcol-head { border-bottom-color: var(--accent-line); }

/* The insertion marker between two cards. .bcol-cards is a 9px-gap flex
   column, so the negative margin cancels the gap the marker would add and
   the cards do not jump as it moves. */
.drop-line {
  position: relative; height: 3px; flex: none;
  border-radius: 999px; background: var(--accent-line);
  margin: -6px 0; animation: dropIn .14s var(--ease) both;
}
.drop-line::before {
  content: ''; position: absolute; left: -3px; top: 50%;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent-line); transform: translateY(-50%);
}

/* ------------------------------------------------------------ 7. drawer */

.drawer {
  margin-left: auto; width: min(720px, 100%); height: 100%;
  background: var(--panel); border-left: 1px solid var(--line);
  display: flex; flex-direction: column;
  animation: slideIn .26s var(--ease) both;
  box-shadow: var(--sh-lg);
}
.drawer-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 18px 20px 15px; border-bottom: 1px solid var(--line);
}
.drawer-head .grow { flex: 1; min-width: 0; }
.drawer-head h2 { font-size: 18px; line-height: 1.32; word-break: break-word; }
.drawer-eyebrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 7px; }
.drawer-id { font-family: var(--f-head); font-size: 11.5px; color: var(--faint); font-weight: 600; }
/* "Auto-saves" hint that flips to "✓ Saved" for a moment after any change,
   so silent autosaves and attachment uploads feel acknowledged. */
.drawer-saved {
  align-self: center; flex: none; margin-right: 2px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 600;
  letter-spacing: .04em; color: var(--faint);
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 9px;
  white-space: nowrap; transition: color .2s var(--ease), border-color .2s var(--ease), background .2s var(--ease);
}
.drawer-saved.show {
  color: var(--accent-ink); background: var(--accent); border-color: var(--accent);
  animation: savedPop .3s var(--ease);
}
@keyframes savedPop { 0% { transform: scale(.85); } 60% { transform: scale(1.06); } 100% { transform: scale(1); } }
@media (max-width: 560px) { .drawer-saved { display: none; } }
.drawer-body { flex: 1; overflow-y: auto; padding: 20px; }
/* Clearer rhythm between the drawer's sections (Status / Time / Brief /
   Attachments / Activity) so it reads as organised groups, not one long list. */
.drawer-body > .sec-title { margin-top: 24px; }
.drawer-body > .sec-title:first-child { margin-top: 0; }

/* The four switchable sections (labels, watchers, checklist, dependencies) are
   wrapped in a .drawer-sec so a turned-off feature takes its whole block with
   it. That wrapper makes their .sec-title a GRANDCHILD of .drawer-body, which
   the direct-child rule above no longer reaches — so restate the rhythm here,
   or those headings sit flush against the block before them. */
.drawer-body > .drawer-sec > .sec-title { margin-top: 24px; }
.drawer-body > .drawer-sec:first-child > .sec-title { margin-top: 0; }
/* A section whose contents were all gated away must leave no gap behind. */
.drawer-sec:empty { display: none; }
.drawer-foot { border-top: 1px solid var(--line); padding: 13px 20px; background: var(--panel2); }

/* ---- task on the left, activity on the right ---------------------------
 *
 * ONE COLUMN (narrow): .drawer-cols is the scroller. The task, then the
 * thread, scroll together as they always did, and the composer sticks to the
 * bottom of the drawer.
 *
 * TWO COLUMNS (>= 1100px): the drawer widens and .drawer-cols becomes a grid.
 * Each pane scrolls on its own — reading a long brief no longer moves the
 * conversation, and the composer sits under the thread it belongs to.
 */
.drawer-cols { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
.drawer-cols > .drawer-body { flex: none; overflow: visible; }
/* Narrow: the pane dissolves, so its two children sit directly in the scroller.
   That is what lets the composer stick to the bottom of the drawer at all
   times — inside its own box it could only stick once you had scrolled down
   to the thread. */
.drawer-side { display: contents; }
.drawer-side > .act-log { padding: 18px 20px 6px; border-top: 1px solid var(--line); }
.drawer-side > .drawer-foot { position: sticky; bottom: 0; z-index: 3; margin-top: auto; }

/* The composer's toolbar appears when you are writing, not before. Twenty
   buttons above an empty box is clutter in a pane this narrow; the moment the
   box (or any tool) has focus they are all there. */
.drawer-side .composer .txa-tools { display: none; }
.drawer-side .composer:focus-within .txa-tools { display: flex; }

@media (min-width: 1100px) {
  .drawer { width: min(1240px, 94vw); }
  .drawer-cols {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(400px, 36%);
    overflow: hidden;
  }
  .drawer-cols > .drawer-body { overflow-y: auto; min-height: 0; }
  .drawer-side {
    display: flex; flex-direction: column;
    border-left: 1px solid var(--line);
    min-height: 0; background: var(--panel2);
  }
  .drawer-side > .act-log { flex: 1; min-height: 0; overflow-y: auto; border-top: 0; }
  .drawer-side > .drawer-foot { position: static; background: var(--panel); }
  /* It has a pane of its own here — nothing to collapse out of the way of. */
  .drawer-side .act-log-sum { pointer-events: none; }
  .drawer-side .act-log-caret { display: none; }
}

.status-picker { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 20px; }
.status-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-size: 12.5px; font-weight: 600;
  transition: all .16s var(--ease);
}
.status-opt .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.status-opt:hover { border-color: currentColor; transform: translateY(-1px); }
.status-opt.on { color: var(--on-status); font-weight: 700; }

/* Read-only drawer: a member viewing a task they neither created nor own.
   The controls stay visible (so the state is legible) but are inert. */
.drawer--readonly .status-opt:disabled,
.drawer--readonly [data-patch]:disabled {
  opacity: .55; cursor: default;
}
.drawer--readonly .status-opt:disabled:hover { border-color: var(--line); transform: none; }
.drawer--readonly .drawer-body::before {
  content: "View only — you're not the owner or assignee of this task.";
  display: block; margin-bottom: 14px; padding: 9px 13px; border-radius: 10px;
  background: var(--panel2); border: 1px solid var(--line);
  color: var(--mut); font-size: 12.5px;
}

.meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(178px, 1fr)); gap: 13px; margin-bottom: 20px; }
.meta-cell { background: var(--panel2); border: 1px solid var(--line); border-radius: 12px; padding: 11px 13px; }
.meta-cell .lbl { margin-bottom: 7px; }
.meta-cell .sel, .meta-cell .inp {
  background: transparent; border: none; padding: 0; font-family: var(--f-head);
  font-weight: 600; font-size: 13.5px; background-position: right 2px center;
  /* the control may never be wider than its cell — a native select truncates
     with no ellipsis, so it must be the TRACK that decides the width */
  min-width: 0; max-width: 100%;
}
/*
 * A focus ring, not the absence of one. The cell strips the border (so the
 * :focus border-color is a no-op) and used to strip the box-shadow too, which
 * left every control in the drawer's meta grid with no visible focus at all —
 * the global outline having already been suppressed on .inp/.sel/.txa.
 */
.meta-cell .sel:focus, .meta-cell .inp:focus {
  box-shadow: 0 0 0 2px var(--accent-soft2);
  background-color: var(--focus-bg);
  border-radius: 6px;
}
.meta-cell .sel { padding-right: 20px; }
.meta-static { font-family: var(--f-head); font-weight: 600; font-size: 13.5px; }
.meta-cell input[type=date]::-webkit-calendar-picker-indicator { filter: var(--pick-invert); cursor: pointer; }

.brief-box {
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; white-space: pre-wrap; word-break: break-word;
  font-size: 13.8px; line-height: 1.65; color: var(--paper); margin-bottom: 20px;
}
.brief-box.empty { color: var(--faint); font-style: italic; }

/* attachments */
.dropzone {
  display: flex; align-items: center; gap: 12px;
  border: 1.5px dashed var(--line2); border-radius: 12px;
  padding: 13px 15px; text-align: left; color: var(--faint); font-size: 12.5px;
  transition: all .18s var(--ease); margin-top: 10px;
}
.dropzone-ico {
  width: 38px; height: 38px; border-radius: 10px; flex: none; font-size: 17px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); transition: all .18s var(--ease);
}
.dropzone-txt { display: flex; flex-direction: column; gap: 1px; line-height: 1.4; }
.dropzone-sub { font-size: 11px; color: var(--faint); }
.dropzone.over {
  border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-fg);
  transform: translateY(-1px); box-shadow: 0 0 0 3px var(--accent-soft);
}
.dropzone b { color: var(--paper); font-family: var(--f-head); }
.dropzone.over b { color: var(--accent-fg); }
.dropzone.over .dropzone-ico { background: var(--accent); transform: scale(1.06); }

.filelist { display: flex; flex-direction: column; gap: 8px; }
.filerow {
  display: flex; align-items: center; gap: 11px;
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 10px; padding: 9px 11px;
  transition: border-color .16s var(--ease), background .16s var(--ease);
}
.filerow:hover { border-color: var(--line2); background: var(--panel3); }
/* Final-result files read as the deliverables: an accent-tinted rail. */
.filelist-final .filerow { border-left: 3px solid var(--accent); }
/* The ★ mark/unmark toggle. Dim by default, gold when this file is a final. */
.file-star { flex: none; color: var(--faint); font-size: 15px; line-height: 1; }
.file-star:hover { color: var(--accent-fg); }
.file-star.on { color: #f2b705; }
span.file-star { padding: 0 6px; cursor: default; }
.file-ico {
  width: 32px; height: 32px; border-radius: 8px; flex: none;
  background: var(--panel3); display: flex; align-items: center; justify-content: center;
  font-size: 14px;
}
/* Stack the name over its meta so a very long filename truncates cleanly
   instead of shoving the "176 KB · Ali · now" line off to the side. */
.file-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.file-name { display: block; font-size: 13px; font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-sub { font-size: 11.5px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.filerow .btn-icon { flex: none; }
/* image attachments preview as a thumbnail */
.file-thumb {
  width: 46px; height: 46px; border-radius: 8px; flex: none; overflow: hidden; display: block;
  background: var(--panel3); border: 1px solid var(--line);
}
.file-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .25s var(--ease); }
.file-thumb:hover img { transform: scale(1.09); }

/* comments */
.thread { display: flex; flex-direction: column; gap: 14px; }
.cmt { display: flex; gap: 11px; animation: fadeUp .3s var(--ease) both; }

/*
 * Replies, one level deep.
 *
 * Indented to sit under the parent's TEXT rather than its avatar — 26px of
 * avatar plus the 11px gap — so the rail lines up with where the words start.
 * A single hairline carries the nesting; a second tier of boxes would put back
 * exactly what the rest of this change takes away.
 */
.cmt-kids {
  display: flex; flex-direction: column; gap: 14px;
  margin: 14px 0 0 37px; padding-left: 15px;
  border-left: 1px solid var(--line);
}
.cmt-kids .avatar { width: 22px; height: 22px; font-size: 9.5px; }
.cmt-kids .cmt { gap: 9px; }

/* Reply is quiet until you go looking for it, like Edit and Delete — it is an
   action on a comment, not information about it. Always visible on touch,
   where there is no hover; see the coarse-pointer block in section 22. */
.cmt-reply {
  margin-top: 6px; padding: 0; border: 0; background: none;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
  color: var(--faint); cursor: pointer;
  opacity: 0; transition: opacity .14s var(--ease), color .14s var(--ease);
}
.cmt:hover > .cmt-body > .cmt-reply,
.cmt:focus-within > .cmt-body > .cmt-reply { opacity: 1; }
.cmt-reply:hover { color: var(--accent-fg); }

/* What you are answering, while you type it. The comment may be far up the
   thread by the time the composer has focus. */
.cmt.is-answering > .cmt-body > .cmt-text {
  box-shadow: -2px 0 0 0 var(--accent-fg);
  border-radius: 3px 10px 10px 3px;
}
.cmt.is-answering > .cmt-body > .cmt-reply { opacity: 1; color: var(--accent-fg); }

/* The "replying to X" bar above the composer. */
.reply-to {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 7px; padding: 5px 6px 5px 10px;
  border-radius: 999px;
  background: var(--accent-soft); border: 1px solid var(--accent-soft2);
  font-size: 12px; color: var(--mut);
}
.reply-to-mark { color: var(--accent-fg); font-size: 12px; }
.reply-to-txt b { color: var(--paper); font-weight: 650; }
.reply-to .btn-icon { margin-left: auto; width: 22px; height: 22px; font-size: 11px; }
.cmt-body { flex: 1; min-width: 0; }
.cmt-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; flex-wrap: wrap; }
.cmt-author { font-family: var(--f-head); font-weight: 600; font-size: 13px; }
.cmt-when { font-size: 11px; color: var(--faint); }
/*
 * A plain team comment is TEXT, not a box.
 *
 * Every comment used to be a filled, bordered bubble, which made a thread a
 * column of identical boxes — and, worse, made the two washes that carry real
 * meaning (a client's own words, and a comment shared with them) just two more
 * boxes among many. Stripping the chrome off the default is what lets those
 * read as exceptional, which is the whole point of them.
 *
 * The border stays as `transparent` rather than being removed: the washed
 * variants below only set border-COLOR, and the geometry has to be identical
 * either way or comments would jump by a pixel as they are shared.
 */
.cmt-text {
  background: transparent; border: 1px solid transparent; border-radius: 10px;
  padding: 1px 0; font-size: 13.5px; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
}
/* The ones that ARE boxes get the padding back, so the wash has something to
   wash. Kept next to the base rule rather than beside each variant — one place
   to look when a comment is the wrong shape. */
.cmt.client .cmt-text,
.cmt.shared .cmt-text,
.cmt:has(.cmt-shared-tag) .cmt-text { padding: 9px 12px; }
/* rich comment content (safe block HTML from mdToHtml) */
.cmt-text.rich { white-space: normal; }
.rich > :first-child { margin-top: 0; }
.rich > :last-child { margin-bottom: 0; }
.rich p { margin: 0 0 8px; }
.rich strong { font-weight: 700; color: var(--paper); }
.rich em { font-style: italic; }
.rich code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em;
  background: var(--panel3); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
}
/* Logical, not left: a list whose text is Arabic is laid out right-to-left
   (dir="auto" from the renderer) and its bullets belong on the right. */
.rich ul, .rich ol { margin: 4px 0 8px; padding-inline-start: 22px; }
.rich li { margin: 2px 0; }
.rich ul li::marker { color: var(--accent-fg); }
.rich ol li::marker { color: var(--accent-fg); font-weight: 700; }
.rich a { color: var(--accent-fg); text-decoration: underline; text-underline-offset: 2px; word-break: break-word; }
.rich a:hover { text-decoration-thickness: 2px; }
/* @mentions rendered by app.js inside a comment body */
.cmt-text .mention, .rich .mention {
  color: var(--accent-fg); background: var(--accent-soft);
  border-radius: 5px; padding: 0 4px; font-weight: 600;
}
.cmt.client .cmt-text {
  border-color: color-mix(in srgb, var(--st-client) 35%, transparent);
  background: color-mix(in srgb, var(--st-client) 8%, transparent);
}
.cmt.client .cmt-author::after {
  content: 'CLIENT'; margin-left: 7px; font-size: 9.5px; letter-spacing: .1em;
  background: color-mix(in srgb, var(--st-client) 20%, transparent);
  color: var(--warn); padding: 1px 6px; border-radius: 999px;
}
.cmt.sys { gap: 9px; align-items: center; }
.cmt.sys .cmt-text {
  background: none; border: none; padding: 0;
  color: var(--faint); font-size: 12.5px; font-style: italic;
}
.cmt-sysdot {
  width: 26px; flex: none; display: flex; align-items: center; justify-content: center;
  color: var(--faint); font-size: 12px;
}
/* composer — formatting toolbar over the input row */
.composer { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.composer-tools { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.md-btn {
  min-width: 30px; height: 30px; padding: 0 8px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-size: 13px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color .14s var(--ease), border-color .14s var(--ease), background .14s var(--ease), transform .14s var(--ease);
}
.md-btn:hover { color: var(--paper); border-color: var(--line2); background: var(--panel3); transform: translateY(-1px); }
.md-btn:active { transform: translateY(0); }
.md-btn.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
.md-btn i { font-style: italic; }
.md-btn b { font-weight: 800; }
.md-btn.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.md-preview { margin-left: auto; padding: 0 12px; }
.md-sep { width: 1px; height: 18px; background: var(--line); margin: 0 3px; }
/* The composer now holds the same editor the brief does: its toolbar, the
   editable surface and the hidden Source textarea are one block, with Send
   beside them. Wrapping so the toolbar keeps its own line on a narrow drawer
   rather than squeezing the writing area down to nothing. */
.composer-row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.composer-row .txa-tools { flex: 1 0 100%; margin-bottom: 0; }
.composer-row .rich-ed,
.composer-row .txa { flex: 1 1 220px; min-width: 0; }
.composer-row .txa { min-height: 46px; max-height: 240px; padding: 11px 13px; }
.rich-ed--cmt { min-height: 46px; max-height: 240px; overflow-y: auto; }
.composer-send { flex: none; }

/* ----------------------------------------------------------- 8. clients */

.ccard {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px; text-align: left; width: 100%; position: relative; overflow: hidden;
  box-shadow: var(--sh-card);
  transition: transform .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.ccard::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: var(--accent);
  /* client stripe — see the note in the accent block */
  background: var(--accent);
}
.ccard:hover { transform: translateY(-4px); border-color: var(--line2); box-shadow: var(--sh); }

/*
 * Client stripes in light mode. Written here, not as a :root token, because
 * var(--accent) has to resolve against the CLIENT accent app.js sets inline on
 * each card — a root-level token would resolve against the member's theme.
 */
[data-theme="light"] .tcard::before,
[data-theme="light"] .cprog::before,
[data-theme="light"] .ccard::before {
  background: oklch(from var(--accent) min(l, .70) c h);
}
.ccard-top { display: flex; align-items: center; gap: 10px; margin-bottom: 3px; }
.ccard h3 { font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccard-ind { font-size: 12.5px; color: var(--faint); margin-bottom: 14px; }
.ccard-stats { display: flex; align-items: baseline; gap: 6px; margin-bottom: 8px; font-size: 12.5px; color: var(--mut); }
.ccard-stats b { font-family: var(--f-head); font-size: 17px; color: var(--paper); }

.cdetail-head {
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  padding-bottom: 20px; margin-bottom: 20px; border-bottom: 1px solid var(--line);
}
.cdetail-mark {
  width: 52px; height: 52px; border-radius: 14px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--f-head); font-weight: 800; font-size: 20px;
  /* The tile is filled with the client's own accent, which an admin can set to
     anything — hard-coding near-black ink gives black-on-black for a dark
     accent. Re-derive readable ink from the fill, exactly like .cr-mark. */
  color: oklch(from var(--accent, #7C3AED) clamp(0, calc((0.62 - l) * 1000), 1) 0 0);
}
@supports not (color: oklch(from red l c h)) {
  /* Older engines: app.js writes --accent-ink alongside the fill. */
  .cdetail-mark { color: var(--accent-ink, #0b0b0c); }
}
.cdetail-cols { display: grid; grid-template-columns: 1.5fr 1fr; gap: 18px; align-items: start; }
.stack { display: flex; flex-direction: column; gap: 18px; }
/* A flex item's automatic minimum is its CONTENT's min-width, so one unbreakable
   string (an email, a long client name) inside a panel could push the whole
   column past the viewport. This is what made the Account view — and only the
   Account view — scroll the body sideways at 375px. */
.stack > * { min-width: 0; }
.stack:empty { display: none; }

.prog-row { margin-bottom: 16px; }
.prog-row:last-child { margin-bottom: 0; }
.prog-top { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.prog-name { font-family: var(--f-head); font-weight: 600; font-size: 13.5px; }
.prog-nums { margin-left: auto; font-size: 12.5px; color: var(--mut); }
.prog-nums b { font-family: var(--f-head); color: var(--paper); font-size: 14px; }
.prog-open { font-size: 11.5px; color: var(--faint); margin-top: 5px; }

/* deliverable editor rows — phase 2 adds auto-generate + title template */
.drow { display: grid; grid-template-columns: 1fr 84px 106px 36px; gap: 8px; align-items: center; margin-bottom: 8px; }
.drow .inp { padding: 8px 11px; font-size: 13.5px; }
.drow-head { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
.drow-extra {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: -2px 0 12px; padding: 9px 11px; border-radius: 10px;
  background: var(--panel2); border: 1px solid var(--line);
}
.drow-extra .inp { flex: 1 1 200px; }
.drow-extra .hint { margin: 0; flex-basis: 100%; }

/* file library */
.fgroup { margin-bottom: 18px; }
.fgroup:last-child { margin-bottom: 0; }
.fgroup-title {
  display: flex; align-items: center; gap: 8px; margin-bottom: 9px;
  font-family: var(--f-head); font-size: 12px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--mut);
}
.fgroup-title .count { color: var(--faint); font-weight: 500; letter-spacing: 0; text-transform: none; }

/* portal panel */
.portal-panel { border-color: var(--accent-soft2); }
.portal-panel .panel-head { border-color: var(--accent-soft); }
.linkbox {
  display: flex; align-items: center; gap: 8px;
  background: var(--well); border: 1px solid var(--line);
  border-radius: 10px; padding: 4px 4px 4px 13px;
}
.linkbox code {
  flex: 1; min-width: 0; font-family: var(--f-mono);
  font-size: 12px; color: var(--accent-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.slugbox { display: flex; align-items: center; gap: 8px; }
.slugbox .prefix {
  font-family: var(--f-mono); font-size: 12px; color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.slugbox .inp { flex: 1 1 120px; font-family: var(--f-mono); font-size: 12.5px; }
.warnbox {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
  border-radius: 10px; padding: 11px 13px; font-size: 12.5px;
  color: var(--warnbox-ink); line-height: 1.55;
}
.pin-callout {
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: 12px; padding: 16px; text-align: center;
}
.pin-callout .code {
  font-family: var(--f-head); font-size: 34px; font-weight: 800; color: var(--accent-fg);
  letter-spacing: .22em; margin: 8px 0 12px; user-select: all;
}

/* -------------------------------------------------- 9. members + account */

.table-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; min-width: 620px; }
table.tbl th {
  text-align: left; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600; padding: 0 14px 10px; white-space: nowrap;
}
table.tbl td { padding: 12px 14px; border-top: 1px solid var(--line); font-size: 13.5px; vertical-align: middle; }
table.tbl tbody tr { transition: background .14s var(--ease); }
table.tbl tbody tr:hover { background: var(--panel2); }
table.tbl td.right, table.tbl th.right { text-align: right; }
/* The identity line wraps rather than widening its panel: an email address is one
   unbreakable token, and on a 375px screen it was the thing setting the Account
   view's min-content width. */
.who { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.who > span { min-width: 0; }
.who-name { font-family: var(--f-head); font-weight: 600; }
.who-sub { font-size: 11.5px; color: var(--faint); overflow-wrap: anywhere; }
.role-tag {
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  padding: 2px 9px; border-radius: 999px; background: var(--panel3); color: var(--mut);
}
.role-tag.admin { background: var(--accent-soft); color: var(--accent-fg); }
.inactive-row { opacity: .5; }

/* --------------------------------------------------------- 10. overlays */

.overlays { position: relative; z-index: 90; }
.overlay {
  position: fixed; inset: 0; z-index: 90;
  background: var(--scrim);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  display: flex; animation: fadeIn .18s var(--ease);
}
.overlay.center { align-items: center; justify-content: center; padding: 20px; }

/* image lightbox */
.lightbox {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; height: 100%; position: relative; animation: lbIn .2s var(--ease);
}
@keyframes lbIn { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
.lb-stage {
  margin: 0; display: flex; flex-direction: column; align-items: center; gap: 12px;
  max-width: min(1100px, 92vw); max-height: 88vh;
}
.lb-img {
  max-width: 100%; max-height: 78vh; object-fit: contain;
  border-radius: 12px; background: #0b0b0c;
  box-shadow: 0 24px 70px rgba(0,0,0,.55); border: 1px solid var(--line2);
}
.lb-cap {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: center;
  max-width: 100%; color: var(--paper); font-size: 13px;
}
.lb-name { font-family: var(--f-head); font-weight: 600; word-break: break-all; }
.lb-meta { display: inline-flex; align-items: center; gap: 14px; color: var(--mut); font-size: 12.5px; }
.lb-open { color: var(--accent-fg); font-weight: 600; text-decoration: none; }
.lb-open:hover { text-decoration: underline; }
.lb-nav {
  flex: none; width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid var(--line2); background: color-mix(in srgb, var(--panel) 82%, transparent);
  color: var(--paper); font-size: 26px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s var(--ease), transform .15s var(--ease), border-color .15s var(--ease);
}
.lb-nav:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); transform: scale(1.06); }
.lb-close {
  position: absolute; top: 4px; right: 6px; width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--line2); background: color-mix(in srgb, var(--panel) 82%, transparent);
  color: var(--paper); font-size: 17px; cursor: pointer; z-index: 2;
  transition: background .15s var(--ease), transform .15s var(--ease);
}
.lb-close:hover { background: var(--danger, #fb7185); color: #fff; transform: scale(1.06); }
@media (max-width: 560px) {
  .lb-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 40px; height: 40px; font-size: 22px; }
  .lb-prev { left: 6px; } .lb-next { right: 6px; }
  .lb-img { max-height: 70vh; }
}

.modal {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-xl); width: 100%; max-width: 520px;
  max-height: calc(100vh - 40px); display: flex; flex-direction: column;
  box-shadow: var(--sh-lg); animation: popIn .2s var(--ease) both;
}
.modal.wide { max-width: 680px; }
.modal-head { display: flex; align-items: center; gap: 12px; padding: 20px 24px 0; }
.modal-head h2 { font-size: 18px; flex: 1; }
.modal-body { padding: 18px 24px 22px; overflow-y: auto; }
.modal-foot {
  display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;
  padding: 0 24px 22px;
}
.modal-foot .grow { flex: 1; }

.toasts {
  position: fixed; z-index: 120; bottom: 22px; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 9px; align-items: center;
  pointer-events: none; width: min(420px, calc(100vw - 24px));
}
.toast {
  background: var(--toast-bg);
  color: var(--paper);
  border: 1px solid var(--line2); border-left: 3px solid var(--accent-line);
  border-radius: 12px; padding: 11px 16px; font-size: 13.5px;
  box-shadow: var(--sh-lg); animation: toastIn .26s var(--ease) both;
  max-width: 100%; word-break: break-word;
}
.toast.err { border-left-color: var(--danger); }
.toast.out { animation: toastOut .2s var(--ease) both; }

/*
 * ONE empty state, six names. Every view that can be empty says so with the same
 * object: a dashed, roomy, un-alarming card. They had drifted apart on padding,
 * icon size, radius and heading size — and My Work's version dropped the
 * paragraph's bottom margin, which jammed its "Go to the board" button straight
 * against the copy. Declared once here; the per-view rules that follow only add
 * what is genuinely local (an overdue tint, a bare variant inside a card).
 */
.empty, .mw-empty, .focus-empty, .guide-empty, .chg-empty, .fin-empty, .tt-empty, .ci-empty {
  text-align: center; padding: 48px 20px; color: var(--faint);
  border: 1px dashed var(--line2); border-radius: var(--r-lg); background: var(--col-bg);
  line-height: 1.6;
}
.empty-ico { font-size: 32px; margin-bottom: 12px; opacity: .8; }
.empty h3, .mw-empty h3, .focus-empty h3, .guide-empty h3, .chg-empty h3, .fin-empty h3, .tt-empty h3, .ci-empty h3 {
  font-family: var(--f-head); font-size: 15.5px; color: var(--paper); margin-bottom: 6px;
}
.empty p, .mw-empty p, .focus-empty p, .guide-empty p, .chg-empty p, .fin-empty p, .tt-empty p, .ci-empty p {
  font-size: 13px; max-width: 340px; margin: 0 auto 16px; line-height: 1.6;
}
/* The trailing margin is for a following action; on its own the paragraph is the
   last thing in the card and does not need it. */
.empty p:last-child, .mw-empty p:last-child, .focus-empty p:last-child,
.guide-empty p:last-child, .chg-empty p:last-child, .fin-empty p:last-child, .tt-empty p:last-child,
.ci-empty p:last-child { margin-bottom: 0; }

.skel {
  background: linear-gradient(100deg, var(--panel2) 30%, var(--panel3) 50%, var(--panel2) 70%);
  background-size: 220% 100%; animation: shimmer 1.3s linear infinite;
  border-radius: 10px;
}
.skel-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.skel-stat { height: 106px; }
.skel-line { height: 13px; margin-bottom: 9px; }

.spinner {
  width: 22px; height: 22px; border-radius: 50%; margin: 30px auto;
  border: 2px solid var(--line2); border-top-color: var(--accent-line);
  animation: spin .7s linear infinite;
}

/* ------------------------------------------------------- 11. theme menu */

/* Both header dropdowns hang off a .menu-host span (position:relative). */
.menu-host { position: relative; display: inline-flex; }

.theme-menu, .notif-menu {
  position: absolute; top: calc(100% + 9px); right: 0; z-index: 60;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--sh-lg);
  animation: popIn .16s var(--ease) both;
}

/* mountMenu() pins these to the viewport instead — the topbar has to become a
 * horizontal scroller on narrow screens, and a scroll container clips its
 * absolutely-positioned descendants. `overflow-x: auto` forces the other axis
 * to compute to auto as well, so there is no CSS-only way out; a fixed element
 * escapes the clip. JS owns left/top/bottom/max-height. */
.theme-menu.menu-pinned, .notif-menu.menu-pinned {
  position: fixed; right: auto; top: auto; z-index: 90;
  overflow-y: auto; overscroll-behavior: contain;
}

.theme-menu { width: 250px; padding: 14px; }
.theme-menu .lbl { margin-bottom: 9px; }
.theme-menu .theme-swatches + .lbl { margin-top: 17px; }

.theme-swatches { display: flex; flex-wrap: wrap; gap: 9px; }
/*
 * The swatch fill arrives inline. app.js also writes a tick glyph and an ink
 * colour for it, but that ink comes from the same over-high threshold noted on
 * --accent-ink, so on violet and rose it would be white-on-mid at 2.7:1.
 * Selection is shown structurally instead — an inner ring in panel colour plus
 * an outer border — which reads on any fill and needs no contrast maths. The
 * glyph is hidden rather than removed so the button keeps its accessible name.
 */
.theme-swatch {
  width: 32px; height: 32px; border-radius: 50%; flex: none; padding: 0;
  border: 2px solid var(--line2);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0; line-height: 0; color: transparent !important;
  transition: transform .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.theme-swatch:hover { transform: scale(1.12); border-color: var(--paper); }
.theme-swatch:focus-visible { outline: 2px solid var(--paper); outline-offset: 3px; }
.theme-swatch.on {
  border-color: var(--paper); transform: scale(1.06);
  box-shadow: inset 0 0 0 3px var(--panel);
}

/* .theme-mode also carries .btn.btn-ghost.btn-sm, so this only adds the
   selected state on top of the button primitive. */
.theme-modes { display: flex; gap: 6px; flex-wrap: wrap; }
.theme-mode { flex: 1 1 auto; justify-content: center; }
.theme-mode.on {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-ink); font-weight: 700;
}
.theme-mode.on:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }

/* Typefaces are a vertical list, not a row: each option is set in its own face
   (app.js applies the stack inline) so the name IS the preview, and that only
   reads if the rows are full width and left-aligned.
   The selected state is a soft accent wash rather than the solid fill the modes
   use — a solid accent behind a font sample would fight the very thing being
   previewed. The left rail is the same selected-marker used by .nav-item.active,
   so selection is never carried by hue alone. */
.theme-fonts .theme-font {
  justify-content: flex-start; text-align: left; width: 100%;
  padding-top: 7px; padding-bottom: 7px;
}
/* --accent-line, not raw --accent: a light accent (lime, amber) sits near
   1.1:1 on white, so the raw token would leave the border and the rail
   invisible in light mode — exactly the "never by hue alone" guarantee this
   rail exists to provide. --accent-line is the token that clamps for strokes. */
.theme-font.on {
  background: var(--accent-soft); border-color: var(--accent-line);
  color: var(--accent-fg); font-weight: 700;
  box-shadow: inset 3px 0 0 var(--accent-line);
}
.theme-font.on:hover { background: var(--accent-soft2); border-color: var(--accent); }

/* ---------------------------------------------------- 12. notifications */

.bell {
  position: relative;
  width: 36px; height: 36px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; color: var(--mut);
  border: 1px solid transparent;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.bell:hover, .bell[data-menu-open] { background: var(--panel3); color: var(--paper); border-color: var(--line); }
/*
 * The unread COUNT, not a dot.
 *
 * This was styled as a bare 9px circle — no font-size, no padding, no
 * centring — while the JS wrote "1", "12" or "99+" into it. The glyph
 * inherited the topbar's ~14px type inside a 9px box, so a single digit was
 * clipped and two were worse. It is a badge, so it is sized like one: the type
 * comes first and the box grows to fit it, with min-width keeping a lone digit
 * circular rather than an oval.
 */
.bell-dot {
  position: absolute; top: 1px; right: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box;
  min-width: 17px; height: 17px; padding: 0 5px;
  border-radius: 999px;
  font-family: var(--f-head);
  font-size: 10px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  color: var(--accent-ink); background: var(--accent);
  box-shadow: 0 0 0 2px var(--panel);
  animation: pulse 2.4s infinite;
}

.notif-menu {
  width: min(374px, calc(100vw - 26px)); max-height: 442px;
  display: flex; flex-direction: column; overflow: hidden;
}
.notif-head { padding: 12px 14px; border-bottom: 1px solid var(--line); flex: none; }
.notif-head .lbl { margin: 0; }
/* Column flex + a max-height, so its rows must not be shrinkable — see .tcard
   and .guide-idx-item for the same trap. A long notification list scrolls. */
.notif-menu > [data-list] { overflow-y: auto; padding: 6px; }
.notif-menu > [data-list] > .notif { flex: none; }

.notif {
  position: relative;
  display: flex; align-items: flex-start; gap: 10px;
  width: 100%; text-align: left;
  padding: 10px 11px; border-radius: 10px;
  border: 1px solid transparent;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.notif:hover { background: var(--panel2); border-color: var(--line); }
.notif + .notif { margin-top: 2px; }
.notif-kind {
  width: 30px; height: 30px; border-radius: 9px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; background: var(--panel3); color: var(--mut);
}
.notif-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.notif-title {
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  color: var(--mut); line-height: 1.4;
}
.notif-body {
  font-size: 12px; color: var(--faint); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.notif-when { font-size: 11px; color: var(--faint); }

/* Unread is carried by weight and a rail, not by colour alone. */
.notif.unread { background: var(--accent-soft); padding-left: 15px; }
.notif.unread .notif-title { color: var(--paper); font-weight: 700; }
.notif.unread .notif-kind { background: var(--accent); color: var(--accent-ink); }
/* the unread wash lifts the surface, so the quiet text has to lift with it */
.notif.unread .notif-body, .notif.unread .notif-when { color: var(--mut); }
.notif.unread::before {
  content: ''; position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 24px; border-radius: 999px; background: var(--accent-line);
}

/* Per-kind tint on the icon tile (OPS_NOTIFY_KINDS). */
.notif-kind[data-kind="assigned"] { color: var(--st-progress-ink); }
.notif-kind[data-kind="mention"]  { color: var(--st-review-ink); }
.notif-kind[data-kind="status"]   { color: var(--accent-fg); }
.notif-kind[data-kind="client"]   { color: var(--st-client-ink); }
.notif-kind[data-kind="due"]      { color: var(--danger); }
.notif.unread .notif-kind[data-kind] { color: var(--accent-ink); }

.notif-empty {
  text-align: center; padding: 38px 22px; color: var(--faint);
  font-size: 12.5px; line-height: 1.6;
}

/* ---------------------------------------------------------- 13. timers */

.timer-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-width: 30px; padding: 6px 11px; border-radius: 999px;
  border: 1px solid var(--line2); background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-size: 12px; font-weight: 600; line-height: 1;
  transition: all .16s var(--ease);
}
.timer-btn[data-face="full"] { padding: 9px 16px; font-size: 13px; }
.timer-btn:hover { color: var(--paper); border-color: var(--line2); background: var(--panel3); transform: translateY(-1px); }
.timer-btn.running {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  font-weight: 700; box-shadow: 0 0 0 4px var(--accent-soft);
}
.timer-btn.running:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }

/* Topbar live readout: .timer-open (pulse + task + clock) plus a stop button. */
.timer-live {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--f-head); font-weight: 700; font-size: 13px;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
  color: var(--accent-fg);
}
.timer-open {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  padding: 5px 12px 5px 10px; border-radius: 999px;
  border: 1px solid var(--accent-soft2); background: var(--accent-soft);
  color: inherit; font: inherit;
  transition: border-color .16s var(--ease), background .16s var(--ease);
}
.timer-open:hover { border-color: var(--accent-line); background: var(--accent-soft2); }
.timer-pulse {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent-line); animation: pulse 1.8s infinite;
}
.timer-task {
  max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--mut); font-weight: 500; font-size: 12.5px;
}
.timer-clock { flex: none; }
/* Used bare in the drawer ("Someone is tracking this now") — no pulse child,
   so it gets its own dot. */
.timer-live:not(:has(.timer-pulse)) { font-size: 12.5px; font-weight: 600; }
.timer-live:not(:has(.timer-pulse))::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent-line); animation: pulse 1.8s infinite;
}

.time-total {
  display: inline-flex; align-items: baseline; gap: 6px;
  font-family: var(--f-head); font-weight: 600; font-size: 12.5px;
  color: var(--mut);
}
.time-total b, .time-total-num {
  font-family: var(--f-head); font-weight: 700; font-size: 15px;
  font-variant-numeric: tabular-nums; color: var(--paper);
}

/* time_entries list in the drawer */
.time-list { display: flex; flex-direction: column; }
.time-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 2px; border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.time-row:last-child { border-bottom: none; }
.time-row .grow { flex: 1; min-width: 0; }
.time-row .when { color: var(--faint); font-size: 11.5px; }
.time-row .dur { font-family: var(--f-head); font-weight: 700; font-variant-numeric: tabular-nums; }
.time-row.live .dur { color: var(--accent-fg); }

/* "Who is working right now" (stats.overview -> active_now) */
.active-now { display: flex; flex-direction: column; gap: 6px; }
.active-row {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 10px 11px; border-radius: 11px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft2);
  transition: transform .15s var(--ease), box-shadow .15s var(--ease);
}
.active-row:hover { transform: translateX(2px); box-shadow: var(--sh-sm); }
.active-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.active-task {
  font-size: 13px; font-weight: 500; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.active-clock, .active-clock-num {
  font-family: var(--f-head); font-weight: 700; font-size: 13.5px;
  font-variant-numeric: tabular-nums; color: var(--accent-fg); flex: none;
}
/* .who-row here is the name + client chip line inside an active row. */
.active-row .who-row {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 0; border: none; background: none;
}
.active-row .who-row:hover { background: none; padding: 0; }
.active-row .who-name { font-size: 12.5px; }

/* -------------------------------------------------------- 14. calendar */

/* Client + assignee filter bar above the month grid. Selects sit in a row and
   size to content (base .sel is width:100%, which would stack them full-width). */
.cal-toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.cal-toolbar .sel { width: auto; min-width: 150px; padding: 8px 30px 8px 12px; font-size: 13.5px; }
.cal-toolbar .btn-sm { flex: none; }

.cal { border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.cal-nav .btn { padding: 6px 12px; }

/* Header row and day grid are both 7-column grids sharing one hairline. */
.cal-head, .cal-grid {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px; background: var(--line);
}
.cal-head { border-bottom: 1px solid var(--line); }
.cal-dow {
  background: var(--panel); padding: 9px 6px; text-align: center;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--mut);
}
.cal-day {
  background: var(--panel); min-height: 112px; padding: 7px;
  display: flex; flex-direction: column; gap: 5px;
  transition: background .15s var(--ease);
}
.cal-day:hover { background: var(--panel2); }
.cal-daynum { display: flex; align-items: center; gap: 5px; }
.cal-daynum > span:first-child {
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700; color: var(--mut);
  min-width: 21px; height: 21px; line-height: 21px; text-align: center;
  border-radius: 50%; flex: none;
}
.cal-count {
  margin-left: auto; font-size: 10px; font-weight: 700; font-family: var(--f-head);
  color: var(--faint); background: var(--panel3);
  border-radius: 999px; padding: 0 6px; line-height: 16px;
}
.cal-day.other { background: var(--panel2); }
.cal-day.other .cal-daynum, .cal-day.other .cal-items { opacity: .45; }
.cal-day.today { background: var(--accent-soft); }
.cal-day.today .cal-daynum > span:first-child { background: var(--accent); color: var(--accent-ink); }

.cal-items { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cal-chip {
  display: flex; align-items: center; gap: 5px;
  width: 100%; text-align: left; min-width: 0;
  padding: 3px 7px; border-radius: 6px;
  background: var(--panel2); border-left: 3px solid var(--accent);
  font-size: 11px; line-height: 1.35; color: var(--paper);
  transition: background .14s var(--ease), transform .14s var(--ease);
}
.cal-chip:hover { background: var(--panel3); transform: translateX(1px); }
.cal-chip-dot { width: 5px; height: 5px; border-radius: 50%; flex: none; }
.cal-chip-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cal-chip.urgent { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 45%, transparent); }
.cal-chip.done { opacity: .55; }
.cal-chip.done .cal-chip-txt { text-decoration: line-through; }
/* A button, not a label: it opens the whole day. */
.cal-more {
  display: block; width: 100%; font-size: 10.5px; font-weight: 600; color: var(--mut);
  font-family: var(--f-head); padding: 2px 4px; text-align: left;
  background: transparent; border: 0; border-radius: 6px; cursor: pointer;
}
.cal-more:hover { background: var(--panel3); color: var(--paper); }

/* The day panel: one row per task. */
.cal-daylist { display: flex; flex-direction: column; gap: 6px; }
.cal-dayrow {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 12px; border-radius: 10px; font: inherit; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--line); border-left: 3px solid var(--accent);
  color: var(--paper);
}
.cal-dayrow:hover { background: var(--panel3); border-color: var(--line2); border-left-color: var(--accent); }
.cal-dayrow-title { flex: 1 1 auto; min-width: 0; font-size: 13.5px; }
.cal-dayrow-late { font-size: 11px; font-weight: 700; color: var(--danger); text-transform: uppercase; letter-spacing: .04em; }

/* "Picked up where you left off" on the new-task form. */
.draftbar {
  display: flex; align-items: center; gap: 12px; margin: 0 0 14px;
  padding: 9px 12px; border-radius: 10px; font-size: 12.5px;
  background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--paper);
}
.draftbar-txt { flex: 1; }

/* -------------------------------------------------------- 15. workload */
/* The per-member bars live in THE WORKLOAD BOARD (end of file). Only the
   segment itself stays here, because section 16 recolours it by data-status. */
.load-seg { min-width: 0; transition: filter .16s var(--ease); }
.load-seg:hover { filter: brightness(1.15); }

/* --------------------------------------- 16. inline-colour repairs (JS) */
/*
 * app.js writes status, priority and client colours straight into inline style
 * attributes, using one fixed palette for both modes. Inline styles outrank
 * this file, so these rules reclaim them with !important.
 *
 * Two kinds live here:
 *
 *   FILLS (bars, legend dots, the selected chip) only need repairing in light
 *   mode, so those rules are scoped to [data-theme="light"].
 *
 *   TEXT (.pill, .prio, .status-opt labels) is repaired in BOTH modes via the
 *   --st-*-ink tokens, which floor lightness in dark and cap it in light.
 *   Unscoped on purpose: backlog and canceled measured 3.7:1 and 2.5:1 as
 *   label text on a DARK panel, so light mode was never the only problem.
 *
 * Where app.js already emits data-status (.stage-seg, .load-seg, .lg-item,
 * .status-opt) that attribute is the hook. .pill and .prio carry no such
 * attribute yet, so those match on the literal hex in the style string; the
 * data-status selector is listed alongside so the rule keeps working the day
 * the JS side adds it.
 */
[data-theme="light"] .stage-seg[data-status="backlog"],
[data-theme="light"] .load-seg[data-status="backlog"]       { background: var(--st-backlog) !important; }
[data-theme="light"] .stage-seg[data-status="in_progress"],
[data-theme="light"] .load-seg[data-status="in_progress"]   { background: var(--st-progress) !important; }
[data-theme="light"] .stage-seg[data-status="review"],
[data-theme="light"] .load-seg[data-status="review"]        { background: var(--st-review) !important; }
[data-theme="light"] .stage-seg[data-status="client_review"],
[data-theme="light"] .load-seg[data-status="client_review"] { background: var(--st-client) !important; }
[data-theme="light"] .stage-seg[data-status="revisions"],
[data-theme="light"] .load-seg[data-status="revisions"]     { background: var(--st-revisions) !important; }
[data-theme="light"] .stage-seg[data-status="done"],
[data-theme="light"] .load-seg[data-status="done"]          { background: var(--st-done) !important; }
[data-theme="light"] .stage-seg[data-status="on_hold"],
[data-theme="light"] .load-seg[data-status="on_hold"]       { background: var(--st-hold) !important; }
[data-theme="light"] .stage-seg[data-status="canceled"],
[data-theme="light"] .load-seg[data-status="canceled"]      { background: var(--st-cancel) !important; }

/*
 * Legend dots take the vibrant fill (decoration, 3:1 is the bar). The selected
 * status chip is white text ON that fill, so it takes the deeper -ink tone —
 * white on the raw fill measured 4.1:1.
 */
[data-theme="light"] .lg-item[data-status="backlog"] .dot { background: var(--st-backlog) !important; }
[data-theme="light"] .lg-item[data-status="in_progress"] .dot { background: var(--st-progress) !important; }
[data-theme="light"] .lg-item[data-status="review"] .dot { background: var(--st-review) !important; }
[data-theme="light"] .lg-item[data-status="client_review"] .dot { background: var(--st-client) !important; }
[data-theme="light"] .lg-item[data-status="revisions"] .dot { background: var(--st-revisions) !important; }
[data-theme="light"] .lg-item[data-status="done"] .dot { background: var(--st-done) !important; }
[data-theme="light"] .lg-item[data-status="on_hold"] .dot { background: var(--st-hold) !important; }
[data-theme="light"] .lg-item[data-status="canceled"] .dot { background: var(--st-cancel) !important; }
[data-theme="light"] .status-opt[data-status="backlog"].on { background: var(--st-backlog-ink) !important; border-color: var(--st-backlog-ink) !important; }
[data-theme="light"] .status-opt[data-status="in_progress"].on { background: var(--st-progress-ink) !important; border-color: var(--st-progress-ink) !important; }
[data-theme="light"] .status-opt[data-status="review"].on { background: var(--st-review-ink) !important; border-color: var(--st-review-ink) !important; }
[data-theme="light"] .status-opt[data-status="client_review"].on { background: var(--st-client-ink) !important; border-color: var(--st-client-ink) !important; }
[data-theme="light"] .status-opt[data-status="revisions"].on { background: var(--st-revisions-ink) !important; border-color: var(--st-revisions-ink) !important; }
[data-theme="light"] .status-opt[data-status="done"].on { background: var(--st-done-ink) !important; border-color: var(--st-done-ink) !important; }
[data-theme="light"] .status-opt[data-status="on_hold"].on { background: var(--st-hold-ink) !important; border-color: var(--st-hold-ink) !important; }
[data-theme="light"] .status-opt[data-status="canceled"].on { background: var(--st-cancel-ink) !important; border-color: var(--st-cancel-ink) !important; }

/* Unselected chips carry the bright hue as inline TEXT colour. */
.status-opt[data-status="backlog"]:not(.on)       { color: var(--st-backlog-ink) !important; }
.status-opt[data-status="in_progress"]:not(.on)   { color: var(--st-progress-ink) !important; }
.status-opt[data-status="review"]:not(.on)        { color: var(--st-review-ink) !important; }
.status-opt[data-status="client_review"]:not(.on) { color: var(--st-client-ink) !important; }
.status-opt[data-status="revisions"]:not(.on)     { color: var(--st-revisions-ink) !important; }
.status-opt[data-status="done"]:not(.on)          { color: var(--st-done-ink) !important; }
.status-opt[data-status="on_hold"]:not(.on)       { color: var(--st-hold-ink) !important; }
.status-opt[data-status="canceled"]:not(.on)      { color: var(--st-cancel-ink) !important; }
.status-opt:not(.on) .dot { background: currentColor !important; }

.pill[data-status="backlog"],
.pill[style*="#6b7280" i]       { color: var(--st-backlog-ink) !important;   border-color: color-mix(in srgb, var(--st-backlog) 40%, transparent) !important; }
.pill[data-status="in_progress"],
.pill[style*="#38bdf8" i], .pill[style*="#22D3EE" i] { color: var(--st-progress-ink) !important;  border-color: color-mix(in srgb, var(--st-progress) 40%, transparent) !important; }
.pill[data-status="review"],
.pill[style*="#a78bfa" i], .pill[style*="#8B5CF6" i] { color: var(--st-review-ink) !important;    border-color: color-mix(in srgb, var(--st-review) 40%, transparent) !important; }
.pill[data-status="client_review"],
.pill[style*="#fbbf24" i]       { color: var(--st-client-ink) !important;    border-color: color-mix(in srgb, var(--st-client) 40%, transparent) !important; }
.pill[data-status="revisions"],
.pill[style*="#fb7185" i], .pill[style*="#E94B9A" i] { color: var(--st-revisions-ink) !important; border-color: color-mix(in srgb, var(--st-revisions) 40%, transparent) !important; }
.pill[data-status="done"],
.pill[style*="#34D399" i] { color: var(--st-done-ink) !important;      border-color: color-mix(in srgb, var(--st-done) 40%, transparent) !important;
                                                       background: color-mix(in srgb, var(--st-done) 16%, transparent) !important; }
.pill[data-status="on_hold"],
.pill[style*="#94a3b8" i]       { color: var(--st-hold-ink) !important;      border-color: color-mix(in srgb, var(--st-hold) 40%, transparent) !important; }
.pill[data-status="canceled"],
.pill[style*="#4b5563" i]       { color: var(--st-cancel-ink) !important;    border-color: color-mix(in srgb, var(--st-cancel) 40%, transparent) !important; }

.prio[style*="#6b7280" i] { color: var(--st-backlog-ink) !important; }
.prio[style*="#38bdf8" i] { color: var(--st-progress-ink) !important; }
.prio[style*="#fb7185" i] { color: var(--st-revisions-ink) !important; }
.prio .prio-pip { background: currentColor !important; }

/*
 * STANDALONE priority pips — the board card, the dashboard due list and the
 * command palette render the pip on its own, outside any .prio wrapper, so the
 * repair above never reached them and they kept the raw dark-mode hex (Normal
 * measured 2.1:1 on a white panel, under the 3:1 WCAG 1.4.11 asks of a
 * meaningful non-text indicator).
 *
 * The hook is data-prio, mirroring what .stage-seg does with data-status. SHAPE
 * carries it as well as hue, the way .fin-pill's triangle separates income from
 * expense: low is a hollow square, normal the filled diamond, urgent a filled
 * triangle. Greyscale-safe, and the three are told apart without reading a
 * label. app.js emits data-prio="1|2|3"; the inline-hex forms below keep the
 * older markup correct until it does.
 */
/* !important because the older markup carries the hex as an INLINE background,
   which outranks any normal declaration. */
.prio-pip[data-prio="2"], .prio-pip[style*="#38bdf8" i] { background: var(--st-progress-ink) !important; }
.prio-pip[data-prio="3"], .prio-pip[style*="#fb7185" i] { background: var(--st-revisions-ink) !important; }
/* hollow square — low sits back */
.prio-pip[data-prio="1"], .prio-pip[style*="#6b7280" i] {
  background: transparent !important;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1.5px var(--st-backlog-ink);
}
/* filled triangle — urgent leans forward. The 45deg rotation the base rule
   applies is undone first, or the triangle would point sideways. */
.prio-pip[data-prio="3"], .prio-pip[style*="#fb7185" i] {
  border-radius: 0; transform: none;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
/* Inside a labelled .prio the pip follows the label's colour (rule above); the
   hollow-square variant keeps its shape but takes its stroke from the label. */
.prio .prio-pip[data-prio="1"] { box-shadow: inset 0 0 0 1.5px currentColor; }

/*
 * Client accents are arbitrary admin-chosen hexes, so they cannot be
 * enumerated. Instead the element's own inherited colour is darkened in
 * place: inside a `color` declaration `currentColor` resolves to the
 * INHERITED value, which is the inline accent. One rule, any accent.
 */
[data-theme="light"] .chip-client .chip-txt { color: oklch(from currentColor min(l, .48) c h); }
[data-theme="light"] .chip-client .dot      { background: oklch(from currentColor min(l, .62) c h) !important; }
[data-theme="light"] .cal-chip              { border-left-color: oklch(from var(--accent) min(l, .62) c h) !important; }
[data-theme="light"] .cal-chip-dot          { background: oklch(from var(--accent) min(l, .62) c h) !important; }

/* ----------------------------------------------------------- 17. motion */

@keyframes fadeIn   { from { opacity: 0; } }
@keyframes popIn    { from { opacity: 0; transform: scale(.965) translateY(6px); } }
@keyframes slideIn  { from { opacity: .4; transform: translateX(44px); } }
@keyframes riseIn   { from { opacity: 0; transform: translateY(14px); } }
@keyframes fadeUp   { from { opacity: 0; transform: translateY(7px); } }
@keyframes toastIn  { from { opacity: 0; transform: translateY(14px) scale(.97); } }
@keyframes toastOut { to   { opacity: 0; transform: translateY(8px) scale(.97); } }
@keyframes spin     { to   { transform: rotate(360deg); } }
@keyframes shimmer  { to   { background-position: -120% 0; } }
@keyframes dropIn   { from { opacity: 0; transform: scaleX(.7); } }
@keyframes breathe  { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.25); opacity: .72; } }
@keyframes bounceBar {
  0%, 100% { transform: scaleY(.55); }
  50%      { transform: scaleY(1.25); }
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 var(--accent-soft2); }
  70%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@keyframes drift {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(-40px, 40px) scale(1.12); }
}
@keyframes countUp { from { opacity: 0; transform: translateY(6px); } }

/* staggered entrance — set --i on each child */
.enter { animation: fadeUp .38s var(--ease) both; animation-delay: calc(var(--i, 0) * 42ms); }
/* count-up landing state for animated numbers */
.stat-num.ticking { animation: countUp .4s var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
    /* The DELAY has to go too. .enter staggers with
       `animation-delay: calc(var(--i) * 42ms)` and fill-mode `both`, which holds
       the element at the keyframe's opacity: 0 for the whole delay. Zeroing only
       the duration left a reduced-motion user staring at an empty grid while up
       to 588ms of stagger elapsed, then watching rows pop in — the opposite of
       what they asked for. */
    animation-delay: 0s !important;
  }
}

/* ------------------------------------------------------- 18. responsive */

@media (max-width: 1080px) {
  .dash-cols, .cdetail-cols { grid-template-columns: 1fr; }
  .view { padding: 22px 18px 96px; }
  .cal-day { min-height: 94px; }
}

@media (max-width: 720px) {
  body { font-size: 14px; }
  .topbar { padding: 0 13px; gap: 9px; }
  .brand-sep, .brand-app { display: none; }
  /* the period stays reachable everywhere — it just loses its label */
  .period-pick { padding: 3px 4px 3px 8px; }
  .period-pick .lbl { display: none; }
  .period-pick input { width: 108px; font-size: 12px; }
  .userchip { padding: 4px 6px; }
  .userchip-name, .userchip-role { display: none; }
  /* the running clock stays; only the task title it belongs to is dropped */
  .timer-task { display: none; }
  .timer-live { font-size: 12px; gap: 5px; }
  .timer-open { padding: 5px 10px 5px 8px; }

  .layout { display: block; }
  .sidenav {
    position: fixed; z-index: 50; bottom: 0; left: 0; right: 0; top: auto;
    width: auto; height: auto; flex-direction: row; gap: 2px;
    padding: 7px 6px calc(7px + env(safe-area-inset-bottom));
    border-right: none; border-top: 1px solid var(--line);
    background: var(--topbar-bg);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    overflow-y: hidden;
  }
  /*
   * width:100% is for the vertical rail; left in place here it made every item's
   * flex BASE the full width of the bar, and 19 shrinkable items then divided a
   * 363px screen into ~19px slivers — icons clipped, every label invisible, and
   * nothing ever exceeded the container so the overflow-x:auto below never
   * produced a scrollbar. A fixed base with no shrink is what makes the bar
   * scroll instead of crush.
   */
  .nav-item {
    flex-direction: column; gap: 3px; padding: 7px 4px; font-size: 10.5px;
    letter-spacing: .01em; justify-content: center; text-align: center;
    width: auto; flex: 0 0 auto; min-width: 62px;
    scroll-snap-align: start;
  }
  .nav-item > span:not(.ico):not(.nav-count):not(.nav-badge) { max-width: 100%; }
  .nav-item .ico { font-size: 17px; width: auto; }
  .nav-item.active::before { left: 50%; right: auto; transform: translateX(-50%); top: 0; bottom: auto; width: 26px; height: 3px; border-radius: 0 0 3px 3px; }
  .nav-count { display: none; }
  .nav-spacer, .nav-foot { display: none; }

  .view { padding: 18px 14px 100px; }
  .view-head h1 { font-size: 21px; }
  .stat-num { font-size: 31px; }
  .stat-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
  .stat { padding: 14px; }

  .toolbar { padding: 10px; gap: 8px; }
  .toolbar .sel, .toolbar .inp { min-width: 0; flex: 1 1 132px; }
  .search-wrap { flex: 1 1 100%; }


  .board {
    margin: 0 -14px; padding: 0 14px 14px;
    scroll-snap-type: x mandatory;
  }
  /* The three deliberate edge-to-edge bleeds opt out of the .view > * cap — a
     max-width of 100% would over-constrain their negative margins and slide them
     left instead of widening them. */
  .view > .board, .view > .viewtabs, .view > .fin-tabs { max-width: none; }
  .bcol { flex-basis: 82vw; max-width: 82vw; max-height: none; }
  .bcol-cards { max-height: 62vh; }
  /* Touch has no dragstart; the drawer's status picker is the way to move a
     card on a phone, so the grab cursor would be a lie. */
  .tcard[draggable="true"] { cursor: pointer; }

  .drawer { width: 100%; border-left: none; }
  .drawer-head, .drawer-body, .drawer-foot { padding-left: 15px; padding-right: 15px; }
  /* A floor, not a fixed pair: the hard 1fr 1fr held two columns all the way
     down to 320px, narrower than the base auto-fit would ever have produced, and
     a 142px cell truncates "Marker Media Iraq" mid-word. Two columns at 375px,
     one below ~330px. */
  .meta-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 9px; }

  .modal { max-width: 100%; border-radius: var(--r-lg); }
  .modal-head { padding: 18px 18px 0; }
  .modal-body { padding: 15px 18px 18px; }
  .modal-foot { padding: 0 18px 18px; }
  .modal-foot .btn { flex: 1; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .drow { grid-template-columns: 1fr 68px 36px; }
  .drow .drow-unit { grid-column: 1 / 3; }
  .auth-card { padding: 26px 20px 24px; }
  .toasts { bottom: 84px; }

  /* menus become sheets pinned to the viewport edges */
  .theme-menu, .notif-menu {
    position: fixed; top: calc(var(--topbar-h) + 6px);
    left: 10px; right: 10px; width: auto; max-width: none;
  }
  .notif-menu { max-height: 66vh; }

  /* calendar: the 7-column grid stays, everything inside it shrinks */
  .cal-day { min-height: 76px; padding: 4px; gap: 3px; }
  .cal-daynum > span:first-child { min-width: 18px; height: 18px; line-height: 18px; font-size: 10.5px; }
  .cal-count { display: none; }
  .cal-chip { padding: 2px 4px; font-size: 10px; border-left-width: 2px; gap: 3px; }
  .cal-dow { padding: 7px 2px; font-size: 9.5px; letter-spacing: .04em; }

  .active-row { padding: 9px; }
}

/*
 * The topbar is the one row that cannot wrap (it is a fixed 62px), so its escape
 * hatch is to SCROLL: nothing is clipped, nothing is squeezed below its content,
 * and every control stays reachable — the bar just moves under the finger.
 *
 * The hatch used to open only below 560px, which left the whole tablet band
 * (561px to ~900px, iPad portrait included) carrying the full desktop payload —
 * brand + Ops + the 132px period input + ＋Task + four 36px icon buttons + the
 * user chip + sign-out, ~860–1020px of content — with neither wrap nor scroll,
 * so flex shrank the buttons below their labels and the text clipped. 1000px
 * covers the band with room to spare; above it the payload genuinely fits.
 */
@media (max-width: 1000px) {
  .topbar {
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .topbar::-webkit-scrollbar { display: none; }
  .topbar > * { flex-shrink: 0; }
  .topbar .grow { flex: 1 0 0; min-width: 0; }
}

@media (max-width: 420px) {
  .topbar { padding: 0 9px; gap: 6px; }
  .brand-logo { height: 18px; }
  .period-pick { padding: 3px 4px 3px 7px; }
  .period-pick input { width: 92px; font-size: 11.5px; }
  .timer-open { padding: 4px 8px 4px 7px; gap: 6px; }
  /* The 36px base STAYS. These were shrunk to 31px to buy room in a bar that
     could not scroll; now that it does, shrinking the targets on the narrowest
     screens — where fingers have least room, and Sign out sits next to the
     account chip — buys nothing and costs accuracy. */
  .userchip { padding: 3px; }
}

/* Below 560px a calendar cell is too narrow for a title: the chips collapse to
   accent dots. The PAINTED disc is 16px, but the tap target is not — a 16px
   target is far under the 44px minimum, and these sit four to a wrapped row. An
   ::after inset of -12px gives a 40x40 hit area without touching the grid the
   discs are laid out in, and the row gap is opened so adjacent targets do not
   overlap. Tooltip and click handler are unchanged. */
@media (max-width: 560px) {
  .cal-day { min-height: 60px; }
  .cal-items { flex-direction: row; flex-wrap: wrap; gap: 8px; }
  .cal-chip {
    position: relative;
    width: 16px; height: 16px; min-width: 16px; padding: 0;
    border-radius: 50%; border-left: none;
    background: var(--accent); justify-content: center;
  }
  .cal-chip::after { content: ''; position: absolute; inset: -12px; }
  .cal-chip.done { opacity: .4; }
  .cal-chip-txt { display: none; }
  .cal-chip-dot { display: none; }
  .cal-more { flex-basis: 100%; }
}
[data-theme="light"] .cal-chip { background: var(--panel2); }
@media (max-width: 560px) {
  [data-theme="light"] .cal-chip { background: oklch(from var(--accent) min(l, .62) c h); }
}

/* ------------------------------------------------ 19. admin tools (ph. 1) */
/*
 * Reports · Invoices · Announcements · Settings. Admin-only surfaces bolted
 * onto the team app. Everything here reuses the tokens above; nothing invents
 * a colour.
 *
 * LEVELS. The four announcement levels (info/success/warn/urgent) are mapped
 * onto the STATUS ramp rather than to new hues, because that ramp already
 * ships both-mode -ink tokens: floored at L>=.70 in dark, capped at L<=.48 in
 * light, each measured past 4.5:1 as text on its own pale tint. So a level
 * bar is legible in both themes for free, and the raw member --accent is never
 * used as ink or as a thin stroke (the light-mode rule). Each mapping sets two
 * locals — --lv (the vivid hue, only ever a fill/stripe, where 3:1 is the bar)
 * and --lv-ink (the readable text tone) — that the components below consume.
 */
.annbar--info,    .update-row[data-level="info"],
.inv-status[data-status="sent"]    { --lv: var(--st-progress);  --lv-ink: var(--st-progress-ink); }
.annbar--success, .update-row[data-level="success"],
.inv-status[data-status="paid"]    { --lv: var(--st-done);      --lv-ink: var(--st-done-ink); }
.annbar--warn,    .update-row[data-level="warn"]     { --lv: var(--st-client);    --lv-ink: var(--st-client-ink); }
.annbar--urgent,  .update-row[data-level="urgent"]   { --lv: var(--st-revisions); --lv-ink: var(--st-revisions-ink); }

/* --- sidebar groups ----------------------------------------------------- */
/*
 * A labelled section inside the rail. There are two of them (Team's Focus, and
 * the admin Tools) and they do NOT both sit at the bottom — Team's Focus lands
 * in the middle of the loose items, so a group needs a rule on BOTH sides when
 * more loose items follow it. Otherwise the items below read as part of the
 * group above them, which is exactly how 19 rows turn back into one wall.
 *
 * Separation is 14px + a hairline; the rows inside a group are 2px apart. That
 * ratio is the whole grouping cue.
 */
.nav-group {
  display: flex; flex-direction: column; gap: 2px;
  flex: none;                       /* see .nav-item — the rail must overflow */
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
/* A group followed by more loose items closes with its own rule. */
.nav-group:has(+ .nav-item) {
  margin-bottom: 14px; padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
/* Feature-off resilience: a group whose every item was flag-gated away leaves no
   heading, no rule and no gap behind. app.js already avoids emitting one — this
   is the guarantee, not the mechanism. */
.nav-group:not(:has(.nav-item)) { display: none; }

/* A quiet section heading, not another row: smaller, wider-tracked, and aligned
   to the item text rather than to the item box. */
.nav-group-label {
  flex: none;
  padding: 0 11px 6px;
  font-family: var(--f-head); font-size: 10px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
}

/* --- notification bar --------------------------------------------------- */
/*
 * The bar belongs to the CONTENT COLUMN, not to the window. It used to be a
 * full-bleed strip hard against the left edge of the browser while every other
 * element in the app started 238px in (212px rail + 26px view padding) — the one
 * thing on screen that ignored the sidebar, and asymmetric at the right edge
 * too. It now starts and ends exactly where the view's content does.
 *
 * Three mountings are supported so the markup can move without the CSS
 * following: as the first child of a .content column beside the rail, as the
 * first child of the .view itself, or (legacy) as a sibling of .layout, where it
 * is indented to clear the rail rather than sitting under it.
 */
.annbar-wrap { display: flex; flex-direction: column; gap: 10px; }
.annbar-wrap:empty, .annbar-wrap[hidden] { display: none; }

/* The column that holds the bar and the view. Optional — the layout is still a
   plain rail + view when app.js does not emit it. */
.layout > .content-col {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
}
.layout > .content-col > .view { flex: 1; }
.content-col > .annbar-wrap { margin: 26px 26px 0; }
/* When the bar is present the view's own top padding is the gap under it, so it
   is trimmed to the standard 18px rhythm instead of doubling to 26. */
.content-col > .annbar-wrap:not(:empty) + .view { padding-top: 18px; }
/* Mounted inside the view, it is simply the first block of the page. */
.view > .annbar-wrap { margin-bottom: 18px; }
/* Legacy mounting, outside .layout: indent past the rail so it still lines up. */
.layout + .annbar-wrap, .app > .annbar-wrap {
  margin: 26px 26px 0 calc(var(--nav-w) + 26px);
}

/* One banner per active announcement, tinted by level. A soft same-hue wash
   under -ink text — the pattern used by .due-tag and .cmt.client — plus a solid
   left stripe in the vivid hue. A card now, not a strip: it has edges on all
   four sides because it no longer spans the window. */
.annbar {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 11px 14px 11px 15px;
  border: 1px solid color-mix(in srgb, var(--lv, var(--accent)) 24%, var(--line));
  border-left: 3px solid var(--lv, var(--accent-line));
  border-radius: var(--r);
  background: color-mix(in srgb, var(--lv, var(--accent)) 12%, var(--panel));
  color: var(--lv-ink, var(--paper));
  font-size: 13.5px; line-height: 1.5;
  animation: fadeUp .3s var(--ease) both;
}
.annbar-ico { flex: none; font-size: 15px; line-height: 1.4; align-self: flex-start; margin-top: 1px; }
/* A basis rather than 0: below ~340px of bar the text takes its own row and the
   dismiss drops beside it, instead of the three children squashing. */
.annbar-main { flex: 1 1 240px; min-width: 0; }
.annbar b, .annbar strong, .annbar-title {
  font-family: var(--f-head); font-weight: 700; letter-spacing: -.01em;
}
/* The title and the body are both inline and sit next to each other in one
   line of markup, so without this they render glued together — "Quarter close
   Timesheets are due Thursday". */
.annbar-title + .annbar-body { margin-left: 7px; }
.annbar-body { color: inherit; opacity: .9; }

/* Pushed to the bar's right edge, where the ✕ lives — not left trailing off the
   end of the body text wherever that happens to stop. */
.annbar-pin { margin-left: auto; flex: none; opacity: .8; font-size: 13px; }
.annbar a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.annbar-dismiss {
  position: relative;
  flex: none; margin-left: auto; align-self: center;
  width: 26px; height: 26px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: inherit; opacity: .7; font-size: 14px; line-height: 1;
  border: 1px solid transparent; background: transparent;
  transition: opacity .15s var(--ease), background .15s var(--ease), border-color .15s var(--ease);
}
/* The chrome stays 26px (a bigger button would shout); the TARGET is 40x40. */
.annbar-dismiss::after { content: ''; position: absolute; inset: -7px; }
.annbar-dismiss:hover {
  opacity: 1;
  background: color-mix(in srgb, var(--lv, var(--paper)) 16%, transparent);
  border-color: color-mix(in srgb, var(--lv, var(--paper)) 34%, transparent);
}

/* --- updates slide-in panel (📣) --------------------------------------- */
/* Anchored right inside the app's .overlay, mirroring .drawer. The head sticks
   so the list scrolls under it. */
.updates-panel {
  margin-left: auto; width: min(392px, 100%); height: 100%;
  background: var(--panel); border-left: 1px solid var(--line);
  overflow-y: auto;
  box-shadow: var(--sh-lg);
  animation: slideIn .26s var(--ease) both;
}
.updates-head {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; gap: 10px;
  padding: 16px 16px 14px 18px; border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.updates-head h2 { font-size: 16px; flex: 1; }
.updates-empty {
  text-align: center; padding: 46px 24px; color: var(--faint);
  font-size: 12.5px; line-height: 1.6;
}
.update-row {
  position: relative;
  display: flex; flex-direction: column; gap: 5px;
  padding: 13px 16px 14px 17px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--lv, var(--line2));
  transition: background .15s var(--ease);
}
.update-row:hover { background: var(--panel2); }
.update-row > strong, .update-title {
  font-family: var(--f-head); font-weight: 700; font-size: 13.5px;
  color: var(--paper); line-height: 1.4;
}
.update-body {
  font-size: 12.5px; color: var(--mut); line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
.update-meta { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-top: 1px; }
.update-when { font-size: 11px; color: var(--faint); }
.update-level {
  font-family: var(--f-head); font-size: 9.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--lv-ink, var(--mut));
  background: color-mix(in srgb, var(--lv, var(--panel3)) 16%, transparent);
  border-radius: 999px; padding: 1px 8px;
}

/* --- invoices list ----------------------------------------------------- */
.inv-table { width: 100%; border-collapse: collapse; min-width: 640px; }
.inv-table th {
  text-align: left; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600; padding: 0 14px 10px; white-space: nowrap;
}
.inv-table td { padding: 12px 14px; border-top: 1px solid var(--line); font-size: 13.5px; vertical-align: middle; }
.inv-table tbody tr { transition: background .14s var(--ease); }
.inv-table tbody tr:hover { background: var(--panel2); }
.inv-table td.right, .inv-table th.right { text-align: right; }
.inv-table .inv-num { font-family: var(--f-head); font-weight: 700; font-variant-numeric: tabular-nums; }

/* status pill — draft/sent/paid/void. sent + paid pick up --lv from the
   mapping block; draft and void carry neutral tones inline. */
.inv-status {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 11px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  letter-spacing: .02em; text-transform: capitalize; white-space: nowrap;
  border: 1px solid var(--line2); color: var(--mut); background: var(--panel2);
}
.inv-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.inv-status[data-status="draft"] { color: var(--mut); background: var(--panel3); border-color: var(--line2); }
.inv-status[data-status="sent"],
.inv-status[data-status="paid"] {
  color: var(--lv-ink);
  background: color-mix(in srgb, var(--lv) 15%, transparent);
  border-color: color-mix(in srgb, var(--lv) 42%, transparent);
}
.inv-status[data-status="void"] {
  color: var(--st-cancel-ink);
  background: color-mix(in srgb, var(--st-cancel) 12%, transparent);
  border-color: color-mix(in srgb, var(--st-cancel) 36%, transparent);
  text-decoration: line-through; text-decoration-thickness: 1px;
}

/* --- invoice editor + line items --------------------------------------- */
.inv-editor { display: flex; flex-direction: column; gap: 16px; }

.lineitems { display: flex; flex-direction: column; gap: 8px; }
.lineitems-head {
  display: grid; grid-template-columns: 1fr 68px 116px 116px 36px; gap: 8px;
  padding: 2px 2px 0;
}
.lineitems-head span {
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600;
}
.lineitems-head span:nth-child(n+2) { text-align: right; }
.lineitem {
  display: grid; grid-template-columns: 1fr 68px 116px 116px 36px;
  gap: 8px; align-items: center;
}
/* Without min-width:0 the 1fr track's automatic minimum is the description
   input's intrinsic width (~180px), which put ~548px of hard minimum into a
   modal that is only ~512px wide on a 600px screen. */
.lineitem .inp, .lineitem .sel { min-width: 0; }
.lineitem .inp { padding: 8px 11px; font-size: 13.5px; }
.lineitem .inp[type="number"], .lineitem .qty, .lineitem .unit { text-align: right; }
/* the computed amount cell sits read-only at the right of the row */
.lineitem .money { padding-right: 2px; color: var(--paper); }
.lineitem .btn-icon { flex: none; }

/* live totals — right-aligned ledger under the items */
.inv-totals {
  margin-left: auto; width: min(288px, 100%);
  display: flex; flex-direction: column; gap: 7px;
  padding-top: 4px;
}
.inv-totals-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 18px;
  font-size: 13px; color: var(--mut);
}
.inv-totals-row .money { color: var(--paper); }
.inv-totals-row.grand {
  margin-top: 5px; padding-top: 10px; border-top: 1px solid var(--line);
  font-family: var(--f-head); font-size: 15px; color: var(--paper);
}
.inv-totals-row.grand .money { font-size: 19px; color: var(--accent-fg); }

/* monetary values everywhere in the tools UI */
.money {
  font-family: var(--f-head); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right;
}
.money.lg { font-size: 18px; }

/* --- settings ---------------------------------------------------------- */
.settings-grid {
  display: grid; gap: 15px 18px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
}
/*
 * Direct children only. As a descendant selector this stripped the 15px
 * separation from EVERY .field nested anywhere inside the two panels the grid
 * holds — so each label in Agency and Invoicing sat directly on the input above
 * it — while giving nothing back, because a field inside a panel body is not a
 * grid item and receives no grid gap.
 */
.settings-grid > .field { margin-bottom: 0; }
/* let a wide field (e.g. address, report footer) run the full grid width */
.settings-grid > .span-2, .settings-grid > .wide, .settings-grid > .field.wide { grid-column: 1 / -1; }

/* --- tools responsive -------------------------------------------------- */
/* The bar's inset tracks .view's own padding at every step, so the two edges
   stay flush: 26px, then 18px, then 14px. */
@media (max-width: 1080px) {
  .content-col > .annbar-wrap { margin: 22px 18px 0; }
  .layout + .annbar-wrap, .app > .annbar-wrap { margin: 22px 18px 0 calc(var(--nav-w) + 18px); }
}

@media (max-width: 720px) {
  /* the sidebar is a horizontal bottom bar here — flatten the groups so their
     items join the row (display:contents drops the rules and the heading with
     the box), and let the bar scroll now that the items keep their own width. */
  /* Two selectors, not one list: a browser that does not understand :has()
     discards the ENTIRE rule it appears in, which would leave the bottom bar
     rendering its groups as stacked columns. The plain selector carries the
     behaviour on its own; the :has() one is only a refinement. */
  .nav-group { display: contents; }
  .nav-group:has(+ .nav-item) { display: contents; }
  .nav-group-label { display: none; }
  .sidenav {
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
    scroll-snap-type: x proximity;
  }
  .sidenav::-webkit-scrollbar { display: none; }

  .updates-panel { width: 100%; border-left: none; }
  /* the rail is at the bottom now, so the bar has no rail to clear */
  .content-col > .annbar-wrap { margin: 18px 14px 0; }
  .layout + .annbar-wrap, .app > .annbar-wrap { margin: 18px 14px 0; }
  .annbar { padding: 10px 12px 10px 13px; font-size: 13px; border-radius: var(--r-sm); }
}
/* The five rigid columns are collapsed from 720px, not 560px: the modal is
   already at max-width:100% by then, so between the two widths the row kept a
   ~548px minimum inside a ~512px body and the Amount column and the delete
   button were pushed out of the visible area. */
@media (max-width: 720px) {
  .lineitems-head { display: none; }
  .lineitem { grid-template-columns: 1fr 1fr 1fr 40px; row-gap: 6px; }
  .lineitem > :first-child { grid-column: 1 / -1; }   /* description spans */
}
@media (max-width: 560px) {
  .inv-totals { width: 100%; }
}

/* ------------------------------------------- 20. task power-ups (ph. 2) */
/*
 * Checklists · dependencies · labels · watchers · comment reactions ·
 * comment edit/delete · @mention autocomplete. These live in the task drawer
 * (a few also on the board card). Nothing invents a colour: legible text comes
 * from --paper / --mut / the status -ink ramp, the member's own colour from the
 * derived --accent-* tokens (never raw --accent as ink or a thin stroke — the
 * light-mode rule), the "blocked" warning from the danger ramp, and arbitrary
 * label colours from the same "tint + lightness-clamped ink" pattern the client
 * stripes use (section 16).
 *
 * LABEL-COLOUR CONTRACT (CSS <-> app.js). A label colour is admin-chosen — any
 * hex — so it cannot be enumerated like the status ramp. app.js passes it to
 * CSS as an inline custom property  style="--lc:#hex"  on each .label-chip /
 * .label-dot. CSS does ALL the contrast maths from there: a soft same-hue tint
 * as the fill, and an ink whose OKLCH lightness is floored in dark (>=.72) and
 * capped in light (<=.46) so the name is legible on its own colour in both
 * themes. The raw --lc is only ever a fill or the >=3:1 dot, never text. Missing
 * --lc falls back to the member accent, so a chip is never colourless.
 *
 * DONE-STATE HOOKS. A checklist item's completed state is honoured from any of
 * .chk-item.done, .chk-box.on, .chk-box:checked, or aria-checked="true", so the
 * JS can express it however is convenient.
 */

/* --- checklist --------------------------------------------------------- */
.checklist { margin-bottom: 20px; }

.chk-progress {
  display: flex; align-items: center; gap: 11px; margin-bottom: 12px;
  font-family: var(--f-head); font-weight: 700; font-size: 12.5px;
  font-variant-numeric: tabular-nums; color: var(--mut);
}
.chk-progress b, .chk-progress .chk-count { color: var(--paper); }
/* .chk-bar is the track; its single child is the fill (width set inline). */
.chk-bar {
  flex: 1; min-width: 0; height: 7px; border-radius: 999px; overflow: hidden;
  background: var(--panel3); box-shadow: inset 0 0 0 1px var(--line);
}
.chk-bar > * {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-line), var(--accent));
  transition: width .6s var(--ease);
}
.chk-bar.full > *, .chk-bar.done > * { box-shadow: 0 0 12px -2px var(--accent-glow); }

.chk-list { display: flex; flex-direction: column; gap: 1px; }
.chk-item {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 6px 6px 6px 4px; border-radius: 9px;
  transition: background .14s var(--ease);
}
.chk-item:hover { background: var(--panel2); }

/* The box is a <button> or a native checkbox: appearance:none lets both share
   one look. Buttons get an animated ✓ via ::after; native inputs (which ignore
   pseudo-elements) get a painted tick from the input-only rule below. */
.chk-box {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 19px; height: 19px; margin-top: 1px; padding: 0;
  border-radius: 6px; border: 1.5px solid var(--line2);
  background: var(--panel2); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--accent-ink); font-size: 12px; font-weight: 900; line-height: 1;
  transition: background .16s var(--ease), border-color .16s var(--ease), transform .12s var(--ease);
}
.chk-box:hover { border-color: var(--accent-line); transform: translateY(-1px); }
/* A read-only drawer disables these, but .chk-box carries data-chk-toggle rather
   than data-patch, so the .drawer--readonly reset never reached it: the one
   control in the drawer that looks most clickable was the one that silently
   wasn't. Mirrors .tt-check:disabled. */
.chk-box:disabled, .chk-box[aria-disabled="true"] {
  opacity: .45; cursor: not-allowed; border-style: dashed;
}
.chk-box:disabled:hover, .chk-box[aria-disabled="true"]:hover {
  border-color: var(--line2); transform: none;
}
.chk-box::after {
  content: '✓'; opacity: 0; transform: scale(.5);
  transition: opacity .14s var(--ease), transform .14s var(--ease);
}
.chk-item.done > .chk-box,
.chk-box.on, .chk-box:checked, .chk-box[aria-checked="true"] {
  background: var(--accent); border-color: var(--accent);
}
.chk-item.done > .chk-box::after,
.chk-box.on::after, .chk-box:checked::after, .chk-box[aria-checked="true"]::after {
  opacity: 1; transform: scale(1);
}
/* Native checkbox path: pseudo-elements don't render on <input>, so paint the
   tick as a background image (dark, on the accent fill). */
input.chk-box:checked {
  background:
    var(--accent)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath d='M2.5 6.5l2.5 2.5 4.5-5' stroke='%230B0B0C' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    center / 12px no-repeat;
}

/* .chk-text is a static label or an inline-edit input — both read as plain
   text on the row. */
.chk-text {
  flex: 1; min-width: 0; align-self: center;
  font-size: 13.5px; line-height: 1.5; color: var(--paper); word-break: break-word;
  background: none; border: 1px solid transparent; border-radius: 7px;
  padding: 2px 4px; margin: -2px 0;
}
input.chk-text:focus, .chk-text:focus {
  outline: none; border-color: var(--accent-line);
  background: var(--panel2); box-shadow: 0 0 0 3px var(--accent-soft);
}
.chk-item.done .chk-text,
.chk-box:checked ~ .chk-text, .chk-box.on ~ .chk-text {
  color: var(--faint); text-decoration: line-through; text-decoration-color: var(--line2);
}

/* Row controls (edit / delete) reuse .btn-icon; keep them quiet until hover. */
.chk-item .btn-icon { width: 28px; height: 28px; flex: none; opacity: 0; transition: opacity .14s var(--ease), background .16s var(--ease), color .16s var(--ease); }
.chk-item:hover .btn-icon, .chk-item:focus-within .btn-icon { opacity: 1; }

.chk-add { display: flex; align-items: center; gap: 8px; margin-top: 9px; padding-left: 2px; }
.chk-add .inp { padding: 8px 12px; font-size: 13px; }
.chk-add .btn { flex: none; }

/* --- dependencies ------------------------------------------------------ */
.deps { margin-bottom: 20px; }
.dep-list { display: flex; flex-direction: column; gap: 7px; margin-bottom: 10px; }
.dep-list:empty { display: none; }

.dep-row {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--panel2); border: 1px solid var(--line);
  font-size: 13px; transition: border-color .14s var(--ease), background .14s var(--ease);
}
.dep-row:hover { border-color: var(--line2); background: var(--panel3); }
.dep-row .dep-title, .dep-row > a, .dep-row > span:not(.pill):not(.dot) {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dep-row > a { color: var(--paper); }
.dep-row > a:hover { color: var(--accent-fg); text-decoration: none; }
.dep-row .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: currentColor; }
/* A blocker that is already finished no longer holds this task up. */
.dep-row.done { opacity: .6; }
.dep-row.done .dep-title, .dep-row.done > a { text-decoration: line-through; }

.dep-remove {
  flex: none; width: 26px; height: 26px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--faint); font-size: 14px; line-height: 1;
  border: 1px solid transparent; background: transparent;
  transition: color .14s var(--ease), background .14s var(--ease), border-color .14s var(--ease);
}
.dep-remove:hover {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}

/* Blocked banner — a clear, unmissable warning at the top of the drawer. The
   danger ramp deepens in light mode, and --danger-ink is legible on the tint in
   both themes (pale rose text in dark, deep rose in light). */
.blocked-banner {
  display: flex; align-items: flex-start; gap: 11px;
  margin: 0 0 18px; padding: 12px 15px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--danger) 14%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  border-left: 4px solid var(--danger);
  color: var(--danger-ink); font-size: 13px; line-height: 1.5;
  animation: fadeUp .3s var(--ease) both;
}
.blocked-banner::before { content: '⛔'; flex: none; font-size: 17px; line-height: 1.3; }
.blocked-banner.no-ico::before { content: none; }
.blocked-banner .bb-main { flex: 1; min-width: 0; }
.blocked-banner b, .blocked-banner strong, .blocked-banner .bb-title {
  font-family: var(--f-head); font-weight: 700; letter-spacing: -.01em;
  color: var(--danger-ink);
}
.blocked-banner ul { list-style: none; margin: 5px 0 0; padding: 0; }
.blocked-banner li { display: flex; align-items: center; gap: 6px; padding: 1px 0; }
.blocked-banner li::before { content: '•'; opacity: .6; }
.blocked-banner a {
  color: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px; word-break: break-word;
}
.blocked-banner a:hover { text-decoration-thickness: 2px; }

/* Board-card blocked badge (task.blocked). */
.tcard-blocked {
  display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--f-head); font-weight: 700; font-size: 11px; line-height: 1;
  padding: 2px 8px; border-radius: 999px;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 34%, transparent);
}

/* --- labels ------------------------------------------------------------ */
.labels { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 20px; }

/* Standalone dot (picker, card, dep row). --lc arrives inline; the raw hue is
   fine as a >=3:1 fill, only clamped so a very light label still shows. */
.label-dot {
  --lc: var(--accent);
  width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block;
  background: var(--lc);
  background: oklch(from var(--lc) max(l, .62) c h);
}
[data-theme="light"] .label-dot { background: oklch(from var(--lc) min(l, .62) c h); }

.label-chip {
  --lc: var(--accent);
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 3px 11px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
  background: color-mix(in srgb, var(--lc) 15%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--lc) 38%, transparent);
  color: var(--paper);                              /* safe fallback */
  color: oklch(from var(--lc) max(l, .72) c h);     /* dark: floor L */
}
.label-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
[data-theme="light"] .label-chip {
  background: color-mix(in srgb, var(--lc) 15%, var(--panel));
  color: oklch(from var(--lc) min(l, .46) c h);     /* light: cap L */
}
/* remove-✕ on a chip in the task's own set */
.label-chip button, .label-chip .label-x {
  color: inherit; opacity: .55; font-size: 12px; line-height: 1;
  padding: 0 0 0 1px; background: none; border: none; cursor: pointer;
  transition: opacity .14s var(--ease);
}
.label-chip button:hover, .label-chip .label-x:hover { opacity: 1; }

/* The picker: existing labels to toggle + an inline create-new row. */
.label-picker {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 6px; padding: 12px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px;
}
.label-picker .label-chip { cursor: pointer; opacity: .5; transition: opacity .14s var(--ease), transform .14s var(--ease); }
.label-picker .label-chip:hover { opacity: .82; transform: translateY(-1px); }
.label-picker .label-chip.on { opacity: 1; }
.label-picker .label-chip.on .label-dot { box-shadow: 0 0 0 3px var(--ring); }

.label-new {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  width: 100%; margin-top: 2px; padding-top: 10px;
  border-top: 1px dashed var(--line2);
}
.label-new .inp { flex: 1 1 130px; padding: 7px 11px; font-size: 13px; }
.label-new input[type="color"] {
  flex: none; width: 34px; height: 34px; padding: 2px; cursor: pointer;
  border: 1px solid var(--line2); border-radius: 9px; background: var(--panel);
}
.label-new input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.label-new input[type="color"]::-webkit-color-swatch { border: none; border-radius: 6px; }
.label-new input[type="color"]::-moz-color-swatch { border: none; border-radius: 6px; }
.label-new .btn { flex: none; }

/* Board-card label row. */
/* Top-right corner of the card, one label under another. Capped at ~half the
   card so a long name never squeezes the client chip; a name that still does
   not fit is ellipsised and its full text is in the chip's title. */
.tcard-top { align-items: flex-start; }
.tcard-labels {
  display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  margin-left: auto; flex: none; max-width: 52%;
}
.tcard-labels .label-chip {
  padding: 1px 8px; font-size: 10.5px; gap: 5px; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tcard-labels .label-chip .label-txt { overflow: hidden; text-overflow: ellipsis; }
.tcard-labels .label-dot { width: 7px; height: 7px; }
.tcard-labels .label-more { padding: 1px 7px; }

/* --- tags ----------------------------------------------------------------
 * The display chip is SOLID: app.js writes the tag colour as the fill and an
 * auto black/white ink (accentInk, the accent's own luminance rule) inline, so
 * the tint/ink maths above is bypassed for .is-solid. The +N overflow chip and
 * the toggle chips (task form, filter menu) reuse the same shape. */
.label-chip.is-solid { border-color: transparent; gap: 4px; }
.label-chip.label-more { background: var(--panel2); border-color: var(--line2); color: var(--mut); }
button.label-chip {
  cursor: pointer; font: inherit;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
}
/* Toggle chips: unselected sit back, selected sit up with a ring. */
.tf-tags .label-chip, .tag-filter-list .label-chip {
  opacity: .48; transition: opacity .14s var(--ease), transform .14s var(--ease), box-shadow .14s var(--ease);
}
.tf-tags .label-chip:hover, .tag-filter-list .label-chip:hover { opacity: .82; transform: translateY(-1px); }
.tf-tags .label-chip.on, .tag-filter-list .label-chip.on { opacity: 1; box-shadow: 0 0 0 2px var(--ring); }
.tf-tags { margin-top: 0; }

/* Month grids: one dot per tag, names in the tooltip. */
.tag-dots { display: inline-flex; align-items: center; gap: 2px; flex: none; margin-left: 2px; }
.tag-dots i { display: block; width: 6px; height: 6px; border-radius: 50%; }

/* Drawer eyebrow: the task's tags beside its id. */
.drawer-tags { display: inline-flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.drawer-tags .label-chip { padding: 1px 8px; font-size: 10.5px; }

/* The Tasks filter bar: one button, a menu of toggle chips beneath it. */
.tag-filter { position: relative; display: inline-block; }
.tag-filter-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; margin-left: 2px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink); font-size: 11px;
}
.tag-filter-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  min-width: 280px; max-width: min(440px, 92vw); padding: 12px;
  background: var(--panel); border: 1px solid var(--line2); border-radius: 12px;
  box-shadow: var(--sh-lg);
}
.tag-filter-list { display: flex; flex-wrap: wrap; gap: 7px; }
.tag-filter-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line2);
}

/* Settings → Tags: one row per tag — swatch, name (click to rename), usage,
   ▲▼, delete — and an add row underneath. Same rhythm as the permissions grid. */
.tag-list { display: flex; flex-direction: column; }
.tag-row { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-top: 1px solid var(--line); }
.tag-row:first-child { border-top: 0; }
.tag-swatch {
  flex: none; width: 26px; height: 26px; padding: 0; border-radius: 8px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--paper) 18%, transparent);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--panel) 55%, transparent);
  transition: transform .14s var(--ease);
}
.tag-swatch:hover { transform: translateY(-1px); }
.tag-name { flex: 1 1 200px; min-width: 0; display: flex; align-items: center; cursor: text; }
.tag-name .label-chip { font-size: 12.5px; padding: 4px 12px; }
.tag-name:focus-visible { outline: 2px solid var(--accent-fg); outline-offset: 2px; border-radius: 999px; }
.tag-edit { max-width: 260px; padding: 6px 10px; font-size: 13px; }
.tag-used {
  flex: none; min-width: 64px; text-align: right;
  font-size: 12px; color: var(--mut); font-variant-numeric: tabular-nums;
}
.tag-move { display: inline-flex; gap: 2px; flex: none; }
.tag-move .btn-icon, .tag-row > .btn-icon { width: 28px; height: 28px; font-size: 11px; }
.tag-add {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line2);
}
.tag-add .inp { flex: 1 1 200px; padding: 8px 12px; font-size: 13.5px; }
.tag-add .btn { flex: none; }

/* The colour popover: the brand swatches, then a native picker for the rest. */
.tag-colors {
  position: fixed; z-index: 1200; padding: 10px;
  background: var(--panel); border: 1px solid var(--line2); border-radius: 12px;
  box-shadow: var(--sh-lg);
}
.tag-colors-grid { display: grid; grid-template-columns: repeat(7, 26px); gap: 7px; }
.tag-sw {
  width: 26px; height: 26px; padding: 0; border-radius: 8px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--paper) 18%, transparent);
  transition: transform .14s var(--ease);
}
.tag-sw:hover { transform: translateY(-1px); }
.tag-sw.on { box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px var(--accent-fg); }
.tag-colors-any {
  display: flex; align-items: center; gap: 8px; margin: 10px 0 0; cursor: pointer;
  font-size: 12.5px; font-weight: 500; color: var(--mut); text-transform: none; letter-spacing: 0;
}
.tag-colors-any input[type="color"] {
  width: 26px; height: 26px; padding: 1px; cursor: pointer;
  border: 1px solid var(--line2); border-radius: 8px; background: var(--panel);
}
@media (max-width: 640px) {
  .tag-row { flex-wrap: wrap; }
  .tag-used { min-width: 0; text-align: left; }
}

/* --- watchers ---------------------------------------------------------- */
.watchers { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 20px; }
.watch-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line2); background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  transition: color .16s var(--ease), background .16s var(--ease), border-color .16s var(--ease), transform .12s var(--ease);
}
.watch-btn:hover { color: var(--paper); background: var(--panel3); border-color: var(--line2); transform: translateY(-1px); }
.watch-btn.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-fg); }
.watch-btn.on:hover { background: var(--accent-soft2); color: var(--accent-fg); }
/* Overlapping avatar stack. .watch-av is a size modifier on .avatar; the panel-
   coloured ring separates the overlap without changing the avatar's box size. */
.watch-av { width: 26px; height: 26px; font-size: 10px; box-shadow: 0 0 0 2px var(--panel); }
.watch-av + .watch-av { margin-left: -9px; }

/* --- comment reactions ------------------------------------------------- */
.reactions { position: relative; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.react-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel2); color: var(--mut);
  font-family: var(--f-head); font-weight: 600; font-size: 12.5px; line-height: 1.5;
  transition: color .14s var(--ease), background .14s var(--ease), border-color .14s var(--ease), transform .12s var(--ease);
}
.react-chip:hover { border-color: var(--line2); background: var(--panel3); transform: translateY(-1px); }
.react-chip .react-count, .react-chip b { font-variant-numeric: tabular-nums; }
/* The viewer's own reaction reads as selected — accent wash + accent ink. */
.react-chip.mine { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-fg); }
.react-chip.mine:hover { background: var(--accent-soft2); }

.react-add {
  display: inline-flex; align-items: center; justify-content: center;
  width: 27px; height: 24px; padding: 0; border-radius: 999px;
  border: 1px dashed var(--line2); background: transparent; color: var(--faint);
  font-size: 14px; line-height: 1;
  transition: color .14s var(--ease), background .14s var(--ease), border-color .14s var(--ease);
}
.react-add:hover { color: var(--accent-fg); border-color: var(--accent-line); background: var(--accent-soft); }

.react-picker {
  position: absolute; z-index: 20; top: calc(100% + 6px); left: 0;
  display: flex; align-items: center; gap: 3px;
  padding: 6px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line2);
  box-shadow: var(--sh-lg); animation: popIn .14s var(--ease) both;
}
.react-picker button {
  width: 34px; height: 34px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1; background: transparent; border: 1px solid transparent;
  transition: background .12s var(--ease), transform .12s var(--ease), border-color .12s var(--ease);
}
.react-picker button:hover { background: var(--panel3); border-color: var(--line); transform: translateY(-2px) scale(1.12); }

/* --- comment edit / delete + edited marker ----------------------------- */
/* Author actions sit in the comment head, quiet until the comment is hovered. */
.cmt-actions { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; opacity: 0; transition: opacity .14s var(--ease); }
.cmt:hover .cmt-actions, .cmt:focus-within .cmt-actions { opacity: 1; }
.cmt-actions button {
  font-family: var(--f-head); font-size: 11px; font-weight: 600;
  color: var(--faint); background: none; border: none; cursor: pointer;
  padding: 2px 6px; border-radius: 6px;
  transition: color .14s var(--ease), background .14s var(--ease);
}
.cmt-actions button:hover { color: var(--paper); background: var(--panel3); }
.cmt-actions button.danger:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }

.cmt-edit { display: flex; flex-direction: column; gap: 8px; }
.cmt-edit .txa { min-height: 60px; }
.cmt-edit-actions { display: flex; gap: 8px; justify-content: flex-end; }

.cmt-edited { margin-left: 4px; font-size: 10.5px; font-style: italic; color: var(--faint); }

/* --- @mention autocomplete --------------------------------------------- */
.composer, .composer-row { position: relative; }
.mention-menu {
  position: absolute; z-index: 70; left: 10px; bottom: calc(100% + 8px);
  min-width: 220px; max-width: min(320px, calc(100vw - 40px)); max-height: 236px;
  overflow-y: auto; padding: 6px;
  background: var(--panel); border: 1px solid var(--line2);
  border-radius: 12px; box-shadow: var(--sh-lg); animation: popIn .13s var(--ease) both;
}
.mention-opt {
  display: flex; align-items: center; gap: 9px;
  width: 100%; text-align: left;
  padding: 7px 9px; border-radius: 9px;
  color: var(--paper); font-size: 13px;
  transition: background .1s var(--ease), color .1s var(--ease);
}
.mention-opt .avatar { width: 24px; height: 24px; font-size: 10px; flex: none; }
.mention-opt .mention-opt-name { font-family: var(--f-head); font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mention-opt .mention-opt-sub { margin-left: auto; font-size: 11px; color: var(--faint); flex: none; }
/* Keyboard cursor (↑/↓) and hover share one highlight. */
.mention-opt.active, .mention-opt:hover { background: var(--accent-soft); color: var(--accent-fg); }
.mention-opt.active .mention-opt-sub, .mention-opt:hover .mention-opt-sub { color: var(--accent-fg); }

/* --- power-ups: responsive + touch ------------------------------------- */
/* No hover on touch: reveal the otherwise hover-gated controls. */
@media (hover: none) {
  .chk-item .btn-icon { opacity: 1; }
  .cmt-actions { opacity: 1; }
  .cmt-reply { opacity: 1; }
}
@media (max-width: 720px) {
  .checklist, .deps, .labels, .watchers { margin-bottom: 18px; }
  .chk-add { flex-wrap: wrap; }
  .chk-add .inp { flex: 1 1 100%; }
  .label-picker { padding: 10px; }
  .mention-menu { left: 6px; right: 6px; min-width: 0; max-width: none; }
}

/* ---------------------------------------------- 21. views (phase 3) */
/*
 * Three ways to see the same tasks — none of them a new colour or a new task
 * write path:
 *
 *   LIST / TABLE  a dense sortable table (tasks.list) with inline edits that
 *                 reuse the existing status/priority/assignee/due controls.
 *   MY WORK       tasks.mine, bucketed by due date.
 *   COMMAND-K     a centred search palette over search{q}.
 *
 * Everything reuses tokens and components already defined above: status and
 * priority read through .pill / .prio (repaired in section 16), labels through
 * .label-chip, the unread dot / blocked badge as-is, filters through .toolbar's
 * .sel / .search-wrap / .toggle-mine. The raw member --accent is never used as
 * ink or a thin stroke — only --accent-fg / --accent-line / --accent-soft — so
 * both themes stay legible under the light-mode rule.
 */

/* --- list / table view ------------------------------------------------- */
.listview { display: flex; flex-direction: column; }

/* Mirrors .toolbar so the board's filters look identical here, then adds the
   group-by axis as a distinct pill (see .groupby below). */
.list-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 11px 13px; margin-bottom: 18px;
  box-shadow: var(--sh-card);
}
.list-toolbar .sel, .list-toolbar .inp { width: auto; min-width: 132px; padding: 8px 12px; font-size: 13.5px; }
.list-toolbar .sel { padding-right: 30px; }
.list-toolbar .search-wrap { flex: 1; min-width: 180px; }
.list-toolbar .search-wrap .inp { width: 100%; }

/* Group-by is a different AXIS from the filters, so it reads as its own pill
   control (mirrors .period-pick), not another plain <select>. Works whether
   app.js emits a container (label + select) or a bare <select class="groupby">. */
.groupby:not(select) {
  display: inline-flex; align-items: center; gap: 8px; flex: none;
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 6px 4px 13px;
}
.groupby:not(select) .lbl { margin: 0; font-size: 10.5px; white-space: nowrap; }
.groupby:not(select) .sel, .groupby:not(select) select {
  width: auto; min-width: 116px; border: none; background-color: transparent;
  padding: 6px 26px 6px 4px; font-family: var(--f-head); font-weight: 600;
}
.groupby:not(select) .sel:hover, .groupby:not(select) select:hover { border: none; }
.groupby:not(select) .sel:focus, .groupby:not(select) select:focus { box-shadow: none; background-color: transparent; }
select.groupby { width: auto; min-width: 150px; }

/* The table scrolls INSIDE this container; the page never scrolls sideways
   (same contract as .table-wrap). A min-width on the table forces the scroll
   rather than crushing columns. */
.tt-scroll {
  overflow-x: auto; overflow-y: hidden;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); box-shadow: var(--sh-card);
}
.ttable { width: 100%; border-collapse: collapse; min-width: 880px; }

.tt-th {
  position: relative; text-align: left; vertical-align: middle;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
  padding: 12px 14px; white-space: nowrap;
  background: var(--panel2); border-bottom: 1px solid var(--line);
  user-select: none;
}
.tt-th.right { text-align: right; }

/* Sortable header: a caret that hints on hover and turns accent + directional
   once the column is the active sort. Base caret points down; asc flips it. */
.tt-th.sortable { cursor: pointer; padding-right: 26px; transition: color .14s var(--ease); }
.tt-th.sortable:hover { color: var(--mut); }
.tt-th.sortable::after {
  content: ''; position: absolute; right: 12px; top: 50%; margin-top: -2px;
  width: 0; height: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0; transition: opacity .14s var(--ease), transform .14s var(--ease), border-top-color .14s var(--ease);
}
.tt-th.sortable:hover::after { opacity: .5; }
.tt-th.sorted-asc, .tt-th.sorted-desc { color: var(--accent-fg); }
.tt-th.sorted-asc::after, .tt-th.sorted-desc::after { opacity: 1; border-top-color: var(--accent-fg); }
.tt-th.sorted-asc::after { transform: rotate(180deg); }

.tt-row { transition: background .14s var(--ease); }
.tt-row:hover { background: var(--panel2); }
.tt-row .tt-cell { border-top: 1px solid var(--line); }

.tt-cell {
  padding: 9px 14px; font-size: 13.5px; vertical-align: middle;
  white-space: nowrap; color: var(--paper);
}
.tt-cell.right { text-align: right; }
.tt-cell.mut { color: var(--mut); }
.tt-cell.num { font-variant-numeric: tabular-nums; color: var(--mut); }
/* The labels cell is the one place cells may wrap, so chips stack instead of
   pushing the row wide. */
.tt-cell:has(.label-chip) { white-space: normal; }
.tt-cell .label-chip { padding: 1px 8px; font-size: 10.5px; }

/* Title cell: clickable (opens the drawer), with room for the unread dot, the
   blocked badge and label chips beside the text. */
.tt-title { min-width: 240px; max-width: 460px; white-space: normal; }
.tt-titlewrap, .tt-title > .tt-titlewrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tt-title .unread-dot { margin-right: 1px; }
.tt-title button, .tt-title a, .tt-title [data-open], .tt-title .tt-name {
  color: var(--paper); font-weight: 600; text-align: left; background: none;
  border: none; padding: 0; cursor: pointer; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
  transition: color .14s var(--ease);
}
.tt-title button:hover, .tt-title a:hover, .tt-title [data-open]:hover, .tt-title .tt-name:hover {
  color: var(--accent-fg); text-decoration: none;
}
.tt-row.unread .tt-title button, .tt-row.unread .tt-title .tt-name { font-weight: 700; }
.tt-title .tcard-blocked { flex: none; }

/* Inline edit control (status / assignee / priority / due). Reads as plain
   text in the cell, reveals its editability on hover, and lights the accent on
   focus — so a can_edit row is quietly discoverable without shouting. Rows the
   member cannot edit simply render plain cells with no .tt-inline. */
.tt-inline {
  appearance: none; -webkit-appearance: none;
  max-width: 100%; border: 1px solid transparent; border-radius: 7px;
  background: transparent; color: inherit;
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  padding: 4px 7px; margin: -4px -7px; cursor: pointer;
  transition: background .14s var(--ease), border-color .14s var(--ease), box-shadow .14s var(--ease);
}
/* background-COLOR — the shorthand would reset the select arrow drawn below. */
.tt-inline:hover { background-color: var(--panel3); border-color: var(--line); }
.tt-inline:focus {
  background-color: var(--focus-bg);
  border-color: var(--accent-line); box-shadow: 0 0 0 3px var(--accent-soft);
}
.tt-inline:focus:not(:focus-visible) { outline: none; }
select.tt-inline {
  padding-right: 24px;
  background-image: var(--sel-arrow); background-repeat: no-repeat;
  background-position: right 6px center;
}
.tt-inline[type="date"] { min-width: 118px; }
.tt-inline[type="date"]::-webkit-calendar-picker-indicator { filter: var(--pick-invert); cursor: pointer; }

/* Group-by header row (spans the table via a colspan cell) with a count. */
.tt-grouphead { background: var(--panel2); }
.tt-grouphead:hover { background: var(--panel2); }
.tt-grouphead .tt-cell, .tt-grouphead td, .tt-grouphead th {
  padding: 9px 14px; border-top: 1px solid var(--line);
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--mut);
  white-space: nowrap;
}
.tt-grouphead:first-child .tt-cell, .tt-group:first-child .tt-grouphead .tt-cell { border-top: none; }
.tt-count {
  display: inline-block; margin-left: 8px;
  background: var(--panel3); color: var(--mut);
  border-radius: 999px; padding: 1px 8px;
  font-size: 11px; font-weight: 700; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

/* Box, padding and colour come from the shared empty-state object (section 10);
   the type scale is set alongside .chg-empty / .fin-empty further down. */

/* --- my work view ------------------------------------------------------ */
/*
 * The cap is gone. 860px inside a 1059px column (1280px screen) or a ~1180px one
 * (1440px) abandoned 200–320px at the right for nothing — and the buckets are
 * cards of short rows, not paragraphs, so there was no reading measure to
 * protect. The column fills the view and lays its buckets out side by side once
 * two of them fit; a very wide monitor still gets a sane maximum.
 *
 * The gap is declared on the SLOT, not here: paintMyWork() writes the sections
 * into #mw-slot, so this wrapper has exactly one child and its own gap resolved
 * to nothing — the four buckets stacked with their borders doubled up and read
 * as one slab.
 */
.mywork { display: flex; flex-direction: column; gap: 18px; max-width: 1400px; }
.mywork > #mw-slot, .mywork > div:only-child {
  display: grid; gap: inherit; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(430px, 1fr));
}
/* An empty state is a statement about the whole page, not about one track. */
.mywork > #mw-slot > .mw-empty, .mywork > #mw-slot > .empty,
.mywork > div:only-child > .mw-empty { grid-column: 1 / -1; }

.mw-section {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--sh-card); overflow: hidden;
}
.mw-sechead {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px; border-bottom: 1px solid var(--line);
  font-family: var(--f-head); font-size: 12px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--mut);
}
.mw-sechead .tt-count, .mw-sechead .mw-count, .mw-sechead .count {
  margin-left: 0;
  background: var(--panel3); color: var(--mut);
  border-radius: 999px; padding: 1px 8px;
  font-size: 11px; font-weight: 700; letter-spacing: 0; font-variant-numeric: tabular-nums;
}
/* The overdue bucket earns a quiet danger tint on its header so it reads first. */
.mw-section.overdue .mw-sechead { color: var(--danger); border-bottom-color: color-mix(in srgb, var(--danger) 28%, var(--line)); }
.mw-section.overdue .mw-sechead .count, .mw-section.overdue .mw-sechead .tt-count {
  background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger);
}

.mw-row {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  padding: 11px 16px; border-bottom: 1px solid var(--line);
  transition: background .14s var(--ease), padding-left .14s var(--ease);
}
.mw-row:last-child { border-bottom: none; }
.mw-row:hover { background: var(--panel2); padding-left: 20px; }
.mw-row .unread-dot { flex: none; }
/* The task title flexes and truncates; everything after it stays put. */
.mw-row .mw-title, .mw-row > .grow {
  flex: 1; min-width: 0; font-size: 13.5px; color: var(--paper);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mw-row:hover .mw-title { color: var(--accent-fg); }
.mw-row .chip, .mw-row .label-chip { flex: none; }
.mw-row .tt-inline, .mw-row .pill { flex: none; }
.mw-row .label-chip { padding: 1px 8px; font-size: 10.5px; }

/* Due stamp — plain by default, emphasised when overdue (danger) or soon (warn),
   matching the .due-tag / .tcard-due language used elsewhere. */
.mw-due {
  flex: none; font-family: var(--f-head); font-weight: 600; font-size: 12px;
  color: var(--faint); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mw-due.overdue { color: var(--danger); }
.mw-due.soon { color: var(--warn); }

/* .mw-empty is the shared empty-state card (section 10) — nothing local to add. */

/* --- command palette (⌘K / Ctrl-K) ------------------------------------- */
/* Sits over the app like a spotlight: blurred scrim, centred near the top so
   results grow downward without shifting the input. */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 130;
  background: var(--scrim);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 12vh 16px 24px;
  animation: fadeIn .16s var(--ease);
}
@media (max-height: 620px) { .cmdk-overlay { padding-top: 6vh; } }

.cmdk {
  width: min(600px, 100%); max-height: 72vh;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--panel); border: 1px solid var(--line2);
  border-radius: var(--r-lg); box-shadow: var(--sh-lg);
  animation: popIn .18s var(--ease) both;
}

/* Search input row — a leading magnifier drawn as a background image so no
   extra markup is needed. */
.cmdk-input {
  width: 100%; flex: none;
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  background: transparent; color: var(--paper);
  font-family: var(--f-head); font-weight: 600; font-size: 16.5px;
  padding: 17px 18px 17px 46px; outline: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%238A8A93' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='7' cy='7' r='5'/%3E%3Cpath d='M11 11l4 4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: 18px center;
}
.cmdk-input::placeholder { color: var(--faint); font-weight: 500; }
/* outline:none above removed the only focus indicator this control had, making
   it the one text field in the app a keyboard user cannot locate. The border it
   already owns becomes the ring. */
.cmdk-input:focus-visible { border-bottom-color: var(--accent-line); box-shadow: 0 1px 0 var(--accent-line); }

.cmdk-list { overflow-y: auto; padding: 8px; min-height: 0; }

.cmdk-sec {
  padding: 12px 10px 6px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase; color: var(--faint);
}
.cmdk-sec:first-child { padding-top: 6px; }

.cmdk-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  padding: 10px 11px; border-radius: 10px;
  border: 1px solid transparent; color: var(--paper);
  transition: background .12s var(--ease), border-color .12s var(--ease), color .12s var(--ease);
}
.cmdk-item + .cmdk-item { margin-top: 1px; }
/* Keyboard cursor (↑/↓) and hover share one highlight, like .mention-opt. */
.cmdk-item.active, .cmdk-item:hover { background: var(--accent-soft); border-color: var(--accent-soft2); color: var(--accent-fg); }

.cmdk-ico {
  width: 30px; height: 30px; border-radius: 9px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel3); color: var(--mut); font-size: 14px;
}
.cmdk-item.active .cmdk-ico { background: var(--accent); color: var(--accent-ink); }
/* A client result carries its own accent inline (--accent on the item); its ink
   is re-derived, exactly like .cr-mark, so any client colour stays legible. */
.cmdk-item[style*="--accent"] .cmdk-ico {
  background: var(--accent);
  color: oklch(from var(--accent) clamp(0, (.62 - l) * 100, 1) min(c, .04) h) !important;
}

.cmdk-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.cmdk-item .cmdk-name, .cmdk-main > :first-child {
  font-family: var(--f-head); font-weight: 600; font-size: 13.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmdk-meta {
  margin-left: auto; flex: none; padding-left: 10px;
  font-size: 11.5px; color: var(--faint); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.cmdk-item.active .cmdk-meta { color: var(--accent-fg); }

.cmdk-empty {
  text-align: center; padding: 40px 22px; color: var(--faint);
  font-size: 13px; line-height: 1.6;
}

/* Footer hint row with keycaps. */
.cmdk-hint {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: none; padding: 10px 14px; border-top: 1px solid var(--line);
  background: var(--panel2); color: var(--faint); font-size: 11.5px;
}
.cmdk-kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  border: 1px solid var(--line2); border-bottom-width: 2px; border-radius: 5px;
  background: var(--panel); color: var(--mut);
  font-family: var(--f-head); font-size: 11px; font-weight: 700; line-height: 1;
}

/* --- views: responsive ------------------------------------------------- */
@media (max-width: 720px) {
  .list-toolbar { padding: 10px; gap: 8px; }
  .list-toolbar .sel, .list-toolbar .inp { min-width: 0; flex: 1 1 132px; }
  .list-toolbar .search-wrap { flex: 1 1 100%; }
  .groupby:not(select) { flex: 1 1 100%; justify-content: space-between; }

  .mywork { max-width: none; }
  .mywork > #mw-slot, .mywork > div:only-child { grid-template-columns: minmax(0, 1fr); }
  .mw-row { padding: 11px 13px; }
  .mw-row:hover { padding-left: 16px; }

  .cmdk-overlay { padding: 8vh 12px 20px; }
  .cmdk-input { font-size: 16px; padding: 15px 16px 15px 44px; }
}

/* ------------------------------------- 22. retainer engine (phase 4) */
/*
 * Content calendar · retainer profitability · task templates · bulk actions.
 * The agency layer: what goes live when, what a retainer actually costs to
 * service, and the two shortcuts (templates, bulk edits) that keep the first
 * two honest.
 *
 * Nothing here invents a colour. Three ramps do all the work:
 *
 *   PUBLISH STATE  mapped onto the status ramp (--st-*), so both themes get a
 *                  legible -ink tone for free — the same trick section 19 uses
 *                  for announcement levels. Each state sets two locals, --cs
 *                  (the vivid hue: fills and dots, where 3:1 is the bar) and
 *                  --cs-ink (the readable text tone).
 *   RETAINER USAGE under / near / over reads as done → client-review → revisions.
 *   CLIENT ACCENT  arrives inline as --accent on a chip or card, exactly as on
 *                  .cal-chip, so the light-mode clamp is written at the use site
 *                  (see the note in the accent block) and never as a :root token.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL. The three publish states differ by SHAPE as
 * well as hue — a hollow ring (draft), a diamond (scheduled) and a filled disc
 * with a halo (published) — and the chip's own left stripe is dashed for draft,
 * solid for scheduled and solid-thick for published. That survives greyscale,
 * every form of colour blindness, and a phone at 40% brightness.
 *
 * The state modifier may sit on the chip OR on the dot itself; every rule
 * matches both, so the JS side can nest it either way.
 */

.cc-state-draft     { --cs: var(--st-backlog);  --cs-ink: var(--st-backlog-ink); }
.cc-state-scheduled { --cs: var(--st-progress); --cs-ink: var(--st-progress-ink); }
.cc-state-published { --cs: var(--st-done);     --cs-ink: var(--st-done-ink); }

/* --- content calendar --------------------------------------------------- */
/*
 * The month grid itself is the calendar from section 14 (.cal / .cal-head /
 * .cal-grid / .cal-day) — publish date is a different AXIS over the same
 * furniture, not a second calendar. Only the chips are new, because a content
 * item carries three facts a due-date chip does not: client, channel and
 * publish state.
 */
.content-cal { display: flex; flex-direction: column; }

/* Filter bar: client · channel · publish state, plus the month nav. Reads as a
   panel (like .list-toolbar) rather than a bare row, because it carries more
   controls than the due-date calendar's .cal-toolbar. */
.cc-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 11px 13px; margin-bottom: 16px;
  box-shadow: var(--sh-card);
}
.cc-toolbar .sel, .cc-toolbar .inp {
  width: auto; min-width: 138px; padding: 8px 12px; font-size: 13.5px;
}
.cc-toolbar .sel { padding-right: 30px; }
.cc-toolbar .grow { flex: 1; }
.cc-toolbar .btn, .cc-toolbar .btn-sm { flex: none; }
.cc-toolbar .cal-nav, .cc-toolbar .cc-nav { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.cc-toolbar .cal-nav .btn, .cc-toolbar .cc-nav .btn { padding: 6px 12px; }

/* Publish days need a little more room than due days — a cell often holds a
   chip per channel. */
.content-cal .cal-day { min-height: 122px; }
.content-cal .cal-items { gap: 4px; }

/*
 * The chip. Tinted by the CLIENT's accent (inline --accent) with a left stripe,
 * so a month scans by client at a glance; the state dot and the channel tag ride
 * inside it. A button, so the whole thing opens the task.
 */
.cc-chip {
  display: flex; align-items: center; gap: 6px;
  width: 100%; text-align: left; min-width: 0; position: relative;
  padding: 3px 6px 3px 7px; border-radius: 7px;
  background: color-mix(in srgb, var(--accent) 11%, var(--panel2));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-left: 3px solid var(--accent);
  /* client stripe, dark: floor L so a deep client accent cannot sink into the
     panel — the light cap is applied below (see the note in the accent block) */
  border-left-color: oklch(from var(--accent) max(l, .62) c h);
  font-size: 11px; line-height: 1.35; color: var(--paper);
  transition: background .14s var(--ease), transform .14s var(--ease), border-color .14s var(--ease);
}
.cc-chip:hover {
  background: color-mix(in srgb, var(--accent) 20%, var(--panel2));
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  transform: translateX(1px);
}
.cc-chip-txt, .cc-chip > span:not(.cc-dot):not(.cc-channel) {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1;
}

/* Per-state chip treatment — the stripe carries the second, non-colour signal. */
.cc-chip.cc-state-draft     { border-left-style: dashed; }
.cc-chip.cc-state-draft .cc-chip-txt { color: var(--cs-ink, var(--mut)); }
.cc-chip.cc-state-scheduled { border-left-style: solid; }
.cc-chip.cc-state-published { border-left-width: 5px; }
.cc-chip.cc-state-published .cc-chip-txt { font-weight: 600; }

/*
 * The state dot. Shape IS the state:
 *   draft      hollow ring   — nothing committed yet
 *   scheduled  diamond       — pinned to a date, not out
 *   published  filled + halo — live
 */
.cc-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--cs, var(--mut));
  transition: box-shadow .16s var(--ease);
}
.cc-state-draft > .cc-dot, .cc-state-draft .cc-dot, .cc-dot.cc-state-draft {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--cs, var(--mut));
}
.cc-state-scheduled > .cc-dot, .cc-state-scheduled .cc-dot, .cc-dot.cc-state-scheduled {
  width: 7px; height: 7px; border-radius: 2px;
  transform: rotate(45deg);
  background: var(--cs);
}
.cc-state-published > .cc-dot, .cc-state-published .cc-dot, .cc-dot.cc-state-published {
  background: var(--cs);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--cs) 32%, transparent);
}

/* Channel tag — a quiet uppercase caption, never a second colour. */
.cc-channel {
  flex: none; margin-left: auto;
  font-family: var(--f-head); font-size: 9px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
  max-width: 74px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cc-chip:hover .cc-channel { color: var(--mut); }

/*
 * .cc-more serves two jobs and is styled for both:
 *   as a BLOCK inside a day cell it is the "+3 more" overflow link (mirrors
 *   .cal-more); inside a chip it is the ⋮ handle that opens the quick
 *   publish-state menu, quiet until the chip is hovered or focused.
 */
.cc-more {
  display: block; text-align: left; padding-left: 4px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 600;
  color: var(--faint);
  transition: color .14s var(--ease);
}
.cc-more:hover { color: var(--accent-fg); }
.cc-chip .cc-more {
  flex: none; margin-left: 0; padding: 0;
  width: 16px; height: 16px; border-radius: 5px; line-height: 14px; text-align: center;
  color: var(--mut); opacity: 0;
  transition: opacity .14s var(--ease), background .14s var(--ease), color .14s var(--ease);
}
.cc-chip:hover .cc-more, .cc-chip:focus-within .cc-more, .cc-chip .cc-more:focus-visible { opacity: 1; }
.cc-chip .cc-more:hover { background: var(--panel3); color: var(--paper); }
/* Touch has no hover, and the chip's own click opens the drawer — without this
   the ⋮ handle is the one hover-gated control in the app with no way to reach
   it on a tablet. Partial opacity, matching .tpl-actions / .contact-actions: it
   stays secondary, it just exists. */
@media (hover: none) { .cc-chip .cc-more { opacity: .7; } }

/* Optional popover for that quick state change (hangs off a .menu-host). */
.cc-menu {
  position: absolute; z-index: 70; top: calc(100% + 5px); left: 0; min-width: 156px;
  padding: 5px; background: var(--panel); border: 1px solid var(--line2);
  border-radius: var(--r-sm); box-shadow: var(--sh-lg);
  animation: popIn .15s var(--ease) both;
}
.cc-menu-item {
  display: flex; align-items: center; gap: 9px;
  width: 100%; text-align: left; padding: 7px 9px; border-radius: 8px;
  font-size: 12.5px; color: var(--paper);
  transition: background .12s var(--ease), color .12s var(--ease);
}
/* each option carries its own state ink, so the menu reads as the three states
   and not as three identical lines */
.cc-menu-item.cc-state-draft, .cc-menu-item.cc-state-scheduled, .cc-menu-item.cc-state-published { color: var(--cs-ink); }
.cc-menu-item:hover, .cc-menu-item.on { background: var(--accent-soft); color: var(--accent-fg); }
.cc-menu-item.on { font-weight: 700; }

/*
 * Light mode, client stripe. var(--accent) here is the CLIENT's inline accent,
 * so the clamp is written at the use site — see the note in the accent block.
 */
[data-theme="light"] .cc-chip {
  border-left-color: oklch(from var(--accent) min(l, .62) c h) !important;
  background: color-mix(in srgb, oklch(from var(--accent) min(l, .62) c h) 9%, var(--panel2));
}
[data-theme="light"] .cc-chip:hover {
  background: color-mix(in srgb, oklch(from var(--accent) min(l, .62) c h) 16%, var(--panel2));
}

/*
 * Drawer: the publish trio (publish date · publish state · channel). They sit in
 * the same .meta-grid as due date and assignee, so they need to read as a GROUP
 * that is not the due date — a faint accent wash and border does that without a
 * second layout. The hook is the autosave attribute the fields already carry.
 */
.meta-cell.publish,
.meta-cell:has([data-patch^="publish"]),
.meta-cell:has([data-patch="channel"]) {
  background: color-mix(in srgb, var(--accent) 6%, var(--panel2));
  border-color: color-mix(in srgb, var(--accent) 26%, var(--line));
}
.meta-cell .hint { margin-top: 5px; font-size: 10.5px; line-height: 1.45; }
.meta-cell .cc-dot { display: inline-block; margin-right: 6px; vertical-align: middle; }

/* --- profitability (admin) --------------------------------------------- */
/*
 * One row per client for the period: contracted fee and hours against hours
 * actually tracked, with the usage bar as the headline. Money is admin-only on
 * the server; this is only its surface.
 *
 * .profit-head and .profit-row share one column template, declared once as
 * --profit-cols on the container, so the labels always sit over their numbers.
 * The eight columns are: client · fee · contracted · used · usage · left ·
 * rate · delivered. A different column count only needs a new --profit-cols.
 */
.profit {
  --profit-cols: minmax(130px, 1.7fr) 94px 84px 84px minmax(110px, 1.5fr) 84px 100px 90px;
  display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--sh-card); overflow: hidden;
}
.profit-head, .profit-row {
  display: grid; grid-template-columns: var(--profit-cols);
  align-items: center; gap: 12px; padding: 12px 16px;
}
.profit-head {
  background: var(--panel2); border-bottom: 1px solid var(--line);
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
  white-space: nowrap;
}
.profit-head > * { overflow: hidden; text-overflow: ellipsis; }

.profit-row {
  border-top: 1px solid var(--line); font-size: 13.5px;
  transition: background .14s var(--ease);
}
.profit-row:first-child { border-top: none; }
.profit-head + .profit-row { border-top: none; }
.profit-row:hover { background: var(--panel2); }

/* The client cell: name + its accent mark, mirroring .client-row elsewhere. */
.profit-row > :first-child, .profit-name {
  display: flex; align-items: center; gap: 9px; min-width: 0;
  font-family: var(--f-head); font-weight: 600;
}
.profit-row .chip, .profit-row .chip-client { flex: none; }
.profit-row .profit-client, .profit-row .profit-name span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* Numbers read as one ledger: tabular, right-aligned, head font. A <b> or the
   .money class inside promotes a value to primary. */
.profit-num {
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  font-variant-numeric: tabular-nums; text-align: right;
  color: var(--mut); white-space: nowrap; min-width: 0;
}
.profit-num b, .profit-num strong { color: var(--paper); font-weight: 700; }
.profit-num small, .profit-num .sub { display: block; font-size: 10.5px; color: var(--faint); font-weight: 500; }
.profit-num.pos b, .profit-num.pos { color: var(--st-done-ink); }
.profit-num.neg b, .profit-num.neg { color: var(--danger); }
.profit-row .money { color: var(--paper); }

/* Usage bar. Under → done, near the limit → client-review amber, over → the
   revisions red PLUS a hatch, so "over" is readable without colour. */
.profit-bar {
  position: relative; min-width: 0; height: 9px;
  border-radius: 999px; background: var(--panel3);
  box-shadow: inset 0 0 0 1px var(--line); overflow: hidden;
}
.profit-fill {
  display: block; height: 100%; width: 0; max-width: 100%;
  border-radius: 999px; background: var(--st-done);
  transition: width .9s var(--ease), background-color .2s var(--ease);
}
.profit-fill.warn { background: var(--st-client); }
.profit-fill.over {
  background-color: var(--st-revisions);
  background-image: repeating-linear-gradient(135deg,
    rgba(255,255,255,.3) 0 4px, transparent 4px 9px);
}

/* The over-serviced flag: a badge, and a whisper of danger under the whole row
   so it is findable while scrolling (same language as .wl-row.over on Workload). */
.profit-flag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 1px 8px; border-radius: 999px;
  font-family: var(--f-head); font-size: 9.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; white-space: nowrap;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 34%, transparent);
}
.profit-flag::before { content: '▲'; font-size: 8px; line-height: 1; }
.profit-flag.warn {
  color: var(--st-client-ink);
  background: color-mix(in srgb, var(--st-client) 15%, transparent);
  border-color: color-mix(in srgb, var(--st-client) 36%, transparent);
}
.profit-flag.warn::before { content: '●'; }
.profit-flag.ok {
  color: var(--st-done-ink);
  background: color-mix(in srgb, var(--st-done) 15%, transparent);
  border-color: color-mix(in srgb, var(--st-done) 36%, transparent);
}
.profit-flag.ok::before { content: '✓'; }
.profit-row.over { background: color-mix(in srgb, var(--danger) 6%, transparent); }
.profit-row.over:hover { background: color-mix(in srgb, var(--danger) 10%, transparent); }

.profit-empty { text-align: center; padding: 46px 20px; color: var(--faint); font-size: 13px; line-height: 1.6; }

/* If the JS renders a real <table> instead of a stack of rows, the grid would
   fight the table box. Hand the layout back to the table and keep the skin. */
table.profit { display: table; width: 100%; border-collapse: collapse; }
table.profit .profit-head, table.profit .profit-row { display: table-row; grid-template-columns: none; padding: 0; gap: 0; }
table.profit th, table.profit td { padding: 12px 14px; vertical-align: middle; }
table.profit .profit-row td { border-top: 1px solid var(--line); }

/* --- task templates (admin) -------------------------------------------- */
/* A template is a task shape, so its card reads like a task card: an accent
   stripe (the scoped client's accent when there is one), a title, then what it
   will produce. */
.tpl-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  align-items: start;
}
.tpl-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 8px;
  padding: 15px 16px 13px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--sh-card);
  transition: transform .18s var(--ease), border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.tpl-card::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent-line);
}
/* client-scoped card — its own accent arrives inline (client stripe, see the
   note in the accent block) */
.tpl-card[style*="--accent"]::before { background: var(--accent); }
[data-theme="light"] .tpl-card[style*="--accent"]::before { background: oklch(from var(--accent) min(l, .70) c h); }
.tpl-card:hover { transform: translateY(-3px); border-color: var(--line2); box-shadow: var(--sh); }
.tpl-card h3, .tpl-name {
  font-family: var(--f-head); font-weight: 700; font-size: 14.5px; line-height: 1.35;
  color: var(--paper); word-break: break-word;
}
.tpl-card .tpl-brief, .tpl-card p {
  font-size: 12.5px; color: var(--mut); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tpl-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; font-size: 11.5px; color: var(--faint); }

/* Scope pill — "All clients" or one client (tinted by that client's accent). */
.tpl-scope {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 2px 10px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11px; font-weight: 600;
  border: 1px dashed var(--line2); background: var(--panel2); color: var(--mut);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tpl-scope[style*="--accent"] {
  border-style: solid;
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, var(--panel));
  color: var(--paper);
  color: oklch(from var(--accent) max(l, .72) c h);
}
[data-theme="light"] .tpl-scope[style*="--accent"] { color: oklch(from var(--accent) min(l, .46) c h); }

.tpl-actions {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line);
}
.tpl-actions .grow { flex: 1; }
.tpl-actions .btn { flex: none; }
/* Destructive controls stay quiet until the card is engaged — but pointer:coarse
   has no hover, so touch keeps them visible. */
.tpl-actions .btn-icon { opacity: .5; transition: opacity .14s var(--ease), background .16s var(--ease), color .16s var(--ease); }
.tpl-card:hover .tpl-actions .btn-icon, .tpl-card:focus-within .tpl-actions .btn-icon { opacity: 1; }
@media (hover: none) { .tpl-actions .btn-icon { opacity: 1; } }

/* The editor — a form panel, not a card. Fields share the settings grid so the
   short ones (priority, channel, qty) pair up and the long ones span. */
.tpl-editor {
  display: grid; gap: 14px 16px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  padding: 16px; background: var(--panel2);
  border: 1px solid var(--line); border-radius: var(--r);
}
/* Direct children — same trap as .settings-grid: a nested .field is not a grid
   item and must keep its own 15px. */
.tpl-editor > .field { margin-bottom: 0; min-width: 0; }
.tpl-editor > .field.wide, .tpl-editor > .span-2, .tpl-editor > .tpl-lines, .tpl-editor > .tpl-foot { grid-column: 1 / -1; }
.tpl-editor .tpl-foot { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.tpl-editor .tpl-foot .grow { flex: 1; }

/*
 * Checklist lines. Numbered by a CSS counter so the editor previews the shape
 * of the checklist the template will create — whether the JS emits a row per
 * line or a single textarea.
 */
.tpl-lines { counter-reset: tplline; display: flex; flex-direction: column; gap: 7px; }
.tpl-lines > .lbl, .tpl-lines > label { margin-bottom: 0; }
.tpl-lines .txa { min-height: 104px; font-size: 13px; line-height: 1.7; }
.tpl-lines > div, .tpl-line {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.tpl-lines > div::before, .tpl-line::before {
  counter-increment: tplline; content: counter(tplline);
  flex: none; width: 20px; height: 20px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel3); color: var(--faint);
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
}
.tpl-lines > .lbl::before, .tpl-lines > label::before,
.tpl-lines > .hint::before, .tpl-lines > .tpl-lines-add::before { content: none; counter-increment: none; }
.tpl-lines .inp { flex: 1; min-width: 0; padding: 7px 11px; font-size: 13px; }
.tpl-lines .btn-icon { flex: none; width: 30px; height: 30px; }
.tpl-lines-add { display: flex; align-items: center; gap: 8px; }

/* --- bulk actions (list view) ------------------------------------------ */
/*
 * A selection bar that floats over the list rather than pushing it: the table
 * keeps its scroll position while rows are picked. It sits above the sidebar
 * (50) and below the modal layer (90), so an open dialog always wins; toasts
 * (120) still land on top to report "n changed, m skipped".
 */
.bulkbar {
  position: fixed; z-index: 80; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  width: max-content; max-width: min(920px, calc(100vw - 28px));
  padding: 9px 12px 9px 14px;
  background: var(--toast-bg); border: 1px solid var(--line2);
  border-radius: 999px; box-shadow: var(--sh-lg);
  animation: toastIn .22s var(--ease) both;
}
.bulkbar[hidden] { display: none !important; }

.bulk-count {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  padding: 4px 12px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--f-head); font-size: 12.5px; font-weight: 700;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.bulk-count b { font-size: 14px; }

.bulk-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.bulk-actions .sel, .bulk-actions .inp {
  width: auto; min-width: 124px; padding: 6px 28px 6px 11px;
  font-family: var(--f-head); font-size: 12.5px; font-weight: 600;
  background: var(--panel2);
}
.bulk-actions .btn { flex: none; padding: 6px 12px; font-size: 12.5px; border-radius: 999px; }
.bulk-actions .btn-icon { border-radius: 50%; }
/* the ✕ that clears the selection */
.bulkbar .bulk-clear { flex: none; margin-left: 2px; }

/*
 * Row selection. .tt-check is the checkbox in the first column (the header copy
 * is select-all and takes :indeterminate for a partial selection); rows the
 * member cannot edit render it disabled, matching the .tt-inline contract —
 * only editable rows can join an edit op.
 */
.tt-check {
  appearance: none; -webkit-appearance: none;
  flex: none; width: 16px; height: 16px; margin: 0; padding: 0; vertical-align: middle;
  border-radius: 5px; border: 1.5px solid var(--line2);
  background: var(--panel2); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--accent-ink); font-size: 11px; font-weight: 900; line-height: 1;
  transition: background .16s var(--ease), border-color .16s var(--ease), transform .12s var(--ease);
}
.tt-check:hover { border-color: var(--accent-line); transform: translateY(-1px); }
.tt-check:checked, .tt-check.on, .tt-check[aria-checked="true"] {
  background: var(--accent); border-color: var(--accent);
}
/* <input> ignores pseudo-elements, so the tick is painted; a <button> variant
   gets the same glyph via ::after. */
input.tt-check:checked {
  background:
    var(--accent)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11'%3E%3Cpath d='M2 5.8l2.3 2.3L9 3.2' stroke='%230B0B0C' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    center / 11px no-repeat;
}
input.tt-check:indeterminate {
  background:
    var(--accent)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11'%3E%3Cpath d='M2.5 5.5h6' stroke='%230B0B0C' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E")
    center / 11px no-repeat;
  border-color: var(--accent);
}
button.tt-check.on::after, button.tt-check[aria-checked="true"]::after { content: '✓'; }
.tt-check:disabled, .tt-check[aria-disabled="true"] {
  opacity: .32; cursor: not-allowed; transform: none; border-style: dashed;
}
/* the checkbox column is as narrow as its content */
.tt-th.tt-checkcell, .tt-cell.tt-checkcell,
.tt-th:has(> .tt-check), .tt-cell:has(> .tt-check) {
  width: 1%; padding-right: 6px; text-align: center;
}

/* A selected row is washed in the accent and marked with a solid edge, so the
   selection survives a hover moving down the table. */
.tt-row.selected > .tt-cell, .tt-row.selected > td { background: var(--accent-soft); }
.tt-row.selected:hover > .tt-cell, .tt-row.selected:hover > td { background: var(--accent-soft2); }
.tt-row.selected > .tt-cell:first-child, .tt-row.selected > td:first-child {
  box-shadow: inset 3px 0 0 var(--accent-line);
}
.tt-row.selected .tt-title button, .tt-row.selected .tt-title .tt-name { color: var(--paper); }

/* Rows this member may not edit: readable, but visibly not actionable, so a
   disabled checkbox isn't the only clue that bulk actions will skip them. */
.tt-row.tt-locked > .tt-cell, .tt-row.tt-locked > td { opacity: .62; }
.tt-row.tt-locked .tt-check { cursor: not-allowed; }
.tt-row.tt-locked > .tt-cell:first-child, .tt-row.tt-locked > td:first-child {
  box-shadow: inset 3px 0 0 var(--line2);
}

/* --- retainer engine: responsive --------------------------------------- */
@media (max-width: 1080px) {
  .profit { --profit-cols: minmax(120px, 1.6fr) 88px 76px 76px minmax(96px, 1.3fr) 76px 92px 82px; }
  .profit-head, .profit-row { gap: 10px; padding: 11px 13px; }
}

/*
 * Below 900px the eight columns stop fitting, so a row becomes a CARD: the
 * client name and the usage bar each take the full width and the numbers wrap
 * into a line under them. The column-label row has nothing left to label, so it
 * goes — each .profit-num can carry its own data-label instead.
 */
@media (max-width: 900px) {
  .profit-head { display: none; }
  .profit-row {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
    padding: 14px; grid-template-columns: none;
  }
  .profit-row > * { flex: 0 0 auto; }
  .profit-row > :first-child, .profit-name { flex: 1 1 100%; font-size: 14.5px; }
  .profit-bar { flex: 1 1 100%; order: 1; height: 8px; }
  .profit-num { order: 2; text-align: left; }
  .profit-num[data-label]::before {
    content: attr(data-label) ' ';
    font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase;
    color: var(--faint); font-weight: 700;
  }
  .profit-flag { order: 0; }
}

@media (max-width: 720px) {
  .cc-toolbar { padding: 10px; gap: 8px; }
  .cc-toolbar .sel, .cc-toolbar .inp { min-width: 0; flex: 1 1 132px; }
  .cc-toolbar .cal-nav, .cc-toolbar .cc-nav { margin-left: 0; flex: 1 1 100%; justify-content: space-between; }

  .content-cal .cal-day { min-height: 84px; padding: 4px; gap: 3px; }
  .cc-chip { padding: 2px 4px 2px 5px; font-size: 10px; gap: 4px; border-left-width: 2px; }
  .cc-chip.cc-state-published { border-left-width: 3px; }
  .cc-channel { display: none; }
  .cc-chip .cc-more { opacity: 1; }

  .tpl-grid { grid-template-columns: 1fr; }
  .tpl-editor { padding: 13px; gap: 12px; grid-template-columns: 1fr; }

  /* clear the bottom nav bar, exactly like .toasts */
  .bulkbar {
    bottom: 84px; left: 12px; right: 12px; transform: none;
    width: auto; max-width: none; border-radius: var(--r);
    justify-content: space-between;
  }
  .bulk-actions { flex: 1 1 100%; }
  .bulk-actions .sel, .bulk-actions .inp, .bulk-actions .btn { flex: 1 1 108px; min-width: 0; }
}

/*
 * Below 560px a publish chip keeps only its client colour and its state SHAPE —
 * the same collapse .cal-chip makes, and the reason the state was never carried
 * by hue alone. Tap target, tooltip and handler all survive.
 */
@media (max-width: 560px) {
  .content-cal .cal-day { min-height: 62px; }
  .content-cal .cal-items { flex-direction: row; flex-wrap: wrap; gap: 8px; }
  /* same 40x40 hit area around an 18px disc as .cal-chip — see the note there */
  .cc-chip::after { content: ''; position: absolute; inset: -11px; }
  .cc-chip {
    position: relative;
    width: 18px; height: 18px; min-width: 18px; padding: 0;
    border-radius: 50%; justify-content: center;
    background: color-mix(in srgb, var(--accent) 30%, var(--panel2));
    border: 1.5px solid var(--accent);
    border-color: oklch(from var(--accent) max(l, .62) c h);
  }
  .cc-chip.cc-state-draft { border-style: dashed; }
  .cc-chip-txt, .cc-chip > span:not(.cc-dot):not(.cc-channel), .cc-chip .cc-more { display: none; }
  .cc-more { flex-basis: 100%; }
  [data-theme="light"] .cc-chip {
    border-color: oklch(from var(--accent) min(l, .62) c h) !important;
    background: color-mix(in srgb, oklch(from var(--accent) min(l, .62) c h) 24%, var(--panel2));
  }
}

/* -------------------------------- 23. portal collaboration (phase 5) */
/*
 * The portal stops being a window and becomes a door. Team side, that is four
 * surfaces:
 *
 *   REQUESTS INBOX   work the client asked for, waiting to be accepted or
 *                    declined (.requests / .req-card and friends)
 *   NAV BADGE        how many are still 'new' (.nav-badge)
 *   SHARE TOGGLE     the control that promotes ONE internal team comment to
 *                    something the client can read (.cmt-share / .cmt-shared-tag)
 *   CLIENT SETTINGS  the three portal permissions and the contact book
 *                    (.client-toggles / .contacts / .contact-row)
 *
 * Nothing here invents a colour. Request status is mapped onto the status ramp
 * (--st-*), the same trick sections 19 and 22 use, so both themes get a legible
 * -ink tone for free; each status sets two locals, --lv (the vivid hue: stripes,
 * dots and washes, where 3:1 is the bar) and --lv-ink (the readable text tone).
 * The member's own colour only ever arrives through the derived --accent-*
 * tokens, or as a raw --accent FILL under --accent-ink — never as ink and never
 * as a thin stroke. That is the light-mode rule, and .nav-badge is the one place
 * in this section that touches the raw accent at all.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL, and here it matters more than anywhere else
 * in the app: two of these controls describe who can SEE something.
 *
 *   request status  new is a solid 3px stripe + a pulsing disc, accepted is a
 *                   solid stripe + a tick, declined is a DASHED stripe + a cross
 *                   on a dashed pill, and the whole card recedes.
 *   share toggle    off is an empty checkbox outline on a quiet pill; on is a
 *                   filled accent pill with a TICK. Box → tick is the same
 *                   affordance as .tt-check, so "shared" is readable in
 *                   greyscale, at 40% brightness and with any colour vision.
 *   shared comment  additionally carries a persistent ↗ SHARED tag in the head
 *                   AND an accent-washed bubble, so the state survives even when
 *                   the hover-gated toggle is not showing.
 *
 * The status modifier may sit on the card, on the pill, or arrive as
 * data-status; every mapping matches all three, so an inbox can never render
 * unstyled because the JS nested it the other way.
 */

.req-card--new,      .req-card.new,      .req-card[data-status="new"],
.req-status--new,    .req-status.new,    .req-status[data-status="new"]
  { --lv: var(--st-client);  --lv-ink: var(--st-client-ink); }

.req-card--accepted, .req-card.accepted, .req-card[data-status="accepted"],
.req-status--accepted, .req-status.accepted, .req-status[data-status="accepted"]
  { --lv: var(--st-done);    --lv-ink: var(--st-done-ink); }

.req-card--declined, .req-card.declined, .req-card[data-status="declined"],
.req-status--declined, .req-status.declined, .req-status[data-status="declined"]
  { --lv: var(--st-cancel);  --lv-ink: var(--st-cancel-ink); }

/* --- requests inbox ----------------------------------------------------- */
/*
 * A request is a short document (title + brief + who asked), so the cards are
 * wider than the template grid's and they stretch to a readable measure rather
 * than packing four to a row.
 */
.requests {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  align-items: start;
}
.requests:empty { display: none; }

/* --- bulk convert: tick several 'new' requests, assign them to one owner ----- */
.req-bulkbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 14px; padding: 10px 14px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--r);
}
.req-bulkbar .grow { flex: 1; }
.req-selall { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: 13px; color: var(--mut); }
.req-selall input { accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; margin: 0; }
.req-selcount { font-family: var(--f-head); font-weight: 600; font-size: 12.5px; color: var(--accent-fg); }

/* the per-card tick sits at the head, before the client chip */
.req-sel { flex: none; display: inline-flex; align-items: center; padding-top: 1px; cursor: pointer; }
.req-check { accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; margin: 0; }
.req-card.is-sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--sh-card); }

.req-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 9px;
  padding: 15px 16px 13px;
  background: var(--panel);
  border: 1px solid var(--line);
  /* the status rail — solid while the request is live, dashed once declined */
  border-left: 3px solid var(--lv, var(--line2));
  border-radius: var(--r);
  box-shadow: var(--sh-card);
  transition: transform .18s var(--ease), border-color .18s var(--ease),
              box-shadow .18s var(--ease), background .18s var(--ease);
}
.req-card:hover { transform: translateY(-2px); border-color: var(--line2); border-left-color: var(--lv, var(--line2)); box-shadow: var(--sh); }

/* An unhandled request is the only one that asks for anything, so it is the
   only one that is tinted and edge-lit. */
.req-card--new, .req-card.new, .req-card[data-status="new"] {
  background: color-mix(in srgb, var(--lv) 7%, var(--panel));
  border-color: color-mix(in srgb, var(--lv) 26%, var(--line));
  border-left-color: var(--lv);
}
/* Handled requests stay legible but stop competing: no tint, and declined ones
   recede a step further and swap the rail to dashed. */
.req-card--declined, .req-card.declined, .req-card[data-status="declined"] {
  border-left-style: dashed;
}
.req-card--declined .req-title, .req-card.declined .req-title,
.req-card[data-status="declined"] .req-title,
.req-card--declined .req-head h3, .req-card.declined .req-head h3 { color: var(--mut); }

.req-head {
  display: flex; align-items: flex-start; gap: 9px; flex-wrap: wrap;
  min-width: 0;
}
.req-head .grow { flex: 1 1 150px; min-width: 0; }
.req-head h3, .req-title {
  flex: 1 1 150px; min-width: 0;
  font-family: var(--f-head); font-weight: 700; font-size: 14.5px;
  line-height: 1.35; color: var(--paper); word-break: break-word;
}
/* the client this request belongs to — the chip carries the client accent
   inline, exactly like .tpl-scope (client stripe, see the accent block) */
.req-head .chip, .req-client { flex: none; max-width: 100%; }

.req-status {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  margin-left: auto; align-self: flex-start;
  padding: 3px 11px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  letter-spacing: .02em; text-transform: capitalize; white-space: nowrap;
  color: var(--lv-ink, var(--mut));
  background: color-mix(in srgb, var(--lv, var(--panel3)) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--lv, var(--line2)) 42%, transparent);
}
/* Shape per status: a disc that pings (new), a tick (accepted), a cross on a
   dashed pill (declined). */
.req-status::before {
  content: ''; flex: none;
  width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}
.req-status--new::before, .req-status.new::before, .req-status[data-status="new"]::before {
  animation: reqPing 2.4s infinite;
}
.req-status--accepted::before, .req-status.accepted::before, .req-status[data-status="accepted"]::before,
.req-status--declined::before, .req-status.declined::before, .req-status[data-status="declined"]::before {
  width: auto; height: auto; border-radius: 0; background: none;
  font-size: 11px; font-weight: 900; line-height: 1;
}
.req-status--accepted::before, .req-status.accepted::before, .req-status[data-status="accepted"]::before { content: '✓'; }
.req-status--declined::before, .req-status.declined::before, .req-status[data-status="declined"]::before { content: '✕'; }
.req-status--declined, .req-status.declined, .req-status[data-status="declined"] { border-style: dashed; }

@keyframes reqPing {
  0%   { box-shadow: 0 0 0 0 currentColor; }
  70%  { box-shadow: 0 0 0 5px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/*
 * The brief. A request can be one line or fifteen, and the team has to read all
 * of it to decide — so it scrolls inside the card instead of being clamped away.
 */
.req-body {
  font-size: 13px; line-height: 1.6; color: var(--mut);
  white-space: pre-wrap; word-break: break-word;
  max-height: 168px; overflow-y: auto;
  padding: 10px 12px; border-radius: 10px;
  background: var(--panel2); border: 1px solid var(--line);
}
.req-body:empty, .req-body.empty { color: var(--faint); font-style: italic; }
.req-body:empty { display: none; }

/* The client's answers to their own form. Laid out as label/value pairs
   rather than dropped into .req-body: they are structured data, and running
   them together as prose loses the one thing the form was for. */
.req-answers {
  display: flex; flex-direction: column; gap: 1px;
  border-radius: 10px; overflow: hidden;
  background: var(--panel2); border: 1px solid var(--line);
}
/* The row gap is 2px, not 12px: request cards sit in a narrow column, and a
   long answer that wraps under its label should cost a line, not a paragraph. */
.req-answer {
  display: flex; gap: 2px 12px; align-items: baseline; flex-wrap: wrap;
  padding: 7px 12px; font-size: 12.5px; line-height: 1.5;
}
.req-answer + .req-answer { border-top: 1px solid var(--line); }
/* Wide enough to line the labels up as a column, narrow enough that a short
   answer still shares the line in a ~320px card. */
.req-answer-lbl {
  flex: 0 0 auto; min-width: 104px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
}
.req-answer-val { flex: 1 1 140px; color: var(--paper); word-break: break-word; }
.req-answer-tags {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 9px 12px; border-top: 1px solid var(--line);
}

.req-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
  font-size: 11.5px; color: var(--faint); line-height: 1.5;
}
.req-meta b, .req-meta strong { font-family: var(--f-head); font-weight: 600; color: var(--mut); }
.req-meta a { color: var(--accent-fg); text-decoration: underline; text-underline-offset: 2px; word-break: break-word; }
/* the decline note / handled-by line, when the JS renders one */
.req-meta .req-note, .req-note {
  flex-basis: 100%; color: var(--mut); font-style: italic; line-height: 1.55;
  padding-left: 9px; border-left: 2px solid var(--line2);
}

.req-actions {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line);
}
.req-actions .grow { flex: 1; }
.req-actions .btn { flex: none; }
/* Destructive controls stay quiet until the card is engaged — pointer:coarse
   has no hover, so touch keeps them visible (same contract as .tpl-actions). */
.req-actions .btn-icon { opacity: .5; transition: opacity .14s var(--ease), background .16s var(--ease), color .16s var(--ease); }
.req-card:hover .req-actions .btn-icon, .req-card:focus-within .req-actions .btn-icon { opacity: 1; }
@media (hover: none) { .req-actions .btn-icon { opacity: 1; } }

/* --- nav count badge ---------------------------------------------------- */
/*
 * .nav-count (section 3) is a passive tally — how many cards are on the board.
 * .nav-badge is an ALERT: n client requests are sitting unanswered. So it is the
 * one solid-accent element in this section, which is legal in both themes
 * because the raw accent is only ever a FILL under --accent-ink.
 */
.nav-badge {
  margin-left: auto; flex: none;
  min-width: 19px; height: 19px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--f-head); font-size: 10.5px; font-weight: 800;
  font-variant-numeric: tabular-nums; line-height: 1;
  box-shadow: 0 0 0 1px var(--ring);
}
/* Zero requests must not leave a floating dot: the JS can either hide it or
   empty it, and both disappear. */
.nav-badge:empty, .nav-badge[hidden] { display: none !important; }

/* --- share a team comment with the client ------------------------------- */
/*
 * THE control of this phase. Team comments are internal by default; this toggle
 * is the only thing that changes that, so its state is carried three ways —
 * shape (empty box → tick), fill (quiet panel → accent), and a separate,
 * always-visible ↗ SHARED tag on the comment itself.
 *
 * It is dimmed rather than hidden when the comment is idle: a member has to be
 * able to SEE that sharing exists without hovering every bubble, but it must not
 * shout over the thread.
 */
.cmt-share {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 2px 9px 2px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel2); color: var(--faint);
  font-family: var(--f-head); font-size: 10px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
  cursor: pointer; opacity: .55;
  transition: opacity .14s var(--ease), color .14s var(--ease),
              background .16s var(--ease), border-color .16s var(--ease), transform .12s var(--ease);
}
/* the state indicator: an empty checkbox off, a tick on */
.cmt-share::before {
  content: ''; flex: none;
  width: 11px; height: 11px; border-radius: 3px;
  border: 1.5px solid var(--line2); background: transparent;
  transition: border-color .14s var(--ease);
}
.cmt-share:hover { opacity: 1; color: var(--paper); background: var(--panel3); border-color: var(--line2); transform: translateY(-1px); }
.cmt-share:hover::before { border-color: var(--accent-line); }
.cmt-share:active { transform: translateY(0); }
.cmt-share:focus-visible { opacity: 1; outline: 2px solid var(--accent-fg); outline-offset: 2px; }

.cmt-share.on {
  opacity: 1;
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.cmt-share.on::before {
  content: '✓'; width: auto; height: auto;
  border: 0; border-radius: 0; background: none;
  font-size: 11px; font-weight: 900; line-height: 1;
}
.cmt-share.on:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); box-shadow: var(--glow); }
.cmt-share:disabled { opacity: .3; cursor: default; transform: none; }

/* Wherever the JS hangs it — loose in the head, or beside edit/delete. */
.cmt-head .cmt-share { margin-left: auto; align-self: center; }
.cmt-actions .cmt-share { opacity: 1; }
/* .cmt-actions is hover-gated (section 20). A comment that IS shared must not
   hide that fact, so an on-toggle inside it keeps the row open. Engines without
   :has still show the ↗ SHARED tag, which is never gated. */
.cmt-actions:has(.cmt-share.on) { opacity: 1; }
@media (hover: none) { .cmt-share { opacity: 1; } }

/*
 * The persistent marker on a shared comment — the mirror of the CLIENT badge on
 * .cmt.client, so a thread reads at a glance: plain = internal, ↗ SHARED = the
 * client can see this, CLIENT = the client wrote it.
 */
.cmt-shared-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 7px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-fg);
  border: 1px solid var(--accent-soft2);
  font-family: var(--f-head); font-size: 9.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
}
.cmt-shared-tag::before { content: '↗'; font-size: 10.5px; letter-spacing: 0; }

/* The bubble itself picks up a wash, so the shared/internal split survives a
   collapsed head on a narrow screen. Both hooks work: a modifier on the comment
   or the tag alone. */
.cmt.shared .cmt-text,
.cmt:has(.cmt-shared-tag) .cmt-text {
  border-color: var(--accent-soft2);
  background: color-mix(in srgb, var(--accent) 7%, var(--panel2));
}
/* A client's own comment already owns the amber treatment (it is client-visible
   by definition, so it may carry the tag too) — it must never be repainted as
   "shared by us". Restated after the wash, at equal specificity, so source order
   settles it. */
.cmt.client .cmt-text {
  border-color: color-mix(in srgb, var(--st-client) 35%, transparent);
  background: color-mix(in srgb, var(--st-client) 8%, transparent);
}

/* --- client portal settings + contacts ---------------------------------- */
/*
 * Three switches that decide what the portal will accept: requests, comments,
 * and the delivery email. Each is a tile rather than a bare row, because these
 * are permissions — they deserve to look like settings, not like a checklist.
 */
.client-toggles {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(212px, 1fr));
  align-items: start;
}
.client-toggles > * { min-width: 0; }
/* .switch is a <label>, and the base label rule carries a 6px bottom margin —
   inside a gapped grid that reads as an uneven column, so it is zeroed here. */
.client-toggles .switch {
  display: flex; align-items: center; gap: 10px; width: 100%; margin-bottom: 0;
  padding: 11px 13px; border-radius: 12px;
  background: var(--panel2); border: 1px solid var(--line);
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.client-toggles .switch:hover { background: var(--panel3); border-color: var(--line2); }
.client-toggles .switch-label { flex: 1; min-width: 0; }
/* An enabled permission lights its tile. The switch already states the value on
   its own, so engines without :has lose nothing but the wash. */
.client-toggles .switch:has(input:checked) {
  background: var(--accent-soft); border-color: var(--accent-line);
}
.client-toggles .switch:has(input:focus-visible) { outline: 2px solid var(--accent-fg); outline-offset: 2px; }
.client-toggles .hint { grid-column: 1 / -1; margin-top: 0; }

/*
 * The contact book. Who at the client hears about a delivery — so the row's
 * primary fact is the address, and "receives updates" is carried by a rail on
 * the left edge (a position cue, not a hue) as well as by its chip.
 */
.contacts { display: flex; flex-direction: column; gap: 8px; }
.contacts:empty { display: none; }

.contact-row {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 10px 12px; border-radius: 12px;
  background: var(--panel2); border: 1px solid var(--line);
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.contact-row:hover { background: var(--panel3); border-color: var(--line2); }
.contact-row > .grow, .contact-main { flex: 1 1 180px; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.contact-row strong, .contact-row b, .contact-name {
  font-family: var(--f-head); font-weight: 600; font-size: 13.5px; color: var(--paper);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.contact-row small, .contact-sub, .contact-email {
  font-size: 11.5px; color: var(--faint); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.contact-row a { color: var(--accent-fg); }
.contact-row .chip { flex: none; }
/* inline add/edit — the row becomes a compact form without changing shape */
.contact-row .inp, .contact-row .sel { flex: 1 1 148px; width: auto; min-width: 0; padding: 7px 11px; font-size: 13px; }
.contact-row .switch { flex: none; margin-bottom: 0; }
.contact-row .field, .contact-row label, .contact-row .lbl { margin-bottom: 0; }

/* receives_updates — the rail plus whatever chip the JS renders */
.contact-row.updates, .contact-row[data-updates="1"] {
  padding-left: 15px; box-shadow: inset 3px 0 0 var(--accent-line);
}

/* a replaced final result, and the fold that holds them */
.filerow.is-old { opacity: .62; }
.filerow.is-old .file-name { text-decoration: line-through; text-decoration-color: var(--faint); }
.old-finals { margin-top: 8px; }
.old-finals > summary { cursor: pointer; font-size: 12px; color: var(--mut); list-style: none; }
.old-finals > summary::-webkit-details-marker { display: none; }
.old-finals > summary::before { content: '▸ '; }
.old-finals[open] > summary::before { content: '▾ '; }
.old-finals .filelist { margin-top: 6px; }

/* client logo on the form */
.logo-field { display: flex; flex-direction: column; gap: 8px; }
.logo-preview { max-height: 56px; max-width: 220px; object-fit: contain; align-self: flex-start;
  padding: 6px 8px; border-radius: 8px; background: var(--panel3); border: 1px solid var(--line); }

/* when they last opened the portal as themselves; a key marks their own passcode */
.contact-seen { flex: none; font-size: 11.5px; color: var(--mut); white-space: nowrap; }
.contact-seen--never { color: var(--faint); font-style: italic; }
.contact-key { margin-left: 6px; font-size: 12px; color: var(--accent-fg); }

/* "last opened" on the portal panel */
.portal-seen {
  display: flex; align-items: center; gap: 8px; margin: 0 0 12px;
  font-size: 12.5px; color: var(--mut);
}
.portal-seen b { color: var(--paper); font-weight: 600; }
.portal-seen-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }
.portal-seen--stale .portal-seen-dot { background: #fbbf24; }
.portal-seen--stale { color: #f6e7bd; }
.portal-seen--never .portal-seen-dot { background: var(--faint); }

/* a client comment about one specific file */
.cmt-about { font-size: 12px; color: var(--mut); margin: 0 0 4px; }
.cmt-about b { color: var(--paper); font-weight: 600; }
.cmt-shared-tag.is-seen { color: var(--accent-fg); }

/* files a client attached to a request */
.req-files { display: flex; flex-wrap: wrap; gap: 6px; }
.req-file {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 5px 9px; border-radius: 8px; font-size: 12px; text-decoration: none;
  background: var(--panel3); border: 1px solid var(--line); color: var(--paper);
}
.req-file:hover { border-color: var(--line2); }
.req-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.req-file-size { color: var(--faint); flex: none; }

.contact-actions {
  display: flex; align-items: center; gap: 6px; flex: none; margin-left: auto;
}
.contact-actions .btn { flex: none; }
.contact-actions .btn-icon { opacity: .55; transition: opacity .14s var(--ease), background .16s var(--ease), color .16s var(--ease); }
.contact-row:hover .contact-actions .btn-icon, .contact-row:focus-within .contact-actions .btn-icon { opacity: 1; }
@media (hover: none) { .contact-actions .btn-icon { opacity: 1; } }

/* --- portal collaboration: responsive ----------------------------------- */
@media (max-width: 720px) {
  .requests { grid-template-columns: 1fr; }
  .req-card { padding: 13px 14px 12px; gap: 8px; }
  .req-body { max-height: 132px; }
  /* one row of full-width actions beats three cramped buttons */
  .req-actions .btn { flex: 1 1 118px; }
  .req-actions .btn-icon { flex: none; }

  /*
   * The sidebar is a bottom bar here and .nav-count is dropped (section 18) —
   * but an unanswered client request is exactly the thing that must survive the
   * collapse, so the badge moves onto the icon instead of disappearing.
   */
  .nav-badge {
    position: absolute; top: 3px; left: 50%; margin-left: 5px;
    min-width: 16px; height: 16px; padding: 0 4px; font-size: 9px;
    /* --ink is the page colour in BOTH modes, and the bottom bar is that colour
       under a blur — so a 2px halo in it cuts the badge off the icon cleanly. */
    box-shadow: 0 0 0 2px var(--ink);
  }

  .client-toggles { grid-template-columns: 1fr; }
  .contact-row { gap: 8px 10px; }
  .contact-actions { margin-left: auto; }
}

@media (max-width: 480px) {
  .req-head h3, .req-title { flex-basis: 100%; }
  .req-status { margin-left: 0; }
  .req-meta { gap: 3px 10px; }
  /* the toggle drops under the author line rather than squeezing it */
  .cmt-head .cmt-share { margin-left: 0; }
  .contact-row > .grow, .contact-main { flex-basis: 100%; }
  .contact-row .inp, .contact-row .sel { flex-basis: 100%; }
}

/* ---------------------------- 24. roles + feature flags (phase 6) */
/*
 * Two surfaces arrive with the superadmin role:
 *
 *   .feature-groups   the Settings > Features panel. 18 capabilities in five
 *                     groups, each row reading label -> help -> switch. Turning
 *                     one off hides it for the whole agency, so the panel is
 *                     built as a list of consequential settings, not a row of
 *                     checkboxes: grouped cards, one row per capability, the
 *                     help text always visible rather than hidden behind a
 *                     tooltip.
 *
 *   .role-pill        the rank badge, in the topbar identity chip and in the
 *                     members table.
 *
 * Both are markup-tolerant: the rows work whether app.js emits
 * (label, help, switch) as three children or wraps the text in a block, and
 * every rule that could inherit the global `label` style (block, uppercase,
 * 11.5px, --mut, 6px bottom margin) resets it explicitly, because these
 * elements are labels wrapping their own checkbox.
 */

/* --- the feature panel -------------------------------------------------- */

.feature-groups {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  align-items: start;
}
.feature-groups > * { min-width: 0; }

.feature-group {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel2);
  overflow: hidden;              /* keeps the first/last row inside the radius */
}

.feature-group-title {
  display: flex; align-items: center; gap: 9px;
  margin: 0; padding: 11px 15px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--mut);
  background: var(--panel); border-bottom: 1px solid var(--line);
}
/* A stroke-weight accent rail, so the group headers read as a set at a glance
   without spending a text colour on them. */
.feature-group-title::before {
  content: ''; flex: none;
  width: 3px; height: 12px; border-radius: 2px;
  background: var(--accent-line);
}

/*
 * One capability. Flex + order rather than a two-column grid: with order the
 * same rule places (label, help, switch) emitted flat — label and switch share
 * the first line, the help wraps full-width beneath — AND the wrapped form,
 * where a text block is the only thing left of the switch.
 */
.feature-row {
  display: flex; flex-wrap: wrap; align-items: center;
  column-gap: 14px; row-gap: 3px;
  padding: 12px 15px;
  margin: 0;                                    /* label reset */
  font-size: 14px; letter-spacing: 0; text-transform: none; color: var(--paper);
  cursor: pointer;
  transition: background .16s var(--ease);
}
.feature-row + .feature-row { border-top: 1px solid var(--line); }
.feature-row:hover { background: var(--panel3); }
.feature-row > * { min-width: 0; flex: 1 1 auto; }

.feature-label {
  order: 1; margin: 0;
  font-family: var(--f-head); font-size: 13.5px; font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--paper);
  line-height: 1.35;
}
.feature-help {
  order: 3; flex: 1 1 100%; margin: 0;
  font-size: 12px; line-height: 1.5; color: var(--faint);
  letter-spacing: 0; text-transform: none; font-weight: 400;
}
/* The switch keeps to the right edge on the first line whichever way the row
   is assembled, and never shrinks. */
.feature-switch {
  order: 2; flex: none; margin: 0 0 0 auto;
  display: inline-flex; align-items: center; gap: 10px;
  cursor: pointer;
}
.feature-switch .switch { margin-bottom: 0; }   /* if it wraps one rather than being one */

/*
 * OFF is the state worth spotting in a list of eighteen, so it gets a second
 * cue beyond the knob position: a quiet rail on the leading edge (matching the
 * inset-rail idiom used by .contact-row.updates) and a stepped-back label. The
 * 15px padding already leaves room for the 3px rail, so nothing shifts.
 * Engines without :has simply lose the extra cue; the switch still states it.
 */
.feature-row:has(input:not(:checked)) { box-shadow: inset 3px 0 0 var(--line2); }
.feature-row:has(input:not(:checked)) .feature-label { color: var(--mut); }
/* Inset ring: the group clips its rows, so an outset outline would be cut off. */
.feature-row:has(input:focus-visible) { outline: 2px solid var(--accent-fg); outline-offset: -2px; }

/* --- permissions grid (Settings) --------------------------------------- */
.perm-grid { display: flex; flex-direction: column; }
.perm-row {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 13px 0; border-top: 1px solid var(--line);
}
.perm-row:first-child { border-top: 0; }
.perm-info { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.perm-name { font-family: var(--f-head); font-weight: 600; font-size: 13.5px; color: var(--paper); }
.perm-help { font-size: 12px; color: var(--mut); line-height: 1.45; }
.perm-roles { display: flex; gap: 8px; flex-wrap: wrap; }
.perm-chk {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mut);
  background: var(--panel2); border: 1px solid var(--line); border-radius: 8px;
  padding: 5px 9px; cursor: pointer; user-select: none;
}
.perm-chk:has(input:checked) { color: var(--paper); border-color: var(--line2); background: var(--panel3); }
.perm-chk input { accent-color: var(--accent); width: 14px; height: 14px; margin: 0; cursor: pointer; }
.perm-chk.perm-locked { opacity: .6; cursor: default; }
.perm-chk.perm-locked input { cursor: default; }

/* --- role pills --------------------------------------------------------- */
/*
 * superadmin vs admin must not rest on hue alone: the two differ in FILL (a
 * solid accent chip vs an outlined one) and in the SHAPE of their leading
 * marker (a filled diamond vs a hollow ring), so they stay distinct in
 * greyscale, under any of the six member accents, and for a colour-blind
 * reader. Member is the unmarked default.
 *
 * The solid variant uses the raw --accent only as a fill under --accent-ink,
 * per the accent contract at the top of this file.
 */
.role-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;
  background: var(--panel3); color: var(--mut);
  border: 1px solid var(--line2);
  vertical-align: middle;
}
.role-pill::before { content: none; }

/* admin — outlined, hollow ring */
.role-pill--admin {
  background: var(--accent-soft);
  color: var(--accent-fg);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.role-pill--admin::before {
  content: ''; flex: none;
  width: 8px; height: 8px; border-radius: 50%;
  border: 2px solid currentColor;
}

/* superadmin — solid, filled diamond (the same pip geometry as .prio-pip, so
   its rotated corners sit inside the existing gap and padding) */
.role-pill--super {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
  padding-right: 10px;
}
.role-pill--super::before {
  content: ''; flex: none;
  width: 8px; height: 8px; border-radius: 2px;
  background: currentColor; transform: rotate(45deg);
}

/* In the identity chip the pill replaces the quiet .userchip-role caption, so
   it drops a size to keep the 62px topbar calm. */
.userchip .role-pill { font-size: 9px; padding: 1px 7px; gap: 5px; margin-top: 2px; }
.userchip .role-pill::before { width: 6px; height: 6px; border-width: 1.5px; }

/* In the members table it sits on the same baseline as the old .role-tag. */
table.tbl .role-pill { font-size: 10px; }

/* --- roles + features: responsive --------------------------------------- */
@media (max-width: 720px) {
  /* the chip loses its name and role caption here — the pill goes with them */
  .userchip .role-pill { display: none; }
  .feature-groups { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
  .feature-group-title { padding: 10px 13px; }
  .feature-row { padding: 11px 13px; column-gap: 10px; }
  .feature-help { font-size: 11.5px; }
}

/* ------------------------- 25. nav + focus + tutorial (phase 7) */
/*
 * Three surfaces, one idea: fewer places, more clarity.
 *
 *   VIEW TABS    Board / List / Calendar / Content stop being four destinations
 *                and become four LENSES on one pipeline. The strip therefore has
 *                to read as ONE control — a tray holding chips — not as four
 *                buttons that happen to sit near each other.
 *   TEAM'S FOCUS Today / This week / This month. A calm surface: no colour
 *                shouting, generous whitespace, client groups that scan top to
 *                bottom. It borrows .mw-section's card shape so a focus list and
 *                My Work feel like the same object.
 *   GUIDE        An index beside an article. Optimised for READING: a ~72ch
 *                measure, real step numbering, and an index that stays put while
 *                the article scrolls.
 *
 * Everything reuses the tokens above. The raw --accent appears only as a fill
 * under --accent-ink or as a >=2px stroke (--accent-line); as ink it is always
 * --accent-fg, so light mode stays legible under the rule at the top of the file.
 *
 * Selected state is never colour alone. The active tab is LIFTED (--lift + a
 * shadow), heavier, and carries an accent bar; the active index item is heavier
 * and carries an accent rail. Both survive greyscale and any of the six accents.
 */

/* --- the lens tab strip -------------------------------------------------- */
/*
 * One tray, chips inside it. The tray scrolls sideways rather than wrapping, so
 * on a phone the four lenses stay on one line and the page never grows a
 * horizontal scrollbar of its own (the same contract as .board / .tt-scroll).
 */
.viewtabs {
  display: flex;
  align-items: stretch;
  gap: 3px;
  padding: 4px;
  margin-bottom: 16px;
  background-color: var(--panel2);
  border: 1px solid var(--line);
  border-radius: calc(var(--r-sm) + 5px);
  box-shadow: var(--sh-card);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  /* the tray is a control, not a list of links */
  width: max-content;
  max-width: 100%;
}
.viewtabs::-webkit-scrollbar { display: none; }

/*
 * SCROLL AFFORDANCE. These trays hide their scrollbar outright, so at 375–768px
 * there was nothing on screen saying more lenses exist off the right edge (a
 * scroll-snap point is not a visual cue). The classic four-layer shadow does it
 * with no markup and no script: two `local` gradients painted in the tray colour
 * scroll WITH the content and cover the shadow when there is nothing more that
 * way, two `scroll` shadows stay pinned to the edges. It self-hides when the
 * tray is not overflowing, so it costs nothing at 1280px+. Requires the tray's
 * flat fill to be set as background-COLOR, which it now is.
 */
.viewtabs, .fin-tabs {
  background-image:
    linear-gradient(to right, var(--panel2), transparent) left / 20px 100% no-repeat local,
    linear-gradient(to left,  var(--panel2), transparent) right / 20px 100% no-repeat local,
    linear-gradient(to right, var(--sh-c2), transparent)  left / 10px 100% no-repeat scroll,
    linear-gradient(to left,  var(--sh-c2), transparent)  right / 10px 100% no-repeat scroll;
}

.viewtab {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px;
  padding: 9px 15px 11px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--mut);
  font-family: var(--f-head);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  scroll-snap-align: start;
  transition: color .16s var(--ease), background .16s var(--ease),
              border-color .16s var(--ease), box-shadow .18s var(--ease);
}
.viewtab .ico { font-size: 14px; line-height: 1; flex: none; }
.viewtab:hover {
  color: var(--paper);
  background: color-mix(in srgb, var(--lift) 60%, transparent);
}
.viewtab:active { transform: none; }

/* Selected: lifted surface + heavier type + an accent bar. Three cues, only one
   of them colour. */
.viewtab.on {
  background: var(--lift);
  border-color: var(--line);
  color: var(--paper);
  font-weight: 700;
  box-shadow: var(--sh-sm);
}
.viewtab.on::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 4px;
  transform: translateX(-50%);
  width: 20px;
  height: 2.5px;
  border-radius: 999px;
  background: var(--accent-line);
}
.viewtab.on .ico { color: var(--accent-fg); }
.viewtab:focus-visible { outline-offset: -2px; }

/* A count riding along in a tab (e.g. Content · 12) stays quiet. */
.viewtab .nav-count, .viewtab .tt-count { margin-left: 0; }
.viewtab.on .nav-count, .viewtab.on .tt-count {
  background: var(--accent-soft2); color: var(--accent-fg);
}

/*
 * The one-line explanation. It is written to work in EITHER position: inside a
 * tab (it wraps onto its own line, since .viewtab wraps) or under the strip as
 * the selected lens's description.
 */
.viewtab-hint {
  display: block;
  font-family: var(--f-body);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.5;
  color: var(--faint);
  white-space: normal;
}
/* A tab that carries its own hint stops being a flex row: as a block, the icon
   and label flow inline and the hint (a block) breaks onto the second line. A
   flex-basis:100% child cannot do that here — the tray is shrink-to-fit, so the
   percentage has no width to resolve against. */
.viewtab:has(.viewtab-hint) {
  display: block;
  max-width: 260px;
  white-space: normal;
  text-align: left;
  padding-top: 8px;
}
.viewtab:has(.viewtab-hint) .ico { margin-right: 7px; }
.viewtab .viewtab-hint {
  margin-top: 2px;
  font-size: 11.5px;
}
.viewtab.on .viewtab-hint { color: var(--mut); }
/* Under the strip: pull up into the tray's own margin so the two read as a pair.
   Finance's lens hint is the same control saying the same kind of thing, so it
   shares the rule rather than hard-coding its own spacing inline. */
.viewtabs + .viewtab-hint, .fin-tabs + .viewtab-hint {
  margin: -8px 0 18px;
  padding-left: 4px;
  max-width: 70ch;
}

/* --- the one-line description under a view title ------------------------- */
/*
 * Every destination says what it is. Same voice as .view-head .sub, but this one
 * can also stand on its own line under the whole header block.
 */
.view-desc {
  color: var(--mut);
  font-size: 13.5px;
  line-height: 1.5;
  max-width: 74ch;
  margin-top: 3px;
}
.view-head .view-desc { margin-top: 3px; }
/* .view-head already carries 22px of bottom margin; a sibling description sits
   inside that gap instead of adding a second one. */
.view-head + .view-desc { margin: -16px 0 18px; }
.view-desc b, .view-desc strong { color: var(--paper); font-weight: 600; }

/* --- Team's Focus -------------------------------------------------------- */
/*
 * A focus surface earns its name by what it LEAVES OUT, not by how narrow it is.
 * The old 900px cap abandoned 160–280px of the column on an ordinary laptop and
 * far more on a wide monitor, and the content is client cards of short rows —
 * there is no reading measure here to protect (the one screen that has one, the
 * guide, keeps its 72ch). The column fills the view, groups sit side by side
 * once two fit, and a very wide monitor still gets a sane ceiling.
 */
.focus {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 1400px;
}
/*
 * paintFocus() writes the client groups into #focus-slot, so .focus's own gap
 * had a single child to space and the cards stacked with their borders doubled
 * up. The slot is the column — and, above ~880px of it, two columns.
 */
.focus > #focus-slot {
  display: grid; gap: inherit; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
}
.focus > #focus-slot > .focus-empty, .focus > #focus-slot > .empty { grid-column: 1 / -1; }

.focus-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 2px;
}
.focus-head .grow { flex: 1; }
/*
 * .focus-head was written as a SECTION header and is also used as the page
 * header, where it collided with .view-head: equal specificity, later in the
 * file, so the Focus page title rendered at 17px against every other view's
 * 26px, and its subtitle was pulled 6px UP into the title instead of sitting
 * 3px below it. Scoped off the page title, both collisions go away.
 */
.focus-sec .focus-head h1, .focus-head:not(.view-head) h1 {
  font-family: var(--f-head); font-size: 17px; font-weight: 700; letter-spacing: -.02em;
}
/* The description always gets its own line under the title row. min-width does
   the wrapping (flex-basis alone loses to .view-desc's own max-width, which
   shrinks the hypothetical size back onto the first line). */
.focus-head .view-desc, .focus-head .sub {
  flex: 0 0 100%; min-width: 100%;
}
.focus-head:not(.view-head) .view-desc, .focus-head:not(.view-head) .sub {
  max-width: none; margin: -6px 0 0;
}

/* Mine / Everyone — the same tray-and-chip language as the lens tabs, one size
   down. Works with .on, [aria-pressed] or [aria-selected] on the options. */
.focus-scope {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: none;
  padding: 3px;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.focus-scope button, .focus-scope > label, .focus-scope .scope-opt {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--f-head);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--mut);
  white-space: nowrap;
  margin: 0;
  cursor: pointer;
  transition: color .16s var(--ease), background .16s var(--ease), box-shadow .18s var(--ease);
}
.focus-scope button:hover, .focus-scope .scope-opt:hover, .focus-scope > label:hover { color: var(--paper); }
.focus-scope .on,
.focus-scope [aria-pressed="true"],
.focus-scope [aria-selected="true"],
.focus-scope input:checked + label {
  background: var(--lift);
  border-color: var(--line);
  color: var(--paper);
  font-weight: 700;
  box-shadow: var(--sh-sm);
}
.focus-scope input { position: absolute; opacity: 0; width: 0; height: 0; }

/* A client group. Same card as .mw-section, so a focus list and My Work are
   visibly the same object seen twice. */
.focus-sec {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
  overflow: hidden;
}
.focus-sec-title {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  font-family: var(--f-head);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--mut);
}
.focus-sec-title .grow { flex: 1; }
.focus-sec-title .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.focus-sec-title .chip, .focus-sec-title .chip-client { text-transform: none; letter-spacing: 0; }

/* The count is the point of a focus surface, so it is legible, tabular and
   never truncated. */
.focus-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; padding: 1px 8px;
  border-radius: 999px;
  background: var(--panel3);
  color: var(--paper);
  font-family: var(--f-head);
  font-size: 11px; font-weight: 700; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  flex: none;
}
.focus-head .focus-count {
  min-width: 26px; padding: 2px 10px; font-size: 12px;
  background: var(--accent-soft); color: var(--accent-fg);
}

/* Overdue reads first — a quiet danger tint on the header only, exactly as
   .mw-section.overdue does. */
.focus-sec.overdue .focus-sec-title,
.focus-sec[data-kind="overdue"] .focus-sec-title {
  color: var(--danger);
  border-bottom-color: color-mix(in srgb, var(--danger) 28%, var(--line));
}
.focus-sec.overdue .focus-count,
.focus-sec[data-kind="overdue"] .focus-count {
  background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger);
}

/* Rows: the existing renderers drop straight in. .mw-row is edge-to-edge inside
   the card; .tcard is a card, so it gets breathing room instead. */
.focus-sec .mw-row:last-child { border-bottom: none; }
.focus-sec > .tcard, .focus-sec .focus-rows > .tcard { margin: 0 12px 10px; }
.focus-sec > .tcard:first-of-type, .focus-sec .focus-rows > .tcard:first-of-type { margin-top: 12px; }
.focus-sec .tt-scroll { border: none; border-radius: 0; box-shadow: none; }

/* A calm empty state — dashed, roomy, no alarm colour. Nothing due is good news.
   The card itself is the shared one from section 10; only the in-card variant is
   local, because inside a .focus-sec the card is already drawn. */
.focus-sec .focus-empty {
  border: none; border-radius: 0; background: transparent; padding: 30px 20px;
}

/* --- the guide ----------------------------------------------------------- */
/*
 * Index left, article right. The index is sticky under the topbar; the article
 * holds a reading measure rather than filling the window, because this is the
 * one screen in the app people actually READ.
 */
.guide {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}

.guide-index {
  position: sticky;
  top: calc(var(--topbar-h) + 16px);
  max-height: calc(100vh - var(--topbar-h) - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
  scrollbar-width: thin;
}
.guide-index .sec-title { margin: 6px 9px 4px; }

/* The filter sticks to the top of the INDEX while the index scrolls — that is
   the only place the plate and the sticky belong. Scoped to a child of
   .guide-index because renderGuide() actually puts it in the .view-head, where a
   bare .guide-filter drew an opaque --panel rectangle behind itself (the one
   header control in the app with a background plate) and stuck to nothing. The
   reset below only ever matched a direct child of .guide, which it never is. */
.guide-index > .guide-filter {
  position: sticky; top: 0; z-index: 1;
  background: var(--panel);
  padding-bottom: 8px;
  margin-bottom: 2px;
}
input.guide-filter, .guide-filter > .inp, .guide-filter input {
  width: 100%;
  padding: 8px 12px;
  font-size: 13px;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--paper);
  outline: none;
  transition: border-color .16s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
}
.guide-filter input:focus, input.guide-filter:focus, .guide-filter > .inp:focus {
  border-color: var(--accent-line);
  background: var(--focus-bg);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.guide > .guide-filter {
  grid-column: 1 / -1;
  position: static;
  background: transparent;
  padding: 0;
  margin: 0;
  max-width: 420px;
}

.guide-idx-item {
  position: relative;
  /* flex: none is LOad-bearing. .guide-index is a column flex container with a
     max-height, so these 35 items are flex items on the main axis — at the
     default flex-shrink: 1 the browser squashes every one of them below its
     content height to make the list fit, and `overflow: hidden` then cuts each
     label mid-line. The result was 23 of 35 rows showing a sliver of a second
     line: rows 17px tall holding 36-54px of text. The list is meant to SCROLL,
     not compress. */
  flex: none;
  display: flex; align-items: flex-start; gap: 9px;
  width: 100%;
  min-height: 34px;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  font-family: var(--f-head);
  font-size: 13px; font-weight: 600;
  color: var(--mut);
  text-align: left;
  line-height: 1.35;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.guide-idx-item:hover { background: var(--panel2); color: var(--paper); text-decoration: none; }
.guide-idx-item .ico { width: 18px; text-align: center; font-size: 14px; flex: none; }
.guide-idx-item .role-pill { margin-left: auto; flex: none; }
.guide-idx-item.on {
  background: var(--accent-soft);
  color: var(--accent-fg);
  font-weight: 700;
}
/* the rail, same marker as .nav-item.active — selection is never hue alone */
.guide-idx-item.on::before {
  content: '';
  position: absolute; left: 0; top: 7px; bottom: 7px;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--accent-line);
}

.guide-body {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
  padding: 24px 28px 30px;
  min-width: 0;
}

.guide-sec {
  max-width: 72ch;
  scroll-margin-top: calc(var(--topbar-h) + 18px);
  counter-reset: gstep;
}
.guide-sec + .guide-sec { margin-top: 34px; padding-top: 28px; border-top: 1px solid var(--line); }
.guide-sec h2, .guide-sec .guide-sec-title {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-family: var(--f-head); font-size: 19px; font-weight: 800;
  letter-spacing: -.02em; color: var(--paper);
  margin-bottom: 8px;
}
.guide-sec h2 .ico, .guide-sec .guide-sec-title .ico { font-size: 18px; }
.guide-sec h3 {
  font-family: var(--f-head); font-size: 13.5px; font-weight: 700;
  color: var(--paper); margin: 20px 0 8px;
}
.guide-sec p, .guide-sec li { font-size: 14px; line-height: 1.7; color: var(--mut); }
.guide-sec p { margin-bottom: 12px; }
.guide-sec p:last-child { margin-bottom: 0; }
.guide-sec .guide-intro { color: var(--paper); font-size: 14.5px; margin-bottom: 16px; }
.guide-sec strong, .guide-sec b { color: var(--paper); font-weight: 600; }
.guide-sec code, .guide-body code {
  font-family: var(--f-mono); font-size: 12.5px;
  background: var(--panel2); border: 1px solid var(--line);
  border-radius: 6px; padding: 1px 6px; color: var(--paper);
}
.guide-sec ul { list-style: none; margin: 0 0 12px; }
.guide-sec ul li { position: relative; padding-left: 18px; margin-bottom: 6px; }
.guide-sec ul li::before {
  content: ''; position: absolute; left: 4px; top: .72em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--line2);
}

/* Steps are numbered by CSS, so app.js can emit plain strings and the numbers
   stay correct however the section is filtered or re-ordered. */
.guide-steps, .guide-sec ol { list-style: none; counter-reset: gstep; margin: 4px 0 14px; }
.guide-step {
  counter-increment: gstep;
  position: relative;
  list-style: none;
  padding: 3px 0 3px 40px;
  margin-bottom: 10px;
  font-size: 14px; line-height: 1.65; color: var(--mut);
}
.guide-step:last-child { margin-bottom: 0; }
.guide-step::before {
  content: counter(gstep);
  position: absolute; left: 0; top: 1px;
  width: 25px; height: 25px;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  color: var(--accent-fg);
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  line-height: 23px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* A tip is an aside, not a warning: an accent rail over a barely-tinted wash. */
.guide-tip {
  position: relative;
  margin: 12px 0;
  padding: 10px 14px 10px 15px;
  border-left: 3px solid var(--accent-line);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: color-mix(in srgb, var(--accent) 7%, var(--panel2));
  color: var(--mut);
  font-size: 13px;
  line-height: 1.6;
}
.guide-tip + .guide-tip { margin-top: 8px; }
.guide-tip strong, .guide-tip b { color: var(--paper); }

/*
 * The empty state is the shared card (section 10). What IS local: paintGuide()
 * replaces the whole of #guide-root — which carries .guide, a two-track grid —
 * with this single node, so without a span it lands in the 236px index track and
 * two thirds of the page sit blank beside it. Same precedent as
 * .guide > .guide-filter above.
 */
.guide-empty { font-size: 13px; }
.guide > .guide-empty { grid-column: 1 / -1; }

/* --- phase 7: responsive -------------------------------------------------- */

/* 1080px, not 980px: below that the 212px rail is still present, so at 768px the
   fixed 236px index left the article — the one screen in the app people actually
   READ — about 262px wide with a ~220px measure. This is the same breakpoint at
   which .dash-cols and .cdetail-cols give up their second column. */
@media (max-width: 1080px) {
  /* The index collapses ABOVE the article and stops being sticky — a sticky
     block over a single column would eat the top of every screen. */
  .guide { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .guide-index {
    position: static;
    max-height: 260px;
    top: auto;
  }
  .guide-body { padding: 20px 20px 26px; }
}

@media (max-width: 720px) {
  .viewtabs {
    /* bleed to the screen edges so the last lens is obviously scrollable-to */
    margin-left: -14px; margin-right: -14px;
    width: auto; max-width: none;
    border-radius: 0;
    border-left: none; border-right: none;
    padding: 5px 14px;
    box-shadow: none;
  }
  .viewtab { padding: 8px 13px 10px; font-size: 13px; }
  .viewtab .viewtab-hint { display: none; }
  .viewtabs + .viewtab-hint, .fin-tabs + .viewtab-hint { margin: 10px 0 16px; padding-left: 0; }

  .view-desc { font-size: 13px; }
  .view-head + .view-desc { margin: -14px 0 16px; }

  .focus { gap: 13px; }
  .focus > #focus-slot { grid-template-columns: minmax(0, 1fr); }
  .focus-head { padding-bottom: 12px; }
  .focus-sec .focus-head h1, .focus-head:not(.view-head) h1 { font-size: 16px; }
  .focus-scope { width: 100%; justify-content: stretch; }
  .focus-scope button, .focus-scope .scope-opt, .focus-scope > label { flex: 1; text-align: center; justify-content: center; }
  .focus-sec-title { padding: 11px 13px; font-size: 11px; }
  .focus-sec > .tcard, .focus-sec .focus-rows > .tcard { margin: 0 10px 9px; }

  /* the index becomes a horizontal rail of chips above the article. Same hidden
     scrollbar as the lens trays, so it takes the same edge-fade affordance —
     see the note on .viewtabs. */
  .guide-index {
    flex-direction: row;
    align-items: center;
    max-height: none;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    padding: 7px;
    border-radius: var(--r);
    background-color: var(--panel);
    background-image:
      linear-gradient(to right, var(--panel), transparent) left / 20px 100% no-repeat local,
      linear-gradient(to left,  var(--panel), transparent) right / 20px 100% no-repeat local,
      linear-gradient(to right, var(--sh-c2), transparent) left / 10px 100% no-repeat scroll,
      linear-gradient(to left,  var(--sh-c2), transparent) right / 10px 100% no-repeat scroll;
  }
  .guide-index::-webkit-scrollbar { display: none; }
  .guide-index .sec-title { display: none; }
  .guide-idx-item { flex: none; padding: 7px 12px; border-radius: 999px; font-size: 12.5px; }
  .guide-idx-item.on::before { content: none; }
  .guide-idx-item .role-pill { display: none; }
  /* in the rail the filter is simply its first item, still full-height and
     tappable; hung off .guide it keeps its own row above the rail. Written with
     the same `>` as the sticky rule it undoes, or that rule's higher
     specificity would survive into the horizontal rail. */
  .guide-filter, .guide-index > .guide-filter {
    position: static; background: transparent; padding: 0; margin: 0;
  }
  .guide-index > .guide-filter, .guide-index > input.guide-filter { flex: 0 0 auto; min-width: 130px; order: -1; }
  .guide > .guide-filter { margin-bottom: 10px; }

  .guide-body { padding: 18px 16px 24px; }
  .guide-sec h2, .guide-sec .guide-sec-title { font-size: 17px; }
  .guide-sec + .guide-sec { margin-top: 26px; padding-top: 22px; }
  .guide-step { padding-left: 34px; }
  .guide-step::before { width: 22px; height: 22px; line-height: 20px; font-size: 11px; }
}

@media (max-width: 420px) {
  .viewtab { padding: 8px 11px 10px; }
  .focus-empty { padding: 38px 16px; }
}

/* Motion: the reduced-motion block in section 17 already zeroes transitions
   globally, so nothing here needs its own opt-out. */

/* --------------------- 26. money — charges · revenue · ledger (phase 8) */
/*
 * The finance layer: what each client is itemised to pay, what the month is
 * expected to bring in, and the general ledger the P&L is computed from.
 *
 * These surfaces are SUPERADMIN-ONLY and behind the 'ledger' flag. The
 * stylesheet cannot enforce either — PHP gates the endpoints and app.js gates
 * the markup — so nothing here is written to "hide" a figure: if a .fin-* node
 * exists, its reader was already allowed to see it.
 *
 * LEGIBILITY OUTRANKS VIBRANCY. Money is read as a COLUMN, not as a row: every
 * numeric cell is tabular-nums, right-aligned and nowrap, so digits stack and a
 * wrong order of magnitude is visible without reading the number. Where a
 * flourish would cost that, the flourish loses. The colour and lift live in the
 * cards, tabs and bars around the tables instead.
 *
 * DIRECTION RAMP. income / expense map onto the status ramp (--st-done and
 * --st-revisions) rather than to new hues — the same trick sections 19 and 22
 * use — because that ramp already ships both-mode -ink tones (floored at L>=.70
 * in dark, capped at L<=.48 in light, each measured past 4.5:1 as text on its
 * own pale tint). Each direction sets two locals that everything below consumes:
 *
 *     --dir       the vivid hue — fills, bars, rails, where 3:1 is the bar
 *     --dir-ink   the readable text tone
 *
 * COLOUR IS NEVER THE ONLY SIGNAL. A direction pill carries a triangle that
 * points up for income and down for expense; a charge kind carries a filled disc
 * for a retainer (recurring) and a hollow ring for an ad-hoc line (this month
 * only); and a negative total already prints its own minus sign, which is the
 * cue that survives greyscale on the P&L cards.
 *
 * SCROLLING. Tables scroll INSIDE .fin-scroll — the same contract as
 * .table-wrap / .tt-scroll / .board — and the page never scrolls sideways. The
 * min-width that forces that scroll is set on the wrapped table only, so a bare
 * .fin-table dropped straight into a panel compresses instead of pushing the
 * body wide.
 */

/* The two directions. Listed once, consumed everywhere; the modifier may sit on
   the pill, the row, the card or the category, so all four spellings map. */
.fin-income, .fin-card.pos, .fin-pill.income, .fin-pill--income,
.fin-pill[data-kind="income"], .fin-cat.income, .fin-cat[data-kind="income"],
.fin-cat-fill.income, .fin-table tr.income, .fin-row.income, .fin-splitbar > .income {
  --dir: var(--st-done); --dir-ink: var(--st-done-ink);
}
.fin-expense, .fin-card.neg, .fin-pill.expense, .fin-pill--expense,
.fin-pill[data-kind="expense"], .fin-cat.expense, .fin-cat[data-kind="expense"],
.fin-cat-fill.expense, .fin-table tr.expense, .fin-row.expense, .fin-splitbar > .expense {
  --dir: var(--st-revisions); --dir-ink: var(--st-revisions-ink);
}

/* --- the Revenue | Ledger | P&L strip ------------------------------------ */
/*
 * The same tray-of-chips as .viewtabs in section 25 (one tray, chips inside,
 * scrolls sideways rather than wrapping) restated rather than aliased: the two
 * strips sit in different views and phase 8 keeps its own responsive rules, so
 * folding .fin-tabs into that selector would tie the two together for good.
 */
.fin-tabs {
  display: flex;
  align-items: stretch;
  gap: 3px;
  padding: 4px;
  margin-bottom: 16px;
  /* background-COLOR, so the scroll-affordance gradients in section 25 can layer
     over it — see the note there. */
  background-color: var(--panel2);
  border: 1px solid var(--line);
  border-radius: calc(var(--r-sm) + 5px);
  box-shadow: var(--sh-card);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  /* the tray is a control, not a list of links */
  width: max-content;
  max-width: 100%;
}
.fin-tabs::-webkit-scrollbar { display: none; }

.fin-tab {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 15px 11px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--mut);
  font-family: var(--f-head);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  scroll-snap-align: start;
  transition: color .16s var(--ease), background .16s var(--ease),
              border-color .16s var(--ease), box-shadow .18s var(--ease);
}
.fin-tab .ico { font-size: 14px; line-height: 1; flex: none; }
.fin-tab:hover {
  color: var(--paper);
  background: color-mix(in srgb, var(--lift) 60%, transparent);
}
.fin-tab:active { transform: none; }

/* Selected: lifted surface + heavier type + an accent bar. Three cues, only one
   of them colour. aria-selected is honoured so a tablist marks itself. */
.fin-tab.on, .fin-tab.active, .fin-tab[aria-selected="true"] {
  background: var(--lift);
  border-color: var(--line);
  color: var(--paper);
  font-weight: 700;
  box-shadow: var(--sh-sm);
}
.fin-tab.on::after, .fin-tab.active::after, .fin-tab[aria-selected="true"]::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 4px;
  transform: translateX(-50%);
  width: 20px;
  height: 2.5px;
  border-radius: 999px;
  background: var(--accent-line);
}
.fin-tab.on .ico, .fin-tab.active .ico, .fin-tab[aria-selected="true"] .ico { color: var(--accent-fg); }
.fin-tab:focus-visible { outline-offset: -2px; }

/* A figure riding along in a tab (Ledger · 42) stays quiet until selected. */
.fin-tab .nav-count, .fin-tab .tt-count, .fin-tab .fin-tab-count { margin-left: 0; }
.fin-tab.on .nav-count, .fin-tab.on .tt-count, .fin-tab.on .fin-tab-count,
.fin-tab[aria-selected="true"] .fin-tab-count {
  background: var(--accent-soft2); color: var(--accent-fg);
}

/* --- section head + month stepper ---------------------------------------- */
/* Title left, controls right, on one line until the phone splits them. */
.fin-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.fin-head h2, .fin-head h3 { font-size: 15.5px; }
.fin-head .grow { flex: 1; }
.fin-head .sub, .fin-sub {
  color: var(--mut); font-size: 12.5px; line-height: 1.5;
}

/* The period is the axis of every screen here, so it reads as one control. */
.fin-period {
  display: inline-flex; align-items: center; gap: 8px;
  margin-left: auto; flex: none;
}
.fin-period .btn, .fin-period .btn-sm { flex: none; padding: 6px 12px; }
.fin-period-label, .fin-period > b, .fin-period > strong {
  font-family: var(--f-head); font-weight: 700; font-size: 13.5px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  min-width: 92px; text-align: center;
}

/* --- big number cards ---------------------------------------------------- */
/*
 * The headline figures: expected · invoiced · paid · due, and income · expense ·
 * net on the P&L. Same furniture as .stat in section 5, but money is a longer
 * string than a task count, so the number is smaller, allowed to wrap at its
 * space (currency code then amount) and never sized by a viewport unit.
 */
.fin-cards {
  display: grid; gap: 14px; margin-bottom: 20px;
  grid-template-columns: repeat(auto-fit, minmax(186px, 1fr));
}
.fin-cards > * { min-width: 0; }

.fin-card {
  position: relative; overflow: hidden;
  padding: 16px 17px 17px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
  text-align: left; width: 100%; min-width: 0;
  transition: transform .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
/* The same bloom .stat carries, tinted by the card's own direction when it has
   one and by the member accent when it does not. */
.fin-card::after {
  content: ''; position: absolute; inset: auto -30% -70% auto;
  width: 150px; height: 150px; border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--dir, var(--accent)) 14%, transparent), transparent 70%);
  opacity: 0; pointer-events: none;
  transition: opacity .3s var(--ease);
}
.fin-card:hover { transform: translateY(-3px); border-color: var(--line2); box-shadow: var(--sh); }
.fin-card:hover::after { opacity: 1; }

.fin-card-label, .fin-card .lbl, .fin-card-title {
  display: flex; align-items: center; gap: 7px;
  margin: 0;
  font-family: var(--f-body);
  font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase;
  color: var(--mut);
}
.fin-card-num, .fin-card .num, .fin-card .money, .fin-card > strong {
  display: block;
  font-family: var(--f-head); font-size: 26px; font-weight: 800;
  line-height: 1.14; margin-top: 8px; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  color: var(--paper);
  /* wraps at the space between currency and amount rather than overflowing —
     never break inside the digits */
  overflow-wrap: normal; word-break: keep-all;
  text-align: left;
}
.fin-card-sub, .fin-card-foot, .fin-card .sub {
  font-size: 11.5px; color: var(--faint); margin-top: 5px; line-height: 1.45;
}
.fin-card-sub b, .fin-card-foot b { color: var(--mut); font-weight: 600; }

/*
 * pos / neg. The rail is the loud cue; the SIGN printed inside the number is the
 * one that survives greyscale, which is why a negative net is never rendered as
 * a bare figure in a red card.
 */
.fin-card.pos::before, .fin-card.neg::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--dir);
}
.fin-card.pos .fin-card-num, .fin-card.pos .num, .fin-card.pos .money { color: var(--dir-ink); }
.fin-card.neg .fin-card-num, .fin-card.neg .num, .fin-card.neg .money { color: var(--dir-ink); }
.fin-card.pos, .fin-card.neg { border-color: color-mix(in srgb, var(--dir) 24%, var(--line)); }
.fin-card.accent .fin-card-num, .fin-card.accent .num, .fin-card.accent .money { color: var(--accent-fg); }
.fin-card.mut .fin-card-num, .fin-card.quiet .fin-card-num { color: var(--mut); }
/* A card that is only a caption (no figure yet) should not sit half-empty. */
.fin-card.sm { padding: 13px 14px; }
.fin-card.sm .fin-card-num { font-size: 21px; }

/* A percentage riding under the net (margin) — quiet, never a second headline. */
.fin-card-pct {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 6px; padding: 1px 8px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--dir-ink, var(--mut));
  background: color-mix(in srgb, var(--dir, var(--panel3)) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--dir, var(--line2)) 34%, transparent);
}

/* --- the revenue + ledger tables ----------------------------------------- */
/*
 * The scroll container. The table scrolls inside it; the page never grows a
 * horizontal scrollbar (same contract as .table-wrap and .tt-scroll). The
 * min-width lives on the WRAPPED table only — see the note at the top of this
 * section for why a bare .fin-table must be able to compress.
 */
/* .inv-table-wrap joins the list: the invoices table was the only list surface in
   the app that sat naked on the page background, because its wrapper class was
   never defined anywhere. */
.fin-scroll, .fin-table-wrap, .inv-table-wrap {
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); box-shadow: var(--sh-card);
}
/*
 * CARD INSIDE A CARD. Dropped into a .panel-body these wrappers drew a second
 * bordered, shadowed box inset 18px inside an 18px-radius panel — two concentric
 * borders and two shadows around one table. The table bleeds to the panel edge
 * instead. Same repair as .focus-sec .tt-scroll.
 */
.panel-body > .fin-scroll, .panel-body > .fin-table-wrap,
.panel-body > .inv-table-wrap, .panel-body > .tt-scroll {
  border: none; border-radius: 0; background: transparent; box-shadow: none;
}
/* It only BLEEDS when it is the whole body — a table sharing the panel with a
   note or a toolbar keeps the body's padding rather than pulling over it. */
.panel-body > .fin-scroll:only-child, .panel-body > .fin-table-wrap:only-child,
.panel-body > .inv-table-wrap:only-child, .panel-body > .tt-scroll:only-child {
  margin: -18px; width: auto;
}
.fin-scroll > .fin-table, .fin-table-wrap > .fin-table,
.table-wrap > .fin-table { min-width: 720px; }
.fin-scroll > .fin-table.wide, .fin-table-wrap > .fin-table.wide { min-width: 880px; }

.fin-table {
  width: 100%; max-width: 100%;
  border-collapse: collapse;
}
.fin-table th {
  text-align: left; vertical-align: middle;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
  padding: 11px 14px; white-space: nowrap;
  background: var(--panel2); border-bottom: 1px solid var(--line);
}
.fin-table td {
  padding: 11px 14px; font-size: 13.5px; vertical-align: middle;
  border-top: 1px solid var(--line); color: var(--paper);
}
/* the header's own bottom rule is the first row's top rule */
.fin-table thead + tbody > tr:first-child > td { border-top: none; }
.fin-table tbody tr { transition: background .14s var(--ease); }
.fin-table tbody tr:hover { background: var(--panel2); }

/*
 * Numeric columns. Right-aligned, tabular and nowrap wherever they are marked —
 * as a class on the cell, as a data hook, or by the .money primitive already in
 * section 19. Headers follow their column so the label sits over its digits.
 */
.fin-table th.num, .fin-table th.right, .fin-table th.money, .fin-table th[data-num],
.fin-table td.num, .fin-table td.right, .fin-table td.money, .fin-table td[data-num],
.fin-table .fin-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.fin-table td.num, .fin-table td.money, .fin-table td[data-num] {
  font-family: var(--f-head); font-weight: 600;
}
/* The name column carries the client chip and is the only one allowed to wrap. */
.fin-table td.name, .fin-table .fin-name {
  font-family: var(--f-head); font-weight: 600; color: var(--paper);
  min-width: 160px; white-space: normal;
}
.fin-table .fin-name-wrap {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.fin-table .fin-name-wrap > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fin-table td.desc, .fin-table .fin-desc {
  white-space: normal; min-width: 200px; max-width: 420px; color: var(--paper);
}
.fin-table td.mut, .fin-table .mut { color: var(--mut); }
.fin-table td.faint, .fin-table td.zero, .fin-num.zero { color: var(--faint); font-weight: 500; }
.fin-table td.pos, .fin-num.pos { color: var(--st-done-ink); }
.fin-table td.neg, .fin-num.neg { color: var(--danger); }
.fin-table td.warn, .fin-num.warn { color: var(--st-client-ink); }
.fin-table td .fin-sub, .fin-table td small {
  display: block; font-size: 10.5px; color: var(--faint); font-weight: 500;
  letter-spacing: 0; margin-top: 2px;
}
.fin-table .chip, .fin-table .chip-client, .fin-table .fin-pill { flex: none; vertical-align: middle; }
.fin-table td .btn-icon { width: 30px; height: 30px; font-size: 13px; }
.fin-table td.act, .fin-table th.act { width: 1%; white-space: nowrap; text-align: right; }

/* Row states: a client with no itemised lines, and a row the period excluded. */
.fin-table tr.inactive, .fin-table tr.muted { opacity: .55; }
.fin-table tr.on, .fin-table tr.sel { background: var(--accent-soft); }

/* Totals. A heavier rule and a lifted surface, so the sum is never mistaken for
   one more row — the pattern .inv-totals-row.grand uses in the editor. */
.fin-table tfoot td,
.fin-table tr.total td, .fin-table tr.fin-total td, .fin-table .fin-total td {
  border-top: 2px solid var(--line2);
  background: var(--panel2);
  font-family: var(--f-head); font-weight: 700; color: var(--paper);
  font-size: 13.5px;
}
.fin-table tfoot tr:hover td, .fin-table tr.total:hover td { background: var(--panel2); }
.fin-table tfoot td.num, .fin-table tfoot td.money, .fin-table tr.total td.num {
  font-variant-numeric: tabular-nums;
}
.fin-table tfoot td.grand, .fin-table tr.total td.grand { color: var(--accent-fg); font-size: 15px; }

/* --- direction pills ----------------------------------------------------- */
/*
 * income / expense on a ledger row. The triangle is the point: it points UP for
 * money in and DOWN for money out, so the two are distinguishable with the hue
 * removed. Geometry copied from .tt-th's caret so it sits on the same baseline.
 */
.fin-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11px; font-weight: 700;
  letter-spacing: .03em; text-transform: capitalize; white-space: nowrap;
  border: 1px solid var(--line2); background: var(--panel2); color: var(--mut);
  vertical-align: middle;
}
/* The marker is declared on the two directions, not on the base: a 0x0 box with
   transparent side borders is invisible but still 8px wide, which would open a
   phantom gap in an undirected pill. */
.fin-pill.income::before, .fin-pill--income::before, .fin-pill[data-kind="income"]::before,
.fin-pill.expense::before, .fin-pill--expense::before, .fin-pill[data-kind="expense"]::before {
  content: ''; flex: none;
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
}
.fin-pill.income::before, .fin-pill--income::before, .fin-pill[data-kind="income"]::before {
  border-bottom: 5px solid currentColor;      /* up — money in */
}
.fin-pill.expense::before, .fin-pill--expense::before, .fin-pill[data-kind="expense"]::before {
  border-top: 5px solid currentColor;         /* down — money out */
}
.fin-pill.income, .fin-pill--income, .fin-pill[data-kind="income"],
.fin-pill.expense, .fin-pill--expense, .fin-pill[data-kind="expense"] {
  color: var(--dir-ink);
  background: color-mix(in srgb, var(--dir) 14%, transparent);
  border-color: color-mix(in srgb, var(--dir) 40%, transparent);
}
/* A pill that is only a category caption keeps the tray tone and no marker. */
.fin-pill.cat, .fin-pill.plain { text-transform: none; letter-spacing: 0; font-weight: 600; }
.fin-pill.cat::before, .fin-pill.plain::before { content: none; }
.fin-pill.sm { font-size: 10px; padding: 1px 8px; gap: 5px; }

/* --- P&L category breakdown ---------------------------------------------- */
/*
 * One row per category: name and count on the left, total on the right, and the
 * share-of-side bar underneath. The bar is what makes the month scannable, so it
 * spans the full row rather than sharing a column with the number.
 */
.fin-cats { display: flex; flex-direction: column; gap: 13px; }
.fin-cats + .fin-cats { margin-top: 18px; }

.fin-cat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 5px 12px;
  align-items: baseline;
  min-width: 0;
}
.fin-cat-name {
  display: flex; align-items: baseline; gap: 8px; min-width: 0;
  font-family: var(--f-head); font-size: 13px; font-weight: 600; color: var(--paper);
}
.fin-cat-name > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fin-cat-count, .fin-cat-pct {
  flex: none;
  font-family: var(--f-body); font-size: 11px; font-weight: 500; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.fin-cat-val {
  text-align: right; white-space: nowrap;
  font-family: var(--f-head); font-size: 13.5px; font-weight: 700; color: var(--paper);
  font-variant-numeric: tabular-nums;
}

.fin-cat-bar {
  grid-column: 1 / -1;
  height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--panel3);
  box-shadow: inset 0 0 0 1px var(--line);
}
.fin-cat-fill {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--dir, var(--accent)) 58%, transparent),
              var(--dir, var(--accent)));
  transition: width .9s var(--ease), filter .16s var(--ease);
}
.fin-cat:hover .fin-cat-fill { filter: brightness(1.1); }
/* A category with nothing in it still draws its rail, so the list keeps its
   rhythm and the zero is visible as a zero rather than as a missing row. */
.fin-cat.empty .fin-cat-val, .fin-cat.zero .fin-cat-val { color: var(--faint); font-weight: 600; }

/* Standalone: a single wide bar splitting income against expense. */
.fin-splitbar {
  display: flex; width: 100%; height: 14px; border-radius: 999px;
  overflow: hidden; background: var(--panel3);
  box-shadow: inset 0 0 0 1px var(--line);
}
.fin-splitbar > span { min-width: 0; transition: flex-grow .9s var(--ease); background: var(--dir, var(--mut)); }

/* --- ledger filter bar ---------------------------------------------------- */
/* Reads as a panel, not a bare row — it carries more controls than any other
   toolbar in the app (period · direction · category · client · search). */
.fin-filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px 10px;
  padding: 11px 13px; margin-bottom: 16px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--sh-card);
}
.fin-filters > * { min-width: 0; }
.fin-filters .sel, .fin-filters .inp {
  width: auto; min-width: 132px; padding: 8px 12px; font-size: 13.5px;
}
.fin-filters .sel { padding-right: 30px; }
.fin-filters .inp[type="date"], .fin-filters .inp[type="month"] { min-width: 148px; }
/* the global `label` rule is block/uppercase/6px-margin — reset it here, these
   are inline captions sitting beside their control */
.fin-filters label, .fin-filters .lbl {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0; white-space: nowrap;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600;
}
.fin-filters .search-wrap { flex: 1 1 200px; min-width: 160px; }
.fin-filters .search-wrap .inp { width: 100%; min-width: 0; }
.fin-filters .grow { flex: 1; }
.fin-filters .btn, .fin-filters .btn-sm { flex: none; }
.fin-filters .fin-filters-end, .fin-filters > .right {
  display: flex; align-items: center; gap: 8px; margin-left: auto; flex: none;
}
/* The count of what the filters matched, read as a caption not a control. */
.fin-filters .fin-count, .fin-count {
  font-size: 11.5px; color: var(--faint); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.fin-count b { color: var(--paper); font-family: var(--f-head); font-weight: 700; }

/* --- per-client charges editor ------------------------------------------- */
/*
 * The itemised lines behind a client's retainer. Same grid idiom as .lineitem in
 * section 19, with one column template declared once on the list so the header
 * labels always sit over their inputs — a different column count only needs a
 * new --chg-cols.
 */
.chg-list {
  --chg-cols: minmax(0, 1fr) 108px 62px 118px 34px;
  display: flex; flex-direction: column; gap: 8px;
}
.chg-head {
  display: grid; grid-template-columns: var(--chg-cols); gap: 8px;
  padding: 2px 2px 0;
}
.chg-head span, .chg-head > * {
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600;
}
.chg-head > :not(:first-child) { text-align: right; }

.chg-row {
  display: grid;
  grid-template-columns: var(--chg-cols, minmax(0, 1fr) 108px 62px 118px 34px);
  gap: 8px; align-items: center; min-width: 0;
}
.chg-row > * { min-width: 0; }
.chg-row .inp, .chg-row .sel { padding: 8px 11px; font-size: 13.5px; }
.chg-row .sel { padding-right: 28px; }
/* amounts and quantities are numbers: right-aligned and tabular in the FIELD
   too, so the column reads straight while it is being edited */
.chg-row .inp[type="number"], .chg-row .chg-amount, .chg-row .chg-qty {
  text-align: right; font-variant-numeric: tabular-nums;
}
.chg-row .btn-icon { flex: none; width: 34px; height: 34px; font-size: 14px; }
/* An inactive line stays visible (it is still billable history) but steps back. */
.chg-row.inactive, .chg-row.off { opacity: .55; }
.chg-row.inactive .chg-total, .chg-row.off .chg-total { text-decoration: line-through; }
/* A read-only row (no inputs) still needs the rhythm of the editable one. */
.chg-row.ro, .chg-row.read { padding: 3px 2px; font-size: 13.5px; }
.chg-label {
  font-family: var(--f-head); font-weight: 600; color: var(--paper);
  overflow: hidden; text-overflow: ellipsis;
}
.chg-detail {
  font-size: 11.5px; color: var(--faint); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis;
}

/*
 * The kind marker. retainer = a FILLED disc (recurring, every month); adhoc = a
 * HOLLOW ring (this period only). Shape carries it; the tints are the accent and
 * the progress hue, both already both-mode safe.
 */
.chg-kind {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;
  background: var(--panel3); color: var(--mut);
  border: 1px solid var(--line2);
  vertical-align: middle; flex: none;
}
.chg-kind::before {
  content: ''; flex: none;
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor;
}
.chg-kind.retainer, .chg-kind[data-kind="retainer"] {
  background: var(--accent-soft); color: var(--accent-fg);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
}
.chg-kind.adhoc, .chg-kind[data-kind="adhoc"] {
  background: color-mix(in srgb, var(--st-progress) 14%, transparent);
  color: var(--st-progress-ink);
  border-color: color-mix(in srgb, var(--st-progress) 38%, transparent);
}
.chg-kind.adhoc::before, .chg-kind[data-kind="adhoc"]::before {
  background: transparent; box-shadow: inset 0 0 0 2px currentColor;
}
/* When the kind is the row's SELECT rather than a badge it is a control, not a
   pill — the same element name serves both forms of the editor. */
select.chg-kind, .chg-kind.sel, .chg-kind > select {
  padding: 8px 26px 8px 11px; border-radius: var(--r-sm);
  font-size: 12.5px; letter-spacing: 0; text-transform: none;
  background-color: var(--panel2); color: var(--paper);
  border-color: var(--line2); width: auto; min-width: 108px;
}
select.chg-kind::before, .chg-kind.sel::before { content: none; }

/* The line total, and the same class as the list's footer sum. As a CELL it is
   just a right-aligned money figure; as a direct child of the list it is the
   grand total bar. */
.chg-total {
  font-family: var(--f-head); font-weight: 600; font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap; color: var(--paper);
}
.chg-list > .chg-total, .chg-total.grand, .chg-totals {
  display: flex; align-items: baseline; justify-content: flex-end;
  flex-wrap: wrap; gap: 6px 14px;
  margin-top: 3px; padding-top: 11px;
  border-top: 1px solid var(--line);
  font-size: 14px; color: var(--mut);
}
.chg-list > .chg-total .lbl, .chg-total.grand .lbl, .chg-totals .lbl,
.chg-list > .chg-total > span:first-child {
  margin: 0; margin-right: auto;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); font-weight: 600;
}
.chg-list > .chg-total b, .chg-list > .chg-total strong, .chg-list > .chg-total .money,
.chg-total.grand b, .chg-totals b, .chg-totals .money {
  font-family: var(--f-head); font-size: 18px; font-weight: 800;
  color: var(--accent-fg); font-variant-numeric: tabular-nums;
}
/* A secondary sum (adhoc for the month) sits under the grand one, quieter. */
.chg-totals .sub, .chg-total .sub {
  flex-basis: 100%; text-align: right;
  font-size: 11.5px; color: var(--faint); font-weight: 500;
}

/* Add-a-line: a dashed slot, so an empty list still shows where lines go. */
.chg-add {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; padding: 10px 12px;
  border: 1px dashed var(--line2); border-radius: var(--r-sm);
  background: transparent; color: var(--mut);
  font-family: var(--f-head); font-size: 12.5px; font-weight: 600;
  transition: color .15s var(--ease), border-color .15s var(--ease), background .15s var(--ease);
}
.chg-add:hover {
  color: var(--accent-fg); border-color: var(--accent-line);
  background: var(--accent-soft);
}

/* .chg-empty / .fin-empty, .tt-empty are the shared empty-state card (section 10). */
.chg-empty, .fin-empty, .tt-empty { font-size: 13px; }

/* --- the exclusions notice ------------------------------------------------ */
/*
 * "3 invoices in another currency were excluded" — a caveat about the FIGURES
 * above it, so it is a caution (the client-review hue), not an error and not a
 * neutral hint: a reader who misses it misreads the total.
 *
 * The glyph is dropped when the note brings its own icon, so app.js may emit
 * either form without doubling up.
 */
.fin-note {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 12px 0;
  padding: 9px 13px 9px 12px;
  border-left: 3px solid var(--nt, var(--st-client));
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: color-mix(in srgb, var(--nt, var(--st-client)) 10%, var(--panel2));
  color: var(--nt-ink, var(--st-client-ink));
  font-size: 12.5px; line-height: 1.55;
}
.fin-note::before { content: '⚠'; flex: none; font-size: 12px; line-height: 1.45; }
.fin-note:has(.ico)::before, .fin-note:has(svg)::before { content: none; }
.fin-note .ico { flex: none; font-size: 13px; line-height: 1.4; }
.fin-note > span, .fin-note > div, .fin-note p { min-width: 0; }
.fin-note b, .fin-note strong { color: inherit; font-weight: 700; }
.fin-note a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.fin-note.info    { --nt: var(--st-progress); --nt-ink: var(--st-progress-ink); }
.fin-note.ok, .fin-note.success { --nt: var(--st-done); --nt-ink: var(--st-done-ink); }
.fin-note.warn    { --nt: var(--st-client);   --nt-ink: var(--st-client-ink); }
.fin-note.danger, .fin-note.error { --nt: var(--danger); --nt-ink: var(--danger-ink); }
.fin-note.info::before, .fin-note.ok::before, .fin-note.success::before { content: 'ⓘ'; }
/* Inside a scroller the note would scroll away from its table — it sits above. */
.fin-scroll + .fin-note, .fin-table-wrap + .fin-note { margin-top: 10px; }

/* --- money: responsive ---------------------------------------------------- */

@media (max-width: 900px) {
  .fin-cards { grid-template-columns: repeat(auto-fit, minmax(162px, 1fr)); gap: 12px; }
  .fin-card-num, .fin-card .num, .fin-card .money { font-size: 23px; }
}

@media (max-width: 720px) {
  /* bleed to the screen edges so the last tab is obviously scrollable-to —
     exactly what .viewtabs does at this width */
  .fin-tabs {
    margin-left: -14px; margin-right: -14px;
    width: auto; max-width: none;
    border-radius: 0;
    border-left: none; border-right: none;
    padding: 5px 14px;
    box-shadow: none;
  }
  .fin-tab { padding: 8px 13px 10px; font-size: 13px; }

  .fin-head { gap: 9px; }
  .fin-period { margin-left: 0; width: 100%; justify-content: space-between; }

  .fin-filters { padding: 10px; gap: 8px; }
  /* controls share the row two-up instead of each claiming 132px. The date and
     month attribute selectors are repeated so this reset OUTWEIGHS the 148px
     min-width above it — at (0,2,0) it lost, and the two date fields plus their
     gap exactly filled a 319px bar with no slack left for anything else. */
  .fin-filters .sel, .fin-filters .inp,
  .fin-filters .inp[type="date"], .fin-filters .inp[type="month"] {
    flex: 1 1 140px; width: auto; min-width: 0;
  }
  .fin-filters .search-wrap { flex: 1 1 100%; }
  .fin-filters .fin-filters-end, .fin-filters > .right { margin-left: 0; width: 100%; }

  .chg-list { --chg-cols: minmax(0, 1fr) 96px 56px 104px 32px; }
  .fin-card { padding: 14px 15px 15px; }
}

@media (max-width: 560px) {
  .fin-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .fin-card-num, .fin-card .num, .fin-card .money { font-size: 20px; margin-top: 6px; }
  .fin-card-label, .fin-card .lbl { font-size: 10px; letter-spacing: .09em; }

  .fin-table th, .fin-table td { padding: 10px 11px; font-size: 13px; }
  .fin-scroll > .fin-table, .fin-table-wrap > .fin-table { min-width: 620px; }

  /*
   * The charge row stops being a grid: with wrap, the label takes the whole
   * first line and the numeric fields share the second, however many of them
   * app.js emits — the same tactic .profit-row uses at this width. Each row
   * becomes its own little card so the pairs never blur together.
   */
  .chg-head { display: none; }
  .chg-row {
    display: flex; flex-wrap: wrap; gap: 7px; align-items: center;
    padding: 10px 11px;
    border: 1px solid var(--line); border-radius: var(--r-sm);
    background: var(--panel2);
  }
  .chg-row > * { flex: 1 1 auto; }
  .chg-row .chg-label, .chg-row > .inp:first-child, .chg-row .chg-detail { flex: 1 1 100%; }
  .chg-row .chg-kind, .chg-row .btn-icon { flex: 0 0 auto; }
  .chg-row .chg-total { flex: 1 1 auto; text-align: right; }
  .chg-list > .chg-total, .chg-totals { justify-content: space-between; }

  .fin-cat-name { font-size: 12.5px; }
  .fin-cat-val { font-size: 13px; }
}

@media (max-width: 420px) {
  .fin-cards { grid-template-columns: 1fr; }
  .fin-card-num, .fin-card .num, .fin-card .money { font-size: 22px; }
  .fin-tab { padding: 8px 11px 10px; }
  .fin-note { font-size: 12px; padding: 9px 11px 9px 10px; }
}

/* ------------------------------------- 27. touch-input floor (last word) */
/*
 * iOS SAFARI ZOOM FLOOR. Focusing a control under 16px makes Mobile Safari
 * magnify the page — which leaves the app scrolled sideways with the topbar off
 * screen and the member pinching back out after every filter change. The app's
 * controls inherit body (14px on a phone) and the toolbars pin several of them
 * to 13.5px or less, so every one of them is raised to the threshold. The
 * command palette was the only control already above it, which is exactly why it
 * is the one place that never zoomed.
 *
 * Density is bought back with PADDING, not with type size.
 *
 * THIS BLOCK IS LAST ON PURPOSE. Most of these selectors are (0,2,0) and their
 * originals are declared in sections 19–26, further down the file than the
 * responsive section — a media query adds no specificity, so only source order
 * can settle it. Anything added below here that sets a control's font-size on a
 * phone will win against it; put it above instead.
 */
@media (max-width: 720px) {
  .inp, .sel, .txa,
  .toolbar .sel, .toolbar .inp,
  .list-toolbar .sel, .list-toolbar .inp,
  .cc-toolbar .sel, .cc-toolbar .inp,
  .fin-filters .sel, .fin-filters .inp,
  .cal-toolbar .sel,
  .chg-row .inp, .chg-row .sel,
  .lineitem .inp,
  .meta-cell .sel, .meta-cell .inp,
  .guide-filter input, input.guide-filter, .guide-filter > .inp,
  /* the monospace switch a brief makes when it holds a table — same floor */
  .txa.is-grid,
  .tt-inline, select.tt-inline { font-size: 16px; }

  .toolbar .sel, .toolbar .inp,
  .list-toolbar .sel, .list-toolbar .inp,
  .cc-toolbar .sel, .cc-toolbar .inp,
  .fin-filters .sel, .fin-filters .inp,
  .cal-toolbar .sel { padding: 7px 11px; }
  .toolbar .sel, .list-toolbar .sel, .cc-toolbar .sel,
  .fin-filters .sel, .cal-toolbar .sel { padding-right: 30px; }
  .chg-row .inp, .chg-row .sel, .lineitem .inp { padding: 7px 10px; }
  .chg-row .sel { padding-right: 26px; }
  /* the meta cell's controls are borderless text, so they keep zero padding */
  .meta-cell .sel, .meta-cell .inp { padding: 0; }
  .meta-cell .sel { padding-right: 20px; }

  /*
   * The topbar's month picker is the one control deliberately left below the
   * floor. It is a native picker rather than a text field, it is 92–108px wide
   * by necessity in a fixed 62px bar, and that bar is a scroller at this width
   * — so a zoom there is recoverable, where a 16px input would push the whole
   * header wider on every phone. An exception, not an oversight.
   */
}

/* --------------------------------------- 28. check-ins (follow-ups + standup) */
/*
 * The `checkins` feature: a client follow-up board, a per-manager compliance
 * rollup, and a daily team standup. Everything here is scoped to .ci- / .su- /
 * .flog, so when the flag is off (no markup emitted) nothing dangles — no bare
 * heading, divider or dead control is styled into existence.
 *
 * Colour is STATE, never decoration: --st-done = followed up (the app's green
 * success tone), --danger = missed (red), --warn = building/partial (amber).
 * Everything else is the neutral panel furniture the rest of the app uses. The
 * two boards deliberately share one grammar: a compact segmented control up top
 * (Daily|Weekly, Mine|Everyone — same language as .viewtabs, tighter), an
 * optional meter, then a list that SCROLLS INSIDE ITSELF. Those lists give
 * their rows flex:none on purpose: under the container's own overflow a growing
 * row set otherwise compresses and clips its note text (the .bcol-cards bug).
 *
 * Sits after section 27 safely: it sets no control font-size on a phone, so it
 * cannot undercut the touch-input floor above.
 */

/* collapse the defensive-empty containers, matching the file's :empty idiom —
   .ci-mgrs is [] for non-admins, .ci-tabs/.flog/.su-list can render bare */
.ci-head:empty, .ci-tabs:empty, .ci-mgrs:empty,
.ci-list:empty, .su-list:empty, .flog:empty { display: none; }

/* --- board header: cadence tabs + scope toggle ------------------------- */
.ci-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px;
  margin-bottom: 16px;
}
.ci-head .grow { flex: 1; }

/* Segmented control. One idiom for BOTH the Daily|Weekly cadence tabs and the
   Mine|Everyone scope toggle, so app.js reuses .ci-tabs/.ci-tab for each. */
.ci-tabs {
  display: inline-flex; align-items: stretch; gap: 3px;
  padding: 4px; background: var(--panel2);
  border: 1px solid var(--line); border-radius: calc(var(--r-sm) + 4px);
  box-shadow: var(--sh-card);
  max-width: 100%; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
}
.ci-tabs::-webkit-scrollbar { display: none; }
.ci-tab {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--mut);
  font-family: var(--f-head); font-size: 13px; font-weight: 600;
  letter-spacing: -.01em; white-space: nowrap;
  transition: color .16s var(--ease), background .16s var(--ease),
              border-color .16s var(--ease), box-shadow .18s var(--ease);
}
.ci-tab .ico { font-size: 14px; line-height: 1; flex: none; }
.ci-tab:hover { color: var(--paper); background: color-mix(in srgb, var(--lift) 60%, transparent); }
.ci-tab.on {
  background: var(--lift); border-color: var(--line);
  color: var(--paper); font-weight: 700; box-shadow: var(--sh-sm);
}
.ci-tab.on .ico { color: var(--accent-fg); }
.ci-tab:focus-visible { outline: 2px solid var(--accent-line); outline-offset: -2px; }
/* a count riding along in a tab stays quiet until its tab is selected */
.ci-tab .nav-count { margin-left: 0; }
.ci-tab.on .nav-count { background: var(--accent-soft2); color: var(--accent-fg); }

/* --- compliance meter: "8 / 12 followed up today" ---------------------- */
.ci-summary {
  display: flex; align-items: center; gap: 14px 18px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 15px 18px; margin-bottom: 16px;
  box-shadow: var(--sh-card);
}
.ci-count {
  font-family: var(--f-head); font-weight: 800; font-size: 26px;
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  color: var(--paper); flex: none;
}
.ci-count b { color: var(--st-done-ink); font-weight: 800; }
.ci-count small { font-size: 14px; color: var(--faint); font-weight: 600; }
.ci-summary .ci-label {
  font-family: var(--f-head); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--mut);
}
/* the meter — green fill (the done portion) over a neutral track */
.ci-bar {
  flex: 1 1 160px; min-width: 140px; height: 9px;
  border-radius: 999px; background: var(--panel3); overflow: hidden;
}
.ci-bar-fill {
  height: 100%; width: 0; border-radius: 999px;
  background: var(--st-done);
  transition: width .8s var(--ease);
}
/* fully caught up earns a soft green bloom */
.ci-bar-fill.full { box-shadow: 0 0 12px -2px color-mix(in srgb, var(--st-done) 60%, transparent); }
/* an admin may want the bar to read the miss as danger when nothing's done yet */
.ci-bar-fill.none, .ci-summary.pending .ci-bar-fill { background: var(--danger); }

/* --- per-account-manager rollup --------------------------------------- */
/* app.js renders the rollup as a .panel with one .ci-mgr-row per manager
   (avatar, name, a progress bar, a done/total count), styled inline there — so
   .ci-mgrs must NOT be a flex row of pills, or it would fight the panel's own
   head/body stacking. This owns only the panel spacing and a row divider. */
.ci-mgrs { margin-bottom: 16px; }
.ci-mgr-row + .ci-mgr-row { border-top: 1px solid var(--line); }

/* --- client follow-up rows --------------------------------------------- */
/* Scrolls inside itself; rows are flex:none so the overflow never squeezes and
   clips them. The client's own colour rides in as a dot (--accent set inline);
   the left border is reserved for follow-up STATE. */
.ci-list {
  display: flex; flex-direction: column; gap: 8px;
  max-height: min(62vh, 640px); overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; padding: 2px; margin: 0 -2px;
}
.ci-row {
  flex: none;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 14px; margin-bottom: 8px;
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--line2); border-radius: var(--r);
  box-shadow: var(--sh-card);
  transition: border-color .16s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
}
.ci-row:hover { box-shadow: var(--sh); border-color: var(--line2); }
.ci-row.done    { border-left-color: var(--st-done); }
.ci-row.pending { border-left-color: var(--danger); background: color-mix(in srgb, var(--danger) 5%, var(--panel)); }
/* the client identity — a colour dot + name, name allowed to truncate */
.ci-row .ci-client, .ci-row > strong {
  flex: 0 1 auto; min-width: 0; max-width: 44%;
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--f-head); font-weight: 700; font-size: 14px;
  color: var(--paper); line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ci-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--accent); box-shadow: 0 0 0 3px var(--ring);
}
.ci-manager { color: var(--faint); font-size: 12px; font-weight: 500; }
/* the newest note this period, truncated to one line in the row */
.ci-note {
  flex: 1 1 auto; min-width: 0;
  font-size: 12.5px; color: var(--mut); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-note .ci-who, .ci-note b { color: var(--faint); font-weight: 600; }
.ci-note.empty, .ci-note:empty { color: var(--faint); font-style: italic; }
.ci-row .ci-status { margin-left: auto; }

/* --- the tick / "not followed up" pills -------------------------------- */
.ci-status {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 4px 11px; border-radius: 999px; white-space: nowrap;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
  border: 1px solid var(--line2); color: var(--mut); background: var(--panel2);
}
.ci-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.ci-status.done {
  color: var(--st-done-ink);
  background: color-mix(in srgb, var(--st-done) 15%, transparent);
  border-color: color-mix(in srgb, var(--st-done) 42%, transparent);
}
.ci-status.done::before { content: '\2713'; width: auto; height: auto; background: none; font-size: 11px; line-height: 1; }
.ci-status.pending {
  color: var(--danger-ink);
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: color-mix(in srgb, var(--danger) 42%, transparent);
}

/* --- team standup (daily check-in) ------------------------------------- */
/* .su-me is the actor's own compose card; .su-list is everyone else. */
.su-me {
  background: var(--panel); border: 1px solid var(--line);
  border-top: 3px solid var(--accent-line);
  border-radius: var(--r-lg); padding: 16px 18px; margin-bottom: 18px;
  box-shadow: var(--sh-card);
}
.su-me.done { border-top-color: var(--st-done); }
.su-me-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.su-me-head h3 { font-family: var(--f-head); font-size: 15px; color: var(--paper); flex: 1; }
.su-me .txa { width: 100%; }
.su-me-foot { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.su-me-foot .grow { flex: 1; }

/* streak badge — amber while building, green once today is logged */
.su-streak {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 4px 11px; border-radius: 999px;
  font-family: var(--f-head); font-size: 12px; font-weight: 700;
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 38%, transparent);
}
.su-streak .ico { font-size: 13px; line-height: 1; }
.su-streak.done, .su-me.done .su-streak {
  color: var(--st-done-ink);
  background: color-mix(in srgb, var(--st-done) 15%, transparent);
  border-color: color-mix(in srgb, var(--st-done) 42%, transparent);
}

.su-list {
  display: flex; flex-direction: column; gap: 8px;
  max-height: min(60vh, 620px); overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; padding: 2px; margin: 0 -2px;
}
.su-row {
  flex: none;
  display: flex; align-items: flex-start; gap: 11px;
  padding: 11px 13px;
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--line2); border-radius: var(--r);
  box-shadow: var(--sh-card);
}
.su-row.done { border-left-color: var(--st-done); }
.su-row .avatar { margin-top: 1px; }
.su-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.su-name { font-family: var(--f-head); font-weight: 700; font-size: 13.5px; color: var(--paper); }
.su-note { font-size: 12.5px; color: var(--mut); line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
.su-note.empty { color: var(--faint); font-style: italic; }
.su-when { font-size: 11px; color: var(--faint); flex: none; margin-left: auto; }

/* --- follow-up log on the client page ---------------------------------- */
/* A left-bordered timeline like .update-row, scrolling inside its own box.
   The cadence tints the stripe: daily = accent, weekly = info — identity, not
   state, so it never borrows the green/red the pills own. */
.flog {
  display: flex; flex-direction: column;
  max-height: min(56vh, 560px); overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--col-bg);
}
.flog-item {
  flex: none;
  display: flex; flex-direction: column; gap: 5px;
  padding: 12px 15px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--line2);
}
.flog-item:last-child { border-bottom: none; }
.flog-item.daily  { border-left-color: color-mix(in srgb, var(--accent-line) 60%, transparent); }
.flog-item.weekly { border-left-color: color-mix(in srgb, var(--info) 65%, transparent); }
.flog-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.flog-cad {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 2px 9px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
}
.flog-cad.daily {
  color: var(--accent-fg); background: var(--accent-soft);
  border: 1px solid var(--accent-soft2);
}
.flog-cad.weekly {
  color: var(--info);
  background: color-mix(in srgb, var(--info) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--info) 38%, transparent);
}
.flog-user { font-family: var(--f-head); font-weight: 700; font-size: 12.5px; color: var(--paper); }
.flog-when { font-size: 11px; color: var(--faint); margin-left: auto; flex: none; }
.flog-note { font-size: 13px; color: var(--mut); line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
.flog-note.empty { color: var(--faint); font-style: italic; }

/* --- narrow screens: rows stack, notes are allowed to wrap -------------- */
@media (max-width: 560px) {
  .ci-row { flex-wrap: wrap; }
  .ci-row .ci-client, .ci-row > strong { flex: 1 1 100%; max-width: none; }
  .ci-note { flex: 1 1 100%; white-space: normal; }
  .ci-row .ci-status { margin-left: 0; }
  .ci-summary { padding: 13px 14px; }
  .ci-count { font-size: 23px; }
  .su-when { margin-left: 0; }
}

/* ============================================================= profiles */
/*
 * A little team personality, behind the "profiles" flag: a cropped photo and a
 * one-line status under each name. INTERNAL only — none of this is emitted on
 * the client portal. The photo itself rides on the .avatar primitive up top
 * (.avatar img); everything here is the status line, the Account editor, and
 * the in-app crop modal. When the flag is off the app simply renders none of
 * it (initials, no status line, no Account section) — no CSS switch needed.
 */

/* --- the one-line status under a name ---------------------------------- */
/* Three homes, one behaviour: small, muted, a single line that ellipsises —
   it may never widen the chip, the members row or the standup card it sits
   in. Both table/standup parents already pin min-width:0 so the clip bites. */
.userchip-status, .member-status, .su-status, .who-status {
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px; line-height: 1.35; color: var(--mut);
}
/* In the topbar chip it stacks under the name and matches the name's clamp. */
.userchip-status { max-width: 150px; font-size: 10.5px; color: var(--faint); margin-top: 1px; }

/* --- Account: the avatar block ----------------------------------------- */
.profile-av-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.profile-av-preview { flex: none; display: inline-flex; }
/* the big round preview reuses .avatar, sized up (photo overlays via .avatar img) */
.avatar.profile-av { width: 84px; height: 84px; font-size: 29px; box-shadow: 0 0 0 1px var(--line); }
.profile-av-side { display: flex; flex-direction: column; gap: 10px; min-width: 0; flex: 1; }
.profile-av-btns { display: flex; gap: 9px; flex-wrap: wrap; }

/* --- Account: the status editor ---------------------------------------- */
.status-edit { position: relative; }
/* leave room for the live counter, which floats at the input's trailing edge */
.status-edit .inp { padding-right: 62px; }
.status-count {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font-family: var(--f-head); font-size: 11px; font-weight: 600;
  color: var(--faint); font-variant-numeric: tabular-nums; pointer-events: none;
}
.status-count.over { color: var(--danger); }
/* the live "Name · status" preview under the field; hidden while empty */
.status-preview { margin-top: 10px; font-size: 12.5px; color: var(--mut); line-height: 1.4; }
.status-preview:empty { display: none; }
.status-preview .who-name { font-weight: 600; color: var(--paper); }
.status-preview .prev-sep { color: var(--faint); }

/* --- the emoji quick-pick (Account) ------------------------------------ */
/* .emoji-row is the wrap container; each .emoji-pick IS a soft square tile
   (the button carries the class — not a child of it). */
.emoji-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.emoji-pick {
  width: 34px; height: 34px; border-radius: 9px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--line); color: var(--paper);
  transition: background .12s var(--ease), border-color .12s var(--ease), transform .12s var(--ease);
}
.emoji-pick:hover { background: var(--panel3); border-color: var(--line2); transform: translateY(-2px) scale(1.1); }
.emoji-pick:active { transform: translateY(0) scale(.95); }

/* --- the crop modal ---------------------------------------------------- */
/* A square viewport the member drags the photo inside; a faint rule-of-thirds
   grid and an accent ring preview the round result. It sizes to ~260px but is
   never wider than its column, so it holds on a 320px phone. The crop maths
   (canvas) is app.js's; this is only the stage, the guides and the slider. */
.crop-stage {
  position: relative; overflow: hidden;
  width: 100%; max-width: 260px; aspect-ratio: 1 / 1; margin: 4px auto 0;
  border-radius: var(--r); border: 1px solid var(--line); background: var(--panel2);
  cursor: grab; touch-action: none; user-select: none;
  --crop-veil: color-mix(in srgb, var(--scrim) 78%, transparent);
  --crop-grid: var(--ring);
}
.crop-stage.dragging { cursor: grabbing; }
/* the canvas the crop is painted on fills the stage exactly — both are square,
   so the visible square matches the 256-space the crop maths draws in (without
   this the 256px-intrinsic canvas overflows a narrow phone stage and the drag
   would not line up with what's shown). */
.crop-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  -webkit-user-drag: none; user-select: none; pointer-events: none;
}
/* mask + guides paint above the photo but never eat the drag */
.crop-stage::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background:
    radial-gradient(circle closest-side at 50% 50%,
      transparent calc(100% - 1.5px),
      var(--accent-line) calc(100% - 1.5px) 100%,
      var(--crop-veil) 100%),
    linear-gradient(var(--crop-grid) 0 0) no-repeat 33.33% 0 / 1px 100%,
    linear-gradient(var(--crop-grid) 0 0) no-repeat 66.66% 0 / 1px 100%,
    linear-gradient(var(--crop-grid) 0 0) no-repeat 0 33.33% / 100% 1px,
    linear-gradient(var(--crop-grid) 0 0) no-repeat 0 66.66% / 100% 1px;
}

/* zoom slider row — no range control exists elsewhere, so it is styled whole */
.crop-zoom { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
.crop-zoom-ico { flex: none; color: var(--faint); font-size: 16px; line-height: 1; user-select: none; }
.crop-zoom input[type="range"] {
  flex: 1; min-width: 0; height: 6px; margin: 0;
  -webkit-appearance: none; appearance: none;
  background: var(--panel3); border-radius: 999px; cursor: pointer; outline: none;
}
.crop-zoom input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--panel);
  box-shadow: 0 1px 3px var(--sh-c2); cursor: grab;
}
.crop-zoom input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--panel);
  box-shadow: 0 1px 3px var(--sh-c2); cursor: grab;
}
.crop-zoom input[type="range"]::-moz-range-track { height: 6px; border-radius: 999px; background: var(--panel3); }
.crop-zoom input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--accent-soft); }
.crop-zoom input[type="range"]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--accent-soft); }

/* the identity chip is avatar-only on a narrow topbar (name + role already go
   at 720px), so the status line goes with them */
@media (max-width: 720px) { .userchip-status { display: none; } }

/* ------------------------------------------------------------- task crew
   A task can be shared. The lead stays in the Assignee select; everyone else
   is a chip under it, and board cards stack the faces. */
.crew { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 8px; }
/* crewHTML/paintCrew ask for avatarHTML(name,'sm',id) — without this the chips
   render at the default 26px and swamp the row. */
.avatar.sm { width: 20px; height: 20px; font-size: 9px; }
.crew-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px 3px 3px; border-radius: 999px;
  background: var(--panel2); border: 1px solid var(--line);
}
.crew-name { font-size: 12px; color: var(--paper); max-width: 130px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.crew-x {
  border: 0; background: none; cursor: pointer; padding: 0 2px;
  font-size: 11px; line-height: 1; color: var(--faint);
}
.crew-x:hover { color: var(--danger); }
/* Compact only where it is compact: the drawer's meta cell. .meta-cell .sel
   outranks a bare .crew-add, so the drawer rule has to carry that specificity,
   and the form's full-width copy keeps the normal .sel sizing. */
.meta-cell .sel.crew-add { max-width: 170px; font-size: 12px; padding: 5px 8px; }

/* overlapping faces on a board card */
.crew-faces { display: inline-flex; align-items: center; }
.crew-faces .avatar { margin-left: -7px; box-shadow: 0 0 0 2px var(--panel2); }
.crew-faces .avatar:first-child { margin-left: 0; }
.crew-more {
  margin-left: -4px; padding: 0 5px; height: 22px; min-width: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--panel3); color: var(--mut);
  font-size: 10.5px; font-weight: 700; box-shadow: 0 0 0 2px var(--panel2);
}

/* ------------------------------------------------- client page: tabs + APIs */
.ctabs {
  display: flex; gap: 6px; flex-wrap: wrap; margin: 18px 0 4px; padding: 4px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px;
}
.ctab {
  padding: 8px 15px; border: 0; border-radius: 9px; cursor: pointer;
  background: none; color: var(--mut);
  font-family: var(--f-head); font-size: 12.5px; font-weight: 700; white-space: nowrap;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.ctab:hover { color: var(--paper); background: var(--panel3); }
.ctab.is-on { background: var(--accent); color: var(--accent-ink); }
.ctab.is-on:hover { background: var(--accent); color: var(--accent-ink); }
.ctab:focus-visible { outline: 2px solid var(--accent-fg); outline-offset: 2px; }
.ctab-pane { display: none; }
.ctab-pane.is-on { display: block; }

/* one card per API */
.intg {
  padding: 15px 16px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--panel2); margin-bottom: 12px;
}
.intg:last-child { margin-bottom: 0; }
.intg-head { display: flex; align-items: center; gap: 9px; margin-bottom: 6px; }
.intg-name { font-family: var(--f-head); font-weight: 700; font-size: 14px; color: var(--paper); }
.intg-help { margin: 0 0 12px; max-width: 70ch; }
.intg-note { margin: -6px 0 12px; color: var(--faint); }
.intg-switch { margin: 4px 0 12px; }
.intg-acts { display: flex; gap: 9px; flex-wrap: wrap; align-items: center; }
.intg-result:empty { display: none; }
.intg-result { margin: 10px 0 0; }

/* The crew picker inside the NEW TASK form. The drawer's version lives in a
   narrow meta cell; here it sits in a full-width field, so it gets its own
   spacing rather than inheriting a 170px cap meant for the drawer. */
.field > .crew { margin-top: 0; margin-bottom: 8px; min-height: 30px; }
.field > .crew:empty { display: none; }
.field > .crew-add { max-width: 100%; width: 100%; }

/* ------------------------------------------------- check-in period navigator
   The board can look back at any day/week. A past board is READ-ONLY, because
   checkins.log always writes against today. */
.ci-nav { display: inline-flex; align-items: center; gap: 4px; }
.ci-nav-label {
  font-family: var(--f-head); font-size: 12.5px; font-weight: 700; color: var(--paper);
  min-width: 96px; text-align: center; white-space: nowrap;
}
.ci-nav.is-past .ci-nav-label { color: var(--accent-fg); }
.ci-nav .btn[disabled] { opacity: .35; cursor: default; }

/* every follow-up in the period, not just the newest */
.ci-note-full { margin-top: 2px; padding-top: 9px; border-top: 1px solid var(--line); }
.ci-note-one + .ci-note-one { margin-top: 9px; padding-top: 9px; border-top: 1px dashed var(--line); }
.ci-note-by {
  display: block; font-size: 11.5px; font-weight: 600; color: var(--faint); margin-bottom: 3px;
}
.ci-note-txt {
  display: block; font-size: 13.5px; line-height: 1.6; color: var(--mut);
  white-space: pre-wrap; word-break: break-word;
}

/* ==== the check-in overview + calendar ===================================
 * The informative corner: what the agency did this week, how, and who has
 * gone quiet. Built on the existing .stat / .panel / .bar vocabulary so it
 * inherits both themes for free.
 */

/* ---- channel chips (used in the feed, the thread, the history, the panel) */
.ci-chan {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 2px 8px; border-radius: 999px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .04em;
  color: var(--info);
  background: color-mix(in srgb, var(--info) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--info) 32%, transparent);
}
.ci-chan.sm { font-size: 10px; padding: 1px 7px; }
/* A row written before channels existed. It must read as missing data, not as
   a real channel — an honest gap beats a confident wrong answer. */
.ci-chan.is-none {
  color: var(--faint); background: var(--panel3); border-color: var(--line);
}
.ci-chan-ico { font-size: 11px; line-height: 1; }
.ci-chan-lab { white-space: nowrap; }

/* ---- the channel picker in every composer */
.ci-chan-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.ci-chan-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: 999px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--line); color: var(--mut);
  font-family: var(--f-head); font-size: 12px; font-weight: 600;
  transition: background .14s var(--ease), border-color .14s var(--ease), color .14s var(--ease);
}
.ci-chan-opt:hover { border-color: var(--line2); color: var(--paper); }
.ci-chan-opt.on {
  background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-fg);
}
.ci-chan-opt:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.ci-chan-opt-ico { font-size: 13px; line-height: 1; }

/* ---- "how we reached them" */
.ci-chan-panel { margin-bottom: 20px; }
.ci-chan-row {
  display: flex; align-items: center; gap: 12px;
  padding: 7px 0; border-top: 1px solid var(--line);
}
.ci-chan-row:first-child { border-top: 0; }
.ci-chan-name {
  flex: 0 0 auto; min-width: 132px; font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ci-chan-bar { flex: 1 1 auto; min-width: 60px; }
.ci-chan-n {
  flex: none; min-width: 62px; text-align: right;
  font-family: var(--f-head); font-weight: 800; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.ci-chan-n small { display: block; font-size: 10.5px; font-weight: 600; color: var(--faint); }
.ci-chan-none { margin: 10px 0 0; }
.ci-top-chan { font-size: 30px !important; line-height: 1.3 !important; }

/* ---- the two-column body */
.ci-ov-grid {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 16px;
  align-items: start;
}
.ci-ov-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* Grid and flex children default to min-width:auto, which refuses to shrink
   below their content — a long client name or an unbroken note would push the
   column wider than its track and scroll the page sideways. */
.ci-feed, .ci-att, .ci-team { min-width: 0; }

/* ---- the activity feed (also used by the calendar's day detail) */
.ci-feed-body, .ci-cal-day-body {
  display: flex; flex-direction: column; gap: 8px;
  max-height: min(62vh, 620px); overflow-y: auto; overscroll-behavior: contain;
  /* .panel-body already owns the 18px inset. A scroller nested inside it must
     not re-pad, or its rows hang outside the panel's inner edge. */
}
.ci-feed-row {
  /* a scrolling column-flex list must never compress its rows */
  flex: none;
  display: flex; flex-direction: column; gap: 4px; width: 100%; text-align: left;
  padding: 10px 12px; cursor: pointer; color: inherit; font: inherit;
  background: var(--panel2); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--r);
  transition: border-color .14s var(--ease), background .14s var(--ease), transform .14s var(--ease);
}
.ci-feed-row:hover { background: var(--lift); border-color: var(--line2); transform: translateX(2px); }
.ci-feed-row:focus-visible { outline: 2px solid var(--accent-line); outline-offset: -2px; }
.ci-feed-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ci-feed-client { font-family: var(--f-head); font-weight: 700; font-size: 13px; }
.ci-feed-cad {
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--faint);
}
.ci-feed-when { font-size: 11.5px; color: var(--faint); flex: none; }
.ci-feed-note {
  font-size: 13px; line-height: 1.5; color: var(--mut);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ci-feed-who { font-size: 11.5px; color: var(--faint); }

/* ---- client last check-in */
.ci-att-body { display: flex; flex-direction: column; gap: 7px; }
.ci-att-row {
  flex: none;
  display: flex; flex-direction: column; gap: 3px; width: 100%; text-align: left;
  padding: 9px 11px; cursor: pointer; color: inherit; font: inherit;
  background: var(--panel2); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--r);
  transition: background .14s var(--ease), border-color .14s var(--ease);
}
.ci-att-row:hover { background: var(--lift); border-color: var(--line2); }
.ci-att-row:focus-visible { outline: 2px solid var(--accent-line); outline-offset: -2px; }
.ci-att-row.is-warm { border-left-color: var(--warn); }
.ci-att-row.is-cold, .ci-att-row.is-never { border-left-color: var(--danger); }
.ci-att-top { display: flex; align-items: center; gap: 8px; }
.ci-att-name {
  font-family: var(--f-head); font-weight: 600; font-size: 13px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-att-days { flex: none; font-size: 11.5px; font-weight: 700; color: var(--faint); }
.ci-att-row.is-warm  .ci-att-days { color: var(--warn); }
.ci-att-row.is-cold  .ci-att-days,
.ci-att-row.is-never .ci-att-days { color: var(--danger); }
.ci-att-meta {
  font-size: 11.5px; color: var(--faint);
  display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
}

/* ---- team this week */
.ci-team-body { display: flex; flex-direction: column; gap: 9px; }
.ci-team-row { display: flex; align-items: center; gap: 10px; }
.ci-team-name {
  flex: 0 0 auto; min-width: 92px; max-width: 132px; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-team-bar { flex: 1 1 auto; min-width: 40px; }
.ci-team-n {
  flex: none; min-width: 58px; text-align: right;
  font-family: var(--f-head); font-weight: 800; font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.ci-team-n small { display: block; font-size: 10px; font-weight: 600; color: var(--faint); }

/* ---- the calendar */
.ci-cal-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.ci-cal-filters .sel { flex: 0 1 auto; min-width: 148px; width: auto; }
.ci-cal {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px;
  margin-bottom: 20px;
}
.ci-cal-h {
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
  text-align: center; padding-bottom: 4px;
}
.ci-cal-cell {
  min-height: 76px; display: flex; flex-direction: column; gap: 3px;
  padding: 7px 8px; text-align: left; cursor: pointer; color: inherit; font: inherit;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  transition: background .14s var(--ease), border-color .14s var(--ease);
}
.ci-cal-cell.is-blank { background: transparent; border-color: transparent; cursor: default; }
.ci-cal-cell:not(.is-blank):hover { background: var(--lift); border-color: var(--line2); }
.ci-cal-cell:focus-visible { outline: 2px solid var(--accent-line); outline-offset: -2px; }
.ci-cal-cell.has-n { background: var(--panel2); }
.ci-cal-cell.is-today { border-color: var(--accent-line); }
.ci-cal-cell.is-open {
  background: var(--accent-soft); border-color: var(--accent);
}
.ci-cal-date { font-family: var(--f-head); font-size: 12px; font-weight: 700; color: var(--faint); }
.ci-cal-cell.is-today .ci-cal-date { color: var(--accent-fg); }
.ci-cal-n {
  font-family: var(--f-head); font-size: 20px; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.ci-cal-dots { display: flex; gap: 2px; flex-wrap: wrap; margin-top: auto; }
.ci-cal-dot { font-size: 11px; line-height: 1; }
.ci-cal-hint { text-align: center; margin: 0 0 8px; }
.ci-cal-day { margin-bottom: 8px; }

@media (max-width: 1000px) {
  .ci-ov-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 720px) {
  .ci-cal { gap: 4px; }
  .ci-cal-cell { min-height: 60px; padding: 5px 6px; }
  .ci-cal-n { font-size: 16px; }
  /* The labels do not fit seven-across on a phone; the icon still identifies it
     and the title attribute still names it. */
  .ci-cal-dots { display: none; }
  .ci-chan-name { min-width: 104px; font-size: 12px; }
  .ci-cal-filters .sel { flex: 1 1 140px; }
}
@media (max-width: 560px) {
  .ci-chan-opt span:not(.ci-chan-opt-ico) { display: none; }
  .ci-chan-opt { padding: 7px 10px; }
  .ci-feed-body, .ci-cal-day-body { max-height: none; }
}

/* ==== the per-client conversation (Check-ins -> By client) ================
 * A client rail on the left, one client's whole follow-up thread on the right.
 * The rail and the thread each scroll inside their own box so the page keeps
 * one scrollbar; below 900px they stack and the rail becomes a short picker.
 */
.ci-thread-wrap {
  display: grid; grid-template-columns: 268px minmax(0, 1fr); gap: 16px;
  align-items: start;
}
/* With no clients there is no rail, so the empty card must not sit in the
   268px track with the rest of the row blank beside it. */
.ci-thread-wrap.is-empty { grid-template-columns: minmax(0, 1fr); }

/* ---- the client rail */
.ci-rail {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  overflow: hidden; position: sticky; top: calc(var(--topbar-h) + 14px);
}
.ci-rail-head { padding: 10px; border-bottom: 1px solid var(--line); }
.ci-rail-q { width: 100%; }
.ci-rail-list {
  display: flex; flex-direction: column;
  max-height: min(66vh, 620px); overflow-y: auto; overscroll-behavior: contain;
}
.ci-cl {
  /* a scrolling list must never compress its rows */
  flex: none;
  display: flex; flex-direction: column; gap: 3px;
  padding: 11px 12px; text-align: left; width: 100%;
  background: transparent; border: 0; border-left: 3px solid transparent;
  border-bottom: 1px solid var(--line); cursor: pointer; color: inherit;
  font: inherit; transition: background .14s var(--ease), border-color .14s var(--ease);
}
.ci-cl:last-child { border-bottom: 0; }
.ci-cl:hover { background: var(--lift); }
.ci-cl.on {
  /* Neutral fill on purpose: --accent-soft resolves against the THEME accent at
     :root, so pairing it with the per-client --accent stripe put two unrelated
     colours on one row. */
  background: var(--lift); border-left-color: var(--accent);
}
.ci-cl:focus-visible { outline: 2px solid var(--accent-line); outline-offset: -2px; }
.ci-cl-top { display: flex; align-items: center; gap: 8px; }
.ci-cl-name {
  font-family: var(--f-head); font-weight: 600; font-size: 13.5px;
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ci-cl.on .ci-cl-name { color: var(--paper); }
.ci-cl-pips { display: inline-flex; gap: 4px; flex: none; }
.ci-cl-meta { font-size: 11.5px; color: var(--faint); }
.ci-cl-last {
  font-size: 12px; color: var(--mut); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* the D / W cadence pips — filled when this period is already cleared */
.ci-pip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 5px; flex: none;
  font-family: var(--f-head); font-size: 9.5px; font-weight: 800; letter-spacing: .02em;
  background: var(--panel3); color: var(--faint); border: 1px solid var(--line);
}
.ci-pip.is-done {
  background: color-mix(in srgb, var(--st-done) 20%, transparent);
  color: var(--st-done-ink); border-color: color-mix(in srgb, var(--st-done) 45%, transparent);
}

/* ---- the thread */
.ci-thread { min-width: 0; }
.ci-th-head {
  display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px; background: var(--panel);
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--r) var(--r) 0 0;
}
.ci-th-id { min-width: 0; }
.ci-th-name { font-size: 18px; margin: 0 0 2px; line-height: 1.25; }
.ci-th-sub { font-size: 12.5px; color: var(--faint); }
.ci-th-stats {
  display: flex; flex-wrap: wrap; gap: 0;
  background: var(--panel2); border: 1px solid var(--line); border-top: 0;
  border-radius: 0 0 var(--r) var(--r); overflow: hidden;
}
.ci-stat {
  flex: 1 1 96px; min-width: 0; padding: 10px 14px;
  border-right: 1px solid var(--line);
}
.ci-stat:last-child { border-right: 0; }
.ci-stat-n {
  font-family: var(--f-head); font-size: 19px; font-weight: 800; line-height: 1.1;
}
.ci-stat-n--text { font-size: 13px; font-weight: 700; padding-top: 5px; }
.ci-stat-l {
  font-size: 10.5px; color: var(--faint); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700; margin-top: 3px;
}

.ci-th-filters {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 16px 0 12px;
}
.ci-th-q { flex: 1 1 200px; min-width: 0; max-width: 340px; }

/* ---- the composer */
.ci-compose {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px; margin-bottom: 18px;
  display: flex; flex-direction: column; gap: 9px;
}
.ci-compose-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ci-compose-title { font-family: var(--f-head); font-weight: 700; font-size: 13px; }
.ci-compose-state { display: inline-flex; align-items: center; gap: 6px; }
.ci-compose-hint { font-size: 11.5px; color: var(--faint); }
.ci-compose-note { min-height: 84px; font-size: 14px; line-height: 1.6; }
.ci-compose-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ci-compose-cad { flex: 0 0 auto; width: auto; min-width: 168px; }

/* ---- the log itself */
.ci-th-log { display: flex; flex-direction: column; gap: 18px; }
.ci-th-day { display: flex; flex-direction: column; gap: 8px; }
.ci-th-day-head {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--f-head); font-size: 11px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--faint);
}
.ci-th-day-head::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.ci-th-entry {
  flex: none;
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--line2); border-radius: var(--r);
  padding: 11px 14px; box-shadow: var(--sh-card);
}
.ci-th-entry--daily  { border-left-color: var(--accent); }
.ci-th-entry--weekly { border-left-color: color-mix(in srgb, var(--info) 65%, transparent); }
.ci-th-entry-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.ci-th-cad {
  font-family: var(--f-head); font-size: 10px; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px; border: 1px solid transparent; flex: none;
}
.ci-th-cad--daily {
  color: var(--accent-fg); background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.ci-th-cad--weekly {
  color: var(--info);
  background: color-mix(in srgb, var(--info) 14%, transparent);
  border-color: color-mix(in srgb, var(--info) 38%, transparent);
}
.ci-th-who { font-weight: 600; font-size: 13px; }
.ci-th-when { font-size: 11.5px; color: var(--faint); }
.ci-th-note {
  font-size: 14px; line-height: 1.62; color: var(--mut); margin-top: 6px;
  white-space: pre-wrap; word-break: break-word;
}
.ci-th-note--none { font-size: 12.5px; color: var(--faint); font-style: italic; }
.ci-th-off { flex: none; }
.ci-compose-off {
  margin: 0 0 18px; padding: 11px 13px;
  background: var(--panel2); border: 1px solid var(--line);
  border-left: 3px solid var(--line2); border-radius: var(--r);
}
.ci-th-more { display: flex; justify-content: center; margin-top: 16px; }
.ci-th-morehint { margin-top: 14px; text-align: center; }

@media (max-width: 900px) {
  .ci-thread-wrap { grid-template-columns: minmax(0, 1fr); }
  /* Sticky is pointless once the rail is a band above the thread, and it would
     pin a 60vh box over the conversation the user just picked. */
  .ci-rail { position: static; }
  .ci-rail-list { max-height: 40vh; }
}
@media (max-width: 560px) {
  .ci-th-stats { display: grid; grid-template-columns: 1fr 1fr; }
  .ci-stat { border-bottom: 1px solid var(--line); }
  .ci-stat:nth-child(2n) { border-right: 0; }
  .ci-th-q { max-width: none; }
}

/* ---- the history modal: a diary of follow-ups, newest day first */
.cih-day + .cih-day { margin-top: 20px; }
.cih-day-head {
  margin: 0 0 8px; font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
}
.cih-row {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 11px 13px; margin-bottom: 8px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 11px;
}
.cih-cad {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-fg); background: var(--accent-soft); padding: 2px 7px; border-radius: 999px;
}
.cih-who { font-size: 12.5px; font-weight: 600; color: var(--paper); }
.cih-when { font-size: 11.5px; color: var(--faint); white-space: nowrap; }
.cih-note {
  flex-basis: 100%; width: 100%; margin-top: 6px; font-size: 13.5px; line-height: 1.6;
  color: var(--mut); white-space: pre-wrap; word-break: break-word;
}
.cih-note--none { color: var(--faint); font-style: italic; }

/* ======================================================= retainer suspensions
 *
 * A month off. Amber rather than red: a suspension is an agreement, not a
 * failure, and the board should not read like something has gone wrong.
 */
.pill-suspended{
  color:#fbbf24;background:rgba(251,191,36,.12);border-color:rgba(251,191,36,.34);
  white-space:nowrap;
}
.pill-suspended.sm{font-size:10.5px;padding:2px 7px}

.suspend-banner{
  display:flex;gap:14px;align-items:flex-start;
  margin:0 0 18px;padding:14px 16px;border-radius:var(--r-sm);
  background:rgba(251,191,36,.08);border:1px solid rgba(251,191,36,.28);
}
.suspend-banner .sb-ico{font-size:17px;line-height:1.3;color:#fbbf24}
.suspend-banner .sb-main{flex:1;min-width:0;font-size:13.4px;line-height:1.6;color:var(--paper)}
.suspend-banner .sb-main b{color:#fbbf24}
.suspend-banner .sb-reason{color:var(--mut);font-style:italic}
.suspend-banner .sb-note{margin-top:5px;font-size:12.4px;color:var(--mut);line-height:1.55}

.susp-list{display:flex;flex-direction:column;gap:2px}
.susp-row{
  display:grid;grid-template-columns:minmax(140px,auto) auto 1fr auto;
  gap:10px 14px;align-items:baseline;
  padding:9px 0;border-bottom:1px solid var(--line);font-size:13px;
}
.susp-row:last-child{border-bottom:0}
.susp-row.is-live{color:var(--paper)}
.susp-when{font-weight:600;color:var(--paper);white-space:nowrap}
.susp-why{color:var(--mut);min-width:0;overflow-wrap:anywhere}
.susp-who{color:var(--faint);font-size:11.8px;white-space:nowrap}

/* The check-in board's "off this month" strip. */
.checkin-off{
  margin:16px 0 0;padding:12px 14px;border-radius:var(--r-sm);
  background:var(--panel2);border:1px dashed var(--line);
}
.checkin-off-title{
  font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--mut);font-weight:700;margin-bottom:8px;
}
.checkin-off-list{display:flex;flex-wrap:wrap;gap:8px}
.checkin-off-item{
  display:inline-flex;align-items:center;gap:7px;
  padding:5px 10px;border-radius:999px;
  background:var(--panel);border:1px solid var(--line);font-size:12.4px;
}
.checkin-off-item .dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
.checkin-off-item .until{color:var(--mut);font-size:11.6px}

@media (max-width:720px){
  .susp-row{grid-template-columns:1fr;gap:3px}
  .susp-who{white-space:normal}
}

/* --------------------------------------------------- notification switches */
.notif-prefs{
  display:flex;flex-wrap:wrap;gap:6px;padding:10px 12px;
  border-top:1px solid var(--line);background:var(--panel2);
}
.notif-pref{
  flex:1 1 130px;display:flex;align-items:center;gap:8px;
  padding:7px 10px;border-radius:var(--r-sm);cursor:pointer;
  background:var(--panel);border:1px solid var(--line);
  color:var(--mut);font:inherit;font-size:12.2px;text-align:left;
  transition:border-color .12s,color .12s;
}
.notif-pref:hover{border-color:var(--line2);color:var(--paper)}
.notif-pref.on{color:var(--paper)}
.notif-pref .np-ico{font-size:12.5px;line-height:1;flex:0 0 auto}
.notif-pref .np-lbl{flex:1;min-width:0}
.notif-pref .np-sw{
  flex:0 0 auto;width:26px;height:15px;border-radius:999px;position:relative;
  background:var(--panel3);border:1px solid var(--line2);transition:background .14s;
}
.notif-pref .np-sw::after{
  content:"";position:absolute;top:1px;left:1px;width:11px;height:11px;border-radius:50%;
  background:var(--mut);transition:transform .14s,background .14s;
}
.notif-pref.on .np-sw{background:var(--accent);border-color:var(--accent)}
.notif-pref.on .np-sw::after{transform:translateX(11px);background:var(--accent-ink,#0B0B0C)}

/* The same three switches, with room to explain themselves, on the Account page. */
.npref-row{padding:12px 0;border-top:1px solid var(--line)}
.npref-row:first-child{padding-top:0;border-top:0}
.npref-row:last-child{padding-bottom:0}
.npref-row .switch{align-items:flex-start}
.npref-row .switch-track{margin-top:2px}
.npref-row .switch-label small{margin-top:2px;line-height:1.45}
.npref-note{
  margin:8px 0 0 52px;font-size:12px;line-height:1.5;color:var(--mut);
  display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;
}
.npref-note.is-ok{color:var(--accent-fg)}
.npref-note.is-warn{color:var(--warn)}
.npref-note b{color:var(--paper)}

/* ------------------------------------------------------ tables in markdown
 *
 * Pasted from a spreadsheet and rendered from a pipe table. It scrolls inside
 * its own box: a fifteen-column paste must not widen the drawer.
 */
.md-table-wrap{
  overflow-x:auto;margin:10px 0;border:1px solid var(--line);
  border-radius:var(--r-sm);background:var(--panel);
  -webkit-overflow-scrolling:touch;
}
.md-table{border-collapse:collapse;width:100%;font-size:12.8px;line-height:1.5}
.md-table th,.md-table td{
  padding:7px 11px;border-bottom:1px solid var(--line);
  border-right:1px solid var(--line);text-align:left;vertical-align:top;
  /* A long cell wraps rather than stretching the table to the width of its
     worst value; a table nobody can read is not an improvement on tabs. */
  max-width:340px;overflow-wrap:anywhere;
}
.md-table th:last-child,.md-table td:last-child{border-right:0}
.md-table tbody tr:last-child td{border-bottom:0}
.md-table th{
  background:var(--panel2);color:var(--paper);font-weight:700;
  white-space:nowrap;position:sticky;top:0;
}
.md-table td{color:var(--mut)}
.md-table tbody tr:hover td{background:var(--panel2);color:var(--paper)}

/* The little toolbar above a brief textarea. */
.txa-tools{display:flex;gap:6px;margin-bottom:6px;flex-wrap:wrap}
/* Preview sits at the far end, away from the formatting buttons — it is a mode
   switch, not another thing that edits the text. */
.txa-tools .md-preview{margin-left:auto}

/* ------------------------------------------------- the rich brief editor
 *
 * A contenteditable that has to read as a form field, so it borrows .txa's box
 * exactly — same background, border, radius, padding and focus ring. Anything
 * less and it stops looking like something you can type in.
 */
.rich-ed{
  width:100%;min-height:150px;max-height:46vh;overflow-y:auto;
  padding:10px 13px;
  background:var(--panel2);border:1px solid var(--line);border-radius:var(--r-sm);
  color:var(--paper);font-size:13.5px;line-height:1.65;
  transition:border-color .16s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
}
.rich-ed:hover{border-color:var(--line2)}
.rich-ed:focus{
  outline:none;border-color:var(--accent-line);
  background-color:var(--focus-bg);box-shadow:0 0 0 3px var(--accent-soft);
}
/* The placeholder. A contenteditable has none of its own, and ::before on an
   empty box is the only way to get one that does not become real content. */
.rich-ed.is-empty::before{
  content:attr(data-ph);color:var(--faint);pointer-events:none;
}
.rich-ed.is-empty:focus::before{opacity:.55}

.rich-ed > :first-child{margin-top:0}
.rich-ed > :last-child{margin-bottom:0}
.rich-ed h2,.rich-ed h3,.rich-ed h4{
  font-family:var(--f-head);color:var(--paper);font-weight:700;
  margin:14px 0 6px;line-height:1.3;
}
.rich-ed h2{font-size:16px}
.rich-ed h3{font-size:14.5px}
.rich-ed h4{font-size:13.5px;color:var(--mut)}
.rich-ed blockquote{
  margin:8px 0;padding-block:2px;padding-inline:12px 0;
  border-inline-start:2px solid var(--accent-line);color:var(--mut);
}
.rich-ed del{color:var(--faint)}
/* Inside the editor a table is being EDITED, so its cells need to be obviously
   clickable and the header must not stick over the content while scrolling. */
.rich-ed .md-table th{position:static}
.rich-ed .md-table th,.rich-ed .md-table td{min-width:56px;cursor:text}
.rich-ed .md-table td:focus,.rich-ed .md-table th:focus{outline:none}
.rich-ed .md-table-wrap{margin:10px 0}

/* The row/column controls, which only appear while the caret is in a table. */
.rich-tbl{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
.rich-tbl[hidden]{display:none}
.rich-tbl .md-btn{font-size:11.5px;padding:0 8px}
.md-btn.danger:hover{
  color:#fecaca;border-color:rgba(248,113,113,.5);background:rgba(248,113,113,.12);
}

/* The Row / Column / Align dropdowns. Parked on <body> by mountFloatingMenu,
   so they are positioned in viewport coordinates and cannot be clipped by the
   overlay the editor sits in. */
.tbl-menu{
  /* 95, not 90: the brief editor lives inside .overlay, which sits at 90.
     Matching it would leave the menu's place in the stack decided by document
     order — true today, and quietly false the first time anything is appended
     to <body> after it. */
  position:fixed;z-index:95;min-width:178px;padding:5px;
  background:var(--panel2);border:1px solid var(--line2);border-radius:10px;
  box-shadow:0 18px 40px rgba(0,0,0,.42);
}
.tbl-menu-item{
  display:block;width:100%;text-align:left;
  padding:7px 10px;border:0;border-radius:7px;background:transparent;
  color:var(--mut);font:inherit;font-size:12.5px;cursor:pointer;
}
.tbl-menu-item:hover{background:var(--panel3);color:var(--paper)}
.tbl-menu-item.danger:hover{background:rgba(248,113,113,.14);color:#fecaca}

/* A merged cell is a deliberate act, so it reads as one rather than looking
   like a table that failed to line up. */
.md-table th[colspan],.md-table td[colspan],
.md-table th[rowspan],.md-table td[rowspan]{
  background:var(--panel2);vertical-align:middle;
}
.rich-ed .md-table th[colspan],.rich-ed .md-table td[colspan],
.rich-ed .md-table th[rowspan],.rich-ed .md-table td[rowspan]{
  box-shadow:inset 0 0 0 1px var(--accent-line);
}

/* ---- checklists, highlights, code blocks and dividers.
   Written once for the editor and for every read-only surface that renders the
   same Markdown — a brief, a comment, the client page — so a checklist does
   not change shape the moment you stop editing it. */
.md-chk{
  display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;margin:0 7px 0 0;vertical-align:-2px;
  border:1.5px solid var(--line2);border-radius:4px;
  font-size:10px;line-height:1;color:transparent;
  user-select:none;flex:none;
}
.md-chk.is-on{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
.rich-ed .md-chk{cursor:pointer}
.rich-ed .md-chk:hover{border-color:var(--accent)}
/* The box replaces the bullet rather than sitting after it. */
li.md-task{
  list-style:none;margin-inline-start:-18px;
  display:flex;align-items:flex-start;
}

mark,.rich mark,.brief-box mark{
  background:var(--accent);color:var(--accent-ink);
  padding:0 3px;border-radius:3px;
}
.rich-ed pre.md-pre,.rich pre.md-pre,.brief-box pre.md-pre{
  margin:10px 0;padding:10px 12px;overflow-x:auto;
  background:var(--panel3);border:1px solid var(--line);border-radius:8px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12px;line-height:1.55;color:var(--paper);white-space:pre;
}
.rich-ed pre.md-pre code,.rich pre.md-pre code,.brief-box pre.md-pre code{
  background:none;padding:0;font-size:inherit;
}
.rich-ed hr,.rich hr,.brief-box hr{
  margin:14px 0;border:0;border-top:1px solid var(--line2);
}
/* Nested lists are the reason a brief can hold a real outline. Tighter than
   the top level so four levels still fit in a 500px field. */
.rich-ed ul ul,.rich-ed ol ol,.rich-ed ul ol,.rich-ed ol ul,
.rich ul ul,.rich ol ol,.rich ul ol,.rich ol ul,
.brief-box ul ul,.brief-box ol ol,.brief-box ul ol,.brief-box ol ul{
  margin:3px 0 3px;padding-inline-start:18px;
}

/* Headings and quotes render outside the editor too — the drawer's brief box,
   the client page, a comment. Same shape, so a brief does not change
   proportions the moment you stop editing it. */
.rich h2,.rich h3,.rich h4,.brief-box h2,.brief-box h3,.brief-box h4{
  font-family:var(--f-head);color:var(--paper);font-weight:700;
  margin:14px 0 6px;line-height:1.3;
}
.rich h2,.brief-box h2{font-size:16px}
.rich h3,.brief-box h3{font-size:14.5px}
.rich h4,.brief-box h4{font-size:13.5px;color:var(--mut)}
.rich > h2:first-child,.brief-box > h2:first-child{margin-top:0}
.rich blockquote,.brief-box blockquote{
  margin:8px 0;padding-block:2px;padding-inline:12px 0;
  border-inline-start:2px solid var(--accent-line);color:var(--mut);
}
.rich del,.brief-box del{color:var(--faint)}
.lbl-hint{
  margin-left:8px;font-weight:400;font-size:11px;color:var(--faint);
  text-transform:none;letter-spacing:0;
}

/* A brief now carries real structure, so give its parts room. */
.brief-box .md-table-wrap{margin:10px 0}
.brief-box p{margin:0 0 8px}
.brief-box p:last-child{margin-bottom:0}
.brief-box ul,.brief-box ol{margin:6px 0 8px;padding-inline-start:20px}
.brief-box li{margin-bottom:3px}
.brief-box code{
  background:var(--panel3);padding:1px 5px;border-radius:4px;
  font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}

/* ============================================================== projects
 *
 * One-off work booked on top of a retainer. Deliberately does NOT reuse the
 * deliverable bar styling: a project has no contracted number, and a progress
 * bar would invent a denominator nobody agreed to.
 */
.proj-list{display:flex;flex-direction:column;gap:2px}
.proj-row{
  display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;
  padding:12px 0;border-bottom:1px solid var(--line);
}
.proj-row:last-child{border-bottom:0}
.proj-row.is-closed{opacity:.55}
.proj-main{flex:1 1 260px;min-width:0}
.proj-name{
  font-size:14px;font-weight:650;color:var(--paper);
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}
.proj-name .pill.sm{font-size:10px;padding:1px 7px;text-transform:uppercase;letter-spacing:.06em}
.proj-detail{font-size:12.6px;color:var(--mut);margin-top:4px;line-height:1.55;overflow-wrap:anywhere}
.proj-meta{
  flex:0 0 auto;display:flex;flex-direction:column;gap:3px;
  text-align:right;font-size:12px;
}
.proj-period{color:var(--paper);font-weight:600;white-space:nowrap}
.proj-count{color:var(--mut);white-space:nowrap}
.proj-count.dim{color:var(--faint)}
.proj-acts{flex:0 0 auto;display:flex;gap:6px;align-items:flex-start}

@media (max-width:640px){
  .proj-meta{text-align:left;flex-direction:row;gap:12px}
  .proj-row{gap:8px}
}

/* ========================================================== request form
 *
 * The extra questions and tags a client answers when raising a request.
 * The panel is a preview of what THEY see; the editor is the modal.
 */
.rqf-list{display:flex;flex-direction:column;gap:2px}
.rqf-item{
  display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;
  padding:9px 0;border-bottom:1px solid var(--line);font-size:13px;
}
.rqf-item:last-child{border-bottom:0}
.rqf-item .rqf-lbl{color:var(--paper);font-weight:600;min-width:0;overflow-wrap:anywhere}
.rqf-item .rqf-kind{color:var(--mut);font-size:11.5px}
.rqf-item .rqf-opts{
  flex:1 1 100%;color:var(--faint);font-size:11.5px;
  overflow-wrap:anywhere;
}
.rqf-chips{display:flex;flex-wrap:wrap;gap:6px}
.rqf-chip{
  font-size:12px;line-height:1.3;color:var(--mut);
  background:var(--panel3);border:1px solid var(--line);
  border-radius:999px;padding:4px 11px;
}
/* A retired chip carries a restore and a delete. The house .btn-icon is a
   36px tap target, which is right on a toolbar and bursts a 26px pill — so
   inside a chip they shrink to the pill's own line. */
.rqf-chip.is-off{
  display:inline-flex;align-items:center;gap:4px;
  opacity:.75;padding:3px 5px 3px 11px;
}
.rqf-chip.is-off .btn-icon{
  width:22px;min-width:0;height:22px;min-height:0;
  padding:0;border-radius:999px;font-size:11px;line-height:1;
}

/* ---------------------------------------------------------- the editor */
.rqf-edit{display:flex;flex-direction:column;gap:10px}
.rqf-row{
  border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--panel2);padding:12px;
}
.rqf-row-top{display:flex;gap:8px;align-items:center}
.rqf-row-top .inp{flex:1 1 auto;min-width:0}
.rqf-row-top .sel{flex:0 0 auto;width:auto;min-width:132px}
.rqf-row-sub{
  display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  margin-top:9px;
}
.rqf-row-sub .inp{flex:1 1 200px;min-width:0}
.rqf-row-sub .switch{flex:0 0 auto;margin:0}
.rqf-row .txa{margin-top:9px}
.rqf-grab{
  flex:0 0 auto;color:var(--faint);cursor:default;
  font-size:12px;letter-spacing:.1em;user-select:none;
}
.rqf-tagline{display:flex;gap:8px;align-items:center}
.rqf-tagline .inp{flex:1 1 auto;min-width:0}

@media (max-width:560px){
  .rqf-row-top{flex-wrap:wrap}
  .rqf-row-top .sel{width:100%}
}

/* The "what the client can see" line above ★ Final results. Quiet by default;
   it earns colour only when a task is actually in front of the client. */
.client-sees{
  padding:7px 10px;border-radius:var(--r-sm);
  background:var(--panel2);border:1px solid var(--line);
}
.client-sees.is-live{
  color:var(--accent-fg);background:var(--accent-soft);border-color:var(--accent-line);
}
/* --warn is the app's own amber and flips for the light theme; the hard-coded
   rgba() this replaced stayed dark-theme amber on a white card. */
.client-sees.is-warn{
  color:var(--warn);
  background:color-mix(in srgb, var(--warn) 10%, transparent);
  border-color:color-mix(in srgb, var(--warn) 34%, transparent);
}
/* The "internal until you share it" note on the reply bar, when the comment
   being answered came from the client. */
.reply-to-txt em { font-style: normal; color: var(--faint); }

/* ============================================== the headline ticker
 *
 * One sentence per client, rolling past everyone. The motion is the point and
 * also the risk, so three rules are not optional:
 *
 *   1. prefers-reduced-motion STOPS it. A bar that moves forever is a genuine
 *      problem for vestibular disorders and for anyone who reads slowly; the
 *      strip stays, scrollable by hand, and nothing is lost.
 *   2. It pauses on hover AND on keyboard focus. A line you cannot finish
 *      reading is worse than no line, and every headline is a real button.
 *   3. It can be switched off per person (the ✕), and stays off.
 *
 * The track holds the same run of items TWICE and translates by -50%, so the
 * loop closes on itself with no jump and no JS timer. --tick-secs is set from
 * the measured content width in app.js: a fixed duration would crawl with
 * three headlines and blur past with thirty.
 */
.tick-wrap { margin: 0 0 12px; }

.tick {
  display: flex; align-items: stretch; gap: 0;
  height: 34px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel);
  overflow: hidden;
}

.tick-tag {
  flex: none; display: inline-flex; align-items: center;
  padding: 0 11px;
  font-family: var(--f-head); font-size: 9.5px; font-weight: 800;
  letter-spacing: .14em; color: var(--accent-ink);
  background: var(--accent);
}

/* The window the track slides through. min-width:0 so it can actually shrink
   inside the flex row rather than pushing the ✕ off the end. */
.tick-view { position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden; }

.tick-track {
  display: flex; width: max-content; height: 100%;
  animation: tick-roll var(--tick-secs, 40s) linear infinite;
}
.tick-run { display: flex; align-items: center; }

@keyframes tick-roll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Rule 2. :focus-within covers tabbing through the headlines. */
.tick:hover .tick-track,
.tick-track:focus-within { animation-play-state: paused; }

/*
 * CENTRED, not baseline-aligned.
 *
 * align-items: baseline lines the three spans up with each other, which sounds
 * right and is not: it positions the flex line by the shared baseline, so the
 * whole group sits high in a 34px bar and the dot — centred against that line,
 * not against the bar — inherits the same offset. Everything here is one short
 * line of text at three sizes, so centring each box is what actually looks
 * level.
 *
 * line-height: 1 makes each span's box hug its own glyphs, so "centred box"
 * and "centred text" are the same thing. There is ~20px of slack in the bar,
 * so nothing clips.
 */
.tick-item {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 0 18px; height: 100%;
  border: 0; background: none; cursor: pointer;
  white-space: nowrap; font-size: 12.5px; line-height: 1; color: var(--mut);
  border-right: 1px solid var(--line);
}
.tick-item:hover { background: var(--panel2); color: var(--paper); }
/* No optical nudge, and that is measured rather than assumed: in Plus Jakarta
   at 12px the cap band runs 1.5px→10.5px inside a 12px box, so its centre is
   the box centre exactly. A nudge here would tilt the dot off the very line it
   is meant to sit on. (Measuring one WORD misleads — "Miswag" has a descender
   and reads 1.5px low; the cap band is the stable reference for a dot beside a
   label.) */
.tick-dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.tick-client {
  font-family: var(--f-head); font-weight: 700; color: var(--paper);
  font-size: 12px; line-height: 1;
}
.tick-date { font-size: 11px; color: var(--faint); line-height: 1; }
.tick-said { color: inherit; line-height: 1; }

.tick-off {
  flex: none; width: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-left: 1px solid var(--line);
  background: var(--panel); color: var(--faint);
  font-size: 11px; line-height: 1; cursor: pointer;
}
.tick-off:hover { color: var(--paper); background: var(--panel2); }

/*
 * Rule 1, and it is last so it wins.
 *
 * The animation is switched off rather than slowed: "reduce" is a request for
 * no motion, not less of it. The strip becomes an ordinary horizontal scroller
 * — every headline is still there and still clickable, it just waits to be
 * read instead of insisting.
 */
@media (prefers-reduced-motion: reduce) {
  .tick-view { overflow-x: auto; }
  .tick-track { animation: none; width: auto; }
  /* The duplicate run exists only to make the loop seamless; with no loop it
     is the same news twice. */
  .tick-run[aria-hidden="true"] { display: none; }
}

/* A phone has no room for a rolling bar next to everything else it is already
   squeezing, and the tag eats a third of the width. */
@media (max-width: 640px) {
  .tick-tag { display: none; }
  .tick-item { padding: 0 12px; font-size: 12px; }
}

/* The headline field in the follow-up composer. Tagged with the same LATEST
   chip the ticker wears, so the connection between "what I type here" and
   "what rolls past the team" needs no explaining. */
.ci-head-row { display: flex; align-items: stretch; margin-top: 8px; }
.ci-head-tag {
  flex: none; display: inline-flex; align-items: center; padding: 0 10px;
  font-family: var(--f-head); font-size: 9.5px; font-weight: 800;
  letter-spacing: .14em; color: var(--accent-ink); background: var(--accent);
  border-radius: var(--r-sm) 0 0 var(--r-sm);
}
.ci-head-in { border-radius: 0 var(--r-sm) var(--r-sm) 0; border-left: 0; }

/* A suspended client in the check-in rail. It keeps its place — the thread is
   the record and you look it up before restarting with them — but it stops
   claiming follow-ups are open, because none are. */
.ci-cl.is-susp { opacity: .72; }
.ci-cl.is-susp:hover, .ci-cl.is-susp.on { opacity: 1; }
.ci-cl-susp {
  font-family: var(--f-head); font-size: 9.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--warn); white-space: nowrap;
}

/* =========================================================================
 * ON-PAGE ALERT CARDS
 *
 * What arrives while the member is looking at MarkerOps: top right, under the
 * bar, big enough to read and slow enough to act on. See notifyCard().
 * ====================================================================== */
.nt-stack {
  position: fixed; z-index: 96;
  top: calc(var(--topbar-h) + 14px); right: 18px;
  width: min(400px, calc(100vw - 24px));
  display: flex; flex-direction: column; gap: 10px;
  pointer-events: none;                 /* the gaps between cards stay click-through */
}
.ntc {
  pointer-events: auto; position: relative; overflow: hidden;
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 40px 15px 14px;
  background: var(--panel); color: var(--paper);
  border: 1px solid var(--line2); border-left: 4px solid var(--accent-line);
  border-radius: 14px; box-shadow: var(--sh-lg);
  cursor: pointer;
  animation: ntcIn .28s var(--ease) both;
}
.ntc.out { animation: ntcOut .2s var(--ease) both; }
@keyframes ntcIn  { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: none; } }
@keyframes ntcOut { to { opacity: 0; transform: translateX(24px); } }
.ntc:hover { border-color: var(--accent-line); }
.ntc[data-kind="mention"], .ntc[data-kind="assigned"] { border-left-color: var(--accent); }
.ntc[data-kind="client"] { border-left-color: var(--warn); }
.ntc[data-kind="due"]    { border-left-color: var(--danger); }
.ntc-ico {
  flex: none; width: 34px; height: 34px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-fg);
  font-size: 15px; font-weight: 700;
}
.ntc-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ntc-kind {
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--accent-fg);
}
.ntc-kind span { color: var(--faint); letter-spacing: .02em; text-transform: none; font-weight: 600; }
.ntc-title { font-family: var(--f-head); font-size: 14.5px; font-weight: 700; line-height: 1.35; word-break: break-word; }
.ntc-body {
  font-size: 13px; line-height: 1.5; color: var(--mut); white-space: pre-line; word-break: break-word;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden;
}
.ntc-acts { display: flex; gap: 8px; margin-top: 8px; }
.ntc-acts .btn { padding: 5px 12px; }
.ntc-x {
  position: absolute; top: 8px; right: 8px; width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: 8px; background: none; color: var(--faint);
  font-size: 12px; cursor: pointer;
}
.ntc-x:hover { background: var(--panel3); color: var(--paper); }
/* How long is left. It stops while the pointer is on the card. */
.ntc-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--accent-line); opacity: .55; transform-origin: left center;
  animation: ntcBar 15s linear both;
}
.ntc.is-held .ntc-bar { animation: none; transform: none; }
@keyframes ntcBar { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (max-width: 560px) {
  .nt-stack { right: 12px; left: 12px; width: auto; }
}
@media (prefers-reduced-motion: reduce) {
  .ntc, .ntc.out { animation: none; }
  .ntc-bar { animation: none; display: none; }
}

/* =========================================================================
 * THE COMMAND BAR
 *
 * The header, rebuilt around the one control the team could not find. Search
 * was a 15px ⌕ in a row of five icons; it is now a field in the middle of the
 * bar, where every tool people already use keeps it. The bar grew from 62px
 * to 72px to make room for controls that are sized to be hit, not squeezed.
 *
 *   brand · period        [ search ………………………… Ctrl K ]        new task · tools · me
 *   where am I                       find                      act        who
 *
 * Kept as one block, after every earlier header rule, so it wins on order
 * alone — and it carries its own narrow-screen rules for the same reason.
 * ====================================================================== */

:root { --topbar-h: 72px; }

.topbar { gap: 14px; padding: 0 20px; }
.topbar .tb-ico { flex: none; display: block; }
.brand-logo { height: 24px; }

/* ---- the period: a caption over the value, so it reads as context ------- */
.topbar .period-pick {
  flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px;
  height: 46px; padding: 0 8px 0 14px; border-radius: 13px;
}
.topbar .period-pick .lbl { font-size: 9.5px; line-height: 1; letter-spacing: .13em; color: var(--faint); }
.topbar .period-pick input { height: 19px; padding: 0; font-size: 13.5px; width: 138px; }

/* ---- search: looks like the field it is --------------------------------- */
.tb-search {
  flex: 1 1 320px; min-width: 220px; max-width: 580px; margin-inline: auto;
  height: 46px; padding: 0 10px 0 15px; border-radius: 13px;
  display: flex; align-items: center; gap: 11px;
  background: var(--panel2); border: 1px solid var(--line);
  color: var(--mut); font: inherit; font-size: 14px; text-align: left; cursor: text;
  transition: border-color .16s var(--ease), background .16s var(--ease), color .16s var(--ease), box-shadow .16s var(--ease);
}
.tb-search .tb-ico { color: var(--accent-fg); }
.tb-search-txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-kbd {
  flex: none; padding: 3px 8px; border-radius: 7px;
  font-family: var(--f-head); font-size: 11px; font-weight: 600; line-height: 1.3;
  color: var(--faint); background: var(--panel); border: 1px solid var(--line2);
}
.tb-search:hover { border-color: var(--line2); background: var(--panel3); color: var(--paper); }
.tb-search:focus-visible {
  outline: none; border-color: var(--accent-line); color: var(--paper);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---- act ------------------------------------------------------------------ */
.btn.tb-new {
  height: 44px; padding: 0 17px 0 14px; border-radius: 13px;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13.5px; white-space: nowrap;
}

/* Three quiet tools in ONE group, so the eye reads a single object rather
   than three loose icons — and so the gap to the primary button means
   something. */
.tb-tools {
  display: flex; align-items: center; gap: 2px;
  padding: 3px; border-radius: 14px;
  background: var(--panel2); border: 1px solid var(--line);
}
.tb-tool, .topbar .bell.tb-tool {
  position: relative; width: 38px; height: 38px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 11px; border: 1px solid transparent; background: none;
  color: var(--mut); cursor: pointer;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.tb-tool:hover, .tb-tool[data-menu-open] { background: var(--panel3); color: var(--paper); border-color: var(--line); }
.tb-tool:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; }
.topbar .bell.has-unread { color: var(--paper); }
.topbar .bell .bell-dot { top: -2px; right: -2px; box-shadow: 0 0 0 2px var(--panel2); }

/* ---- me --------------------------------------------------------------------- */
.topbar .userchip {
  height: 46px; padding: 0 10px 0 5px; gap: 10px; border-radius: 14px;
  cursor: pointer; text-align: left;
}
.topbar .userchip > .avatar { width: 34px; height: 34px; font-size: 12px; flex: none; }
/* Name and role on one line, status under them: two lines, not three. */
.topbar .userchip-id {
  display: grid; grid-template-columns: auto auto; justify-content: start;
  align-items: center; column-gap: 7px; row-gap: 2px; line-height: 1.15;
}
.topbar .userchip-name { font-size: 13.5px; max-width: 150px; }
.topbar .userchip .role-pill { margin-top: 0; }
.topbar .userchip-status { grid-column: 1 / -1; margin-top: 0; max-width: 220px; }
.userchip-caret { display: inline-flex; color: var(--faint); transition: transform .16s var(--ease), color .16s var(--ease); }
.userchip:hover .userchip-caret { color: var(--paper); }
.userchip[data-menu-open] { border-color: var(--line2); background: var(--panel3); }
.userchip[data-menu-open] .userchip-caret { transform: rotate(180deg); color: var(--paper); }

/* ---- the identity menu ------------------------------------------------------ */
.theme-menu.user-menu { width: 270px; padding: 8px; }
.um-head { display: flex; align-items: center; gap: 11px; padding: 8px 8px 12px; }
.um-head .avatar { flex: none; }
.um-id { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.um-id b { font-family: var(--f-head); font-size: 14px; font-weight: 700; color: var(--paper); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.um-id span { font-size: 12px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.um-item {
  width: 100%; display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border: 0; border-radius: 9px; background: none;
  color: var(--paper); font: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
  transition: background .14s var(--ease), color .14s var(--ease);
}
.um-item .tb-ico { color: var(--mut); }
.um-item:hover, .um-item:focus-visible { background: var(--panel3); outline: none; }
.um-item.danger:hover, .um-item.danger:focus-visible { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.um-item.danger:hover .tb-ico { color: var(--danger); }
.um-sep { height: 1px; margin: 6px 4px; background: var(--line); }

/* ---- narrower --------------------------------------------------------------- */
/* Laptop: the status line and the keyboard hint are the first things to go. */
@media (max-width: 1280px) {
  .topbar .userchip-status { display: none; }
  .topbar .userchip-name { max-width: 110px; }
  .tb-search { min-width: 180px; }
}
/* Tablet and below: the bar is a scroller here (see the 1000px rule above),
   so search folds to a full-size button rather than a field with no room. */
@media (max-width: 1000px) {
  :root { --topbar-h: 62px; }
  .topbar { gap: 10px; padding: 0 14px; }
  .tb-search {
    flex: none; min-width: 0; width: 44px; height: 44px; padding: 0;
    justify-content: center; margin-inline: auto 0; cursor: pointer;
  }
  .tb-search-txt, .tb-kbd { display: none; }
  .topbar .period-pick { height: 44px; }
  .btn.tb-new { height: 42px; }
  .topbar .userchip { height: 44px; }
}
@media (max-width: 720px) {
  .topbar .userchip-id, .userchip-caret { display: none; }
  .topbar .userchip { padding: 0 5px; }
  .btn.tb-new span { display: none; }
  .btn.tb-new { padding: 0 13px; }
  .topbar .period-pick input { width: 118px; font-size: 12.5px; }
}

/* =========================================================================
 * THE WORKLOAD BOARD
 *
 * Ali, 22 Oct 2026: "I don't like the UI in the workload page." The page is
 * now four headline tiles, a ranked bar chart on one shared scale (team
 * average drawn on every track, the figure at the end of each bar, deadline
 * pressure as coloured pills) and a rail that names who needs a hand and who
 * has room. Segment colours still come from .load-seg[data-status] so the
 * light-theme repairs in section 16 keep applying.
 * ========================================================================= */

.view-head .wl-sort { margin: 0; padding: 3px; align-self: center; flex-wrap: nowrap; }
.wl-sort .ctab { padding: 6px 12px; font-size: 12px; }

.wl-tiles { margin-bottom: 18px; }
.wl-tiles .stat { padding: 15px 17px; }
.wl-tiles .stat-num { font-size: 32px; margin-top: 6px; }
.wl-tiles .stat-foot { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.wl-cols { display: grid; grid-template-columns: minmax(0, 1fr) 312px; gap: 18px; align-items: start; }
.wl-rail { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.wl-rail .panel-body { padding: 8px; }
.wl-rail .chip.alert {
  color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}

/* The legend lives in the panel head: only the stages someone holds, plus the
   average marker explained once. */
.wl-legend { flex: 1; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px 14px; font-size: 11.5px; color: var(--mut); }
.wl-lg { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.wl-lg .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.wl-lg-avg i { display: inline-block; width: 2px; height: 12px; border-radius: 1px; background: var(--paper); opacity: .6; }

.wl-main .panel-head { flex-wrap: wrap; row-gap: 6px; }
.wl-main .panel-head h2 { white-space: nowrap; }
.wl-list { display: flex; flex-direction: column; gap: 6px; }
/* Two lines per member: the bar on top at full width (so every bar shares one
   scale), its deadline pills underneath as annotations of that bar. */
.wl-row {
  display: grid; grid-template-columns: 20px 196px minmax(0, 1fr);
  grid-template-rows: auto auto; align-items: center;
  column-gap: 14px; row-gap: 7px;
  width: 100%; text-align: left; cursor: pointer;
  padding: 10px 12px 10px 10px; border-radius: 13px;
  background: var(--panel2); border: 1px solid var(--line);
  transition: background .15s var(--ease), border-color .15s var(--ease),
              transform .15s var(--ease), box-shadow .15s var(--ease);
}
.wl-row:hover { background: var(--panel3); border-color: var(--line2); transform: translateY(-1px); box-shadow: var(--sh-card); }
.wl-row:focus-visible { outline: 2px solid var(--accent-fg); outline-offset: 2px; }
.wl-row.over {
  border-color: color-mix(in srgb, var(--danger) 38%, var(--line));
  background: color-mix(in srgb, var(--danger) 7%, var(--panel2));
}
.wl-row.over:hover { background: color-mix(in srgb, var(--danger) 11%, var(--panel3)); }

.wl-rank, .wl-who { grid-row: 1 / span 2; }
.wl-bar   { grid-column: 3; grid-row: 1; }
.wl-pills { grid-column: 3; grid-row: 2; }
.wl-rank {
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700; color: var(--faint);
  text-align: center; font-variant-numeric: tabular-nums;
}
.wl-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.wl-who .who-av { width: 34px; height: 34px; font-size: 12.5px; flex: none; }
.wl-names { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.wl-names .who-name { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-sub { font-size: 11.5px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wl-sub.over {
  color: var(--danger); font-family: var(--f-head); font-weight: 700;
  font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
}

/* The bar: one shared scale (the busiest member is 100%), the team average as
   a thin line through every track, the figure at the end. */
.wl-bar { display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; gap: 10px; min-width: 0; }
.wl-track {
  position: relative; display: block; height: 16px; border-radius: 999px;
  background: var(--panel3); box-shadow: inset 0 0 0 1px var(--line2);
}
.wl-row:hover .wl-track { background: var(--panel); }
.wl-fill {
  display: flex; height: 100%; width: 0; border-radius: 999px; overflow: hidden;
  transition: width .9s var(--ease);
}
.wl-fill .load-seg { min-width: 0; }
.wl-fill .load-seg + .load-seg { box-shadow: inset 1px 0 0 color-mix(in srgb, var(--panel) 70%, transparent); }
.wl-avg {
  position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; z-index: 1;
  background: var(--paper); opacity: .55; border-radius: 1px; pointer-events: none;
}
.wl-val {
  font-family: var(--f-head); font-weight: 800; font-size: 14.5px; text-align: right;
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.wl-row.over .wl-val { color: var(--danger); }

.wl-pills { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-start; min-width: 0; }
.wl-pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px;
  font-family: var(--f-head); font-size: 11px; font-weight: 700; white-space: nowrap;
  background: var(--panel3); color: var(--mut); border: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.wl-pill.warn  { color: var(--warn);   border-color: color-mix(in srgb, var(--warn) 38%, transparent);   background: color-mix(in srgb, var(--warn) 10%, transparent); }
.wl-pill.alert { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 38%, transparent); background: color-mix(in srgb, var(--danger) 10%, transparent); }
.wl-pill.quiet { color: var(--faint); border-color: transparent; background: color-mix(in srgb, var(--paper) 5%, transparent); }
.wl-pill.ok    { color: var(--st-done-ink); border-color: color-mix(in srgb, var(--st-done) 38%, transparent); background: color-mix(in srgb, var(--st-done) 9%, transparent); }

/* Members with nothing open: their own quiet group, not blanks among the bars. */
.wl-group {
  display: flex; align-items: center; gap: 8px; margin: 18px 0 10px;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--faint);
}
.wl-row--free { grid-template-columns: 20px 196px minmax(0, 1fr) auto; grid-template-rows: auto; opacity: .85; }
.wl-row--free .wl-rank, .wl-row--free .wl-who { grid-row: 1; }
.wl-row--free .wl-free-txt { grid-column: 3; grid-row: 1; }
.wl-row--free .wl-pills { grid-column: 4; grid-row: 1; }
.wl-row--free:hover { opacity: 1; }
.wl-free-txt { font-size: 12.5px; color: var(--faint); }

.wl-how { margin-top: 16px; font-size: 12px; color: var(--faint); }
.wl-how summary { cursor: pointer; color: var(--mut); font-family: var(--f-head); font-weight: 600; font-size: 12px; }
.wl-how summary:hover { color: var(--paper); }
.wl-how p { margin: 8px 0 0; line-height: 1.55; max-width: 72ch; }

/* The rail's rows: a face, a name, the reason, and one number. */
.wl-att {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  width: 100%; text-align: left; cursor: pointer;
  padding: 8px 10px; border-radius: 11px; border: 1px solid transparent;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.wl-att + .wl-att { margin-top: 2px; }
.wl-att:hover { background: var(--panel2); border-color: var(--line); }
.wl-att:focus-visible { outline: 2px solid var(--accent-fg); outline-offset: 2px; }
.wl-att .who-av { width: 28px; height: 28px; font-size: 11px; }
.wl-att-txt { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.wl-att-txt .who-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wl-att-why { font-size: 11.5px; color: var(--faint); line-height: 1.35; }
.wl-att-why.alert { color: var(--danger); }
.wl-att-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 28px; padding: 0 9px; border-radius: 999px;
  font-family: var(--f-head); font-weight: 800; font-size: 12.5px;
  background: var(--panel3); color: var(--paper); font-variant-numeric: tabular-nums;
}
.wl-att-n.alert { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }
.wl-att-n.ok    { background: color-mix(in srgb, var(--st-done) 16%, transparent); color: var(--st-done-ink); }
.wl-calm { margin: 4px 6px; font-size: 12.5px; color: var(--mut); line-height: 1.5; }

@media (max-width: 1180px) {
  .wl-cols { grid-template-columns: 1fr; }
  .wl-rail { display: grid; grid-template-columns: 1fr 1fr; }
  .wl-tiles { grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }
  /* The legend drops under the title as one flowing row, not a tall stack. */
  .wl-legend { flex: 1 1 100%; justify-content: flex-start; }
}
@media (max-width: 900px) {
  .wl-row { grid-template-columns: 20px 160px minmax(0, 1fr); }
  .wl-row--free { grid-template-columns: 20px 160px minmax(0, 1fr) auto; }
  .wl-row--free .wl-free-txt { display: none; }
}
@media (max-width: 720px) {
  .wl-rail { grid-template-columns: 1fr; }
  .wl-row { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; padding: 10px; }
  .wl-rank { display: none; }
  .wl-who { grid-row: 1; grid-column: 1; }
  .wl-bar { grid-column: 1; grid-row: 2; }
  .wl-pills { grid-column: 1; grid-row: 3; }
  .wl-row--free { grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto; }
  .wl-row--free .wl-who { grid-column: 1; }
  .wl-row--free .wl-pills { grid-column: 2; }
  .wl-tiles .stat-num { font-size: 26px; }
  .view-head .wl-sort { width: 100%; }
  .wl-sort .ctab { flex: 1; text-align: center; padding: 7px 6px; }
}

/* =========================================================================
 * THE COMPACT TASK DRAWER
 *
 * Ali, 1 Oct 2026: the drawer "is making a lot of the window size. The
 * information is important" — so nothing is removed. Three moves instead:
 *
 *   1. DENSITY. Every field was its own card (a 4-up grid of 170px boxes,
 *      391px tall); they are now label/value ROWS in one grid — the same
 *      fields in about a third of the height. Hints became tooltips, rows of
 *      files and steps lost their padding, type came down a notch.
 *   2. MINIMISE / MAXIMISE. Each section (.dsec) folds to a one-line header
 *      that still carries its summary — "2/5", "Blocked by 1" — so folding
 *      puts away controls, not information.
 *   3. THE PANES. The activity pane minimises to a rail or maximises to the
 *      whole drawer.
 *
 * Everything is scoped to .drawer: the same components (.filerow, .chk-item,
 * .status-opt …) are used on other pages at their normal size.
 * ====================================================================== */

.drawer { font-size: 13.5px; }

/* ---- header ---------------------------------------------------------- */
.drawer .drawer-head { padding: 11px 14px 10px 16px; gap: 4px; align-items: center; }
.drawer .drawer-eyebrow { gap: 6px; margin-bottom: 3px; }
.drawer .drawer-head h2 { font-size: 16.5px; line-height: 1.28; }
.drawer .drawer-head .btn-icon { width: 30px; height: 30px; font-size: 14px; border-radius: 8px; }
.drawer .drawer-eyebrow .chip { padding: 2px 8px; font-size: 11px; }
.drawer .drawer-saved { margin-right: 6px; }

/* ---- the task pane ---------------------------------------------------- */
.drawer .drawer-body { padding: 12px 16px 16px; }
.drawer .blocked-banner {
  margin-bottom: 10px; padding: 6px 10px; gap: 8px;
  border-radius: 8px; border-left-width: 3px; font-size: 12.5px; line-height: 1.4;
}
.drawer .blocked-banner .blocked-ico { font-size: 13px; }

.drawer .status-picker { gap: 5px; margin-bottom: 10px; }
.drawer .status-opt { padding: 4px 10px; gap: 5px; font-size: 12px; }
.drawer .status-opt .dot { width: 6px; height: 6px; }
.drawer .tcard-hold { font-size: 12.5px; }

/* ---- a minimisable section --------------------------------------------
 *
 * The heading has to LOOK like something you can press. As plain grey caps on
 * the drawer's own background it read as a caption — Ali: "these should be in
 * a different colour or something to show that they are headers and can be
 * folded". So the heading is a bar of its own: a tinted, bordered strip, the
 * name in the accent, a chevron in a button-shaped box, and the word for what
 * a click will do (Show / Hide) at the far end. Open, the bar carries an
 * accent edge, so the state is legible without reading the chevron.
 */
.dsec { margin-top: 6px; }
.dsec-head {
  display: flex; align-items: center; gap: 8px; min-height: 34px;
  padding: 0 8px 0 5px; border-radius: 9px;
  background: var(--panel2); border: 1px solid var(--line);
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.dsec-head:hover { background: var(--panel3); border-color: var(--line2); }
.dsec:not(.is-closed) > .dsec-head {
  border-color: color-mix(in srgb, var(--accent-line) 45%, var(--line));
  box-shadow: inset 3px 0 0 var(--accent-line);
}
.dsec-toggle {
  flex: 1; min-width: 0; align-self: stretch;
  display: flex; align-items: center; gap: 8px;
  padding: 0; margin: 0; background: none; border: 0; border-radius: 6px;
  color: var(--faint); font: inherit; text-align: left; cursor: pointer;
}
.dsec-caret {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1;
  background: var(--accent-soft); color: var(--accent-fg);
  transition: transform .16s var(--ease), background .16s var(--ease);
}
.dsec:not(.is-closed) > .dsec-head .dsec-caret { transform: rotate(90deg); }
.dsec-toggle:hover .dsec-caret { background: var(--accent-soft2); }
.dsec-title {
  flex: none;
  font-family: var(--f-head); font-size: 11.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--accent-fg);
}
/* What the click will do, in a word, pushed to the end of the bar. */
.dsec-toggle::after {
  content: 'Hide'; flex: none; margin-left: auto; padding-left: 10px;
  font-family: var(--f-head); font-size: 11px; font-weight: 600; color: var(--faint);
}
.dsec.is-closed > .dsec-head .dsec-toggle::after { content: 'Show'; }
.dsec-toggle:hover::after { color: var(--paper); }
.dsec-toggle:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
/* What is inside, in the header. Counts stay put; prose summaries (.when-closed)
   only appear once the section is minimised — open, the content says it. */
.dsec-sum {
  min-width: 0; display: block;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font-size: 12px; color: var(--mut);
}
.dsec:not(.is-closed) > .dsec-head .dsec-sum.when-closed { display: none; }
.dsec-sum b { font-family: var(--f-head); font-weight: 700; color: var(--paper); font-variant-numeric: tabular-nums; }
.dsec-actions { flex: none; display: flex; align-items: center; gap: 6px; }
.dsec-body { padding: 8px 2px 6px; }
.drawer .drawer-born { margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--line); font-size: 11.5px; }

/* ---- Details: label / value rows -------------------------------------- */
.props {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(236px, 1fr));
  column-gap: 22px;
}
.prop {
  display: grid; grid-template-columns: 92px minmax(0, 1fr);
  align-items: center; column-gap: 8px;
  min-height: 32px; padding: 1px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.prop--wide, .prop--rest { grid-column: 1 / -1; }
/* Labels take whatever is left of the last line beside Watchers — when the
   grid is three columns wide. Narrower, they get a line of their own. */
.drawer-body .dsec-body { container: dsec / inline-size; }
@container dsec (min-width: 752px) { .prop--rest { grid-column: span 2; } }
.prop > .lbl {
  margin: 0; font-family: var(--f-body); font-size: 12px; font-weight: 500;
  letter-spacing: 0; text-transform: none; color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.prop-val { min-width: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 5px 6px; }
/* Borderless until hovered or focused: at rest a row reads as text, and the
   control shows itself the moment the pointer or the keyboard reaches it. */
.prop .sel, .prop .inp {
  width: auto; min-width: 0; max-width: 100%; height: 28px;
  padding: 0 7px; margin-left: -7px;
  background-color: transparent; border: 1px solid transparent; border-radius: 7px;
  font-family: var(--f-head); font-weight: 600; font-size: 13px; line-height: 1;
}
.prop .sel { padding-right: 22px; background-position: right 6px center; }
.prop .sel:hover, .prop .inp:hover { border-color: var(--line2); background-color: var(--panel2); }
.prop .sel:focus, .prop .inp:focus {
  border-color: var(--accent-line); background-color: var(--focus-bg);
  box-shadow: 0 0 0 2px var(--accent-soft2);
}
.prop input[type=number] { width: 68px; }
.prop input[type=date]::-webkit-calendar-picker-indicator,
.prop input[type=month]::-webkit-calendar-picker-indicator { filter: var(--pick-invert); cursor: pointer; }
/* The publish trio still reads as a group that is not the due date. */
.prop--pub > .lbl { color: color-mix(in srgb, var(--accent-fg) 70%, var(--faint)); }
.prop .cc-dot { flex: none; }

.prop .crew { margin-top: 0; gap: 5px; }
.prop .crew-chip { padding: 2px 7px 2px 2px; gap: 5px; }
.prop .sel.crew-add {
  max-width: 150px; height: 26px; margin-left: 0; font-size: 12px; font-weight: 500;
  color: var(--mut); border-color: var(--line); border-style: dashed;
}
.prop .labels, .prop .watchers { margin-bottom: 0; gap: 5px 6px; }
.prop .label-edit { padding: 3px 8px; font-size: 12px; }
.prop .watch-btn { padding: 3px 10px; font-size: 12px; gap: 5px; }
.prop .watch-av { width: 22px; height: 22px; font-size: 9px; }
.prop .hint { margin: 0; font-size: 12px; }
/* "No watchers yet." — the lone Watch button already says so, on one line. */
.prop .watchers > .hint { display: none; }
.switch--sm { gap: 7px; }
.switch--sm .switch-track { width: 32px; height: 18px; }
.switch--sm .switch-track::after { width: 12px; height: 12px; }
.switch--sm input:checked + .switch-track::after { transform: translateX(14px); }
.switch--sm .switch-label { font-family: var(--f-head); font-size: 13px; font-weight: 600; white-space: nowrap; }

/* ---- Time -------------------------------------------------------------- */
.drawer .timer-btn[data-face="full"] { padding: 5px 11px; font-size: 12px; }
.drawer .dsec-sum .time-total { font-size: 12px; gap: 5px; }
.drawer .dsec-sum .time-total b, .drawer .dsec-sum .time-total-num { font-size: 13px; }
.drawer .dsec-sum .timer-live { font-size: 11.5px; }

/* ---- Brief -------------------------------------------------------------- */
.drawer .brief-box { margin-bottom: 0; padding: 10px 13px; border-radius: 10px; font-size: 13.5px; line-height: 1.6; }

/* ---- Checklist ----------------------------------------------------------- */
.chk-bar.chk-bar--mini { display: inline-block; vertical-align: middle; width: 72px; height: 5px; margin-right: 8px; }
.drawer .checklist { margin-bottom: 0; }
.drawer .chk-item { padding: 2px 4px 2px 2px; gap: 8px; border-radius: 7px; }
.drawer .chk-text { font-size: 13px; line-height: 1.45; }
.drawer .chk-item .btn-icon { width: 24px; height: 24px; font-size: 12px; }
.drawer .chk-add { margin-top: 5px; gap: 6px; }
.drawer .chk-add .inp { padding: 5px 10px; font-size: 13px; }
.drawer .chk-add .btn { padding: 5px 11px; }

/* ---- Dependencies: the two lists side by side ------------------------------ */
.drawer .deps {
  margin-bottom: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 8px 18px;
}
.drawer .dep-block > .lbl { display: block; margin-bottom: 5px; font-size: 11px; }
.drawer .dep-list { gap: 4px; margin-bottom: 6px; }
.drawer .dep-row { padding: 4px 8px; gap: 8px; border-radius: 8px; font-size: 12.5px; }
.drawer .dep-row .pill { padding: 1px 7px; font-size: 10.5px; }
.drawer .dep-add-btn { padding: 4px 9px; font-size: 12px; }

/* ---- files: one line each, two to a row when there is room ----------------- */
.drawer .drawer-body .filelist { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 5px 8px; }
.drawer .drawer-body .filelist > .hint { grid-column: 1 / -1; }
.drawer .filerow { padding: 4px 6px 4px 5px; gap: 8px; border-radius: 8px; min-width: 0; }
.drawer .file-ico { width: 26px; height: 26px; border-radius: 6px; font-size: 12px; }
.drawer .file-thumb { width: 30px; height: 30px; border-radius: 6px; }
.drawer .file-name { font-size: 12.5px; line-height: 1.3; }
.drawer .file-sub { font-size: 11px; line-height: 1.3; }
.drawer .filerow .btn-icon { width: 26px; height: 26px; font-size: 13px; border-radius: 7px; }
.drawer .file-star { font-size: 14px; }
/* The time log is two short facts per row — name and when — on a single line. */
.drawer .time-log .file-main { flex-direction: row; align-items: baseline; gap: 8px; }
.drawer .time-log .file-name { flex: none; max-width: 50%; }
.drawer .time-log .filerow { padding: 3px 6px; background: none; border-color: transparent; }
.drawer .time-log .file-ico { width: 20px; height: 20px; background: none; }

.drawer .dropzone { margin-top: 6px; padding: 5px 9px; gap: 9px; border-radius: 9px; font-size: 12px; }
.drawer .dropzone-ico { width: 24px; height: 24px; border-radius: 7px; font-size: 12px; }
.drawer .dropzone-txt { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px 12px; }
.drawer .dropzone .btn-sm { padding: 2px 8px; font-size: 12px; }
.drawer .client-sees { margin-bottom: 6px !important; font-size: 11.5px; }
.drawer .old-finals { margin-top: 6px; }

/* ---- the activity pane ------------------------------------------------------ */
.drawer .drawer-side > .act-log { padding: 10px 14px 4px; }
.drawer .act-log-sum { min-height: 30px; }
.drawer .act-log[open] > .act-log-sum { margin-bottom: 8px; }
.drawer .thread { gap: 10px; }
.drawer .thread .cmt { gap: 9px; }
.drawer .thread .cmt > .avatar { width: 24px; height: 24px; font-size: 9.5px; }
.drawer .thread .cmt-kids .avatar { width: 20px; height: 20px; font-size: 9px; }
.drawer .cmt-author { font-size: 12.5px; }
.drawer .cmt-head { gap: 6px; margin-bottom: 2px; }
.drawer .drawer-foot { padding: 9px 12px; }
.drawer .composer { gap: 6px; }
.drawer .composer-row { gap: 8px; }
/* One line tall until it is being written in; it still grows with the text. */
.drawer .rich-ed--cmt { min-height: 38px; padding: 8px 11px; font-size: 13px; line-height: 1.5; }
.drawer .composer:focus-within .rich-ed--cmt { min-height: 84px; }
.drawer .composer-send { padding: 8px 14px; }

/* ---- THE THREAD ----------------------------------------------------------
 *
 * Two kinds of row, and they must not look alike.
 *
 * AN EVENT ("moved Backlog → Review") is a record, not a message: one small
 * upright line on a dotted timeline, with the member who did it named first.
 * Consecutive events close up into a block, so six of them read as one piece
 * of history rather than six rows competing with the conversation.
 *
 * A COMMENT is who, when, the words — then ONE row for everything you can do
 * to it: react, reply, share, edit, delete. Those controls used to live in
 * three places, two of them hover-only, and the space they held while hidden
 * is what made the thread look gappy.
 */
.drawer .thread { gap: 12px; }

.drawer .cmt.sys { gap: 9px; align-items: stretch; }
.drawer .cmt-sysdot { width: 24px; flex: none; position: relative; }
.drawer .cmt-sysdot::before {
  content: ''; position: absolute; left: 50%; top: 6px;
  width: 5px; height: 5px; margin-left: -2.5px; border-radius: 50%;
  background: var(--line2);
}
.drawer .cmt.sys .cmt-text {
  padding: 0; font-size: 11.5px; line-height: 1.5; font-style: normal; color: var(--faint);
}
.cmt-sysby { font-weight: 600; color: var(--mut); }
.drawer .cmt.sys .cmt-when { font-size: 10.5px; white-space: nowrap; }
/* A run of events: 3px apart, joined by a hairline from each dot down to the
   next one — however many lines the event above it wraps to. */
.drawer .thread > .cmt.sys + .cmt.sys { margin-top: -9px; }
.drawer .thread > .cmt.sys:has(+ .cmt.sys) .cmt-sysdot::after {
  content: ''; position: absolute; left: 50%; top: 13px; bottom: -7px;
  width: 1px; margin-left: -.5px; background: var(--line);
}

.drawer .cmt-head { gap: 7px; margin-bottom: 1px; align-items: baseline; }
/* The tag's text already carries its own mark (↗ or ✓); the generated arrow
   in front of it made "↗ ✓ SEEN BY CLIENT". */
.drawer .cmt-shared-tag::before { content: none; }
.drawer .cmt-author { font-size: 12.5px; }
.drawer .cmt-text { font-size: 13px; line-height: 1.55; }
.drawer .cmt.client .cmt-text,
.drawer .cmt:has(.cmt-shared-tag) .cmt-text { margin-top: 3px; padding: 7px 10px; }

.cmt-foot {
  display: flex; align-items: center; flex-wrap: wrap; gap: 0 2px;
  margin-top: 3px; min-height: 22px;
}
.cmt-foot .reactions { margin: 0 6px 0 0; gap: 4px; }
.cmt-foot .react-chip { padding: 0 7px; font-size: 11.5px; }
.cmt-foot .react-add {
  width: 22px; height: 22px; border: 0; border-radius: 6px;
  font-size: 15px; color: var(--faint);
}
.cmt-foot .react-add:hover { background: var(--panel3); color: var(--accent-fg); }
/* The wrapper dissolves, so on a narrow screen the actions wrap one by one
   beside the reactions instead of dropping to a line of their own as a block. */
.cmt-acts { display: contents; }
.cmt-act {
  padding: 2px 6px; border: 0; border-radius: 6px; background: none; cursor: pointer;
  font-family: var(--f-head); font-size: 11px; font-weight: 600; line-height: 1.5;
  color: var(--faint); white-space: nowrap;
  transition: color .14s var(--ease), background .14s var(--ease), opacity .14s var(--ease);
}
.cmt-act:hover { color: var(--paper); background: var(--panel3); }
.cmt-act.danger:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }
.cmt-act:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; }
.cmt-act:disabled { opacity: .4; cursor: default; }
.cmt-act-share.on { color: var(--accent-fg); }
.cmt.is-answering > .cmt-body .cmt-act[data-cmt-reply] { color: var(--accent-fg); }
/* Nothing in the row is hover-only. A control that is invisible until the
   pointer arrives still takes its space, and in a narrow pane that space is a
   blank line under the comment — the very gap this row exists to remove. They
   are quiet instead: faint until the comment is hovered. */
.cmt:hover > .cmt-body > .cmt-foot .cmt-act:not(:hover):not(.on),
.cmt:focus-within > .cmt-body > .cmt-foot .cmt-act:not(:hover):not(.on) { color: var(--mut); }

/* Replies hang under the parent's words: 24px of avatar + the 9px gap. */
.drawer .cmt-kids { margin: 8px 0 0 33px; padding-left: 11px; gap: 10px; }
.drawer .thread > .cmt-kids { margin-top: -4px; }

.side-bar, .side-rail { display: none; }

@media (min-width: 1100px) {
  /* The pane's own header strip: its name, and its minimise / maximise. The
     <details> summary that does this job in one column is redundant here. */
  .side-bar {
    display: flex; align-items: center; gap: 2px; flex: none;
    padding: 7px 8px 6px 14px; border-bottom: 1px solid var(--line);
  }
  .side-title {
    flex: 1; min-width: 0;
    font-family: var(--f-head); font-size: 11px; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase; color: var(--mut);
  }
  .side-title span { color: var(--faint); }
  .side-bar .btn-icon { width: 28px; height: 28px; font-size: 13px; border-radius: 7px; }
  .drawer .drawer-side .act-log-sum { display: none; }
  .drawer .drawer-side > .act-log { padding-top: 12px; }

  /* Activity minimised: a rail, and the drawer gives the board its room back. */
  .drawer.split-task { width: min(900px, 96vw); }
  .drawer.split-task .drawer-cols { grid-template-columns: minmax(0, 1fr) 40px; }
  .drawer.split-task .drawer-side > .act-log,
  .drawer.split-task .drawer-side > .drawer-foot,
  .drawer.split-task .side-bar { display: none; }
  .drawer.split-task .side-rail {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    width: 100%; height: 100%; padding: 12px 0;
    background: none; border: 0; color: var(--mut); cursor: pointer;
    font-family: var(--f-head); font-size: 11px; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase;
    transition: background .16s var(--ease), color .16s var(--ease);
  }
  .drawer.split-task .side-rail:hover { background: var(--panel3); color: var(--paper); }
  .side-rail-ico { font-size: 14px; letter-spacing: 0; }
  .side-rail-txt { writing-mode: vertical-rl; }
  .side-rail-n {
    min-width: 20px; padding: 2px 5px; border-radius: 999px; text-align: center;
    background: var(--accent); color: var(--accent-ink);
    font-size: 10.5px; letter-spacing: 0;
  }

  /* Activity maximised: the conversation takes the drawer, the task steps out. */
  .drawer.split-activity { width: min(820px, 96vw); }
  .drawer.split-activity .drawer-cols { grid-template-columns: minmax(0, 1fr); }
  .drawer.split-activity .drawer-cols > .drawer-body { display: none; }
  .drawer.split-activity .drawer-side { border-left: 0; }
  .drawer.split-activity [data-split="activity"] { color: var(--accent-fg); background: var(--accent-soft); }
}

/* Phones: the six header buttons take the first line and the title gets the
   full width under them, instead of being squeezed into a five-line column. */
@media (max-width: 560px) {
  .drawer .drawer-head { flex-wrap: wrap; justify-content: flex-end; row-gap: 2px; padding-top: 6px; }
  .drawer .drawer-head .grow { order: 2; flex: 1 0 100%; }
}

/* Phones: 16px controls, or iOS zooms the page on focus (same floor the old
   meta cells had). */
@media (max-width: 720px) {
  .prop .sel, .prop .inp { font-size: 16px; }
  .props { grid-template-columns: minmax(0, 1fr); }
  .prop { grid-template-columns: 104px minmax(0, 1fr); min-height: 38px; }
}


/* ===================================================================
   Irada Hub additions
   =================================================================== */

/* The brand touch on the shell: a 2px gradient line under the top bar. */
.topbar { border-bottom-color: transparent; }
.topbar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--brand-grad); pointer-events: none;
}
.brand .brand-logo { height: 28px; }
.brand-app { color: var(--paper); letter-spacing: .12em; }

/* Sign-in: navy ground, the brand gradient as a soft glow, a white mark on a
   navy band at the top of the (neutral) card. */
.auth { background: var(--brand-navy); color-scheme: dark; }
.auth-glow {
  background:
    radial-gradient(circle at 30% 30%, color-mix(in srgb, var(--brand-pink) 34%, transparent), transparent 60%),
    radial-gradient(circle at 70% 60%, color-mix(in srgb, var(--brand-purple) 34%, transparent), transparent 62%),
    radial-gradient(circle at 50% 90%, color-mix(in srgb, var(--brand-cyan) 26%, transparent), transparent 60%);
  opacity: .8;
}
.auth-card { overflow: hidden; }
.auth-brand {
  margin: -34px -30px 26px; padding: 22px 30px 20px;
  background: var(--brand-navy);
  position: relative;
}
.auth-brand::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--brand-grad);
}
.auth-brand .brand-logo { height: 38px; }
.auth-brand .brand-fallback { color: #fff; }
.auth-legal { color: rgba(255,255,255,.72); }
@media (max-width: 560px) {
  .auth-brand { margin: -26px -20px 22px; padding: 18px 20px 16px; }
}

/* Roles: the collaborator pill, and their entity under it. */
.role-pill--collab {
  color: var(--st-progress-ink);
  background: color-mix(in srgb, var(--st-progress) 14%, transparent);
  border-color: color-mix(in srgb, var(--st-progress) 34%, transparent);
}
.member-entity { margin-top: 4px; font-size: 11.5px; }
.role-help { margin: 0 0 14px; }

/* Invite outcome inside the PIN callout. */
.invite-status {
  margin: 0 0 14px; padding: 10px 12px; border-radius: var(--r-sm);
  font-size: 13px; line-height: 1.5; border: 1px solid var(--line);
}
.invite-status.is-ok {
  color: var(--st-done-ink);
  background: color-mix(in srgb, var(--st-done) 10%, transparent);
  border-color: color-mix(in srgb, var(--st-done) 30%, transparent);
}
.invite-status.is-fail {
  color: var(--warnbox-ink, var(--danger));
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}

/* A task's channel (Channels module): eyebrow chip, Details link, form note. */
.chip-channel { color: var(--accent-fg); text-decoration: none; }
.chip-channel:hover { border-color: var(--accent-line); }
.channel-link { color: var(--accent-fg); font-weight: 600; text-decoration: none; }
.channel-link:hover { text-decoration: underline; }
.tf-channel { margin: 0 0 12px; }

/* Module hosts. A module's navSection sits at the top of the sidebar; the
   module owns everything inside it (chat.css / events.css). */
.nav-module { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
.nav-module:empty { display: none; }
.view--module { min-height: 0; }
