/* Cyber Academy — base: faces, reset, document typography, role classes, links, focus, print.
 *
 * Values come from tokens.css; what each role class is for is the visual contract's decision
 * (`notes/design-rulebook.md`, typography). Nothing here is mode-specific: the mode tokens make it so.
 *
 * FACES. Ten self-hosted WOFF2 files, two subsets each (Czech needs `latin-ext`); licences and provenance in
 * `fonts/README.md`. `unicode-range` is copied verbatim from the fontsource package, so a page that sets no Czech
 * letter never downloads the `latin-ext` file. Normal style only — the UI sets no italic, and `font-synthesis: none`
 * on the body stops the browser inventing one or a bold the families do not ship.
 */

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* == Reset and document ==
 * THE DOCUMENT IS THE ONE VERTICAL SCROLL CONTAINER, on every page and viewport. No `height: 100%` on html, body or
 * #app, and no `overflow` on main: the top bar, the sidebar, sticky table headers and the bulk bar are
 * `position: sticky` against the document, which also keeps browser find, scroll restoration and a phone's
 * retracting toolbar working. An inner scroller is what produced the phone double scrollbar. */
*, *::before, *::after { box-sizing: border-box; }

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

/* THE PAGE WIDTH NEVER CHANGES, AND ONLY A MODAL DIALOG LOCKS THE DOCUMENT (`client.ui.overlay`; the rulebook's
 * floating layers and scroll). Chromium honours the root's `scrollbar-gutter: stable` only while the page actually
 * overflows, so a short page and a long one differed by the scrollbar's width; `overflow-y: scroll` reserves the
 * gutter on every page, and the track is transparent (below), so a page with nothing to scroll shows an empty strip,
 * never a bar. React Aria writes `overflow: hidden` inline on the root whenever one of its popovers is modal — every
 * select, menu and popover — which stops the page scrolling and drops the gutter, so the root refuses every lock but
 * the dialog's own (`data-scroll-lock`), which gives back the measured width its scrollbar leaves. */
/* `overflow-x` is named only to clear the library's inline shorthand; beside `scroll` it computes to `auto`. */
html:not([data-scroll-lock]) { overflow-x: visible !important; overflow-y: scroll !important; }
html[data-scroll-lock] { overflow: hidden; padding-inline-end: var(--scroll-lock-gutter, 0px); }

/* A control focused by keyboard is scrolled clear of every sticky bar (tokens.css, sticky bars), with 8 px to
 * spare; a control in a table row is also kept clear of the sticky 40 px header above it. */
html { scroll-padding-top: calc(var(--sticky-top) + 8px); scroll-padding-bottom: calc(var(--sticky-bottom) + 8px); }

body {
  margin: 0;
  font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-sans);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  background: var(--bg);
  color: var(--text-1);
}

/* The mode root paints its own ground and ink, so a mode nested inside another (the customer area inside the
 * shell during the migration) never shows the outer mode's text colour on its own surface. */
[data-mode] { background: var(--bg); color: var(--text-1); }

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
img { max-width: 100%; }
svg { flex: none; }
table { border-collapse: collapse; }

/* Controls behave like controls: double-clicking one must not select its label. Prose, names and figures stay
 * selectable. */
button, .btn, .tag, .avatar, .choice, label.field > .field-label { -webkit-user-select: none; user-select: none; }

/* Figures that sit in columns or change in place keep their width. */
table, time, .tabular { font-variant-numeric: tabular-nums; }

/* == Role classes (`notes/design-rulebook.md`, typography) == */
.t-figure     { font: var(--weight-semibold) var(--text-28) / var(--lh-28) var(--font-sans); font-variant-numeric: tabular-nums; }
/* A title declares its line height as `--heading-lh` too, so a count badge in it can centre on the title's line. */
.t-title-1    { font: var(--weight-semibold) var(--text-24) / var(--lh-24) var(--font-sans); --heading-lh: var(--lh-24); }
.t-title-2    { font: var(--weight-semibold) var(--text-18) / var(--lh-18) var(--font-sans); --heading-lh: var(--lh-18); }
.t-title-3    { font: var(--weight-semibold) var(--text-16) / var(--lh-16) var(--font-sans); --heading-lh: var(--lh-16); }
.t-body-lg    { font: var(--weight-regular) var(--text-16) / var(--lh-16) var(--font-sans); }
.t-body       { font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-sans); }
.t-body-strong { font: var(--weight-medium) var(--text-14) / var(--lh-14) var(--font-sans); }
.t-small      { font: var(--weight-regular) var(--text-13) / var(--lh-13) var(--font-sans); }
.t-caption    { font: var(--weight-medium) var(--text-12) / var(--lh-12) var(--font-sans); letter-spacing: 0.01em; }
.t-mono       { font: var(--weight-regular) var(--text-14) / var(--lh-14) var(--font-mono); }
.t-code-input { font: var(--weight-medium) var(--text-24) / var(--lh-24) var(--font-mono); letter-spacing: 0.25em; }

