/* portal.css — the Extreme Tools shell.
 *
 * Palette, spacing, radius, elevation and motion come from
 * /assets/extreme-tokens.css, generated from the design system MASTER
 * (vendor/lexminion/design-system/extreme-ltd/MASTER.md). This file must not
 * restate those values: it maps the shell's own names onto them, adds the few
 * shell-only tokens the MASTER does not cover, and styles shell components.
 *
 * Every shell-only value is measured and recorded in docs/design/shell.md.
 * The shell is single-theme by decision — there is no dark palette here.
 */

/* ── 1 · Self-hosted faces ───────────────────────────────────────────────
   Aliased at /assets/fonts/ so the frozen renderer snapshot keeps its own
   verbatim relative paths. This is the only font path the shell advertises. */
@font-face{font-family:"Instrument Sans";src:url("/assets/fonts/instrument-sans-latin-ext-400-normal.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Instrument Sans";src:url("/assets/fonts/instrument-sans-latin-ext-600-normal.woff2") format("woff2");font-weight:500 700;font-style:normal;font-display:swap}
@font-face{font-family:"Newsreader";src:url("/assets/fonts/newsreader-latin-ext-500-normal.woff2") format("woff2");font-weight:300 700;font-style:normal;font-display:swap}
@font-face{font-family:"Newsreader";src:url("/assets/fonts/newsreader-latin-ext-500-italic.woff2") format("woff2");font-weight:300 700;font-style:italic;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("/assets/fonts/jetbrains-mono-latin-ext-400-normal.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("/assets/fonts/jetbrains-mono-cyrillic-ext-400-normal.woff2") format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"JetBrains Mono";src:url("/assets/fonts/jetbrains-mono-latin-ext-600-normal.woff2") format("woff2");font-weight:500 600;font-style:normal;font-display:swap}

/* ── 2 · Alias layer ─────────────────────────────────────────────────────
   The shell's historic token names, re-pointed at MASTER tokens. Keeping the
   names means component rules stay untouched while the palette changes, and
   it absorbs the renderer's competing dialect in exactly one place. */
:root{
  --font-sans:"Instrument Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  --font-serif:"Newsreader",Georgia,"Iowan Old Style",serif;
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --sunken:var(--surface-2);       /* MASTER: insets, rows, hover fills */
  --ink-3:var(--muted);            /* white / --bg surfaces only (MASTER §1) */
  --radius:var(--r-lg);
  --accent-ink:var(--on-accent);
  --shadow:var(--shadow-sm);

  --ok:var(--sev-ok);     --ok-soft:var(--sev-ok-bg);
  --warn:var(--sev-medium); --warn-soft:var(--sev-medium-bg);
  --bad:var(--sev-critical); --bad-soft:var(--sev-critical-bg);

  /* Shell-only. Per-tool identity on the hub; ratios in docs/design/shell.md.
     n8n's own brand #ea4b71 behaves exactly like the brand green does in
     MASTER §3 — 3.53:1 on the page, so it is a MARK only, never small text —
     so the same rule is applied: a darkened #b03354 carries the text. */
  --tool-lex:var(--accent);        --tool-lex-text:var(--accent-text);  --tool-lex-soft:var(--accent-soft);
  --tool-n8n:#ea4b71;              --tool-n8n-text:#b03354;             --tool-n8n-soft:#fdeaef;
  --tool-flow:var(--sev-low);      --tool-flow-text:var(--sev-low);     --tool-flow-soft:var(--sev-low-bg);
  --tool-sys:var(--ink-2);         --tool-sys-text:var(--ink-2);        --tool-sys-soft:var(--surface-2);

  /* MASTER §9b arc motif: 58px rhythm, 1.5px stroke, brand green at 5%
     on page canvases and 6% inside the login glow. */
  --canvas-arc:rgba(106,151,32,.05);
  --login-arc:rgba(106,151,32,.06);
}

/* ── 2b · Brand Kit overrides ────────────────────────────────────────────
   The four variables an administrator sets, consumed here and nowhere else.

   /brand-kit/tokens.css defines --brand-* and is linked AFTER this file, so
   the cascade does the work: these declarations read whatever it set. The
   var() fallback is the MASTER value, which is what renders if that stylesheet
   is missing, blocked, or served before the brand table exists — the shell
   must never lose its palette because one request failed.

   The fallbacks are literal hex only because a custom property cannot fall
   back to its own previous value; tests/brand.test.js pins each one against
   the generated token file, so they cannot drift from MASTER.

   Only these four MASTER tokens are re-pointed. --accent-soft, --on-accent and
   every severity token keep their generated values: severity is functional,
   not brand (MASTER §4), and an administrator must not be able to recolour a
   critical finding by choosing a brand. */
:root{
  --accent:var(--brand-primary,#6a9720);
  --focus-ring:var(--brand-primary,#6a9720);
  --accent-strong:var(--brand-secondary,#4d7016);
  --accent-deep:var(--brand-accent,#2f4a1a);
  --accent-text:var(--brand-accent-text,#4d7016);

  /* Re-declares the §2 alias so all five brand consumptions read together.
     Later declaration in the same file wins, exactly as --accent above wins
     over the generated token file. The fallback is §2's own stack verbatim —
     tests/brand.test.js pins the two against each other. */
  --font-sans:var(--brand-font,"Instrument Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif);
}

/* ── 3 · Base ────────────────────────────────────────────────────────────*/
*,*::before,*::after{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);
  font:14px/1.5 var(--font-sans);-webkit-font-smoothing:antialiased}
a{color:var(--accent-text);text-underline-offset:2px}
/* Scoped: .tile and .brand are anchors whose children must keep their own
   colours — an unscoped a:hover turned the entire card green on hover. */
a:not(.tile):not(.brand):hover{color:var(--accent-deep)}
svg{flex:none}
.icon{width:20px;height:20px;display:block}

/* Focus is never removed: 2px ring, offset, on everything interactive. */
:where(a,button,input,select,textarea,[tabindex],dialog):focus-visible{
  outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:var(--r-sm)}

h1{font-family:var(--font-serif);font-weight:500;font-size:clamp(26px,2.4vw,32px);
  line-height:1.15;letter-spacing:-.01em;margin:0 0 var(--space-2)}
h2{font-family:var(--font-serif);font-weight:500;font-size:21px;line-height:1.3;
  margin:var(--space-6) 0 var(--space-3)}
.sub{color:var(--ink-2);font-size:14px;margin:0 0 var(--space-6);max-width:66ch}
.muted{color:var(--muted);font-size:12.5px}
.mono{font-family:var(--mono);font-size:12.5px;overflow-wrap:anywhere}
.eyebrow{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin:0 0 var(--space-2)}

/* ── 4 · Page canvas + chrome ────────────────────────────────────────────*/
.canvas{min-height:100vh;
  background-image:repeating-radial-gradient(circle at 100% 0%,
    transparent 0 58px,var(--canvas-arc) 58px 59.5px)}

.topbar{display:flex;align-items:center;gap:var(--space-4);padding:0 var(--space-5);height:60px;
  background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:20}
.brand{display:flex;align-items:center;gap:var(--space-2);margin-right:auto;
  color:var(--ink);text-decoration:none}
.brand-logo{height:22px;width:auto;display:block}          /* MASTER §10: min render height 20px */
.brand-name{font-family:var(--font-serif);font-weight:500;font-size:16px;letter-spacing:-.01em}
/* The way back. One component, one destination, one string — first in the
   topbar on a workspace page, first in the sidebar header in the console, and
   mirroring the LexMinion app's own hub-return control. Quieter than a nav
   item: it is a way out, not a destination competing with the others. */
.back-link{display:inline-flex;align-items:center;gap:6px;flex:none;
  padding:7px 11px;border-radius:var(--r-pill);text-decoration:none;
  color:var(--ink-2);font-size:13px;font-weight:500;
  transition:background var(--motion-fast) var(--motion-ease),color var(--motion-fast) var(--motion-ease)}
.back-link:hover{background:var(--surface-2);color:var(--ink)}
.back-link .icon{width:16px;height:16px;flex:none}
/* The topbar's back sits left of the wordmark and is separated from it, so the
   two controls that both lead to the hub do not read as one wide button. */
.topbar .back-link{margin-right:calc(var(--space-2) * -1);padding-left:0}

.topbar nav{display:flex;gap:var(--space-1)}
.topbar nav a{display:flex;align-items:center;gap:7px;padding:7px 11px;border-radius:var(--r-pill);
  text-decoration:none;color:var(--ink-2);font-size:13.5px;font-weight:500;
  transition:background var(--motion-fast) var(--motion-ease),color var(--motion-fast) var(--motion-ease)}
.topbar nav a:hover{background:var(--surface-2);color:var(--ink)}
.topbar nav a[aria-current="page"]{background:var(--accent-soft);color:var(--accent-deep);font-weight:600}
.topbar nav a .icon{width:16px;height:16px}
/* The one crossing into the console. Deliberately not a nav item: it does not
   name a page in this surface, it leaves it. Sits after the nav and before the
   session controls, so the bar reads destinations · crossing · session. */
.to-console{display:inline-flex;align-items:center;gap:7px;flex:none;
  padding:7px 12px;border-radius:var(--r-pill);text-decoration:none;
  border:1px solid var(--border);background:var(--surface);
  color:var(--ink-2);font-size:13px;font-weight:600;
  transition:background var(--motion-fast) var(--motion-ease),color var(--motion-fast) var(--motion-ease),
             border-color var(--motion-fast) var(--motion-ease)}
.to-console:hover{background:var(--accent-soft);border-color:transparent;color:var(--accent-deep)}
.to-console .icon{width:16px;height:16px;flex:none}
/* A hairline between the way out and the wordmark: two controls that both lead
   to the hub must not read as one wide button. */
.topbar .back-link+.brand{padding-left:var(--space-4);
  border-left:1px solid var(--border);margin-left:var(--space-1)}

.wrap{max-width:1120px;margin:0 auto;padding:var(--space-7) var(--space-5) var(--space-8)}

/* ── 5 · Hub tiles ───────────────────────────────────────────────────────*/
.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(288px,1fr));gap:var(--space-4)}
.tile{position:relative;display:flex;flex-direction:column;gap:var(--space-3);
  padding:var(--space-5);background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);box-shadow:var(--shadow-sm);text-decoration:none;color:inherit;
  transition:transform var(--motion-fast) var(--motion-ease),
             box-shadow var(--motion-fast) var(--motion-ease),
             border-color var(--motion-fast) var(--motion-ease)}
a.tile:hover{transform:translateY(-2px);box-shadow:var(--shadow-md);border-color:var(--border-2)}
a.tile:active{transform:translateY(0)}
.tile-icon{width:40px;height:40px;border-radius:var(--r-md);display:grid;place-items:center;
  background:var(--tool-sys-soft);color:var(--tool-sys-text)}
.tile-icon .icon{width:22px;height:22px}
.tile-name{font-family:var(--font-serif);font-weight:500;font-size:17px;line-height:1.25}
.tile-desc{color:var(--ink-2);font-size:13.5px;line-height:1.5;flex:1;overflow-wrap:anywhere;min-width:0}
.tile-foot{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;
  color:var(--tool-sys-text)}
.tile-foot .icon{width:15px;height:15px;
  transition:transform var(--motion-fast) var(--motion-ease)}
a.tile:hover .tile-foot .icon{transform:translateX(3px)}
/* Per-tool identity: the icon tile and the call to action carry the hue. */
.tile[data-app="lexminion"]{--tool-sys-soft:var(--tool-lex-soft);--tool-sys-text:var(--tool-lex-text)}
.tile[data-app="n8n"]{--tool-sys-soft:var(--tool-n8n-soft);--tool-sys-text:var(--tool-n8n-text)}
.tile[data-app="workflow-library"]{--tool-sys-soft:var(--tool-flow-soft);--tool-sys-text:var(--tool-flow-text)}
.tile.soon{opacity:.6;pointer-events:none}
/* The hub's grids (index.html): six columns, each tile spanning 6 ÷ the size
   of its row, so every row is full and nothing is left alone at the end. */
#tiles.tiles,.tiles-compact{grid-template-columns:repeat(6,minmax(0,1fr))}
#tiles .tile,.tiles-compact .tile{grid-column:span var(--span,2)}
.account-tiles{margin-top:var(--space-7)}
.hub-section-title{margin:0 0 var(--space-3);font-family:var(--font-sans);font-size:12px;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.tiles-compact .tile{display:grid;grid-template-columns:40px minmax(0,1fr) auto;align-items:center;
  column-gap:var(--space-4);row-gap:2px;padding:var(--space-4) var(--space-5)}
.tiles-compact .tile-icon{grid-row:span 2}
.tiles-compact .tile-name{font-size:16px}
.tiles-compact .tile-desc{grid-column:2;font-size:13px}
.tiles-compact .tile-foot{grid-column:3;grid-row:1 / span 2;font-size:0;gap:0}
.tiles-compact .tile-foot .icon{width:18px;height:18px}
@media (max-width:760px){
  #tiles.tiles,.tiles-compact{grid-template-columns:1fr}
  #tiles .tile,.tiles-compact .tile{grid-column:auto}
}

/* ── 6 · Panels, forms, buttons ──────────────────────────────────────────*/
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  padding:var(--space-5);box-shadow:var(--shadow-sm);margin-bottom:var(--space-4)}
.card > h2:first-child{margin-top:0}
.row{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap}
.field{margin-bottom:var(--space-3)}
label{display:block;font-size:12.5px;font-weight:500;color:var(--ink-2);margin:0 0 5px}
/* Form controls inherit the design system — once, here, for every surface.
   This used to be an allow-list of three input types, so anything else the
   shell rendered fell back to the browser's own control: its font, its size,
   its border, its blue focus ring. input[type=number] in the categories editor
   was the visible case — it sat in a table of system-styled controls looking
   like it came from a different application, because it did.
   Written as "every input EXCEPT the ones that are genuinely not text fields",
   so the next input type added is covered by default rather than forgotten. */
/* :where() so the exclusion list costs NOTHING in specificity. Spelled out,
   the five :not()s scored (0,5,1) and quietly out-ranked every single-class
   override — .cap-field's width lost to this rule's width:100% and the cap
   fields collapsed to a few characters wide. :where() makes this (0,0,1), so a
   class can still win, which is the whole point of a base rule. */
input:where(:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]):not([type=color])),
select,textarea{
  width:100%;padding:9px 12px;border:1px solid var(--border-input);border-radius:var(--r-md);
  background:var(--surface);color:var(--ink);font:inherit;font-size:14px;
  transition:border-color var(--motion-fast) var(--motion-ease),
             box-shadow var(--motion-fast) var(--motion-ease)}
/* The native spinner is the other half of why a number field looked foreign:
   it adds browser chrome no other control in the shell has, and it invites
   click-stepping a money value. The field stays type=text with a numeric
   inputmode — the keypad on a phone, no spinner anywhere. */
input[type=number]{-moz-appearance:textfield;appearance:textfield}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
input::placeholder,textarea::placeholder{color:var(--placeholder)}
input[type=checkbox],input[type=radio]{accent-color:var(--accent);width:auto;cursor:pointer}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-soft)}
button{font:inherit;font-size:13.5px;font-weight:600;padding:9px 16px;border-radius:var(--r-pill);
  border:1px solid var(--border-input);background:var(--surface);color:var(--ink);cursor:pointer;
  transition:background var(--motion-fast) var(--motion-ease),
             border-color var(--motion-fast) var(--motion-ease)}
button:hover{background:var(--surface-2)}
button.primary{background:var(--accent-strong);color:var(--on-accent);border-color:transparent}
button.primary:hover{background:var(--accent-deep)}
button.danger{color:var(--sev-critical);border-color:var(--sev-critical)}
button.danger:hover{background:var(--sev-critical-bg)}
button:disabled{opacity:.5;cursor:not-allowed}
button:disabled:hover{background:var(--surface)}

/* ---- the chip: one checkable component, everywhere ----------------------
   Capabilities, house-key access and the category switches all render a
   checkbox that reads as a setting rather than a form field. They were three
   near-copies built from .pill plus inline styles, differing in which severity
   colour they borrowed — .ok here, .warn there — so the same control meant
   green in one column and amber in the next.
   One component now: OUTLINE when off, filled --accent-soft when on. The state
   is carried by the checkbox itself (:has), so nothing has to remember to add
   a class, and the label keeps its own <input> and its own data- attributes. */
.chip{display:inline-flex;align-items:center;gap:6px;margin:2px 3px 2px 0;
  padding:4px 11px;border-radius:var(--r-pill);cursor:pointer;
  border:1px solid var(--border-input);background:var(--surface);
  color:var(--ink-2);font-size:12.5px;font-weight:600;line-height:1.5;
  transition:background var(--motion-fast) var(--motion-ease),
             border-color var(--motion-fast) var(--motion-ease),
             color var(--motion-fast) var(--motion-ease)}
.chip:hover{background:var(--surface-2);color:var(--ink)}
.chip:has(input:checked){background:var(--accent-soft);border-color:transparent;
  color:var(--accent-text)}
.chip:has(input:checked):hover{background:var(--accent-soft);color:var(--accent-deep)}
.chip:has(input:focus-visible){outline:2px solid var(--focus-ring);outline-offset:2px}
.chip input{width:auto;margin:0;flex:none}
/* A provider with no house key behind it: settable, but honestly dimmed. */
.chip.inert{color:var(--muted)}
.chip.inert:has(input:checked){color:var(--accent-text)}

/* ---- a capped number, and the state of having no cap --------------------
   An empty daily-limit field means "no cap", which is a decision — but an
   empty box reads as a field nobody has filled in yet. The placeholder is
   styled to read as a value: same weight and colour as the accompanying text,
   not the lighter --placeholder every other empty field uses. */
.cap-field{width:112px;min-width:0}
.cap-field:placeholder-shown{color:var(--ink-2);font-style:normal}
.cap-field::placeholder{color:var(--ink-2);opacity:1}
.cap-field:focus::placeholder{color:var(--placeholder)}

/* ── 7 · Tables, pills, notices ──────────────────────────────────────────*/
table{width:100%;border-collapse:collapse;font-size:13.5px}
th{text-align:left;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted);padding:0 var(--space-3) var(--space-2)}
td{padding:12px var(--space-3);border-top:1px solid var(--border);vertical-align:middle;color:var(--ink)}
tbody tr:hover td{background:var(--surface-2)}
.scroll{overflow-x:auto}

