:root{
    --ground:#F2F4F8; --panel:#FFFFFF; --panel-2:#F7F9FC;
    --ink:#161A21; --muted:#586170; --faint:#8791A0; --line:#E5E9F0; --line-2:#EEF1F6;
    --accent:#5A57D8; --accent-soft:#ECECFB; --accent-ink:#4744C6;
    --warm:#C2761F; --warm-soft:#FAEFDD; --good:#1E9B57; --good-soft:#E4F4EB;
    --info:#3A78D0; --info-soft:#E5EFFB; --warn:#BC760D; --warn-soft:#F9EFDB;
    --crit:#D44343; --crit-soft:#FAE7E7; --bb:#7A8593; --bb-soft:#EDF0F4;
    --shadow:0 1px 2px rgba(16,22,40,.04),0 3px 14px rgba(16,22,40,.05);
    --shadow-lg:0 16px 50px rgba(16,22,40,.16); --r:13px; --r-sm:9px;
    --font-ui:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    --font-num:ui-monospace,"SF Mono","Cascadia Mono","Roboto Mono",Menlo,monospace;
    /* Handwriting faces. BOTH stay loaded (index.html) even though only one is in use: Birthstone is
       kept deliberately, for other surfaces later, and so that swapping the signature back to it is
       two lines here rather than a round trip through the font request.

       Every fallback is a real cursive face shipping with Windows or macOS, so the effect survives
       the web font not arriving — a signature that fails to a sans-serif stops reading as a
       signature at all. */
    --font-sig-fallback:"Segoe Script","Brush Script MT","Snell Roundhand",cursive;
    --font-sig-alexbrush:"Alex Brush",var(--font-sig-fallback);
    --font-sig-birthstone:"Birthstone",var(--font-sig-fallback);
    /* Size travels WITH the face, because the two are not interchangeable at one value: Alex Brush
       is heavier and slanted and settles at 18px, Birthstone is lighter and more upright and needs
       22px to carry the same weight beside the pill. Switching faces means switching both lines. */
    --font-sig:var(--font-sig-alexbrush);
    --font-sig-size:18px;              /* 22px if switching to --font-sig-birthstone */
  }
  @media (prefers-color-scheme:dark){:root{
    --ground:#0D1016; --panel:#151922; --panel-2:#1A1F29; --ink:#E6EAF2; --muted:#96A0AF; --faint:#68717F;
    --line:#242A36; --line-2:#1E232E; --accent:#8A8BF2; --accent-soft:#1D2038; --accent-ink:#B3B3F8;
    --warm:#DA9A4B; --warm-soft:#2A2012; --good:#3EC079; --good-soft:#11271A; --info:#5D9AE7; --info-soft:#131E2D;
    --warn:#D89A3B; --warn-soft:#2A2012; --crit:#E96D6D; --crit-soft:#2C1717; --bb:#808A98; --bb-soft:#1C212B;
    --shadow:0 1px 2px rgba(0,0,0,.3),0 6px 20px rgba(0,0,0,.3); --shadow-lg:0 20px 60px rgba(0,0,0,.55);
  }}
  :root[data-theme="light"]{--ground:#F2F4F8;--panel:#FFFFFF;--panel-2:#F7F9FC;--ink:#161A21;--muted:#586170;--faint:#8791A0;--line:#E5E9F0;--line-2:#EEF1F6;--accent:#5A57D8;--accent-soft:#ECECFB;--accent-ink:#4744C6;--warm:#C2761F;--warm-soft:#FAEFDD;--good:#1E9B57;--good-soft:#E4F4EB;--info:#3A78D0;--info-soft:#E5EFFB;--warn:#BC760D;--warn-soft:#F9EFDB;--crit:#D44343;--crit-soft:#FAE7E7;--bb:#7A8593;--bb-soft:#EDF0F4;--shadow:0 1px 2px rgba(16,22,40,.04),0 3px 14px rgba(16,22,40,.05);--shadow-lg:0 16px 50px rgba(16,22,40,.16);}
  :root[data-theme="dark"]{--ground:#0D1016;--panel:#151922;--panel-2:#1A1F29;--ink:#E6EAF2;--muted:#96A0AF;--faint:#68717F;--line:#242A36;--line-2:#1E232E;--accent:#8A8BF2;--accent-soft:#1D2038;--accent-ink:#B3B3F8;--warm:#DA9A4B;--warm-soft:#2A2012;--good:#3EC079;--good-soft:#11271A;--info:#5D9AE7;--info-soft:#131E2D;--warn:#D89A3B;--warn-soft:#2A2012;--crit:#E96D6D;--crit-soft:#2C1717;--bb:#808A98;--bb-soft:#1C212B;--shadow:0 1px 2px rgba(0,0,0,.3),0 6px 20px rgba(0,0,0,.3);--shadow-lg:0 20px 60px rgba(0,0,0,.55);}

  *{box-sizing:border-box}
  /* ---- Count badges (.badge) — one primitive, see DESIGN-SYSTEM.md §Count badges ----
     Two rules, both learned the hard way:
       1. A badge NEVER relies on space outside its host box. Every rail and bar in this app is a
          scroll container (#nav and #main are overflow:auto), so anything hanging outside a button
          or a nav item gets sliced by the nearest one. Corner badges are pinned inside the host.
       2. The ring that separates a badge from the icon beneath it is a box-shadow, NOT a border.
          `*{box-sizing:border-box}` means a 2px border eats 4px of a 16px pill, leaving 12px of
          interior for a taller line box — the digits then paint onto the ring and read as cut off.
          Glyphs are centred by grid + line-height:1 so the text box can never exceed the pill. */
  .badge{--badge-h:16px;display:inline-grid;place-items:center;min-width:var(--badge-h);height:var(--badge-h);padding:0 5px;
    background:var(--crit);color:#fff;border-radius:999px;font-size:10.5px;font-weight:700;line-height:1;
    font-variant-numeric:tabular-nums;white-space:nowrap}
  body{margin:0;background:var(--ground);color:var(--ink);font-family:var(--font-ui);font-size:14px;line-height:1.45;-webkit-font-smoothing:antialiased}
  .num{font-family:var(--font-num);font-variant-numeric:tabular-nums}
  .eyebrow{font-size:11px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
  h1,h2,h3{margin:0;text-wrap:balance}
  a{color:inherit;text-decoration:none} button{font-family:inherit;cursor:pointer}
  :focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}

  .app{display:grid;grid-template-columns:228px 1fr;min-height:100vh}
  .app.collapsed{grid-template-columns:62px 1fr}
  .sidebar{background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;padding:14px 12px}
  .brand{display:flex;align-items:center;gap:9px;padding:6px 8px 12px;font-weight:700;font-size:18px}
  .brand .mark{width:26px;height:26px;border-radius:8px;flex:none;display:grid;place-items:center;background:linear-gradient(135deg,var(--accent),#847fee);color:#fff;font-weight:800}
  .brand .word{font-style:italic}
  .app.collapsed .brand .word,.app.collapsed .nav-label,.app.collapsed .nav-group-label,.app.collapsed .u-meta{display:none}
  .nav{display:flex;flex-direction:column;gap:1px;margin-top:2px;overflow-y:auto}
  .nav-group-label{font-size:10px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--faint);padding:12px 10px 5px}
  .nav a{display:flex;align-items:center;gap:11px;padding:8px 10px;border-radius:var(--r-sm);color:var(--muted);font-weight:500;position:relative}
  .nav a .ic{width:18px;height:18px;flex:none;stroke:currentColor;stroke-width:1.8;fill:none;opacity:.9}
  .nav a:hover{background:var(--line-2);color:var(--ink)}
  .nav a.active{background:var(--accent-soft);color:var(--accent-ink);font-weight:600}
  .nav a .badge{margin-left:auto;flex:none}
  /* The label yields before the badge does — a long nav label must ellipsis, never push the count
     out past the rail's edge (.nav is a scroll container, so anything pushed out is sliced off). */
  .nav a .nav-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .app.collapsed .nav a{justify-content:center;padding:9px}
  /* Collapsed rail: there is no room beside the icon (37px item), so the count stops being a
     flow item and becomes a corner marker pinned INSIDE the item — see §Count badges. */
  .app.collapsed .nav a .badge{position:absolute;top:1px;right:1px;margin:0;--badge-h:15px;font-size:9px;padding:0 4px;box-shadow:0 0 0 2px var(--panel)}
  .app.collapsed .nav a.active .badge{box-shadow:0 0 0 2px var(--accent-soft)}
  .side-foot{margin-top:auto;border-top:1px solid var(--line);padding-top:10px;display:flex;align-items:center;gap:9px;padding-left:6px}
  /* position/overflow so a borrowed headshot can sit OVER the initials (js/engine.js uAv, phase 3):
     the letters show while the image loads, and stay if it fails — onerror removes the img, so a
     bridge outage degrades to the plain coloured circle rather than an empty disc. */
  .avatar{width:28px;height:28px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:11px;font-weight:700;color:#fff;position:relative;overflow:hidden}
  .avatar img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit;display:block}
  .u-meta{min-width:0;line-height:1.25}.u-meta .n{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.u-meta .r{font-size:11px;color:var(--faint)}
  .um-item{padding:8px 10px;border-radius:7px;font-size:13px;color:var(--ink)}.um-item:hover{background:var(--panel-2)}

  .main{min-width:0;display:flex;flex-direction:column;max-height:100vh;overflow-y:auto}
  .topbar{position:sticky;top:var(--banner-h,0px);z-index:20;background:color-mix(in srgb,var(--ground) 84%,transparent);backdrop-filter:blur(10px);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;padding:10px 22px}
  .icon-btn{width:34px;height:34px;border-radius:9px;border:1px solid var(--line);background:var(--panel);display:grid;place-items:center;color:var(--muted)}
  .icon-btn:hover{color:var(--ink);border-color:var(--faint)}.icon-btn .ic{width:17px;height:17px;stroke:currentColor;stroke-width:1.8;fill:none}
  /* Compact variant for repeated per-row actions in table cells (see DESIGN-SYSTEM.md §Icon actions) —
     a common, self-explanatory action (refresh, cancel, copy…) repeated down a column should be a
     small icon + tooltip, not a text button re-reading the same word N times down the page. */
  .icon-btn.sm{width:26px;height:26px;border-radius:7px;display:inline-grid}
  .icon-btn.sm .ic{width:14px;height:14px}
  .icon-btn.crit{color:var(--crit)}.icon-btn.crit:hover{border-color:var(--crit)}
  .search{flex:1;max-width:380px;display:flex;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:7px 12px;color:var(--faint)}
  .search .ic{width:16px;height:16px;stroke:currentColor;stroke-width:1.9;fill:none}
  .search input{border:0;background:transparent;color:var(--ink);flex:1;outline:none;font-size:13px}
  .spacer{flex:1}
  .scope{display:inline-flex;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:3px}
  .scope button{border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12.5px;padding:5px 13px;border-radius:7px;display:flex;align-items:center;gap:6px}
  .scope button.on{background:var(--accent);color:#fff}
  .scope .dot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.75}
  /* ── Nav scope toggle (sidebar) ── */
  .nav-scope{padding:2px 2px 6px}
  /* inline-flex keeps the pill at natural width — not stretched to the full sidebar */
  .nav-scope .scope{display:inline-flex}
  .nav-scope .scope button{flex:1;justify-content:center;padding:5px 10px;font-size:12px}
  /* single-letter abbreviations (M/F) only shown in the collapsed rail */
  .scope-short{display:none}
  /* Section separators — between Practice/Personal and Personal/Admin */
  .nav-sep{height:1px;background:var(--line);margin:8px 4px 4px}
  /* Collapsed rail: scope toggle hidden — it's a setting, change it when expanded */
  .app.collapsed .nav-scope{display:none}
  /* Separators visible in collapsed rail with slightly tighter horizontal margin */
  .app.collapsed .nav-sep{margin:6px 8px 4px}
  select.viewas{border:1px solid var(--line);background:var(--panel);color:var(--ink);border-radius:9px;padding:7px 9px;font-size:12.5px;font-weight:600;max-width:150px}
  .sync{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted);padding:6px 10px;border-radius:9px;border:1px solid var(--line);background:var(--panel);white-space:nowrap}
  .sync .live{width:7px;height:7px;border-radius:50%;background:var(--good)}
  .btn{background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:9px;padding:7px 12px;font-weight:600;font-size:12.5px;display:inline-flex;align-items:center;gap:7px}
  .btn:hover{border-color:var(--faint)}.btn .ic{width:15px;height:15px;stroke:currentColor;stroke-width:1.9;fill:none}
  .btn.accent{background:var(--accent);color:#fff;border-color:transparent}

  /* Full available width — the surface fills the viewport beside the sidebar (see DESIGN-SYSTEM.md
     §Surface panes). Wide tables get the room; no artificial column of dead space on the right. */
  .page{padding:20px 22px 70px;width:100%}
  .page-head{display:flex;align-items:flex-end;gap:14px;margin-bottom:18px;flex-wrap:wrap}
  .page-head h1{font-size:24px;letter-spacing:-.02em}.page-head .sub{color:var(--muted);font-size:13.5px;margin-top:3px}
  .surface{display:none}.surface.active{display:block;animation:fade .22s ease}
  @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
  @media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

  /* ---- HOME's four bands -------------------------------------------------------------------
     The page is a stack of labelled sections rather than a grid of cards, because the labels are
     what stop it reading as a control panel: each band says what it is and who it is for before it
     shows a single number. Generous vertical rhythm for the same reason — the old page's problem
     was density, not content. */
  .hband{margin:0 0 26px}
  .hband-h{display:flex;align-items:baseline;gap:10px;margin:0 0 11px}
  .hband-h h3{font-size:13px;font-weight:700;letter-spacing:.01em;color:var(--ink)}
  .hband-s{font-size:11.5px;color:var(--faint);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .hband-h .link{margin-left:auto;font-size:12px;color:var(--accent-ink);font-weight:600;flex:none}
  .hband .tiles{margin-bottom:0}

  /* "On you" — bigger type than the KPI tiles below it, because this band is the answer to the
     question the reader actually arrived with, and it should win the first glance. */
  .hb-figs{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:13px}
  .hb-fig{display:flex;flex-direction:column;align-items:flex-start;gap:3px;text-align:left;
    background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:15px 16px;
    box-shadow:var(--shadow);cursor:pointer;color:var(--ink);font-family:var(--font-ui)}
  .hb-fig:hover{border-color:var(--accent)}
  .hb-fig .n{font-family:var(--font-num);font-variant-numeric:tabular-nums;font-size:30px;font-weight:700;line-height:1;letter-spacing:-.02em}
  .hb-fig .l{font-size:12.5px;font-weight:600;margin-top:5px}
  .hb-fig .h{font-size:11px;color:var(--faint)}
  /* A zero is still shown — "nothing is on you" is worth knowing — but it stops inviting a click. */
  .hb-fig.zero{cursor:default;box-shadow:none;background:var(--panel-2)}
  .hb-fig.zero:hover{border-color:var(--line)}
  .hb-fig.zero .n{color:var(--faint)}
  .hb-warn{margin-left:auto;flex:none;font-size:11.5px;font-weight:700;color:var(--crit);
    background:var(--crit-soft);border:1px solid color-mix(in srgb,var(--crit) 25%,transparent);
    border-radius:999px;padding:4px 11px;cursor:pointer}
  .hb-warn:hover{border-color:var(--crit)}

  /* The practice calendar and the book, side by side. The calendar is the wider of the two: it is a
     bucket rail plus rows, where the book is four compact cards. */
  .hsplit{display:grid;grid-template-columns:1.25fr 1fr;gap:16px;align-items:start}
  .hsplit .dlc{margin-bottom:0}
  /* Pulse's charts, at half the page width — a 2x2 rather than the full-width row they had. */
  .hbook{display:grid;grid-template-columns:repeat(2,1fr);gap:13px}
  .hbook .card{margin:0}
  @media (max-width:1080px){.hsplit{grid-template-columns:1fr}.hbook{grid-template-columns:repeat(2,1fr)}}
  @media (max-width:700px){.hbook{grid-template-columns:1fr}}

  .tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:13px;margin-bottom:18px}
  .tile{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:14px 15px;box-shadow:var(--shadow)}
  .tile .lab{font-size:12px;color:var(--muted);font-weight:500}
  .tile .big{font-size:28px;font-weight:700;letter-spacing:-.02em;margin-top:7px;line-height:1}
  .tile .foot{font-size:11.5px;color:var(--faint);margin-top:6px}
  .tile.crit .big{color:var(--crit)}.tile.warm .big{color:var(--warm)}.tile.good .big{color:var(--good)}

  .split{display:grid;grid-template-columns:1fr 330px;gap:16px;align-items:start}
  .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}
  /* Me-scope indicator: subtle dashed border on cards/tiles in Me-scope-aware surfaces (Practice section only) */
  #app.scope-is-me [data-me-scope] .card,
  #app.scope-is-me [data-me-scope] .tile{border:1px dashed color-mix(in srgb,var(--accent) 40%,var(--line))}
  .void-pending-row:hover td{background:var(--panel-2)}
  /* Warming-up state (a surface withholding figures until its data has landed). Reuses the rbspin
     keyframes the refresh buttons already use — one spin animation for the whole app. */
  .warm-spin{width:26px;height:26px;border:2.5px solid var(--line-2);border-top-color:var(--accent);border-radius:50%;display:inline-block;animation:rbspin .85s linear infinite}
  .warm-pulse{animation:dtPulse 1.6s ease-in-out infinite}
  .card-h{display:flex;align-items:center;gap:10px;padding:13px 15px 10px}
  .card-h h3{font-size:14px}.card-h .count{font-family:var(--font-num);font-size:12px;color:var(--faint);background:var(--line-2);border-radius:20px;padding:1px 8px}
  .card-h .link{margin-left:auto;font-size:12.5px;color:var(--accent-ink);font-weight:600}

  .grp-lab{display:flex;align-items:center;gap:8px;padding:11px 15px 5px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
  .grp-lab.over{color:var(--crit)}.grp-lab .n{font-family:var(--font-num);color:var(--faint)}
  /* A truncated work group says how much it is hiding and opens the rest (openHomeDrill) — the
     cockpit lists a readable head of each bucket, it never silently swallows the tail. */
  .grp-lab .link{margin-left:auto;text-transform:none;letter-spacing:0;font-size:11.5px;color:var(--accent-ink);cursor:pointer}
  .grp-more{border-top:1px solid var(--line-2);padding:9px 15px;font-size:12px;color:var(--accent-ink);font-weight:600;cursor:pointer}
  .grp-more:hover{background:var(--panel-2)}
  .wrow{display:grid;grid-template-columns:4px 1fr auto;align-items:stretch;border-top:1px solid var(--line-2)}
  .wrow:hover{background:var(--panel-2);cursor:pointer}
  .wrow .stripe{border-radius:2px;margin:11px 0}
  .wrow .body{padding:10px 8px 10px 13px;min-width:0}
  /* No fixed character cap — the title uses the whole 1fr column and ellipsises only when it
     genuinely runs out of room (.wrow .body is min-width:0, so the grid track does the clamping). */
  .wrow .title{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .wrow .metaline{display:flex;align-items:center;gap:7px;margin-top:4px;flex-wrap:wrap}
  /* Inside a client's own drawer the title WRAPS instead of ellipsising. The drawer is narrow and the
     firm's job titles are long and front-loaded with the client name, so every row clipped at
     "Fineas Nedelea - Relevant Contracts T…" — the period, which is the only thing distinguishing one
     row from the next, was always the part cut off. Nothing else competes for the width there now
     (jobRow's inClient drops the duplicated client name and the owner avatars), so two lines of title
     costs nothing. Vertically centred against the taller row so the right-hand column still lines up. */
  .wrow-full .title{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.35}
  .wrow-full .right{align-items:center}
  .tag{font-size:10.5px;font-weight:600;color:var(--muted);background:var(--line-2);border-radius:6px;padding:1px 7px;white-space:nowrap}
  .tag.draft-pending{color:var(--accent-ink);background:var(--accent-soft)}
  .client{font-size:11.5px;color:var(--faint)}
  .wrow .right{padding:10px 15px;display:flex;align-items:center;gap:11px;justify-content:flex-end}
  .owners{display:flex}.owners .avatar{width:22px;height:22px;font-size:9px;border:2px solid var(--panel);margin-left:-6px}.owners .avatar:first-child{margin-left:0}
  .due{font-family:var(--font-num);font-size:11.5px;color:var(--muted);text-align:right;min-width:74px}
  .due.over{color:var(--crit);font-weight:600}.due small{display:block;color:var(--faint);font-weight:400;font-size:10px}
  .pill{font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:20px;white-space:nowrap;display:inline-flex;align-items:center;gap:5px}
  .pill .d{width:6px;height:6px;border-radius:50%;background:currentColor}
  .pill.good{color:var(--good);background:var(--good-soft)}.pill.info{color:var(--info);background:var(--info-soft)}
  .pill.warn{color:var(--warn);background:var(--warn-soft)}.pill.crit{color:var(--crit);background:var(--crit-soft)}
  .pill.bb{color:var(--bb);background:var(--bb-soft)}.pill.accent{color:var(--accent-ink);background:var(--accent-soft)}

  .rail{display:flex;flex-direction:column;gap:14px}
  .att{padding:10px 15px;border-top:1px solid var(--line-2);display:flex;gap:10px;align-items:flex-start}
  .att:hover{background:var(--panel-2)}.att .t{font-weight:600;font-size:12.5px}.att .s{font-size:11px;color:var(--faint);margin-top:2px}
  .att .when{margin-left:auto;font-family:var(--font-num);font-size:10px;color:var(--faint);white-space:nowrap}
  .presence{padding:6px}.pr{display:flex;align-items:center;gap:9px;padding:6px 9px;border-radius:8px}.pr:hover{background:var(--panel-2)}
  .pr .live{width:7px;height:7px;border-radius:50%;flex:none}.pr .nm{font-size:12.5px}.pr .fo{margin-left:auto;font-size:11px;color:var(--faint)}
  /* What someone is clocked on to (presenceRows, js/engine.js). Accent so it reads as live activity
     rather than another line of metadata; the job name is allowed to ellipsis — the row is narrow and
     the person's name must never be squeezed for it. */
  .pr .pr-clk{display:flex;align-items:center;gap:4px;margin-top:2px;font-size:10.5px;color:var(--accent-ink);min-width:0}
  .pr .pr-clk.paused{color:var(--bb)}
  .pr .pr-clk svg{width:11px;height:11px;flex:none;stroke:currentColor;stroke-width:2;fill:none}
  .pr .pr-clk span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

  .subtabs{display:inline-flex;gap:2px;background:var(--line-2);border-radius:10px;padding:3px}
  .subtabs button{border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12.5px;padding:6px 13px;border-radius:7px}
  .subtabs button.on{background:var(--panel);color:var(--ink);box-shadow:var(--shadow)}
  /* Triage's tab bar (js/mail.js renderMail) — several .subtabs pills side by side, ONE functional
     data-tabgroup on this wrapper (see the comment at the call site for why: the app's hash-routing
     walk only ever looks at the first [data-tabgroup] on a surface). Purely visual grouping — a gap
     wide enough to read as separate clusters (Emails/Triage/Insights vs. App Notifications vs.
     Follow-ups), not one long bar. Deliberately NO flex-wrap: inline-flex + wrap is a real Chromium
     sizing trap — the shrink-to-fit width collapses to just the WIDEST single child (verified live:
     three ~100-200px pills stacked vertically instead of sitting in a row, each starting flush left)
     because a wrap-enabled container's intrinsic width is computed as if only one item needs to fit
     per line. Three short pill groups never need to wrap in this header at any real window width. */
  .mail-tabgroups{display:inline-flex;gap:14px}
  /* The email-related cluster (Emails/Triage/Email Insights) tinted apart from the plain-grey
     App Notifications / Follow-ups pills beside it — asked for directly once the three groups had
     a gap between them, so the one that's actually "the email area" reads as visually distinct, not
     just spaced apart. Accent tokens, same family the rest of the app already uses for "this is the
     active/primary thing" (nav .active, .btn.accent) — not a new one-off colour. */
  .subtabs.mail-group{background:var(--accent-soft)}
  .subtabs.mail-group button{color:var(--accent-ink)}
  .subtabs.mail-group button.on{background:var(--panel);color:var(--accent-ink)}
  /* Full-page (client/job) tab bar: icon + label, matching the drawer rail. Scoped to .pg-iconbar so
     every other .subtabs control is unchanged. Icons follow the button colour (currentColor). */
  .subtabs.pg-iconbar{flex-wrap:wrap}
  .subtabs.pg-iconbar button{display:inline-flex;align-items:center;gap:6px}
  .subtabs.pg-iconbar button svg{width:15px;height:15px;flex:none;display:block}
  .tabpane{display:none}.tabpane.on{display:block}

  .toolbar{display:flex;align-items:center;gap:9px;margin-bottom:14px;flex-wrap:wrap}
  .chip{border:1px solid var(--line);background:var(--panel);color:var(--muted);font-weight:600;font-size:12px;padding:6px 11px;border-radius:20px}
  .chip.on{background:var(--accent-soft);border-color:transparent;color:var(--accent-ink)}
  select.f,input.f{border:1px solid var(--line);background:var(--panel);color:var(--ink);border-radius:9px;padding:7px 10px;font-size:12.5px;outline:none}
  input.f{min-width:200px}
  /* Client drawer → Jobs / Deadlines tab control bars. Sticky so they stay put while the (possibly
     long) list scrolls under. Full-bleed via negative margins that cancel .dwr-body's 16px/20px
     padding — otherwise a row scrolls into the padding gap ABOVE a top:0 sticky bar. */
  /* top:-16px (not 0) intentionally: the sticky anchor is .dwr-body's content-box top, which sits
     16px below the visual top because of .dwr-body's padding-top:16px. A -16px offset pulls the bar
     back up flush; the matching 16px padding-top keeps its controls clear of the clip edge. */
  .cjobs-tb,.cdls-tb{position:sticky;top:-16px;z-index:3;display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:-16px -20px 8px;padding:16px 20px 12px;background:var(--panel);border-bottom:1px solid var(--line-2)}
  .cjobs-tb input.f{flex:1 1 180px;min-width:150px}
  .cjobs-tb select.f,.cdls-tb select.f{padding:7px 9px;font-size:12px}
  .cjobs-tb label,.cdls-tb label{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:600;color:var(--muted)}
  .cjobs-tb .lnk{margin-left:auto;display:flex;gap:2px}
  .cjobs-tb .lnk button{border:0;background:transparent;color:var(--accent-ink);font-weight:600;font-size:11.5px;cursor:pointer;padding:4px 6px;border-radius:6px}
  .cjobs-tb .lnk button:hover{background:var(--accent-soft)}
  /* Collapsible group headers in the grouped jobs list. */
  .cjobs-grp{cursor:pointer;user-select:none}
  .cjobs-grp .chev{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;flex:none;transition:transform .15s;color:var(--faint)}
  .cjobs-grp.collapsed .chev{transform:rotate(-90deg)}
  .mini{display:flex;align-items:center;gap:7px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:6px 10px;color:var(--faint)}
  .mini .ic{width:15px;height:15px;stroke:currentColor;stroke-width:1.9;fill:none}.mini input{border:0;background:transparent;color:var(--ink);outline:none;font-size:12.5px;min-width:180px}

  .pulse-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:15px}.pulse-grid.three{grid-template-columns:repeat(3,1fr)}.pulse-grid.four{grid-template-columns:repeat(4,1fr)}.pulse-grid.five{grid-template-columns:repeat(5,1fr)}
  .stat{padding:13px 15px}.stat .lab{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:var(--faint);font-weight:600}
  .stat .v{font-size:25px;font-weight:700;margin-top:5px;letter-spacing:-.02em}.stat .h{font-size:11.5px;color:var(--muted);margin-top:4px}
  .bars{padding:5px 15px 13px}.bar{display:flex;align-items:center;gap:9px;padding:6px 0;border-top:1px solid var(--line-2)}.bar:first-child{border-top:0}
  .bar .bl{font-size:12px;min-width:120px;display:flex;align-items:center;gap:7px;text-align:left}
  .bar .track{flex:1;height:7px;border-radius:4px;background:var(--line-2);overflow:hidden;display:block}
  /* display:block is load-bearing. .fill is a <span>, so without it the element is INLINE and both
     width and height are ignored — which is why these bars have never drawn a fill, on Pulse or
     anywhere else. All anyone ever saw was the empty grey track. */
  .bar .fill{display:block;height:100%;border-radius:4px;min-width:2px}
  /* A drillable row is a button: same layout, but it says so on hover and takes focus. */
  .bar.clickable{border:0;background:none;font-family:var(--font-ui);color:var(--ink);cursor:pointer;border-radius:6px;padding:6px;margin:0 -6px;width:calc(100% + 12px)}
  .bar.clickable:hover{background:var(--panel-2)}
  .bar.clickable:hover .bv{color:var(--accent-ink)}
  .bar .bv{font-family:var(--font-num);font-size:12px;font-weight:600;min-width:38px;text-align:right}
  .swatch{width:9px;height:9px;border-radius:3px;flex:none}

  .tbl{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
  .tbl thead th{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--faint);font-weight:600;text-align:left;padding:10px 13px;border-bottom:1px solid var(--line);background:var(--panel-2);cursor:pointer;white-space:nowrap}
  .tbl td{padding:10px 13px;border-bottom:1px solid var(--line-2);font-size:12.5px;vertical-align:middle}
  .tbl tbody tr:last-child td{border-bottom:0}.tbl tbody tr:hover{background:var(--panel-2);cursor:pointer}
  .tbl .jn{font-weight:600}.tbl .cl{font-size:11px;color:var(--faint)}.tbl .fee{font-family:var(--font-num);font-weight:600;text-align:right}
  .table-wrap{overflow-x:auto}.pagi{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:10px 0;font-size:12px;color:var(--muted)}

  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:13px}
  .ccard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:14px;cursor:pointer}
  .ccard:hover{border-color:var(--accent)}
  .ccard .top{display:flex;align-items:center;gap:10px}.ccard .nm{font-weight:700;font-size:14px;line-height:1.2}.ccard .ty{font-size:11px;color:var(--faint);font-weight:600}
  .ccard .svcs{display:flex;gap:5px;flex-wrap:wrap;margin:11px 0 12px}.ccard .foot{display:flex;align-items:center;gap:10px;border-top:1px solid var(--line-2);padding-top:10px}
  .hdot{width:8px;height:8px;border-radius:50%;flex:none}

  .cal{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}
  .cal-h{display:flex;align-items:center;gap:12px;padding:12px 15px;border-bottom:1px solid var(--line)}.cal-h .mo{font-weight:700;font-size:15px}
  .cal-grid{display:grid;grid-template-columns:repeat(7,1fr)}
  .cal-dow{font-size:10.5px;font-weight:600;color:var(--faint);text-transform:uppercase;padding:8px 11px;border-bottom:1px solid var(--line)}
  .cal-cell{min-height:96px;border-right:1px solid var(--line-2);border-bottom:1px solid var(--line-2);padding:6px 7px;cursor:pointer}.cal-cell:nth-child(7n){border-right:0}
  .cal-cell:hover{background:var(--panel-2)}.cal-cell.out{background:var(--panel-2);opacity:.5;cursor:default}.cal-cell.out:hover{background:var(--panel-2)}
  .cal-cell.dim{background:var(--panel-2)}.cal-cell.today{outline:2px solid var(--accent);outline-offset:-2px}
  .cal-cell .dn{font-family:var(--font-num);font-size:11.5px;color:var(--muted);font-weight:600;display:flex;align-items:center;gap:5px}
  .cal-cell .dn .td{font-family:var(--font-sans);font-size:9px;font-weight:700;color:var(--accent-ink);text-transform:uppercase;letter-spacing:.04em}
  .cal-cell .dn .cnt{margin-left:auto;font-size:9.5px;background:var(--line);color:var(--muted);border-radius:8px;padding:0 5px;font-weight:700}
  .ev{font-size:10px;font-weight:600;padding:2px 5px;border-radius:4px;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
  .ev .xtag{font-size:8px;text-transform:uppercase;opacity:.8;font-weight:700}
  .ev.g{color:var(--good);background:var(--good-soft)}.ev.r{color:var(--crit);background:var(--crit-soft)}.ev.b{color:var(--info);background:var(--info-soft)}.ev.a{color:var(--warn);background:var(--warn-soft)}
  .more{font-size:10px;color:var(--faint);margin-top:2px}
  /* calendar chrome — month nav + view toggle */
  .cal-nav{display:flex;align-items:center;gap:8px}.cal-nav .mo{font-weight:700;font-size:15px;min-width:130px;text-align:center}
  .cal-od{font-size:11px;font-weight:700;color:var(--crit);background:var(--crit-soft);border-radius:20px;padding:2px 9px;margin-left:6px}
  .calseg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden}
  .calseg button{border:0;background:var(--panel);color:var(--muted);font-size:12px;font-weight:600;padding:5px 13px;cursor:pointer}
  .calseg button.on{background:var(--ink);color:var(--panel)}
  .cal-empty{margin:12px;border-color:var(--warn);color:var(--ink)}
  /* spans view — execution windows as laned bars per week */
  .cal-spans{display:flex;flex-direction:column}
  .cal-dowrow{display:grid;grid-template-columns:repeat(7,1fr)}
  .sp-week{border-bottom:1px solid var(--line-2)}.sp-week:last-child{border-bottom:0}
  .sp-hdr{display:grid;grid-template-columns:repeat(7,1fr)}
  .sp-dn{font-family:var(--font-num);font-size:10.5px;color:var(--muted);padding:4px 8px;border-right:1px solid var(--line-2)}.sp-dn:nth-child(7n){border-right:0}
  .sp-dn.out{opacity:.4}.sp-dn.today{color:var(--accent-ink);font-weight:700}
  .sp-lanes{display:grid;grid-template-columns:repeat(7,1fr);gap:3px;padding:2px 0 7px;position:relative}
  .sp-bar{border-radius:5px;font-size:10px;font-weight:600;padding:2px 7px;overflow:hidden;display:flex;align-items:center;cursor:pointer;min-width:0}
  .sp-bar .sp-lbl{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .sp-bar.info{background:var(--info-soft);color:var(--info)}.sp-bar.warn{background:var(--warn-soft);color:var(--warn)}.sp-bar.crit{background:var(--crit-soft);color:var(--crit)}.sp-bar.good{background:var(--good-soft);color:var(--good)}
  .sp-bar.temp{outline:1.5px solid currentColor;outline-offset:-1.5px}
  .sp-bar.breach{background:var(--crit);color:#fff;font-weight:700}
  .sp-bar.ghost{background:transparent;border:1px dashed var(--line);color:var(--faint);opacity:.75;cursor:default;font-style:italic}
  .sp-wall{align-self:stretch;border-left:2px dashed var(--crit);justify-self:end;width:0;pointer-events:none}
  /* reschedule dialog */
  .rs-job .nm{font-weight:700;font-size:14px}.rs-job .rs-meta{font-size:11.5px;color:var(--muted);margin-top:3px}
  .rs-quick{display:flex;gap:7px;flex-wrap:wrap}
  .rs-f{display:flex;flex-direction:column;gap:5px;font-size:12px;font-weight:600;color:var(--muted)}.rs-f .f{max-width:200px}
  .rs-preview{font-size:12.5px;color:var(--ink)}
  .rs-ok{font-size:12px;color:var(--good);background:var(--good-soft);border-radius:8px;padding:8px 11px}
  .rs-breach{font-size:12px;color:var(--crit);background:var(--crit-soft);border:1px solid var(--crit);border-radius:8px;padding:9px 11px;line-height:1.45}
  .sp-none{grid-column:1 / 8;font-size:10px;color:var(--faint);padding:0 8px}
  .swatch.wall{width:0;height:12px;border-left:2px dashed var(--crit);background:none}
  .legend{display:flex;gap:15px;flex-wrap:wrap;margin-top:12px;font-size:12px;color:var(--muted)}.legend span{display:flex;align-items:center;gap:6px}
  .dl-sec{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--faint);padding:11px 15px 4px}.dl-sec.over{color:var(--crit)}
  .dl{display:flex;align-items:center;gap:11px;padding:10px 15px;border-top:1px solid var(--line-2)}.dl:hover{background:var(--panel-2);cursor:pointer}
  .dl .dt{font-family:var(--font-num);font-size:11.5px;color:var(--muted);min-width:64px}.dl .nm{font-weight:600;font-size:13px}.dl .clx{font-size:11px;color:var(--faint)}
  .dl .due{font-family:var(--font-num);font-size:11px;color:var(--muted)}.dl .due.over{color:var(--crit);font-weight:700}
  /* Calendar → Deadlines (js/calendar-deadlines.js): explicit scope bar, bucket tabs, capped block */
  .cdl-scope{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden}
  .cdl-scope button{border:0;background:var(--panel);color:var(--muted);font-size:12px;font-weight:600;padding:5px 12px;cursor:pointer}
  .cdl-scope button b{font-family:var(--font-num);font-variant-numeric:tabular-nums;margin-left:4px}
  .cdl-scope button.on{background:var(--ink);color:var(--panel)}
  .cdl-scope button:disabled{opacity:.45;cursor:default}
  .cdl-say{padding:10px 15px 0;font-size:11.5px;color:var(--muted);line-height:1.5}
  .cdl-tabs{padding:10px 15px 2px}
  .cdl-tabs .subtabs button b{font-family:var(--font-num);font-variant-numeric:tabular-nums;margin-left:4px}
  .cdl-sech{font-weight:600;letter-spacing:0;text-transform:none;color:var(--faint)}
  .cdl-more{display:flex;align-items:center;gap:9px;padding:10px 15px;font-size:11.5px;color:var(--muted);border-top:1px solid var(--line-2)}
  .cdl-none{padding:16px 15px;font-size:12.5px;color:var(--faint)}
  /* The Calendar's head control: the scope strip over the two tabs. One column so the strip reads as
     governing everything under it, right-aligned to sit under the page title's own right edge. */
  .cal-head-ctl{display:flex;flex-direction:column;align-items:flex-end;gap:8px}
  .cdl-scope{flex-wrap:wrap}
  /* Two halves, shaded apart: what is yours reads on the panel, the firm's on the quieter ground —
     the same pairing Home's matrix uses for its bands, so the grouping means one thing app-wide. */
  .cdl-scope button.yours{font-weight:700;background:var(--panel);color:var(--ink)}
  .cdl-scope button.firm{background:var(--panel-2);color:var(--muted)}
  .cdl-scope button.edge{border-left:2px solid var(--line)}
  .cdl-scope button.on,.cdl-scope button.firm.on{background:var(--ink);color:var(--panel)}
  /* Future tab: the year multi-select. Chips rather than a <select>, because the whole point is that
     more than one year can be on at once and you can see which. */
  .cdl-years{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:11px 15px 4px}
  .cdl-years .lab{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}
  .cdl-years button{border:1px solid var(--line);background:var(--panel);color:var(--muted);font-family:var(--font-num);font-variant-numeric:tabular-nums;font-size:11.5px;font-weight:600;padding:3px 9px;border-radius:20px;cursor:pointer}
  .cdl-years button.on{background:var(--ink);color:var(--panel);border-color:var(--ink)}
  .cdl-yhint{font-size:11px;color:var(--faint)}
  /* day-detail overlay rows */
  .btn.sm{padding:4px 9px;font-size:11px;border-radius:7px}
  .cald-body{max-height:min(70vh,560px);overflow:auto;display:block;padding:0}
  .cald-row{display:flex;align-items:flex-start;gap:11px;padding:11px 16px;border-top:1px solid var(--line-2)}.cald-row:first-child{border-top:0}
  .cald-row .hdot{margin-top:4px}.cald-main{min-width:0;flex:1}.cald-main .nm{font-weight:600;font-size:13.5px}.cald-main .sub{font-size:12px;color:var(--muted)}.cald-main .meta{font-size:10.5px;color:var(--faint);margin-top:2px}
  .cald-side{display:flex;flex-direction:column;align-items:flex-end;gap:3px;flex:none}
  .cald-side .due{font-family:var(--font-num);font-size:12px;font-weight:600}.cald-side .due.over{color:var(--crit)}
  .cald-side .ld{font-size:10.5px;color:var(--faint)}
  /* Not faint italic: "no job exists for this deadline" is a finding, not a footnote. Sized to sit
     beside the Reschedule button without pretending to be one. */
  .cald-side .nolink{font-size:10.5px;color:var(--warn);font-weight:600;white-space:nowrap;background:var(--warn-soft);border:1px solid color-mix(in srgb,var(--warn) 28%,transparent);border-radius:999px;padding:3px 8px;cursor:help}
  .cald-acts{display:flex;gap:6px;align-items:center;margin-top:3px}
  /* Admin → Prototype editing toggles */
  .pf-row{display:flex;align-items:center;gap:14px;padding:12px 15px;border-top:1px solid var(--line-2)}
  .pf-row .pf-t{font-weight:600;font-size:13px}.pf-row .pf-d{font-size:11.5px;color:var(--muted);margin-top:2px}
  .pf-switch{flex:none;width:42px;height:24px;border-radius:20px;border:1px solid var(--line);background:var(--panel-2);position:relative;cursor:pointer;padding:0;transition:background .15s}
  .pf-switch .pf-knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--muted);transition:transform .15s,background .15s}
  .pf-switch.on{background:var(--warn);border-color:var(--warn)}.pf-switch.on .pf-knob{transform:translateX(18px);background:#fff}
  .pf-switch[disabled]{opacity:.5;cursor:default}

  .cov-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:13px;margin-bottom:16px}
  .cov{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:14px 15px;cursor:pointer;border-left:3px solid var(--line)}
  .cov:hover{border-color:var(--accent)}.cov.on{border-color:var(--accent)}
  .cov.sev-crit{border-left-color:var(--crit)}.cov.sev-warn{border-left-color:var(--warn)}.cov.sev-ok{border-left-color:var(--good)}
  .cov .cn{font-size:24px;font-weight:700;letter-spacing:-.02em}.cov .ct{font-size:12.5px;font-weight:600;margin-top:2px}.cov .cd{font-size:11px;color:var(--faint);margin-top:4px}

  .feed .fi{display:flex;gap:11px;padding:12px 15px;border-top:1px solid var(--line-2)}.feed .fi:hover{background:var(--panel-2)}
  .feed .ft{font-size:13px;line-height:1.4}.feed .ft b{font-weight:600}.feed .ft .q{color:var(--muted)}.feed .fm{font-size:11px;color:var(--faint);margin-top:4px;display:flex;align-items:center;flex-wrap:wrap;gap:2px}.feed .fm .pill{font-size:9.5px;padding:1px 7px;margin-right:2px}
  .feed .fb{font-size:12.5px;line-height:1.45;color:var(--muted);margin-top:3px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  .feed .feed-tgt{font-weight:600;color:var(--accent-ink);cursor:pointer}.feed .feed-tgt:hover{text-decoration:underline}
  /* Notification row — shared by Team → Inbox and the header bell dropdown */
  .nrow{display:flex;gap:11px;padding:11px 15px;border-top:1px solid var(--line-2)}.nrow:first-child{border-top:0}.nrow:hover{background:var(--panel-2)}
  .nrow .ft{font-size:12.5px;line-height:1.45;display:flex;align-items:center;flex-wrap:wrap;gap:5px}.nrow .ft .q{color:var(--muted)}
  .nrow .ft .pill{font-size:9.5px;padding:1px 7px}.nrow .feed-tgt{font-weight:600;color:var(--accent-ink);cursor:pointer}.nrow .feed-tgt:hover{text-decoration:underline}
  .nrow .fb{font-size:12px;line-height:1.45;color:var(--muted);margin-top:3px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  .nrow .fm{font-size:10.5px;color:var(--faint);margin-top:3px}
  .nrow.compact{padding:9px 13px;gap:9px}.nrow.compact .fb{-webkit-line-clamp:1}
  .nrow.read{opacity:.62}.nrow.read:hover{opacity:1}
  /* Header bell */
  #notifBtn{position:relative}
  /* Corner marker on the 34px button. The small overhang stays inside the topbar's 10px padding,
     which is what keeps it clear of #main's clip edge when the topbar is stuck at the top; the ring
     is a box-shadow so the pill keeps its full 16px interior (see §Count badges). */
  #notifBtn .badge{position:absolute;top:-4px;right:-4px;font-size:9.5px;padding:0 4px;box-shadow:0 0 0 2px var(--panel)}
  /* What's new (✨) — the badge is a friendly "there's something new", not an alert, so accent not crit. */
  #wnBtn .badge{position:absolute;top:-4px;right:-4px;font-size:9.5px;padding:0 4px;background:var(--accent);box-shadow:0 0 0 2px var(--panel)}
  .wn-modal{width:min(500px,94vw)}
  /* Id selector, not a class — modal.js injects .am-body{display:grid;padding:…} at runtime (after
     this file), so a same-specificity class would lose to it; #wn-list outranks it either way. */
  #wn-list{display:block;gap:0;padding:6px 0}
  .wn-entry{padding:11px 18px;border-top:1px solid var(--line-2)}.wn-entry:first-child{border-top:0}
  .wn-meta{display:flex;align-items:center;gap:8px;margin-bottom:3px}
  .wn-date{font-size:11px;color:var(--faint);font-variant-numeric:tabular-nums}
  /* Subtle per-entry copy button — sits at the far right of the meta row, faint until you hover the
     entry, for devs who want to paste an update straight into Teams. */
  .wn-copy{margin-left:auto;flex:none;display:inline-grid;place-items:center;width:24px;height:24px;padding:0;border:0;background:none;color:var(--faint);border-radius:6px;cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
  .wn-entry:hover .wn-copy,.wn-copy:focus-visible{opacity:1}
  .wn-copy:hover{color:var(--accent-ink);background:var(--line-2)}
  .wn-copy.copied{opacity:1;color:var(--good)}
  .wn-copy .ic{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none}
  .wn-title{font-weight:700;font-size:12.5px;color:var(--ink)}
  .wn-body{margin-top:2px;font-size:11.5px;color:var(--muted);line-height:1.45}
  .wn-body b{font-weight:600;color:var(--ink)}
  .wn-more-wrap{display:flex;justify-content:center;padding:14px 18px 6px}
  .wn-empty{padding:24px 18px;text-align:center;font-size:12.5px;color:var(--faint)}
  #notifMenu{position:absolute;top:42px;right:0;width:360px;max-width:86vw;background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow-lg);z-index:60;overflow:hidden}
  .notif-h{display:flex;align-items:center;justify-content:space-between;padding:11px 14px;border-bottom:1px solid var(--line);font-weight:700;font-size:13px}.notif-h .q{font-weight:500;font-size:11px;color:var(--faint)}
  .notif-list{max-height:52vh;overflow-y:auto}
  .notif-foot{padding:10px 14px;border-top:1px solid var(--line);font-size:12px;font-weight:600;color:var(--accent-ink);cursor:pointer;text-align:center}.notif-foot:hover{background:var(--panel-2)}
  .kv{display:flex;justify-content:space-between;padding:8px 15px;border-top:1px solid var(--line-2);font-size:12.5px}.kv:first-of-type{border-top:0}.kv .k{color:var(--muted)}.kv .v{font-weight:600;font-family:var(--font-num)}

  .backdrop{position:fixed;inset:0;background:rgba(12,16,26,.42);opacity:0;pointer-events:none;transition:opacity .2s;z-index:40}.backdrop.open{opacity:1;pointer-events:auto}
  .drawer{position:fixed;top:0;right:0;height:100vh;width:min(580px,94vw);background:var(--panel);border-left:1px solid var(--line);box-shadow:var(--shadow-lg);transform:translateX(100%);transition:transform .26s cubic-bezier(.4,0,.2,1);z-index:50;display:flex;flex-direction:column}
  .drawer.open{transform:none}
  /* The drawer is a fixed-height (100vh) flex COLUMN. The header and tab rail must never shrink — only
     the body may — or a tall tab (Jobs/Deadlines/Timeline/Grading) makes the column overflow and squeezes
     the "top area" (the name clips, the rail collapses). flex:none pins header+rail; the body grows and
     scrolls (flex:1 1 0 + min-height:0 so it can actually shrink below its content and scroll internally). */
  .dwr-h{padding:17px 20px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;gap:12px;overflow:hidden;flex:0 0 auto}.dwr-h .nm{font-size:18px;font-weight:700}.dwr-h .sb{font-size:12px;color:var(--faint);margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .dwr-body{padding:16px 20px;overflow-y:auto;flex:1 1 0;min-height:0}
  /* Tab rail: a horizontal scroller (never a wrap) so 9+ tabs don't clip in the narrow side panel.
     flex:0 0 auto is load-bearing — with overflow-x:auto a flex item's automatic min-height becomes 0,
     so without it the rail collapsed to just its scrollbar when the panel body was tall. To stay compact,
     each tab is an ICON with the label revealed only when it's active (full label always in the tooltip);
     tabs with no mapped icon keep their text. Thin dividers separate them. */
  .dwr-tabs{display:flex;gap:0;padding:0 10px;border-bottom:1px solid var(--line);flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;flex:0 0 auto;scrollbar-width:thin}
  .dwr-tabs::-webkit-scrollbar{height:6px}.dwr-tabs::-webkit-scrollbar-thumb{background:var(--line-2);border-radius:3px}
  .dwr-tabs button{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12.5px;padding:11px 13px;border-bottom:2px solid transparent;white-space:nowrap;flex:0 0 auto;cursor:pointer;transition:color .15s}
  .dwr-tabs button + button{border-left:1px solid var(--line-2)}
  .dwr-tabs button svg{width:16px;height:16px;flex:none;display:block}
  .dwr-tabs button:hover{color:var(--body)}
  .dwr-tabs button.on{color:var(--accent-ink);border-bottom-color:var(--accent)}
  .dwr-tabs button.has-ico:not(.on) .dt-lbl{display:none}   /* icon-only until active; full label lives in title */

  .loader{position:fixed;inset:0;z-index:100;display:grid;place-items:center;background:var(--ground)}
  .loader.hidden{display:none}
  .drop{width:min(560px,90vw);background:var(--panel);border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow-lg);padding:34px;text-align:center}
  .drop .logo{width:52px;height:52px;border-radius:14px;margin:0 auto 16px;display:grid;place-items:center;background:linear-gradient(135deg,var(--accent),#847fee);color:#fff;font-weight:800;font-size:24px;font-style:italic}
  .drop h2{font-size:20px;margin-bottom:6px}.drop p{color:var(--muted);font-size:13.5px;margin:0 0 20px}
  .dz{border:2px dashed var(--line);border-radius:14px;padding:26px;color:var(--faint);font-size:13px;transition:border-color .15s,background .15s}
  .dz.hot{border-color:var(--accent);background:var(--accent-soft)}
  .drop .or{margin:14px 0;color:var(--faint);font-size:12px}
  .note{margin:20px 0 0;padding:11px 15px;border-radius:var(--r);border:1px dashed var(--line);background:var(--panel-2);font-size:12px;color:var(--muted)}
  .note.err{border:1px solid var(--crit);border-style:solid;background:var(--crit-soft);color:var(--crit)}

  /* ---- Access gate: "Continue with Microsoft" + collapsed serial-key fallback, mirrors the
     real production app's login screen (src/components/auth/login-form.tsx) ---- */
  /* Proper app sign-in (see #loginGate) — brand identity, not an explainer. */
  .login{max-width:400px;padding:40px 34px 30px}
  .login .brand-title{font-size:30px;font-weight:800;font-style:italic;letter-spacing:-.01em;margin:2px 0 6px}
  .login .brand-tag{color:var(--faint);font-size:12px;font-weight:600;letter-spacing:.02em;margin:0 0 24px;text-transform:none}
  .login .login-foot{color:var(--faint);font-size:12px;margin:14px 0 0}
  .ms-btn{width:100%;justify-content:center;height:44px;background:var(--ink);color:var(--panel);border:0;font-size:13.5px;font-weight:600;border-radius:var(--r-sm)}
  .ms-btn:hover{opacity:.92}.ms-btn:disabled{opacity:.6}
  .serial-key{margin-top:18px;text-align:left}
  .serial-key summary{cursor:pointer;list-style:none;text-align:center;font-size:12.5px;font-weight:600;color:var(--faint)}
  .serial-key summary:hover{color:var(--ink)}
  .serial-key summary::-webkit-details-marker{display:none}
  .serial-key form{margin-top:14px}
  .serial-key #serialKeyInput{width:100%;box-sizing:border-box;margin-bottom:10px}
  .serial-key #serialKeySubmit{width:100%;justify-content:center}

  /* DataTable primitive */
  .dt-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:11px;flex-wrap:wrap}
  .dt-seg{display:inline-flex;background:var(--line-2);border-radius:9px;padding:3px}
  .dt-seg button{border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12px;padding:5px 13px;border-radius:6px;cursor:pointer;white-space:nowrap}
  .dt-seg button.on{background:var(--panel);color:var(--ink);box-shadow:var(--shadow)}
  /* Divides options that aren't the same KIND of choice — e.g. WIP's budget buckets, which partition
     billable work, versus Internal, which is a different population entirely. */
  .dt-seg .dt-seg-div{width:1px;background:var(--line);margin:5px 4px;flex:0 0 1px}
  .dt-edit{display:inline-flex;align-items:center;gap:2px;cursor:pointer;border-radius:6px;padding:1px 3px 1px 1px;margin:-1px -3px -1px -1px}
  .dt-edit:hover{background:var(--line-2)}
  .dt-edit .dt-edit-c{width:12px;height:12px;stroke:var(--faint);stroke-width:2;fill:none;opacity:.5;flex:none}
  .dt-edit:hover .dt-edit-c{opacity:.9}
  .dt-editsel{min-width:120px;font-size:12px}
  .btn.dt-on{border-color:var(--accent);color:var(--accent-ink);background:var(--accent-soft)}
  tr.dt-grouphdr td{background:var(--panel-2);font-weight:700;font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);padding:8px 13px;border-bottom:1px solid var(--line)}
  .dt-grouphdr{cursor:pointer;user-select:none}
  .dt-grouphdr .dt-gcount,.dt-grouphdr.sect .dt-gcount{font-family:var(--font-num);color:var(--faint);background:var(--line-2);border-radius:20px;padding:0 7px;margin-left:8px;font-weight:600}
  /* Opt-in via cfg.groupCountAccent (see datatable.js) — the count itself is the signal, e.g. a
     Queue client holding more than one open bill, i.e. one that can be merged. */
  .dt-grouphdr .dt-gcount-accent,.dt-grouphdr.sect .dt-gcount-accent{color:var(--warn);background:var(--warn-soft)}
  .dt-gsum{display:inline-flex;gap:14px;margin-left:16px;font-family:var(--font-num);text-transform:none;letter-spacing:0;font-weight:600}
  .dt-gsum-item{color:var(--ink)}
  .dt-gsum-lbl{color:var(--faint);font-weight:600;margin-right:3px}
  .dt-grouped{display:flex;flex-direction:column;gap:14px}
  .dt-grouphdr.sect{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);padding:2px 2px 8px;display:flex;align-items:center}
  .dt-grp-chev{display:inline-block;width:13px;text-align:center;margin-right:5px;color:var(--faint);font-size:10px}
  .so-row{grid-template-columns:64px 1.4fr 1fr auto}.so-lead{font-size:11px;color:var(--faint)}
  .mock-pill{display:inline-flex;align-items:center;gap:4px;font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--warm);background:var(--warm-soft);border:1px dashed var(--warm);border-radius:20px;padding:1px 8px;white-space:nowrap}
  .dt-toolbar .spacer{flex:1;min-width:12px}
  .dt-badge{font-family:var(--font-num);font-size:10px;background:var(--accent);color:#fff;border-radius:20px;padding:0 5px;margin-left:5px}
  .dt-chips{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:-2px 0 12px}
  .dt-chip{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:600;background:var(--accent-soft);color:var(--accent-ink);border-radius:20px;padding:3px 5px 3px 11px}
  .dt-chip button{border:0;background:transparent;color:inherit;font-size:15px;line-height:1;cursor:pointer;padding:0 3px;opacity:.7}.dt-chip button:hover{opacity:1}
  .dt-chip.clr{background:var(--line-2);color:var(--muted);padding:3px 11px;cursor:pointer}
  /* Pagination — first/prev/next/last icon buttons + page-size select (shown above & below every table) */
  .pagi select.f{padding:5px 8px;font-size:12px;width:auto}
  .pagi .num{color:var(--ink);font-weight:600;padding:0 2px;font-family:var(--font-num)}
  .dt-pagebtn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;flex:none;border-radius:7px;border:1px solid var(--line);background:var(--panel);color:var(--muted);cursor:pointer}
  .dt-pagebtn:hover:not(:disabled){background:var(--panel-2);color:var(--ink);border-color:var(--faint)}
  .dt-pagebtn:disabled{opacity:.35;cursor:default}
  .dt-pagebtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none}
  /* Sticky header + independent vertical scroll, with a synced horizontal scrollbar duplicated above the table */
  .dt-hscroll-top{overflow-x:auto;overflow-y:hidden;height:14px;margin-bottom:2px}
  .dt-hscroll-top-inner{height:1px}
  /* Multi-select filter: a button that opens a checkbox list. Used where a row can carry SEVERAL
     values (staff on a job, states a job's time sits in), which a single-choice select can't express
     without listing every combination as its own option. */
  .dt-multi{position:relative;display:inline-block}
  .dt-multi-menu{display:none;position:absolute;top:calc(100% + 4px);left:0;z-index:30;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:var(--shadow-lg);padding:6px;min-width:210px;max-height:300px;overflow:auto}
  .dt-multi.open .dt-multi-menu{display:block}
  .dt-multi-menu label{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;font-size:12px;color:var(--ink);cursor:pointer;white-space:nowrap}
  .dt-multi-menu label:hover{background:var(--row-hover)}
  /* Distinguishes "still arriving" from "nothing here" in a table's empty state — a quiet pulse,
     since it appears on first view of a heavy collection and shouldn't read as an error. */
  /* Continuity marker injected between rows when sorted by a gapRow column (e.g. missing invoice
     numbers). Reads as a break in the sequence, not a data row — no hover, dashed rule. */
  tr.dt-gaprow td{background:repeating-linear-gradient(135deg,transparent,transparent 6px,var(--line-2) 6px,var(--line-2) 7px);padding:5px 13px;font-size:11px;color:var(--muted);border-top:1px dashed var(--line);border-bottom:1px dashed var(--line)}
  tr.dt-gaprow .gap-big{color:var(--warn);font-weight:600}
  .dt-loading{color:var(--muted);animation:dtPulse 1.4s ease-in-out infinite}
  @keyframes dtPulse{0%,100%{opacity:.55}50%{opacity:1}}
  .dt-multi-div{height:1px;background:var(--line);margin:5px 8px}
  .dt-multi-quick{display:flex;flex-wrap:wrap;gap:5px;padding:0 2px 6px;margin-bottom:4px;border-bottom:1px solid var(--line-2)}
  .dt-multi-clear,.dt-multi-preset{padding:3px 9px;border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--muted);font-size:11px;cursor:pointer;font-family:inherit;white-space:nowrap}
  .dt-multi-clear:hover,.dt-multi-preset:hover{color:var(--ink);border-color:var(--accent);background:var(--row-hover)}
  .dt-multi-preset{color:var(--accent)}
  .dt-scroll-wrap{position:relative}
  .dt-scroll{overflow:auto;max-height:560px;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);background:var(--panel)}
  .dt-scroll .tbl{border:0;box-shadow:none;border-radius:0;overflow:visible;border-collapse:separate;border-spacing:0}
  .dt-scroll thead th{position:sticky;top:0;z-index:3;background:var(--panel-2);box-shadow:0 1px 0 var(--line)}
  /* §4.18 totals row — pinned to the bottom the same way the header pins to the top, so a total
     stays visible over a long scrolled table instead of scrolling away with the last row. */
  .dt-scroll tfoot td{position:sticky;bottom:0;z-index:2;background:var(--panel-2);box-shadow:0 -1px 0 var(--line);font-weight:600;padding-top:9px;padding-bottom:9px}
  /* stickyCols (opt-in per table, cfg.stickyCols = N) — pin the N leading columns so the client/name
     (and any lead/oversight kept beside it) stay put when a wide grid is scrolled sideways (e.g.
     Practice setup's client × service matrix). The selection checkbox column, when present, pins flush
     left; each pinned data column pins to its right at --sx-<i> (cumulative width measured post-render
     in dtSyncScrollWidths). Opaque backgrounds so scrolled cells pass behind, not through; the header
     corner sits above both the sticky header (z-index 3) and the pinned body cells; one divider shadow
     marks the frozen block's right edge. Gated on .dt-sticky1 so no other table is touched. */
  .dt-sticky1 .dt-stick-sel{position:sticky;left:0;z-index:1;background:var(--panel)}
  .dt-sticky1 .dt-stick-col{position:sticky;z-index:1;background:var(--panel)}
  .dt-sticky1 .dt-sc-0{left:var(--sx-0,0)}
  .dt-sticky1 .dt-sc-1{left:var(--sx-1,0)}
  .dt-sticky1 .dt-sc-2{left:var(--sx-2,0)}
  .dt-sticky1 .dt-sc-3{left:var(--sx-3,0)}
  .dt-sticky1 .dt-sc-4{left:var(--sx-4,0)}
  .dt-sticky1 .dt-sc-5{left:var(--sx-5,0)}
  .dt-sticky1 .dt-stick-edge{box-shadow:2px 0 5px -2px rgba(0,0,0,.18)}
  .dt-sticky1 tbody tr:hover .dt-stick-sel,.dt-sticky1 tbody tr:hover .dt-stick-col{background:var(--panel-2)}
  .dt-sticky1 thead .dt-stick-sel,.dt-sticky1 thead .dt-stick-col{z-index:5;background:var(--panel-2)}
  /* The native vertical scrollbar of a sticky-header box always spans the FULL box (header included),
     which reads as a glitch: the thumb implies you've scrolled past the header when it's still
     pinned in view. So in Chromium we hide the native vertical scrollbar and draw our own thumb
     that's explicitly positioned to start below the header (top offset measured in JS — see
     dtSyncVScroll) — the real .dt-scroll element still does the actual scrolling (wheel/touch/
     keyboard all keep working); this is purely a visual + drag-to-scroll affordance on top of it.
     Firefox has no per-axis scrollbar-width control, so it keeps its native scrollbar as-is there. */
  .dt-scroll::-webkit-scrollbar{width:0;height:10px}
  .dt-scroll::-webkit-scrollbar-track{background:transparent}
  .dt-scroll::-webkit-scrollbar-thumb{background:var(--faint);border-radius:5px}
  .dt-scroll::-webkit-scrollbar-thumb:hover{background:var(--muted)}
  .dt-vtrack{position:absolute;top:0;right:2px;bottom:2px;width:6px;pointer-events:none}
  .dt-vthumb{position:absolute;left:0;right:0;border-radius:3px;background:var(--faint);opacity:.55;pointer-events:auto;cursor:pointer}
  .dt-vthumb:hover,.dt-vthumb.dragging{opacity:.9}
  .qb-hint{font-size:12px;color:var(--muted);margin-bottom:10px}
  .qb-rows{display:flex;flex-direction:column;gap:4px}
  .qb-row{display:grid;grid-template-columns:1.2fr 1fr 1.2fr auto;gap:7px;align-items:center}
  .qb-row .f{min-width:0}.qb-row .qb-del{padding:6px 11px;color:var(--muted)}
  .qb-conn{padding:1px 0}.qb-conn .f{width:auto;padding:3px 12px;font-size:11px;font-weight:800;letter-spacing:.04em;color:var(--accent-ink);background:var(--accent-soft);border-color:transparent}
  .dt-conn{font-size:10px;font-weight:800;letter-spacing:.05em;color:var(--muted);padding:0 3px}
  .am-modal.inv-modal{width:min(890px,96vw);max-height:93vh}
  .inv-body{padding:20px;background:var(--panel-2);overflow-x:auto}
  .inv-zoom-wrap{overflow:hidden;margin:0 auto}
  /* The whole A4 page stack scales as one (see invPaginate) — scaling each sheet separately
     would leave the un-scaled gaps between them. */
  .inv-zoom-wrap .inv-pages{transform-origin:top left}
  .inv-pay-bar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 18px;border-bottom:1px solid var(--line);background:var(--panel-2)}
  .inv-pay-bar .note{font-size:11.5px}
  .am-modal.drill-modal{width:min(1100px,97vw);max-height:90vh}
  .drill-modal .drill-body{padding:16px 18px}
  /* Email reader popover (js/mail.js mailOpenReal) — reuses mailReadingHtml/mailComposeHtml as-is
     (built for the wide 3-pane Emails tab column), so just widen the modal and undo the couple of
     flex/height assumptions those carry from living inside .mail-shell normally. */
  .am-modal.mail-reader-modal{width:min(820px,94vw);max-height:88vh}
  .mail-reader-modal .am-h .btn{margin-left:auto;flex:none}
  .mail-reader-modal .am-h .am-x{margin-left:8px}
  .mail-reader-modal .am-body{display:block;padding:0}
  .mail-reader-modal .mail-reading{overflow:visible}
  /* The modal has no definite height for a shrink to resolve against (.am-body is display:block),
     so the card keeps sizing to its content there — see the flex:0 1 auto note on .mail-compose. */
  .mail-reader-modal .mail-compose{margin:14px;max-height:none;flex:0 0 auto}
  /* Triage's grouped queue (js/mail-action-queue.js). Cards grouped by WHO IS OWED, sized by what is
     worth doing, over one detail area that does not move. Size is meaning here, not decoration:
     equal cards would say all nine groups matter equally, which is false. */
  /* ONE ROW. Three stacked blocks spent most of a screen on nine numbers before anything had been
     chosen. The families survive as a label and a divider rather than a block each — owed-to-a-person
     before owed-to-the-data is the point of the page — and the captions moved into tooltips, since a
     caption explains a group you have NOT picked, and the one you have still shows its full sentence
     in the detail header below. */
  /* WRAPS, never scrolls. The first cut scrolled horizontally and clipped "No job" off the right at
     1280 — and the real page gives up another 200px to the nav. A horizontal scrollbar on the
     primary navigation of a surface is worse than a second line: it hides whole groups behind a
     gesture, and the group you cannot see is the one you stop working. */
  /* EMAIL INSIGHTS: cards grouped by what a figure is ABOUT, not one figure per box. Two wide, two
     sharing a row — the shapes are what let you find the same card again next month without reading
     the headings. */
  .mi-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
  .mi-card{border:1px solid var(--line);border-radius:var(--r-md);background:var(--panel);overflow:hidden}
  .mi-card.wide{grid-column:1 / -1}
  .mi-card-h{display:flex;align-items:center;gap:8px;padding:11px 14px;border-bottom:1px solid var(--line-2)}
  .mi-card-h h3{margin:0;font-size:12.5px;font-weight:650}
  .mi-card-q{margin-left:auto;font-size:11px;color:var(--faint)}
  .mi-ic{width:15px;height:15px;flex:none;opacity:.75}
  .mi-card-body{padding:13px 14px;display:flex;align-items:center;gap:18px;flex-wrap:wrap}
  .mi-card-body.flush{padding:0;display:block}
  /* The headline number of the card that has one. */
  .mi-hero{font-family:var(--font-num);font-size:38px;font-weight:700;line-height:1;display:flex;
    align-items:baseline;gap:8px;flex:none}
  .mi-hero span{font-family:var(--font);font-size:11px;font-weight:500;color:var(--faint)}
  .mi-hero.good{color:var(--good)}.mi-hero.warn{color:var(--warn)}.mi-hero.crit{color:var(--crit)}
  .mi-stats{display:flex;gap:16px;flex-wrap:wrap;flex:1;min-width:0}
  .mi-stat{display:flex;flex-direction:column;gap:1px;min-width:96px;text-align:left;
    border:0;background:none;padding:0;font-family:inherit;color:var(--ink)}
  .mi-stat-n{font-family:var(--font-num);font-size:19px;font-weight:700;line-height:1.15}
  .mi-stat-l{font-size:12px;font-weight:500}
  .mi-stat-f{font-size:10.5px;color:var(--faint)}
  .mi-stat.good .mi-stat-n{color:var(--good)}
  .mi-stat.warn .mi-stat-n{color:var(--warn)}
  /* A stat you can click is a doorway to Triage — underlined on hover so it reads as one. */
  button.mi-stat{cursor:pointer}
  button.mi-stat:hover .mi-stat-l{text-decoration:underline}
  button.mi-stat:hover .mi-stat-n{color:var(--accent)}
  .mi-card .mi-roles{flex:1 0 100%;margin-top:2px}
  @media (max-width:900px){ .mi-cards{grid-template-columns:minmax(0,1fr)} }
  .aq-fams{display:flex;align-items:stretch;flex-wrap:wrap;gap:0 0;row-gap:12px;margin-bottom:16px}
  .aq-fam{display:flex;flex-direction:column;gap:5px;padding:0 11px;
    border-left:1px solid var(--line-2)}
  .aq-fam:first-child{padding-left:0;border-left:0}
  .aq-fam-tiles{flex-wrap:wrap}
  .aq-fam-lab{font-size:9.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
    color:var(--faint);cursor:default}
  .aq-fam-tiles{display:flex;gap:6px}

  /* Icon, count, label on one line. The count leads because it is what the eye is scanning for. */
  .aq-card{display:flex;align-items:center;gap:7px;cursor:pointer;white-space:nowrap;
    border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel);
    padding:7px 11px;font-family:inherit;font-size:12px;color:var(--ink);
    transition:border-color .12s,box-shadow .12s}
  .aq-card:hover{border-color:var(--accent)}
  .aq-card.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
  .aq-card-n{font-family:var(--font-num);font-weight:700;font-size:16px;line-height:1}
  .aq-card-t{font-weight:500}
  /* Size still carries meaning — Quick clear is the highest yield per minute, the watch list has
     nothing to do — just at a scale that fits on one line. */
  .aq-card.xl .aq-card-n{font-size:21px}
  .aq-card.xl .aq-card-t{font-weight:600}
  .aq-card.lg .aq-card-n{font-size:18px}
  .aq-card.sm .aq-card-n{font-size:15px}
  .aq-card.warn .aq-card-n{color:var(--warn)}
  .aq-card.info .aq-card-n{color:var(--info)}
  .aq-card.good .aq-card-n{color:var(--ok,var(--accent))}
  .aq-card.mute{background:transparent;border-style:dashed}
  .aq-card.mute .aq-card-n{color:var(--faint);font-size:14px}
  .aq-card.mute .aq-card-t{color:var(--muted)}
  .aq-ic{width:14px;height:14px;flex:none;opacity:.8}
  .aq-card.xl .aq-ic{width:16px;height:16px}
  .card-h h3 .aq-ic{margin-right:7px;vertical-align:-3px}
  .aq-detail.empty{padding:34px;text-align:center;color:var(--faint);font-size:12.5px;
    border:1px dashed var(--line);border-radius:var(--r-md)}
  .aq-card.xl .aq-ic{width:19px;height:19px}
  .aq-card.mute .aq-ic{opacity:.55}
  .card-h h3 .aq-ic{margin-right:7px;vertical-align:-3px}
  /* The headline over a big filing group: how much of it can be answered without reading anything.
     Accent, because it is the difference between a task and a wall. */
  .aq-banner{margin:0 15px 10px;padding:10px 13px;border-radius:var(--r-sm);font-size:12.5px;
    background:var(--accent-soft);color:var(--accent-ink);border:1px solid var(--accent);
    display:flex;align-items:center;gap:12px}
  .aq-banner>div{flex:1}
  .aq-banner .btn{flex:none;white-space:nowrap}
  .aq-banner.quiet{background:var(--line-2);color:var(--muted);border-color:var(--line)}
  .aq-banner b{font-family:var(--font-num);font-size:15px}
  /* A suggestion wears its confidence. The two deductive ones are solid; the guess is dotted, which
     is the visual form of "this one still needs you to look". */
  /* The chip IS the button — one click attaches that job. It has to look pressable, or it reads as
     a label and the only route left is the checkbox and the bulk bar. */
  .aq-sug{font-size:11.5px;padding:3px 9px;border-radius:999px;white-space:nowrap;cursor:pointer;
    border:1px solid transparent;font-family:inherit;transition:filter .12s,border-color .12s}
  .aq-sug:hover{filter:brightness(.94);border-color:var(--accent)}
  .aq-sug.certain{background:var(--accent-soft);color:var(--accent-ink);font-weight:600}
  .aq-sug.likely{background:var(--line-2);color:var(--ink)}
  .aq-sug.maybe{background:transparent;color:var(--muted);border:1px dashed var(--line)}
  /* A model's answer reads as its own thing — neither a certainty nor a word-overlap guess. Dashed
     like the guess it is, tinted so it is obvious which suggestions came from where. */
  .aq-sug.ai{background:transparent;color:var(--accent-ink);border:1px dashed var(--accent)}
  .aq-airow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 15px 10px}
  .aq-ainote{font-size:11px;color:var(--faint)}
  .aq-ainote.done{color:var(--good)}
  .aq-ainote.err{color:var(--crit)}
  /* "6 identical · 21 Aug to 2 Sep" beside the subject. The dates are the checkable part — a bare
     count asks you to take it on trust. */
  .aq-dupe{margin-left:8px;font-size:10.5px;padding:1px 7px;border-radius:999px;
    background:var(--line-2);color:var(--muted);white-space:nowrap}
  .aq-acts{display:flex;gap:6px;flex:none;flex-wrap:wrap;justify-content:flex-end}
  .aq-acts .btn{font-size:11.5px;padding:5px 10px}
  /* The Actioned blocker (js/mail.js mailActionedBlockHtml) — what is outstanding, and what answers
     it, one line per flag. */
  .mab-list{display:flex;flex-direction:column;gap:8px}
  .mab-row{display:flex;flex-direction:column;gap:2px;padding:9px 11px;border:1px solid var(--line);border-left:3px solid var(--warn);border-radius:8px;background:var(--panel-2)}
  .mab-row b{font-size:12.5px}
  .mab-row span{font-size:11.5px;color:var(--muted)}
  /* "Add contact" dialog (js/mail.js mailAttnAddContactOpen) — three modes as a segmented control
     rather than radios, since each one swaps the whole form below it; and a two-up row for the
     first/last name pair, which reads as one field split rather than two unrelated ones. */
  .macm-tabs{display:inline-flex;gap:2px;background:var(--line-2);border-radius:10px;padding:3px;margin-bottom:12px}
  .macm-tabs button{border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12px;padding:6px 11px;border-radius:7px;cursor:pointer}
  .macm-tabs button.on{background:var(--panel);color:var(--ink);box-shadow:var(--shadow)}
  .macm-2col{display:grid;grid-template-columns:1fr 1fr;gap:10px}
  .stat.clickable,.tile.clickable,.cov.clickable{cursor:pointer}.stat.clickable:hover,.tile.clickable:hover{border-color:var(--accent)}
  /* Queue "Review bench" — persistent pre-send checks with checking… / clear / review states */
  .review-bench{border:1px solid var(--line);border-radius:12px;background:var(--panel);padding:11px 13px;margin-bottom:14px}
  .rb-head{display:flex;align-items:center;gap:10px;margin-bottom:9px}
  .rb-title{font-weight:800;letter-spacing:.05em;text-transform:uppercase;font-size:10px;color:var(--faint)}
  .rb-h{font-size:12px;display:inline-flex;align-items:center;gap:6px}
  .rb-h-load{color:var(--muted)} .rb-h-warn{color:var(--crit);font-weight:700} .rb-h-ok{color:var(--good);font-weight:700}
  .rb-chips{display:flex;flex-wrap:wrap;gap:8px}
  .rb-chip{display:inline-flex;align-items:center;gap:7px;font-size:12px;padding:7px 11px;border-radius:9px;border:1px solid var(--line);background:var(--panel-2);color:var(--muted);white-space:nowrap;font-weight:600}
  button.rb-chip{cursor:pointer;transition:filter .12s} button.rb-chip:hover{filter:brightness(1.04)}
  .rb-chip.rb-crit{border-color:var(--crit);background:var(--crit-soft);color:var(--crit)}
  .rb-chip.rb-warn{border-color:var(--warn);background:var(--warn-soft);color:var(--warn)}
  .rb-chip.rb-accent{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-ink)}
  .rb-chip.rb-clear{color:var(--faint);font-weight:500} .rb-chip.rb-clear .rb-check{color:var(--good);font-weight:800}
  .rb-chip.rb-loading{color:var(--muted);font-weight:500;border-style:dashed}
  .rb-chip b{font-weight:800} .rb-cta{font-weight:700;opacity:.75;font-size:11px}
  .rb-state{font-style:italic;opacity:.8}
  .rb-dot{width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
  .rb-spin{width:11px;height:11px;border:2px solid var(--line-2);border-top-color:currentColor;border-radius:50%;display:inline-block;animation:rbspin .7s linear infinite;flex:none}
  @keyframes rbspin{to{transform:rotate(360deg)}}
  /* Karbon-linked badge in drawer subtitles (job + client): green mark + open + refresh */
  .kl{display:inline-flex;align-items:center;gap:4px;margin-left:8px;vertical-align:middle}
  .kl-mark{display:inline-flex;align-items:center;justify-content:center;width:17px;height:17px;border:1.5px solid var(--good);border-radius:5px;color:var(--good);font-size:10px;font-weight:800;line-height:1;background:color-mix(in srgb,var(--good) 10%,transparent)}
  .kl-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:1px solid var(--line);border-radius:5px;background:var(--panel-2);color:var(--muted);cursor:pointer;transition:color .12s,border-color .12s}
  .kl-btn:hover{color:var(--accent-ink);border-color:var(--accent)}
  .kl-btn.kl-spin{color:var(--muted);pointer-events:none}
  .kl-btn.kl-spin svg{animation:rbspin .7s linear infinite}
  .kl-none{color:var(--faint)}
  /* Top-bar Karbon "Sync now" (#ksyncBtn) — the ⟳ spins while a quick sync is in flight. */
  #ksyncBtn.ksync-spin{pointer-events:none;color:var(--accent-ink)}
  #ksyncBtn.ksync-spin svg{animation:rbspin .7s linear infinite}
  /* Compact variant for an inline job-row meta line — slightly smaller controls, tighter margin. */
  .kl-row{margin-left:6px;gap:3px}
  .kl-row .kl-mark{width:16px;height:16px;font-size:9.5px}
  .kl-row .kl-btn{width:18px;height:18px}
  .banner{position:sticky;top:0;z-index:21;background:var(--warm-soft);color:var(--warm);font-size:12px;font-weight:600;padding:7px 22px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:8px}
  .banner.announce{background:var(--accent-soft);color:var(--accent-ink)}
  /* "Read more" on an announcement — underlined so it reads as a link against text that is already
     coloured and bold, which on its own would not distinguish it. */
  .banner-more{color:inherit;text-decoration:underline;text-underline-offset:2px;white-space:nowrap}
  .banner-more:hover{opacity:.75}

  /* ============================ MAIL ============================ */
  .mail-connect{max-width:460px;margin:60px auto;text-align:center;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:34px}
  .mail-connect .ic-lg{width:46px;height:46px;border-radius:14px;margin:0 auto 16px;display:grid;place-items:center;background:linear-gradient(135deg,var(--accent),#847fee);color:#fff}
  .mail-connect .ic-lg svg{width:24px;height:24px;stroke:#fff;stroke-width:1.8;fill:none}
  .mail-connect h2{font-size:18px;margin-bottom:6px}.mail-connect p{color:var(--muted);font-size:13px;margin:0 0 20px;line-height:1.5}
  .mail-connect .err{margin-top:14px;padding:10px 13px;border-radius:9px;background:var(--crit-soft);color:var(--crit);font-size:12px;text-align:left}
  .mail-shell{display:flex;height:calc(100vh - 190px);min-height:520px;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);background:var(--panel);overflow:hidden}
  .mail-rail{width:196px;flex:none;border-right:1px solid var(--line);display:flex;flex-direction:column;overflow-y:auto;padding:12px 8px}
  .mail-rail .compose-btn{margin:2px 6px 12px}
  /* Signature + Disconnect (2026-08-21) — moved here from the page-wide header, icon-only so they
     don't compete with Compose for weight. */
  .mail-rail-tools{display:flex;gap:6px;margin:0 6px 12px}
  .mail-rail-tools .icon-btn{flex:1}
  /* Demo inbox + Help (2026-08-26) — moved out of the page-wide header into the Emails column, since
     that is what they are about. margin-top:auto pins them to the foot of a short rail; on a long one
     the auto margin collapses and they simply follow the last folder, which is where you would look
     for them anyway. Labelled rather than icon-only: neither is familiar enough to survive a glyph. */
  .mail-rail-foot{margin-top:auto;display:flex;flex-direction:column;gap:6px;padding:14px 6px 2px}
  .mail-rail-foot .btn{width:100%;justify-content:center}
  /* The same way in, for the state that has no rail to hang it off — no mailbox connected, so the
     connect card is all the Emails tab has. Still inside the tab, never in the page-wide header. */
  .mail-connect-demo{margin-top:14px;padding-top:14px;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:center;gap:10px;font-size:12.5px;color:var(--muted)}
  .mail-folder{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:var(--r-sm);color:var(--muted);font-size:12.5px;font-weight:500;cursor:pointer}
  .mail-folder:hover{background:var(--line-2);color:var(--ink)}
  .mail-folder.on{background:var(--accent-soft);color:var(--accent-ink);font-weight:600}
  .mail-folder .ic{width:15px;height:15px;stroke:currentColor;stroke-width:1.8;fill:none;flex:none}
  .mail-folder .n{margin-left:auto;font-family:var(--font-num);font-size:10.5px;color:var(--faint)}
  .mail-folder.on .n{color:var(--accent-ink)}
  .mail-rail .grp-lab{padding:14px 9px 4px 9px;font-size:10px}
  .mail-folder .del-x{opacity:0;margin-left:4px;color:var(--faint);font-size:10.5px;white-space:nowrap}.mail-folder:hover .del-x{opacity:1}
  .mail-list{width:340px;flex:none;border-right:1px solid var(--line);display:flex;flex-direction:column;overflow:hidden}
  .mail-list-tools{display:flex;align-items:center;gap:6px;padding:9px 10px;border-bottom:1px solid var(--line)}
  .mail-list-tools .chk{flex:none}
  .mc-selcount{font-size:12.5px;color:var(--muted);white-space:nowrap;margin-right:2px}
  .mail-list-rows{flex:1;overflow-y:auto}
  .mail-row{display:flex;gap:9px;padding:10px 12px;border-bottom:1px solid var(--line-2);cursor:pointer;align-items:flex-start}
  .mail-row:hover{background:var(--panel-2)}.mail-row.sel{background:var(--accent-soft)}.mail-row.unread .subj{font-weight:700}
  .mail-row .chk{margin-top:3px}
  .mail-row .body{min-width:0;flex:1}
  .mail-row .top{display:flex;align-items:baseline;gap:6px}
  /* Widened from 15ch (2026-08-25): a row now names the PEOPLE in the conversation, not just the
     newest sender, and "Anupreksha, Lisa" does not fit in fifteen characters. */
  .mail-row .from{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22ch}
  /* How many messages are in the conversation. Outside .from on purpose — .from ellipsis-truncates,
     and a count clipped out of sight is exactly how the draft badge went missing. */
  .mail-thread-n{flex:none;font-size:10.5px;font-weight:650;font-family:var(--font-num);color:var(--muted);
    background:var(--panel-2);border:1px solid var(--line-2);border-radius:9px;padding:0 5px;line-height:15px}
  .mail-row.unread .mail-thread-n{color:var(--ink);border-color:var(--line)}
  .mail-row .when{margin-left:auto;font-size:10.5px;color:var(--faint);white-space:nowrap;font-family:var(--font-num)}
  .mail-row .subj{font-size:12.5px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-row .prev{font-size:11.5px;color:var(--faint);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-row .flags{display:flex;gap:5px;align-items:center;margin-top:5px;flex-wrap:wrap}
  .mail-row .star{color:var(--faint)}.mail-row .star.on{color:var(--warm)}
  .mail-reading{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}
  .mail-reading-h{padding:14px 18px;border-bottom:1px solid var(--line)}
  .mail-reading-h .subj{font-size:16px;font-weight:700}
  .mail-reading-actions{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap}
  .mra-left,.mra-right{display:flex;gap:6px}
  .icon-btn.on{color:var(--warm);border-color:var(--warm)}
  /* Compact 2026-08-21 — was 4 full .kv rows (From/To/Cc/Received, ~35px each, bordered+padded) and
     ate a third of the preview before any email content showed. Same fields, two dense lines. */
  .mail-reading-meta{padding:8px 18px;border-bottom:1px solid var(--line-2);font-size:12px;color:var(--muted);display:flex;flex-direction:column;gap:3px}
  .mail-reading-meta b{color:var(--ink);font-weight:600}
  .mrm-row{display:flex;align-items:baseline;gap:5px;line-height:1.4}
  .mrm-row .mrm-date{margin-left:auto;flex:none;color:var(--faint);font-size:11px;font-family:var(--font-num);padding-left:10px}
  .mrm-cc-lab{margin-left:8px}
  .mail-reading-scroll{flex:1;min-width:0;overflow-y:auto;overflow-x:hidden;padding:18px}
  .mail-reading-scroll .mail-html{font-size:13.5px;line-height:1.6;color:var(--ink)}
  /* Comments as their own region below the body scroller (2026-08-21) — was appended INSIDE the
     body's own scroll blob, so reaching the compose box on a long email meant scrolling past the
     whole thing first. flex:none caps it to its own content up to max-height; a comment thread
     longer than that scrolls WITHIN mail-comment-thread (below, scoped to this context only — the
     bare .mail-comment-thread class is shared with js/reviews.js, which must not inherit this cap)
     while the compose row (mail-comment-input-row, un-scoped, already flex:none by nature) stays
     pinned beneath it, always reachable without scrolling anything else first. */
  /* No overflow:hidden here on purpose — the @mention dropdown (.mail-at-dd) is an absolutely
     positioned CHILD of .mail-comment-input-row that pops up ABOVE the textarea; an ancestor with
     overflow:hidden between it and the viewport would clip it off mid-use. max-height alone still
     properly bounds this flex column and forces mail-comment-thread's own overflow-y:auto below to
     activate — clipping isn't needed for that, just for the (unlikely) case content still pokes out
     a pixel or two past the cap, which isn't worth losing the dropdown over. */
  .mail-comment-area{flex:none;display:flex;flex-direction:column;max-height:46%;border-top:1px solid var(--line-2);padding:12px 18px;gap:8px}
  .mail-comment-area .mail-comment-thread{flex:0 1 auto;min-height:0;overflow-y:auto;margin-bottom:0}
  .mail-comment-area .mail-comment-input-row{flex:none}
  /* .mail-html renders untrusted email body content in three places (the reading pane above, the
     read-only preview modal, and quoted prior turns inside compose) — a real email is unbroken-length
     content with no guaranteed wrap points (a long URL, a run of underscores from a signature line, a
     pasted table), and without this ONE such token forces its container wider than the flex layout
     around it. Flex items default to min-width:auto, not 0, so the whole .mail-shell/.mail-reading
     chain gets shoved wide, squashing the folder rail and pushing the reading pane into horizontal
     overflow — reported 2026-08-21 against a specific email whose body had exactly this shape.
     overflow-wrap:anywhere is deliberately more aggressive than break-word: it breaks even where
     there's no better option, the only way to guarantee one long token can't blow out the layout
     again. Bare class rule (not scoped to one parent) so it protects all three usages, not just this
     one — the failure mode is identical regardless of which container the body happens to render in. */
  .mail-html{overflow-wrap:anywhere;word-break:break-word}
  .mail-html img,.mail-html table{max-width:100%}
  .mail-compose{min-width:0}
  .mail-empty{flex:1;display:grid;place-items:center;color:var(--faint);font-size:13px;text-align:center;padding:30px}
  /* A self-contained floating card (its own border/shadow/margin), sized to its own content, rather
     than stretched to match the shell's fixed height — that stretch either left the footer stranded
     far below a short message, or forced mail-compose-fields to grow into blank space wrapped in its
     own confusing (and mostly empty) scrollbar. Any leftover room below the card is just the reading
     pane's plain background, same as a short message being read shows blank space beneath it — not
     a second, unexplained scroll region.

     The cap USED to be max-height:calc(100% - 28px), which cut the Send/Save/Cancel row off the
     bottom of the screen on any long draft. 100% is the height of .mail-reading — but the card does
     not have that box to itself: the "Edit draft" header is its sibling in the same column. So the
     card was allowed to be the full height MINUS its own margins, the header pushed it down by
     another ~50px, and .mail-reading's overflow:hidden quietly ate the footer. It was never
     scrolled-past-able either, because the footer is outside the fields' scroll region.
     flex-shrink does the job properly: the card takes its content's height and shrinks only when
     there is not enough room, which is precisely the leftover space after the header. min-height:0
     on both is what lets a flex item shrink below its content at all (the default is auto = never),
     and .mail-compose-fields is where that shrink lands, as its own scroll region. */
  .mail-compose{margin:14px 18px;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);background:var(--panel-2);display:flex;flex-direction:column;flex:0 1 auto;min-height:0;overflow:hidden}
  .mail-compose-fields{overflow-y:auto;min-height:0;padding:14px 18px}
  .mail-compose .row{display:flex;gap:8px;margin-bottom:8px;align-items:center}
  .mail-compose .row label{font-size:11.5px;color:var(--faint);min-width:34px}
  .mail-compose input.f,.mail-compose textarea.f{flex:1;width:100%}
  .mail-compose textarea.f{min-height:130px;resize:vertical;font-family:var(--font-ui)}
  .mail-compose .foot{flex:none;display:flex;gap:8px;padding:12px 18px;border-top:1px solid var(--line);justify-content:flex-end}
  .mail-linktags{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px;align-items:center}
  .mail-linktags .tag .x{opacity:.6;font-size:12px}.mail-linktags .tag .x:hover{opacity:1}
  /* Confirmed marker on a multi-client auto pill (js/mail.js linkTags) — plain text, not a button,
     once a pill no longer needs a decision. */
  .mail-attn-confirmed{opacity:.7;font-size:11px;margin-left:3px}
  /* "Needs attention" row badges (js/mail.js mailAttentionBadgesHtml) — Triage list rows AND
     Timeline rows share this styling. warn = a decision this app can't make for you (an unlinked
     contact, a stranger); info = a filing gap that just needs the reading pane (multi-client still
     pending, no job yet) — same visual weight as the existing .tag pills elsewhere, distinguished by
     colour rather than size so a busy row doesn't get louder than the rest of the list. */
  .mail-attn{display:inline-flex;gap:4px;flex-wrap:wrap}
  .mail-attn-tag{display:inline-flex;align-items:center;gap:4px;padding:1px 7px;border-radius:99px;font-size:10.5px;font-weight:600;white-space:nowrap}
  .mail-attn-tag.warn{background:var(--warn-soft);color:var(--warn)}
  .mail-attn-tag.info{background:var(--info-soft);color:var(--info)}
  .mail-attn-action{cursor:pointer;text-decoration:underline;text-underline-offset:2px;opacity:.85}
  .mail-attn-action:hover{opacity:1}
  /* THE TRIAGE BAR (js/mail.js mailReadingHtml, 2026-08-24) — every triage action in one visually
     fenced panel between the email's identity and its body, one labelled row per kind of action
     (Next step / Client / Job). The left accent border + tinted ground is what says "this is the
     working surface, not part of the message" — the exact complaint was controls reading as email
     decoration. */
  .mail-triage-bar{border-bottom:1px solid var(--line-2);border-left:3px solid var(--accent);background:var(--panel-2);padding:8px 18px 8px 15px;display:flex;flex-direction:column;gap:6px}
  .mtb-row{display:flex;align-items:center;gap:8px}
  .mtb-lab{flex:none;width:64px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}
  .mtb-content{display:flex;align-items:center;gap:5px;flex-wrap:wrap;min-width:0}
  /* The note that sits beside a triage row — a suggestion the app has made, or a plain statement of
     what it found. Reported as a mismatch (2026-08-24): the "nobody matches a contact" line was a
     bare .q and rendered at a different size to the suggestion note next to it, so two things of the
     same weight looked like two different kinds of thing. One class now, used by both. */
  .mtb-sug{font-size:10.5px;color:var(--faint);line-height:1.35}
  /* An unsent draft reply to the open conversation, offered rather than opened for you (2026-08-25).
     Sits above the triage strip because it is about the message itself, not about filing it. */
  .mail-draftbar{display:flex;align-items:center;gap:9px;padding:7px 14px;font-size:12px;color:var(--body);
    background:color-mix(in srgb,var(--accent) 7%,var(--panel));border-bottom:1px solid var(--line-2)}
  .mail-draftbar .btn{margin-left:auto;padding:3px 10px;font-size:11.5px}

  /* The folded triage strip (2026-08-24). One line where five rows of controls used to sit
     permanently above the message — the reading pane had almost no room for the email itself. */
  .mtb-strip{display:flex;align-items:center;gap:10px;padding:6px 14px;border-bottom:1px solid var(--line-2);background:var(--panel-2);font-size:11.5px;flex-wrap:wrap}
  .mtb-strip.ready{background:color-mix(in srgb,var(--good) 8%,var(--panel-2))}
  .mtb-toggle{display:inline-flex;align-items:center;gap:7px;border:0;background:transparent;cursor:pointer;padding:2px 0;color:var(--body);font:inherit;font-weight:650}
  .mtb-caret{color:var(--faint);font-size:10px;width:9px}
  .mtb-dots{display:inline-flex;gap:3px}
  .mtb-dot{width:7px;height:7px;border-radius:50%;background:var(--line);display:block}
  .mtb-dot.on{background:var(--good)}
  /* The triage trail (2026-08-25) — how the conversation got to the state the strip is reporting.
     A count, not a word: the control has to be small enough to sit on a strip that is already doing
     a job, and the number is the only part worth reading before you decide to open it. */
  .mtb-trail-btn{display:inline-flex;align-items:center;gap:4px;border:0;background:transparent;cursor:pointer;
    padding:2px 6px;border-radius:7px;color:var(--muted);font:inherit;font-size:11px;font-weight:650}
  .mtb-trail-btn .ic{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
  .mtb-trail-btn:hover{background:var(--panel);color:var(--ink)}
  .mtb-trail-btn.on{background:var(--panel);color:var(--ink)}
  /* Tall enough for an ordinary thread to show WHOLE. At 210px a five-turn conversation came to
     nine entries and showed eight, with the ninth below a thin scrollbar nobody notices — reported
     as "why is this turn not in your audit trail?" when it was in it all along. A trail that hides
     rows is worse than no trail, because it invites exactly that conclusion. Long threads still
     scroll, but by then the region is plainly a scroller and the header says how many to expect. */
  .mail-trail{border-bottom:1px solid var(--line-2);background:var(--panel-2);padding:0 14px 8px;max-height:min(46vh,420px);overflow-y:auto}
  .mtr-head{position:sticky;top:0;background:var(--panel-2);padding:7px 0 4px;font-size:10.5px;font-weight:650;
    letter-spacing:.03em;text-transform:uppercase;color:var(--faint);border-bottom:1px solid var(--line-2)}
  .mtr-row{display:flex;align-items:baseline;gap:9px;padding:4px 0;font-size:11.5px;border-bottom:1px solid var(--line-2)}
  /* Correspondence vs decision, told apart by a mark rather than by reading the line. The arrows
     reuse the timeline's own in/out colours (.mail-tl-dir) so one direction means one thing
     everywhere in the app. */
  .mtr-mark{flex:none;width:13px;text-align:center;font-size:11px;line-height:1}
  .mtr-mark.in{color:var(--info)}
  .mtr-mark.out{color:var(--good)}
  .mtr-mark.ev{color:var(--line);font-size:8px}
  .mtr-row.mail .mtr-what{font-weight:600}
  .mtr-row:last-child{border-bottom:0}
  .mtr-what{flex:1;min-width:0;color:var(--ink)}
  .mtr-who{color:var(--muted);white-space:nowrap}
  .mtr-when{font-family:var(--font-num);color:var(--faint);white-space:nowrap;font-size:10.5px}
  /* Amber, not grey. "Not ready" is a STATE — the same orange the unanswered contact and client
     questions already wear — and a muted grey read as a caption rather than as one half of a scale
     whose other half is green. */
  .mtb-missing{color:var(--warn)}
  .mtb-missing b{font-weight:700}
  .mtb-ready{color:var(--good);font-weight:650}
  /* A decision the app made and nobody has confirmed — dashed, so it reads as provisional beside a
     confirmed one, and clickable to confirm. */
  /* Thin. `border-style:dashed` with no width falls back to `medium` (~3px), and .mtb-decided
     already carried a 3px left bar — together they drew the heavy box reported as "don't make it
     this thick borders, make them lighter". Width and colour are both stated now. */
  .tag.mtb-decided.auto{border:1px dashed color-mix(in srgb,var(--good) 55%,transparent);cursor:pointer}
  .mns-set.auto{border:1px dashed color-mix(in srgb,var(--accent) 55%,transparent);cursor:pointer}
  .tag.mtb-decided.auto:hover{border-color:var(--good)}
  .mns-set.auto:hover{border-color:var(--accent)}
  .mtb-auto{border:1px solid var(--line);background:var(--panel);border-radius:6px;cursor:pointer;color:var(--accent-ink);font-size:11px;line-height:1;padding:3px 7px}
  .mtb-auto:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,var(--panel))}
  .mtb-clear{margin-left:auto;font-size:11.5px;padding:3px 11px}
  .mail-triage-bar.folded{display:none}
  /* The two other folds (2026-08-25) — the message actions and the comment box, which took
     permanent height above and below an email that had very little left. */
  .mra-more.on{color:var(--accent-ink)}
  /* Tinted, so it reads as a thing to use rather than a rule at the foot of the pane — "give it a
     different colour so that it catches the attention of the user". The @ carries the meaning, so it
     is sized up and the words never repeat it. */
  .mail-comment-toggle{display:flex;align-items:center;gap:8px;width:100%;border:0;border-top:1px solid var(--line-2);cursor:pointer;padding:8px 15px;font:inherit;font-size:12px;font-weight:650;text-align:left;
    background:color-mix(in srgb,var(--accent) 7%,var(--panel));color:var(--accent-ink)}
  .mail-comment-toggle:hover{background:color-mix(in srgb,var(--accent) 14%,var(--panel))}
  .mct-at{font-size:16px;font-weight:800;line-height:1;color:var(--accent)}
  .mail-comment-area.folded{flex:none}
  /* A recorded decision — who said it and why live in the title; × undoes it. */
  .tag.mtb-decided{color:var(--good);background:var(--good-soft);border-left:3px solid var(--good)}
  /* Reason presets in the "why" dialogs — radio rows, presets before Other. */
  .mtb-reasons{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}
  .mtb-reason{display:flex;align-items:center;gap:7px;font-size:12.5px;cursor:pointer;padding:3px 2px}
  /* Next-step verb row (js/mail.js mailNextStepRowHtml) — hosted inside the bar's first row, which
     carries the label. The suggested verb (✦, study-driven) is pre-lit so the common case is a
     single click on an already-highlighted answer. */
  .mail-nextstep{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11.5px}
  .mns-btn{border:1px solid var(--line);background:var(--panel);color:var(--muted);font-size:11px;font-weight:600;padding:3px 9px;border-radius:99px;cursor:pointer}
  .mns-btn:hover{border-color:var(--accent);color:var(--accent-ink)}
  .mns-btn.sug{border-color:var(--good);color:var(--good);background:var(--good-soft)}
  .mns-set{display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:99px;background:var(--accent-soft);color:var(--accent-ink);font-weight:600}
  .mns-set .x{cursor:pointer;opacity:.6}.mns-set .x:hover{opacity:1}
  .tag.mns-pill{color:var(--accent-ink);background:var(--accent-soft);border-left:3px solid var(--accent)}
  /* Filing COUNTS on a list row (2026-08-24) — "2 clients", "1 job". Quiet by design: settled
     filing should read as settled, leaving the orange warnings to carry the eye. Names and actions
     live in the reading pane's triage bar. */
  .tag.mail-count{color:var(--muted);background:var(--line-2)}
  /* The mention badge on an email that is not in your mailbox (mailSharedMentionRows). Accent, not
     the muted grey the counts use: it is the reason the row is in your list at all. The left edge
     marks the whole row as borrowed, and the spacer keeps the body aligned with rows that do have a
     checkbox. */
  /* The redaction review, and the AI compose dialog in front of it. */
  .mrv-modal{max-width:760px}
  .mrv-lead{font-size:12.5px;color:var(--muted);margin:0 0 10px}
  .mrv-chips{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:10px}
  .mrv-chip{font-size:11px;padding:3px 9px;border-radius:999px;cursor:pointer;font-family:inherit;
    border:1px solid var(--line);background:var(--panel-2);color:var(--muted)}
  .mrv-chip.on{background:var(--accent-soft);color:var(--accent-ink);border-color:var(--accent)}
  /* A redaction a person added reads differently from one a rule proposed — it is the part of the
     review that could not have happened without them. */
  .mrv-chip.manual{border-style:dashed;font-weight:600}
  .mrv-tools{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:8px}
  .mrv-count{font-size:11px;color:var(--faint);font-family:var(--font-num)}
  .mrv-body{font-size:11.5px;color:var(--muted);display:flex;align-items:center;gap:6px;margin-left:auto}
  /* Monospace, because this is a payload and not prose — it should not look like a nicely rendered
     email, it should look like the string it is. */
  .mrv-text{font-family:var(--font-mono,ui-monospace,monospace);font-size:11.5px;line-height:1.55;
    white-space:pre-wrap;word-break:break-word;background:var(--panel-2);border:1px solid var(--line);
    border-radius:var(--r-sm);padding:11px 13px;max-height:38vh;overflow:auto;margin:0;user-select:text}
  /* ARMED. A marking tool that looks the same as no tool is why the eraser read as doing nothing:
     you drag, you let go, and without the box itself changing there is nothing to tell you the
     gesture landed. The crosshair says the box is a target, the accent edge says which mode, and the
     selection colour is the accent so the drag itself is visibly the tool's colour and not the
     browser's ordinary blue. */
  .mrv-text.marking{border-color:var(--accent);cursor:crosshair}
  .mrv-text.marking::selection{background:var(--accent);color:#fff}
  .mrv-text.marking::-moz-selection{background:var(--accent);color:#fff}
  /* What just happened, in one line. It replaces the old silence after an action. */
  .mrv-hint{font-size:11.5px;color:var(--accent-ink);background:var(--accent-soft);
    border-radius:var(--r-sm);padding:5px 9px;margin-bottom:8px}
  .mrv-rw{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px;padding:8px 10px;
    border:1px solid var(--accent);border-radius:var(--r-sm);background:var(--accent-soft)}
  /* The quoted original is capped: a long drag must not push the input off the row. */
  .mrv-rw-q{font-size:11.5px;color:var(--accent-ink);max-width:260px;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap}
  .mrv-rw .f{flex:1;min-width:180px}
  .mrv-none{font-size:12px;color:var(--warn);margin-bottom:10px}
  .mrv-foot{font-size:11px;color:var(--faint);flex:1;margin-right:12px}
  .mai-modal{max-width:520px}
  .mai-opts{display:flex;gap:12px}
  .mai-opts .am-f{flex:1}
  .mai-note{font-size:11.5px;color:var(--muted);margin:10px 0 0}
  .mai-after{font-size:11.5px;color:var(--muted);margin:6px 0;padding:7px 10px;border-radius:var(--r-sm);
    background:var(--panel-2);border:1px solid var(--line-2)}
  .tag.mail-shared{color:var(--accent);background:var(--accent-soft);font-weight:600}
  /* The watch list reads the other way round — somebody else owes this, so it is muted, not accent. */
  .tag.mail-waiting{color:var(--muted);background:var(--line-2)}
  .mail-row.shared{border-left:2px solid var(--accent)}
  .mail-row .chk-spacer{display:inline-block;width:13px;flex:0 0 13px}
  /* Quick filters sit in the rail among the folders and behave like them, so they inherit
     .mail-folder wholesale; only the count needs to lean on the accent, since a filter's count is a
     number of things waiting on you rather than a number of unread messages. */
  .mail-folder.mail-quick .n{color:var(--accent-ink)}
  .mail-folder.mail-quick.on .n{color:inherit}
  .mail-more{display:flex;align-items:center;gap:6px;padding:7px 9px;margin-top:6px;border-radius:var(--r-sm);color:var(--faint);font-size:12px;font-weight:600;cursor:pointer;user-select:none}
  .mail-more:hover{background:var(--line-2);color:var(--ink)}
  /* A queued-but-not-yet-created suggestion: context beside the action, never instead of it. */
  .mail-attn-note{opacity:.7;font-style:italic;margin-right:2px}
  /* ---- filing pill colours (js/mail.js mailClientPillKind) --------------------------------------
     Where an email was filed, readable at a glance without opening anything. Jobs already had their
     own colour (.tag.draft-pending, accent) and keep it; these three split what used to be one flat
     grey "client" pill into the three things it can actually be. Distinct hues rather than shades of
     one, because the point is telling them apart in peripheral vision:
       org         info   (blue)   — a company
       person      good   (green)  — an individual who is a client in their own right
       person-org  warm   (amber)  — an individual who is BOTH, i.e. also a contact at a company;
                                     the case that explains one email landing on two timelines
     Left border rather than a different background alone: the pills sit next to job pills and each
     other, and a bar reads as a category marker faster than a fill does. */
  .tag.pill-org{color:var(--info);background:var(--info-soft);border-left:3px solid var(--info)}
  .tag.pill-person{color:var(--good);background:var(--good-soft);border-left:3px solid var(--good)}
  .tag.pill-person-org{color:var(--warm);background:var(--warm-soft);border-left:3px solid var(--warm)}
  /* CONTACT pills (2026-08-24) — a person, not a client, and deliberately quieter than the three
     client colours above: several contacts on an email is the normal case and must not read as a
     warning. Neutral ground, no left rule, with the type/role in the muted secondary colour. */
  .tag.pill-contact{color:var(--ink);background:var(--line-2);border:1px solid var(--line)}
  .tag.pill-contact .q{color:var(--muted);font-weight:400}
  /* Filing pickers: the shared .mail-ac dropdown, re-anchored to its own input (its default offsets
     were tuned for the compose recipient rows). */
  .mail-pick{position:relative;display:inline-block}
  .mail-pick .mail-pick-dd{left:0;right:auto;top:100%;min-width:280px;max-width:380px;max-height:240px}
  .mail-pick .mail-ac-i input{margin-right:4px}
  /* Email timelines (client drawer Timeline tab; job drawer + dedicated pages later) */
  .mail-tl{display:flex;flex-direction:column}
  .mail-tl-row{display:flex;gap:10px;padding:10px 14px;border-bottom:1px solid var(--line-2);cursor:pointer;align-items:flex-start}
  .mail-tl-row:hover{background:var(--panel-2)}
  .mail-tl-dir{flex:none;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:700;margin-top:2px}
  .mail-tl-dir.in{background:var(--accent-soft);color:var(--accent-ink)}
  .mail-tl-dir.out{background:var(--line-2);color:var(--muted)}
  .mail-tl-main{min-width:0;flex:1}
  .mail-tl-top{display:flex;align-items:baseline;gap:8px}
  .mail-tl-who{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-tl-when{margin-left:auto;font-size:10.5px;color:var(--faint);white-space:nowrap;font-family:var(--font-num)}
  .mail-tl-subj{font-size:12.5px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-tl-prev{font-size:11.5px;color:var(--faint);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-tl-cmt{font-size:11px;color:var(--accent-ink);font-weight:600;margin-left:4px}
  /* Email comments — stored on the link doc, rendered below the email body in the reading pane */
  .mail-comment-section{border-top:1px solid var(--line-2);margin-top:20px;padding-top:16px}
  .mail-comment-thread{display:flex;flex-direction:column;gap:8px;margin-bottom:12px}
  /* One card per comment thread (2026-08-25). Most emails will only ever have one, so a single card
     has to look ordinary rather than like scaffolding — the structure only earns its keep when a
     conversation genuinely has two questions running at once. */
  .mail-comment-card{border:1px solid var(--line-2);border-radius:9px;background:var(--panel);padding:8px 10px}
  .mail-comment-card.targeted{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
  .mail-comment-card.done{background:var(--panel-2);opacity:.9}
  .mct-head{display:flex;align-items:center;gap:8px;font-size:12px}
  .mct-dot{flex:none;font-size:8px;color:var(--warn)}
  .mct-dot.done{font-size:11px;color:var(--good)}
  .mct-dot.you{color:var(--accent)}
  /* "Somebody is waiting on YOU" — on the folded header so it is visible without opening, and on the
     card so opening answers "which thread?" straight away. */
  .mct-you{flex:none;font-size:10px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
    color:var(--accent-ink);background:var(--accent-soft);border-radius:9px;padding:1px 7px}
  .mail-comment-card.wants-me{border-color:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}
  .mct-title{flex:1;min-width:0;font-weight:650;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* The title is the rename control. Underlined only on hover so the card stays quiet until you go
     looking — a dotted underline on every thread name would read as an error. */
  .mct-title.can-rename{cursor:text;border-bottom:1px dashed transparent}
  .mct-title.can-rename:hover{border-bottom-color:var(--line)}
  .mct-rename{flex:1;min-width:0;font-size:12px;font-weight:650;padding:2px 6px}
  .mct-n{flex:none;font-family:var(--font-num);font-size:10.5px;color:var(--muted);background:var(--panel-2);
    border:1px solid var(--line-2);border-radius:9px;padding:0 5px;line-height:15px}
  .mct-btn{flex:none;border:1px solid var(--line);background:var(--panel);border-radius:7px;cursor:pointer;
    padding:2px 8px;font:inherit;font-size:11px;color:var(--muted)}
  .mct-btn:hover{color:var(--ink);border-color:var(--muted)}
  .mct-btn.accent{color:var(--accent-ink);border-color:var(--accent-soft);background:var(--accent-soft)}
  .mct-btn[disabled]{opacity:.45;cursor:default}
  .mct-res{margin-top:6px;font-size:11.5px;color:var(--ink)}
  /* The header line: how many separate things are going on, and the one way to start another. */
  .mct-bar{display:flex;align-items:center;gap:8px;padding:0 2px 2px}
  .mct-count{flex:1;min-width:0;font-size:11px;font-weight:650;letter-spacing:.03em;text-transform:uppercase;color:var(--faint)}
  /* The collapsed preview — enough to know whether the thread needs you, not the thread itself. */
  .mct-prev{margin-top:6px;padding-left:16px;border-left:2px solid var(--line-2)}
  .mct-prev-row{display:flex;align-items:baseline;gap:8px;font-size:11px}
  .mct-prev-who{font-weight:650;color:var(--muted)}
  .mct-prev-when{margin-left:auto;font-family:var(--font-num);color:var(--faint);font-size:10.5px;white-space:nowrap}
  .mct-prev-text{font-size:12px;color:var(--ink);margin:1px 0 5px;overflow-wrap:anywhere}
  .mct-prev-more{font-size:10.5px;color:var(--faint);font-style:italic;margin:0 0 5px}
  .mail-comment-to{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted);padding:0 0 6px}
  .mail-comment-to b{color:var(--ink)}
  .mail-comment-row{display:flex;gap:8px;align-items:flex-start}
  .mail-comment-avatar{flex:none;width:26px;height:26px;border-radius:50%;background:var(--accent-soft);color:var(--accent-ink);font-size:11px;font-weight:700;display:grid;place-items:center;margin-top:1px}
  .mail-comment-body{flex:1;background:var(--panel-2);border-radius:9px;padding:8px 10px;min-width:0}
  .mail-comment-header{display:flex;align-items:baseline;gap:8px;margin-bottom:3px}
  .mail-comment-name{font-size:11.5px;font-weight:600}
  .mail-comment-when{margin-left:auto;font-size:10.5px;color:var(--faint);font-family:var(--font-num)}
  .mail-comment-edited{color:var(--faint);font-weight:400;cursor:help}
  .mail-comment-actions{display:flex;gap:8px;margin-left:6px}
  .mail-comment-actions button{border:0;background:none;padding:0;font-size:10.5px;color:var(--faint);font-weight:600;cursor:pointer}
  .mail-comment-actions button:hover{color:var(--accent-ink);text-decoration:underline}
  .mail-comment-row.deleted .mail-comment-body{background:transparent;border:1px dashed var(--line)}
  .mail-comment-tombstone{color:var(--faint);font-style:italic}
  .mail-comment-edit-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:6px}
  .mail-comment-text{font-size:12.5px;line-height:1.45}
  .mail-comment-text .at{color:var(--accent-ink);font-weight:600}
  .mail-comment-input-row{display:flex;gap:8px;align-items:flex-end;position:relative}
  .mail-comment-input-row.rv-col{flex-direction:column;align-items:stretch;gap:6px}
  .mail-comment-input-row.rv-col .btn{align-self:flex-start;min-width:64px}
  /* Live @-mention highlighting while typing (not just once posted): the backdrop div sits
     UNDER the textarea, showing the same text with mention spans given a visible background —
     its own base text stays color:transparent so it never double-renders under the textarea's
     real glyphs. Every font/padding/border/line-height value below must match the textarea's
     exactly or the two layers drift out of alignment. */
  .mail-comment-input-wrap{position:relative;flex:1;min-width:0}
  .mail-comment-backdrop{position:absolute;inset:0;z-index:0;box-sizing:border-box;border:1px solid transparent;border-radius:9px;font-size:12.5px;line-height:1.45;padding:8px 10px;font-family:var(--font-ui);white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:transparent;pointer-events:none}
  .mail-comment-backdrop .at{background:var(--accent-soft);border-radius:3px;color:transparent}
  .mail-comment-input-wrap textarea{position:relative;z-index:1;width:100%;resize:none;border:1px solid var(--line);border-radius:9px;background:transparent;color:var(--ink);font-size:12.5px;line-height:1.45;padding:8px 10px;font-family:var(--font-ui);min-height:38px;max-height:120px;overflow-y:auto;box-sizing:border-box}
  .mail-comment-input-wrap textarea:focus{outline:none;border-color:var(--accent)}
  .mail-at-dd{position:absolute;bottom:calc(100% + 4px);left:0;min-width:200px;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:var(--shadow-lg);z-index:30;overflow:hidden;display:none}
  .mail-at-dd:not(:empty){display:block}
  .mail-at-item{padding:7px 11px;font-size:12.5px;cursor:pointer}
  .mail-at-item:hover{background:var(--panel-2)}
  /* Document register — attachments discovered across filed emails (client/job Documents tabs) */
  .mail-docs{display:flex;flex-direction:column}
  .mail-doc-group{display:flex;align-items:center;gap:7px;padding:9px 14px 6px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--faint);background:var(--panel-2);border-bottom:1px solid var(--line-2)}
  .mail-doc-count{font-family:var(--font-num);font-weight:600;letter-spacing:0;background:var(--line-2);color:var(--muted);border-radius:20px;padding:1px 7px;font-size:10.5px}
  .mail-doc-row{display:flex;gap:10px;padding:9px 14px;border-bottom:1px solid var(--line-2);align-items:center;cursor:pointer}
  .mail-doc-row:hover{background:var(--panel-2)}
  .mail-doc-icon{flex:none;font-size:16px;line-height:1}
  .mail-doc-main{min-width:0;flex:1}
  .mail-doc-name{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-doc-size{font-weight:500;color:var(--faint);font-size:11px;font-family:var(--font-num)}
  .mail-doc-meta{font-size:11.5px;color:var(--faint);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mail-doc-row .mail-doc-dl{flex:none;padding:4px 10px;font-size:11.5px}
  /* Insights — responsiveness analytics (Triage 4th tab, js/mail-insights.js) */
  .mi-head{display:flex;align-items:center;gap:10px;margin-bottom:14px}
  .mi-months{display:flex;gap:3px;background:var(--panel-2);border-radius:9px;padding:3px}
  .mi-months button{border:0;background:transparent;color:var(--muted);font-weight:600;font-size:12px;padding:5px 11px;border-radius:6px}
  .mi-months button.on{background:var(--panel);color:var(--ink);box-shadow:var(--shadow)}
  .mi-head .btn{margin-left:auto}
  .mi-clickable{cursor:pointer;border-radius:var(--r);transition:box-shadow .12s}
  .mi-clickable:hover{box-shadow:0 0 0 2px var(--accent-soft)}
  .mi-roles{padding:4px 14px}
  .mi-role-row{display:flex;justify-content:space-between;padding:6px 0;font-size:12.5px;border-bottom:1px solid var(--line-2);cursor:pointer}
  .mi-role-row:last-child{border-bottom:0}
  .mi-role-row:hover{color:var(--accent-ink)}
  .mi-role-row.warn span{color:var(--warn)}
  .mi-drill-list{display:flex;flex-direction:column}
  .mi-drill-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line-2)}
  .mi-drill-row:last-child{border-bottom:0}
  .mi-drill-main{min-width:0;flex:1}
  .mi-add-form{flex:1;display:flex;flex-direction:column;gap:6px;padding:8px 0}
  .mi-add-form .f{width:100%}
  .mi-row{display:flex;align-items:center;gap:10px;padding:9px 14px;border-bottom:1px solid var(--line-2)}
  .mi-row:last-child{border-bottom:0}
  .mi-row-main{min-width:0;flex:1}
  .mi-row-main[data-mail-open-real]{cursor:pointer}
  .mi-row-main[data-mail-open-real]:hover .mi-row-who{color:var(--accent-ink)}
  .mi-row-who{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mi-row-subj{font-size:11.5px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .mi-row-age{flex:none;font-family:var(--font-num);font-size:11.5px;color:var(--muted);font-weight:600}
  .mi-excl-list{margin-top:14px;background:var(--panel-2);border-radius:10px;padding:4px 14px}
  .mi-excl-h{font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--faint);padding:8px 0 4px}
  .mi-excl-row{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:7px 0;font-size:12px;color:var(--muted);border-top:1px solid var(--line-2)}
  .mi-foot{margin-top:14px;font-size:11px;color:var(--faint);line-height:1.5}
  .mi-info{display:inline-flex;vertical-align:-2px;margin-left:4px;color:var(--faint);cursor:help}
  .mi-info svg{fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}
  .mi-info:hover,.mi-info:focus-visible{color:var(--accent)}
  .err{margin:0 0 10px;padding:9px 12px;border-radius:9px;background:var(--crit-soft);color:var(--crit);font-size:12px}

  /* Compose — chip recipients, Cc/Bcc toggle, attachments, rich text */
  .mc-row{display:flex;gap:8px;margin-bottom:8px;align-items:flex-start;position:relative}
  .mc-row label{font-size:11.5px;color:var(--faint);min-width:34px;padding-top:8px}
  .mail-recip{flex:1;display:flex;flex-wrap:wrap;gap:5px;align-items:center;border:1px solid var(--line);background:var(--panel);border-radius:9px;padding:5px 8px;min-height:34px}
  .mail-recip:focus-within{border-color:var(--accent)}
  .mail-recip.drag-over{border-color:var(--accent);border-style:dashed;background:var(--accent-soft)}
  .mail-recip .recip-input{flex:1;min-width:120px;border:0;background:transparent;outline:none;font-size:12.5px;color:var(--ink);padding:3px 2px}
  .mchip{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:600;background:var(--accent-soft);color:var(--accent-ink);border-radius:20px;padding:3px 5px 3px 10px;white-space:nowrap;cursor:grab}
  .mchip.dragging{opacity:.4}
  .mchip .x{cursor:pointer;opacity:.7;font-size:14px;line-height:1}.mchip .x:hover{opacity:1}
  .mc-ccbcc-toggle{display:flex;gap:8px;padding-top:8px}
  .mc-ccbcc-toggle .link{font-size:11.5px;color:var(--accent-ink);font-weight:600;cursor:pointer}
  .mail-ac{position:absolute;left:42px;right:0;top:100%;z-index:30;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:var(--shadow-lg);max-height:180px;overflow-y:auto;margin-top:2px}
  .mail-ac-i{padding:7px 11px;font-size:12.5px;cursor:pointer;display:flex;gap:7px;align-items:baseline}
  .mail-ac-i:hover{background:var(--panel-2)}.mail-ac-i span{color:var(--faint);font-size:11px}
  .mc-atts{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
  .mail-att-chip{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;background:var(--line-2);color:var(--muted);border-radius:20px;padding:4px 6px 4px 10px}
  /* A file that came with the message being forwarded, rather than one picked here. Marked, because
     a forward that quietly grew three paperclips otherwise reads as the app attaching things on your
     behalf. */
  .mail-att-chip.from-original{border-style:dashed;color:var(--muted)}
  .mail-att-chip .x{cursor:pointer;opacity:.7}.mail-att-chip .x:hover{opacity:1}
  .mc-toolbar-row{margin-bottom:8px}
  .mail-quill{background:var(--panel);border-radius:9px;overflow:hidden}
  .mail-quill .ql-toolbar.ql-snow{border-color:var(--line);background:var(--panel-2);border-radius:9px 9px 0 0}
  .mail-quill .ql-container.ql-snow{border-color:var(--line);border-radius:0 0 9px 9px;font-size:13.5px;font-family:var(--font-ui)}
  /* No cap of its own (a signature with a logo image needs more than a fixed 220px, and capping it
     here on top of mail-compose-fields' own scroll was two nested scrollbars for one thing) — the
     card as a whole is what's bounded (see .mail-compose), with a single scroll region in fields. */
  .mail-quill .ql-editor{min-height:90px;color:var(--ink)}
  .mail-quill .ql-editor.ql-blank::before{color:var(--faint);font-style:normal}
  .mail-quill .ql-snow .ql-stroke{stroke:var(--muted)}.mail-quill .ql-snow .ql-fill{fill:var(--muted)}
  .mail-quill .ql-snow .ql-picker{color:var(--muted)}
  .mail-quill .ql-snow.ql-toolbar button:hover .ql-stroke,.mail-quill .ql-snow.ql-toolbar button.ql-active .ql-stroke{stroke:var(--accent-ink)}
  .mail-quill .ql-snow.ql-toolbar button:hover .ql-fill,.mail-quill .ql-snow.ql-toolbar button.ql-active .ql-fill{fill:var(--accent-ink)}

  /* Collapsible quoted-thread turns — sits below the Quill editor, never inside it */
  .mail-quote-thread{margin-top:14px;border-top:1px solid var(--line-2)}
  .mail-quote-turn{border-bottom:1px solid var(--line-2)}
  .mqt-head{display:flex;align-items:center;gap:8px;padding:9px 2px;cursor:pointer;font-size:12px;color:var(--muted)}
  .mqt-head:hover{color:var(--ink)}
  .mqt-chev{width:13px;height:13px;flex:none;stroke:currentColor;stroke-width:2;fill:none;transition:transform .15s}
  .mail-quote-turn.open .mqt-chev{transform:rotate(90deg)}
  .mqt-from{font-weight:600;color:var(--ink)}
  .mqt-to{color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* Attachments on a quoted turn. Sits before the date rather than after it, so a row that carries
     files reads as "who, what's attached, when" — the clip is the thing you are scanning for. */
  .mqt-clip{flex:none;border:1px solid var(--line-2);background:var(--panel-2);border-radius:999px;
    cursor:pointer;padding:0 7px;font:inherit;font-size:10.5px;line-height:16px;color:var(--muted)}
  .mqt-clip:hover{border-color:var(--muted);color:var(--ink)}
  /* Opening the original: an icon on the header line beside the sender and the date, not a button in
     the body. Quiet until the row is hovered, since it is an occasional reach rather than the point
     of the row. */
  .mqt-openbtn{flex:none;border:0;background:transparent;cursor:pointer;padding:2px 4px;border-radius:6px;
    color:var(--faint);display:inline-flex;opacity:0;transition:opacity .12s}
  .mqt-head:hover .mqt-openbtn,.mqt-openbtn:focus{opacity:1}
  .mqt-openbtn:hover{color:var(--ink);background:var(--panel-2)}
  .mqt-openbtn .ic{width:12px;height:12px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
  .mqt-when{margin-left:auto;font-family:var(--font-num);color:var(--faint);white-space:nowrap;font-size:11px}
  /* A time we could not check against our own record, shown as the sender wrote it. Marked, because
     it is the one date on the row that may be in another timezone. */
  /* A turn we hold no record of. It used to be italic and faded, which read as "something is wrong
     with this row" rather than "we cannot vouch for this time" — reported as "why is this one styled
     differently?". The label says it instead, and the date is left looking like a date. */
  .mqt-when.as-quoted{opacity:.8}
  .mqt-asq{flex:none;margin-left:auto;font-size:9.5px;font-weight:650;letter-spacing:.03em;text-transform:uppercase;
    color:var(--faint);border:1px solid var(--line-2);border-radius:8px;padding:0 5px;line-height:14px;cursor:help}
  .mqt-asq + .mqt-when{margin-left:6px}
  .mqt-body{padding:2px 2px 12px 21px;font-size:13px;line-height:1.55;color:var(--ink)}
  /* The folded signature block (2026-08-25). The name is set in an italic serif so it reads as a
     sign-off rather than a button label — the point is that you can still see who signed the email
     even while the block itself is out of the way. */
  /* Two clear lines between the last line of the message and the signature control, always. Asked
     for as: "where none exists we give it two lines, and if there is more gap we still bring it down
     to a consistent gap." The second half is the harder one and is done in JS, not here —
     mailTrimTrailingBlanks drops the run of empty MsoNormal paragraphs Outlook leaves behind, so this
     margin is the ONLY thing setting the space rather than being added to whatever the sender's
     client happened to emit. 40px is two lines of the 13.5px/1.6 body text. */
  .mail-sigfold{margin:40px 0 10px}
  .msf-btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line-2);background:var(--panel-2);
    border-radius:999px;cursor:pointer;padding:3px 11px 3px 9px;color:var(--muted);font:inherit}
  .msf-btn:hover{border-color:var(--muted);color:var(--ink)}
  .msf-btn .ic{width:13px;height:13px;flex:none;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
  /* An actual hand, not italics pretending. Alex Brush (index.html) with a cursive fallback stack, so
     a blocked or slow font still reads as a signature rather than as body text — the whole point of
     the treatment is that the name looks SIGNED.

     Bigger than the serif it replaces because a brush face has a small x-height: at 12.5px it was a
     squiggle, and 18px matches the visual weight of the text around it. */
  .msf-name{font-family:var(--font-sig);font-size:var(--font-sig-size);line-height:1;color:var(--ink);padding-bottom:2px}
  .mail-sigfold.open .msf-btn{background:var(--panel);border-color:var(--line)}
  .msf-body{margin-top:8px;padding-left:11px;border-left:2px solid var(--line-2);font-size:12.5px;color:var(--muted)}
  .msf-body img{max-width:100%}
  /* The same turns in the READ view (2026-08-25). Given a heading and a little more air than the
     composer's version, because here they are a list you choose from rather than a tail below the
     thing you are writing. Rows sit on the panel so the collapsed history reads as one block. */
  .mail-quote-thread.read{margin-top:18px;border-top:1px solid var(--line);padding-top:4px}
  .mail-quote-thread.read .mqt-lab{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:650;letter-spacing:.03em;text-transform:uppercase;color:var(--faint);padding:8px 2px 2px}
  /* Expand all / collapse all. Two distinct glyphs rather than one that flips — see the note in
     mailReadTurnsHtml. `.off` is the "this would change nothing" state: still there, still says what
     it does on hover, but plainly not the one to press. */
  .mqt-all{margin-left:auto;display:inline-flex;gap:2px}
  .mqt-allbtn{border:0;background:transparent;cursor:pointer;padding:3px 5px;border-radius:6px;color:var(--muted);display:inline-flex}
  .mqt-allbtn:hover{background:var(--panel-2);color:var(--ink)}
  .mqt-allbtn .ic{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
  /* Dimmed, not erased: at --line it read as a missing button rather than an idle one, which is a
     different message. Opacity keeps the shape legible so the pair still shows both choices. */
  .mqt-allbtn.off{opacity:.3;cursor:default}
  .mqt-allbtn.off:hover{background:transparent;color:var(--muted)}
  .mail-quote-thread.read .mqt-head{padding:8px 8px;border-radius:7px}
  .mail-quote-thread.read .mqt-head:hover{background:var(--panel-2)}
  .mail-quote-thread.read .mail-quote-turn:last-child{border-bottom:0}

  /* Admin → Practice setup: the client × service matrix, the staff rollup, the yearly staff load.
     A matrix cell has to read at a glance across 20 columns, so a person is an initial disc rather
     than a name, and "inherited from the client default" is carried by opacity — the same value, just
     not set on the service itself. */
  .ps-bar-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin:0 0 12px}
  .ps-bar-lab{font-size:11.5px;font-weight:600;color:var(--muted)}
  .ps-bar-hint{font-size:11.5px;color:var(--faint);flex:1;min-width:180px}
  .ps-ini{display:inline-flex;align-items:center;justify-content:center;width:21px;height:21px;border-radius:50%;
    font-size:9.5px;font-weight:700;color:#fff;letter-spacing:.02em;vertical-align:middle}
  .ps-ini.inherit{opacity:.42}
  .ps-none{color:var(--faint)}
  .ps-off{font-size:10.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.05em}
  /* ORG / PER — a two-letter marker beside the name, so the organisation/personal split is visible in
     the grid itself and not only in the Kind filter. */
  .ps-kind{font-size:9px;font-weight:700;letter-spacing:.06em;padding:1px 4px;border-radius:4px;
    background:var(--bb-soft);color:var(--bb);vertical-align:middle}
  .ps-kind.org{background:var(--info-soft);color:var(--info)}
  .ps-mix{display:inline-block;font-size:11px;color:var(--muted);background:var(--panel-2);border:1px solid var(--line-2);
    border-radius:5px;padding:1px 6px;margin:1px 4px 1px 0;white-space:nowrap}
  .ps-mix b{font-family:var(--font-num);color:var(--ink)}
  .ps-mix.more{color:var(--faint);cursor:default}
  .ps-prog{display:inline-flex;align-items:center;gap:7px}
  .ps-bar{display:inline-block;height:6px;width:64px;border-radius:3px;background:var(--line-2);overflow:hidden;vertical-align:middle}
  .ps-bar i{display:block;height:100%;background:var(--good);border-radius:3px}
  .ps-pct{font-family:var(--font-num);font-size:11px;color:var(--muted);min-width:30px;text-align:right}
  .ps-frac{font-family:var(--font-num);font-size:11.5px;color:var(--muted);white-space:nowrap}
  .ps-frac b{color:var(--ink)}
  .ps-frac.done,.ps-frac.done b{color:var(--good)}

  /* ---- Folded group in the bill editor (js/invoice.js) ----------------------------------------
     ONE header per fold, then its members indented beneath it. The banner used to repeat on every
     member card, so a 7-line fold said "prints as one line" seven times. */
  .lie-foldhead{border:1px solid var(--accent);border-bottom:0;border-radius:10px 10px 0 0;
    background:var(--accent-soft);padding:9px 12px;margin-bottom:0}
  /* Members hang off the header: square the top corners and share its left edge so the group reads
     as one block rather than as loose cards that happen to follow a banner. */
  .lie-foldmem{border-left:3px solid var(--accent)!important;border-radius:0 10px 10px 0!important;margin-bottom:4px!important}
  .lie-foldhead+.lie-foldmem{border-top-right-radius:0!important}

  /* ---- Bill editor selection bar (js/invoice.js lieBulkBarHtml) -------------------------------
     Sticky, so on a 25-line bill the actions stay reachable while you scroll to tick more lines —
     scrolling back to the top to act was the thing that made the old per-card buttons feel necessary. */
  .lie-bulk{position:sticky;top:0;z-index:3;display:flex;align-items:center;gap:9px;flex-wrap:wrap;
    padding:9px 12px;margin:0 0 12px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2)}
  .lie-bulk.on{border-color:var(--accent);background:var(--accent-soft)}
  .lie-bulk-n{font-size:12px;font-weight:700;color:var(--muted);white-space:nowrap}
  .lie-bulk.on .lie-bulk-n{color:var(--accent-ink)}
  .lie-bulk-acts{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
  /* Disabled stays legible rather than ghosted — the tooltip on it is the explanation of why the
     action does not apply, and nobody hovers something they read as absent. */
  .lie-bulk .btn[disabled]{opacity:.5;cursor:not-allowed}
  .lie-bulk .lie-bulk-wo:not([disabled]):hover,.lie-bulk .lie-bulk-remove:not([disabled]):hover{border-color:var(--crit);color:var(--crit)}

  /* ---- Invoice / credit-note preview action bar (js/invoice.js) -------------------------------
     Its own row under the header. These used to share the header with the title, page count and zoom,
     and on a voided invoice with a payment link that came to nine text buttons wrapping over two
     lines. Grouped by what the action does to the document — work on it · send it · reverse it —
     with a hairline between groups so the eye can find the group before the button. */
  .inv-actions{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:8px 20px;border-bottom:1px solid var(--line);background:var(--panel-2)}
  .inv-actg{display:inline-flex;align-items:center;gap:4px}
  .inv-actg+.inv-actg{padding-left:14px;border-left:1px solid var(--line)}
  .inv-actions .icon-btn{width:30px;height:30px;border-radius:8px}
  .inv-actions .icon-btn .ic{width:15px;height:15px}
  .inv-actions .icon-btn:hover{background:var(--panel);border-color:var(--accent);color:var(--accent-ink)}
  /* Reversing a document is not the same class of act as printing it — the void/credit group reads
     in the critical colour on hover so a mis-aimed click has a moment to register. */
  .inv-actg:last-child .inv-void:hover,.inv-actg:last-child .inv-credit:hover{border-color:var(--crit);color:var(--crit)}
  .inv-zoomrange{height:4px;-webkit-appearance:none;appearance:none;background:var(--line);border-radius:2px;outline:none}
  .inv-zoomrange::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:var(--accent);cursor:pointer}
  .inv-zoomrange::-moz-range-thumb{width:13px;height:13px;border:0;border-radius:50%;background:var(--accent);cursor:pointer}

  /* ---- Client grading (js/client-grades.js) — the drawer scorecard ---------------------------- */
  .cg-pane{padding:2px 0}
  .cg-head{display:flex;gap:16px;align-items:center;padding:14px 15px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2);margin-bottom:12px}
  .cg-badge{width:64px;height:64px;border-radius:14px;display:grid;place-items:center;font-size:24px;font-weight:800;letter-spacing:-.02em;color:#fff;background:var(--bb);flex:none}
  .cg-badge.good{background:var(--good)}.cg-badge.info{background:var(--info)}
  .cg-badge.warn{background:var(--warn)}.cg-badge.crit{background:var(--crit)}
  .cg-head-b{min-width:0}
  .cg-score{font-family:var(--font-num);font-size:26px;font-weight:800;line-height:1.1;color:var(--ink)}
  .cg-pcm{font-size:15px;font-weight:600;color:var(--faint);margin-left:2px}
  .cg-meta{font-size:12px;color:var(--muted);margin-top:3px}
  .cg-stale{display:flex;align-items:center;gap:8px;margin-top:7px;font-size:12px;color:var(--warn)}
  .cg-caps{border:1px solid var(--warn);background:var(--warn-soft);border-radius:11px;padding:12px 15px;margin-bottom:12px;font-size:12.5px;color:var(--ink)}
  .cg-caps ul{margin:7px 0 0;padding-left:19px}.cg-caps li{margin:3px 0;color:var(--muted)}
  .cg-actions{display:flex;align-items:center;gap:12px;margin-bottom:16px}
  .cg-grp{margin-bottom:18px}
  .cg-grp-h{display:flex;align-items:center;gap:10px;padding:0 2px 8px;border-bottom:1px solid var(--line-2);margin-bottom:8px}
  .cg-grp-h h4{font-size:13px;font-weight:700;margin:0}
  .cg-grp-p{font-family:var(--font-num);font-size:12px;color:var(--muted);margin-left:auto}
  .cg-grp-bar{display:inline-block;height:6px;width:72px;border-radius:3px;background:var(--line-2);overflow:hidden}
  .cg-grp-bar i{display:block;height:100%;background:var(--good);border-radius:3px}
  .cg-row{display:flex;align-items:flex-start;gap:11px;padding:9px 4px;border-bottom:1px solid var(--line-2)}
  .cg-row:last-child{border-bottom:0}
  .cg-dot{width:9px;height:9px;border-radius:50%;flex:none;margin-top:5px;background:var(--bb)}
  .cg-dot.good{background:var(--good)}.cg-dot.info{background:var(--info)}
  .cg-dot.warn{background:var(--warn)}.cg-dot.crit{background:var(--crit)}
  .cg-dot.none{background:transparent;border:1.5px dashed var(--faint)}
  .cg-lab{flex:1;min-width:0}.cg-lab b{font-size:12.5px}
  .cg-hint{font-size:11.5px;color:var(--faint);margin-top:2px;line-height:1.45}
  .cg-detail{font-size:11.5px;color:var(--muted);margin-top:3px}
  .cg-val{text-align:right;font-size:12px;color:var(--ink);min-width:140px;flex:none}
  .cg-unans{color:var(--faint);font-style:italic}
  .cg-src{display:block;font-size:10px;letter-spacing:.05em;text-transform:uppercase;margin-top:3px;color:var(--faint)}
  .cg-src.ovr{color:var(--warn)}.cg-src.ev{color:var(--info)}.cg-src.ans{color:var(--accent-ink)}
  /* What survives of the old embedded incident list: the "Risk on the record" summary card, which is
     all this tab shows now — the records themselves live on Record (js/client-records-ui.js). The
     per-incident row classes (.old / -d / -a / -b / -del) went with the renderer that used them. */
  .cg-inc{border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:8px;background:var(--panel)}
  .cg-inc-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .cg-inc-n{font-size:12px;color:var(--muted);margin-top:6px;line-height:1.5}
  .cg-note{font-size:12.5px;color:var(--muted);line-height:1.6;padding:2px}
  .cg-empty{font-size:12px;color:var(--faint);padding:8px 2px}
  .cg-hist{display:flex;align-items:center;gap:11px;padding:6px 2px;border-bottom:1px solid var(--line-2);font-size:12px;color:var(--muted)}
  .cg-hist:last-child{border-bottom:0}

  /* ---- Client records (js/client-records-ui.js) — the drawer's Record tab ----------------------
     Reuses the grading pane's group/heading/hint/empty rhythm above deliberately: the two tabs sit
     next to each other on the same drawer and a second visual language between them would read as
     two different apps. Only the row and the alarm band are new shapes. */
  .cr-pane{padding:2px 0}
  .cr-grp{margin-bottom:18px}
  .cr-grp-h{display:flex;align-items:center;gap:10px;padding:0 2px 8px;border-bottom:1px solid var(--line-2);margin-bottom:8px}
  .cr-grp-h h4{font-size:13px;font-weight:700;margin:0}
  .cr-hint{font-size:11.5px;color:var(--faint);margin:0 0 10px;line-height:1.45}
  .cr-empty{font-size:12px;color:var(--faint);padding:8px 2px}
  .cr-row{border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:8px;background:var(--panel);cursor:pointer}
  .cr-row:hover{background:var(--panel-2)}
  /* The escalated tier, and the second axis on top of it. A complaint has to read differently from a
     note-to-self at a glance or the tiers are decoration: `.escalated` takes a crit left bar and a
     heavier title, and `.outstanding` deepens it — an escalated record nobody has dealt with is the
     loudest row on the tab. An outstanding LESSON is warm, not critical: it is a reminder, not a
     complaint, and making both scream leaves nothing to escalate to. */
  .cr-row.escalated{border-left:3px solid var(--crit);border-color:var(--crit) var(--line) var(--line) var(--crit)}
  .cr-row.escalated .cr-title{font-weight:700}
  .cr-row.escalated.outstanding{background:var(--crit-soft)}
  .cr-row.escalated.outstanding .cr-ico{background:var(--crit);color:#fff}
  .cr-row.outstanding:not(.escalated){border-left:3px solid var(--warn)}
  /* "Insight asked and did not get an answer." Distinct from empty and from loading, because a
     reader has to be able to tell "nothing has happened here" from "we could not check". */
  .cr-stalled{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:9px 13px;margin-bottom:14px;
    border:1px solid var(--warn);background:var(--warn-soft);border-radius:10px;font-size:12.5px;color:var(--ink)}
  /* One line at the top of the tab: what is still owed on this client, before anybody scrolls. */
  .cr-outstanding{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 13px;margin-bottom:14px;
    border:1px solid var(--warn);background:var(--warn-soft);border-radius:10px;font-size:12.5px;color:var(--ink)}
  .cr-row-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
  .cr-ico{width:18px;height:18px;border-radius:5px;display:grid;place-items:center;font-size:11px;flex:none;background:var(--line-2);color:var(--muted)}
  .cr-title{font-size:12.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:38ch}
  .cr-when{font-family:var(--font-num);font-size:11.5px;color:var(--muted);margin-left:auto;flex:none}
  .cr-meta{font-size:11.5px;color:var(--muted);margin-top:5px;line-height:1.5}
  .cr-body{font-size:12px;color:var(--muted);margin-top:6px;line-height:1.55;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .cr-cred{font-size:11.5px;color:var(--accent-ink);margin-top:5px}
  .cr-foot{font-size:11px;color:var(--faint);margin-top:6px}
  .cr-res{font-size:12px;color:var(--muted);margin-top:6px;padding-top:6px;border-top:1px solid var(--line-2);line-height:1.5}
  /* The un-dismissable band. Loud on purpose, and it carries no close control of any kind — see the
     header of js/client-records-ui.js. */
  .cr-alarm{border:1px solid var(--crit);background:var(--crit-soft);border-radius:11px;padding:12px 15px;margin-bottom:16px}
  .cr-alarm-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12.5px;color:var(--ink)}
  .cr-alarm .cr-hint{margin:7px 0 9px}
  .cr-alarm-row{display:flex;align-items:center;gap:11px;padding:8px 0;border-top:1px solid var(--line-2)}
  .cr-fo-grp{border:1px solid var(--line);border-radius:10px;margin-bottom:8px;background:var(--panel);overflow:hidden}
  .cr-fo-h{display:flex;align-items:center;gap:9px;padding:10px 12px;cursor:pointer;flex-wrap:wrap}
  .cr-fo-h:hover{background:var(--panel-2)}
  .cr-fo-caret{color:var(--faint);font-size:10px;width:10px;flex:none}
  .cr-fo-grp .cr-fo{padding:9px 12px;border-top:1px solid var(--line-2);border-bottom:0}
  .cr-fo-grp .cr-empty{padding:8px 12px}
  .cr-fo{display:flex;align-items:flex-start;gap:11px;padding:9px 4px;border-bottom:1px solid var(--line-2)}
  .cr-fo:last-child{border-bottom:0}
  .cr-fo .hdot{margin-top:5px;flex:none}
  .cr-fo.late .cr-title{color:var(--crit)}
  /* A period where process-guide and the recorded answer disagree. Warm, not critical: nothing is
     wrong with the filing, something is wrong with the record of it (js/filing-outcomes.js rule 1). */
  .cr-fo.clash{background:var(--warn-soft)}
  .cr-pg-foot{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 12px;margin-top:4px;
    border-top:1px solid var(--line-2);font-size:11.5px;color:var(--faint);line-height:1.45}
  .cr-read-s{margin-bottom:12px}
  .cr-read-s b{font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}
  .cr-read-s p{margin:4px 0 0;font-size:12.5px;color:var(--ink);line-height:1.6;white-space:pre-wrap}
  .cr-acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}

  /* ---- Revenue demand notices (js/revenue-demands-ui.js) --------------------------------------
     Shares the register's row rhythm on purpose — they sit on the same tab — with one difference:
     an OPEN notice inside its window is warm, and one past day 4 (.hot) is critical, because that
     is the moment the primary owner and the partners start getting copied. */
  .rd-row{border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:8px;background:var(--panel);cursor:pointer}
  .rd-row:hover{background:var(--panel-2)}
  /* ---- THE ESCALATION LADDER. An open demand notice is a FILLED card, never a white one with a hint
     of colour down its edge: Revenue has written to a client demanding money and there is a clock on
     it, so the base is already loud and the levels above it get louder. The level is decided in
     js/revenue-demands.js (rdLoudness) — the maximum of what the letter says and what the clock says
     — precisely so this stylesheet is not the thing choosing between two colours by source order.
       lvl1  a demand, inside the four working days      — amber, filled
       lvl2  a demand past the window                    — red
       lvl3  a FINAL demand: enforcement is next         — red, heavier, red heading
       lvl4  REFERRED TO THE SHERIFF: fees and interest are already being added — the loudest thing
             on the tab, with a ring, because there is no stage after it
     A resolved notice gets none of this on purpose (lvl0, the plain .rd-row above): if closed ones
     shouted too, the open ones would stop standing out, which is the entire point. */
  /* Level 1 is written as the default for ANY open notice as well as for lvl1 by name: if a level ever
     arrives with no rule of its own, it still renders as an open demand notice rather than as a plain
     white card — invisible in exactly the case the ladder exists for. */
  .rd-row.open,.rd-row.open.lvl1{border-left:4px solid var(--warn);background:var(--warn-soft);
    border-color:color-mix(in srgb,var(--warn) 32%,var(--line));
    border-left-color:var(--warn)}
  .rd-row.open:hover,.rd-row.open.lvl1:hover{background:color-mix(in srgb,var(--warn) 16%,var(--panel))}
  /* Every red level mixes against --panel rather than hard-coding a tint, so the ladder keeps the same
     three distinguishable steps in the dark theme, where --panel is nearly black and a fixed pale red
     would collapse them all into one. */
  .rd-row.open.lvl2{border-left-color:var(--crit);background:var(--crit-soft);
    border-color:color-mix(in srgb,var(--crit) 32%,var(--line))}
  .rd-row.open.lvl2:hover{background:color-mix(in srgb,var(--crit) 18%,var(--panel))}
  .rd-row.open.lvl3{border-left:5px solid var(--crit);
    background:color-mix(in srgb,var(--crit) 24%,var(--panel));
    border-color:color-mix(in srgb,var(--crit) 55%,var(--line))}
  .rd-row.open.lvl3:hover{background:color-mix(in srgb,var(--crit) 32%,var(--panel))}
  .rd-row.open.lvl3 .rd-head{color:var(--crit);font-weight:800}
  .rd-row.open.lvl4{border-left:7px solid var(--crit);border-color:var(--crit);
    background:color-mix(in srgb,var(--crit) 40%,var(--panel));
    box-shadow:0 0 0 2px color-mix(in srgb,var(--crit) 30%,transparent)}
  .rd-row.open.lvl4:hover{background:color-mix(in srgb,var(--crit) 48%,var(--panel))}
  .rd-row.open.lvl4 .rd-head{color:var(--ink);font-weight:800}
  /* At lvl4 the card itself is saturated enough that muted greys start to disappear into it. */
  .rd-row.open.lvl4 .rd-meta,.rd-row.open.lvl4 .rd-when{color:var(--ink);opacity:.75}
  .rd-row.open.lvl4 .rd-next{color:var(--ink);font-weight:600}
  /* The stage badge goes SOLID at lvl4. A crit-on-crit-soft pill sitting on a saturated red card is
     the one element that loses legibility as the ladder gets louder, and "referred to the sheriff" is
     the last thing that should be hard to read. */
  .rd-row.open.lvl4 .pill.crit{background:var(--crit);color:#fff}
  /* The window is gone. EMPHASIS on top of whichever level applies, never a colour of its own — it
     says the four days are up, not how bad the letter is. */
  .rd-row.open.hot .rd-next{color:var(--crit);font-weight:600}
  .rd-row.open.hot .rd-when{color:var(--crit)}
  .rd-row-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
  .rd-head{font-size:12.5px;font-weight:700;color:var(--ink)}
  .rd-client{font-size:12.5px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:26ch}
  .rd-when{font-family:var(--font-num);font-size:11.5px;color:var(--muted);margin-left:auto;flex:none}
  .rd-meta{font-size:11.5px;color:var(--muted);margin-top:5px;line-height:1.5}
  .rd-next{font-size:12px;color:var(--ink);margin-top:7px;line-height:1.5}
  .rd-res{font-size:12px;color:var(--muted);margin-top:7px;padding-top:6px;border-top:1px solid var(--line-2);line-height:1.5}
  .rd-read-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:8px;font-size:13px}
  /* The stage warning. Loud, and deliberately the only red block in the reading dialog — it is the
     sentence that says what happens if nobody acts. */
  .rd-warn{border:1px solid var(--crit);background:var(--crit-soft);border-radius:9px;padding:10px 12px;
    margin:10px 0;font-size:12.5px;color:var(--ink);line-height:1.55}
  .rd-trail{margin-top:12px;border-top:1px solid var(--line-2);padding-top:10px;max-height:260px;overflow:auto}
  .rd-trail-i{padding:7px 0;border-bottom:1px solid var(--line-2);display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:11.5px}
  .rd-trail-i:last-child{border-bottom:0}
  .rd-trail-i .cr-body{flex-basis:100%;margin-top:2px}
  /* The remove control on the edit form. Set apart and tinted, not styled as a peer of Save: it is
     the one action on that dialog you cannot undo from the same dialog. */
  .cr-del-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 11px;border-radius:9px;
    border:1px solid var(--line);background:var(--panel-2);font-size:11.5px;color:var(--muted)}
  .cr-del-row .cr-del-btn{margin-left:auto;color:var(--crit);border-color:var(--crit)}

  /* ---- Global search: the top-bar box, its dropdown, and the #search results surface ------------
     (js/search.js). The dropdown is absolutely positioned out of the topbar, which is itself
     position:sticky with a backdrop-filter — so it needs its own opaque background and a z-index
     ABOVE the topbar's 20, or the blur layer paints over the results. */
  .search-wrap{position:relative;flex:1;max-width:420px;display:flex}
  .search-wrap .search{flex:1;max-width:none}
  .search-kbd{font-family:var(--font-num);font-size:10px;color:var(--faint);border:1px solid var(--line);border-radius:5px;padding:1px 5px;flex:none;letter-spacing:.02em}
  .search-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:60;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-lg);max-height:min(72vh,560px);overflow:auto;padding:5px}
  .sr-empty{padding:13px 12px;font-size:12.5px;color:var(--faint);display:flex;align-items:center;gap:9px;flex-wrap:wrap}
  .sr-foot{display:flex;align-items:center;gap:10px;padding:9px 11px 7px;margin-top:4px;border-top:1px solid var(--line-2);font-size:11.5px;color:var(--faint);cursor:pointer}
  .sr-foot:hover{background:var(--panel-2)}
  .sr-foot .sr-see{margin-left:auto;color:var(--accent-ink);font-weight:600}

  /* One row primitive for both the dropdown and the surface (.compact is the dropdown variant), so a
     result reads identically in the two places it can appear. */
  /* align-items:start, not center: the title wraps now, so a two-line row must not float its icon
     and its buttons in the middle of the gap. */
  .sr-row{display:grid;grid-template-columns:26px 1fr auto;align-items:start;gap:10px;padding:9px 10px;border-radius:var(--r-sm);cursor:pointer;outline:none}
  .sr-row:hover,.sr-row.on,.sr-row:focus-visible{background:var(--panel-2)}
  .sr-row.on{box-shadow:inset 0 0 0 1px var(--line)}
  .sr-ic{display:grid;place-items:center;color:var(--faint);padding-top:1px}
  .sr-ic .ic{width:16px;height:16px;stroke:currentColor;stroke-width:1.8;fill:none}
  .sr-body{min-width:0}
  /* The title is the answer to the search, so it is never truncated. overflow-wrap:anywhere so a long
     unbroken token (a reference, a URL) still cannot push the row wider than its column. */
  .sr-t{font-size:13px;font-weight:600;color:var(--ink);line-height:1.35;overflow-wrap:anywhere}
  .sr-s{font-size:11.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
  /* Was a right-hand column competing with the title for width; now its own line under the subtitle. */
  .sr-m{font-family:var(--font-num);font-size:10.5px;color:var(--faint);margin-top:2px;overflow-wrap:anywhere}
  /* The matched run. A background tint rather than bold, so highlighting never reflows the row. */
  .sr-t mark,.sr-s mark{background:var(--accent-soft);color:var(--accent-ink);border-radius:3px;padding:0 1px}
  .sr-right{display:flex;align-items:center;gap:9px;justify-content:flex-end;flex:none}
  .sr-kind{font-size:10.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted);background:var(--line-2);border-radius:20px;padding:2px 8px;white-space:nowrap}
  .sr-pin,.sr-new{border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:7px;flex:none}
  .sr-pin{font-size:10.5px;font-weight:700;padding:3px 8px}
  .sr-pin:hover{color:var(--accent-ink);border-color:var(--accent)}
  .sr-new{width:24px;height:24px;display:inline-grid;place-items:center}
  .sr-new:hover{color:var(--ink);border-color:var(--faint)}
  .sr-new .ic{width:13px;height:13px;stroke:currentColor;stroke-width:1.9;fill:none}
  /* The dropdown is a narrow column, so the row drops its kind chip there and keeps the glyph. */
  .sr-row.compact{padding:7px 8px;grid-template-columns:22px 1fr auto}
  .sr-row.compact .sr-t{font-size:12.5px}

  .sr-chips{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:0 0 14px}
  .sr-group{margin-bottom:14px}
  .sr-group .sr-ic{width:20px}
  .sr-list{padding:2px 6px 8px}
  .sr-more{padding:8px 15px 12px;font-size:11.5px;color:var(--faint);border-top:1px solid var(--line-2)}
  .sr-pad{padding:14px 15px}

  /* Pinned scope chip — the omni-filter's only always-visible surface. Accent-tinted because it
     changes what every other page shows: a global filter that looks like neutral chrome is a global
     filter people stop noticing. */
  .pin-chip{display:inline-flex;align-items:center;gap:7px;flex:none;background:var(--accent-soft);border:1px solid var(--accent);border-radius:20px;padding:3px 4px 3px 10px;max-width:290px}
  .pin-k{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--accent-ink)}
  .pin-l{font-size:12px;font-weight:600;color:var(--accent-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .pin-open{font-size:10.5px;font-weight:700;color:var(--accent-ink);background:transparent;border:1px solid var(--accent);border-radius:6px;padding:2px 7px;flex:none}
  .pin-open:hover{background:var(--panel)}
  .pin-x{width:20px;height:20px;display:inline-grid;place-items:center;border:0;background:transparent;color:var(--accent-ink);flex:none}
  .pin-x .ic{width:13px;height:13px;stroke:currentColor;stroke-width:2.2;fill:none}
  .pin-x:hover{background:var(--panel);border-radius:50%}
  /* A clickable document reference inside a ledger/table cell (docRefIsOpenable, js/doc-open.js) —
     drawn as a link ONLY where the document is actually held, so it can never be a dead click. */
  .doc-ref{color:var(--accent-ink);font-weight:600;cursor:pointer;background:none;border:0;padding:0;font-size:inherit;font-family:inherit}
  .doc-ref:hover{text-decoration:underline}

  /* Recent searches, shown in the dropdown when the box is empty (js/search.js). */
  .sr-recent-h{display:flex;align-items:center;gap:8px;padding:8px 10px 5px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--faint)}
  .sr-recent-clr{margin-left:auto;border:0;background:transparent;color:var(--accent-ink);font-size:10.5px;font-weight:700;text-transform:none;letter-spacing:0}
  .sr-recent-clr:hover{text-decoration:underline}
  .sr-row.sr-recent .sr-m{font-family:var(--font-ui);font-size:10.5px;color:var(--faint);opacity:0}
  .sr-row.sr-recent:hover .sr-m{opacity:1}

  /* DataTable quick search — the forgiving, ranked box that leads a register's toolbar
     (cfg.search, js/datatable.js). Mirrors the top-bar .search so the two read as the same control
     doing the same job at two scopes. */
  .dt-search{display:flex;align-items:center;gap:7px;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:5px 9px;color:var(--faint);min-width:190px;max-width:290px;flex:0 1 250px}
  .dt-search.on{border-color:var(--accent);background:var(--accent-soft)}
  .dt-search .ic{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;flex:none}
  .dt-search input{border:0;background:transparent;color:var(--ink);flex:1;outline:none;font-size:12px;min-width:0}
  .dt-search.on input{color:var(--accent-ink);font-weight:600}
  .dt-search-x{width:18px;height:18px;display:inline-grid;place-items:center;border:0;background:transparent;color:var(--accent-ink);flex:none}
  .dt-search-x .ic{width:12px;height:12px;stroke-width:2.4}
  .dt-search-x:hover{background:var(--panel);border-radius:50%}

  @media (max-width:900px){.search-kbd{display:none}.pin-chip{max-width:170px}}

  @media (max-width:1080px){.split{grid-template-columns:1fr}.tiles,.pulse-grid.three,.pulse-grid.four,.pulse-grid.five{grid-template-columns:repeat(2,1fr)}.pulse-grid,.cov-grid{grid-template-columns:1fr}}
/* ---- Permissions matrix (Admin → People → Permissions, js/permissions-matrix.js) ----
   Deliberately small and quiet: fourteen columns of switches becomes noise the moment each one is
   the size of a normal control, so a cell is a 20px square that reads as ticked/blank at a glance
   and carries its explanation on hover. `firmoff` greys a whole column when the firm-wide switch
   above is off — the permission is still really held, it just cannot be used, and showing it as
   simply "off" would be a lie the person's drawer would then contradict. */
.pm-cell{width:20px;height:20px;border-radius:5px;border:1px solid var(--line-2);background:var(--bg-2);
  display:inline-flex;align-items:center;justify-content:center;font-size:11px;line-height:1;
  color:transparent;cursor:pointer;padding:0;transition:background .12s,border-color .12s}
.pm-cell:hover{border-color:var(--accent)}
.pm-cell.on{background:var(--good-soft);border-color:var(--good);color:var(--good);font-weight:700}
.pm-cell.locked{cursor:default;opacity:.55}
.pm-cell.locked:hover{border-color:var(--line-2)}
.pm-cell.firmoff{opacity:.4;filter:grayscale(1)}
.pm-na{color:var(--line-2);font-weight:700;cursor:default}

/* The firm-wide switch strip — one chip per capability, same order as the columns below it. */
.pm-firm{display:flex;flex-wrap:wrap;gap:6px;padding:10px 15px 14px}
.pm-chip{display:inline-flex;align-items:center;gap:6px;padding:4px 9px;border-radius:999px;
  border:1px solid var(--line-2);background:var(--bg-2);color:var(--muted);font-size:11.5px;
  cursor:pointer;transition:background .12s,border-color .12s,color .12s}
.pm-chip:hover{border-color:var(--accent)}
.pm-chip .pm-dot{width:7px;height:7px;border-radius:50%;background:var(--line-2);flex:none}
.pm-chip.on{background:var(--good-soft);border-color:var(--good);color:var(--good);font-weight:600}
.pm-chip.on .pm-dot{background:var(--good)}

/* ---- Notification policy matrix (Admin → Notifications, js/notifications-matrix.js) ----
   Reuses .pm-cell for the channel switches so a switch means the same thing on both admin grids.
   The audience chips are their own thing: they carry a word, not a tick, because "who" is only
   readable as a name. Off chips stay visible rather than being hidden — the question this screen
   answers is as often "who is NOT getting this" as "who is". */
.nm-aud{display:flex;flex-wrap:wrap;gap:4px;max-width:330px}
.nm-chip{font-size:10.5px;line-height:1.5;padding:1px 7px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line-2);background:transparent;color:var(--faint);transition:background .12s,border-color .12s,color .12s}
.nm-chip:hover{border-color:var(--accent);color:var(--muted)}
.nm-chip.on{background:var(--good-soft);border-color:var(--good);color:var(--good);font-weight:600}
/* A firm-wide audience is a standing group, not a relationship — different shape so an accidental
   "every partner, every admin" on a busy event is visible at a glance. */
.nm-chip.firm{border-style:dashed}
.nm-chip.firm.on{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
.nm-chip.locked{cursor:default;opacity:.6}
.nm-chip.locked:hover{border-color:var(--line-2);color:var(--faint)}

/* ---- Reports → Rates & realization: the blended-rate timeline (js/rate-timeline.js) ------------
   The chart is inline SVG with no library, so every colour has to come from the tokens the rest of
   the app uses — hard-coded hex here would be the one thing on the page that ignores dark mode. */
.rt-card .card-h{align-items:center}
.rt-legend{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--muted)}
.rt-k{width:14px;height:3px;border-radius:2px;display:inline-block}
.rt-k-bar{height:10px;width:8px;background:color-mix(in srgb,var(--accent) 30%,var(--panel));border:1px solid var(--line)}
.rt-k-line{background:var(--accent)}
.rt-k+.rt-k{margin-left:10px}
.rt-head{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px 16px;padding:14px 16px 4px}
.rt-big{display:flex;align-items:baseline;gap:6px}
.rt-v{font:600 30px/1 var(--font-num);color:var(--ink);letter-spacing:-.5px}
.rt-u{font-size:12.5px;color:var(--muted)}
.rt-delta{font:600 13px/1 var(--font-ui);padding:3px 8px;border-radius:99px}
.rt-delta.down{color:var(--crit);background:var(--crit-soft)}
.rt-delta.up{color:var(--good);background:var(--good-soft)}
.rt-sub{flex:1 1 100%;font-size:12px;color:var(--muted)}
.rt-chartwrap{padding:4px 12px 10px}
.rt-svg{width:100%;height:auto;display:block}
.rt-grid{stroke:var(--line-2);stroke-width:1}
.rt-yr{stroke:var(--line);stroke-width:1;stroke-dasharray:3 3}
.rt-yrlab{font:600 10px var(--font-ui);fill:var(--faint)}
.rt-axis{font:11px var(--font-num);fill:var(--faint)}
.rt-axis2{fill:var(--bb)}
/* --accent-soft is tuned for panel fills, and against the dark panel it lands at about 1.1:1 —
   invisible for a bar that carries a number. Tinting the accent itself keeps the bars legible in
   both themes without a second hard-coded colour. */
.rt-bar{fill:var(--accent);fill-opacity:.3}
.rt-line{fill:none;stroke:var(--accent);stroke-width:2.5;stroke-linejoin:round}
.rt-dot{fill:var(--panel);stroke:var(--accent);stroke-width:2}
.rt-note{padding:2px 16px 14px;font-size:11.5px;color:var(--faint);line-height:1.5}
.rt-empty{padding:18px;color:var(--muted);font-size:13px}
.rt-chip{display:inline-block;margin:1px 4px 1px 0;padding:1px 7px;border-radius:99px;
  background:var(--panel-2);border:1px solid var(--line);font-size:11px;color:var(--muted);white-space:nowrap}
.rt-chip b{color:var(--ink);font-weight:600}
.rt-tbl .rt-dn{color:var(--crit)}
.rt-tbl .rt-up{color:var(--good)}

/* ---- Boot screen (js/boot-screen.js) ----
   Five marks, one per collection group, settling green as each group lands; the orbit is the gate.
   Every colour comes from the app's own tokens so this reads as Insight rather than as a widget. */
.bsc{position:fixed; inset:0; z-index:9000; background:var(--ground); display:grid; place-items:center;
  opacity:0; transition:opacity .3s ease; padding:24px}
.bsc.in{opacity:1}
/* The first-paint variant (the copy in index.html). It has to become visible with no JS at all,
   since the point is to cover the window where the scripts are still downloading — but it keeps the
   same 400ms-ish hold-back in pure CSS, so a warm boot removes it before it was ever seen. Ends at
   the same opacity as .in, so js/boot-screen.js adopting the node is not a visible handover. */
.bsc.bscpre{animation:bscPreIn .3s ease .45s forwards}
@keyframes bscPreIn{to{opacity:1}}
.bsc-panel{display:flex; flex-direction:column; align-items:center; gap:24px; text-align:center; max-width:560px; width:100%}
.bsc-brand{display:flex; align-items:center; gap:10px; font-weight:700; font-size:18px; letter-spacing:-.015em; color:var(--ink)}
.bsc-m{width:26px; height:26px; border-radius:8px; background:var(--accent); color:#fff; display:grid; place-items:center; font-size:14px; font-weight:800}
.bsc-cap{margin:-14px 0 0; font-size:13px; color:var(--muted); min-height:20px; transition:color .3s}
.bsc.done .bsc-cap{color:var(--good); font-weight:600}
.bsc-marks{display:grid; grid-template-columns:repeat(5,1fr); gap:10px; width:100%}
.bsc-cell{background:var(--panel); border:1px solid var(--line); border-radius:var(--r-sm);
  padding:15px 8px 12px; display:flex; flex-direction:column; align-items:center; gap:11px;
  transition:border-color .35s ease, background .35s ease}
.bsc-cell.done{border-color:transparent; background:var(--good-soft)}
.bsc-stage{height:70px; display:grid; place-items:center}
.bsc-lab{font-size:10.5px; font-weight:650; line-height:1.3; color:var(--muted)}
.bsc-cell.done .bsc-lab{color:var(--good)}
/* dots — clients & contacts. Took over from the counting number, so it has to carry the card on its
   own and must not read as another bar chart. */
.bsc-dots{display:grid; grid-template-columns:repeat(3,12px); gap:8px; justify-content:center}
.bsc-dots i{width:12px; height:12px; border-radius:50%; background:var(--line);
  transition:background .3s ease}
.bsc-dots i.on{background:var(--accent)}
.bsc-cell.done .bsc-dots i{background:var(--good)}
/* ring — jobs */
.bsc-ring{position:relative; width:66px; height:66px}
.bsc-ring i{position:absolute; left:50%; top:50%; width:2.5px; height:9px; margin:-33px 0 0 -1.25px;
  border-radius:2px; background:var(--line); transform-origin:50% 33px; transition:background .2s ease}
.bsc-ring i.on{background:var(--accent)}
.bsc-cell.done .bsc-ring i{background:var(--good)}
/* ledger — billing. Widths taper (set inline) so it reads as figures, not five loading bars. */
.bsc-ledger{width:78px; display:flex; flex-direction:column; gap:7px; align-items:flex-start}
.bsc-ledger span{height:5px; border-radius:3px; background:var(--line); position:relative; overflow:hidden}
.bsc-ledger span::after{content:""; position:absolute; inset:0; background:var(--accent);
  transform:scaleX(0); transform-origin:left; transition:transform .45s cubic-bezier(.4,0,.2,1)}
.bsc-ledger span.on::after{transform:scaleX(1)}
.bsc-cell.done .bsc-ledger span::after{background:var(--good); transform:scaleX(1)}
/* pulse — time & people */
.bsc-pulse{width:42px; height:42px; border-radius:13px; background:var(--accent); color:#fff;
  display:grid; place-items:center; font-size:21px; font-weight:800;
  animation:bscBreathe 2s cubic-bezier(.4,0,.2,1) infinite; transition:background .35s}
.bsc-cell.done .bsc-pulse{animation:none; background:var(--good)}
@keyframes bscBreathe{0%,100%{transform:scale(1)} 50%{transform:scale(.9)}}
/* cols — deadlines. Keeps a varied height when settled so it still reads as a chart. */
.bsc-cols{display:flex; align-items:flex-end; gap:5px; height:62px}
.bsc-cols i{width:8px; border-radius:2px; background:var(--line); height:10px;
  transition:height .4s cubic-bezier(.4,0,.2,1), background .3s}
.bsc-cols i.on{background:var(--accent)}
.bsc-cell.done .bsc-cols i{background:var(--good)}
/* ---- idle motion: every mark is alive from the first frame ----
   The marks used to sit still until their own collections landed, and during the phases before that
   they were dimmed too. On a real 30-40s boot that is five grey, motionless cards under a spinner —
   which reads as "stuck", the exact conclusion this screen exists to prevent.

   So each mark now runs its own loop in accent blue from the moment the screen appears. This is NOT a
   progress report and must not be read as one: it promises only "these are the things being fetched".
   The honest signals are untouched and still have to be earned — a card turns GREEN only when its
   collections have genuinely arrived, the caption still names the group actually holding boot up, and
   a failure still replaces the whole screen with the error. Optimistic motion, truthful completion.

   Nothing here animates `transform` on .bsc-ring i: that carries an inline rotate() placing the tick
   on the dial, and a keyframe touching transform would fling them all to twelve o'clock. */

/* One stagger scale, shared by every mark, so they ripple instead of blinking in unison. Goes up to
   8 because the jobs group has eight collections — the widest mark on screen. */
.bsc-stage i:nth-child(1),.bsc-stage span:nth-child(1){animation-delay:0s}
.bsc-stage i:nth-child(2),.bsc-stage span:nth-child(2){animation-delay:.11s}
.bsc-stage i:nth-child(3),.bsc-stage span:nth-child(3){animation-delay:.22s}
.bsc-stage i:nth-child(4),.bsc-stage span:nth-child(4){animation-delay:.33s}
.bsc-stage i:nth-child(5),.bsc-stage span:nth-child(5){animation-delay:.44s}
.bsc-stage i:nth-child(6){animation-delay:.55s}
.bsc-stage i:nth-child(7){animation-delay:.66s}
.bsc-stage i:nth-child(8){animation-delay:.77s}

.bsc-dots i{animation:bscDot 1.5s ease-in-out infinite}
@keyframes bscDot{0%,100%{background:var(--line); transform:scale(.82)}
  45%{background:var(--accent); transform:scale(1.06)}}

.bsc-ring i{animation:bscTick 1.3s ease-in-out infinite}
@keyframes bscTick{0%,100%{background:var(--line)} 40%{background:var(--accent)}}

.bsc-cols i{animation:bscEq 1.6s ease-in-out infinite}
@keyframes bscEq{0%,100%{height:12px; background:var(--line)}
  50%{height:52px; background:var(--accent)}}

.bsc-ledger span::after{animation:bscSweep 1.9s ease-in-out infinite}
.bsc-ledger span:nth-child(1)::after{animation-delay:0s}
.bsc-ledger span:nth-child(2)::after{animation-delay:.16s}
.bsc-ledger span:nth-child(3)::after{animation-delay:.32s}
.bsc-ledger span:nth-child(4)::after{animation-delay:.48s}
@keyframes bscSweep{0%,100%{transform:scaleX(0)} 55%{transform:scaleX(1)}}

/* Landed. The motion stops and the settled green state underneath shows through — this is the moment
   that actually means something, so it must look different in kind from the waiting state. */
.bsc-cell.done .bsc-dots i,
.bsc-cell.done .bsc-ring i,
.bsc-cell.done .bsc-cols i,
.bsc-cell.done .bsc-ledger span::after{animation:none}

/* orbit — the gate. Collapses its dots to a point when everything is in. */
.bsc-gate{display:flex; flex-direction:column; align-items:center; gap:12px}
.bsc-orbit{position:relative; width:40px; height:40px; animation:bscRot 1.5s linear infinite}
.bsc-orbit i{position:absolute; width:7px; height:7px; border-radius:50%; background:var(--accent); transition:all .5s cubic-bezier(.4,0,.2,1)}
.bsc-orbit i:nth-child(1){top:0; left:50%; margin-left:-3.5px; opacity:1}
.bsc-orbit i:nth-child(2){top:50%; right:0; margin-top:-3.5px; opacity:.6}
.bsc-orbit i:nth-child(3){bottom:0; left:50%; margin-left:-3.5px; opacity:.32}
.bsc-orbit i:nth-child(4){top:50%; left:0; margin-top:-3.5px; opacity:.15}
@keyframes bscRot{to{transform:rotate(360deg)}}
.bsc.done .bsc-orbit{animation:none}
.bsc.done .bsc-orbit i{background:var(--good); opacity:1}
.bsc.done .bsc-orbit i:nth-child(1){top:50%; margin-top:-3.5px}
.bsc.done .bsc-orbit i:nth-child(2){right:50%; margin-right:-3.5px}
.bsc.done .bsc-orbit i:nth-child(3){bottom:50%; margin-bottom:-3.5px}
.bsc.done .bsc-orbit i:nth-child(4){left:50%; margin-left:-3.5px}
.bsc-gatelab{font-size:11.5px; color:var(--faint); font-family:var(--font-num); font-variant-numeric:tabular-nums}
.bsc.done .bsc-gatelab{color:var(--good)}
/* ---- something to read while you wait (js/boot-interlude.js) ----
   A 30-40 second wait you only watch feels far longer than one you spent reading. Sits below the
   spinner, deliberately quiet: it is the thing you notice second, never competing with the caption
   that tells you what is actually happening. */
/* The offer itself. Reads as an invitation, not a control — it sits with "Continue anyway" as the
   other answer to a long wait, so it must not look like the primary action. */
/* The ready confirmation, shown only when somebody chose to read something and boot then finished.
   It is a report, not an interruption: the screen says it is done and waits to be dismissed, because
   pulling them into the app mid-sentence is exactly what they opted out of. */
.bsc-ready{display:flex; flex-direction:column; align-items:center; gap:9px}
.bsc-ready-note{margin:0; font-size:11.5px; color:var(--good); font-weight:600}
.bsc-ready-go{font:inherit; font-size:12.5px; font-weight:650; padding:7px 20px; border-radius:99px;
  border:0; background:var(--good); color:#fff; cursor:pointer; box-shadow:var(--shadow);
  transition:transform .15s ease, filter .15s ease}
.bsc-ready-go:hover{filter:brightness(1.06)}
.bsc-ready-go:active{transform:translateY(1px)}

.bsc-offer{font:inherit; font-size:12px; font-weight:600; padding:5px 15px; border-radius:99px;
  border:1px dashed var(--line); background:transparent; color:var(--muted); cursor:pointer;
  transition:border-color .2s, color .2s, background .2s}
.bsc-offer:hover:enabled{border-style:solid; border-color:var(--accent); color:var(--accent-ink); background:var(--panel)}
.bsc-offer:disabled{cursor:default; opacity:.6; border-style:solid}
/* Said out loud when the model surfaced the quotation rather than a person checking it. */
/* The "this came from a model" note, on a generated quote or a generated true/false statement. */
.bsc-ai-flag{font-size:10px; color:var(--faint); font-style:normal; letter-spacing:.01em}

.bsc-interlude{max-width:430px; min-height:74px; display:flex; align-items:flex-start; justify-content:center}
.bsc-quote{margin:0; display:flex; flex-direction:column; gap:7px}
.bsc-quote blockquote{margin:0; font-size:13px; line-height:1.6; color:var(--muted); font-style:italic}
.bsc-quote figcaption{font-size:11px; color:var(--faint); line-height:1.45}
.bsc-quote figcaption a{color:var(--accent-ink); text-decoration:underline; text-underline-offset:2px}
.bsc-quote figcaption a:hover{color:var(--accent)}
.bsc-quiz{display:flex; flex-direction:column; align-items:center; gap:8px}
.bsc-quiz-tag{font-size:9.5px; font-weight:750; letter-spacing:.09em; text-transform:uppercase; color:var(--faint)}
.bsc-quiz-q{margin:0; font-size:13px; line-height:1.55; color:var(--muted)}
.bsc-quiz-row{display:flex; gap:7px}
.bsc-quiz-btn{font:inherit; font-size:11.5px; font-weight:650; padding:4px 16px; border-radius:99px;
  border:1px solid var(--line); background:var(--panel); color:var(--muted); cursor:pointer;
  transition:border-color .2s, color .2s, background .2s}
.bsc-quiz-btn:hover:enabled{border-color:var(--accent); color:var(--accent-ink)}
.bsc-quiz-btn:disabled{cursor:default; opacity:.55}
.bsc-quiz-btn.right{border-color:transparent; background:var(--good-soft); color:var(--good); opacity:1}
.bsc-quiz-btn.wrong{border-color:transparent; background:var(--warn-soft); color:var(--warn); opacity:1}
.bsc-quiz-a{margin:0; font-size:11.5px; line-height:1.5; color:var(--faint); opacity:0;
  transition:opacity .3s ease}
.bsc-quiz-a.in{opacity:1}
/* Nothing dims when boot finishes. That rule made sense when the screen left immediately; now it can
   be HELD open for somebody still reading, and fading the thing they asked for — to .35, mid-sentence
   — makes it unreadable at exactly the moment they are reading it. */

.bsc-skip{font:inherit; font-size:12px; color:var(--muted); background:none; border:0;
  text-decoration:underline; cursor:pointer; padding:4px}
@media (prefers-reduced-motion: reduce){
  .bsc-pulse,.bsc-orbit,.bsc-dots i,.bsc-ring i,.bsc-cols i,.bsc-ledger span::after{animation:none}
}
@media (max-width:640px){ .bsc-marks{grid-template-columns:repeat(2,1fr)} }

/* ============================ DEMO INBOX + GUIDED TOUR ============================
   js/mail-demo.js and js/tour.js. The demo bar sits directly under the Triage page head and must
   read as a mode, not a notice — hence the solid accent pill and the full-width tint, rather than
   the quieter .note treatment an informational strip would get. */
.demo-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 14px;padding:9px 12px;
  background:var(--accent-soft);border:1px solid var(--accent);border-radius:var(--r-sm)}
.demo-pill{flex:none;background:var(--accent);color:#fff;border-radius:999px;padding:2px 9px;
  font-size:10.5px;font-weight:700;letter-spacing:.08em}
.demo-say{flex:1;min-width:220px;font-size:12.5px;color:var(--ink)}
.demo-score{font-family:var(--font-num);font-variant-numeric:tabular-nums;font-size:12px;
  color:var(--accent-ink);font-weight:600;white-space:nowrap}
.demo-stamp{font-size:11px;color:var(--muted);white-space:nowrap}
.demo-bar .btn{flex:none}

/* The spotlight is ONE element: a transparent box over the target with a 9999px shadow doing the
   dimming. pointer-events stays off it so the page underneath is still genuinely clickable — a step
   can ask the person to click the thing being pointed at, which is what makes this teach rather
   than narrate. Only the card itself takes clicks. */
.tour-root{position:fixed;inset:0;pointer-events:none}
.tour-spot{position:fixed;border-radius:10px;pointer-events:none;
  box-shadow:0 0 0 9999px rgba(10,14,22,.58),0 0 0 2px var(--accent) inset;
  outline:2px solid var(--accent);outline-offset:0;transition:left .18s ease,top .18s ease,width .18s ease,height .18s ease}
.tour-card{position:fixed;width:min(360px,92vw);pointer-events:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-lg);padding:14px 15px 12px}
.tour-h{display:flex;align-items:center;gap:8px;margin-bottom:4px}
.tour-count{font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.tour-x{margin-left:auto;border:0;background:none;color:var(--faint);font-size:14px;line-height:1;padding:2px 4px;border-radius:5px}
.tour-x:hover{color:var(--ink);background:var(--panel-2)}
.tour-card h4{margin:0 0 6px;font-size:15px;font-weight:650;color:var(--ink)}
.tour-body{font-size:13px;color:var(--muted);line-height:1.5}
.tour-body p{margin:0 0 7px}
.tour-body p:last-child{margin-bottom:0}
.tour-body b{color:var(--ink);font-weight:600}
.tour-foot{display:flex;align-items:center;gap:8px;margin-top:12px}
.tour-foot .tour-next{margin-left:auto}
.tour-hint{margin-left:auto;font-size:12px;color:var(--accent-ink);font-weight:600}
@media (prefers-reduced-motion: reduce){ .tour-spot{transition:none} }

/* The mode marker that follows you off Triage. Bottom-LEFT so it never sits under invToast
   (bottom-right, z 200), and below that z so a save message still wins the corner it owns. */
.demo-mark{position:fixed;left:14px;bottom:14px;z-index:190;border:1px solid var(--accent);
  background:var(--accent);color:#fff;border-radius:999px;padding:5px 12px;font:inherit;
  font-size:10.5px;font-weight:700;letter-spacing:.08em;box-shadow:var(--shadow);opacity:.92}
.demo-mark:hover{opacity:1}

/* ---- File tracker (process-guide bridge) ---------------------------------------------------
   A ratio, never a stored percentage: process-guide's templates are editable, so the denominator
   moves. The bar is derived at render time from done/total and the number is always shown beside it,
   because the bar alone would imply a precision the underlying data does not have. */
.ft-cards{display:flex;gap:10px;flex-wrap:wrap;margin:0 0 14px}
.ft-card{min-width:132px;padding:10px 13px;border:1px solid var(--line-2);border-radius:9px;background:var(--panel-2)}
.ft-card.ok{border-color:color-mix(in srgb,var(--ok) 40%,var(--line-2))}
.ft-card.bad{border-color:color-mix(in srgb,var(--bad) 45%,var(--line-2))}
.ft-card.warn{border-color:color-mix(in srgb,var(--warn) 45%,var(--line-2))}
.ft-c-n{font:600 21px/1.1 var(--font-num);color:var(--ink)}
.ft-c-l{margin-top:3px;font-size:11.5px;color:var(--muted)}
.ft-head,.ft-row{display:grid;grid-template-columns:minmax(0,2.2fr) 96px 190px minmax(0,1.3fr) auto;gap:12px;align-items:center}
.ft-head{padding:0 12px 6px;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--faint)}
.ft-list{border:1px solid var(--line-2);border-radius:9px;overflow:hidden}
.ft-row{padding:9px 12px;border-top:1px solid var(--line-2);background:var(--panel)}
.ft-list .ft-row:first-child{border-top:0}
.ft-cl .nm{font-weight:600;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ft-cl .sub{font-size:11.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ft-due{font-family:var(--font-num);font-size:12px;color:var(--muted);white-space:nowrap}
.ft-prog{display:flex;align-items:center;gap:8px}
.ft-bar{flex:1;height:7px;border-radius:5px;background:var(--line-2);overflow:hidden}
.ft-fill{display:block;height:100%}
.ft-fill.ok{background:var(--ok)}
.ft-fill.warn{background:var(--warn)}
.ft-fill.bad{background:var(--bad)}
.ft-num{font-family:var(--font-num);font-size:11.5px;color:var(--muted);white-space:nowrap}
.ft-flags{display:flex;gap:5px;flex-wrap:wrap}
.ft-flag{font-size:10.5px;padding:1px 7px;border-radius:999px;border:1px solid var(--line-2);color:var(--muted);white-space:nowrap}
.ft-flag.bad{border-color:color-mix(in srgb,var(--bad) 50%,var(--line-2));color:var(--bad)}
.ft-flag.warn{border-color:color-mix(in srgb,var(--warn) 50%,var(--line-2));color:var(--warn)}
.ft-acts{display:flex;gap:6px;justify-content:flex-end}
.ft-age{margin-top:10px;font-size:11.5px;color:var(--faint);display:flex;align-items:center;gap:9px}
/* Pulling the filing/payment dates across (js/file-tracker.js ftFilingPullHtml). A panel rather than
   a button in the header: it carries a dry run, its own result and up to forty rows of it. */
.ft-pull{margin-top:16px;border:1px solid var(--line);border-radius:11px;padding:12px 14px;background:var(--panel)}
.ft-pull-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:13px}
.ft-pull-h .btn{margin-left:0}
.ft-pull-h b{margin-right:auto}
.ft-pull-note{font-size:11.5px;color:var(--faint);margin-top:7px;line-height:1.5}
.ft-pull-rows{margin-top:9px;max-height:280px;overflow:auto}
.ft-pull-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;padding:6px 2px;border-top:1px solid var(--line-2);font-size:11.5px}
.ft-pull-row b{min-width:14ch}
@media (max-width:900px){
  .ft-head{display:none}
  .ft-row{grid-template-columns:1fr;gap:6px}
  .ft-acts{justify-content:flex-start}
}


/* Deployment badge — see js/env-badge.js. Warn tokens, not critical: this is "know where you are",
   not "something is broken". Both tokens are theme-aware, so it stays legible in dark mode. */
.env-badge{display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 9px;border-radius:6px;
  background:var(--warn-soft);color:var(--warn);border:1px solid var(--warn);
  font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;cursor:default}
.env-badge::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--warn);flex:0 0 auto}


/* The message bar stays put. It is sticky at the very top with the top bar sticking BELOW it
   (--banner-h, measured and published by bannerSyncOffset in js/surfaces.js), because the banner
   carries standing instructions -- "you are on the experimental backend" -- and an instruction that
   scrolls away is one that only applies to people who have not scrolled. The height has to be
   measured rather than hard-coded: the text wraps to two or three lines on a narrow window. */
.banner{white-space:normal}
