/* suite-kit — the RDA suite design system (CANONICAL: extracted from Lemma).
   One kit, one drift-guard test, every app. Served with NO build step: each app mounts this
   package at /static/kit and links /static/kit/suite-kit.css BEFORE its own thin accent sheet.

   Two-tier token model — this is what lets one kit dress every app without flattening identity:
     • the kit defines SEMANTIC tokens as `var(--rda-*, <company-fallback>)`
     • each app sets the base `--rda-*` accent vars (and per-edition overrides) in ITS sheet
   So an app overrides the ACCENT ONLY: set --rda-navy (+ the derived chrome follows). Lemma keeps
   navy/plum; Proof teal/indigo; Theorem violet; Axiom moss; Academy royal. Red is never an accent
   (it means primary-action / FAIL only — see the drift-guard).

   Type is SELF-HOSTED: Geist (interface/body) + Geist Mono (readouts/figures), variable woff2,
   from this package's fonts/ — so dev and prod render identically and tabular figures line up on
   every OS. App-specific tokens (a planner's grid metrics, a utilisation ramp) stay in the app. */
@font-face{font-family:"Geist";font-style:normal;font-weight:100 900;font-display:swap;
  src:url("/static/kit/fonts/Geist-Variable.woff2") format("woff2")}
@font-face{font-family:"Geist Mono";font-style:normal;font-weight:100 900;font-display:swap;
  src:url("/static/kit/fonts/GeistMono-Variable.woff2") format("woff2")}