.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:var(--r-pill);
  font-size:11px;font-weight:600;letter-spacing:.02em;
  background:var(--surface-2);color:var(--ink-2)}
.pill.ok{background:var(--ok-soft);color:var(--ok)}
.pill.bad{background:var(--bad-soft);color:var(--bad)}
.pill.warn{background:var(--warn-soft);color:var(--warn)}
.pill.accent{background:var(--accent-soft);color:var(--accent-text)}

.notice{padding:11px 14px;border-radius:var(--r-md);font-size:13.5px;margin-bottom:var(--space-3)}
.notice.err{background:var(--bad-soft);color:var(--bad)}
/* A caution, not a failure: nothing has gone wrong, but the next action costs
   something the reader should see first. Same tokens as .pill.warn, so the two
   severities read the same wherever they appear. */
.notice.warn{background:var(--warn-soft);color:var(--warn)}
.notice.ok{background:var(--ok-soft);color:var(--ok)}
.notice.info{background:var(--accent-soft);color:var(--accent-text)}

.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--border);margin-bottom:var(--space-5)}
.tabs button{border:0;background:transparent;border-radius:0;border-bottom:2px solid transparent;
  padding:10px 14px;color:var(--ink-2);font-weight:500}
.tabs button:hover{background:transparent;color:var(--ink)}
.tabs button[aria-selected="true"]{border-bottom-color:var(--accent);color:var(--ink);font-weight:600}