/* The muted ink for a secondary sentence or placeholder that sits beside the facts it qualifies. */
.muted { color: var(--text-3); }

@media (max-width: 639px) {
  .t-figure  { font-size: var(--text-24); line-height: var(--lh-24); }
  .t-title-1 { font-size: 20px; line-height: 28px; --heading-lh: 28px; }
}

code, kbd, samp { font-family: var(--font-mono); font-size: 0.93em; }

/* == Selection ==
 * Selected text is highlighted in BOIT lime in every mode — the same token as the academy tile — with the tile's ink
 * on it (15.26:1). Never the browser's blue. */
::selection { background-color: var(--console-accent); color: var(--console-surface); }

/* == Links ==
 * A bare anchor is this product's link, never the browser's blue and visited purple: `--link` (operator: text-1;
 * customer: the validated brand secondary; neutral: ink), underlined. `:where()` keeps the rule at the specificity
 * of a bare `a`, so every classed anchor — a button-shaped link, a nav item, the lockup — keeps its own colour. */
:where(a) { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
:where(a):visited { color: var(--link); }

/* == Focus ==
 * Keyboard focus on a control is a LIFT, a SURFACE SHIFT, a RESTRAINED GLOW and the FOCUS DASH — never a ring, an
 * outline, a border or anything that boxes the control (`notes/design-rulebook.md`, components). The dash is the
 * measured 3:1 cue: a detached 2 px pill, 12–16 px long, never as long as a side of the control; the other three make
 * the state unmistakable without framing it.
 *
 * ONE RECIPE PER CONTROL CLASS, bound here once. A component opts in with `data-focus`:
 *   filled  primary and destructive buttons: the fill steps to its hover colour, the fill-tinted lift, the glow, a
 *           dash in the button's own ink (≥ 4.5:1 on the fill by the brand validation, so ≥ 3:1)
 *   raised  secondary buttons and other raised controls: the fill lightens, one elevation step, the glow, the dash
 *   ghost   ghost and icon buttons: the hover fill and a short dash under the content; no glow
 *   item    menu items, sidebar items, account rows: the hover fill and a dash at the content start
 *   well    text inputs, selects, dropdown triggers: NOT drawn here. An input is recessed into its holder and its
 *           focus is the recess deepening onto the surface beneath and its label taking the focus colour
 *           (components.css, the input rule); it never takes a dash, a lift, a glow or any external shadow, and
 *           nothing in this file draws one on `.control`, `.input` or `[data-focus="well"]`
 *   check   checkboxes and radios: a lift and the glow; a short dash under the box
 *   segment segmented items: the unselected segment gains a half-step fill; the dash under the label
 *   link    links in text: a 2 px underline in the focus colour, 3 px below the text — no fill, no block
 *   text    text-only controls (a heading with help, a disclosure summary): the dash alone, in the focus colour, just
 *           under the text — no fill, no underline
 *   icon    a bare glyph that carries a tooltip: the glyph darkens on a round focus tint — no dash, which a 14-20 px
 *           icon cannot hold cleanly
 * A component sets only the dash geometry (`--dash-x`, `--dash-bottom`, `--dash-w`, `--dash-color`) and the fills
 * its recipe reads (`--fill-hover`, `--fill-shadow-2`), never a focus rule of its own.
 *
 * Hover never shows the dash, so hover and focus differ in kind as well as degree. Mouse focus on these controls
 * shows nothing: no rule here reads `:focus:not(:focus-visible)`. (An input is different: it is a typing target, and
 * its recess deepens on any focus, from a click too — components.css, the input rule.) A disabled control that stays
 * focusable to explain itself shows the dash alone. A heading or a status line that takes focus by script is not a
 * control and draws nothing.
 *
 * A visually hidden native input (a file picker) holds the focus for the control that is its visible face, the
 * next sibling: `input.sr-only:focus-visible + [data-focus]` draws that face's recipe while the input has focus. */
:where([data-focus]) { position: relative; }

/* Forced colours paint this outline in a system colour; everywhere else it is invisible. */
:focus-visible { outline: 2px solid transparent; outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }

/* FALLBACKS for what has not opted into a recipe, so no focusable element is ever left without a perceivable cue.
 * Zero-specificity `:where()` throughout: any authored rule of the element's own wins over these.
 *   - A link takes the link recipe (below).
 *   - Any other button, disclosure summary or scripted tab stop takes the hover fill and a centred 12 px dash in its
 *     own text colour, which is ≥ 4.5:1 on its own fill wherever its text is compliant, so the dash is ≥ 3:1. The
 *     customer sidebar's items (`.sidebar__item`, links whose labels are hidden while the rail is collapsed) take
 *     this one too, until `client.ui.sidebar` opts into `item`.
 * No native colour swatch is drawn: a brand colour is edited in the colour editor's text fields
 * (`client.ui.colour-editor`), which are wells. */
:where(button, summary, [tabindex="0"], .sidebar__item):not([data-focus]) { position: relative; }
:where(button, summary, [tabindex="0"], .sidebar__item):not([data-focus]):focus-visible { background-color: var(--surface-hover); }
:where(button, summary, [tabindex="0"], .sidebar__item):not([data-focus]):focus-visible::after {
  content: "";
  position: absolute;
  left: var(--dash-x, 50%);
  bottom: var(--dash-bottom, 3px);
  width: var(--focus-dash-w-sm);
  height: var(--focus-dash-h);
  border-radius: var(--radius-full);
  background: currentColor;
  transform: translateX(var(--dash-shift, -50%));
  pointer-events: none;
  animation: focus-dash-in var(--dur-fast) var(--ease-out);
}
/*   - A bare input, select or textarea with none of the input and checkbox classes follows the input rule as far as
 *     it can without its formulas: the deeper recess on focus. */
:where(input, select, textarea):where(:not([data-focus], .control, .input, .check__box, .sr-only, [type="color"])):where(:focus) {
  box-shadow: var(--inset-focus);
}
h1[tabindex="-1"]:focus, h2[tabindex="-1"]:focus, [role="status"][tabindex="-1"]:focus { outline: none; }

/* The dash. */
[data-focus]:not([data-focus="well"]):not([data-focus="check"]):not([data-focus="link"]):not([data-focus="icon"]):focus-visible::after,
input.sr-only:focus-visible + [data-focus]::after,
:is([data-focus="check"], .check__box):focus-visible::before {
  content: "";
  position: absolute;
  left: var(--dash-x, 50%);
  bottom: var(--dash-bottom, 4px);
  width: var(--dash-w, var(--focus-dash-w));
  height: var(--focus-dash-h);
  border-radius: var(--radius-full);
  background: var(--dash-color, var(--focus-color));
  transform: translateX(var(--dash-shift, -50%));
  pointer-events: none;
  animation: focus-dash-in var(--dur-fast) var(--ease-out);
}
@keyframes focus-dash-in { from { opacity: 0; transform: translateX(var(--dash-shift, -50%)) scaleX(0.4); } }

[data-focus="filled"]  { --dash-color: currentColor; --dash-bottom: 5px; }
[data-focus="ghost"]   { --dash-w: var(--focus-dash-w-sm); --dash-bottom: 3px; }
[data-focus="item"]    { --dash-bottom: 3px; }   /* centred in the row or square, as every dash is */
[data-focus="segment"] { --dash-bottom: 3px; }
[data-focus="text"]    { --dash-bottom: -5px; }
:is([data-focus="check"], .check__box) { --dash-w: var(--focus-dash-w-sm); --dash-bottom: auto; }
:is([data-focus="check"], .check__box):focus-visible::before { top: calc(100% + var(--focus-dash-gap)); }

/* The state each recipe adds. `:not([aria-disabled="true"])`: a control disabled with a reason keeps its rest look
 * and shows only the dash, in the focus colour, since its disabled ink is too faint to carry 3:1. */
[data-focus="filled"]:not([aria-disabled="true"]):focus-visible {
  background-color: var(--fill-hover);
  box-shadow: var(--fill-shadow-2), var(--focus-glow);
}
[data-focus="raised"]:not([aria-disabled="true"]):focus-visible,
input.sr-only:focus-visible + [data-focus="raised"] {
  background-color: var(--control-fill-hover);
  box-shadow: var(--elev-2), var(--focus-glow);
}
[data-focus="ghost"]:not([aria-disabled="true"]):focus-visible,
[data-focus="item"]:not([aria-disabled="true"]):focus-visible { background-color: var(--surface-hover); }
[data-focus="icon"] { border-radius: var(--radius-full); transition: color var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard); }
[data-focus="icon"]:focus-visible { color: var(--text-1); background-color: var(--focus-tint); }
:is([data-focus="check"], .check__box):focus-visible { box-shadow: var(--elev-1), var(--focus-glow); }
[data-focus="segment"]:not([aria-selected="true"]):not([aria-checked="true"]):focus-visible {
  background-color: color-mix(in srgb, var(--surface) 50%, var(--surface-2));
}
/* A focused selected segment already has the thumb's fill; it lifts a step so focus reads at a glance. */
[data-focus="segment"]:is([aria-selected="true"], [aria-checked="true"]):focus-visible { box-shadow: var(--elev-2); }
[data-focus][aria-disabled="true"]:focus-visible { --dash-color: var(--focus-color); }
:where(a:not(.sidebar__item)):not([data-focus]):focus-visible,
[data-focus="link"]:focus-visible {
  text-decoration-line: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-color: var(--focus-color);
  transition: text-decoration-color var(--dur-fast) var(--ease-standard);
}

