/* ============================================================================================
   Speecon UI kit (REL-11, CR-1) — ONE shared stylesheet every tool loads.
   Single source of truth for the product look (GOAL §3.2): colour, type, spacing and every shared
   component live here, not copy-pasted into each tool's page.

   CR-1 (owner 2026-11-07): the look is taken from the Speecon company website (speecon.com) — a warm
   off-white page, an ink near-black, a coral accent and a yellow "sun" highlight, with Anton (display,
   uppercase), Inter (body/UI) and a Fraunces italic serif eyebrow. It is tuned for dense WORKING
   screens — calm surfaces, readable tables, clear state — not a marketing page.

   Theming: tokens are CSS custom properties on :root. Dark mode follows the OS by default and can be
   pinned per-page with data-theme="light" | "dark" on <html> (the exact pattern the operator console
   already used, so its toggle keeps working). The dark theme is derived from the website's dark bands
   (ink background, ink-surface cards, sun as the primary accent). Load fonts.css BEFORE this file.

   Accessibility: body text and status text meet WCAG AA (>=4.5:1) on their own surface in both themes
   (ratios recorded in ui/README.md). The coral accent (--ui-coral) is DECORATIVE only — underlines,
   rules, the waveform mark; it fails AA as text, so text/links/buttons use --ui-coral-text instead.
   Status is NEVER carried by colour alone (pills/alerts carry a word, not just a hue).
   ============================================================================================ */

/* ---- Design tokens — LIGHT (default) ------------------------------------------------------- */
:root {
  /* surfaces + ink (website: page #fbfaf7, cream hero band, white card, ink near-black) */
  --ui-bg: #fbfaf7;            /* app background — the website's warm off-white page */
  --ui-surface: #ffffff;       /* cards, panels, the sheet content sits on */
  --ui-surface-2: #faf3e3;     /* cream warm fill: table headers, tracks, inset rows */
  --ui-surface-3: #f1ead9;     /* deeper warm fill: hover, selected track */
  --ui-cream: #faf3e3;         /* the website's hero band / tinted card */
  --ui-ink: #14161a;           /* primary text — near-black */
  --ui-ink-muted: #5a6470;     /* secondary / body text (>=4.5:1 on --ui-bg and --ui-surface) */
  --ui-ink-faint: #868f9b;     /* decorative / placeholder / large text only */
  --ui-line: #14161a1a;        /* hairline borders (ink @ 10%) */
  --ui-line-soft: #14161a14;   /* softest divider (ink @ 8%) */
  --ui-line-strong: #14161a33; /* emphasised borders: inputs, chips (ink @ 20%) */
  /* An OPAQUE hairline. Needed wherever alpha hex cannot be used: the print/offline report render
     (xhtml2pdf's CSS parser, see ui/inline.py) and raster plots. Visually matches --ui-line. */
  --ui-line-solid: #e3e0d8;
  --ui-paper: #ffffff;         /* white in BOTH themes — the canvas of a raster plot a tool embeds */

  /* brand — the website palette */
  --ui-coral: #e8734a;          /* DECORATIVE accent only: active underline, rule lines, waveform — NOT text */
  --ui-coral-text: #b84e2c;     /* eyebrows, links, primary-button background — AA as text */
  --ui-coral-text-hover: #a34424;
  --ui-sun: #ffd400;            /* highlight marker behind display words; primary button on dark bands */
  --ui-sun-hover: #ffe04d;
  /* Text ON the sun. The sun itself does NOT flip for dark (it is the brand yellow in both themes),
     so its text must not flip either: --ui-ink goes light in dark mode and becomes unreadable on
     yellow (found by the PM while publishing the design system, REL-11 → REL-12). 12.7:1 either way. */
  --ui-on-sun: #14161a;
  --ui-ink-surface: #171c22;    /* card surface inside a dark (ink) band */
  --ui-on-dark: #e8eaed;        /* text on a dark band */
  --ui-on-dark-muted: #9aa3ad;  /* secondary text on a dark band */
  --ui-on-dark-faint: #808a96;  /* faint text on a dark band (large/decorative) */
  --ui-eyebrow: var(--ui-coral-text); /* the small uppercase / serif-italic eyebrow colour */

  /* legacy accent aliases kept stable so existing tools need no token rename. In CR-1 the single
     brand accent is coral; the filled primary action flips to the sun on dark (see the dark block). */
  --ui-brand: #14161a;          /* the ink brand: dark bands, footer, the wordmark colour */
  --ui-brand-ink: #ffffff;      /* text on a dark brand band */
  --ui-accent: var(--ui-coral-text);        /* link / active colour */
  --ui-accent-fill: var(--ui-coral-text);   /* primary button background (coral on light) */
  --ui-accent-fill-hover: var(--ui-coral-text-hover);  /* …and its hover (the SUN's hover on dark) */
  --ui-accent-contrast: #ffffff;            /* text on the primary button */
  --ui-on-brand-link: #ffffff;              /* a link on a dark brand band */
  --ui-on-brand-muted: var(--ui-on-dark-muted);

  /* header (website: light, sticky, blurred, 1px bottom line) */
  --ui-header-bg: #fbfaf7;
  --ui-header-ink: #14161a;

  /* semantic status — deliberately separate from the brand accent (a rebrand never moves "error") */
  --ui-ok: #1e7a46;      --ui-ok-bg: #e3f1e8;      --ui-ok-ink: #0f5a30;
  --ui-warn: #8a5a00;    --ui-warn-bg: #f6ecd4;    --ui-warn-ink: #6b4700;
  --ui-danger: #b42318;  --ui-danger-bg: #fbe7e4;  --ui-danger-ink: #8a1c13;
  --ui-info: #1c5ba8;    --ui-info-bg: #e6eef8;    --ui-info-ink: #164c8c;
  --ui-synth: #4b4fa6;   --ui-synth-bg: #e8e9f6;   --ui-synth-ink: #343a86;  /* synthetic probe */
  --ui-real: var(--ui-ok); --ui-real-bg: var(--ui-ok-bg); --ui-real-ink: var(--ui-ok-ink);
  --ui-focus: var(--ui-coral-text);   /* focus ring (coral on light, sun on dark) */

  /* ---- DATA colours — a scale, NOT status (owner decision 2026-11-08, REL-12) ---------------
     The diagnostics colour *domain data* (a works/weak/fails mosaic, a term-category overlay), which
     is not the semantic status set: a rebrand must not move "error", and a new category must not
     steal a status hue. This is the Okabe-Ito eight-colour qualitative palette (built for
     deuteranopia/protanopia), the one the audit / remix / tts / aug mosaics and the compare overlay
     already used — collected here so there is ONE copy (GOAL §3.2) instead of five. Hues are the
     tools' own; dark mode lifts the ones that go muddy on black.
     The RGB triplets are the source; the hex twins are composed from them, so each colour is written
     once. The triplets exist because an overlay needs per-mark alpha: rgb(var(--ui-data-rgb-blue) / 40%). */
  --ui-data-rgb-blue: 0 114 178;        --ui-data-blue: rgb(var(--ui-data-rgb-blue));
  --ui-data-rgb-orange: 230 159 0;      --ui-data-orange: rgb(var(--ui-data-rgb-orange));
  --ui-data-rgb-green: 0 158 115;       --ui-data-green: rgb(var(--ui-data-rgb-green));
  --ui-data-rgb-purple: 204 121 167;    --ui-data-purple: rgb(var(--ui-data-rgb-purple));
  --ui-data-rgb-sky: 86 180 233;        --ui-data-sky: rgb(var(--ui-data-rgb-sky));
  --ui-data-rgb-vermillion: 213 94 0;   --ui-data-vermillion: rgb(var(--ui-data-rgb-vermillion));
  --ui-data-rgb-yellow: 150 122 0;      --ui-data-yellow: rgb(var(--ui-data-rgb-yellow));
  --ui-data-rgb-slate: 100 112 128;     --ui-data-slate: rgb(var(--ui-data-rgb-slate));
  --ui-data-rgb-none: 145 145 145;      --ui-data-none: rgb(var(--ui-data-rgb-none));
  /* the neutral "marked here" ink an overlay draws its patterns in (a pattern, not a hue) */
  --ui-data-rgb-ink: 20 20 24;          --ui-data-ink: rgb(var(--ui-data-rgb-ink));
  /* "measured, but absent" — a dotted/hatched tile, deliberately not a hue of the scale */
  --ui-data-absent: #9aa3b0;            --ui-data-absent-bg: #e7eaef;

  /* type (website: Anton display, Inter body, Fraunces italic serif eyebrow) */
  --ui-font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ui-font-display: "Anton", "Arial Narrow", "Helvetica Neue Condensed", sans-serif;
  --ui-font-serif: "Fraunces", Georgia, "Times New Roman", serif;
  --ui-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --ui-fs-xs: 0.75rem; --ui-fs-sm: 0.8125rem; --ui-fs-base: 0.9375rem; --ui-fs-md: 1.0625rem;
  --ui-fs-lg: 1.25rem; --ui-fs-xl: 1.5rem; --ui-fs-2xl: 2rem;
  --ui-lh: 1.5; --ui-lh-tight: 1.2;

  /* spacing / shape (website radii: buttons 10, cards 12, panels 16, pills 999) */
  --ui-sp-1: 0.25rem; --ui-sp-2: 0.5rem; --ui-sp-3: 0.75rem; --ui-sp-4: 1rem;
  --ui-sp-5: 1.5rem; --ui-sp-6: 2rem; --ui-sp-7: 3rem;
  --ui-r-sm: 8px; --ui-r-md: 10px; --ui-r-lg: 12px; --ui-r-panel: 16px; --ui-r-pill: 999px;
  --ui-shadow-sm: 0 1px 2px rgba(20,22,26,.05), 0 1px 3px rgba(20,22,26,.07);
  --ui-shadow-md: 0 14px 32px rgba(20,22,26,.12);

  color-scheme: light;
}