/* Key ownership stays readable on narrow screens without changing the theme. */
.console-wrap > .tabs{flex-wrap:wrap}
.key-advanced summary{cursor:pointer;padding:10px 0;font-weight:600}
.key-actions{align-items:flex-end}
.key-entry{flex:1 1 220px;min-width:0}
.key-entry input{display:block;width:100%;margin-top:6px}
.personal-key-status{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:8px 0}
.review-history-row{border-top:1px solid var(--border);padding:16px 0;overflow-wrap:anywhere}
.review-history-content{white-space:pre-wrap;overflow-wrap:anywhere;max-height:480px;overflow:auto;
  background:var(--surface-2);padding:16px;border-radius:var(--r-md);font-size:13px}
#employee-history{scroll-margin-top:20px}

.reveal{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-md);
  padding:12px 14px;font-family:var(--mono);font-size:12.5px;color:var(--ink-2);
  word-break:break-all;white-space:pre-wrap}   /* --ink-2 on --surface-2: --muted here is banned */

dialog{border:1px solid var(--border);border-radius:var(--r-lg);background:var(--surface);
  color:var(--ink);padding:var(--space-5);max-width:560px;width:calc(100% - 32px);
  box-shadow:var(--shadow-md)}
dialog::backdrop{background:rgba(31,33,37,.45)}