:root{
  /* --brand is the edition hue for CHROME (topbar, hero, avatars, KPI tiles, boot) — it stays
     saturated in dark mode, unlike --navy which doubles as heading ink and flips light. */
  --brand: var(--rda-navy, #152D49);
  /* Hero-band gradient endpoints. The hero carries white text, so it must stay DARK in BOTH themes.
     It reads from --topbar (deep chrome, which every app keeps dark in dark mode) rather than --brand:
     an accent app may lighten --brand in dark mode for accent-pop, which would wash the hero out and
     strand its white text (the low-contrast bug this fixes). Apps that define no --topbar (Lemma) fall
     back to --brand, so their hero is byte-for-byte unchanged. --hero-2 (the light end) is app-tunable;
     accent apps set their own deep value, else it derives a slightly-lifted shade of --hero-1. */
  --hero-1: var(--topbar, var(--brand));
  --hero-2: color-mix(in srgb, var(--hero-1) 86%, #fff);
  --navy: var(--rda-navy, #152D49);
  --navy-soft: var(--rda-navy-soft, #2C4360);
  --red: var(--rda-red, #E72D3F);
  --red-dark: var(--rda-red-dark, #C4002A);
  /* the FILL role of red (the primary button). Identical to --red in light; in dark it stays saturated
     while --red lightens for legibility as ink. Split so white-on-red never becomes white-on-pink. */
  --red-fill: var(--rda-red, #E72D3F);
  --red-fill-dark: var(--rda-red-dark, #C4002A);

/* ── chart series ──────────────────────────────────────────────────────────────────────────────
     Dedicated tokens, because a series must NEVER be painted with an ink token: --navy-soft is
     near-black in light and near-white in dark, so a series drawn with it silently swaps identity
     with the theme and the reader's mental key inverts. These keep the same hue in both themes and
     only shift lightness enough to stay legible on the ground.
     Two hues, deliberately: blue + plum are the only pair that clears contrast against BOTH of the
     suite's surfaces once green/amber/red are reserved for status. If a chart needs a third
     category, it wants a different chart — not a third hue. --ch-ref is for reference lines
     (capacity, targets, thresholds): present, never competing with the data. */
  --ch-1: #5B87CE;
  --ch-2: #9C3F73;
  --ch-1-soft: #DCE6F6;
  --ch-2-soft: #F2E0EA;
  --ch-ref: #5A6A82;
  --blue: var(--rda-blue, #6F97D8);
  --blue-panel: var(--rda-blue-panel, #DBE3F0);
  --blue-soft: var(--surface-blue-soft, #ECF0F7);
  --panel: var(--surface-panel, #F6F8FC);
  --rule: var(--rda-rule, #E7ECF3);
  --rule-blue: var(--rda-rule-blue, #C8D2E3);
  /* The boundary of an INTERACTIVE control — the edge that tells you a thing is a field, a chip or
     a segmented control. WCAG 1.4.11 asks 3:1 against adjacent colours, and --rule-blue measures
     1.52:1 against a white field fill and 1.40:1 against --bg: on every input in all three apps.
     A separate token rather than retuning --rule-blue, which also draws PURELY DECORATIVE lines
     (.st-line, .sk-gate-rule, .av.partner) that would become noticeably heavier for no gain.
     #7E8BA8: 3.42:1 on the field fill, 3.13:1 on --bg, 3.21:1 on --panel. */
  --field-line: var(--rda-field-line, #7E8BA8);
  /* The focus ring. It was --blue #6F97D8, which measures 2.72:1 on --bg and 2.96:1 on --surface —
     under the 1.4.11 3:1 floor, on EVERY anchor, button, select and checkbox in all three apps,
     since the rule that paints it is zero-specificity and nothing overrides it.
     A separate token rather than retuning --blue, which also paints .notif-dot, .st-dot:hover and
     .notif-allread; retuning it to clear 3:1 would drag three unrelated indicators darker.
     Defaults to the app's own accent, so an app that has already measured its accent against its
     grounds gets a ring that is correct by construction rather than by coincidence. */
  --focus-ring: var(--rda-focus, var(--accent, #2F5FA8));
  --ink: var(--rda-ink, #152D49);
  --soft: var(--ink-soft, #2C4360);
  --muted: var(--ink-muted, #5A6A82);
  --faint: var(--ink-faint, #7D8A9E);
  --ghost: var(--ink-ghost, #A7AFBE);
  --green: #1E9E78; --amber: #E9A23B;
  /* amber as INK (countdown text, "soon" labels): the fill amber fails contrast as text on light
     surfaces, so status text uses this darker read — lifted in the dark block like --brass. */
  --amber-ink: #8A5E0F;   /* matches .pill.amber's measured ink — #9A6A12 was 3.93:1 on the amber wash, this clears it (see the .pill note) */
  --font: "Geist", -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, monospace;
  /* quiet-depth surfaces: tinted canvas + borderless white cards on layered shadows */
  --bg:#F4F5F8;
  --surface:#fff;
  --surface-2:#FBFBFD;
  --field:#fff;
  --modal-veil:rgba(21,45,73,.42);
  --card-line:rgba(23,20,40,.04);
  --shadow-card:0 1px 2px rgba(23,20,40,.05),0 8px 28px rgba(23,20,40,.07);
  --shadow-card-hover:0 4px 10px rgba(23,20,40,.07),0 18px 44px rgba(23,20,40,.11);
  /* motion — one signature curve + a small duration scale, so transitions feel crafted, not stock */
  --ease:cubic-bezier(.2,.72,.32,1);
  --ease-in:cubic-bezier(.45,.05,.55,.95);
  --t1:.12s; --t2:.17s; --t3:.24s; --t4:.42s;
  /* brass — the "Instrument" identity accent: calibration graduations, eyebrows, edition stamps.
     A warm note against the cool brand; sits on every edition's hero. */
  --brass:#A9772A; --brass-2:#C29A50;
  --nav-ink:color-mix(in srgb,var(--brand) 72%,#000);   /* topbar/footer: a deeper shade of the brand */
  --sh1:color-mix(in srgb,var(--brand) 90%,#fff);
  --sh2:color-mix(in srgb,var(--brand) 76%,#fff);
  --sh3:color-mix(in srgb,var(--brand) 63%,#fff);
  --sh4:color-mix(in srgb,var(--brand) 52%,#fff);
}
/* chart legend — the key to a plot. Marks live here with the --ch-* tokens they colour, so a
   legend can never be styled by one app and coloured by another. Match the mark to the plot:
   filled = drawn filled, .sw-out = drawn as a dashed outline, .sw-line = drawn as a rule. */
.legend{display:flex;flex-wrap:wrap;gap:9px 16px}
.lg{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--soft)}
.sw{width:15px;height:13px;border-radius:3px;flex:0 0 auto}
.sw.dash{background:var(--surface);border:1.5px dashed var(--ch-1)}
/* legend marks that match the plot: a hollow swatch for uncommitted demand (drawn as a dashed-outline
   bar), and a short dashed RULE for the capacity reference — which is a line, and was previously
   advertised with a filled square. --sw-c carries the series colour so the mark can't drift from it. */
.sw-out{background:transparent;border:1.5px dashed var(--sw-c,var(--ch-1))}
.sw-line{width:16px;height:0;border-radius:0;border-top:2px solid var(--sw-c,var(--muted));align-self:center}
.sw-line.dash{border-top-style:dashed}

@supports not (color:color-mix(in srgb,red 50%,blue)){
  :root{--sh1:var(--navy);--sh2:var(--navy-soft);--sh3:#3D5E7E;--sh4:var(--blue);--nav-ink:var(--navy)}
}

/* ── base document ─────────────────────────────────────────────────────────────────────────────
   The kit ships the type + surface tokens but a bare <body> won't consume them, so every app used to
   repeat this rule — and a new app that forgot it inherited the browser's serif ("Geist didn't take").
   Own it here: every app gets Geist + the shell's flex-column layout for free. An app sheet loads AFTER
   the kit and can still override any of it. */
html,body{margin:0}
/* dvh, NOT 100%. A percentage min-height resolves against the parent's height, and <html> has none,
   so `min-height:100%` computed to `auto` and the whole sticky-footer mechanism below — main's
   flex:1 0 auto and .appfoot's flex-shrink:0 — had nothing to stretch INTO. On any page shorter than
   the viewport the footer stopped wherever the content did: on a one-card "My courses" that left the
   branded floor at 541px with 359px of bare background under it, which reads as a page that failed
   to finish loading. dvh rather than vh so mobile browser chrome collapsing doesn't leave a gap.

   box-sizing:border-box because min-height sizes the CONTENT box: an app that puts padding on its
   own body — the kit specimen pads 60px — would otherwise be 100dvh PLUS that padding, i.e. every
   short page permanently scrolls by exactly the padding. border-box makes 100dvh mean the whole box,
   which is what "fill the viewport" is meant to say. */
body{min-height:100vh;min-height:100dvh;box-sizing:border-box;display:flex;flex-direction:column;
  background:var(--bg);color:var(--ink);
  font-family:var(--font);font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased}

/* ── shell components ──────────────────────────────────────────────────────────────────────────
   Canonical recipes shared by every app. Domain views, charts and app-specific variants (a project
   card's accent edge, an insight's inline tag) stay in each app's sheet. Extracted verbatim from
   Lemma so a consuming app renders pixel-identically; verified via the drift-guard test. */

/* buttons — radius 9px · 13px · weight 600 · sentence case · translateY(-1px) hover (the suite recipe;
   red is primary-action / FAIL only, never an app accent — see the drift-guard) */
.btn{font-family:var(--font);font-weight:600;cursor:pointer;border-radius:9px;padding:9px 16px;font-size:13px;letter-spacing:-.003em;line-height:1.25;border:1px solid transparent;transition:background var(--t1) var(--ease),border-color var(--t1) var(--ease),color var(--t1) var(--ease),box-shadow var(--t1) var(--ease),transform var(--t1) var(--ease)}
/* A LINK wearing .btn. Anything that navigates should be an <a>, so this is the common case rather
   than an edge one — but .btn only ever set geometry, so an anchor kept its underline, sat inline,
   and left its label hard against the padding while the <button> beside it centred. Two controls
   side by side, one of them visibly not a button. */
a.btn{display:inline-flex;align-items:center;justify-content:center;text-decoration:none}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:translateY(.5px) scale(.985)}
@media (prefers-reduced-motion: reduce){.btn:hover{transform:none}}
.btn.btn-sm{padding:6px 12px;font-size:12px;border-radius:8px}
.btn-red{background:var(--red-fill);color:#fff;box-shadow:0 1px 2px rgba(21,45,73,.13)}.btn-red:hover{background:var(--red-fill-dark)}
/* the brand-filled primary action (New / Create / Issue …). Distinct from .btn-red, which stays reserved
   for FAIL / destructive. Uses --brand so each edition gets its accent and it flips correctly in dark. */
.btn-primary{background:var(--brand);color:#fff;box-shadow:0 1px 2px rgba(21,45,73,.13)}
.btn-primary:hover{background:color-mix(in srgb,var(--brand) 87%,#000)}
.btn-ghost{background:rgba(28,34,51,.05);color:var(--soft);border-color:transparent}.btn-ghost:hover{border-color:transparent;color:var(--navy);background:rgba(28,34,51,.09)}
.spacer{margin-left:auto}

/* cards — borderless-feel on tinted canvas, layered shadows */
.card{border:1px solid var(--card-line);border-radius:16px;padding:20px 22px;background:var(--surface);box-shadow:var(--shadow-card)}
.card h3{margin:0 0 3px;font-size:15px;font-weight:600;color:var(--navy)}
.card .sub{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--faint);margin-bottom:14px}
.cardgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
/* card header — the title + action row atop a content card (an app may tint/retune it) */
.card-h{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 18px;border-bottom:1px solid var(--rule)}
.card-h h3{margin:0;font-size:14px;font-weight:600;color:var(--navy)}

/* status chips — mono uppercase pills */
.pill{display:inline-block;font-family:var(--mono);font-size:9.5px;text-transform:uppercase;letter-spacing:.05em;padding:2px 8px;border-radius:999px;background:var(--blue-soft);color:var(--navy-soft)}
/* The status pills carry their ink as a LITERAL and until 2026-08-06 had NO dark override, so
   #0f6e56 sat on a translucent green wash over a dark surface and measured 2.08:1 — in every app
   that has ever used .pill.green in dark mode. The tint is translucent, so it composites
   differently on --panel, --surface and --bg; the dark values below are measured on the darkest
   and clear on all three (green 7.04-8.39, amber 6.03-7.19, muted 6.92-8.23).
   These are INKS. --green and --amber themselves are fill tones, far too light to read at 9.5px on
   a wash of themselves — the same mistake in the other direction. */
.pill.green{background:rgba(30,158,120,.14);color:#0f6e56}
/* #8A5E0F, not #9a6a12: measured on the pill's OWN tinted ground (#F2E9DB, the 16% amber over the
   page) rather than on the page, the old ink came to 3.93:1. A pill is a small, bold, load-bearing
   word — "Waiting", "Needs a reply" — and it is read at a glance across a table. 4.73:1. */
.pill.amber{background:rgba(233,162,59,.16);color:#8A5E0F}
.pill.muted{background:rgba(90,100,120,.12);color:#5a6478}
/* kind tag — a mono micro-label filled with the app's brand (a document kind / type). An app may override
   the fill (e.g. a neutral navy) as a deliberate variant; in dark the brand lightens, so the text darkens. */
.kindtag{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.05em;color:#fff;background:var(--brand);border-radius:5px;padding:2px 7px;white-space:nowrap}

/* tables — mono uppercase micro-label heads, right-aligned mono numerics */
table{width:100%;border-collapse:collapse;font-size:14px}
th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--faint);font-weight:600;padding:8px 12px;border-bottom:1px solid var(--rule)}
td{padding:11px 12px;border-bottom:1px solid var(--rule);color:var(--soft);transition:background var(--t1) var(--ease)}
td.num,th.num{text-align:right;font-family:var(--mono)}
tr:hover td{background:var(--panel)}

/* shell — the topbar (54px, sticky, --nav-ink chrome), the brand cluster, nav pills, the edition badge
   (9px/700/.14em) and the home button — all shared, so every app's top-left matches (Lemma's recipe). */
.topbar{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:0 30px;height:54px;
  background:var(--nav-ink);
  box-shadow:0 1px 0 rgba(0,0,0,.05),0 6px 18px rgba(10,12,24,.14);
  position:sticky;top:0;z-index:40}
@supports not (color:color-mix(in srgb,red 50%,blue)){.topbar{background:var(--brand)}}
.lab-badge{display:inline-block;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.14em;
  background:rgba(255,255,255,.14);color:rgba(255,255,255,.8);padding:2px 7px;border-radius:3px;
  margin-left:7px;vertical-align:2px;border:1px solid rgba(255,255,255,.18)}
/* brand cluster — the logo mark + app name, vertically centred. The mark is a BLOCK so it carries no
   inline-baseline gap that would shift it off the name's centre (that was the drift). An app supplies
   only the mark (cfg.mark); the kit sizes + centres it, so every app's top-left reads identically. */
.brand{display:flex;align-items:center;gap:12px;cursor:pointer;text-decoration:none;color:inherit}
.brand:hover{opacity:.92}
/* WHITE, explicitly. The kit whitens .brand-text and .navbtn and never whitened the mark, so an
   SVG drawn with fill="currentColor" inherited the page ink — navy on the navy bar, 1.06:1, gone.
   Every app had already worked around it in its own way (Theorem bakes #fff into the mark's
   attributes, Lemma styles its own .lemmamark class) and Academy had not, so Academy's top-left was
   simply empty. Colouring it here is what the comment above already claims the kit does. */
.brand-logo{display:flex;align-items:center;flex:0 0 auto;color:#fff}
.brand-logo svg{height:28px;width:28px;display:block}
.brand-text{font-weight:600;color:#fff;font-size:15px;letter-spacing:-.01em;line-height:1.1}
.brand-text small{display:none}   /* a tagline slot some markup carries — hidden in the nav bar (it lives in the hero) */
.topnav{display:flex;gap:5px;align-items:center;height:100%;margin-left:auto}
.navbtn{font-family:var(--font);font-size:13px;font-weight:500;letter-spacing:-.003em;color:rgba(255,255,255,.72);background:none;border:none;border-radius:99px;padding:7px 13px;cursor:pointer;position:relative;height:auto;display:inline-flex;align-items:center;transition:color var(--t1) var(--ease),background var(--t1) var(--ease)}
.navbtn:hover{color:#fff;background:rgba(255,255,255,.08)}
.navbtn.on{color:#fff;background:rgba(255,255,255,.15)}
.navhome{padding:7px 10px}
.navhome-lbl{display:none}
.navhome svg{width:17px;height:17px;display:block}
.topbar-right{display:flex;align-items:center;gap:8px;flex:0 0 auto;margin-left:6px}

/* account menu — avatar → a panel of items (the shared account-menu pattern) + the help button */
.acctwrap::before{content:"";flex:0 0 auto;width:1px;height:22px;background:rgba(255,255,255,.18);margin:0 7px 0 1px}
.acctwrap{position:relative;display:flex;align-items:center;height:100%}
/* min-height matches the AVATAR variant. This button carries either a 30px .av disc or an 18px
   gear, and with no vertical padding it was 30px tall for a signed-in user and 18px for
   everyone else — a topbar control that changed size with sign-in state, and 18px fails WCAG
   2.2 SC 2.5.8's 24px minimum outright. One height, both states. */
.acctbtn{display:inline-flex!important;align-items:center;gap:5px;padding:0 4px 0 2px;min-height:30px}
.acctbtn .av{width:30px;height:30px;font-size:11px;box-shadow:0 0 0 1.5px rgba(255,255,255,.34)}
.acctchev{color:rgba(255,255,255,.72);transition:transform var(--t2) var(--ease)}
.acctbtn[aria-expanded="true"] .acctchev{transform:rotate(180deg)}
.acct-panel{position:absolute;top:calc(100% + 9px);right:0;min-width:220px;background:var(--surface);border:1px solid var(--card-line);border-radius:14px;padding:6px;z-index:50;box-shadow:0 1px 3px rgba(21,45,73,.08),0 16px 44px rgba(21,45,73,.20)}
.acct-head{padding:9px 11px 7px}
.acct-name{font-weight:600;color:var(--navy);font-size:13.5px;letter-spacing:-.005em;line-height:1.25}
.acct-role{color:var(--faint);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.07em;margin-top:3px}
.acct-item{display:block;width:100%;text-align:left;background:none;border:none;font-family:var(--font);font-size:13px;font-weight:500;color:var(--soft);padding:9px 11px;border-radius:9px;cursor:pointer;transition:background var(--t1) var(--ease),color var(--t1) var(--ease)}
.acct-item:hover{background:var(--panel);color:var(--navy)}
.acct-sep{height:1px;background:var(--rule);margin:5px 7px}
.acct-danger{color:var(--red)}
.acct-danger:hover{background:color-mix(in srgb,var(--red) 8%,#fff);color:var(--red)}
.helpbtn{width:34px;height:34px;padding:0;border-radius:99px;display:flex;align-items:center;justify-content:center;transition:background var(--t1) var(--ease);font-size:16px;font-weight:700;letter-spacing:0}
.helpbtn::after{display:none}
.helpbtn.on,.helpbtn:hover{background:rgba(255,255,255,.14);color:#fff}

/* nav dropdown groups — informative CARD popovers (icon tile + title + one-line description) */
.navdrop{position:relative;height:100%;display:flex;align-items:center}
.navchev{width:9px;height:6px;flex:0 0 9px;margin-left:4px;transition:transform var(--t2) var(--ease)}
.navdrop:hover .navchev{transform:rotate(180deg)}
.navdrop-panel{display:none;position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);
  min-width:270px;max-width:320px;background:var(--surface);border-radius:14px;flex-direction:column;gap:2px;
  padding:6px;z-index:50;box-shadow:0 1px 3px rgba(21,45,73,.08),0 16px 44px rgba(21,45,73,.22);
  border:1px solid var(--card-line)}
.navdrop::after{content:"";position:absolute;left:0;right:0;top:100%;height:12px}   /* hover bridge across the gap */
.navdrop:hover .navdrop-panel{display:flex}
.navdrop .navbtn::after{display:none}
.navdrop-item{height:auto!important;width:100%;justify-content:flex-start;align-items:flex-start;gap:11px;
  padding:9px 10px!important;border-radius:10px;text-align:left;white-space:normal;
  color:var(--soft);transition:background var(--t1) var(--ease),color var(--t1) var(--ease)}
.navdrop-item:hover{background:var(--panel);color:var(--soft)}
.navdrop-item.on{background:var(--blue-soft);color:var(--soft)}
.nd-ic{width:30px;height:30px;flex:0 0 30px;border-radius:9px;background:var(--brand);color:#fff;
  display:flex;align-items:center;justify-content:center;margin-top:1px}
.nd-ic svg{width:16px;height:16px}
.nd-tx{display:flex;flex-direction:column;gap:1px;min-width:0}
.nd-tx b{font-size:13px;font-weight:600;color:var(--navy);line-height:1.25;display:flex;align-items:center;gap:6px}
.nd-tx small{font-size:11px;font-weight:400;color:var(--muted);line-height:1.35}
.navdrop:has(.navbtn.on) .navdrop-btn{color:#fff;background:rgba(255,255,255,.15)}

/* main content container — 1280px, 30px gutters (the locked shell geometry) + panel switching */
main{max-width:1280px;margin:0 auto;padding:0 30px 70px;flex:1 0 auto;width:100%;box-sizing:border-box}
.panel{display:none;position:relative}.panel.active{display:block;animation:fade var(--t2) var(--ease)}
@keyframes fade{from{opacity:0}to{opacity:1}}
.loadveil{position:absolute;inset:0;background:rgba(255,255,255,.65);backdrop-filter:blur(1px);display:flex;align-items:flex-start;justify-content:center;padding-top:18vh;z-index:6;transition:opacity var(--t3) var(--ease)}
.loadveil[hidden]{display:none}
.loadveil.hiding{opacity:0;pointer-events:none}
/* the spinner glyph — completes the primitive so .loadveil is turnkey (was only positioning before). */
.loadveil .spin{width:28px;height:28px;border:3px solid var(--rule);border-top-color:var(--brand);border-radius:50%;animation:lvspin .7s linear infinite}
@keyframes lvspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.loadveil .spin{animation:none;border-top-color:var(--rule)}}

/* segmented control */
.seg{display:inline-flex;border:1px solid var(--field-line);border-radius:8px;overflow:hidden}
.seg button{font-family:var(--font);font-size:12.5px;font-weight:600;letter-spacing:-.003em;padding:7px 14px;background:var(--field);color:var(--muted);border:none;border-left:1px solid var(--field-line);cursor:pointer;transition:background var(--t1) var(--ease),color var(--t1) var(--ease)}
.seg button:first-child{border-left:none}.seg button.on{background:var(--brand);color:#fff}
.seg button:not(.on):hover{background:var(--panel);color:var(--soft)}

/* form fields — a field wrapper (uppercase micro-label over a control) + the .in control
   (input/select/textarea) with a brand focus ring. Promoted from Theorem; apps emit .ff/.in + inherit. */
.ff{display:flex;flex-direction:column;gap:4px}
.ff>span{font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
/* 16px on phones, and it is not a taste decision: Mobile Safari ZOOMS THE VIEWPORT whenever a
   focused input is under 16px, and does not zoom back out — so every field left the reader on a
   magnified, horizontally pannable page to pinch out of. Theorem had known this for months and
   fixed exactly one field (`.ask-in`, with that comment); every form either app authors through
   was still 13px. Padding is unchanged, so the control grows ~33px → ~38px, which also moves it
   toward a thumb-sized target. */
.in{font-family:var(--font);font-size:13px;color:var(--ink);background:var(--field);border:1px solid var(--field-line);border-radius:8px;padding:7px 10px;width:100%;box-sizing:border-box;transition:border-color var(--t1) var(--ease),box-shadow var(--t1) var(--ease)}
@media (max-width:640px){.in{font-size:16px}}
.in:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 16%,transparent)}
.in.mono{font-family:var(--mono);font-size:11.5px}
/* Checkboxes and radios keep their NATIVE box — accent-color tints it and nothing else changes, so
   the control stays a real checkbox for the keyboard, for assistive tech and for form autofill. The
   alternative (appearance:none plus a hand-drawn tick) is how a checkbox quietly stops announcing
   its own state. Sizing them up from the ~13px default matches the 16px the fields around them use;
   Academy already had this rule under a private .adm-check class, which meant its consent checkbox —
   the one control on a public page that a person must actually tick — was the browser default sat
   next to fully styled fields. */
input[type="checkbox"],input[type="radio"]{accent-color:var(--brand);width:16px;height:16px;margin:0}
.in.bad{border-color:var(--red)}
.in.bad:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--red) 20%,transparent)}
select.in{cursor:pointer}

/* ── deadline date-block · readiness bar · filter chip ─────────────────────────────────────────
   Pipeline primitives (born in Lemma's applications board; generic to any dated work-queue).
   .dblock is a calendar tile — day over month — whose urgency variant carries the ONE colour
   step a deadline reader needs: .due-late (missed, red ink) · .due-amber (inside a month) ·
   .due-brass (inside two) · .due-quiet (later / informational). Colour never replaces the date.
   .rbar is a 4px completeness bar (green done, amber .mid en route) for "how ready is this
   item" readings; pair it with a mono label, never use it for money or utilisation (those have
   their own idioms). .fchip is the row-filter pill (client-side filters over a list/board). */
.dblock{flex:0 0 44px;align-self:flex-start;text-align:center;font-family:var(--mono);border-radius:9px;padding:6px 0 5px;background:var(--u0,var(--blue-soft))}
.dblock .d{display:block;font-size:17px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;color:var(--soft)}
.dblock .m{display:block;font-size:8.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;margin-top:3px;color:var(--muted)}
.dblock.due-amber{background:rgba(233,162,59,.14)}.dblock.due-amber .d{color:var(--amber-ink)}
.dblock.due-brass{background:rgba(169,119,42,.12)}.dblock.due-brass .d{color:var(--brass)}
.dblock.due-late{background:rgba(231,45,63,.12)}.dblock.due-late .d{color:var(--red)}
.dblock.sm{flex-basis:38px;border-radius:8px;padding:5px 0 4px}
.dblock.sm .d{font-size:14px}
.rbar{height:4px;border-radius:3px;background:var(--u0,var(--blue-soft));overflow:hidden;min-width:56px}
.rbar i{display:block;height:100%;border-radius:3px;background:var(--green)}
.rbar i.mid{background:var(--amber)}
.fchip{font-family:var(--font);font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;border:1px solid var(--field-line);background:var(--field);color:var(--muted);cursor:pointer;transition:background var(--t1) var(--ease),color var(--t1) var(--ease),border-color var(--t1) var(--ease)}
.fchip.on{background:var(--brand);border-color:var(--brand);color:#fff}
.fchip:not(.on):hover{background:var(--panel);color:var(--soft)}
select.fchip{appearance:none;-webkit-appearance:none}

/* ── step track ────────────────────────────────────────────────────────────────────────────────
   A lifecycle as a dotted progress line (application stages, onboarding, a document's review
   path). Each .st cell is one stage: a clickable dot, the connecting line to the next stage, a
   label and an optional mono sub-line (a date, "now"). States: .done (green, ✓ in the dot),
   .on (brand — add .won/.lost for a terminal outcome), default = still ahead. The DOT is the
   control; keep it a <button> and put the wiring hook on it. */
.steptrack{display:flex;align-items:flex-start;flex:1;min-width:260px}
.st{flex:1;position:relative;min-width:0}
.st-dot{width:22px;height:22px;border-radius:50%;background:var(--surface);border:2px solid var(--field-line);display:grid;place-items:center;font-size:11px;line-height:1;color:#fff;padding:0;cursor:pointer;position:relative;z-index:2;font-family:var(--font);transition:border-color var(--t1) var(--ease),background var(--t1) var(--ease)}
.st.done .st-dot{background:var(--green);border-color:var(--green)}
.st.on .st-dot{background:var(--brand);border-color:var(--brand)}
.st.on.won .st-dot{background:var(--green);border-color:var(--green)}
.st.on.lost .st-dot{background:var(--red-fill);border-color:var(--red-fill)}
.st:not(.done):not(.on) .st-dot:hover{border-color:var(--blue)}
.st-line{position:absolute;top:10px;left:26px;right:4px;height:2px;background:var(--rule-blue);z-index:1}
.st.done .st-line{background:var(--green)}
.st:last-child .st-line{display:none}
.st-lbl{font-size:12px;font-weight:600;color:var(--muted);margin-top:7px}
.st.on .st-lbl{color:var(--ink)}
.st.done .st-lbl{color:var(--green)}
.st.on.lost .st-lbl{color:var(--red)}
.st-sub{font-family:var(--mono);font-size:10px;color:var(--faint);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
textarea.in{resize:vertical;min-height:56px}

/* modal / dialog — a scrolling veil + a centred card (header · body · footer). Built by SuiteKit.modal(),
   which adds Esc + backdrop-close + focus-first. Promoted from Theorem so apps stop hand-rolling overlays. */
.veil{position:fixed;inset:0;background:var(--modal-veil);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);display:flex;align-items:flex-start;justify-content:center;padding:6vh 20px;z-index:60;overflow:auto;animation:fade var(--t2) var(--ease)}
.modal{width:min(680px,100%);background:var(--surface);border-radius:16px;box-shadow:var(--shadow-card-hover);overflow:hidden;animation:modalpop var(--t3) var(--ease)}
.modal.modal-wide{width:min(920px,100%)}
@keyframes modalpop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.veil,.modal{animation:none}}
.modal-h{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--rule)}
.modal-h h3{margin:0;font-size:15px;font-weight:600;color:var(--navy)}
.xbtn{background:none;border:none;cursor:pointer;font-size:14px;color:var(--muted);padding:4px 9px;border-radius:8px;line-height:1}
.xbtn:hover{background:var(--panel);color:var(--navy)}
.modal-b{padding:18px 20px;display:flex;flex-direction:column;gap:14px}
.modal-f{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--rule);background:var(--panel)}

/* KPI stat band — mono tabular readouts (borderless; used inside a view where cards would be too heavy) */
.statband{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:20px 28px;margin-bottom:24px;align-items:start}
.statband .stat{min-width:0;padding:2px 0}
.statband .val{font-family:var(--mono);font-size:29px;font-weight:600;letter-spacing:-.015em;font-variant-numeric:tabular-nums;color:var(--ink);line-height:1.05;white-space:nowrap}
.statband .val.warn{color:var(--red)}
/* --muted, not --faint. At 10px uppercase with letterspacing, --faint measures 3.21:1 on --bg —
   under the floor, on the label that says what each headline number MEANS. The number above it is
   --ink and perfectly legible, which is what let this survive: the tile looks fine at a glance and
   the part carrying the meaning is the part that fails. --muted measures 5.04:1. */
.statband .label{font-family:var(--mono);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-top:8px;line-height:1.35}
.statband .meta{font-size:11.5px;color:var(--muted);margin-top:5px;line-height:1.3}

/* ── home — the landing pattern EVERY app opens on: a brand-hued hero band with the .statband primitive
   (above) reused INSIDE it as frosted-glass tiles. Promoted verbatim from Lemma's home (the canonical
   one) so every edition matches; apps converge onto it via SuiteKit.home({mark,title,edition,desc,stats})
   instead of each re-rolling a hero (that was the drift). --brand keeps each edition's hue in dark too,
   so the band reads identically; --brass-2 stamps the edition (the warm note that sits on every hero). */
.home{animation:fade var(--t2) var(--ease)}
.home-hero{position:relative;overflow:hidden;border-radius:18px;padding:38px 42px 34px;margin:22px 0 26px;color:#fff;
  background:linear-gradient(120deg,color-mix(in srgb,var(--hero-1) 74%,#000) 0%,var(--hero-1) 58%,var(--hero-2) 100%);
  box-shadow:var(--shadow-card)}
@supports not (color:color-mix(in srgb,red 50%,blue)){.home-hero{background:var(--hero-1)}}
/* The faint corner watermark. The app's OWN mark, rendered by mountShell's home() into .home-hero-wm
   — Theorem should not wear Lemma's logo behind its hero, which is what the hard-coded fallback below
   did to every app but Lemma. The ::after fallback survives for a hero built as static markup with no
   mark passed (Lemma's index.html); `.has-wm` switches it off once a real mark is present. */
.home-hero-wm{position:absolute;right:-60px;top:-80px;width:300px;height:300px;pointer-events:none;
  opacity:.06;color:#fff;fill:currentColor;z-index:0}
.home-hero-wm svg{width:100%;height:100%;fill:currentColor;stroke:currentColor}
.home-hero.has-wm::after{content:none}
.home-hero::after{content:"";position:absolute;right:-60px;top:-80px;width:300px;height:300px;pointer-events:none;opacity:.06;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M5,32 a27,27 0 1,0 54,0 a27,27 0 1,0 -54,0 Z M9,32 a23,23 0 1,0 46,0 a23,23 0 1,0 -46,0 Z M32,17 L45,24.5 L45,39.5 L32,47 L19,39.5 L19,24.5 Z M32,21 L41.5,26.5 L41.5,37.5 L32,43 L22.5,37.5 L22.5,26.5 Z'/%3E%3C/svg%3E") no-repeat center/contain}
.home-hero-inner{display:flex;align-items:center;gap:22px;position:relative;z-index:1}
.home-hero-mark{width:54px;height:54px;flex:0 0 54px;color:rgba(255,255,255,.9);fill:currentColor}
.home-hero-mark svg{width:100%;height:100%;fill:currentColor}
.home-hero-text{min-width:0}
.home-hero-name{font-size:33px;font-weight:700;color:#fff;letter-spacing:-.02em;line-height:1.1;margin:0;display:flex;align-items:baseline;gap:11px;flex-wrap:wrap}
.home-hero-edition,.home-edition{font-family:var(--mono);font-size:12.5px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--brass-2)}
.home-hero-tagline{display:none}   /* a legacy tagline slot some markup carries — hidden unless an app opts in */
.home-hero-desc{font-size:14px;color:rgba(255,255,255,.8);line-height:1.55;max-width:640px;margin:8px 0 0}
/* the .statband primitive re-skinned as frosted tiles when it sits in the hero (was each app's #homeStats) */
.home-hero .statband{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));grid-auto-rows:1fr;gap:14px;padding:0;margin:26px 0 0;position:relative;z-index:1;align-items:stretch}
.home-hero .statband .stat{padding:16px 18px;border-radius:12px;min-width:0;display:flex;flex-direction:column;
  background:rgba(255,255,255,.11);border:1px solid rgba(255,255,255,.16)}
.home-hero .statband .stat .meta{margin-top:auto;padding-top:6px}   /* pin the sub-label to the tile floor */
.home-hero .statband .val{color:#fff}
.home-hero .statband .val.warn{color:#ffa3b0}
.home-hero .statband .label{color:rgba(255,255,255,.66)}
.home-hero .statband .meta{color:rgba(255,255,255,.5)}
/* a stat tile can deep-link (app passes onClick → a <button>); subtle lift, otherwise identical */
.home-hero .statband button.stat{cursor:pointer;text-align:left;font:inherit;
  transition:background var(--t2) var(--ease),transform var(--t2) var(--ease),border-color var(--t2) var(--ease)}
.home-hero .statband button.stat:hover{background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.3);transform:translateY(-2px)}
/* the selected tile (app passes active:true) — a brighter frosted fill + a firmer edge, the "filter on" cue */
.home-hero .statband button.stat.on{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.5);box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}
.home-hero .statband button.stat.on:hover{background:rgba(255,255,255,.22)}
@media (prefers-reduced-motion:reduce){.home-hero .statband button.stat:hover{transform:none}}
@media(max-width:820px){.home-hero{padding:26px 22px}.home-hero-name{font-size:26px}.home-hero-inner{gap:16px}.home-hero-mark{width:44px;height:44px;flex-basis:44px}
  /* Narrow: the hero tiles pair up (a 4-stat band becomes 2×2) rather than collapsing to one tall
     column — promoted from Lemma's #homeStats override, which is where the 140px floor was proven.
     Below ~330px of content width they fall back to one column on their own, which is correct.
     Equal height comes from grid-auto-rows:1fr + align-items:stretch above and holds row-to-row, so
     the second row matches the first even when one tile's sub-label wraps. */
  .home-hero .statband{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-top:18px}
  .home-hero .statband .stat{padding:16px;border-radius:12px}}

/* branded empty state — an app's own glyph (.dm) above a message + optional CTA */
.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
  gap:14px;padding:44px 20px 50px;color:var(--muted);animation:emptyIn var(--t3) var(--ease)}
@keyframes emptyIn{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
.empty-mini{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  padding:22px 12px;text-align:center;color:var(--faint);font-size:12.5px;animation:emptyIn var(--t3) var(--ease)}
.empty-mini .dm{width:30px;height:38px;fill:#CBD6E7}
@media (prefers-reduced-motion: reduce){.empty,.empty-mini{animation:none}}
.empty .dm{width:54px;height:70px;fill:#CBD6E7}
/* On dark the glyph is a WATERMARK, not a light source — #CBD6E7 on a dark panel is the brightest
   thing on the screen, and the emptiest panel then shouts loudest. Mixed up from --surface (never
   down from #fff), so it stays a hair above the card it sits on in every theme the apps define.
   Kit-owned because every app's empty state hit this; Lemma used to carry a hard-coded copy that
   covered .empty but not .empty-mini. */
html[data-theme=dark] .empty .dm,html[data-theme=dark] .empty-mini .dm{
  fill:color-mix(in srgb,var(--surface) 78%,#8899B5)}
.empty .et{font-size:14.5px;color:var(--soft);font-weight:500;max-width:340px}
.empty .es{font-size:12.5px;color:var(--faint);max-width:340px;margin-top:-6px}
.empty .empty-cta{margin-top:4px}

/* ── the floating-dock footer rule ─────────────────────────────────────────────────────────────
   A fixed dock opted in with SuiteKit.clearFooter() rides above .appfoot instead of sitting on it.
   The shell writes the lift to `translate`; docks own `transform` for their entrance and hover, so
   the two never fight. The blanket prefers-reduced-motion rule above already flattens this
   transition to .01ms — that is deliberate: the dock still clears the footer, it just stops
   sliding. */
.sk-clears-footer{transition:translate .18s var(--ease,ease)}

/* centre-bottom navy toast (transient; exempt from the floating-dock footer rule) */
#toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(20px);background:var(--brand);color:#fff;padding:11px 18px;border-radius:8px;font-size:13px;opacity:0;pointer-events:none;transition:opacity var(--t3) var(--ease),transform var(--t3) var(--ease);z-index:100;box-shadow:0 10px 30px rgba(21,45,73,.3)}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* respect reduced-motion everywhere — near-instant, no loops */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}

/* ── footer (.appfoot) — the branded floor: app wordmark · "by [RDA wordmark]" · quiet links + version.
   Uses --nav-ink so it matches the topbar; the #rda-wm symbol is injected by the shell (or the app). */
.appfoot{flex-shrink:0;background:var(--nav-ink);color:rgba(255,255,255,.52);font-size:12px;
  padding:15px 30px;margin-top:40px;display:flex;align-items:center;justify-content:space-between}
.appfoot .footbrand{color:#fff;font-weight:700}
/* the RDA wordmark, inline, inherits white; -3.6px sits its glyph baseline (167.9 of the 225.5 viewBox)
   on the footer text baseline so "App · by Red Door Analytics" reads as one line */
/* the RDA wordmark, inline, inherits white. -4.4px: its glyph baseline (167.9 of the 225.5 viewBox) sits
   ~1px BELOW the lowercase "by" baseline — optically balanced, since the wordmark's caps otherwise read
   high against lowercase text (a pure baseline match, -3.6px, looked slightly too high). */
.rda-wm{height:14px;width:auto;color:#fff;display:inline-block;vertical-align:-4.4px;margin:0 2px}
/* padding, not min-height: vertical padding on an INLINE box grows the hit area without
   touching the line box, so the footer sentence does not move. 14px wordmark + 2x6 = 26. */
.rda-link{display:inline;cursor:pointer;text-decoration:none;padding:6px 2px;transition:opacity var(--t2) var(--ease)}
.rda-link:hover .rda-wm{opacity:.7}
.appfoot .footright{display:inline-flex;gap:18px;align-items:center}
/* ── footer contrast ───────────────────────────────────────────────────────────────────────
   These three sat at .38-.50 white on the footer bar and measured 3.17-4.39:1, under the 4.5
   AA minimum for text this size, in BOTH themes — the bar is the same navy either way, so the
   failure was not a dark-mode bug, it was there all along in both. Measured on the actual
   composited background rather than guessed: .50 reaches 4.5 in dark and .51 in light, so .52
   clears both with a little room. */
.appfoot .footmeta{font-family:var(--mono);font-size:10.5px;letter-spacing:.04em;color:rgba(255,255,255,.52)}
.appfoot .footlink{background:none;border:none;font:inherit;font-size:12px;color:rgba(255,255,255,.7);font-weight:600;cursor:pointer;padding:0}
.appfoot .footlink:hover{color:#fff}
.appfoot .footver{background:none;border:none;padding:0;font-family:var(--mono);font-size:11px;color:rgba(255,255,255,.52);cursor:pointer;letter-spacing:.02em}
.appfoot .footver:hover{color:rgba(255,255,255,.75)}

/* ── target size in the footer ────────────────────────────────────────────────────────────────
   SC 2.5.8 wants 24x24 CSS px. The version button rendered 14x13 and the footer links about
   40x15 — they are flex children of .footright, standalone controls rather than links inside a
   sentence, so the "inline" exemption does not reach them.

   The hit area is enlarged, NOT the box. Giving them min-height:24px would work and would make the
   footer bar ~9px taller in three apps at once, one of which has a pixel-for-pixel baseline; the
   success criterion is about the area that responds to a pointer, and an absolutely positioned
   ::after inside the button is exactly that. Zero visual change, real target.

   The 18px flex gap is wider than the 5px each side this adds, so no two targets overlap.

   .rda-link is genuinely inline in "App · by Red Door Analytics" and so IS exempt — it gets the
   same treatment anyway, because vertical padding on an inline box grows the hit area without
   touching the line box, and a 14px-tall wordmark is a poor target whatever the spec allows. */
.appfoot .footver, .appfoot .footlink{position:relative}
.appfoot .footver::after, .appfoot .footlink::after{content:"";position:absolute;
  top:50%;left:50%;transform:translate(-50%,-50%);
  min-width:24px;width:100%;height:24px}
.encstate{display:inline-flex;align-items:center;gap:6px;margin-left:10px}
.encstate::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--ghost);display:inline-block}
.encstate.ok::before{background:var(--green)}.encstate.warn::before{background:var(--amber)}

/* avatar primitive — the account chip's initials disc (the topbar sizes .acctbtn .av, above) */
.av{width:32px;height:32px;flex:0 0 32px;border-radius:50%;background:var(--brand);color:#fff;
  display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600}
.av.partner{background:var(--surface);color:var(--navy);border:1.5px dashed var(--rule-blue)}
/* nav count badge — a small red pill on a nav item (notifications / attention) */
.navbadge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:15px;padding:0 4px;
  margin-left:6px;font-family:var(--mono);font-size:9.5px;font-weight:500;border-radius:999px;background:var(--red);color:#fff}
.navbadge[hidden]{display:none}

/* boot splash — an app glyph on the brand, masking first paint (theme/edition already resolved) until
   the app mounts, then .gone fades it out. The app supplies the mark + word; the mechanism is shared. */
@keyframes bootPulse{0%,100%{opacity:.5;transform:scale(.96)}50%{opacity:1;transform:scale(1)}}
#boot{position:fixed;inset:0;z-index:200;background:var(--brand);display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:20px;transition:opacity var(--t4) var(--ease);opacity:1}
#boot.gone{opacity:0;pointer-events:none}
#boot .boot-mark{width:112px;height:auto;animation:bootPulse 1.4s ease-in-out infinite}
#boot .boot-word{font-family:var(--font);font-weight:700;font-size:46px;color:#fff;letter-spacing:.005em;line-height:1}
#boot .boot-sub{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.22em;color:rgba(255,255,255,.62)}
@media (prefers-reduced-motion: reduce){#boot .boot-mark{animation:none}}

/* mobile shell (≤820px) — the topnav collapses to a hamburger-triggered drawer; dropdown groups flatten
   to inline lists; the footer stacks. Feature views set their own breakpoints — this is the shell only. */
.navtoggle{display:none;background:none;border:none;color:#fff;font-size:23px;line-height:1;cursor:pointer;padding:4px 8px}
@media(max-width:820px){
  .topbar{padding:0 14px;gap:10px}
  .brand-text{display:flex;flex-direction:column;align-items:flex-start;line-height:1.15}
  .brand-text .lab-badge{margin-left:0;margin-top:2px}
  .navtoggle{display:block}
  .topnav{position:absolute;top:54px;left:0;right:0;display:none;flex-direction:column;align-items:stretch;
    height:auto;max-height:calc(100dvh - 54px);overflow-y:auto;-webkit-overflow-scrolling:touch;
    gap:0;padding:6px 0;background:var(--nav-ink);box-shadow:0 14px 28px rgba(0,0,0,.28);z-index:60}
  /* max-height + overflow: without them the open drawer ran to its natural height with no way to
     scroll — on phone-height screens everything below the first groups was simply unreachable
     (found live in Lemma, 2026-08-13; every app's drawer shares this rule). */
  .topnav.open{display:flex}
  .topnav .navbtn{height:auto;justify-content:flex-start;padding:13px 22px;font-size:13px;border-radius:0}
  /* in the drawer the home row says its name, like every other row */
  .topnav .navhome{gap:10px}
  .topnav .navhome-lbl{display:inline}
  .topnav .navbtn.on{background:rgba(255,255,255,.08)}
  .navdrop{height:auto;display:flex;flex-direction:column;align-items:stretch}
  .navchev{display:none}
  .navdrop-panel{position:static;display:flex;transform:none;box-shadow:none;border-radius:0;border:none;
    background:transparent;padding:0;gap:0;min-width:0;max-width:none}
  .navdrop-item{padding:11px 32px!important;border-radius:0;color:rgba(255,255,255,.78)}
  .navdrop-item .nd-ic{display:none}
  .navdrop-item .nd-tx small{display:none}
  .navdrop-item .nd-tx b{color:inherit;font-weight:inherit;font-size:inherit}
  .navdrop-item:hover{background:rgba(255,255,255,.06);color:#fff}
  .navdrop-item.on{background:rgba(255,255,255,.08);color:#fff}
  main{padding:0 14px 56px}
  .appfoot{flex-direction:column;gap:10px;padding:16px 14px 18px;text-align:center}
  .appfoot .footright{flex-wrap:wrap;justify-content:center;gap:10px 16px}
}
/* THE SAME DRAWER WHEN THE BAR IS TOO TIGHT FOR ITS NAV, at any width (L-33). suite-kit-navfit.js
   measures the bar and sets html.sk-nav-tight; between 820px and the width a nav needs, the page used
   to scroll sideways instead. Only the BAR's rules are twinned — the page's padding and the footer's
   stacking belong to narrow screens, not to a long nav. ⚠ Keep these identical to their ≤820px
   originals above: tests/test_nav_fit.py compares them rule for rule. */
html.sk-nav-tight .topbar{padding:0 14px;gap:10px}
html.sk-nav-tight .brand-text{display:flex;flex-direction:column;align-items:flex-start;line-height:1.15}
html.sk-nav-tight .brand-text .lab-badge{margin-left:0;margin-top:2px}
html.sk-nav-tight .navtoggle{display:block}
html.sk-nav-tight .topnav{position:absolute;top:54px;left:0;right:0;display:none;flex-direction:column;align-items:stretch;
  height:auto;max-height:calc(100dvh - 54px);overflow-y:auto;-webkit-overflow-scrolling:touch;
  gap:0;padding:6px 0;background:var(--nav-ink);box-shadow:0 14px 28px rgba(0,0,0,.28);z-index:60}
html.sk-nav-tight .topnav.open{display:flex}
html.sk-nav-tight .topnav .navbtn{height:auto;justify-content:flex-start;padding:13px 22px;font-size:13px;border-radius:0}
html.sk-nav-tight .topnav .navhome{gap:10px}
html.sk-nav-tight .topnav .navhome-lbl{display:inline}
html.sk-nav-tight .topnav .navbtn.on{background:rgba(255,255,255,.08)}
html.sk-nav-tight .navdrop{height:auto;display:flex;flex-direction:column;align-items:stretch}
html.sk-nav-tight .navchev{display:none}
html.sk-nav-tight .navdrop-panel{position:static;display:flex;transform:none;box-shadow:none;border-radius:0;border:none;
  background:transparent;padding:0;gap:0;min-width:0;max-width:none}
html.sk-nav-tight .navdrop-item{padding:11px 32px!important;border-radius:0;color:rgba(255,255,255,.78)}
html.sk-nav-tight .navdrop-item .nd-ic{display:none}
html.sk-nav-tight .navdrop-item .nd-tx small{display:none}
html.sk-nav-tight .navdrop-item .nd-tx b{color:inherit;font-weight:inherit;font-size:inherit}
html.sk-nav-tight .navdrop-item:hover{background:rgba(255,255,255,.06);color:#fff}
html.sk-nav-tight .navdrop-item.on{background:rgba(255,255,255,.08);color:#fff}

/* ── dark theme — a real token remap (not a filter): surfaces, ink scale, lines, shadows, and a
   brightened red so actions/links keep contrast on dark panels. --brand keeps each edition's hue for
   the chrome; an app wanting a neutral topbar in dark overrides --nav-ink itself. The app stamps
   data-theme on <html> pre-paint (see the shell), so first paint is already themed and a user pref
   beats the OS — hence an attribute, not a media query. Apps converge onto this instead of re-deriving
   it (that was the drift). */
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{
  color-scheme:dark;
  --bg:#13151C; --surface:#1B1E28; --surface-2:#171A22; --field:#232734;
  --card-line:rgba(255,255,255,.055);
  --modal-veil:rgba(0,0,0,.6);
  --shadow-card:0 1px 2px rgba(0,0,0,.3),0 8px 28px rgba(0,0,0,.35);
  --shadow-card-hover:0 4px 10px rgba(0,0,0,.35),0 18px 44px rgba(0,0,0,.5);
  --brass:#CDA55C; --brass-2:#DEBC7E;
  --panel:#20242F; --rule:#2A2E3B; --rule-blue:#343A4C;
  --field-line:#66738C;   /* 3.11:1 on --field, 3.24:1 on --panel, 3.82:1 on --bg */
  --pill-ok:#5ED4A9; --pill-warn:#E2B564; --pill-muted:#AEB7C8;   /* see the .pill note */
  --focus-ring: var(--rda-focus, var(--accent, #7FA9E8));
  --ink:#E8EAF2; --navy:#E8EAF2; --navy-soft:#C9CEDD; --soft:#C4CADA;
  --muted:#98A0B4; --faint:#7C8499; --ghost:#5C6376;
  --blue:#7BA0DC; --blue-soft:#232B3D; --blue-panel:#2A3348;
  --amber-ink:#E2B564;
  /* --red is lightened in dark because it is mostly used as INK (failing figures, error text) and the
     brand red is unreadable on a dark ground. But the same token also FILLS the primary button, which
     turned every "Delete"/"Stop the booking" pill pink. Split the two roles: --red keeps the readable
     ink value, --red-fill stays the saturated brand red that white text sits on at any theme. */
  --red:color-mix(in srgb,var(--rda-red,#E72D3F) 45%,#FF9FC4);
  --red-dark:color-mix(in srgb,var(--rda-red,#E72D3F) 62%,#FF9FC4);
  --red-fill:var(--rda-red,#E72D3F);
  --red-fill-dark:var(--rda-red-dark,#C4002A);
  /* same two hues, lifted for a dark ground — the HUE is unchanged, so a reader's key survives the
     theme switch. Only lightness moves; identity does not. */
  --ch-1:#7BA0DC;
  --ch-2:#C77BA6;
  --ch-1-soft:#25324A;
  --ch-2-soft:#3A2635;
  --ch-ref:#98A0B4;
}
:root[data-theme="dark"]{scrollbar-color:#3A4052 transparent}
:root[data-theme="dark"] ::-webkit-scrollbar{width:10px;height:10px;background:transparent}
:root[data-theme="dark"] ::-webkit-scrollbar-track{background:transparent}
:root[data-theme="dark"] ::-webkit-scrollbar-thumb{background:#333949;border-radius:8px;border:2px solid transparent;background-clip:padding-box}
:root[data-theme="dark"] ::-webkit-scrollbar-thumb:hover{background:#414A5E;background-clip:padding-box}
:root[data-theme="dark"] .btn-ghost{background:rgba(255,255,255,.07)}
:root[data-theme="dark"] .pill.green{color:var(--pill-ok)}
:root[data-theme="dark"] .pill.amber{color:var(--pill-warn)}
:root[data-theme="dark"] .pill.muted{color:var(--pill-muted)}
:root[data-theme="dark"] .btn-ghost:hover{background:rgba(255,255,255,.12);color:var(--ink)}
:root[data-theme="dark"] .loadveil{background:rgba(19,21,28,.65)}
:root[data-theme="dark"] .kindtag{color:#171A22}   /* dark text on the lightened-in-dark brand fill */

/* ── top-right cluster order (Lemma's exact order: notifications · help · account) */
.topbar-right .notifwrap{order:1}.topbar-right .helpbtn{order:2}.topbar-right .acctwrap{order:3}
.acctgear{color:#fff;opacity:.9}
.acctbtn:hover .acctgear{opacity:1}

/* ── notification bell (promoted verbatim from Lemma) — a bell button with an unread badge and a panel;
   the panel opens on click, items highlight on hover, the ✕ reveals on item hover. */
.notifwrap{position:relative;display:flex;align-items:center}
.notifwrap[hidden]{display:none}
.bellbtn{position:relative}
.bellico{width:18px;height:18px}
.bellbadge{position:absolute;top:1px;right:1px;min-width:14px;height:14px;padding:0 3px;margin-left:0;font-size:9px}
.notif-panel{position:absolute;top:calc(100% + 6px);right:0;width:330px;max-height:62vh;overflow-y:auto;
  background:var(--surface);color:var(--navy);border-radius:14px;box-shadow:0 14px 36px rgba(21,45,73,.24);
  border:1px solid var(--card-line);z-index:120}
.notif-panel[hidden]{display:none}
.notif-head{display:flex;align-items:center;justify-content:space-between;padding:11px 14px;
  border-bottom:1px solid var(--rule);position:sticky;top:0;background:var(--surface);border-radius:12px 12px 0 0}
.notif-head b{font-size:13px}
.notif-allread{font-size:11.5px;color:var(--blue);cursor:pointer;background:none;border:none;padding:0;font-family:inherit;margin-left:12px}
.notif-allread:hover{text-decoration:underline}
.notif-list{display:flex;flex-direction:column}
.notif-item{display:flex;gap:9px;align-items:flex-start;text-align:left;width:100%;padding:10px 14px;
  background:none;border:none;border-bottom:1px solid var(--rule);cursor:pointer;font-family:inherit}
.notif-item:last-child{border-bottom:none}
.notif-item:hover{background:var(--panel)}
.notif-item.unread{background:var(--blue-soft)}
.notif-dot{flex:0 0 7px;width:7px;height:7px;border-radius:50%;margin-top:4px;background:transparent}
.notif-item.unread .notif-dot{background:var(--blue)}
.notif-b{display:flex;flex-direction:column;gap:2px;min-width:0}
.notif-title{font-size:12.5px;line-height:1.32;color:var(--navy)}
.notif-time{font-family:var(--mono);font-size:10px;color:var(--muted)}
.notif-empty{padding:20px 14px;color:var(--muted);font-size:12.5px;text-align:center}
@media (max-width:560px){.notif-panel{position:fixed;left:8px;right:8px;width:auto;top:54px}}

/* ── Settings + How-to as FULL PAGES (SuiteKit renders these into main) — a centred prose guide, the
   way Lemma's instructions page reads; Appearance (theme) lives on the Settings page. */
.sk-page{animation:fade var(--t2) var(--ease)}
.sk-pagehead{padding:28px 0 10px}
.sk-pagehead .eyebrow{font-family:var(--mono);font-size:10.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--brand);margin-bottom:8px}
.sk-pagehead h1{font-size:26px;font-weight:600;letter-spacing:-.02em;margin:0;color:var(--navy)}
.guide{max-width:820px;display:flex;flex-direction:column;gap:22px;padding-bottom:12px}
.guide-sec h3{margin:0 0 5px;font-size:15px;font-weight:600;color:var(--navy)}
.sk-prose{font-size:13.5px;color:var(--soft);line-height:1.6}
.sk-prose p{margin:.4em 0}
.sk-prose code{font-family:var(--mono);font-size:11.5px;background:var(--panel);border-radius:5px;padding:1px 5px}
.sk-setcard{display:flex;flex-direction:column;gap:9px;max-width:420px}
.sk-setcard h3{margin:0;font-size:14px;font-weight:600;color:var(--navy)}
.sk-setnote{margin:0;font-size:12.5px;color:var(--muted)}

/* ── responsive tables ─────────────────────────────────────────────────────────────────────────
   Any table in the main content scrolls horizontally on a narrow screen instead of clipping off the
   right edge. Every app inherits this; a feature view can still add finer per-table rules. */
@media(max-width:820px){ main table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch} }

/* ── mobile bottom tab bar ─────────────────────────────────────────────────────────────────────
   mountShell emits `.botbar` from the nav; CSS shows it ≤700px IN PLACE OF the hamburger, so phones get
   the primary destinations one tap away. It carries the chrome (--nav-ink, dark in both themes), so its
   colours are white-alpha rather than surface tokens — there is no light background under it either way. */
.botbar{display:none}
@media(max-width:700px){
  .navtoggle{display:none}
  .botbar{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:55;gap:2px;background:var(--nav-ink);
    padding:6px 6px calc(6px + env(safe-area-inset-bottom,0px));border-top:1px solid rgba(255,255,255,.07)}
  main{padding-bottom:8px}   /* the footer sits below main; give it, not main, the bar's clearance */
  .appfoot{margin-bottom:calc(62px + env(safe-area-inset-bottom,0px))}   /* clear the fixed bar so the footer shows */
  /* Lift the toast above the bar. It sits at bottom:26px with z-index 100 against the bar's 55, so on
     a phone it landed ON the tab bar — covering the labels, and reading as a rendering fault rather
     than as a message. Toasts are also where a failure is reported, which is the worst thing to have
     half-obscured. */
  #toast{bottom:calc(74px + env(safe-area-inset-bottom,0px))}
}
.botbtn{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;gap:3px;min-width:0;
  background:none;border:0;cursor:pointer;color:rgba(255,255,255,.6);font-family:inherit;font-size:10px;
  font-weight:600;letter-spacing:-.01em;padding:5px 2px;border-radius:9px;transition:color .14s ease}
.botbtn svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
.botbtn span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.botbtn.on,.botbtn:active{color:#fff}
.botbtn.on::before{content:"";position:absolute;top:0;left:30%;right:30%;height:2px;border-radius:2px;background:var(--accent-mid,var(--accent))}

/* ── "What's new" changelog (footer version button → SuiteKit.whatsNew modal) ──────────────────────
   ONE design for the whole suite, modelled on Lemma's: a panel of releases (version heading · bullets),
   drawn in kit tokens so it takes each app's palette — --navy headings, --soft body, --brand accent. */
.sk-clpanel{background:var(--panel);border:1px solid var(--rule);border-radius:12px;padding:14px;max-height:62vh;overflow:auto}
.sk-clh{font-weight:700;color:var(--navy);font-size:14px;margin:16px 0 6px}
.sk-clh:first-child{margin-top:0}
.sk-clsub{font-weight:600;color:var(--brand);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin:12px 0 4px}
.sk-clp{font-size:12.5px;color:var(--soft);line-height:1.5;margin:6px 0}
.sk-cllist{margin:4px 0;padding-left:18px}
.sk-cllist li{font-size:12.5px;color:var(--soft);line-height:1.5;margin:3px 0}
.sk-cllist li::marker{color:var(--brand)}                 /* the per-app accent touch */
.sk-clp b,.sk-cllist li b{color:var(--navy);font-weight:600}
/* The release's user-facing summary — one file, two audiences. It sits directly under the
   heading and carries the whole story for someone who does not build the app; the detail
   below it is free to be as specific as it likes. Styled as a callout on purpose: an entry
   that is MISSING one should look wrong at a glance, in the reader as well as in the file. */
.sk-clsum{margin:6px 0 8px;padding:8px 12px;border-left:3px solid var(--brand);
  background:var(--panel-2,rgba(0,0,0,.03));border-radius:0 8px 8px 0;
  font-size:13px;line-height:1.5;color:var(--navy)}
.sk-clsum b{font-weight:600}
.sk-cl-empty{font-size:13px;color:var(--muted)}
.sk-clmore{margin:14px 0 2px;padding-top:12px;border-top:1px solid var(--rule);text-align:center}
.sk-clmore-btn{font:inherit;font-size:12px;font-weight:600;padding:7px 15px;border-radius:8px;
  border:1px solid var(--rule);background:var(--surface);color:var(--brand);cursor:pointer;transition:border-color .12s,background .12s}
.sk-clmore-btn:hover{border-color:var(--brand);background:var(--panel)}

/* ── sign-in gate (SuiteKit.mountGate) ────────────────────────────────────────────────────────────
   ONE door for the whole suite, modelled on Lemma's: a full-viewport wash in the app's chrome hue and
   a centred card. Drawn in kit tokens, so it takes each app's palette and flips with the theme for
   free — the card is --surface, which is already dark in dark mode.

   The primary action stays RDA red rather than the app accent: the kit reserves red for the primary
   action, and before you are signed in you are at the SUITE's door. The MARK on the card is the app's
   own, which is what tells you which room is behind it. */
.sk-gate{position:fixed;inset:0;z-index:210;display:flex;align-items:center;justify-content:center;padding:20px;
  background:var(--hero-1)}
@supports (background:color-mix(in srgb,red 50%,blue)){
  .sk-gate{background:linear-gradient(160deg,var(--hero-1),color-mix(in srgb,var(--hero-1) 76%,#000))}
}
.sk-gate-card{background:var(--surface);border:1px solid var(--card-line);border-radius:14px;
  padding:30px 32px 34px;width:440px;max-width:94vw;box-shadow:0 24px 60px rgba(21,45,73,.30)}
.sk-gate-brand{display:flex;align-items:center;gap:10px;margin-bottom:22px}
.sk-gate-brand svg{width:30px;height:30px;color:var(--brand);fill:currentColor}
.sk-gate-brand span{font-family:var(--font);font-weight:700;font-size:22px;color:var(--navy);letter-spacing:.005em}
.sk-gate-card h2{margin:0 0 4px;font-size:24px;font-weight:500;letter-spacing:-.02em;color:var(--navy)}
.sk-gate-sub{margin:0 0 20px;color:var(--muted);font-size:14px;line-height:1.5}
.sk-gate-card label{display:block;font-weight:600;font-size:10px;text-transform:uppercase;
  letter-spacing:.09em;color:var(--faint);margin:14px 0 5px}
.sk-gate-card input,.sk-gate-card select{width:100%;font-family:var(--font);font-size:14px;padding:9px 11px;
  background:var(--field);color:var(--ink);border:1px solid var(--rule-blue);border-radius:8px}
.sk-gate-card input:focus,.sk-gate-card select:focus{outline:none;border-color:var(--brand)}
.sk-gate-go{width:100%;margin-top:18px;padding:12px;font-size:13px}
.sk-gate-rule{display:flex;align-items:center;gap:12px;margin:22px 0 4px;color:var(--ghost);
  font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;font-weight:600}
.sk-gate-rule::before,.sk-gate-rule::after{content:"";flex:1;height:1px;background:var(--rule-blue)}
/* The outcome notice. `warn` (amber) is for OUR failures — an unreachable service is not the person's
   fault and must not be dressed as a refusal; `bad` (red) is for a genuine refusal. */
.sk-gate-note{margin:2px 0 14px;padding:10px 12px;border-radius:9px;font-size:13px;line-height:1.45;
  background:var(--panel);color:var(--soft);border:1px solid var(--rule)}
.sk-gate-note.bad{color:var(--red);border-color:var(--red)}
.sk-gate-note.warn{color:var(--soft);border-color:var(--amber)}

/* ============ polish pass (2026-07-31) ============ */
/* Keyboard focus is visible on EVERYTHING interactive the kit draws — zero-specificity, so an
   app's own focus treatment always wins. Before this, kit chrome (nav, bell, account menu,
   footer links) had no ring at all. */
:where(a,button,[tabindex="0"],select,input[type="checkbox"]):focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
/* The chrome is the one ground `--focus-ring` cannot work on. It resolves to `--accent`, and the
   topbar, the footer and the mobile tab bar are all painted `--nav-ink` — the brand at 72% black,
   DARK IN BOTH THEMES. Measured in Theorem: 1.86:1, against WCAG 1.4.11's 3:1, so a keyboard user
   tabbing through the chrome could not tell which of six buttons they were on. Light theme ONLY —
   dark measured 5.69:1, which is exactly why it survived every review done on a dark-mode machine.
   `--accent-mid` is the app's light-on-chrome ink and is already what `.botbtn.on::before` uses on
   this same ground (Theorem 4.73:1, Academy 5.12:1 — both apps measure it). White is the fallback
   for an app that has not defined one: maximum contrast, never worse than what it replaces. */
.topbar,.botbar,.appfoot{--focus-ring:var(--accent-mid,#FFFFFF)}
/* ...but the account menu and the notification panel are `--surface` panels that live INSIDE
   the topbar, so a custom property set on the chrome inherits straight into them: the ring
   would land on a LIGHT background, at 2.3:1 in Theorem and WHITE ON WHITE in any app that has
   not defined `--accent-mid`. Put the page's own ring back wherever the chrome opens a panel. */
.acct-panel,.notif-panel{--focus-ring:var(--rda-focus,var(--accent,#2F5FA8))}
/* .seg{overflow:hidden} clips the ring out of existence on a segmented control — which in Academy
   is the Stata/R switch, the defining control of the product. Inset the ring rather than removing
   the clip, which is what gives the control its rounded ends. */
.seg button:focus-visible{outline-offset:-2px}
/* Settings sections are CARDS (Lemma's settings language): surface, hairline, soft shadow —
   and the page lays them in a responsive grid instead of one bare column. */
.guide:has(.sk-setcard){display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;align-items:start;max-width:960px}
.sk-setcard{background:var(--surface);border:1px solid var(--card-line);border-radius:16px;padding:18px 20px;box-shadow:var(--shadow-card);max-width:none}
.sk-setcard h3{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
/* Guide prose keeps a reading measure on wide screens */
.guide-sec{max-width:68ch}
