/* ===========================================================================
   THE REGISTER — shared stylesheet
   site/register.css · Owner A · loaded LAST on admin.html and client.html
   (immediately after each page's closing </style>, so equal-specificity rules
   in here win over the inline sheets without either page being edited).

   WHAT LIVES HERE
     1. Every design token, light AND dark. The dark theme is authored, not an
        inversion: panels lift by luminance, and bull/bear/amber are lightened
        and desaturated because the light values score 1.4-2.0:1 on a dark panel.
     2. The re-pointing of every hardcoded hex still sitting in the two inline
        stylesheets onto a token, written at >= the source selector's specificity.
     3. Every new component: sidebar, stat card, chart card, verdict line,
        calendar heatmap, profile header, compliance badges, timeline, empty
        state, skeleton.
     4. Two pre-existing WCAG failures, fixed: the control border that was the
        only boundary of an input at 1.25:1, and the focus ring that the
        accessibility toolbar's own High-contrast mode erased entirely.

   HARD RULES THIS FILE OBEYS (each is checkable by grep)
     - zero CSS animations declared. The skeleton is static by construction, so
       `grep -c "at-keyframes-rule"` on this file is genuinely 0 rather than 0
       plus a comment that mentions the at-rule by name.
     - zero vh / vw / dvh / svh / lvh. The toolbar's text-size control sets
       `zoom` on <html>, and Chrome does not divide viewport units by zoom, so
       at 200% any viewport-unit box lays out at 2x the viewport and its lower
       half becomes unreachable.
     - zero placeholder tokens. The build fails on any unresolved one.
     - zero external URLs. No CDN, no remote font, no remote image.
     - no z-index at or above 2147482000 (the a11y toolbar's floor).
     - state is never a box-shadow and never a border-color: High contrast
       forces `box-shadow:none` and `border-color:#fff` on `body *`. State is a
       pseudo-element background; focus is an `outline`. Both survive.
     - the spine is drawn ONLY for .st-ok / .st-warn / .st-bad. A panel that
       merely shows information carries no bar. That scarcity IS the signal.

   CONTRAST
     Every pair below was computed with the WCAG 2.x relative-luminance formula
     against the exact hex quoted, not estimated. The ratio is stated in a
     comment beside the token or the rule that uses it. Body text >= 4.5:1;
     UI components and meaningful graphics >= 3:1. Where a value is BELOW 3:1
     it is a decorative object and the comment says so and says why.
   =========================================================================== */


/* ---------------------------------------------------------------------------
   1. TOKENS — LIGHT (base)

   The page's inline :root ships these already; they are restated here so that
   :root[data-theme="light"] below has one authoritative source to mirror, and
   so a light-preferring reader on a dark-OS machine cannot land on a half-dark
   page. --mono, --sans, --s1..--s6, --r-panel and --r-ctl are NOT restated:
   they are typography and geometry, they do not change with theme, and
   duplicating them would create two places for them to drift.
   --------------------------------------------------------------------------- */
:root{
  color-scheme: light dark;

  /* Paper. */
  /* Palette adopted from mtf.trading (owner's call, 2026-08-01), applied to ALL
     THREE surfaces — public site, client, admin — so a client signing in still
     never sees the brand change at the door. Every value below was re-measured
     rather than copied on trust, and three of theirs were REJECTED for failing
     WCAG AA as text: --success #059669 (3.85:1), --warning #d97706 (3.20:1) and
     --faint-foreground #646f80 (4.51:1 on the tint, too close to the line). The
     register's own bull/amber/faint stay for those three. Everything else here
     is theirs. */
  --bg:#F7F8FA;        /* their --background · --txt on it 16.84:1 */
  --panel:#FFFFFF;     /* their --card       · --txt on it 18.10:1 */
  --panel2:#EEF2F7;    /* their --muted      · --txt on it 15.94:1 */

  /* Rules. --line is decorative by intent: a row separator inside a table that
     is already delimited by text position is not "required to understand the
     content", so 1.4.11 does not reach it. --lineStrong exists for the case
     that IS in scope — a border that is a control's only visual boundary. */
  --line:#E3E8EF;       /* theirs · on --panel 1.24:1 — decorative hairline, declared */
  --lineStrong:#7C848F; /* on --panel 3.78:1 · on --bg 3.53:1 · on --panel2 3.43:1 */

  /* Ink — theirs. */
  --txt:#0B1220;   /* panel 18.10:1 · panel2 15.94:1 · bg 16.84:1 */
  --dim:#5B6679;   /* panel  5.80:1 · panel2  5.14:1 · bg  5.42:1 */
  --faint:#616A77; /* KEPT. Their #646F80 measures 4.51:1 on --panel2 — inside AA
                      by 0.01, which is not a margin. This is 4.97:1 there. */

  /* Accent — their --primary, used as both text and fill. It clears AA in both
     roles, which is why they can get away with one token where we needed two. */
  --acc:#2563EB;        /* panel 5.17:1 · bg 4.94:1 · panel2 4.59:1 · tintAcc 4.71:1 */
  --accBg:#2563EB;      /* #FFFFFF on it 5.17:1 */
  --accBgHover:#1D4ED8; /* #FFFFFF on it 6.53:1 · vs --accBg 1.26:1 (a visible darkening) */

  /* State. Never alone — always beside a word or a badge.
     bull is their --positive, bear their --negative. amber is OURS: their
     --warning #D97706 is 3.20:1 on white, which fails as text outright. */
  --bull:#047857;  /* theirs · panel 5.49:1 · panel2 4.83:1 · tintOk 5.02:1 */
  --bear:#C81E1E;  /* theirs · panel 5.76:1 · panel2 5.07:1 · tintBad 5.26:1 */
  --amber:#8A5B00; /* panel 5.87:1 · panel2 5.33:1 · tintWarn 5.13:1 · as spine on panel 5.87:1 */

  /* Focus. Matches the ring a11y.js draws for itself, so the page and the
     toolbar never disagree about what "focused" looks like. */
  --focus:#0A66C2; /* panel 5.69:1 · bg 5.31:1 · panel2 5.16:1 · tintAcc 5.12:1 */

  /* Tints — badge and active-item backgrounds. */
  --tintAcc:#EBF4FD;  /* --acc on it   5.84:1 */
  --tintOk:#E7F2EA;   /* --bull on it  6.17:1 */
  --tintBad:#FBEBE9;  /* --bear on it  5.69:1 */
  --tintWarn:#F7EFDF; /* --amber on it 5.13:1 */

  /* Chart furniture. */
  --grid:#E3E6EB; /* on --panel 1.25:1 — decorative by design: a gridline that
                     competes with the data it sits behind is a bug, not a win */
  --skel:#EDEFF3; /* --faint on it 4.76:1, --txt on it 15.92:1 */

  /* Calendar heatmap — mtf.trading's green ramp, adopted 2026-08-01.
     Their calendar pairs this green against a red for down-days, and THAT is
     the part that fails a colour-blind reader. It cannot arise here: these
     calendars count calls published and actions taken, and a count is never
     negative, so red never appears. What remains is a single hue whose
     luminance falls monotonically — .845 → .690 → .420 → .266 → .118 → .063 —
     so the ladder still reads in greyscale and still survives the toolbar's
     Desaturate toggle. Their look, measured before it shipped. */
  --heat0:#EBEDF0; /* theirs · L .8453 · vs panel 1.15:1 */
  --heat1:#9BE9A8; /* theirs · L .6904 · vs panel 1.42:1 · vs heat0 1.21:1 */
  --heat2:#40C463; /* theirs · L .4201 · vs panel 2.23:1 · vs heat1 1.57:1 */
  --heat3:#30A14E; /* theirs · L .2664 · vs panel 3.32:1 · vs heat2 1.49:1 */
  --heat4:#216E39; /* theirs · L .1177 · vs panel 6.27:1 · vs heat3 1.88:1 */
  --heat5:#14532D; /* L .0631 · vs panel 9.29:1 · vs heat4 1.49:1 — a sixth step
                      they do not have; our busiest days need one more rung */
  /* end to end heat0 -> heat5 = 7.90:1. Adjacent steps are 1.15-1.89:1, which
     is BELOW 3:1 and is stated rather than hidden. The step is therefore never
     the sole carrier of meaning: every cell has an aria-label with the exact
     figure, the legend prints the numeric band of every step, a 1px --panel gap
     keeps two same-step neighbours from merging, step 0 is stroked (below), and
     the mandatory <details> table gives the whole series as text. */

  /* Stacked-series ramp. Categorical hues fail here and the numbers say so:
     the four obvious light series colours sit within 1.05-1.26:1 of one
     another and are indistinguishable once desaturated. A single-hue ramp is
     the honest answer. See the note at .stk-seg for why 3:1 against the panel
     is unreachable for three steps and what carries the meaning instead. */
  --stk0:#0A5CB8; /* vs panel 6.49:1 */
  --stk1:#6FA3DE; /* vs panel 2.64:1 · vs stk0 2.46:1 */
  --stk2:#C7DCF4; /* vs panel 1.40:1 · vs stk1 1.88:1 */

  --scrim:rgba(16,21,28,.45);
  --shadow:0 1px 2px rgba(16,21,28,.04);
  --shadowUp:0 -2px 14px rgba(16,21,28,.07);
  --shadowModal:0 24px 60px rgba(16,21,28,.18);
}