.langs{display:flex;gap:2px;background:var(--surface-2);border-radius:var(--r-pill);padding:3px}
.langs button{padding:4px 11px;border:0;background:transparent;font-size:11.5px;font-weight:600;
  border-radius:var(--r-pill);color:var(--ink-2)}
.langs button:hover{background:transparent;color:var(--ink)}
.langs button[aria-pressed="true"]{background:var(--surface);color:var(--ink);box-shadow:var(--shadow-sm)}

/* ── 7b · Neutral states ─────────────────────────────────────────────────
   One component for "there is nothing here yet" and "this is switched off".
   Both are ordinary conditions, not failures, so neither uses the error
   colours: a quiet inset panel, a muted glyph, a plain explanation, and the
   action that would change the situation when there is one. */
.state{display:flex;flex-direction:column;align-items:center;gap:var(--space-3);
  padding:var(--space-6) var(--space-5);text-align:center;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg)}
.state-icon{width:44px;height:44px;border-radius:var(--r-md);display:grid;place-items:center;
  background:var(--surface-2);color:var(--ink-2)}
.state-icon .icon{width:24px;height:24px}
.state-title{font-family:var(--font-serif);font-weight:500;font-size:18px;color:var(--ink)}
.state-body{color:var(--ink-2);font-size:13.5px;line-height:1.55;max-width:52ch;margin:0}
.state-note{color:var(--muted);font-size:12.5px;font-family:var(--mono);margin:0}
.state .row{margin-top:var(--space-1)}
/* An empty state stands in for the whole grid, not for one cell in it. */
.tiles > .state{grid-column:1/-1}

/* ── 8 · The front door: login and 404 ───────────────────────────────────
   The one hero moment in the shell. Arcs at 6%, a generous wordmark, a
   Newsreader greeting, and a single raised card. */
.centre{min-height:100vh;display:grid;grid-template-rows:1fr auto;justify-items:center;
  padding:var(--space-6) var(--space-4);
  background-image:repeating-radial-gradient(circle at 100% 0%,
    transparent 0 58px,var(--login-arc) 58px 59.5px)}
.gate{width:100%;max-width:404px;align-self:center;display:flex;flex-direction:column;
  align-items:center;gap:var(--space-5)}
.gate-hero{text-align:center;display:flex;flex-direction:column;align-items:center;gap:var(--space-3)}
.gate-logo{margin-bottom:var(--space-2)}   /* clearspace: >= 0.5x logo height (MASTER §10) */
.gate-logo{width:min(228px,72vw);height:auto;display:block}
.gate-hero h1{font-size:clamp(27px,3.4vw,34px);margin:0}
.gate-hero .sub{margin:0;font-size:14px;max-width:34ch}
.auth-card{width:100%;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);padding:var(--space-5) var(--space-5) var(--space-4);
  box-shadow:var(--shadow-md)}
.auth-card h1{font-size:19px;margin-bottom:var(--space-1)}
.auth-card .sub{font-size:13px;margin-bottom:var(--space-4)}
.auth-card button.primary{width:100%;margin-top:var(--space-2)}
/* The secondary way out of an auth form — "Forgot password?", "Back to sign
   in", "Go to sign in". Centred under the primary action and deliberately
   quiet: it is the path for the minority who need it, and it must not compete
   with the button that most readers came for. */
/* The remember-me opt-in. A real <label> wrapping the control, so the text is
   part of the hit target rather than decoration beside it. */
.check{display:flex;align-items:center;gap:9px;font-size:13.5px;cursor:pointer;
  margin-top:var(--space-3)}
.check input[type=checkbox]{width:16px;height:16px;margin:0;flex:none;accent-color:var(--accent)}
.check-hint{margin:5px 0 0 25px}
.auth-alt{text-align:center;margin:var(--space-3) 0 0}
.auth-alt a{color:var(--muted);text-decoration:underline}
.auth-alt a:hover{color:var(--ink-2)}
.gate-lang{display:flex;flex-direction:column;gap:5px;align-items:center}
.gate-lang .sub{max-width:44ch}
.gate-lang--second{width:100%;margin-top:var(--space-3);padding-top:var(--space-4);
  border-top:1px solid var(--border)}