/* == Scrollbars ==
 * ONE QUIET SCROLLBAR, EVERYWHERE THE PRODUCT SCROLLS: the document, `.scroll-region`, a dialog body, the sidebar's
 * list, a popover — any scroller, so none falls back to the platform's chrome. A transparent 8 px gutter holds a
 * rounded thumb drawn 4 px at rest and 6 px under the pointer or while dragged (the thumb's transparent border is the
 * spacer, `background-clip: padding-box`), so it reads as part of the surface; its colour stays ≥ 3:1 at rest and
 * strengthens under the pointer and while dragged. The hit area is the whole gutter whatever the drawn width. There
 * is no CSS hook for "is scrolling". The Firefox properties sit behind `@supports`: Chromium 121+ ignores
 * `::-webkit-scrollbar` on any element that also sets the standard ones. Forced colours keep the platform's own
 * scrollbars, and overlay-scrollbar platforms ignore all of it. `.scroll-region` remains the class a deliberate
 * inner scroller uses, and it also contains its overscroll. */
.scroll-region { overflow: auto; overscroll-behavior: contain; }
@media not (forced-colors: active) {
  ::-webkit-scrollbar { width: var(--scrollbar-size); height: var(--scrollbar-size); }
  ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
  ::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: var(--radius-full);
    background: var(--scrollbar-thumb) padding-box;
  }
  ::-webkit-scrollbar-thumb:hover { border-width: 1px; background-color: var(--scrollbar-thumb-hover); }
  ::-webkit-scrollbar-thumb:active { border-width: 1px; background-color: var(--scrollbar-thumb-active); }
  @supports (-moz-appearance: none) {
    * { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) transparent; }
    :hover { scrollbar-color: var(--scrollbar-thumb-hover) transparent; }
  }
}

/* == Forced colours ==
 * Shadows and fills disappear, so the focus outline above is painted in the system highlight; the boundaries a
 * control needs to be found at all are components.css's. */
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
}

/* == Visually hidden, still announced == */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* == Reduced motion ==
 * The duration tokens already fall to 0 under `prefers-reduced-motion`; this catches any animation or transition
 * that does not read them, including a browser default. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* == Print ==
 * Cosmetic only — the audited report is the server export. Chrome and controls drop out; a chart prints with its
 * data table. */
@media print {
  .sidebar, .mobile-topbar, .no-print, .bulk-bar { display: none !important; }
  .chart-card__table { display: block !important; }
  .chart-card__plot { display: block !important; }
  .chart-card__aside > .icon-btn, .page-header__actions { display: none !important; }
  .chart-card, .stat-tile, .report-activity { break-inside: avoid; }
  .charts-grid { grid-template-columns: minmax(0, 1fr) !important; }
  body, [data-mode] { background: #FFFFFF; color: #000000; }
}