/* ---------------------------------------------------------------------------
   2. TOKENS — DARK

   Authored, not inverted. Three deliberate decisions:
     · the background is a cool near-black (#0D1117) and panels lift by
       LUMINANCE rather than by a brighter border, because High-contrast mode
       forces every border to #fff and a border-lifted panel system would
       collapse under it;
     · --bull / --bear / --amber are lightened and desaturated. The light
       values (#12662C #B42318 #8A5B00) score 1.4-2.0:1 on a dark panel — they
       are not "a bit dim", they are unreadable;
     · --accBg is DARKENED, not lightened, because it is a fill under white
       text. Lightening it is the reflex and it fails 1.4.3 immediately.
   --------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark){
  :root{
    color-scheme: dark;
    /* NEAR-BLACK, matching mtf.trading: page #000, cards #0A0A0A, hairlines
       #1F1F1F. The old surfaces were GitHub's blue-tinted greys; these are
       neutral, which is what makes that console read as an instrument rather
       than a document. Every ratio below is BETTER than the values it
       replaces — a darker ground raises the contrast of light text, so this
       change costs no accessibility and buys some (--faint went 5.98 -> 6.84,
       --lineStrong 3.87 -> 4.43 against a 3:1 non-text floor). Recomputed,
       not estimated. */
    --bg:#000000;        /* --txt on it 17.53:1 */
    --panel:#0A0A0A;     /* --txt on it 16.53:1 */
    --panel2:#141414;    /* --txt on it 15.38:1 */
    --line:#1F1F1F;       /* on --panel 1.20:1 — decorative hairline, declared */
    --lineStrong:#6E7886; /* on --panel 4.43:1 · on --bg 4.69:1 · on --panel2 4.12:1 */
    --txt:#E6EBF2;   /* panel 16.53:1 · panel2 15.38:1 · bg 17.53:1 */
    --dim:#AAB4C0;   /* panel  9.43:1 · panel2  8.77:1 · bg 10.00:1 */
    --faint:#8E99A6; /* panel  6.84:1 · panel2  6.37:1 · bg  7.26:1 */
    --acc:#7CB6F2;        /* panel 9.27:1 · bg 9.83:1 · panel2 8.62:1 · tintAcc 7.02:1 */
    --accBg:#1F6FD0;      /* #FFFFFF on it 4.95:1 */
    --accBgHover:#1A5FB4; /* #FFFFFF on it 6.29:1 · vs --accBg 1.27:1.
       NOT the #2B7FE0 first drafted: white on that is 4.03:1 and fails 1.4.3
       on hover. Darkening on hover also matches the light theme's direction. */
    --bull:#4FBF72;  /* panel 8.51:1 · panel2 7.92:1 · tintOk  6.60:1 · as spine on panel 8.51:1 */
    --bear:#F0776A;  /* panel 7.12:1 · panel2 6.63:1 · tintBad 6.08:1 · as spine on panel 7.12:1 */
    --amber:#D9A441; /* panel 8.80:1 · panel2 8.19:1 · tintWarn 7.03:1 · as spine on panel 8.80:1 */
    --focus:#8FC2FF; /* panel 10.68:1 · bg 11.33:1 · panel2 9.94:1 · tintAcc 8.09:1 */
    --tintAcc:#12283F;  /* --acc on it   7.02:1 */
    --tintOk:#102A19;   /* --bull on it  6.60:1 */
    --tintBad:#301513;  /* --bear on it  6.08:1 */
    --tintWarn:#2B2110; /* --amber on it 7.03:1 */
    --grid:#1A1A1A; /* on --panel 1.13:1 — decorative by design */
    --skel:#16161A; /* --faint on it 6.02:1, --txt on it 14.55:1 */
    /* Dark heatmap: the green ramp GitHub uses on its own dark theme, which is
       where mtf.trading's scale comes from. Luminance climbs with the value
       here — .011 → .043 → .112 → .281 → .474 → .587 — the mirror of the light
       ladder, so the calendar reads the same way in either theme. */
    --heat0:#161B22; /* L .0107 · vs panel 1.05:1 */
    --heat1:#0E4429; /* L .0431 · vs panel 1.53:1 · vs heat0 1.53:1 */
    --heat2:#006D32; /* L .1117 · vs panel 2.66:1 · vs heat1 1.74:1 */
    --heat3:#26A641; /* L .2805 · vs panel 5.42:1 · vs heat2 2.04:1 */
    --heat4:#39D353; /* L .4743 · vs panel 8.60:1 · vs heat3 1.59:1 */
    --heat5:#57E389; /* L .5871 · vs panel 10.4:1 · vs heat4 1.22:1 */
    /* end to end 10.9:1; adjacent 1.22-2.04:1, declared, redundancy as above */
    --stk0:#8FC2FF; /* vs panel 9.34:1 */
    --stk1:#3D74B0; /* vs panel 3.56:1 · vs stk0 2.62:1 */
    --stk2:#1B3A5C; /* vs panel 1.49:1 · vs stk1 2.39:1 */
    --scrim:rgba(0,0,0,.72);
    --shadow:0 1px 2px rgba(0,0,0,.45);
    --shadowUp:0 -2px 14px rgba(0,0,0,.55);
    --shadowModal:0 24px 60px rgba(0,0,0,.65);
  }
}

/* The explicit override, higher specificity (0,2,0) than the :root rules above,
   so it beats both the light base and the prefers-color-scheme block in either
   direction. Set by the theme button as document.documentElement.dataset.theme. */
:root[data-theme="dark"]{
  color-scheme: dark;
  /* Mirrors the prefers-color-scheme block above, value for value. */
  --bg:#000000; --panel:#0A0A0A; --panel2:#141414;
  --line:#1F1F1F; --lineStrong:#6E7886;
  --txt:#E6EBF2; --dim:#AAB4C0; --faint:#8E99A6;
  --acc:#7CB6F2; --accBg:#1F6FD0; --accBgHover:#1A5FB4;
  --bull:#4FBF72; --bear:#F0776A; --amber:#D9A441;
  --focus:#8FC2FF;
  --tintAcc:#12283F; --tintOk:#102A19; --tintBad:#301513; --tintWarn:#2B2110;
  --grid:#1A1A1A; --skel:#16161A;
  --heat0:#141414; --heat1:#15304A; --heat2:#1B4670;
  --heat3:#2A6BA8; --heat4:#4C97D8; --heat5:#8FC2FF;
  --stk0:#8FC2FF; --stk1:#3D74B0; --stk2:#1B3A5C;
  --scrim:rgba(0,0,0,.72);
  --shadow:0 1px 2px rgba(0,0,0,.45);
  --shadowUp:0 -2px 14px rgba(0,0,0,.55);
  --shadowModal:0 24px 60px rgba(0,0,0,.65);
}

/* Every light value restated. Without this a reader who has chosen Light on a
   machine whose OS is Dark gets whatever the prefers-color-scheme block set
   for any token this rule forgot — a half-dark page. */
:root[data-theme="light"]{
  color-scheme: light;
  --bg:#F6F7F9; --panel:#FFFFFF; --panel2:#F2F4F7;
  --line:#E3E6EB; --lineStrong:#7C848F;
  --txt:#10151C; --dim:#495260; --faint:#616A77;
  --acc:#0A5CB8; --accBg:#0071E3; --accBgHover:#005FC4;
  --bull:#12662C; --bear:#B42318; --amber:#8A5B00;
  --focus:#0A66C2;
  --tintAcc:#EBF4FD; --tintOk:#E7F2EA; --tintBad:#FBEBE9; --tintWarn:#F7EFDF;
  --grid:#E3E6EB; --skel:#EDEFF3;
  --heat0:#F2F4F7; --heat1:#D6E6F7; --heat2:#A9C9EC;
  --heat3:#6FA3DE; --heat4:#2E77C6; --heat5:#0A4A93;
  --stk0:#0A5CB8; --stk1:#6FA3DE; --stk2:#C7DCF4;
  --scrim:rgba(16,21,28,.45);
  --shadow:0 1px 2px rgba(16,21,28,.04);
  --shadowUp:0 -2px 14px rgba(16,21,28,.07);
  --shadowModal:0 24px 60px rgba(16,21,28,.18);
}


/* ---------------------------------------------------------------------------
   3. RE-POINTING THE INLINE STYLESHEETS ONTO TOKENS

   Both consoles still carry hardcoded hexes below their :root block. Each one
   below is a literal that would stay light-themed forever. This file loads
   after the inline <style>, so at EQUAL specificity these win with no HTML
   edit — but only at equal specificity. Two of the source selectors are
   heavier than their class name suggests and are matched exactly, by weight,
   further down. !important is never needed: the source is an inline sheet,
   never an !important declaration.
   --------------------------------------------------------------------------- */

/* Panels and the modal, on both pages. */
.panel{box-shadow:var(--shadow)}
.kyc-overlay{background:var(--scrim)}
.kyc-box{box-shadow:var(--shadowModal)}

/* The dark modal would otherwise have NO visible boundary. A 72% black scrim
   over --bg #0D1117 computes to roughly #040507; the modal's own --panel
   #151B23 sits at 1.18:1 against it, and its --line border is forced to #fff
   only in High contrast, not in normal dark. So in dark the modal border is
   promoted to --lineStrong: 3.87:1 against the panel it encloses. */
:root[data-theme="dark"] .kyc-box{border-color:var(--lineStrong)}
@media (prefers-color-scheme: dark){ .kyc-box{border-color:var(--lineStrong)} }
:root[data-theme="light"] .kyc-box{border-color:var(--line)}

/* Badges. Text must stay meaningful with the colour removed — every badge on
   both consoles already carries a word ("open", "target hit", "sl hit"). */
.b-open{background:var(--tintAcc);color:var(--acc)}   /* 5.84:1 L · 7.02:1 D */
.b-ok{background:var(--tintOk);color:var(--bull)}     /* 6.17:1 L · 6.60:1 D */
.b-bad{background:var(--tintBad);color:var(--bear)}   /* 5.69:1 L · 6.08:1 D */
.b-warn{background:var(--tintWarn);color:var(--amber)}/* 5.13:1 L · 7.03:1 D */

/* Action chips (admin home). */
.chip-bad{background:var(--tintBad)}
.chip-warn{background:var(--tintWarn)}
.chip-ok{background:var(--tintOk)}

/* Buttons. #fff is KEPT as button.act's colour: it is text on --accBg, which
   is 4.70:1 light and 4.95:1 dark. It is not a stray literal, it is the pair
   the token was chosen for. a.act is the same control rendered as a link —
   used where a popup blocker would eat a scripted window.open. */
button.act:hover,a.act:hover{background:var(--accBgHover)}
button.mini:hover{color:var(--acc);border-color:var(--accBg);background:var(--tintAcc)}

/* SPECIFICITY TRAP 1 — admin.html's client filter chips.
   Source selector `#clientFilters button.on` is (1,1,1): one id, one class,
   one type. A `.on{}` rule here would lose to it no matter how late it loads.
   Matched exactly. */
#clientFilters button.on{background:var(--tintAcc);color:var(--acc);border-color:var(--accBg)}

/* SPECIFICITY TRAP 2 — client.html's onboarding wizard step bar.
   Source selector `.wiz-bar li.wiz-cur` is (0,2,1). Matched exactly. */
.wiz-bar li.wiz-cur{background:var(--tintAcc);color:var(--acc);border-color:var(--accBg)}
.wiz-bar li.wiz-done{border-color:var(--bull);color:var(--bull)}


/* ---------------------------------------------------------------------------
   4. THE CONTROL BORDER — a pre-existing AA gap, fixed

   WCAG 1.4.11 exempts decoration but not "a boundary required to identify a
   control". Today input, select, textarea and button.mini draw their ONLY
   boundary in --line at 1.25:1. On a site publishing an AA conformance claim
   that is a real failure, not a nicety. --lineStrong takes them to 3.78:1
   light / 3.87:1 dark against the panel they sit on.
   --------------------------------------------------------------------------- */
input,select,textarea,button.mini{border-color:var(--lineStrong)}


/* ---------------------------------------------------------------------------
   5. THE FOCUS RING — a pre-existing 2.4.7 failure, fixed

   Both consoles currently do:
       input:focus{outline:none; border-color:var(--accBg);
                   box-shadow:0 0 0 3px rgba(0,113,227,.14)}
   Two independent problems:
     · the toolbar's High-contrast mode forces `box-shadow:none !important` AND
       `border-color:#fff !important` on `body *`. Both channels are erased, so
       a focused input on either console has NO focus indicator at all;
     · outside High contrast, rgba(0,113,227,.14) over white computes to about
       1.1:1 — a 1.4.11 failure in its own right.

   `outline` is the one channel High contrast does not erase. The `outline:none`
   on :focus in the inline sheets is overridden explicitly below at equal
   specificity by a later-loading rule.
   --------------------------------------------------------------------------- */
input:focus,select:focus,textarea:focus{
  outline:revert; border-color:var(--accBg); box-shadow:none}

input:focus-visible,select:focus-visible,textarea:focus-visible,
button:focus-visible,a:focus-visible,summary:focus-visible,
button.mini:focus-visible,
#tabs button:focus-visible,
.rangesel button:focus-visible,
.mobile-bar button:focus-visible,
.skip:focus-visible,
[tabindex]:focus-visible{
  outline:3px solid var(--focus);   /* 5.69:1 L / 9.34:1 D against --panel */
  outline-offset:1px;
  box-shadow:none}

/* button.act is the one control with a saturated fill of its own. At
   outline-offset:1px the ring's inner neighbour is the button body, and
   --focus against --accBg is 1.21:1 light / 2.67:1 dark — it would read as a
   fringe, not a ring. 2px of offset puts --panel on BOTH sides of the ring, so
   the pair that matters is --focus on --panel: 5.69:1 light, 9.34:1 dark. */
button.act:focus-visible{outline:3px solid var(--focus);outline-offset:2px;box-shadow:none}

/* Sidebar items sit flush to the rail edge; an outward ring would clip. */
#tabs button:focus-visible{outline-offset:-3px}