.gate-lang--second h1{font-size:21px;color:var(--ink-2)}
.gate-lang--second .sub{font-size:13px;color:var(--muted)}
.gate-action{margin-top:var(--space-1)}
.gate-foot{display:flex;align-items:center;gap:var(--space-3);padding-top:var(--space-5);
  color:var(--muted);font-size:11.5px}

/* ── 9 · Motion ──────────────────────────────────────────────────────────*/
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition-duration:0ms!important;animation-duration:0ms!important}
  a.tile:hover{transform:none}
  a.tile:hover .tile-foot .icon{transform:none}
}

/* ── 10 · Small screens ──────────────────────────────────────────────────*/
@media (max-width:820px){
  /* Labels collapse to icons rather than pushing the bar wider than the
     viewport; each anchor keeps an aria-label, so the destination is still
     announced and still reachable. */
  .topbar nav a span{display:none}
  .topbar nav a{padding:9px}
  .topbar nav a .icon{width:18px;height:18px}
}
@media (max-width:600px){
  /* Brand, language and sign-out keep the first row; the nav takes a full
     row of its own underneath. Nothing is hidden — a single row cannot hold
     all four destinations plus the session controls at 375px. */
  .topbar{flex-wrap:wrap;height:auto;padding:8px var(--space-3);gap:var(--space-2);row-gap:6px}
  .topbar nav{order:3;width:100%;justify-content:space-between;gap:2px}
  .topbar nav a{flex:1;justify-content:center}
  .topbar .brand-name{display:none}
  .topbar .back-link span{display:none}
  .topbar .back-link+.brand{padding-left:var(--space-2);margin-left:0}
  .to-console{min-height:44px}
  .topbar .brand-logo{height:20px}
  .wrap{padding:var(--space-5) var(--space-3) var(--space-7)}
  .tiles{grid-template-columns:1fr}
  button,.topbar nav a{min-height:44px}     /* touch target floor */
}

/* ── 12 · Review history (Phase 4(e)) ────────────────────────────────────
   One row per review. The severity breakdown is the point of the row, so it
   sits on the same line as the date rather than behind a click, and it uses
   the same --sev-* tokens the LexMinion app uses for the same three words. */
.hist-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}
.hist-row{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  padding:var(--space-4) var(--space-5);display:flex;flex-direction:column;gap:10px}
.hist-main{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  flex-wrap:wrap}
.hist-name{font-family:var(--font-serif);font-size:16px;color:var(--ink);
  overflow-wrap:anywhere}
.hist-meta{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  font-size:13px;color:var(--ink-2)}
.hist-err{margin:0;font-size:12.5px;color:var(--bad);font-family:var(--mono);
  overflow-wrap:anywhere}
.hist-actions{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap;
  font-size:13px}
.hist-actions button.link,.hist-actions a{background:none;border:0;padding:0;
  color:var(--accent-text);font:inherit;cursor:pointer;text-decoration:underline;
  text-underline-offset:2px}
.hist-actions button.link:hover,.hist-actions a:hover{color:var(--accent-deep)}

.sev-count{display:inline-flex;align-items:center;gap:5px;font-size:12.5px;font-weight:600}
.sev-count::before{content:"";width:8px;height:8px;border-radius:50%;background:currentColor}
.sev-critical{color:var(--sev-critical)}
.sev-medium{color:var(--sev-medium)}
.sev-low{color:var(--sev-low)}

/* ── 12b · Counterparty check -------------------------------------------
   Two separate actions because each maps to one potentially billable
   YouScore product. Results use definition lists and bounded vertical lists;
   the page never needs a raw-response/code viewer. */
.ys-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4)}
.ys-actions section{padding:var(--space-4);border:1px solid var(--border);
  border-radius:var(--r-md);background:var(--surface-2)}
.ys-actions h2{font-size:17px;margin:0 0 var(--space-2)}
.ys-actions .sub{min-height:3.1em;margin:0 0 var(--space-3)}
.ys-result-card{margin-top:var(--space-4)}
.ys-result-head{display:flex;justify-content:space-between;align-items:flex-start;
  gap:var(--space-3);flex-wrap:wrap;margin-bottom:var(--space-4)}
.ys-result-head h2{margin:0;overflow-wrap:anywhere}
.ys-details,.ys-totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:var(--space-3);margin:0}
.ys-totals{margin-bottom:var(--space-4)}
.ys-detail{padding:var(--space-3);background:var(--surface-2);border-radius:var(--r-md)}
.ys-detail dt{color:var(--muted);font-size:11.5px;font-weight:600;text-transform:uppercase;
  letter-spacing:.05em;margin-bottom:5px}
.ys-detail dd{margin:0;color:var(--ink);font-size:13.5px;line-height:1.5;overflow-wrap:anywhere}
.ys-list{margin-top:var(--space-5)}
.ys-list h3{font-size:15px;margin-bottom:var(--space-3)}
.ys-list ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}
.ys-list li{padding:var(--space-3);border:1px solid var(--border);border-radius:var(--r-md);
  display:flex;flex-direction:column;gap:5px;overflow-wrap:anywhere}
.ys-list li>span{color:var(--ink-2);font-size:13px}
.ys-feature-head{display:flex;justify-content:space-between;align-items:flex-start;
  gap:var(--space-3);flex-wrap:wrap}
.ys-feature p{margin:0;line-height:1.5}
@media (max-width:700px){.ys-actions{grid-template-columns:1fr}.ys-actions .sub{min-height:0}}

/* ---- admin: compact user rows + detail view (redesign phase 1) ----------
   The user row was seven columns of live controls, so one person took about a
   third of the screen. The row is now a summary and the controls live in a
   disclosure row underneath. Colours come from the generated token file only. */
.users-table td{vertical-align:top}
.users-table .u-email{font-weight:600;color:var(--ink);overflow-wrap:anywhere}
.users-table tr[data-user-row] td{padding-top:10px;padding-bottom:10px}
.users-table tr[data-user-row]:hover td{background:var(--surface-2)}
.users-table tr[data-user-detail] td{
  background:var(--surface-2);border-top:0;padding:0}