/* ---- Design tokens — DARK ------------------------------------------------------------------ */
/* Derived from the website's dark bands: ink background, ink-surface cards, on-dark text, the SUN as
   the filled primary, coral-text lightened to stay AA on ink. Applied when the OS prefers dark AND the
   page hasn't pinned light, OR when a page pins dark. The two selectors carry an identical body; both
   are needed so a page-level toggle can override the OS either way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ui-bg: #14161a; --ui-surface: #171c22; --ui-surface-2: #1e242c; --ui-surface-3: #262d36;
    --ui-cream: #1e242c;
    --ui-ink: #e8eaed; --ui-ink-muted: #9aa3ad; --ui-ink-faint: #808a96;
    --ui-line: #e8eaed1a; --ui-line-soft: #e8eaed12; --ui-line-strong: #e8eaed33; --ui-line-solid: #2b3139;
    --ui-coral: #ef8a5f; --ui-coral-text: #ef8a5f; --ui-coral-text-hover: #f3a07d;
    --ui-sun: #ffd400; --ui-sun-hover: #ffe04d;
    --ui-ink-surface: #1e242c; --ui-on-dark: #e8eaed; --ui-on-dark-muted: #9aa3ad; --ui-on-dark-faint: #808a96;
    --ui-brand: #0e1013; --ui-brand-ink: #e8eaed;
    --ui-accent: var(--ui-coral-text);
    --ui-accent-fill: var(--ui-sun); --ui-accent-fill-hover: var(--ui-sun-hover); --ui-accent-contrast: #14161a;  /* sun primary on dark, like the site's dark bands */
    --ui-on-brand-link: #e8eaed; --ui-on-brand-muted: #9aa3ad;
    --ui-header-bg: #14161a; --ui-header-ink: #e8eaed;
    --ui-ok: #5bc98a;   --ui-ok-bg: #16302a;   --ui-ok-ink: #86e0ac;
    --ui-warn: #e6b860; --ui-warn-bg: #332a16; --ui-warn-ink: #f0ce8c;
    --ui-danger: #f2857a; --ui-danger-bg: #37211f; --ui-danger-ink: #f7a69d;
    --ui-info: #6fa8e6; --ui-info-bg: #182a3f; --ui-info-ink: #9cc4f0;
    --ui-synth: #9ca0e6; --ui-synth-bg: #22243f; --ui-synth-ink: #c3c6f0;
    --ui-real: var(--ui-ok); --ui-real-bg: var(--ui-ok-bg); --ui-real-ink: var(--ui-ok-ink);
    --ui-focus: var(--ui-sun);
    /* data scale — lifted where the light hue goes muddy on an ink background (REL-12) */
    --ui-data-rgb-blue: 96 168 224; --ui-data-rgb-orange: 242 181 68; --ui-data-rgb-green: 64 196 157;
    --ui-data-rgb-purple: 226 154 196; --ui-data-rgb-sky: 134 205 247; --ui-data-rgb-vermillion: 255 122 69;
    --ui-data-rgb-yellow: 214 176 33; --ui-data-rgb-slate: 154 166 184; --ui-data-rgb-none: 150 150 150;
    --ui-data-rgb-ink: 245 246 250;
    --ui-data-absent: #6b7482; --ui-data-absent-bg: #232a33;
    --ui-shadow-sm: 0 1px 2px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.36);
    --ui-shadow-md: 0 6px 18px rgba(0,0,0,.44);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --ui-bg: #14161a; --ui-surface: #171c22; --ui-surface-2: #1e242c; --ui-surface-3: #262d36;
  --ui-cream: #1e242c;
  --ui-ink: #e8eaed; --ui-ink-muted: #9aa3ad; --ui-ink-faint: #808a96;
  --ui-line: #e8eaed1a; --ui-line-soft: #e8eaed12; --ui-line-strong: #e8eaed33; --ui-line-solid: #2b3139;
  --ui-coral: #ef8a5f; --ui-coral-text: #ef8a5f; --ui-coral-text-hover: #f3a07d;
  --ui-sun: #ffd400; --ui-sun-hover: #ffe04d;
  --ui-ink-surface: #1e242c; --ui-on-dark: #e8eaed; --ui-on-dark-muted: #9aa3ad; --ui-on-dark-faint: #808a96;
  --ui-brand: #0e1013; --ui-brand-ink: #e8eaed;
  --ui-accent: var(--ui-coral-text);
  --ui-accent-fill: var(--ui-sun); --ui-accent-fill-hover: var(--ui-sun-hover); --ui-accent-contrast: #14161a;
  --ui-on-brand-link: #e8eaed; --ui-on-brand-muted: #9aa3ad;
  --ui-header-bg: #14161a; --ui-header-ink: #e8eaed;
  --ui-ok: #5bc98a;   --ui-ok-bg: #16302a;   --ui-ok-ink: #86e0ac;
  --ui-warn: #e6b860; --ui-warn-bg: #332a16; --ui-warn-ink: #f0ce8c;
  --ui-danger: #f2857a; --ui-danger-bg: #37211f; --ui-danger-ink: #f7a69d;
  --ui-info: #6fa8e6; --ui-info-bg: #182a3f; --ui-info-ink: #9cc4f0;
  --ui-synth: #9ca0e6; --ui-synth-bg: #22243f; --ui-synth-ink: #c3c6f0;
  --ui-real: var(--ui-ok); --ui-real-bg: var(--ui-ok-bg); --ui-real-ink: var(--ui-ok-ink);
  --ui-focus: var(--ui-sun);
    /* data scale — lifted where the light hue goes muddy on an ink background (REL-12) */
    --ui-data-rgb-blue: 96 168 224; --ui-data-rgb-orange: 242 181 68; --ui-data-rgb-green: 64 196 157;
    --ui-data-rgb-purple: 226 154 196; --ui-data-rgb-sky: 134 205 247; --ui-data-rgb-vermillion: 255 122 69;
    --ui-data-rgb-yellow: 214 176 33; --ui-data-rgb-slate: 154 166 184; --ui-data-rgb-none: 150 150 150;
    --ui-data-rgb-ink: 245 246 250;
    --ui-data-absent: #6b7482; --ui-data-absent-bg: #232a33;
  --ui-shadow-sm: 0 1px 2px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.36);
  --ui-shadow-md: 0 6px 18px rgba(0,0,0,.44);
  color-scheme: dark;
}