/* Focus must never be the ONLY thing that moved: keyboard users who arrive at
   a control by tabbing get the ring, pointer users who click get nothing
   extra, which is what :focus-visible is for. No :focus-within theatrics. */


/* ---------------------------------------------------------------------------
   6. PAGE SHELL, SKIP LINK, VISUALLY-HIDDEN

   The desktop sidebar is position:sticky inside a CSS grid, NEVER
   position:fixed. Reason: the toolbar's Desaturate and Invert toggles apply a
   `filter` to <html>, and a filtered element becomes the containing block for
   its position:fixed descendants — a fixed sidebar silently stops being
   viewport-fixed and scrolls away while either toggle is on. Sticky is immune.

   minmax(0,1fr) on the content column is load-bearing, not a flourish: with a
   plain 1fr, one wide table or one wide SVG blows the grid track out and the
   PAGE scrolls horizontally, which is exactly the published claim this file
   exists to keep.
   --------------------------------------------------------------------------- */
.shell{
  display:grid;
  grid-template-columns:248px minmax(0,1fr);
  gap:var(--s5);
  max-width:1440px;
  margin:0 auto;
  padding:var(--s3) max(var(--s3),env(safe-area-inset-right))
          var(--s3) max(var(--s3),env(safe-area-inset-left))}

/* The client console carries 6 nav items, not 13, so it gets a narrower rail.
   This is an OPT-IN modifier — `class="shell shell-narrow"` — deliberately not
   keyed off `.wrap`, because BOTH consoles put their app inside `.wrap` and a
   `.wrap .shell` rule would silently re-size the admin rail too. If the client
   markup omits the modifier the only consequence is a 248px rail instead of
   220px: `.wrap`'s own max-width:1180px already clamps the shell there, so
   nothing overflows and nothing breaks. */
.shell-narrow{grid-template-columns:220px minmax(0,1fr)}

.content{min-width:0}          /* the second half of the no-horizontal-scroll
                                  contract: a grid item's default min-width is
                                  auto, which lets its content set the track's
                                  floor and defeats minmax(0,1fr) on its own */
.content:focus{outline:none}   /* it is tabindex="-1" purely as a focus target
                                  after a nav change; a ring on a whole region
                                  is noise, and the #navLive announcement plus
                                  the moved caret already report the change */

.sidebar{
  position:sticky;      /* never fixed — see the note above */
  top:0;
  align-self:start;
  padding:var(--s3) 0;
  min-width:0}
/* No background on the rail: it sits directly on --bg. Every sidebar contrast
   pair in this file is therefore stated against --bg, not --panel. */

/* Skip link. It lives inside #app, not inside <body>: #app is display:none
   until boot(), so a body-level skip link would be the first tab stop on the
   login screen pointing at an unrendered target — the keyboard user's very
   first action would do nothing and say nothing, which is worse than having no
   skip link. Inside #app it is the first focusable element in exactly the
   state where skipping is useful. */
.skip{
  position:absolute;
  left:-9999px;
  top:0;
  z-index:60;                    /* far below the toolbar's 2147482000 floor */
  background:var(--panel);
  color:var(--txt);              /* 18.32:1 L / 14.45:1 D */
  border:1px solid var(--lineStrong);
  border-radius:var(--r-ctl);
  padding:12px 16px;
  min-height:44px;
  display:inline-flex;
  align-items:center;
  font:600 13.5px var(--sans);
  text-decoration:none}
.skip:focus{left:var(--s3);top:var(--s3)}

/* Visually hidden but read aloud. Used by <caption class="vh"> on every chart
   data table and by the #navLive status region. */
.vh{
  position:absolute!important;
  width:1px;height:1px;
  padding:0;margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0}


/* ---------------------------------------------------------------------------
   7. SIDEBAR

   Structure is a re-grouping of the SAME #tabs container the delegated click
   handler already owns: children stay <button data-t="...">, the .on class
   stays the active marker, and style.display='none' on an individual button
   stays a working hide. Anything else and roles.js, settings.js, home.js and
   renderGate() break silently.

   The rail is typographic. No icons: an icon set would be a second signature
   competing with the spine AND a new self-hosted asset class.
   --------------------------------------------------------------------------- */
.sb-brand{padding:var(--s2) var(--s3) var(--s4)}
.sb-brand-n{display:block;font:600 14px var(--sans);color:var(--txt);letter-spacing:-.01em}
.sb-brand-r{display:block;font:400 11px var(--mono);color:var(--faint);margin-top:2px}
                                              /* --faint on --bg: 5.11:1 L / 6.54:1 D */

.sb-group{margin-bottom:var(--s3)}
.sb-group[hidden]{display:none}   /* pruneNav() hides a whole group when every
                                     one of its items has been hidden at
                                     runtime; without this the [hidden]
                                     attribute loses to the block display an
                                     author rule would otherwise set */

/* Group header. Capitals mark panel headers and nothing else — this is the one
   other place they appear, and it is deliberate: a group header IS a panel
   header for the rail. */
.sb-gh{
  display:flex;align-items:center;gap:var(--s2);
  width:100%;
  background:none;border:0;
  padding:var(--s2) var(--s3);
  cursor:pointer;
  color:var(--faint);            /* on --bg: 5.11:1 light / 6.54:1 dark */
  font:600 10.5px/1 var(--sans);
  letter-spacing:.11em;
  text-transform:uppercase;
  text-align:left}
.sb-gh-l{flex:1}

/* Chevron: CSS only, aria-hidden, no asset. Rotation is the non-colour channel
   that pairs with aria-expanded. */
.sb-gh-c{width:8px;height:8px;flex:none;position:relative}
.sb-gh-c::after{
  content:"";position:absolute;inset:0;margin:auto;
  width:6px;height:6px;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px);
  transition:transform .12s}
.sb-gh[aria-expanded="false"] .sb-gh-c::after{transform:rotate(-45deg) translate(-1px,1px)}

.sb-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:1px}
.sb-list[hidden]{display:none}
.sb-list > li{margin:0}
.sb-list > li[hidden]{display:none}   /* a hidden <button> must not leave a
                                         visible wrapper carrying the list's
                                         gap and padding — pruneNav() sets this */

#tabs .sb-list > li > button{
  position:relative;
  display:flex;align-items:center;gap:var(--s2);
  width:100%;
  background:none;border:0;
  border-radius:var(--r-ctl);
  padding:9px var(--s3);
  cursor:pointer;
  color:var(--dim);              /* idle, on --bg: 7.37:1 light / 9.01:1 dark */
  font:500 13px var(--sans);
  text-align:left;
  transition:background-color .12s,color .12s}

#tabs .sb-list > li > button:hover{background:var(--panel2);color:var(--txt)}
                                              /* --txt on --panel2: 16.63:1 L / 13.22:1 D */

/* Active. Three redundant channels, only one of which is colour:
   aria-current="page" for assistive tech, weight 600 for the eye, and a 3px
   left rule — the same visual grammar as the spine, applied to the rail.
   It is drawn in --acc and never in a state colour, so it can never be
   mistaken for st-ok / st-warn / st-bad. */
#tabs .sb-list > li > button.on{
  background:var(--tintAcc);
  color:var(--acc);              /* on --tintAcc: 5.84:1 light / 7.02:1 dark */
  font-weight:600;
  box-shadow:none}               /* kills the inline sheet's old .on shadow */
#tabs .sb-list > li > button.on::before{
  content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;
  border-radius:0 2px 2px 0;
  background:var(--acc)}         /* on --bg: 6.05:1 light / 8.86:1 dark */

/* Count chip on a nav item. It is `hidden` until a count exists, and the
   button's accessible name must be updated to include it in words — a bare
   numeral hanging off a label is not a name. */
.sb-n{
  margin-left:auto;
  font:500 11px var(--mono);
  font-variant-numeric:tabular-nums;
  color:var(--faint);
  background:var(--panel2);
  border-radius:999px;
  padding:1px 7px}
.sb-n[hidden]{display:none}
#tabs .sb-list > li > button.on .sb-n{color:var(--acc);background:var(--panel)}

.sb-foot{padding:var(--s4) var(--s3) 0;margin-top:var(--s3);border-top:1px solid var(--line)}
/* The leaving terms, in the console's own footer: refunds, cancellation, the
   agreement and the grievance route. Quiet by design — they should be findable
   without competing with the desk itself (2026-08-23). */
.sb-policy{display:flex;flex-wrap:wrap;gap:4px 12px;padding:0 0 var(--s3);font-size:11px;line-height:1.6}
.sb-policy a{color:var(--text-ter);text-decoration:none}
.sb-policy a:hover,.sb-policy a:focus-visible{text-decoration:underline;color:var(--text-dom)}
/* The foot and the phone's Display & account panel both stack full-width
   controls (theme, sign out); they need space between them, not a gap property
   on a non-flex parent. */
.sb-theme + .sb-theme{margin-top:var(--s2)}
.sb-theme{
  width:100%;
  background:none;
  border:1px solid var(--lineStrong);   /* 3.53:1 L / 4.23:1 D on --bg */
  border-radius:var(--r-ctl);
  color:var(--dim);                     /* 7.37:1 L / 9.01:1 D on --bg */
  padding:9px var(--s3);
  min-height:44px;
  cursor:pointer;
  font:500 12.5px var(--sans);
  text-align:left}
.sb-theme:hover{background:var(--panel2);color:var(--txt)}

/* The live region that announces the section after a nav change. Visually
   hidden; role="status" is set in the markup. */
#navLive{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}


/* ---------------------------------------------------------------------------
   8. MOBILE NAVIGATION

   Accepted and documented degradation: a position:fixed bottom bar un-sticks
   and scrolls with the page while Desaturate or Invert is on, for the
   containing-block reason in section 6. It is not fixable without either the
   top layer (which would cover the accessibility toolbar) or dropping those
   toolbar features. The mitigation is structural: on admin every section is
   also reachable from the in-flow #navAll panel, and on client all six items
   are in the bar and the page is short.

   Height budget: the toolbar's floating button sits at
   bottom: calc(74px + safe-area) below 820px, so any bottom bar taller than
   64px + safe-area collides with it. Every number below respects that.
   --------------------------------------------------------------------------- */

/* #navAll (admin only) is an IN-FLOW block at the top of the content column —
   no overlay, no position:fixed, no <dialog>, no popover. In-flow content is
   immune to the filter and zoom toggles and cannot cover the toolbar. */
#navAll{
  display:block;
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  padding:var(--s3);
  margin:0 0 var(--s3)}
#navAll[hidden]{display:none}

/* When the rail is parked inside #navAll it is a plain in-flow list again, not
   a bottom bar. This is what keeps the two breakpoints from ever producing two
   bars on admin at the same time. */
#navAll .sidebar{
  position:static;
  padding:0;
  background:none;
  border-top:0;
  box-shadow:none}

.mobile-bar{display:none}

@media (max-width:1023px){
  /* Admin's nav budget breaks first: a 248px rail out of a ~900px viewport is
     a quarter of the screen. Density is a separate question with a separate
     number (820px, owned by the page) — density is about text size, nav is
     about horizontal budget, and they do not share a breakpoint. */
  .mobile-bar{
    display:flex;
    position:fixed;left:0;right:0;bottom:0;
    z-index:50;                     /* far below the toolbar's 2147482000 */
    gap:0;
    background:var(--panel);
    border-top:1px solid var(--line);
    padding:var(--s1) var(--s1) calc(var(--s1) + env(safe-area-inset-bottom));
    box-shadow:var(--shadowUp)}
  .mobile-bar button{
    flex:1 1 0;min-width:0;
    border:0;background:none;border-radius:10px;
    min-height:50px;                /* 50 + 2*4 padding = 58px <= the 64px cap */
    color:var(--dim);               /* 7.90:1 L / 8.24:1 D on --panel */
    font:600 11px var(--sans);
    padding:var(--s1) 2px;
    cursor:pointer;
    white-space:normal;line-height:1.2}
  .mobile-bar button[aria-current="page"]{
    background:var(--panel2);color:var(--acc);font-weight:700}
  .mobile-bar button:focus-visible{outline:3px solid var(--focus);outline-offset:-3px}
}