.u-detail{padding:var(--space-4) var(--space-3) var(--space-5)}
.u-detail-head{margin-bottom:var(--space-3)}
.u-grid{display:grid;gap:var(--space-4);
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.u-field .eyebrow{margin-bottom:6px}
.u-field select{width:auto;max-width:100%}
/* Keep status compact; allow the three user actions to wrap so translated
   labels do not squeeze the access information into fragments. */
.users-table th:nth-child(5),.users-table tr[data-user-row] td:nth-child(5),
.users-table th:last-child,
.users-table tr[data-user-row] td:last-child{width:1%;white-space:nowrap}
.users-table tr[data-user-row] td:last-child .row{flex-wrap:wrap;min-width:150px;max-width:210px}
.users-table tr[data-user-row] td:nth-child(3){min-width:150px}
/* The name column. It was the first column until the directory gained a
   selection checkbox (2026-10-14); a width on :first-child then went to the
   checkbox column and starved the others. */
.users-table th:nth-child(2),.users-table tr[data-user-row] td:nth-child(2){width:30%}
/* The detail cell is ALSO a td:last-child (it is the row's only cell), so the
   nowrap above reached it and the chip groups overlapped instead of wrapping.
   Scoped to the summary row above; stated here because it looked like a grid
   bug and is not. */
.users-table tr[data-user-detail] td{white-space:normal;width:auto}

/* ---- collapsible card header (redesign phase 2) -------------------------
   The create-user form sat open above the user table permanently, so the thing
   an admin comes to read was pushed down by a thing they occasionally write.
   The heading is now the disclosure control. It is a real <button> inside the
   <h2>, so the heading stays a heading for a screen reader and the control
   stays keyboard-operable and focusable without a tabindex. */
.disclosure-h{margin:0;font-size:inherit}
button.disclosure{display:flex;align-items:center;gap:var(--space-2);
  width:100%;text-align:left;padding:0;border:0;background:transparent;
  border-radius:var(--r-sm);
  font-family:var(--font-serif);font-size:17px;font-weight:500;color:var(--ink)}
button.disclosure:hover{background:transparent;color:var(--accent-deep)}
button.disclosure:focus-visible{outline:2px solid var(--focus-ring);outline-offset:3px}
.disclosure-mark{width:0;height:0;flex:none;
  border-left:6px solid var(--ink-2);
  border-top:5px solid transparent;border-bottom:5px solid transparent;
  transition:transform var(--motion-fast) var(--motion-ease)}
button.disclosure[aria-expanded="true"] .disclosure-mark{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.disclosure-mark{transition:none}}
#newuser-body{padding-top:var(--space-4)}

/* ── 14 · Brand Kit panel (/admin/brand) ─────────────────────────────────
   Tokens only, no raw hex — MASTER §11. The one place a literal colour is
   allowed to appear is a swatch's background, which JavaScript sets through
   CSSOM (element.style.setProperty), never as a style attribute in markup:
   style-src governs <style>, <link> and the style ATTRIBUTE, so CSSOM keeps
   the preview working under an enforced policy with no 'unsafe-inline'. */
.brand-grid{display:grid;gap:var(--space-4);grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.brand-field{display:flex;flex-direction:column;gap:var(--space-2)}
.brand-pick{display:flex;align-items:center;gap:var(--space-2)}
.brand-pick input[type=color]{inline-size:44px;block-size:44px;padding:2px;flex:none;
  border:1px solid var(--border-input);border-radius:var(--r-sm);background:var(--surface);cursor:pointer}
.brand-pick input[type=text]{font-family:var(--mono);text-transform:lowercase}
.brand-help{margin:0;font-size:12.5px;line-height:1.4;color:var(--ink-2)}

/* Measured ratios. Never colour alone — every chip carries its number and a
   pass/fail word, the same rule MASTER §11 applies to severity badges. */
.brand-ratios{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:var(--space-2) 0 0;padding:0;list-style:none}
.brand-ratio{display:inline-flex;align-items:baseline;gap:6px;padding:3px 9px;border-radius:var(--r-pill);
  font-size:11px;font-weight:600;letter-spacing:.04em;border:1px solid var(--border)}
.brand-ratio b{font-family:var(--mono);font-weight:600;letter-spacing:0}
.brand-ratio.is-pass{background:var(--ok-soft);color:var(--ok);border-color:transparent}
.brand-ratio.is-fail{background:var(--bad-soft);color:var(--bad);border-color:transparent}

/* The preview is scoped: --brand-* are re-set on this element only, so what is
   on screen is the candidate palette and the surrounding page stays the saved
   one. Nothing is applied to :root until save. */
.brand-preview{border:1px solid var(--border);border-radius:var(--r-lg);
  background:var(--bg);padding:var(--space-5);display:grid;gap:var(--space-4)}
.brand-preview h3{margin:0;font-family:var(--font-serif);font-weight:500;font-size:17px;color:var(--ink)}
.brand-preview .row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3)}
.brand-swatches{display:flex;flex-wrap:wrap;gap:var(--space-3);margin:0;padding:0;list-style:none}
.brand-swatch{display:flex;flex-direction:column;gap:6px;font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--ink-2)}
.brand-chip{inline-size:64px;block-size:40px;border-radius:var(--r-md);border:1px solid var(--border)}
.brand-swatch code{font-family:var(--mono);font-size:11px;letter-spacing:0;color:var(--ink-2);text-transform:uppercase}
.brand-actions{display:flex;flex-wrap:wrap;gap:var(--space-3);align-items:center;margin-top:var(--space-4)}
.brand-actions .spacer{flex:1 1 auto}
.brand-dirty{font-size:12.5px;color:var(--ink-2)}
.brand-logo-row{display:flex;flex-wrap:wrap;gap:var(--space-5);align-items:flex-start}
.brand-logo-frame{flex:none;min-inline-size:180px;min-block-size:76px;display:flex;
  align-items:center;justify-content:center;padding:var(--space-3);
  border:1px dashed var(--border-2);border-radius:var(--r-md);background:var(--surface-2)}
.brand-logo-img{max-inline-size:220px;max-block-size:64px;display:block}
.brand-logo-meta{flex:1 1 260px;min-inline-size:0}
.brand-logo-name{margin:0 0 var(--space-3);font-size:12.5px;color:var(--ink-2);overflow-wrap:anywhere}
.brand-logo-name code{font-family:var(--mono);font-size:12px}
.brand-logo-meta .brand-actions{margin-top:0}