/* ---- Base ---------------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: var(--ui-font-sans);
  font-size: var(--ui-fs-base);
  line-height: var(--ui-lh);
  color: var(--ui-ink);
  background: var(--ui-bg);
}
a { color: var(--ui-accent); text-underline-offset: 2px; }
a:hover { text-decoration: underline; }
/* Anton is the display face; it is a tall condensed poster face, so it is used for the H1 / product
   title only, with the smaller subheads in Inter for legibility on dense working screens. */
h1 { font-family: var(--ui-font-display); font-weight: 400; letter-spacing: .01em; line-height: var(--ui-lh-tight);
     color: var(--ui-ink); font-size: var(--ui-fs-xl); margin: 0 0 var(--ui-sp-4); }
h2, h3 { font-family: var(--ui-font-sans); font-weight: 700; line-height: var(--ui-lh-tight); color: var(--ui-ink); }
h2 { font-size: var(--ui-fs-md); margin: var(--ui-sp-5) 0 var(--ui-sp-2); }
h3 { font-size: var(--ui-fs-base); font-weight: 600; margin: var(--ui-sp-4) 0 var(--ui-sp-1); }
p { margin: 0 0 var(--ui-sp-3); }
small, .ui-small { font-size: var(--ui-fs-sm); }
strong, b { font-weight: 700; }
hr { border: 0; border-top: 1px solid var(--ui-line); margin: var(--ui-sp-4) 0; }