@media (max-width:820px){
  /* One column, and the rail becomes the bottom bar it already is on client. */
  .shell,.wrap .shell{grid-template-columns:minmax(0,1fr);gap:0}

  .sidebar{
    position:fixed;left:0;right:0;bottom:0;
    /* top:auto is load-bearing. The desktop rail is position:sticky with top:0,
       and top is NOT reset by switching to fixed — a fixed box with both top and
       bottom set and auto height resolves top-anchored, which silently parked
       this bar under the masthead instead of at the bottom of the screen. The
       whole point of the phone layout is that it is thumb-reachable. */
    top:auto;
    z-index:50;
    display:flex;
    margin:0;gap:0;
    padding:var(--s1) var(--s1) calc(var(--s1) + env(safe-area-inset-bottom));
    background:var(--panel);
    border-top:1px solid var(--line);
    box-shadow:var(--shadowUp)}

  /* The groups flatten into one flex row: display:contents removes the group
     and list boxes from layout without removing them from the accessibility
     tree, so the <ul>/<li> semantics a screen reader announces survive. */
  .sidebar .sb-brand,.sidebar .sb-foot{display:none}
  .sidebar .sb-gh{display:none}
  .sidebar .sb-group,.sidebar .sb-list{display:contents}
  .sidebar .sb-group[hidden],.sidebar .sb-list[hidden]{display:none}
  .sidebar .sb-list > li{flex:1 1 0;min-width:0;display:flex}
  /* ...but never a hidden one. `.sb-list > li[hidden]{display:none}` up in the
     base layer has the SAME specificity as the line above (0,2,1 both ways), so
     the later rule won and every item pruneNav() had hidden for this role came
     back the moment the rail became a bottom bar. A viewer would see Team and
     Settings on a phone and nowhere else. Re-state it here, after. */
  .sidebar .sb-list > li[hidden]{display:none}

  #tabs .sb-list > li > button{
    justify-content:center;
    border-radius:10px;
    min-height:50px;
    padding:var(--s1) 2px;
    font:600 11px var(--sans);
    white-space:normal;line-height:1.2;
    text-align:center}
  #tabs .sb-list > li > button.on{background:var(--panel2);color:var(--acc)}
  /* The 3px rule is a rail idiom; in a bar it reads as a stray tick, so the
     active state falls back to its other two channels (weight + aria-current)
     plus the tint. */
  #tabs .sb-list > li > button.on::before{display:none}
  .sb-n{margin-left:4px}

  /* ...unless the rail is parked inside #navAll, where it stays a list. */
  #navAll .sidebar{
    position:static;display:block;
    padding:0;background:none;border-top:0;box-shadow:none}
  #navAll .sidebar .sb-gh{display:flex}
  #navAll .sidebar .sb-group{display:block}
  #navAll .sidebar .sb-list{display:flex;flex-direction:column}
  #navAll .sidebar .sb-list > li{display:block}
  /* Same trap, higher specificity: this id-qualified rule also outranks the
     base [hidden] rule, so the "All sections" sheet would list the sections
     this role cannot use. */
  #navAll .sidebar .sb-list > li[hidden],
  #navAll .sidebar .sb-group[hidden]{display:none}
  #navAll #tabs .sb-list > li > button{
    justify-content:flex-start;text-align:left;
    padding:9px var(--s3);font:500 13px var(--sans);min-height:44px}
}

/* @media(display-mode:standalone) safe-area handling for the installed PWA.
   The inline sheets carry this rule against the pre-rename nav class; moving to
   `.sidebar` would silently drop it, so it is carried here. */
@media (display-mode:standalone){
  .sidebar{top:env(safe-area-inset-top)}
}
@media (display-mode:standalone) and (max-width:820px){
  .sidebar{top:auto;padding-bottom:calc(var(--s1) + env(safe-area-inset-bottom))}
}
@media (display-mode:standalone) and (max-width:1023px){
  .mobile-bar{padding-bottom:calc(var(--s1) + env(safe-area-inset-bottom))}
}


/* ---------------------------------------------------------------------------
   9. STAT CARD

   One fact, big. A stat card is a .stat, NOT a .panel — it never carries a
   spine. A grid of little state-reporting bars would teach the eye to ignore
   the one bar that matters.
   --------------------------------------------------------------------------- */
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--s2)}

.stat{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--r-panel);
  padding:var(--s3) var(--s4);
  min-width:0}

/* The one addition to the capitals rule, and it is justified: a stat card is a
   mini panel and this label is its header. Nothing else gains capitals. */
.stat-l{font:600 10.5px/1.2 var(--sans);letter-spacing:.11em;text-transform:uppercase;
  color:var(--faint)}                     /* on --panel: 5.47:1 L / 5.98:1 D */

.stat-v{font:600 28px/1.1 var(--mono);font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;color:var(--txt);margin-top:var(--s2)}
                                          /* on --panel: 18.32:1 L / 14.45:1 D */

.stat-c{font-size:11.5px;color:var(--faint);margin-top:var(--s1);line-height:1.5}

/* The delta ships only where the series has a stable denominator — revenue and
   calls published. On a compliance register the counts are low and lumpy, and
   "up 12 on the previous 30 days" over 3 -> 15 renders noise as signal.
   Everywhere else the card prints .stat-was, the prior ABSOLUTE value, which
   is a fact rather than a growth reflex. */
.stat-d{display:flex;align-items:baseline;flex-wrap:wrap;gap:5px;
  font-size:12px;margin-top:var(--s2)}
.stat-d-g{flex:none}                       /* glyph, aria-hidden */
.stat-d-w{color:var(--dim)}                /* 7.90:1 L / 8.24:1 D */
.stat-d-up{color:var(--bull)}              /* 7.09:1 L / 7.44:1 D */
.stat-d-down{color:var(--bear)}            /* 6.57:1 L / 6.23:1 D */
.stat-d-flat{color:var(--dim)}             /* 7.90:1 L / 8.24:1 D */
/* Three channels, only one of which is colour: the glyph, the explicit sign in
   the figure (+12 / -12), and the word in .stat-d-w. Under High contrast every
   colour is gone and the sign and the words still carry it. */

.stat-was{font-size:12px;color:var(--dim);margin-top:var(--s2)}
                                           /* 7.90:1 L / 8.24:1 D — and it needs
                                              no redundancy: it is a bare fact */

.stat-spark{margin-top:var(--s2);height:24px}
.stat-spark[hidden]{display:none}


/* ---------------------------------------------------------------------------
   10. CHART CARD + VERDICT + RANGE SELECTOR

   The verdict sentence is where the boldness is spent: one plain-English line
   that states what the chart shows before the reader touches an axis. The
   chart itself is quiet — one hue, thin strokes, no gradient, no shadow, no
   animation of any kind.

   A chart card is an information panel and therefore carries NO spine, with
   exactly one exception across both consoles (open compliance tasks, which IS
   the state report and which no other panel reports).
   --------------------------------------------------------------------------- */
.chart-card{overflow:visible}

.chart-top{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2);
  padding:var(--s3) var(--s4) 0}
.chart-top:empty{display:none}

.verdict{
  margin:var(--s3) var(--s4) var(--s2);
  font:500 13.5px/1.5 var(--sans);
  color:var(--txt)}                        /* 18.32:1 L / 14.45:1 D */

/* A fixed floor in px, never a viewport unit, so swapping the skeleton for the
   drawn chart cannot shift the page. */
.chart-body{
  min-height:180px;
  padding:0 var(--s4) var(--s3);
  overflow-x:auto}                         /* a wide chart scrolls inside its
                                              own box; the page never does */
/* A share bar is one stacked bar and a legend — roughly 70px. Giving it the
   tall charts' 180px floor left a third of the card empty. Its skeleton is
   matched below, so there is still nothing to shift on first paint. */
.chart-body-sm{min-height:92px}
.chart-body-sm .sk-chart{height:60px}
.chart-body svg{display:block;width:100%;height:auto;max-width:100%}
.chart-body text{font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:10.5px;fill:var(--faint)}
/* SVG <text> does not inherit font-variant-numeric reliably from body, so it
   is set here explicitly rather than hoped for. */

.ch-grid line{stroke:var(--grid);stroke-width:1}
.ch-axis line{stroke:var(--lineStrong);stroke-width:1}   /* 3.78:1 L / 3.87:1 D
                                                            — the axis IS
                                                            required to read the
                                                            chart, so it is not
                                                            allowed to be a
                                                            decorative hairline */
.ch-line{fill:none;stroke:var(--acc);stroke-width:2;stroke-linecap:round;
  stroke-linejoin:round;vector-effect:non-scaling-stroke}
.ch-line-2{stroke-dasharray:4 3}
.ch-line-3{stroke-dasharray:1 3}
.ch-area{fill:var(--acc);fill-opacity:.10}
:root[data-theme="dark"] .ch-area{fill-opacity:.16}
@media (prefers-color-scheme: dark){ .ch-area{fill-opacity:.16} }
:root[data-theme="light"] .ch-area{fill-opacity:.10}
.ch-dot{fill:var(--acc)}
.ch-bar{fill:var(--acc)}                   /* 6.49:1 L / 8.10:1 D vs panel */
.ch-bar-zero{fill:none;stroke:var(--lineStrong);stroke-width:2}
/* A zero bucket draws a 2px stub rather than nothing, so the reader sees
   "measured, and it was zero" instead of a hole they have to interpret. */
.ch-end{fill:var(--txt);font-weight:600}
.ch-readout{margin:0 var(--s4) var(--s2);font:500 12.5px var(--mono);
  font-variant-numeric:tabular-nums;color:var(--dim);min-height:18px}

/* Stacked segments. Three steps of one hue cannot all clear 3:1 against the
   panel AND 3:1 against each other — that is arithmetic, not a shortcut: to
   clear 3:1 against white a fill needs relative luminance <= 0.30, and three
   such steps each 3:1 apart drives the third to a negative luminance. So the
   fills are declared (6.49 / 2.64 / 1.40 light; 9.34 / 3.56 / 1.49 dark) and
   the meaning is carried by four other channels: a 1px --panel stroke so
   neighbouring segments never merge, a direct label on every segment, a
   distinct <pattern> swatch per series in the legend, and the mandatory
   <details> data table. Maximum three series — a four-series stack is not
   honestly readable at this palette discipline and must be split into two
   charts. */
.stk-seg{stroke:var(--panel);stroke-width:1}
.stk-0{fill:var(--stk0)}
.stk-1{fill:var(--stk1)}
.stk-2{fill:var(--stk2)}
.stk-legend{display:flex;flex-wrap:wrap;gap:var(--s3);padding:0 var(--s4) var(--s3);
  font-size:12px;color:var(--dim)}
.stk-legend li{display:flex;align-items:center;gap:6px;list-style:none}
.stk-legend svg{width:14px;height:14px;flex:none}

/* Share bar and donut. Every percentage prints its raw count beside it — a
   bare percentage on a track record is the exact figure a reader over-reads. */
.sharebar{display:block;width:100%;height:auto}
.share-legend{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s4);
  padding:var(--s2) var(--s4) var(--s3);font-size:12.5px;color:var(--dim)}
.share-legend li{display:flex;align-items:center;gap:6px;list-style:none}
.share-sw{width:12px;height:12px;border-radius:3px;flex:none;
  border:1px solid var(--lineStrong)}      /* the swatch gets a >=3:1 boundary
                                              so a pale fill is still a shape */