/* ── 15 · The console shell ──────────────────────────────────────────────
   Sidebar, not topbar: the two surfaces are told apart by their shape. The
   console is LIGHT — docs/design/console.md §5 records why the dark console
   asked for was not built (MASTER §9/§11 forbid dark panels on this system,
   and the Brand Kit's contrast gate in src/brand.js measures against the three
   light surfaces only). Every value here is a token; the only new pairing is
   --muted on --sidebar-bg, measured at 4.71 and recorded in console.md §6. */
.console-shell{display:grid;grid-template-columns:min(264px,26vw) 1fr;min-height:100vh}
.sidebar{background:var(--sidebar-bg);border-right:1px solid var(--border);
  display:flex;flex-direction:column;gap:var(--space-5);
  padding:var(--space-4) var(--space-3);
  position:sticky;top:0;height:100vh;overflow-y:auto}
.sidebar-head{display:flex;flex-direction:column;gap:var(--space-3);
  padding-bottom:var(--space-4);border-bottom:1px solid var(--border)}
/* The band is persistent and never scrolls away: the console always says what
   it is, which is the whole of how it is distinguished from the workspace. */
.sidebar-band{margin:0;padding:0 var(--space-2);font-size:11px;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.sidebar-nav{display:flex;flex-direction:column;gap:2px}
.sidebar-nav a{position:relative;display:flex;align-items:center;gap:10px;
  padding:9px var(--space-3);border-radius:var(--r-md);text-decoration:none;
  color:var(--ink-2);font-size:13.5px;font-weight:500;min-height:40px;
  transition:background var(--motion-fast) var(--motion-ease),color var(--motion-fast) var(--motion-ease)}
.sidebar-nav a:hover{background:var(--surface-2);color:var(--ink)}
.sidebar-nav a .icon{width:17px;height:17px;flex:none}
/* MASTER §9, verbatim: white fill, --accent-deep label, 3px --accent left bar
   as the non-text cue. The bar is a pseudo-element so the label keeps its
   padding and the row keeps its radius. */
.sidebar-nav a[aria-current="page"]{background:var(--surface);color:var(--accent-deep);
  font-weight:600;box-shadow:var(--shadow-sm)}
.sidebar-nav a[aria-current="page"]::before{content:"";position:absolute;
  left:0;top:50%;transform:translateY(-50%);width:3px;height:60%;
  border-radius:0 var(--r-sm) var(--r-sm) 0;background:var(--accent)}
.sidebar-foot{margin-top:auto;display:flex;flex-wrap:wrap;align-items:center;
  gap:var(--space-2);padding-top:var(--space-4);border-top:1px solid var(--border)}
.sidebar-foot button{flex:none}

.console-main{min-width:0;
  background-image:repeating-radial-gradient(circle at 100% 0%,
    transparent 0 58px,var(--canvas-arc) 58px 59.5px)}
.console-wrap{max-width:1120px;margin:0 auto;padding:var(--space-7) var(--space-5) var(--space-8)}

@media (max-width:820px){
  /* The sidebar stops being a column and becomes a band across the top: at this
     width a 264px rail costs more than it gives. Nothing is hidden — the
     section list scrolls sideways inside itself rather than the page. */
  /* auto/1fr, or the single-column grid stretches the band to half the viewport
     trying to fill min-height:100vh. */
  .console-shell{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .sidebar{position:static;height:auto;flex-direction:row;flex-wrap:wrap;
    align-items:center;gap:var(--space-2) var(--space-3);
    padding:var(--space-2) var(--space-3);
    border-right:0;border-bottom:1px solid var(--border)}
  .sidebar-head{flex-direction:row;align-items:center;gap:var(--space-3);
    padding-bottom:0;border-bottom:0;margin-right:auto}
  .sidebar-nav{flex-direction:row;order:3;width:100%;gap:var(--space-1);
    overflow-x:auto;scrollbar-width:thin}
  .sidebar-nav a{flex:none;min-height:44px}
  .sidebar-nav a[aria-current="page"]::before{left:50%;top:auto;bottom:0;
    transform:translateX(-50%);width:60%;height:3px;
    border-radius:var(--r-sm) var(--r-sm) 0 0}
  /* The session controls join the header row rather than claiming one of their
     own: at this width every row costs the content below it. */
  .sidebar-foot{margin-top:0;padding-top:0;border-top:0;order:2}
  .console-wrap{padding:var(--space-5) var(--space-3) var(--space-7)}
}
@media (max-width:600px){
  .sidebar .back-link span{display:none}
  .back-link{min-height:44px}
}

/* ---- the categories editor (phase 2) ------------------------------------
   The table is a form laid out in columns, so every cell aligns to the top of
   its row and every column to its own header — the Priority and Users columns
   were centring their content under a left-aligned <th>.

   EVERY column rule below is scoped to tr[data-cat] — a category row. The
   table's LAST row is not one: it is the add-a-category row, and its cells do
   not line up with the columns (a name field, then one cell spanning the rest).
   Unscoped, "column 2 never wraps" reached that row's hint paragraph and held
   656px of Ukrainian on one line inside a 178px cell, at every width, since
   phase 2. Found by scripts/responsive-sweep.js on 2026-08-27. */
#categories td{vertical-align:middle}
/* The two house-key chips belong on one line. Letting them wrap made every row
   two chips tall for no information gained. */
#categories tr[data-cat] td:nth-child(2){white-space:nowrap;width:1%}
/* The two checkbox columns (Priority, Brand Kit) and Users: shrink to fit. */
#categories tr[data-cat] td:nth-child(3),#categories th:nth-child(3),
#categories tr[data-cat] td:nth-child(4),#categories th:nth-child(4),
#categories tr[data-cat] td:nth-child(7),#categories th:nth-child(7){
  text-align:left;width:1%;white-space:nowrap}
/* The two cap fields. */
#categories tr[data-cat] td:nth-child(5),#categories tr[data-cat] td:nth-child(6),
#categories th:nth-child(5),#categories th:nth-child(6){width:1%;white-space:nowrap}
/* Name and its default marker read as one thing on one line. */
.cat-name{display:flex;align-items:center;gap:var(--space-2);min-width:0}
.cat-name input{min-width:130px}
.cat-name .pill{flex:none}

/* ---- categories table: fit the card, don't scroll sideways ---------------
   Measured at 1024px: the table wanted 1099px inside a 662px card, so the
   Save and Delete buttons on every row sat off-screen behind a horizontal
   scroll. The row's primary action should not need to be hunted for.
        Назва 222 · Доступ 216 · Пріоритет 108 · Ліміт $ 136
        Перевірок 136 · Користувачі 106 · дії 175   = 1099
   Fixed layout with proportional columns, the two provider chips stacked
   instead of side by side, and the row actions allowed to wrap. Markup and
   every data- attribute are unchanged — this is styling only. */
.cats-table{table-layout:fixed;width:100%}
.cats-table th,.cats-table td{overflow-wrap:anywhere}
/* EIGHT columns since 2026-08-27, when Brand Kit joined at position 4 and
   pushed the caps, Users and the row actions along by one. These percentages
   are the proportions the 1024px measurement above produced, re-cut for the
   extra column; they still sum to 100.
     1 name · 2 house access · 3 priority · 4 brand kit
     5 $ cap · 6 review cap · 7 users · 8 actions */
.cats-table th:nth-child(1),.cats-table td:nth-child(1){width:20%}
.cats-table th:nth-child(2),.cats-table td:nth-child(2){width:14%}
.cats-table th:nth-child(3),.cats-table td:nth-child(3){width:8%}
.cats-table th:nth-child(4),.cats-table td:nth-child(4){width:8%}
.cats-table th:nth-child(5),.cats-table td:nth-child(5){width:12%}
.cats-table th:nth-child(6),.cats-table td:nth-child(6){width:12%}
.cats-table th:nth-child(7),.cats-table td:nth-child(7){width:8%}
.cats-table th:nth-child(8),.cats-table td:nth-child(8){width:18%}
/* Inputs stop asserting an intrinsic width of their own. */
.cats-table input[type=text],.cats-table input:not([type]),.cats-table .cap-field{
  width:100%;min-width:0;box-sizing:border-box}
.cats-table .cat-name{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0}
.cats-table .cat-name input{flex:1 1 90px}
/* Two provider chips stacked: side by side they were the second-widest column
   in the table and neither label is long. */
.cats-table td:nth-child(2) .chip{display:flex;width:100%;margin:2px 0}
/* The row actions wrap rather than forcing the table wider than its card. */
.cats-table td:nth-child(8) .row{flex-wrap:wrap;gap:6px}
.cats-table td:nth-child(8) button{flex:1 1 auto;padding-left:10px;padding-right:10px}
/* The add row's button carries the longest label in the table
   ("Додати категорію") and buttons do not wrap by default, so it was the one
   thing still pushing past the card at 1024px. Let it wrap. */
.cats-table button{white-space:normal;min-width:0}

/* ---- the categories table below 900px -----------------------------------
   Eight columns of controls do not fit a card that is itself ~330px wide, and
   "fit the card" stops being the right answer there: proportional widths made
   the name input 40px (cutting off "Marketer") and each cap input 24px (five
   characters), which is a control nobody can use. Measured at 375px by
   scripts/responsive-sweep.js.

   So below 900px the table sizes to its content and scrolls inside the
   .scroll container it is already wrapped in. That is the shell's existing
   idiom for a wide table, and it keeps shell.md's actual rule — the PAGE does
   not scroll horizontally at 375px, the container does. Every control keeps a
   width it can be typed into. */
@media (max-width:900px){
  .cats-table{table-layout:auto;width:auto;min-width:100%}
  .cats-table th,.cats-table td{white-space:nowrap;overflow-wrap:normal}
  /* The percentages above are meaningless under auto layout and would fight
     the content-based sizing that replaces them. */
  .cats-table th:nth-child(1),.cats-table td:nth-child(1),
  .cats-table th:nth-child(2),.cats-table td:nth-child(2),
  .cats-table th:nth-child(3),.cats-table td:nth-child(3),
  .cats-table th:nth-child(4),.cats-table td:nth-child(4),
  .cats-table th:nth-child(5),.cats-table td:nth-child(5),
  .cats-table th:nth-child(6),.cats-table td:nth-child(6),
  .cats-table th:nth-child(7),.cats-table td:nth-child(7),
  .cats-table th:nth-child(8),.cats-table td:nth-child(8){width:auto}
  .cats-table .cat-name input{flex:0 0 auto;width:150px;min-width:150px}
  .cats-table .cap-field{width:104px;min-width:104px}
  /* The row actions go back to one line: wrapping them made the row taller
     without making the table narrower, now that the table may be wider than
     the card. */
  .cats-table td:nth-child(8) .row{flex-wrap:nowrap}
  .cats-table td:nth-child(8) button{flex:0 0 auto;white-space:nowrap}
  /* …but the add row's hint is prose and must still wrap, or it sets the
     table's width all by itself. */
  #categories tr:not([data-cat]) td{white-space:normal;overflow-wrap:anywhere;
    max-width:340px}
}