/* Visible keyboard focus everywhere (accessibility). Mouse clicks don't trigger :focus-visible. */
:focus-visible {
  outline: 2px solid var(--ui-focus);
  outline-offset: 2px;
  border-radius: var(--ui-r-sm);
}

/* ---- Eyebrow + highlight (website signatures) ---------------------------------------------- */
/* The small uppercase label above a heading. Default is Inter 700; add --serif for the Fraunces
   italic serif variant the website uses. Colour is coral-text (AA). */
.ui-eyebrow { display: inline-block; font-size: 12.5px; font-weight: 700; text-transform: uppercase;
              letter-spacing: .14em; color: var(--ui-eyebrow); margin: 0 0 var(--ui-sp-2); }
.ui-eyebrow--serif { font-family: var(--ui-font-serif); font-style: italic; font-weight: 400;
                     text-transform: none; letter-spacing: 0; font-size: 1.15rem; }
/* The sun marker behind a display word (box-decoration-break keeps it across line wraps). */
.ui-highlight { background: linear-gradient(transparent 12%, var(--ui-sun) 12%, var(--ui-sun) 92%, transparent 92%);
                -webkit-box-decoration-break: clone; box-decoration-break: clone; padding-inline: .08em;
                color: var(--ui-on-sun); }

/* ---- Brand mark (optional inline-SVG waveform, matches the website favicon) ----------------- */
.ui-mark { display: inline-block; width: 1.5em; height: 1.5em; border-radius: 6px; vertical-align: -.35em; }
.ui-mark .sq { fill: var(--ui-ink); }
.ui-mark .bar { fill: var(--ui-coral); }
/* CSS-only twin for pages that cannot carry the inline <svg> (a Jinja macro's one-line call, a
   generated offline page): an ink square with the waveform masked out of it in coral. The mask is a
   kit asset, so the url() is relative to kit.css and resolves under whatever /ui/ the tool serves. */
.ui-mark--css { position: relative; background: var(--ui-ink); }
.ui-mark--css::after {
  content: ""; position: absolute; inset: 0; background: var(--ui-coral);
  -webkit-mask: url("mark-bars.svg") center / contain no-repeat;
          mask: url("mark-bars.svg") center / contain no-repeat;
}

/* ---- App shell + top bar (website light sticky header) ------------------------------------- */
.ui-app { min-height: 100vh; display: flex; flex-direction: column; }
.ui-topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: var(--ui-sp-3);
  padding: var(--ui-sp-3) var(--ui-sp-4);
  background: var(--ui-header-bg); color: var(--ui-header-ink);
  border-bottom: 1px solid var(--ui-line);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  flex-wrap: wrap;
}
.ui-topbar__brand {
  font-family: var(--ui-font-display); font-weight: 400; font-size: 1.3rem;
  color: var(--ui-header-ink); text-decoration: none; text-transform: uppercase; letter-spacing: .03em;
}
.ui-topbar__tag { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .14em;
                  color: var(--ui-ink-muted); white-space: nowrap; }
.ui-topbar__tool { font-size: var(--ui-fs-sm); color: var(--ui-ink-muted);
                   border-left: 1px solid var(--ui-line); padding-left: var(--ui-sp-3); }
.ui-topbar__spacer { margin-left: auto; }
.ui-topbar__meta { font-size: var(--ui-fs-sm); color: var(--ui-ink-muted); font-variant-numeric: tabular-nums; }
.ui-topbar a:not(.ui-topbar__brand) { color: var(--ui-accent); }
.ui-topbar__back::before { content: "\2039 "; }   /* back-to-console link for operator tools */

/* ---- Navigation (tab bar under the top bar — light, coral active underline) ---------------- */
.ui-nav { display: flex; flex-wrap: wrap; background: var(--ui-bg);
          border-bottom: 1px solid var(--ui-line); padding: 0 var(--ui-sp-2); }
.ui-nav a {
  color: var(--ui-ink-muted); text-decoration: none; padding: var(--ui-sp-2) var(--ui-sp-3);
  font-size: var(--ui-fs-sm); font-weight: 500; border-bottom: 2px solid transparent;
}
.ui-nav a:hover { color: var(--ui-ink); background: color-mix(in srgb, var(--ui-ink) 5%, transparent);
                  text-decoration: none; }
.ui-nav a.active, .ui-nav a[aria-current="page"] {
  border-bottom-color: var(--ui-coral); color: var(--ui-ink); font-weight: 600;
}

/* ---- Main container ------------------------------------------------------------------------ */
.ui-main { max-width: 960px; margin: var(--ui-sp-5) auto; padding: 0 var(--ui-sp-4) var(--ui-sp-7); width: 100%; }
.ui-main--wide { max-width: 1160px; }
.ui-lede { color: var(--ui-ink-muted); font-size: var(--ui-fs-sm); margin: 0 0 var(--ui-sp-4); }

/* ---- Buttons (website: 10px radius, Inter 600, coral primary, ghost w/ ink border) --------- */
.ui-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-sp-2);
  font: inherit; font-size: var(--ui-fs-sm); font-weight: 600; line-height: 1;
  padding: .55rem 1rem; border: 1px solid var(--ui-line-strong); border-radius: var(--ui-r-md);
  background: var(--ui-surface); color: var(--ui-ink); cursor: pointer; text-decoration: none;
  transition: background .18s, border-color .18s, transform .18s;
}
.ui-btn:hover:not(:disabled) { background: var(--ui-surface-2); text-decoration: none; transform: translateY(-1px); }
.ui-btn--primary { background: var(--ui-accent-fill); border-color: var(--ui-accent-fill); color: var(--ui-accent-contrast); }
.ui-btn--primary:hover:not(:disabled) { background: var(--ui-accent-fill-hover); border-color: var(--ui-accent-fill-hover); }
.ui-btn--secondary { background: var(--ui-surface-2); border-color: var(--ui-line-strong); color: var(--ui-ink); }
.ui-btn--ghost { background: transparent; border-color: var(--ui-line-strong); color: var(--ui-ink); }
.ui-btn--ghost:hover:not(:disabled) { background: color-mix(in srgb, var(--ui-ink) 4%, transparent); }
.ui-btn--on-dark { background: var(--ui-sun); border-color: var(--ui-sun); color: var(--ui-on-sun); }
.ui-btn--on-dark:hover:not(:disabled) { background: var(--ui-sun-hover); border-color: var(--ui-sun-hover); }
.ui-btn--danger { background: var(--ui-danger-bg); border-color: var(--ui-danger); color: var(--ui-danger-ink); }
.ui-btn--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--ui-danger) 18%, var(--ui-danger-bg)); }
.ui-btn--sm { padding: .3rem .6rem; font-size: var(--ui-fs-xs); }
.ui-btn:disabled, .ui-btn.disabled { opacity: .5; cursor: not-allowed; transform: none; }

