/* ==========================================================================
   MarkerOps — team app design system (phase 2)
   Marker Media: ink surfaces, a PER-MEMBER accent, dark + light modes.

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

  --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, Roboto, sans-serif;
  --f-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

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

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

/* ---------------------------------------------------------------- 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:  #38bdf8;
  --st-review:    #a78bfa;
  --st-client:    #fbbf24;
  --st-revisions: #fb7185;
  --st-done:      #DAFD1B;
  --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:  #0284C7;
  --st-review:    #7C3AED;
  --st-client:    #B45309;
  --st-revisions: #E11D48;
  --st-done:      #65A30D;
  --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: #DAFD1B;

  /*
   * 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 writes --accent-ink inline on <html> from
   * its own accentInk(), whose luminance threshold (0.42) is too high: it
   * hands WHITE to violet and rose, landing at 2.7:1 — a fail. Inline styles
   * outrank a normal rule, so this is the only way the stylesheet can
   * guarantee legible ink. When app.js adopts the 0.179 crossover the two
   * agree and this !important becomes a no-op. See the handover note.
   */
  --accent-ink: #0B0B0C;
  --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: #e6ff45;
  --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(218,253,27,.14);
  --accent-soft:  color-mix(in srgb, var(--accent) 15%, transparent);
  --accent-soft2: rgba(218,253,27,.30);
  --accent-soft2: color-mix(in srgb, var(--accent) 30%, transparent);
  --accent-glow:  rgba(218,253,27,.40);
  --accent-glow:  color-mix(in srgb, var(--accent) 40%, transparent);
}

/*
 * 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; }
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);
  outline: none;
  transition: border-color .16s var(--ease), background .16s var(--ease), box-shadow .16s var(--ease);
}
.inp:hover, .sel:hover, .txa:hover { border-color: var(--line2); }
.inp:focus, .sel:focus, .txa:focus {
  border-color: var(--accent-line);
  background: var(--focus-bg);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.txa { resize: vertical; min-height: 96px; line-height: 1.6; }
.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; }
.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; }
.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; }

.avatar {
  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); }

.unread {
  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;
}

.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 swap. app.js hard-codes the white PNG's src, so the light-mode
 * counterpart is swapped in with `content:` rather than by changing @src.
 * Falls back to the white mark on engines without it (see the notes in the
 * handover) — everything else in light mode is unaffected.
 */
[data-theme="light"] .brand-logo { content: url('img/logo-markermedia-dark.png'); }

.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; outline: none; color: var(--paper);
  font-family: var(--f-head); font-weight: 600; font-size: 13px; width: 132px;
}
.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)); }

.sidenav {
  width: var(--nav-w); flex: none;
  border-right: 1px solid var(--line);
  padding: 16px 10px;
  position: sticky; top: var(--topbar-h);
  height: calc(100vh - var(--topbar-h));
  display: flex; flex-direction: column; gap: 4px;
  background: var(--nav-bg);
}
.nav-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; padding: 10px 13px; border-radius: var(--r-sm);
  font-family: var(--f-head); font-weight: 600; font-size: 14px;
  color: var(--mut); text-align: left;
  position: relative; overflow: hidden;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.nav-item .ico { width: 20px; text-align: center; font-size: 15px; flex: none; }
.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: 9px; bottom: 9px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--accent-line);
}
.nav-count {
  margin-left: auto; 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); }
.nav-spacer { flex: 1; }
.nav-foot { padding: 10px 13px; font-size: 11px; color: var(--faint); line-height: 1.5; }

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

.view-head {
  display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
  margin-bottom: 22px;
}
.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; }
.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; }