.donut-hole-v{font:600 22px var(--mono);font-variant-numeric:tabular-nums;fill:var(--txt)}
.donut-hole-l{font:600 10.5px var(--sans);letter-spacing:.11em;fill:var(--faint)}

/* Funnel — replaces the CSS journey bars. The drop-off is a --bear figure and
   it always carries the word "lost" beside it. */
.fn-track{fill:var(--panel2)}
.fn-fill{fill:var(--acc)}
.fn-label{fill:var(--dim);font-family:var(--sans);font-size:12.5px}
.fn-num{fill:var(--txt);font-weight:600}
.fn-lost{fill:var(--bear);font-weight:600}  /* 6.57:1 L / 6.23:1 D vs panel */

/* The data table under every chart. Mandatory, not optional: it is the screen
   reader path, the "I want the actual number" path, and the reason the chart
   itself can afford to be this minimal. */
.chart-data{border-top:1px solid var(--line);margin-top:var(--s2)}
.chart-data > summary{
  padding:var(--s3) var(--s4);
  cursor:pointer;
  font:500 12.5px var(--sans);
  color:var(--acc);                        /* 6.49:1 L / 8.10:1 D */
  min-height:44px;
  display:flex;align-items:center}
.chart-data > summary::marker{color:var(--faint)}
.chart-data .tw{padding:0 var(--s4) var(--s3);overflow-x:auto}

.chart-src{padding:0 var(--s4) var(--s3);color:var(--faint);font-size:12px}

/* Range selector. Opt-in, not default: it exists on four admin charts only,
   and on zero client charts. A range option the data cannot back must not
   exist — "ALL" here means "everything this endpoint returned", and .chart-src
   states what that was. */
.rangesel{display:flex;gap:var(--s1);flex-wrap:wrap;margin-left:auto}
.rangesel button{
  background:var(--panel2);
  border:1px solid transparent;
  color:var(--dim);                        /* 7.17:1 L / 7.54:1 D on --panel2 */
  border-radius:999px;
  padding:6px 14px;
  min-height:44px;   /* was 32px. These are now on the CLIENT dashboard too, i.e.
                        on phones, and 44px is the floor this site publishes. */
  min-width:44px;
  cursor:pointer;
  font:500 12px var(--mono);
  font-variant-numeric:tabular-nums}
.rangesel button:hover{color:var(--txt)}
.rangesel button[aria-pressed="true"]{
  background:var(--tintAcc);
  border-color:var(--accBg);
  color:var(--acc);                        /* 5.84:1 L / 7.02:1 D */
  font-weight:700}
/* aria-pressed + weight + tint: three channels, one of them colour. */

/* Chart display toggles — mtf.trading's SMOOTH / LOG / GRID toolbar, rebuilt to
   this site's floor. Theirs are 12px checkboxes with no programmatic label;
   these are 44px buttons carrying aria-pressed, so they work with a thumb and
   with a screen reader. Same idea, same place on the card, none of the cost. */
.chctl{display:flex;gap:var(--s1);flex-wrap:wrap}
.chctl-b{
  background:transparent;
  border:1px solid var(--line);
  color:var(--dim);                        /* 5.80:1 L / 8.24:1 D on --panel */
  border-radius:var(--r-ctl);
  padding:0 12px;
  min-height:44px;                         /* the published touch floor */
  min-width:44px;
  cursor:pointer;
  font:500 12px var(--sans);
  letter-spacing:.02em}
.chctl-b:hover:not(:disabled){background:var(--panel2);color:var(--txt)}
.chctl-b[aria-pressed="true"]{
  background:var(--tintAcc);
  border-color:var(--accBg);
  color:var(--acc);
  font-weight:700}
.chctl-b:disabled{
  opacity:.55;
  cursor:not-allowed;
  /* the reason is on the accessible name, never on colour alone */
  text-decoration:line-through}
.chctl-b:focus-visible{outline:3px solid var(--focus);outline-offset:2px}


/* ---------------------------------------------------------------------------
   11. CALENDAR HEATMAP

   Deliberately NOT the red/green grid it is adapted from. A red/green activity
   grid is unreadable for roughly 8% of men and meaningless after desaturation.
   Activity is a magnitude, not a verdict, so it gets a magnitude ramp; red and
   green stay reserved for --bull/--bear, which always carry a word.

   Cells are not focusable, by design: a year is 365 tab stops. The <details>
   table is the keyboard and screen-reader path and the <desc> says so.
   --------------------------------------------------------------------------- */
.hm-c{rx:2}
.hm-c[data-step="1"]{fill:var(--heat1)}
.hm-c[data-step="2"]{fill:var(--heat2)}
.hm-c[data-step="3"]{fill:var(--heat3)}
.hm-c[data-step="4"]{fill:var(--heat4)}
.hm-c[data-step="5"]{fill:var(--heat5)}

/* Step 0 is stroked, and in --lineStrong rather than --line. Computed: --line
   against --heat0 is 1.14:1 light and 1.26:1 dark — the stroke would itself be
   invisible, and a quiet fortnight would render as blank paper, which reads as
   "no data" instead of "no activity". --lineStrong against --heat0 is 3.43:1
   light / 3.54:1 dark, and 3.78 / 3.87 against the --panel gap, so a zero cell
   is a visible, countable box in both themes and under Desaturate. */
.hm-c[data-step="0"]{fill:var(--heat0);stroke:var(--lineStrong);stroke-width:1}

.hm-legend{
  display:flex;align-items:center;gap:6px;flex-wrap:wrap;
  padding:var(--s2) var(--s4) var(--s3);
  font-size:11.5px;color:var(--faint)}
.hm-legend .hm-sw{width:11px;height:11px;border-radius:2px;flex:none;
  border:1px solid transparent}
.hm-legend .hm-sw[data-step="0"]{background:var(--heat0);border-color:var(--lineStrong)}
.hm-legend .hm-sw[data-step="1"]{background:var(--heat1)}
.hm-legend .hm-sw[data-step="2"]{background:var(--heat2)}
.hm-legend .hm-sw[data-step="3"]{background:var(--heat3)}
.hm-legend .hm-sw[data-step="4"]{background:var(--heat4)}
.hm-legend .hm-sw[data-step="5"]{background:var(--heat5)}
.hm-legend .hm-band{font-family:var(--mono);font-variant-numeric:tabular-nums}
/* The bands are computed from the data and printed. A fixed legend over
   variable data is a lie with a nice layout. */


/* ---------------------------------------------------------------------------
   12. SPARKLINE
   Trend beside a figure. No axes, no labels, no interaction, no tooltip.
   Always --acc: a sparkline never turns red or green, because it is a shape
   and the delta or .stat-was line beside it already carries direction in words.
   Fewer than three points renders nothing and the container is hidden — a
   two-point sparkline is a decoration pretending to be information.
   --------------------------------------------------------------------------- */
.spark{display:block;width:100%;height:24px}
.spark path{fill:none;stroke:var(--acc);stroke-width:1.5;
  vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
.spark circle{fill:var(--acc)}


/* ---------------------------------------------------------------------------
   13. PROFILE HEADER
   The avatar is generated initials in one neutral colour. No upload endpoint
   exists and none is proposed; and a colour that varies per person reads as a
   code the reader is expected to decipher. It is aria-hidden — the <h2> is the
   accessible identity.
   --------------------------------------------------------------------------- */
.prof-top{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s4)}
.prof-av{
  width:44px;height:44px;flex:none;
  border-radius:10px;
  background:var(--panel2);
  color:var(--txt);                        /* 16.63:1 L / 13.22:1 D */
  display:flex;align-items:center;justify-content:center;
  font:600 16px var(--mono);
  letter-spacing:.02em}
.prof-id{min-width:0;flex:1}
/* Tracking sits at 0, not the -.01em used elsewhere: once KYC is accepted this
   is the name of record and the KRA holds it in block capitals, which read
   cramped at tracking chosen for mixed case. .prof-src captions it, so the
   capitals read as official rather than as a styling fault. */
.prof-n{font:600 18px/1.3 var(--sans);letter-spacing:0;color:var(--txt)}
.prof-src{margin-top:3px;font-size:11.5px;color:var(--dim)}
.prof-m{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2);
  margin-top:6px;font-size:12.5px;color:var(--dim)}
.prof-since{color:var(--dim)}              /* 7.90:1 L / 8.24:1 D */
/* .prof is the ONE panel on the client console that reports the subscription.
   #meBody drops to spine(..., null) at both of its call sites and the gate
   panel keeps its own class — the gate and the profile are mutually exclusive,
   so at most one subscription spine is ever painted. The badge text always
   contains the date AND the word, so the badge class is redundant. */


/* ---------------------------------------------------------------------------
   14. COMPLIANCE BADGES
   Earned, not a table row. Three redundant channels: the glyph, the word, and
   the date (or the word "pending"). The glyph is aria-hidden because the words
   already say it. .ebadges carries NO spine: it is a record of what has been
   done, and the profile header above it already reports state.
   --------------------------------------------------------------------------- */
.ebadges{list-style:none;margin:0;padding:0 var(--s4) var(--s4);
  display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--s2)}
.ebadge{
  display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  border-radius:var(--r-ctl);
  padding:10px var(--s3);
  font-size:12.5px}
.ebadge-earned{background:var(--tintOk);color:var(--bull)}   /* 6.17:1 L / 6.60:1 D */
.ebadge-pending{background:var(--panel2);color:var(--dim)}   /* 7.17:1 L / 7.54:1 D */
.ebadge-m{flex:none;font-weight:700}
.ebadge-t{flex:1;min-width:0;font-weight:500}
.ebadge-d{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:11.5px}
.ebadge button.mini{margin-left:auto}

/* ── A HELD PLAN'S FACTS, ACROSS THE CARD ─────────────────────────────────
   .g3 was referenced by the plan card from the day that card was written and
   was never defined in any stylesheet. With no rule, its three cells fell back
   to inline spans and stacked down the left edge of a 1400px card — price,
   end date and agreement in a single narrow column with the whole right side
   of the card empty. The layout the markup was asking for simply never
   shipped, which is most of why this page looked unfinished.

   auto-fit rather than a fixed three, because the same card carries a fourth
   cell on some states and has to collapse cleanly on a phone. */
.g3{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
  gap:var(--s2);margin:0 0 var(--s3)}
/* The tile chrome is for a row of FIGURES. .g3 is reused by the admin plan
   editor as a row of form fields (.pr-row), where a filled box round each
   input adds weight and the differing control heights make the boxes
   misalign. Same grid, no chrome. */
.g3:not(.pr-row) > span{display:flex;flex-direction:column;gap:4px;min-width:0;
  padding:11px 13px;background:var(--panel2);border-radius:var(--r-ctl)}
.pr-row > span{display:flex;flex-direction:column;gap:5px;min-width:0}
.pr-row{align-items:start !important}
.g3 label{font:600 9.5px/1 var(--sans);letter-spacing:.13em;text-transform:uppercase;
  color:var(--dim)}
/* The figure sits on its own line under the label; the qualifier that follows
   it (" / month", " · 27 days left") trails at reading size rather than
   competing with it. */
.g3 > span > b{font:600 18px/1.2 var(--mono);letter-spacing:-.02em;color:var(--txt);
  font-variant-numeric:tabular-nums}
.g3 > span > .dim{font-size:12px;color:var(--dim)}
.g3 a{font-size:13px}

/* Ending a plan is a destructive act and must not read as the card's call to
   action. For most of a subscription's life renew is correctly withheld —
   there is nothing to renew mid-period — so this was the ONLY button on the
   card and therefore looked like the thing to press. Same control, quieter
   until reached for. */