/* ---- Forms --------------------------------------------------------------------------------- */
.ui-field { display: flex; flex-direction: column; gap: var(--ui-sp-1); margin: 0 0 var(--ui-sp-4); }
.ui-field > label, .ui-label { font-size: var(--ui-fs-sm); font-weight: 500; color: var(--ui-ink); }
input[type=text], input[type=search], input[type=email], input[type=number], input[type=password],
select, textarea, .ui-input {
  font: inherit; font-size: var(--ui-fs-sm); color: var(--ui-ink); background: var(--ui-surface);
  border: 1px solid var(--ui-line-strong); border-radius: var(--ui-r-sm); padding: .45rem .6rem;
  width: 100%; max-width: 100%;
}
input::placeholder, textarea::placeholder { color: var(--ui-ink-faint); }
.ui-field__hint { font-size: var(--ui-fs-xs); color: var(--ui-ink-muted); }

/* ---- Upload area (drop zone + visible picker + the chosen files; markup: templates/upload.j2,
        behaviour: upload.js). Without the script it is still a working, visible file picker. ---- */
.ui-upload { margin: 0 0 var(--ui-sp-4); }
.ui-upload__zone {
  display: flex; flex-direction: column; align-items: center; gap: var(--ui-sp-2); text-align: center;
  padding: var(--ui-sp-5) var(--ui-sp-4); border: 2px dashed var(--ui-line-strong);
  border-radius: var(--ui-r-lg); background: var(--ui-surface);
  transition: background .18s, border-color .18s;
}
.ui-upload.is-over .ui-upload__zone {
  border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent) 8%, var(--ui-surface));
}
.ui-upload__title { font-weight: 600; margin: 0; }
.ui-upload__hint { margin: 0; font-size: var(--ui-fs-xs); color: var(--ui-ink-muted); max-width: 60ch; }
/* the real input stays in the page (keyboard, screen reader, form submit) but is not drawn */
.ui-upload__input { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
                    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.ui-upload__input:focus-visible + .ui-upload__zone .ui-upload__pick { outline: 2px solid var(--ui-accent);
                                                                      outline-offset: 2px; }
.ui-upload__pick { cursor: pointer; }
.ui-upload__list { list-style: none; margin: var(--ui-sp-3) 0 0; padding: 0; }
.ui-upload__list:empty { display: none; }
.ui-upload__item {
  display: flex; align-items: center; gap: var(--ui-sp-3); padding: var(--ui-sp-2) var(--ui-sp-3);
  border: 1px solid var(--ui-line); border-radius: var(--ui-r-sm); background: var(--ui-surface);
  margin-bottom: var(--ui-sp-1); font-size: var(--ui-fs-sm);
}
.ui-upload__name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ui-upload__size { color: var(--ui-ink-muted); font-size: var(--ui-fs-xs); white-space: nowrap; }
.ui-upload__item.is-bad { border-color: var(--ui-danger); background: var(--ui-danger-bg); color: var(--ui-danger-ink); }
.ui-upload__note { font-size: var(--ui-fs-xs); }

/* ---- Chips (website pill, pressed = ink bg + white) ---------------------------------------- */
.ui-chip { display: inline-flex; align-items: center; gap: .35em; cursor: pointer;
           background: var(--ui-bg); color: var(--ui-ink-muted); border: 1px solid var(--ui-line-strong);
           border-radius: var(--ui-r-pill); padding: .35rem .8rem; font: inherit; font-size: var(--ui-fs-sm);
           font-weight: 600; }
.ui-chip:hover { color: var(--ui-ink); }
.ui-chip[aria-pressed="true"], .ui-chip.is-pressed {
  background: var(--ui-ink); border-color: var(--ui-ink); color: var(--ui-bg); }

/* ---- Tables (dense, tabular) --------------------------------------------------------------- */
.ui-table { border-collapse: collapse; width: 100%; font-size: var(--ui-fs-sm); }
.ui-table caption { text-align: left; color: var(--ui-ink-muted); font-size: var(--ui-fs-xs); padding-bottom: var(--ui-sp-1); }
.ui-table th, .ui-table td { border: 1px solid var(--ui-line); padding: .4rem .6rem; text-align: left; vertical-align: top; }
.ui-table thead th, .ui-table th[scope=col] { background: var(--ui-surface-2); font-weight: 600; }
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
.ui-table .num, .ui-table td.num, .ui-table th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---- Status pills + badges ----------------------------------------------------------------- */
.ui-pill {
  display: inline-flex; align-items: center; gap: .35em; border-radius: var(--ui-r-pill);
  padding: .1rem .55rem; font-size: var(--ui-fs-xs); font-weight: 600; white-space: nowrap;
  background: var(--ui-surface-2); color: var(--ui-ink-muted);
}
.ui-pill.is-ok     { background: var(--ui-ok-bg);     color: var(--ui-ok-ink); }
.ui-pill.is-warn, .ui-pill.is-open { background: var(--ui-warn-bg); color: var(--ui-warn-ink); }
.ui-pill.is-danger { background: var(--ui-danger-bg); color: var(--ui-danger-ink); }
.ui-pill.is-info   { background: var(--ui-info-bg);   color: var(--ui-info-ink); }
.ui-pill.is-synth  { background: var(--ui-synth-bg);  color: var(--ui-synth-ink); }
.ui-pill.is-real   { background: var(--ui-real-bg);   color: var(--ui-real-ink); }
.ui-badge { display: inline-block; min-width: 1.4em; text-align: center; border-radius: var(--ui-r-pill);
            padding: 0 .4em; font-size: var(--ui-fs-xs); font-weight: 700; background: var(--ui-surface-3);
            color: var(--ui-ink); font-variant-numeric: tabular-nums; }

/* ---- Cards --------------------------------------------------------------------------------- */
.ui-card { background: var(--ui-surface); border: 1px solid var(--ui-line); border-radius: var(--ui-r-lg);
           padding: var(--ui-sp-5) var(--ui-sp-5); margin: var(--ui-sp-4) 0; }
.ui-card--tinted { background: var(--ui-bg); }
.ui-card--link { display: block; color: inherit; text-decoration: none;
                 transition: box-shadow .2s, transform .2s; }
.ui-card--link:hover { box-shadow: var(--ui-shadow-md); transform: translateY(-1px); text-decoration: none; }
.ui-card__title { font-family: var(--ui-font-sans); font-weight: 700; font-size: var(--ui-fs-md);
                  margin: 0 0 var(--ui-sp-2); color: var(--ui-ink); }

/* ---- Alerts -------------------------------------------------------------------------------- */
.ui-alert { border: 1px solid var(--ui-line); border-left-width: 4px; border-radius: var(--ui-r-sm);
            padding: var(--ui-sp-3) var(--ui-sp-4); margin: var(--ui-sp-4) 0; font-size: var(--ui-fs-sm); }
.ui-alert.is-danger { background: var(--ui-danger-bg); border-color: var(--ui-danger); color: var(--ui-danger-ink); }
.ui-alert.is-warn   { background: var(--ui-warn-bg);   border-color: var(--ui-warn);   color: var(--ui-warn-ink); }
.ui-alert.is-info   { background: var(--ui-info-bg);   border-color: var(--ui-info);   color: var(--ui-info-ink); }
.ui-alert.is-ok     { background: var(--ui-ok-bg);     border-color: var(--ui-ok);     color: var(--ui-ok-ink); }

/* ---- Empty state --------------------------------------------------------------------------- */
.ui-empty { text-align: center; color: var(--ui-ink-muted); background: var(--ui-surface-2);
            border: 1px dashed var(--ui-line-strong); border-radius: var(--ui-r-md);
            padding: var(--ui-sp-6) var(--ui-sp-4); }
.ui-empty__title { font-weight: 600; color: var(--ui-ink); margin-bottom: var(--ui-sp-1); }

/* ---- Audio player row ---------------------------------------------------------------------- */
.ui-audio { display: flex; align-items: center; gap: var(--ui-sp-3); padding: var(--ui-sp-2) var(--ui-sp-3);
            background: var(--ui-surface-2); border: 1px solid var(--ui-line); border-radius: var(--ui-r-sm); }
.ui-audio__label { font-size: var(--ui-fs-sm); font-weight: 500; min-width: 0; }
.ui-audio audio { height: 34px; }
.ui-audio__meta { margin-left: auto; font-size: var(--ui-fs-xs); color: var(--ui-ink-muted);
                  font-variant-numeric: tabular-nums; }

/* ---- Code / IPA ---------------------------------------------------------------------------- */
code, .ui-code, .ui-ipa { font-family: var(--ui-font-mono); font-size: .92em; }
.ui-code { background: var(--ui-surface-2); border: 1px solid var(--ui-line); border-radius: var(--ui-r-sm);
           padding: .05rem .35rem; }
.ui-ipa { background: var(--ui-synth-bg); color: var(--ui-synth-ink); border-radius: var(--ui-r-sm);
          padding: .05rem .35rem; }

/* ---- Tooltip (CR-2: a hover/focus popover, e.g. the pronunciation verification symbol palette) -
   ONE shared element a page positions itself (a popover's placement depends on the trigger's own
   layout, which the kit cannot know) — this only supplies the look: tokens only, visible in both
   themes, and never a mouse target (`pointer-events: none`) so it can never steal the hover/focus
   it is describing. A page toggles it with the `hidden` attribute, never a display/visibility
   literal, so one rule here covers both open/closed state and the "nothing is in the DOM to catch
   a stray click" default. */
.ui-tooltip {
  position: fixed; z-index: 20; max-width: 15rem;
  background: var(--ui-surface); color: var(--ui-ink); border: 1px solid var(--ui-line-strong);
  border-radius: var(--ui-r-md); padding: var(--ui-sp-2) var(--ui-sp-3);
  font-size: var(--ui-fs-sm); line-height: 1.4; box-shadow: var(--ui-shadow-md);
  pointer-events: none;
}
.ui-tooltip[hidden] { display: none; }
.ui-tooltip__word { font-size: 1.05em; }
.ui-tooltip__mark { font-weight: 700; color: var(--ui-coral-text); }
.ui-tooltip__note { display: block; margin-top: var(--ui-sp-1); color: var(--ui-ink-muted); }

/* ---- Dark band (an ink section inside a light page, like the website's "suite" band) -------- */
.ui-band-dark { background: var(--ui-brand); color: var(--ui-on-dark); border-radius: var(--ui-r-panel);
                padding: var(--ui-sp-6); }
.ui-band-dark .ui-card { background: var(--ui-ink-surface); border-color: var(--ui-line-strong); color: var(--ui-on-dark); }

/* A dense table that is wider than its column scrolls INSIDE its own box, at every width — the
   evaluation instrument's timeline clips at 1440 px, not only on a phone (UX_REVIEW finding #5). */
.ui-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%;
                   /* …and it must be allowed to shrink when it is a flex or grid item:
                      min-width defaults to `auto`, i.e. the table's own minimum. */
                   min-width: 0; }