/* --- 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;
  max-height: calc(100vh - var(--topbar-h) - 190px);
  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 {
  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);
}

.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; }
.drawer-foot { border-top: 1px solid var(--line); padding: 13px 20px; background: var(--panel2); }

.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;
}
.meta-cell .sel:focus, .meta-cell .inp:focus { box-shadow: none; background: transparent; }
.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); }
.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: 13px; }
.cmt { display: flex; gap: 11px; animation: fadeUp .3s var(--ease) both; }
.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); }
.cmt-text {
  background: var(--panel2); border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 13px; font-size: 13.5px; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
}
.cmt:hover .cmt-text { border-color: var(--line2); }
/* 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;
}
.rich ul, .rich ol { margin: 4px 0 8px; padding-left: 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; }
.composer-row { display: flex; gap: 10px; align-items: flex-end; }
.composer-row .txa { flex: 1; min-height: 46px; max-height: 200px; padding: 11px 13px; }
.composer-preview {
  flex: 1; min-height: 46px; max-height: 200px; overflow-y: auto;
  background: var(--panel2); border: 1px solid var(--line2); border-radius: 12px;
  padding: 11px 13px; font-size: 13.5px; line-height: 1.6;
}
.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, #DAFD1B) 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; }

.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; }
.who { display: flex; align-items: center; gap: 10px; }
.who-name { font-family: var(--f-head); font-weight: 600; }
.who-sub { font-size: 11.5px; color: var(--faint); }
.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; }

.empty {
  text-align: center; padding: 52px 20px; color: var(--faint);
  border: 1px dashed var(--line2); border-radius: var(--r-lg); background: var(--col-bg);
}
.empty-ico { font-size: 32px; margin-bottom: 12px; opacity: .8; }
.empty h3 { font-size: 15.5px; color: var(--paper); margin-bottom: 6px; }
.empty p { font-size: 13px; max-width: 340px; margin: 0 auto 16px; line-height: 1.6; }

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

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

/* ---------------------------------------------------- 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); }
.bell-dot {
  position: absolute; top: 3px; right: 3px;
  min-width: 9px; height: 9px; border-radius: 999px;
  background: var(--accent-line);
  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; }
.notif-menu > [data-list] { overflow-y: auto; padding: 6px; }

.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; }
.cal-more {
  display: block; font-size: 10.5px; font-weight: 600; color: var(--faint);
  font-family: var(--f-head); padding-left: 4px; text-align: left;
}

/* -------------------------------------------------------- 15. workload */