button.mini.mini-quiet{color:var(--dim);border-color:var(--line)}
button.mini.mini-quiet:hover{color:var(--bear);border-color:var(--bear);background:transparent}

/* ── FORM PRIMITIVES (.f, .grid2) ─────────────────────────────────────────
   Used by the admin settings panel and the coupon editor since they were
   written, and defined in no stylesheet. Without them a field's caption and
   its input were two inline boxes with nothing between them, and .grid2 —
   meant to put four fields in two columns — was a plain block, so every field
   ran the full width of the panel. */
.f{display:flex;flex-direction:column;gap:5px;min-width:0;margin-bottom:var(--s3)}
.f > span{font:600 9.5px/1 var(--sans);letter-spacing:.13em;text-transform:uppercase;
  color:var(--dim)}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:0 var(--s3)}
@media (max-width:640px){.grid2{grid-template-columns:1fr}}

/* ── BUYING A PLAN: one control row ──────────────────────────────────────
   The period picker had no width of its own, so it inherited the console's
   full-width form styling and a four-option select spanned the whole 1157px
   card — while "Add this plan", the control that actually takes the money,
   was rendered at .mini size beside it. The field is now sized to its content
   and the buy button is the primary control it always should have been.
   Wraps to two rows on a phone rather than shrinking the tap targets. */
.buyrow{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--s3);
  margin-top:var(--s3)}
.buyrow-f{display:flex;flex-direction:column;gap:5px;min-width:0;flex:0 1 320px}
.buyrow-f label{font:600 9.5px/1 var(--sans);letter-spacing:.13em;
  text-transform:uppercase;color:var(--dim)}
.buyrow-f select{width:100%}
.buyrow .act{flex:0 0 auto}
.buy-n{font-size:15px}

/* ── A PLAN'S PRICES, ALIGNED ─────────────────────────────────────────────
   Four periods in one table cell. Joined with middots they ran on across two
   wrapped lines and could not be compared; in a two-column grid the figures
   stack in their own column and the eye can run down them, which is the only
   reason to show four prices at once rather than one.
   max-content on the figure column so the rupee amounts align on their left
   edge without stretching the cell. */
.pricegrid{display:grid;grid-template-columns:max-content 1fr;
  gap:2px var(--s2);align-items:baseline}
.pricegrid b{font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.pricegrid span{font-size:11.5px}
/* A count and its total belong on one line. */
.nowrap{white-space:nowrap}
@media (max-width:560px){
  .buyrow-f{flex:1 1 100%}
  .buyrow .act{width:100%}
}

/* The signed agreement, offered from the profile header. Sits under the earned
   badges because it is the same kind of thing: a record of something completed,
   not a control that changes state. */
.prof-doc{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
  margin:0 var(--s4) var(--s4);padding:10px var(--s3);
  background:var(--panel2);border-radius:var(--r-ctl);font-size:12.5px}
.prof-doc-l{flex:1;min-width:0;font-weight:500}
@media (pointer:coarse){.prof-doc{min-height:44px}}
/* .mini is 25px high, which is fine for a mouse and well under the 44px a
   finger needs. These are the download and agreement controls on a client's
   own record, so they have to be reachable on the phone most clients will use.
   Padding rather than height, so the label stays centred. */
@media (pointer:coarse){
  button.mini,a.mini{min-height:44px;padding-top:10px;padding-bottom:10px}
}

/* Checkboxes were the browser default: 13x13 CSS pixels, no focus ring of
   their own, and nothing tying the box to its words. "Show on the public
   website" decides whether a plan is on sale — it should not be the smallest
   target in the console. accent-color keeps the native control (and its
   native keyboard behaviour) while giving it this console's blue. */
input[type=checkbox],input[type=radio]{
  width:17px;height:17px;accent-color:var(--accBg);cursor:pointer;
  margin:0 8px 0 0;vertical-align:-3px;flex:0 0 auto}
input[type=checkbox]:focus-visible,input[type=radio]:focus-visible{
  outline:2px solid var(--acc);outline-offset:2px}
@media (pointer:coarse){
  input[type=checkbox],input[type=radio]{width:22px;height:22px}
}

/* ── COLLAPSIBLE PANELS ───────────────────────────────────────────────────
   The class is set on the PANEL and the CSS hides its children; nothing in
   the DOM is restructured, because both consoles rebuild panels as data
   arrives and several scripts hold references inside them.

   :not(.ph) rather than a body wrapper for the same reason — the header is
   the one child that must survive, or there is nothing left to click. */
/* NOT display:none. A chart measures its host to decide its viewBox, and a
   host inside a display:none panel measures nothing — charts.js falls back to
   320px, so a panel collapsed at page load came back at the wrong aspect
   ratio (a payments chart 842px tall instead of 190). Zero height with
   overflow hidden takes the panel's space away while leaving its children
   laid out at the FULL WIDTH, so anything drawn inside is drawn correctly and
   nothing has to be repainted on the way back.
   visibility:hidden keeps the folded content out of the tab order and the
   accessibility tree, which display:none did for free. */
.panel.is-collapsed > *:not(.ph){
  height:0;min-height:0;padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;
  overflow:hidden;visibility:hidden;border:0}
/* The header keeps its bottom rule only while something sits under it. */
.panel.is-collapsed > .ph{border-bottom:0}
.panel[data-collapsible] > .ph{cursor:pointer;user-select:none}

.ph-tog{margin-left:auto;flex:0 0 auto;display:inline-flex;align-items:center;
  justify-content:center;width:28px;height:28px;padding:0;margin-right:-6px;
  background:none;border:0;border-radius:var(--r-ctl);color:var(--faint);
  cursor:pointer;transition:transform .16s ease,color .12s,background .12s}
/* .x already claims margin-left:auto to push meta right; when both are present
   the toggle must not fight it for the same space. */
.ph .x + .ph-tog{margin-left:var(--s2)}
.ph-tog svg{width:16px;height:16px;display:block}
.ph-tog:hover{color:var(--txt);background:var(--panel2)}
.ph-tog:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
.panel.is-collapsed > .ph > .ph-tog{transform:rotate(-90deg)}
@media (prefers-reduced-motion:reduce){.ph-tog{transition:none}}
/* A finger needs more than 28px. */
@media (pointer:coarse){.ph-tog{width:44px;height:44px;margin-right:-10px}}


/* ---------------------------------------------------------------------------
   15. ACTIVITY TIMELINE
   <ol> because it is ordered. The kind is always a WORD — never a coloured dot,
   never a colour-coded rail. The rail is one neutral rule; only "today" is
   inked, and that is a position cue, not a category cue.
   --------------------------------------------------------------------------- */
.tl{list-style:none;margin:0;padding:0 var(--s4) var(--s3)}
.tl-day{
  font:500 11.5px var(--sans);
  color:var(--faint);                      /* 5.47:1 L / 5.98:1 D */
  padding:var(--s3) 0 var(--s1);
  text-transform:none}                     /* capitals are reserved for panel
                                              headers; day headers stay sentence
                                              case */
.tl-i{
  display:flex;gap:var(--s3);flex-wrap:wrap;
  border-left:3px solid var(--line);
  padding:var(--s2) 0 var(--s2) var(--s3);
  margin-left:2px}
.tl-i[data-today="1"]{border-left-color:var(--acc)}
.tl-when{flex:none;width:106px;color:var(--faint);font-size:12px}
.tl-when time{font-family:var(--mono);font-variant-numeric:tabular-nums}
.tl-body{flex:1;min-width:0}
.tl-kind{
  display:inline-block;
  font:600 10.5px var(--sans);
  letter-spacing:.04em;
  color:var(--dim);                        /* 7.90:1 L / 8.24:1 D */
  margin-right:6px}
.tl-what{color:var(--txt);font-size:13px}  /* 18.32:1 L / 14.45:1 D */


/* ---------------------------------------------------------------------------
   16. EMPTY STATE
   Two lines: what is absent, and what will make it appear. No illustration, no
   icon. An empty state is never an error state and never carries a spine — a
   register with nothing in it yet is not a register in trouble. A FAILED fetch
   is a different thing: that variant carries role="alert" and a Retry button,
   set in the markup, not here.
   --------------------------------------------------------------------------- */
.empty{padding:var(--s5) var(--s4);text-align:center;color:var(--faint)}
.empty-t{font-size:13.5px;color:var(--dim)}   /* 7.90:1 L / 8.24:1 D */
.empty-w{font-size:12.5px;margin-top:var(--s1)} /* --faint 5.47:1 L / 5.98:1 D */
.empty button.mini{margin-top:var(--s3)}

/* ── Metrics wall + hero KPI (shared) ─────────────────────────────────────
   Design language read from the MTF analytics reference and reduced to what
   transfers: one hero figure that leads, then a dense wall of grouped mono
   metrics — each a micro-label, a big value, and a plain sub-line. Uses the
   per-page spacing/mono tokens (--s*, --mono, --r-panel) which every page
   that loads this file defines; --tintOk carries a fallback for safety.
   Admin Home uses it; the client dashboard's own-status header uses it too —
   but the client surface never renders a market figure or a return here, only
   the client's account state. */
.kpi{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s4);
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r-panel);
  padding:var(--s5) var(--s5) var(--s4);margin-bottom:var(--s2)}
.kpi .kl{font:600 10px var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--faint)}
.kpi .kv{font:600 40px/1 var(--mono);letter-spacing:-.03em;margin-top:var(--s2)}
.kpi .ks{font:400 12.5px var(--sans);color:var(--dim);margin-top:var(--s2)}
.kpi .kr{flex:0 0 auto;text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:4px}
@media(max-width:640px){.kpi{flex-direction:column}.kpi .kr{text-align:left;align-items:flex-start}}