/* …and the table inside it must be allowed to be WIDER than the box, or it just crushes its columns
   instead of scrolling — which is exactly how the evaluation instrument's SUBJECT column clipped. */
.ui-table-scroll > table { width: auto; min-width: 100%; }

/* ---- Flow (a process as numbered steps in two lanes) ----------------------------------------
   One component for the customer's overview and the operator console, so both show the SAME
   pipeline the same way. A flow answers three questions at a glance: which steps there are (the
   numbered icon tiles, left to right), WHO does each one (its lane: the upper lane is one party,
   the lower lane the other), and WHEN (the line runs through the steps in order and changes lane
   at every hand-off; it is solid as far as the process has come, dashed ahead).

   Markup (ui/templates/flow.j2 writes it for the Jinja apps; the console builds the same nodes):
     div.ui-flow[style="--ui-flow-n: <number of steps>"]
       span.ui-flow__lane.ui-flow__lane--a     the upper lane's name
       span.ui-flow__lane.ui-flow__lane--b     the lower lane's name
       ol.ui-flow__steps > li.ui-flow__step.is-lane-a|is-lane-b.is-<state>[.is-handoff][.is-optional]
         span.ui-flow__carry                   optional, on a hand-off: WHAT is handed over
         a|button.ui-flow__open
           span.ui-flow__icon  (svg + span.ui-flow__num + optional span.ui-flow__mark)
           span.ui-flow__text  (span.ui-flow__name + span.ui-flow__who + span.ui-pill)
   `is-handoff` marks a step whose predecessor is in the other lane; its `ui-flow__carry` names what
   crosses the lanes there (a questionnaire, a term list, a pack), so the flow also shows the
   exchange of data. `is-optional` marks a step the process does not wait for (a dashed tile). The
   pill says the state as a WORD — the tile's colour is never the only signal.

   States: is-done (reached: ink tile), is-now (the READER's move: sun tile), is-wait (the other
   party's move: coral outline), is-later (ahead: dashed), is-problem (danger), is-plain (a map of
   the steps themselves, carrying no state).

   Geometry: the two icon rows and the gap between them have fixed heights, and each step's text
   sits outside them (above the upper lane, below the lower one), so the connecting line never has
   to know how many lines a name wraps to. On a phone the lanes fold into one list, the lower
   lane's steps indented. */