.load { display: flex; flex-direction: column; gap: 4px; }
.load-row {
  display: grid; grid-template-columns: 178px minmax(0, 1fr) auto;
  align-items: center; gap: 12px;
  width: 100%; text-align: left;
  padding: 11px 8px; border-radius: 11px;
  border: 1px solid transparent;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.load-row:hover { background: var(--panel2); border-color: var(--line); }
.load-row.over { background: color-mix(in srgb, var(--danger) 6%, transparent); }
.load-row.over:hover { border-color: color-mix(in srgb, var(--danger) 30%, transparent); }

.load-name { display: flex; align-items: center; gap: 9px; min-width: 0; }
.load-name .who-av { width: 28px; height: 28px; font-size: 11px; }
.load-who { min-width: 0; display: flex; flex-direction: column; }
.load-who .who-name {
  font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.load-flag {
  font-family: var(--f-head); font-size: 10px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase; color: var(--danger);
}

/* .load-track is the full-width rail; .load-bar is the member's share of the
   busiest member's load, so bar widths compare across rows. */
.load-track {
  display: block; min-width: 0; height: 12px;
  border-radius: 999px; background: var(--panel3);
  box-shadow: inset 0 0 0 1px var(--line);
  overflow: hidden;
}
.load-bar {
  display: flex; height: 100%; width: 0; border-radius: 999px; overflow: hidden;
  transition: width .9s var(--ease);
}
.load-seg { min-width: 0; transition: filter .16s var(--ease); }
.load-seg + .load-seg { box-shadow: inset 1px 0 0 color-mix(in srgb, var(--panel) 70%, transparent); }
.load-bar:hover .load-seg:hover { filter: brightness(1.15); }

.load-meta {
  display: flex; align-items: center; gap: 14px;
  font-size: 12px; color: var(--mut); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.load-stat b {
  font-family: var(--f-head); font-weight: 700; color: var(--paper); font-size: 13.5px;
}
.load-stat.warn b  { color: var(--warn); }
.load-stat.alert   { color: var(--danger); }
.load-stat.alert b { color: var(--danger); }

/* --------------------------------------- 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]       { 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]       { 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]       { color: var(--st-revisions-ink) !important; border-color: color-mix(in srgb, var(--st-revisions) 40%, transparent) !important; }
.pill[data-status="done"],
.pill[style*="#DAFD1B" 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; }

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

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

@media (max-width: 1080px) {
  .dash-cols, .cdetail-cols { grid-template-columns: 1fr; }
  .view { padding: 22px 18px 96px; }
  .load-row { grid-template-columns: 160px minmax(0, 1fr); row-gap: 8px; }
  .load-meta { grid-column: 2 / 3; justify-content: flex-start; }
  .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);
  }
  .nav-item {
    flex-direction: column; gap: 3px; padding: 7px 4px; font-size: 10.5px;
    letter-spacing: .01em; justify-content: center; text-align: center;
  }
  .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;
  }
  .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; }
  .meta-grid { grid-template-columns: 1fr 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; }

  .load-row { grid-template-columns: minmax(0, 1fr); row-gap: 8px; }
  .load-meta { grid-column: 1; gap: 12px; font-size: 11.5px; flex-wrap: wrap; }
  .active-row { padding: 9px; }
}

/*
 * The topbar is the one row that cannot wrap (it is a fixed 62px). Below 560px
 * it becomes a scroller instead of pushing the page sideways: measured 169px of
 * horizontal PAGE overflow at 375px otherwise, which would let the whole layout
 * be dragged off-screen. Everything stays reachable, the bar just scrolls.
 */
@media (max-width: 560px) {
  .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; }
  .bell, .btn-icon { width: 31px; height: 31px; }
  .userchip { padding: 3px; }
}

/* Below 560px a calendar cell is too narrow for a title: the chips collapse
   to accent dots. They keep their tap target, their tooltip and their click
   handler, so nothing becomes unreachable — only shorter. */
@media (max-width: 560px) {
  .cal-day { min-height: 60px; }
  .cal-items { flex-direction: row; flex-wrap: wrap; gap: 4px; }
  .cal-chip {
    width: 16px; height: 16px; min-width: 16px; padding: 0;
    border-radius: 50%; border-left: none;
    background: var(--accent); justify-content: center;
  }
  .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 Tools group (admin only) ---------------------------------- */
/* A labelled section under the main nav. */
.nav-group {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 10px; padding-top: 11px;
  border-top: 1px solid var(--line);
}
/* The top notification bar's container — a plain stack, NOT a sidebar group.
   (It previously borrowed .toolsbar and inherited the group's divider + gap.) */
.annbar-wrap { display: flex; flex-direction: column; gap: 8px; }
.annbar-wrap:empty { display: none; }
.nav-group-label {
  padding: 2px 13px 5px;
  font-family: var(--f-head); font-size: 10.5px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; color: var(--faint);
}

/* --- notification bar (below the topbar) ------------------------------- */
/* 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. */
.annbar {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 11px 14px 11px 15px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--lv, var(--accent-line));
  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; }
.annbar-main { flex: 1; min-width: 0; }
.annbar b, .annbar strong, .annbar-title {
  font-family: var(--f-head); font-weight: 700; letter-spacing: -.01em;
}
.annbar-body { color: inherit; opacity: .9; }
.annbar a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.annbar-dismiss {
  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);
}
.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;
}
.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));
}
.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 -------------------------------------------------- */
@media (max-width: 720px) {
  /* the sidebar is a horizontal bottom bar here — flatten the Tools group so
     its items join the row, and let the bar scroll if the extra admin items
     overflow. */
  .nav-group { display: contents; }
  .nav-group-label { display: none; }
  .sidenav { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none; }
  .sidenav::-webkit-scrollbar { display: none; }

  .updates-panel { width: 100%; border-left: none; }
  .annbar { padding: 10px 12px 10px 13px; font-size: 13px; }
}
@media (max-width: 560px) {
  .lineitems-head { display: none; }
  .lineitem { grid-template-columns: 1fr 1fr 1fr 40px; row-gap: 6px; }
  .lineitem > :first-child { grid-column: 1 / -1; }   /* description spans */
  .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); }