/* A review that could not search everything says so, in the place the absence
   is felt. Warning-toned, not error-toned: nothing failed, the coverage was
   narrower than the reader might assume. */
.portal-coverage-notice {
  margin: 0 0 14px; padding: 10px 13px;
  border: 1px solid var(--warn);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: var(--warn-soft);
  color: var(--ink);
  font-size: 13.5px; line-height: 1.45;
}

.review-history-filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,160px),1fr));gap:12px;margin-top:12px}
.review-history-filters label{margin:0;min-width:0}
.review-history-filters input,.review-history-filters select{width:100%;min-width:0}

/* Settings: the accounting detail behind a disclosure (settings.html). */
.usage-detail{margin-top:var(--space-3);font-size:12.5px;line-height:1.6}
.usage-detail summary{cursor:pointer;color:var(--accent-text);font-weight:600}
.usage-detail[open] summary{margin-bottom:var(--space-2)}

/* Phones: the workspace bar keeps one row — the console crossing shows its
   icon (its name stays as the accessible label and tooltip). */
@media (max-width:480px){
  .topbar{flex-wrap:nowrap;gap:6px;padding-inline:10px}
  .topbar .to-console span{display:none}
  .topbar .to-console{padding:9px 10px}
  .topbar #signout{padding-inline:10px}
  .topbar .back-link{min-width:36px;justify-content:center}
  .topbar .back-link+.brand{padding-left:6px}
  .topbar .brand-logo{height:18px}
  .topbar .langs button{padding-inline:9px}
}