.ui-flow {
  --ui-flow-icon: 3.75rem; --ui-flow-gap: 3.75rem; --ui-flow-band: .6rem;
  display: grid; grid-template-columns: max-content repeat(var(--ui-flow-n, 1), minmax(0, 1fr));
  grid-template-rows: auto var(--ui-flow-icon) var(--ui-flow-gap) var(--ui-flow-icon) auto;
}
/* the two lane bands, behind the icon rows */
.ui-flow::before, .ui-flow::after {
  content: ""; grid-column: 1 / -1; margin: calc(-1 * var(--ui-flow-band)) 0;
  border-radius: var(--ui-r-lg); background: var(--ui-surface-2);
}
.ui-flow::before { grid-row: 2; }
.ui-flow::after { grid-row: 4; }
.ui-flow__lane {
  grid-column: 1; align-self: center; z-index: 1; max-width: 9rem;
  padding: 0 var(--ui-sp-4) 0 var(--ui-sp-3); font-size: 11px; font-weight: 700; line-height: 1.35;
  text-transform: uppercase; letter-spacing: .12em; color: var(--ui-ink);
}
.ui-flow__lane--a { grid-row: 2; }
.ui-flow__lane--b { grid-row: 4; }
.ui-flow__steps {
  grid-column: 2 / -1; grid-row: 1 / -1; display: grid; grid-template-columns: subgrid;
  grid-template-rows: subgrid; list-style: none; margin: 0; padding: 0;
}
.ui-flow__step { grid-row: 1 / -1; display: grid; grid-template-rows: subgrid; min-width: 0; }
.ui-flow__open {
  grid-row: 1 / -1; grid-column: 1; z-index: 1; display: grid; grid-template-rows: subgrid;
  justify-items: center; min-width: 0; padding: 0; border: 0; background: none; font: inherit;
  color: inherit; text-align: center; text-decoration: none; cursor: pointer;
}
.ui-flow__open:hover { text-decoration: none; }
.ui-flow__icon {
  grid-row: 2; position: relative; display: grid; place-items: center;
  width: var(--ui-flow-icon); height: var(--ui-flow-icon); border-radius: 28%;
  background: var(--ui-surface); border: 1.5px solid var(--ui-line-strong); color: var(--ui-ink);
  transition: transform .18s, box-shadow .18s;
}
.ui-flow__icon svg { width: 46%; height: 46%; }
.ui-flow__text {
  grid-row: 1; align-self: end; display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 0; max-width: 100%; padding: 0 var(--ui-sp-1) calc(var(--ui-flow-band) + var(--ui-sp-2));
}
.ui-flow__step.is-lane-b .ui-flow__icon { grid-row: 4; }
.ui-flow__step.is-lane-b .ui-flow__text {
  grid-row: 5; align-self: start; padding: calc(var(--ui-flow-band) + var(--ui-sp-2)) var(--ui-sp-1) 0;
}
.ui-flow__open:hover .ui-flow__icon { transform: translateY(-2px); box-shadow: var(--ui-shadow-md); }
.ui-flow__open:hover .ui-flow__name { text-decoration: underline; text-underline-offset: 2px; }
.ui-flow__name { font-size: var(--ui-fs-sm); font-weight: 600; line-height: 1.25; color: var(--ui-ink); }
.ui-flow__who { font-size: var(--ui-fs-xs); color: var(--ui-ink-muted); }
.ui-flow__text > .ui-pill { margin-top: var(--ui-sp-1); max-width: 100%; white-space: normal;
                            justify-content: center; text-align: center; }