.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. */
.tcard-labels { display: flex; flex-wrap: wrap; gap: 5px; margin: 0 0 8px 4px; }
.tcard-labels .label-chip { padding: 1px 8px; font-size: 10.5px; gap: 5px; }
.tcard-labels .label-dot { width: 7px; height: 7px; }

/* --- 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; }
}
@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 { 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);
}
.tt-inline:hover { background: var(--panel3); border-color: var(--line); }
.tt-inline:focus {
  outline: none; background: var(--focus-bg);
  border-color: var(--accent-line); box-shadow: 0 0 0 3px var(--accent-soft);
}
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;
}

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

/* --- my work view ------------------------------------------------------ */
.mywork { display: flex; flex-direction: column; gap: 18px; max-width: 860px; }

.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 { 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 {
  text-align: center; padding: 54px 22px; color: var(--faint);
  border: 1px dashed var(--line2); border-radius: var(--r-lg);
  background: var(--col-bg); line-height: 1.6;
}
.mw-empty h3 { font-family: var(--f-head); font-size: 15.5px; color: var(--paper); margin-bottom: 6px; }
.mw-empty p { font-size: 13px; max-width: 320px; margin: 0 auto; }

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

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

/* 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 .load-row.over). */
.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);
}
.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: 4px; }
  .cc-chip {
    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; }

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

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

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

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

.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. */
.viewtabs + .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 being narrow. The column is capped at 900px
 * so a due list is read, not scanned across a 27" monitor.
 */
.focus {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 900px;
}

.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 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%; 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. */
.focus-empty {
  text-align: center;
  padding: 46px 22px;
  color: var(--faint);
  border: 1px dashed var(--line2);
  border-radius: var(--r-lg);
  background: var(--col-bg);
  line-height: 1.6;
}
.focus-empty h3 {
  font-family: var(--f-head); font-size: 15.5px; color: var(--paper); margin-bottom: 6px;
}
.focus-empty p { font-size: 13px; max-width: 340px; margin: 0 auto; }
.focus-empty .empty-ico { font-size: 26px; margin-bottom: 10px; opacity: .7; }
.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 sits at the top of the index and stays there while it scrolls.
   If app.js hangs it off .guide instead, it spans both columns rather than
   becoming a third grid item (see the .guide > .guide-filter rule below). */
.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;
  display: flex; align-items: center; gap: 9px;
  width: 100%;
  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;
  overflow: hidden;
  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); }

.guide-empty {
  text-align: center;
  padding: 52px 20px;
  color: var(--faint);
  border: 1px dashed var(--line2);
  border-radius: var(--r-lg);
  background: var(--col-bg);
  line-height: 1.6;
  font-size: 13px;
}
.guide-empty h3 {
  font-family: var(--f-head); font-size: 15.5px; color: var(--paper); margin-bottom: 6px;
}
.guide-empty p { max-width: 340px; margin: 0 auto; }

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

@media (max-width: 980px) {
  /* 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 { margin: 10px 0 16px; padding-left: 0; }

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

  .focus { gap: 13px; }
  .focus-head { padding-bottom: 12px; }
  .focus-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 */
  .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);
  }
  .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 */
  .guide-filter { position: static; 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: 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.
 */
.fin-scroll, .fin-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);
}
.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 {
  text-align: center; padding: 40px 20px;
  color: var(--faint); font-size: 13px; line-height: 1.6;
  border: 1px dashed var(--line2); border-radius: var(--r);
  background: var(--col-bg);
}
.chg-empty h3, .fin-empty h3 {
  font-family: var(--f-head); font-size: 15px; color: var(--paper); margin-bottom: 6px;
}
.chg-empty p, .fin-empty p { max-width: 360px; margin: 0 auto; }

/* --- 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 */
  .fin-filters .sel, .fin-filters .inp { 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; }
}