.mwall{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:var(--s2)}
.mgroup{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-panel);padding:var(--s4)}
.mgroup .gh{font:600 10px var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--faint);margin-bottom:var(--s2)}
.mcell{padding:10px 0;border-top:1px solid var(--line)}
.mcell:first-of-type{border-top:0;padding-top:2px}
.mcell .ml{font:600 9.5px var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.mcell .mv{font:600 21px/1.15 var(--mono);letter-spacing:-.02em;margin-top:2px}
.mcell .ms{font:400 11.5px var(--sans);color:var(--dim);margin-top:1px}

.dpill{display:inline-flex;align-items:center;gap:3px;border-radius:999px;padding:1px 8px;font:600 11px var(--mono);margin-top:3px}
.dpill.up{background:var(--tintOk,#E7F2EA);color:var(--bull)}
.dpill.down{background:var(--tintBad,#FBEBE9);color:var(--bear)}
.dpill.flat{background:var(--panel2,#F2F4F7);color:var(--dim)}

.segtog{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-ctl);overflow:hidden;background:var(--panel)}
.segtog button{border:0;background:none;padding:5px 12px;font:500 12px var(--sans);color:var(--dim);cursor:pointer}
.segtog button.on{background:var(--accBg);color:#fff}

/* Count pill inside a segmented tab. Muted against the resting tab and
   inverted on the selected one, so the number never competes with the label
   it belongs to. */
.segtog .tct{font:600 10.5px var(--mono);opacity:.62;margin-left:2px}
.segtog button.on .tct{opacity:.85}

/* The per-plan tab strip above a call list. Scrolls on its own when a client
   holds more plans than fit; the page must never scroll sideways because of it. */
.ptabs{padding:10px 16px 2px;overflow-x:auto;-webkit-overflow-scrolling:touch}
.ptabs .segtog{max-width:none}
.ptabs button{white-space:nowrap}

/* The call card header. Direction is no longer stated here: it is carried by
   which COLUMN the card sits in, plus the card's own tint and spine. Repeating
   it a third time inside a half-width column cost the room that the status
   badge and the AI-disclosure badge needed, and pushed both onto extra lines. */
.chead-main{display:flex;gap:8px;align-items:center;flex-wrap:wrap;min-width:0}

/* A spinner, shown only while an action is genuinely in flight — the answer to
   "it feels like it hung." A ring, 15px, the accent colour, spun by transform.
   Under reduced-motion it does not spin; it pulses opacity instead, so there is
   still a visible sign of life without rotation. */
.spin{display:inline-block;width:15px;height:15px;vertical-align:-2px;
  border:2px solid var(--line);border-top-color:var(--accBg);border-radius:50%;
  animation:spin .7s linear infinite}
.spin.sm{width:12px;height:12px;border-width:2px}
@keyframes spin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){
  .spin{animation:spin-pulse 1s ease-in-out infinite}
}
@keyframes spin-pulse{0%,100%{opacity:.35}50%{opacity:1}}


/* ---------------------------------------------------------------------------
   17. LOADING SKELETON — static by construction
   No animation, no keyframes, and therefore no prefers-reduced-motion
   exception to write: deleting the shimmer deletes the exception. A shimmer is
   decoration on a document that bans decoration everywhere else, and at this
   data volume the 150ms render threshold means it would almost never be seen.
   The container carries aria-busy="true" while pending and drops it on render;
   it must NOT carry role="status" — a screen reader should not read a
   placeholder aloud.
   --------------------------------------------------------------------------- */
.sk{background:var(--skel);border-radius:6px;opacity:.75}
.sk-l{height:11px;width:60%}                    /* stat card label bar */
.sk-v{height:28px;width:80%;margin-top:var(--s2)}/* stat card figure bar */
.sk-chart{height:180px;width:100%}               /* matches .chart-body's floor */
.sk-row{height:14px;width:100%;margin-bottom:var(--s2)}


/* ---------------------------------------------------------------------------
   18. THE SPINE — restated so it cannot drift

   Drawn ONLY for .st-ok / .st-warn / .st-bad, and drawn as a ::before
   background. That is not a stylistic preference: High-contrast mode forces
   background-color:#000, border-color:#fff and box-shadow:none on `body *`,
   and its selector does not match pseudo-elements — so a ::before background
   is the one construction that survives it. A border-drawn spine turns white;
   a box-shadow-drawn spine disappears.

   As graphical objects on --panel: --bull 7.09:1 L / 7.44:1 D,
   --amber 5.87:1 L / 7.70:1 D, --bear 6.57:1 L / 6.23:1 D. All clear 3:1.
   And it is always redundant with wording or a badge inside the panel — colour
   never carries the meaning alone.

   A .panel with no state class gets no ::before at all. There is deliberately
   no neutral or accent spine: a page half-painted in bars teaches the eye to
   ignore the bar that matters.
   --------------------------------------------------------------------------- */
.panel.st-ok::before,.panel.st-warn::before,.panel.st-bad::before{
  content:"";position:absolute;left:0;top:0;bottom:0;width:3px}
.panel.st-ok::before{background:var(--bull)}
.panel.st-warn::before{background:var(--amber)}
.panel.st-bad::before{background:var(--bear)}
/* Stat cards, chart cards and empty states are none of the above and therefore
   never match. .stat is not a .panel at all, by construction. */


/* ---------------------------------------------------------------------------
   19. TOUCH TARGETS
   44px minimum on every interactive control, applied on coarse pointers so a
   mouse-driven desk console keeps its density.
   --------------------------------------------------------------------------- */
@media (pointer:coarse){
  .sb-gh,
  #tabs .sb-list > li > button,
  .sb-theme,
  .mobile-bar button,
  #navAllBtn,
  .rangesel button,
  .chart-data > summary,
  .skip,
  button.mini,
  .ebadge button.mini{min-height:44px}
  .rangesel button{min-width:44px}
  .ebadge{min-height:44px}
  .tl button.mini{min-height:44px}
}


/* ---------------------------------------------------------------------------
   20. REDUCED MOTION
   Scoped away from the accessibility toolbar's own chrome: a11y.js already
   handles its switch transitions under this query, and this file has no
   business reaching into the top layer to second-guess it.
   There is no animation declared in this file to disable — the skeleton is
   static, so the reduced-motion carve-out it would have needed does not exist.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  body *:not(#a11yFab):not(#a11yFab *):not(#a11yPanel):not(#a11yPanel *){
    transition:none!important;
    animation:none!important;
    scroll-behavior:auto!important}
  .sb-gh-c::after{transition:none}
}


/* ---------------------------------------------------------------------------
   21. NO HORIZONTAL PAGE SCROLL — the enforcement, not the aspiration
   Wide content scrolls inside its own container; the page never does. The grid
   half of this contract is minmax(0,1fr) plus .content{min-width:0} in section 6.
   --------------------------------------------------------------------------- */
.tw{overflow-x:auto;max-width:100%}
.chart-card table{min-width:0}
img,svg,video,canvas{max-width:100%}
pre,code{overflow-x:auto;max-width:100%}
.stat-v,.tl-what,.prof-n,.verdict{overflow-wrap:anywhere}


/* ---------------------------------------------------------------------------
   COMMAND PALETTE — Ctrl-K on both consoles

   Seventeen destinations in the admin console and seven in the client
   dashboard. Grouping them made them tidy; it did not make them fast. Search is
   what makes a console of this size quick, and it is the one change that
   answers "hard to find things" and "too many clicks" on both surfaces at once.

   Sits on the existing tokens — no new colours, so it inherits dark mode and
   high-contrast mode with the rest of the system.
--------------------------------------------------------------------------- */
.cmdwrap{position:fixed;inset:0;z-index:80;display:flex;align-items:flex-start;
  justify-content:center;padding:12vh 16px 16px}
/* MUST come after the rule above. [hidden] is only display:none in the user
   agent sheet, and any class selector outranks it — so declaring display:flex
   on .cmdwrap left the palette permanently open across the whole dashboard,
   swallowing clicks behind its shade. Anything hidden by attribute that also
   carries a display needs this companion rule. */
.cmdwrap[hidden]{display:none}
.cmd-shade{position:absolute;inset:0;background:rgba(8,12,20,.46);backdrop-filter:blur(2px)}
.cmd{position:relative;width:min(560px,100%);background:var(--panel);
  border:1px solid var(--line);border-radius:12px;overflow:hidden;
  box-shadow:0 18px 48px -12px rgba(8,12,20,.34),0 4px 12px -4px rgba(8,12,20,.16)}
.cmd-in{width:100%;box-sizing:border-box;border:0;border-bottom:1px solid var(--line);
  background:none;color:var(--txt);font:400 15px var(--sans);padding:15px 18px;outline:none}
.cmd-in::placeholder{color:var(--faint)}
.cmd-in:focus{box-shadow:inset 0 -2px 0 var(--accBg)}
.cmd-list{list-style:none;margin:0;padding:6px;max-height:46vh;overflow-y:auto}
.cmd-item{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:7px;
  cursor:pointer;font-size:14px;color:var(--txt)}
.cmd-item.on{background:var(--tintAcc)}
.cmd-item.on .cmd-l{color:var(--acc);font-weight:600}
.cmd-l{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmd-g{font:500 10.5px var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}
.cmd-n{font:600 11px var(--mono);color:var(--dim);background:var(--panel2);
  padding:1px 7px;border-radius:999px}
.cmd-cur{font:600 9.5px var(--mono);letter-spacing:.08em;text-transform:uppercase;
  color:var(--bull);background:var(--tintOk);padding:2px 7px;border-radius:999px}
.cmd-empty{padding:16px 12px;color:var(--faint);font-size:13.5px;font-style:italic}
.cmd-foot{border-top:1px solid var(--line);padding:8px 14px;background:var(--panel2);
  font-size:11.5px;color:var(--faint);display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.cmd-foot kbd{font:600 10.5px var(--mono);background:var(--panel);color:var(--dim);
  border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:1px 5px}

/* The trigger that tells people the shortcut exists. A keyboard shortcut
   nobody is told about is a shortcut nobody uses. */
.cmdbtn{display:inline-flex;align-items:center;gap:8px;background:var(--panel);
  border:1px solid var(--line);border-radius:8px;padding:6px 10px;cursor:pointer;
  color:var(--faint);font:400 13px var(--sans);min-width:170px}
.cmdbtn:hover{border-color:var(--lineStrong);color:var(--dim)}
.cmdbtn kbd{margin-left:auto;font:600 10.5px var(--mono);background:var(--panel2);
  border:1px solid var(--line);border-radius:4px;padding:1px 5px;color:var(--dim)}
@media(max-width:640px){
  .cmdwrap{padding:8vh 12px 12px}
  .cmdbtn{min-width:0}
  .cmdbtn .cmdbtn-t,.cmdbtn kbd{display:none}
}
@media(prefers-reduced-motion:reduce){.cmd-shade{backdrop-filter:none}}


/* ---------------------------------------------------------------------------
   MTF DESIGN DNA — applied to BOTH consoles from one place

   client.html and admin.html each carry their own copy of .panel/.ph inline.
   That duplication is why the two drifted. This file loads AFTER both, so the
   refinements below reach every panel on every screen without editing either
   copy and without letting them diverge again.

   Three things carry the look in mtf-analytics, and they are the three things
   the consoles were missing:

     1. Section labels: smaller, heavier, and tracked much wider (.14em) than a
        normal caption. They read as furniture, not as content, which is what
        lets the data underneath sit forward.
     2. Numbers: set tight. MTF runs -.04em on its big figures; at 30-40px a
        default tracking looks slack and amateur, and tightening is most of the
        difference between "a table" and "a product".
     3. Structure by BORDER, not by gap. MTF's stats grid is one rounded
        container divided by hairlines, rather than floating cards with space
        between them. It reads as a single instrument instead of scattered
        parts, which is the specific complaint about clutter.
--------------------------------------------------------------------------- */

/* 1. Section labels — furniture, not content. */
.ph{letter-spacing:.14em;font-size:10px}
.ph .x{letter-spacing:0}

/* 2. Numbers, set tight. Applied by size: the larger the figure, the more it
      needs pulling in. .num is already tabular everywhere it is used. */
.kpi .kv{letter-spacing:-.04em}
.mcell .mv{letter-spacing:-.03em}
.stat-v{letter-spacing:-.04em}

/* 3. The bordered stat grid — MTF's signature block.
      One container, hairline dividers, no gaps. Cells sized by auto-fit so the
      same markup gives four across on a laptop and two on a phone without a
      media query per breakpoint. */
.sgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));
  gap:0;border:1px solid var(--line);border-radius:var(--r-panel);
  background:var(--panel);overflow:hidden;margin:var(--s3) 0}
.stat{padding:16px 18px 17px;display:flex;flex-direction:column;gap:5px;
  min-height:104px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
/* The trailing hairlines on the last row/column would draw a double border
   against the container's own edge. */
.sgrid .stat:last-child{border-right:0}
.stat-l{font:600 9.5px var(--sans);letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint)}
.stat-v{font:600 27px/1.05 var(--mono);color:var(--txt);font-variant-numeric:tabular-nums}
.stat-s{font:400 12px var(--sans);color:var(--dim);margin-top:auto}
.stat-d{display:inline-flex;align-items:center;gap:3px;font:600 12px var(--sans)}
.stat-d.up{color:var(--bull)}
.stat-d.down{color:var(--bear)}
.stat-d.flat{color:var(--faint)}
@media(max-width:520px){
  .stat{padding:13px 14px;min-height:88px}
  .stat-v{font-size:23px}
}


/* ---------------------------------------------------------------------------
   MTF CHART TREATMENT

   The chart module already draws the right things and carries its own classes,
   so the look is entirely a matter of these hooks — no drawing code changes,
   and none of its accessibility work is disturbed.

   What mtf-analytics does differently, and what is adopted here:

     * Gridlines recede. MTF's sit far below the series; a gridline that
       competes with the data is a bug. Dashed and lighter, so the eye reads
       the line first and the scale only when it looks for it.
     * The series is the heaviest thing on the chart. 2px -> 2.25px, round
       joins, and an area fill dropped to .07 so a filled series never muddies
       the line that defines it.
     * Axis text is small, wide-tracked and muted — the same furniture
       treatment as the section labels, so a chart's scale reads as part of the
       frame rather than as content.
     * Figures are tabular everywhere. Numbers that shift width as they change
       are the single most common reason a live dashboard feels unsteady.
--------------------------------------------------------------------------- */

/* Gridlines recede; the axis stays legible because it carries the scale. */
.ch-grid line{stroke:var(--grid);stroke-width:1;stroke-dasharray:2 4;opacity:.85}
.ch-axis line{stroke:var(--line);stroke-width:1}

/* The series carries the chart. */
.ch-line{stroke-width:2.25;stroke-linejoin:round}
.ch-area{fill-opacity:.07}
.ch-dot{stroke:var(--panel);stroke-width:2}

/* Axis and legend text: furniture, not content. */
.ch-ax-t,.ch-tick,.ch-lab{font-family:var(--sans);font-size:10px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;fill:var(--faint)}
.ch-end{font-family:var(--mono);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.ch-readout{letter-spacing:-.01em;font-variant-numeric:tabular-nums}

/* Chart card head, MTF's .chart-header: a real title, not a caption. */
.ch-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
  flex-wrap:wrap;padding:var(--s4) var(--s4) 0}
.ch-title{font:700 15px var(--sans);letter-spacing:-.02em;color:var(--txt)}
.ch-sub{font:400 12px var(--sans);color:var(--faint)}

/* Legend rows, MTF's .lwc-legend-row: muted name, solid value, aligned figures. */
.stk-legend{gap:var(--s3) var(--s4);font-variant-numeric:tabular-nums}
.stk-legend li{font-size:12.5px;color:var(--dim)}
.stk-legend li b{color:var(--txt);font-weight:600;font-family:var(--mono);letter-spacing:-.02em}

/* Bars: soften the corners the way MTF does, so a bar chart does not read as
   harder-edged than the cards around it. */
.ch-bar{rx:2}


/* Integrity strip rows. Shown only when a check fails, so they are always
   worth reading rather than being scrolled past. */
.icheck{padding:9px 0;border-top:1px solid var(--line)}
.icheck:first-child{border-top:0;padding-top:0}
.icheck b{font-size:13.5px;font-weight:600;color:var(--txt)}
.icheck.sev b{color:var(--bear)}


/* Onboarding step error. Sits at the TOP of the step card, because under a
   long step it fell below the fold: the client pressed the button again, saw
   nothing happen, and read the page as hung. Styled as an alert rather than a
   grey note so it is not mistaken for helper text. */
.wizerr{margin:0 0 14px;padding:10px 13px;border-radius:var(--r-ctl);
  background:var(--tintBad);color:var(--bear);
  border-left:3px solid var(--bear);font:500 13.5px var(--sans);line-height:1.55}
.wizerr[hidden]{display:none}


/* "Since you last looked" — the question a returning client actually has, which
   a list of live calls does not answer. Accent-tinted rather than red or green:
   it is news, not a warning and not a result. */
.sincebar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  background:var(--tintAcc);border:1px solid var(--line);border-left:3px solid var(--accBg);
  border-radius:var(--r-ctl);padding:10px 14px;margin:0 0 12px;font-size:13.5px;color:var(--txt)}
.sincebar b{font-weight:600}
.sincebar .linkish{margin-left:auto;background:none;border:0;color:var(--acc);
  font:500 12.5px var(--sans);cursor:pointer;text-decoration:underline;padding:0}


/* The lede. mtf-analytics opens with a sentence, not a number: it tells the
   reader what happened and leaves the figures below as the evidence. Set larger
   than body copy and lighter than a heading, so it reads as an opening line
   rather than as a title or as another metric. */
.lede{font:400 16.5px/1.6 var(--sans);color:var(--dim);
  margin:2px 0 16px;max-width:74ch}
.lede b{color:var(--txt);font-weight:600}
@media(max-width:640px){ .lede{font-size:15px;margin-bottom:12px} }


/* A button that reads as a link. The report must carry the client's token, so
   it cannot be an <a href>, but it should not look like a form control. */
.linky{background:none;border:0;padding:0;color:var(--acc);font:inherit;
  cursor:pointer;text-decoration:underline}
.linky:hover{color:var(--accBgHover)}

/* Panel titles are headings.
   Both consoles render 21+ panel titles as .ph divs. Visually they read as
   headings, but to a screen reader they were plain text: navigating the
   dashboard by heading returned almost nothing, so the only way through was to
   read it linearly top to bottom. This is a Reg 19A / WCAG 1.3.1 concern on a
   site that carries an accessibility mandate, and it costs nothing to fix —
   role and aria-level are applied in script, so no markup or styling changes
   and no risk of a layout regression. */
.ph[role="heading"] { /* appearance unchanged — semantics only */ }

/* ═══════════════════════════════════════════════════════════════════════════
   MTF.TRADING COMPONENTS

   Four patterns carried over from the MTF Analytics console. Both systems
   already share IBM Plex, the 12px radius, the hairline-on-card structure and
   the tiny tracked uppercase label, so this is the part that was genuinely
   missing rather than a restyle.

   One thing deliberately NOT carried over: mtf colours a figure green or red
   and leaves it at that. This console's rule is that colour reinforces a word,
   never replaces it — a badge carries the state AND the date — because colour
   alone fails 1.4.1 and this desk has an accessibility mandate. So .delta
   below always prints its direction as a glyph and its figure as text; the
   colour is the third channel, not the only one.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 1. THE HERO FIGURE ───────────────────────────────────────────────────
   A single number worth more of the screen than anything around it, with its
   label above and its qualifiers below. */
.hero-stat{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--s4);
  justify-content:space-between;padding:var(--s4) var(--s4) var(--s5)}
.hero-stat-main{min-width:0;flex:1 1 320px}
.hero-l{font:600 9.5px/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;
  color:var(--dim);display:flex;align-items:center;gap:6px}
.hero-v{font:600 42px/1.02 var(--mono);letter-spacing:-.04em;color:var(--txt);
  font-variant-numeric:tabular-nums;margin:8px 0 6px;overflow-wrap:anywhere}
.hero-meta{font:600 10px/1.4 var(--sans);letter-spacing:.09em;text-transform:uppercase;
  color:var(--acc)}
.hero-sub2{margin-top:5px;font-size:12.5px;color:var(--dim);max-width:70ch}
.hero-stat-side{display:flex;flex-direction:column;align-items:flex-end;gap:8px;
  flex:0 1 auto;min-width:0}
@media (max-width:640px){
  .hero-v{font-size:32px}
  .hero-stat{padding:var(--s3) var(--s3) var(--s4)}
  .hero-stat-side{align-items:flex-start;width:100%}
}

/* ── 2. THE DELTA PILL ────────────────────────────────────────────────────
   Direction as a glyph, the change as text, the comparison date beside it.
   Readable with the colour removed, which is the test that matters. */
.delta{display:inline-flex;align-items:baseline;gap:7px;padding:5px 11px;
  border-radius:var(--r-pill,999px);font:600 12.5px var(--sans);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.delta-up{background:var(--tintOk);color:var(--bull)}
.delta-down{background:var(--tintBad);color:var(--bear)}
.delta-flat{background:var(--panel2);color:var(--dim)}
.delta-vs{font:500 9.5px var(--sans);letter-spacing:.1em;text-transform:uppercase;
  opacity:.82}

/* ── 3. THE INFO DOT ──────────────────────────────────────────────────────
   A definition attached to a label. This console is full of terms with a
   precise meaning — "cover", "closed", "reached target 1" — and a client
   guessing at them is a complaint waiting to happen. Native title text, so it
   needs no script and survives with JS off; aria-label carries it to a
   screen reader. */
.info-dot{display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;border-radius:50%;border:1px solid var(--lineStrong);
  color:var(--faint);background:none;cursor:help;padding:0;flex:0 0 auto;
  font:600 9px/1 var(--sans);letter-spacing:0;text-transform:none}
.info-dot:hover,.info-dot:focus-visible{color:var(--txt);border-color:var(--txt)}
.info-dot:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
@media (pointer:coarse){.info-dot{width:20px;height:20px;font-size:11px}}

/* ── 4. THE SEGMENTED CONTROL ─────────────────────────────────────────────
   One choice from a short set, where a <select> would hide the options. The
   pressed state is carried by aria-pressed, so the styling has something true
   to key off rather than a class the markup has to remember to keep in sync. */
.seg{display:inline-flex;gap:2px;padding:3px;background:var(--panel2);
  border-radius:var(--r-ctl);flex-wrap:wrap}
.seg button{appearance:none;border:0;background:none;cursor:pointer;
  padding:6px 13px;border-radius:calc(var(--r-ctl) - 2px);
  font:600 12px var(--sans);color:var(--dim);white-space:nowrap;
  transition:background var(--dur-fast,.12s),color var(--dur-fast,.12s)}
.seg button:hover{color:var(--txt)}
.seg button[aria-pressed="true"]{background:var(--panel);color:var(--txt);
  box-shadow:var(--shadow)}
.seg button:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
@media (pointer:coarse){.seg button{padding:11px 15px}}
@media (prefers-reduced-motion:reduce){.seg button{transition:none}}

/* ─── busy buttons & toasts (js/ui-busy.js) ──────────────────────────────────
   A press used to be silent: you found out it worked by noticing the page had
   redrawn. The spinner says "heard you", the toast says "done". Both are
   driven centrally, so no handler needs to opt in. */
button[aria-busy="true"], .act[aria-busy="true"], [role="button"][aria-busy="true"]{
  position:relative; opacity:.65; cursor:progress; pointer-events:none;
}
button[aria-busy="true"]::before, .act[aria-busy="true"]::before,
[role="button"][aria-busy="true"]::before{
  content:""; display:inline-block; width:.72em; height:.72em;
  margin-right:.5em; vertical-align:-.05em;
  border:2px solid currentColor; border-right-color:transparent;
  border-radius:50%; animation:ui-spin .6s linear infinite;
}
@keyframes ui-spin{to{transform:rotate(360deg)}}

.ui-toasts{position:fixed; right:16px; bottom:16px; z-index:9999;
  display:flex; flex-direction:column; gap:8px; pointer-events:none; max-width:min(92vw,380px)}
.ui-toast{background:#12321f; color:#eafaf1; border:1px solid #1f6f47;
  border-left:3px solid #2f9e63; border-radius:6px; padding:9px 13px;
  font-size:13px; line-height:1.45; box-shadow:0 6px 22px rgba(0,0,0,.28);
  animation:ui-toast-in .22s ease-out}
.ui-toast.is-bad{background:#331b19; color:#fbe9e7; border-color:#7d332b; border-left-color:#c4483a}
.ui-toast.is-out{opacity:0; transform:translateY(6px); transition:opacity .35s, transform .35s}
@keyframes ui-toast-in{from{opacity:0; transform:translateY(8px)}to{opacity:1; transform:none}}

@media (prefers-reduced-motion:reduce){
  button[aria-busy="true"]::before,.act[aria-busy="true"]::before,
  [role="button"][aria-busy="true"]::before{animation:ui-pulse 1.1s ease-in-out infinite}
  @keyframes ui-pulse{50%{opacity:.35}}
  .ui-toast{animation:none}
  .ui-toast.is-out{transition:opacity .2s}
}