/* the step's number (top left of the tile) and its mark (top right: a check, an exclamation mark) */
.ui-flow__num, .ui-flow__mark {
  position: absolute; top: -.45rem; display: grid; place-items: center; min-width: 1.3rem; height: 1.3rem;
  border-radius: var(--ui-r-pill); border: 2px solid var(--ui-surface-2); font-size: .7rem;
  font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
}
.ui-flow__num { left: -.45rem; background: var(--ui-surface); color: var(--ui-ink); }
.ui-flow__mark { right: -.45rem; background: var(--ui-ok); color: var(--ui-surface); }

/* the line: drawn by the step it ARRIVES at, from the previous step's centre to this one's */
.ui-flow__step::before {
  content: ""; grid-column: 1; width: 100%; transform: translateX(-50%); pointer-events: none;
  border: 0 dashed var(--ui-line-strong);
}
.ui-flow__step:first-child::before { display: none; }
.ui-flow__step.is-lane-a:not(.is-handoff)::before { grid-row: 2; align-self: center; border-top-width: 2px; }
.ui-flow__step.is-lane-b:not(.is-handoff)::before { grid-row: 4; align-self: center; border-top-width: 2px; }
/* a hand-off: along the lane the line comes from, then straight across the gap into the next tile —
   down into the top of a lower-lane tile, up into the bottom of an upper-lane one. A tile is thus
   entered from the other lane's side and left through its right edge, so no two segments overlap. */
.ui-flow__step.is-lane-b.is-handoff::before {
  grid-row: 2 / 4; margin-top: calc(var(--ui-flow-icon) / 2 - 1px);
  border-top-width: 2px; border-right-width: 2px; border-top-right-radius: 1rem;
}
.ui-flow__step.is-lane-a.is-handoff::before {
  grid-row: 3 / 5; margin-bottom: calc(var(--ui-flow-icon) / 2 - 1px);
  border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 1rem;
}
/* what is handed over there: a short label in the gap between the lanes, left of the line */
.ui-flow__carry {
  grid-column: 1; grid-row: 3; align-self: center; z-index: 1; width: 100%; transform: translateX(-50%);
  padding: 0 .55rem 0 .3rem; text-align: right; font-size: 11px; line-height: 1.2;
  color: var(--ui-ink-muted); pointer-events: none;
}
.ui-flow__step.is-done::before, .ui-flow__step.is-now::before, .ui-flow__step.is-wait::before,
.ui-flow__step.is-problem::before { border-style: solid; border-color: var(--ui-ink); }
.ui-flow__step.is-plain::before { border-style: solid; }

.ui-flow__step.is-done .ui-flow__icon { background: var(--ui-ink); border-color: var(--ui-ink); color: var(--ui-bg); }
.ui-flow__step.is-now .ui-flow__icon {
  background: var(--ui-sun); border-color: var(--ui-sun); color: var(--ui-on-sun);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--ui-sun) 35%, transparent);
}
.ui-flow__step.is-now .ui-flow__mark { background: var(--ui-ink); color: var(--ui-bg); }
.ui-flow__step.is-wait .ui-flow__icon { border: 2px solid var(--ui-coral); }
.ui-flow__step.is-later .ui-flow__icon { border-style: dashed; color: var(--ui-ink-muted); }
.ui-flow__step.is-later .ui-flow__name { color: var(--ui-ink-muted); }
.ui-flow__step.is-problem .ui-flow__icon {
  background: var(--ui-danger-bg); border-color: var(--ui-danger); color: var(--ui-danger-ink);
}
.ui-flow__step.is-problem .ui-flow__mark { background: var(--ui-danger); }
/* a step the process does not wait for */
.ui-flow__step.is-optional .ui-flow__icon { border-style: dashed; }
/* the step whose details are open below the flow */
.ui-flow__open[aria-current="step"] .ui-flow__icon { outline: 2px solid var(--ui-focus); outline-offset: 3px; }

/* A phone: one list in step order; the lower lane's steps are indented, the text sits beside the tile. */
@media (max-width: 760px) {
  .ui-flow { --ui-flow-icon: 3.25rem; display: block; }
  .ui-flow::before, .ui-flow::after, .ui-flow__lane, .ui-flow__step::before { display: none; }
  .ui-flow__steps { display: block; }
  .ui-flow__step { display: block; padding: var(--ui-sp-2) 0; }
  .ui-flow__step.is-lane-b { margin-left: 2rem; }
  .ui-flow__carry { display: block; width: auto; transform: none; padding: 0 0 var(--ui-sp-1); text-align: left; }
  .ui-flow__carry::before { content: "\2193\00a0"; }
  .ui-flow__open { display: flex; align-items: center; gap: var(--ui-sp-3); width: 100%; text-align: left; }
  .ui-flow__icon { flex: none; }
  .ui-flow__text, .ui-flow__step.is-lane-b .ui-flow__text {
    flex: 1; display: grid; grid-template-columns: 1fr auto; column-gap: var(--ui-sp-3);
    align-items: center; justify-items: start; padding: 0; }
  .ui-flow__name, .ui-flow__who { grid-column: 1; }
  .ui-flow__text > .ui-pill { grid-column: 2; grid-row: 1 / span 2; margin: 0; }
}

/* ---- Utilities ----------------------------------------------------------------------------- */
.ui-muted { color: var(--ui-ink-muted); }
.ui-tnum { font-variant-numeric: tabular-nums; }
.ui-mono { font-family: var(--ui-font-mono); }
.ui-display { font-family: var(--ui-font-display); font-weight: 400; text-transform: uppercase; letter-spacing: .01em; }
.ui-stack > * + * { margin-top: var(--ui-sp-3); }
.ui-row { display: flex; gap: var(--ui-sp-3); align-items: center; flex-wrap: wrap; }

/* ---- Responsive ---------------------------------------------------------------------------- */
@media (max-width: 640px) {
  .ui-main { margin-top: var(--ui-sp-4); padding: 0 var(--ui-sp-3) var(--ui-sp-6); }
  .ui-topbar { gap: var(--ui-sp-2); }
  .ui-topbar__tag { display: none; }
  .ui-topbar__tool { border-left: 0; padding-left: 0; }
}
