/* console/style.css — Norn Console shell.
 * One file, one token pair (daylight / dark) via CSS custom properties, overridden by a `data-theme`
 * attribute the toggle stamps on <html>. `@media (prefers-color-scheme: dark)` supplies the default
 * when the visitor hasn't chosen, `[data-theme="dark"]` / `[data-theme="light"]` always win over it.
 */

/* ---------------------------------------------------------------------------------------------
   Reset
--------------------------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, div, span, h1, h2, h3, h4, p, ul, ol, li, form, fieldset, table, th, td, a, button, input, select, label, pre, code {
  margin: 0; padding: 0; font: inherit; color: inherit; border: 0; vertical-align: baseline;
}
html { -webkit-text-size-adjust: 100%; }
button, input, select { background: none; }
button { cursor: pointer; }
a { text-decoration: none; }
table { border-collapse: collapse; width: 100%; }
ul, ol { list-style: none; }
img, svg { display: block; max-width: 100%; }

/* ---------------------------------------------------------------------------------------------
   Tokens
--------------------------------------------------------------------------------------------- */
:root {
  --paper: #ffffff;
  --card: #f2f3f7;
  --ink: #20242c;
  --muted: #646b76;
  --rule: #e6e8ee;
  --accent: #5a51e8;
  --accent-bg: #edebfc;
  --accent-ink: #ffffff;
  --canvas-image: none;
  --canvas-size: 18px 18px;
  --shadow-card: 0 1px 3px rgba(32, 36, 44, 0.08);
  --shadow-raised: 0 4px 16px rgba(32, 36, 44, 0.12);
  --focus-ring: 0 0 0 3px rgba(90, 81, 232, 0.35);

  --decision-allow: #0ca30c;
  --decision-allow-bg: #e6f7e6;
  --decision-review: #fab219;
  --decision-review-bg: #fff4de;
  --decision-block: #d03b3b;
  --decision-block-bg: #fbe7e7;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR", "Malgun Gothic", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #0c1118;
    --card: #1a2434;
    --ink: #e6ebf2;
    --muted: #8a93a3;
    --rule: #232d3a;
    --accent: #4c8dff;
    --accent-bg: rgba(76, 141, 255, 0.16);
    --accent-ink: #ffffff;
    --canvas-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-raised: 0 6px 20px rgba(0, 0, 0, 0.45);
    --focus-ring: 0 0 0 3px rgba(76, 141, 255, 0.45);

    --decision-allow: #35c93b;
    --decision-allow-bg: rgba(53, 201, 59, 0.14);
    --decision-review: #fabb3d;
    --decision-review-bg: rgba(250, 187, 61, 0.14);
    --decision-block: #ef5a5a;
    --decision-block-bg: rgba(239, 90, 90, 0.14);
  }
}

:root[data-theme="dark"] {
  --paper: #0c1118;
  --card: #1a2434;
  --ink: #e6ebf2;
  --muted: #8a93a3;
  --rule: #232d3a;
  --accent: #4c8dff;
  --accent-bg: rgba(76, 141, 255, 0.16);
  --accent-ink: #ffffff;
  --canvas-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-raised: 0 6px 20px rgba(0, 0, 0, 0.45);
  --focus-ring: 0 0 0 3px rgba(76, 141, 255, 0.45);

  --decision-allow: #35c93b;
  --decision-allow-bg: rgba(53, 201, 59, 0.14);
  --decision-review: #fabb3d;
  --decision-review-bg: rgba(250, 187, 61, 0.14);
  --decision-block: #ef5a5a;
  --decision-block-bg: rgba(239, 90, 90, 0.14);
}

:root[data-theme="light"] {
  --paper: #ffffff;
  --card: #f2f3f7;
  --ink: #20242c;
  --muted: #646b76;
  --rule: #e6e8ee;
  --accent: #5a51e8;
  --accent-bg: #edebfc;
  --accent-ink: #ffffff;
  --canvas-image: none;
  --shadow-card: 0 1px 3px rgba(32, 36, 44, 0.08);
  --shadow-raised: 0 4px 16px rgba(32, 36, 44, 0.12);
  --focus-ring: 0 0 0 3px rgba(90, 81, 232, 0.35);

  --decision-allow: #0ca30c;
  --decision-allow-bg: #e6f7e6;
  --decision-review: #fab219;
  --decision-review-bg: #fff4de;
  --decision-block: #d03b3b;
  --decision-block-bg: #fbe7e7;
}

/* ---------------------------------------------------------------------------------------------
   Base
--------------------------------------------------------------------------------------------- */
html, body { height: 100%; }
body {
  font-family: var(--font-sans);
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
  background-color: var(--paper);
  background-image: var(--canvas-image);
  background-size: var(--canvas-size);
  min-height: 100%;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, [tabindex]:focus-visible {
  box-shadow: var(--focus-ring);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}

/* ---------------------------------------------------------------------------------------------
   App shell
--------------------------------------------------------------------------------------------- */
:root {
  --rail-w-expanded: 248px;
  --rail-w-collapsed: 64px;
  --rail-w: var(--rail-w-expanded);
  --panel-w: 0px;
  --topbar-h: 56px;
}
:root[data-rail="collapsed"] { --rail-w: var(--rail-w-collapsed); }

/* The setup/tutorial panel is a FLOATING overlay (see .app-panel below), deliberately NOT a grid
   column: a column that appears/disappears resizes the whole shell, so the topbar and main content
   visibly jump ("the header jitters") every time the panel toggles or the tutorial page hides it.
   An overlay changes nothing about the layout underneath — the tutorial reads as floating above the
   app. --panel-w survives only as a legacy no-op for anything still referencing it. */
#app {
  min-height: 100%;
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) 1fr auto;
  grid-template-areas:
    "rail topbar"
    "rail main"
    "rail footer";
}

/* pre-login (login / invite): no rail, no panel */
#app[data-shell="auth"] {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "topbar" "main" "footer";
}
#app[data-shell="auth"] .app-rail,
#app[data-shell="auth"] .app-panel,
#app[data-shell="auth"] .rail-scrim { display: none; }

.app-rail   { grid-area: rail;   border-right: 1px solid var(--rule); background-color: var(--card); }
.app-topbar { grid-area: topbar; height: var(--topbar-h); display: flex; align-items: center; gap: 10px;
              padding: 0 20px; border-bottom: 1px solid var(--rule); background-color: var(--paper); }
.app-main   { grid-area: main; }
.app-footer { grid-area: footer; }
.app-panel {
  position: fixed; top: calc(var(--topbar-h) + 16px); right: 16px; width: 300px;
  max-height: calc(100vh - var(--topbar-h) - 32px); overflow-y: auto;
  border: 1px solid var(--rule); border-radius: var(--radius-lg); background-color: var(--card);
  box-shadow: var(--shadow-raised); z-index: 60;
}
/* On narrower viewports the top-right card sits exactly over page-head actions (the blind walkthrough
   found it covering the very "Mint new key" button it points at). Below 1600px, dock it bottom-right
   instead — page heads and their primary buttons live at the top; the bottom edge is the least
   action-dense area. Wide monitors keep the top-right placement, where nothing overlaps. */
@media (max-width: 1600px) {
  .app-panel { top: auto; bottom: 20px; max-height: calc(100vh - var(--topbar-h) - 40px); }
}

/* full-height columns whose contents pin while main scrolls */
.rail-inner  { position: sticky; top: 0; max-height: 100vh; overflow-y: auto;
               display: flex; flex-direction: column; gap: 6px; padding: 12px 10px 20px; }
.panel-inner { padding: 18px 16px; }

.app-main { max-width: 1080px; margin: 0 auto; padding: 22px 28px 60px; width: 100%; }
/* Data-dense views (compliance library, decisions list — see render()'s isWideView) opt out of the
   comfortable prose measure: a wall of table columns reads better using the screen it's given, where a
   settings form or a decision's detail page would only get harder to read wider. Still capped, not
   edge-to-edge — 1600px matches the breakpoint the app already treats as "wide monitor" elsewhere (see
   .app-panel above) — and every table inside keeps scrolling in its own .table-wrap, never the page
   body. */
.app-main.view-wide { max-width: 1600px; }
.skip-link { position: absolute; top: 8px; left: 8px; z-index: 200; padding: 8px 12px;
             background-color: var(--card); border: 1px solid var(--accent); border-radius: var(--radius-sm); }
.rail-scrim { display: none; }
.brand { display: flex; align-items: center; gap: 10px; margin-right: 6px; }
.brand-tile {
  width: 30px; height: 30px; border-radius: 9px; background: var(--accent); color: var(--accent-ink);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; flex-shrink: 0;
}
.brand-name { font-weight: 700; font-size: 15px; letter-spacing: -0.01em; white-space: nowrap; }

.tabs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; flex: 1; }
.tab {
  display: inline-flex; align-items: center; padding: 7px 13px; border-radius: 999px;
  font-size: 13.5px; font-weight: 500; color: var(--muted); white-space: nowrap;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.tab:hover { color: var(--ink); background-color: var(--accent-bg); }
.tab.active { color: var(--accent); font-weight: 700; }
@media (prefers-color-scheme: dark) {
  .tab.active { color: var(--ink); font-weight: 600; background-color: var(--card); box-shadow: var(--shadow-raised); }
}
:root[data-theme="dark"] .tab.active { color: var(--ink); font-weight: 600; background-color: var(--card); box-shadow: var(--shadow-raised); }
:root[data-theme="light"] .tab.active { color: var(--accent); font-weight: 700; background-color: transparent; box-shadow: none; }

.header-right { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 5px 11px 5px 5px; border-radius: 999px;
  border: 1px solid var(--rule); font-size: 13px; background-color: var(--card);
}
.chip-tile {
  width: 22px; height: 22px; border-radius: 999px; background: var(--accent-bg); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0;
}
.help-btn {
  width: 30px; height: 30px; border-radius: 999px; border: 1px solid var(--rule); font-weight: 700;
  display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 13px;
}
.help-btn:hover { color: var(--ink); border-color: var(--accent); }

/* header segmented-toggle chip (theme: Light/Dark) — same chip shell as the org/user chips, two
   segmented options inside */
.chip.seg-chip { padding: 3px; gap: 2px; }
.seg-opt { padding: 4px 9px; border-radius: 999px; font-size: 12px; font-weight: 700; color: var(--muted); }
.seg-opt:hover { color: var(--ink); }
.seg-opt.active { background-color: var(--accent); color: var(--accent-ink); }

/* header language selector — a native <select>, styled to sit flush among the chip-shaped controls
   either side of it rather than looking like a stray form control */
.lang-select {
  border: 1px solid var(--rule); border-radius: 999px; padding: 6px 26px 6px 12px; font-size: 13px;
  font-weight: 600; color: var(--ink); background-color: var(--card);
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 13px center, right 8px center; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
  appearance: none; -webkit-appearance: none;
}
.lang-select:hover { border-color: var(--accent); }


/* The theme toggle and language selector used to live here (both now in the header — see .seg-chip/
   .lang-select above); nothing else does, so an empty footer takes up no space at all. */
.app-footer {
  border-top: 1px solid var(--rule); padding: 14px 20px; display: flex; gap: 18px; justify-content: center;
  align-items: center; font-size: 12.5px; color: var(--muted); flex-wrap: wrap;
}
.app-footer:empty { display: none; border-top: none; padding: 0; }

/* login / invite: slim chrome, no tabs */
.auth-header {
  border-bottom: 1px solid var(--rule); padding: 14px 20px; display: flex; align-items: center; justify-content: space-between;
}
.auth-shell { flex: 1; display: flex; align-items: center; justify-content: center; padding: 40px 20px; }
.auth-card { width: 100%; max-width: 380px; background-color: var(--card); border: 1px solid var(--rule);
  border-radius: var(--radius-lg); padding: 28px 26px; box-shadow: var(--shadow-card); }
.auth-card h1 { font-size: 18px; margin-bottom: 6px; }
.auth-card .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 20px; }

/* ---------------------------------------------------------------------------------------------
   Typography / page heads
--------------------------------------------------------------------------------------------- */
h1.page-title { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; }
h2.section-title { font-size: 15px; font-weight: 700; margin-bottom: 2px; }
.page-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 18px; flex-wrap: wrap; }
/* Two or more actions in a page head (get-started: Refresh + Reset tutorial) group into ONE flex row,
   so .page-head's space-between keeps title-left / actions-right instead of spreading the buttons apart,
   and a narrow viewport wraps the pair together rather than splitting it across two lines. */
.page-head-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.muted { color: var(--muted); }
.hint { color: var(--muted); font-size: 13px; }

/* ---------------------------------------------------------------------------------------------
   Cards / grids
--------------------------------------------------------------------------------------------- */
.card {
  background-color: var(--card); border: 1px solid var(--rule); border-radius: var(--radius-lg);
  padding: 18px 20px; box-shadow: var(--shadow-card); margin-bottom: 16px;
}
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 16px; }
.grid-2 { display: grid; grid-template-columns: 1.3fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 860px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } .grid-2 { grid-template-columns: 1fr; } }
@media (max-width: 480px) { .grid-4 { grid-template-columns: 1fr; } }
/* .grid-2's default 1.3fr/1fr split is deliberate for term-pair (a command beside its response — see
   that class's own comment): the left column carries the longer text. It is the wrong ratio for a pair
   of PEER cards that describe two options of the same thing (decision detail's Action/Signed-receipt,
   Flow's First-attempt/Remediated, Overview's Decision mix/Top block reasons) — those rendered at
   visibly different widths, and un-stretched align-items:start let them sit at different heights too.
   `.grid-2-equal` overrides both, opted into at each of those call sites rather than changed on the base
   class so term-pair (which DOES want 1.3fr/1fr) is untouched. */
.grid-2.grid-2-equal { grid-template-columns: 1fr 1fr; align-items: stretch; }
@media (max-width: 860px) { .grid-2.grid-2-equal { grid-template-columns: 1fr; } }

/* stat tile */
.stat-tile { cursor: pointer; display: block; text-align: left; }
.stat-tile .stat-label { font-size: 12.5px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.stat-tile .stat-value { font-size: 26px; font-weight: 700; margin-top: 6px; letter-spacing: -0.01em; }
.stat-tile.dec-allow .stat-value { color: var(--decision-allow); }
.stat-tile.dec-block .stat-value { color: var(--decision-block); }
.stat-tile.dec-review .stat-value { color: var(--decision-review); }

/* decision-mix bar */
.mix-bar { display: flex; gap: 2px; height: 14px; border-radius: 999px; overflow: hidden; margin: 10px 0 12px; }
.mix-seg { height: 100%; }
.mix-seg.allow { background-color: var(--decision-allow); }
.mix-seg.review { background-color: var(--decision-review); }
.mix-seg.block { background-color: var(--decision-block); }
.mix-legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12.5px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-dot { width: 9px; height: 9px; border-radius: 999px; flex-shrink: 0; }
.legend-dot.allow { background-color: var(--decision-allow); }
.legend-dot.review { background-color: var(--decision-review); }
.legend-dot.block { background-color: var(--decision-block); }

/* 14-day bar chart */
.bar-chart { display: flex; align-items: flex-end; gap: 4px; height: 110px; padding-top: 4px; }
.bar-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; min-width: 0; }
.bar-fill {
  width: 100%; max-width: 22px; background-color: var(--decision-block); border-radius: 4px 4px 2px 2px;
  min-height: 3px;
}
/* A zero-count day: a thin baseline tick ("zero but present"), never the same Math.max floor a tiny
   nonzero day gets ("small but present") — see the isZero branch in viewOverview()'s barsHtml, app.js. */
.bar-fill.zero { min-height: 2px; background-color: var(--rule); border-radius: 1px; }
.bar-col:hover .bar-fill, .bar-col:focus-visible .bar-fill { filter: brightness(1.12); }
.bar-day { font-size: 10px; color: var(--muted); margin-top: 6px; white-space: nowrap; }
.bar-tooltip {
  font-size: 12px; background-color: var(--ink); color: var(--paper); border-radius: var(--radius-sm);
  padding: 4px 8px; position: absolute; transform: translate(-50%, -100%); margin-top: -8px; pointer-events: none;
  white-space: nowrap; z-index: 20; box-shadow: var(--shadow-raised);
}
.bar-chart-wrap { position: relative; }

/* violations list — each row is ONE stable line: label, then the code as a mono chip, then the count,
   never a wrapping stack; see blockReasonRowHtml() in app.js */
.violations-list { display: flex; flex-direction: column; gap: 0; }
.violation-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 0;
  border-top: 1px solid var(--rule);
}
.violation-row:first-child { border-top: none; }
.violation-label {
  font-weight: 600; font-size: 13.5px; flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.violation-code { flex-shrink: 0; }
.violation-count { font-weight: 700; font-variant-numeric: tabular-nums; flex-shrink: 0; }

/* chain badge */
.chain-badge { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.chain-dot { width: 10px; height: 10px; border-radius: 999px; flex-shrink: 0; }
.chain-dot.ok { background-color: var(--decision-allow); }
.chain-dot.issue { background-color: var(--decision-block); }
.chain-head { font-family: var(--font-mono); font-size: 12px; color: var(--muted); word-break: break-all; }

/* ---------------------------------------------------------------------------------------------
   Tables
--------------------------------------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
table.data-table { width: 100%; font-size: 13.5px; }
table.data-table th {
  text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted);
  font-weight: 600; padding: 8px 12px; border-bottom: 1px solid var(--rule); white-space: nowrap;
}
table.data-table td { padding: 10px 12px; border-bottom: 1px solid var(--rule); vertical-align: middle; }
table.data-table tbody tr.clickable { cursor: pointer; }
table.data-table tbody tr.clickable:hover { background-color: var(--accent-bg); }
table.data-table tbody tr:last-child td { border-bottom: none; }

/* Excel-style column filters (compliance library): a second <thead> row whose controls sit under the
   column each one filters. The cells are <td> — they hold controls, not column names — and the whole
   table lives inside the filter <form>, the only parse-valid placement (a <form> may wrap a <table>,
   but may not sit between <table> and <tr>). */
table.data-table thead tr.filter-row td {
  padding: 6px 10px 10px; border-bottom: 1px solid var(--rule); vertical-align: top;
}
table.data-table thead tr.filter-row select,
table.data-table thead tr.filter-row input {
  width: 100%; max-width: 100%; font-size: 12.5px; padding: 5px 7px; font-family: inherit;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  background-color: var(--paper); color: var(--ink);
}
/* Every <select> here (standard/category/modality/status) lists EVERY value the whole library has, not
   just what the current filter shows — idx.families/idx.facets are fetched once (state.complianceIndex)
   and never change across a filter switch. A <select>'s own intrinsic content width (driven by its
   widest <option>, invisible until opened) is what the browser's auto table-layout falls back to when
   sizing this cell, because a percentage width cannot be resolved before the column width is known — so
   without this override the column stayed pinned near that catalog-wide widest option forever, no matter
   which standard was actually selected. A definite `width` (0 counts) is what that fallback consults
   instead; `min-width: 100%` then restores the real rendered width, so nothing looks different but the
   column is finally free to size off the rows on screen right now. Verified against a real Chromium
   layout: without this, switching from a long standard name to a short one left the Standard column
   unchanged (~745px); with it, the column tracks the visible content exactly like a from-scratch render
   with no hidden long options at all. */
table.data-table thead tr.filter-row select { width: 0; min-width: 100%; }
table.data-table thead tr.filter-row td.filter-search { min-width: 220px; display: flex; gap: 6px; }
table.data-table thead tr.filter-row td.filter-search input { flex: 1 1 auto; width: auto; }
/* Enter in the search box still applies it; the button (app.js) is now a REAL, focusable control that
   fires the identical form submit (onDocSubmit -> submitComplianceFilter), so a mouse-only or
   screen-reader user has the same path a keyboard Enter always had — no longer hidden/inert. */
.filter-row .filter-submit {
  flex: 0 0 auto; white-space: nowrap;
}
td.mono, .mono { font-family: var(--font-mono); font-size: 12.5px; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Compliance library table only: switching standard/status/family swings ref-code length wildly (a
   bare "5" next to "AC-2(1)(a)(iii)"), and now that the column widths genuinely reflow with each switch
   (see the filter-row select fix above), the Ref column could legitimately collapse to almost nothing on
   a short-ref standard. A stable floor keeps it readable without pinning it — everything past the floor
   still comes from content, same as every other column, and this is scoped to .compliance-table so it
   never touches the decisions/keys/members tables that also use .data-table. */
table.data-table.compliance-table td:first-child,
table.data-table.compliance-table th:first-child { min-width: 90px; }

/* decision / status pills — color always paired with a text label */
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; }
.pill.dec-allow { background-color: var(--decision-allow-bg); color: var(--decision-allow); }
.pill.dec-review { background-color: var(--decision-review-bg); color: var(--decision-review); }
.pill.dec-block { background-color: var(--decision-block-bg); color: var(--decision-block); }
.pill.neutral { background-color: var(--accent-bg); color: var(--accent); }
.pill.status-ok { background-color: var(--decision-allow-bg); color: var(--decision-allow); }
.pill.status-off { background-color: var(--rule); color: var(--muted); }

/* API key scope chips — 'verify' is the muted, unremarkable default; 'verify+rules' gets the accent
   treatment so the elevated (can author/import rules) scope always reads as the notable one. */
.pill.scope-verify { background-color: var(--rule); color: var(--muted); }
.pill.scope-rules { background-color: var(--accent-bg); color: var(--accent); }

/* Setup/tutorial pill — INCOMPLETE (solid accent fill, white text) vs COMPLETE (outline, muted text) */
.pill.setup-pill { text-decoration: none; }
.pill.setup-incomplete { background-color: var(--accent); color: var(--accent-ink); }
.pill.setup-complete { background-color: transparent; color: var(--muted); border: 1px solid var(--accent); }

/* ---------------------------------------------------------------------------------------------
   Forms / buttons
--------------------------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.field label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.field input, .field select, .field textarea {
  border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 9px 11px; font-size: 14px;
  background-color: var(--paper); color: var(--ink); font-family: inherit;
}
.field textarea { resize: vertical; }
.field-error { color: var(--decision-block); font-size: 12.5px; }

/* radio-option groups (e.g. the mint-key scope choice, the guard builder's value-mode choice) —
   ".field .radio-option" (not just ".radio-option") so this beats the ".field label" rule above (both
   match the same <label> element; the extra class is what wins the specificity tie, not source order).
   Each option is a bordered, clickable BOX whose whole surface reflects the checked state (accent
   border + tint via :has) — a bare radio dot next to loose text read as ambiguous, especially for the
   two-option value-mode row. */
.radio-group { display: flex; flex-direction: column; gap: 8px; margin-top: 2px; }
.radio-group.radio-row { flex-direction: row; flex-wrap: wrap; }
.radio-group.radio-row .radio-option { flex: 1 1 140px; }
.field .radio-option {
  display: flex; align-items: flex-start; gap: 9px; font-size: 13.5px; font-weight: 400; color: var(--ink);
  cursor: pointer; border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 9px 11px;
  background-color: var(--paper); transition: border-color 0.12s ease, background-color 0.12s ease;
}
.field .radio-option:hover { border-color: var(--accent); }
.field .radio-option:has(input[type="radio"]:checked) { border-color: var(--accent); background-color: var(--accent-bg); }
.radio-option input[type="radio"] { margin-top: 3px; flex-shrink: 0; accent-color: var(--accent); }
/* A role-restricted option (e.g. the mint-key scope choice's 'verify+rules' radio for an Operator) —
   dimmed, not-allowed cursor, but still VISIBLE with its "requires Admin" note, never hidden. */
.field .radio-option.disabled { opacity: 0.55; cursor: not-allowed; }
.field .radio-option.disabled:hover { border-color: var(--rule); }
.radio-option.disabled input[type="radio"] { cursor: not-allowed; }
.form-error { background-color: var(--decision-block-bg); color: var(--decision-block); border-radius: var(--radius-sm);
  padding: 9px 12px; font-size: 13px; margin-bottom: 14px; }
.form-notice { background-color: var(--accent-bg); color: var(--ink); border-radius: var(--radius-sm);
  padding: 9px 12px; font-size: 13px; margin-bottom: 14px; }
/* Specificity elicitation (add-rule modal): REVIEW-toned, not an error — the submit was not refused, the
   author was asked a question and can still proceed. Bordered so it reads as a block to act on rather
   than a tint to skim past. */
.form-prompt { background-color: var(--decision-review-bg); color: var(--ink);
  border: 1px solid var(--decision-review); border-radius: var(--radius-sm);
  padding: 11px 13px; font-size: 13px; margin-bottom: 14px; }
.form-prompt-title { font-weight: 700; margin-bottom: 4px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 9px 16px;
  border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 600; background-color: var(--accent);
  color: var(--accent-ink); transition: opacity 0.12s ease; border: 1px solid transparent;
}
.btn:hover { opacity: 0.9; }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }
.btn.secondary { background-color: transparent; color: var(--ink); border-color: var(--rule); }
.btn.secondary:hover { background-color: var(--accent-bg); border-color: var(--accent); }
.btn.danger { background-color: var(--decision-block); color: #fff; }
.btn.danger.outline { background-color: transparent; color: var(--decision-block); border-color: var(--decision-block); }
.btn.small { padding: 5px 11px; font-size: 12.5px; }
/* A deliberately QUIET button: .btn's geometry, muted-on-transparent, no border until hover. For an
   action that must be FINDABLE without competing — "Reset tutorial" sits beside Refresh and inside the
   completed banner on #/get-started, and neither place should read as a call to action. Hover resolves to
   the ACCENT, never the block red: the reset deletes nothing (it stamps a tutorial epoch), so the control
   must not imply a destructive act it does not perform. */
.btn.quiet { background-color: transparent; color: var(--muted); border-color: transparent; }
.btn.quiet:hover { color: var(--accent); border-color: var(--rule); opacity: 1; }
/* Compliance citation copy buttons: the pre-copy label (compliance_copy_cite) and post-copy
   feedback (citation_copied) are different lengths in every locale, so without a floor width the
   button visibly resizes when the label swaps. citation_copied is the longer of the two in en/ko/pl
   alike, so sizing to its longest translation (pl "Cytat skopiowany") covers every state in every
   language; nowrap keeps it from wrapping to a second line instead of widening. */
.btn-copy-cite { min-width: 160px; white-space: nowrap; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.select-inline { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; }
.select-inline select { border: 1px solid var(--rule); border-radius: var(--radius-sm); padding: 6px 9px; background-color: var(--paper); color: var(--ink); }

/* pagination */
.pagination { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 12px; flex-wrap: wrap; }

/* ---------------------------------------------------------------------------------------------
   Code / snippet
--------------------------------------------------------------------------------------------- */
.snippet {
  background-color: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-md);
  padding: 14px 16px; font-family: var(--font-mono); font-size: 12.5px; line-height: 1.6; overflow-x: auto;
  white-space: pre; color: var(--ink);
}
@media (prefers-color-scheme: dark) { .snippet { background-color: #0a0e14; } }
:root[data-theme="dark"] .snippet { background-color: #0a0e14; }
:root[data-theme="light"] .snippet { background-color: var(--paper); }
.snippet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.snippet-title { font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.ph { color: var(--accent); font-weight: 700; }
/* The one-line explainer under a MISO snippet (what GATE_HOST is, and this workspace's real value).
   Inside .empty-state — which centres its text and pins the snippet to 560px — it has to track the
   snippet's box rather than the card's, or it runs the full card width under a narrow code block. */
.snippet-hint { margin-top: 8px; }
.empty-state .snippet-hint { max-width: 560px; margin-left: auto; margin-right: auto; }

/* Terminal-first tutorial: a runnable command beside the response it produces. Column widths come
   from .grid-2 (1.3fr/1fr — the command is the longer text) and so does the stack below 860px, which
   puts the command first on a phone. */
.term-pair { margin-top: 10px; }
/* Grid items default to min-width:auto, so a long curl would widen its own track and push the page
   into a horizontal scroll; min-width:0 plus wrapping keeps it inside the column instead. */
.term-block { min-width: 0; }
.term-snippet { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------------------------
   Empty / not-found / no-access states
--------------------------------------------------------------------------------------------- */
.empty-state { text-align: center; padding: 34px 20px; }
.empty-state .empty-title { font-size: 15px; font-weight: 700; margin-bottom: 6px; }
.empty-state .empty-body { color: var(--muted); font-size: 13.5px; margin-bottom: 16px; }
.empty-state .snippet { text-align: left; max-width: 560px; margin: 0 auto; }

.center-state { display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 60px 20px; gap: 8px; }
.center-state .icon { width: 46px; height: 46px; border-radius: 999px; background-color: var(--accent-bg);
  color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 700;
  margin-bottom: 6px; }

/* ---------------------------------------------------------------------------------------------
   Modal
--------------------------------------------------------------------------------------------- */
.modal-overlay {
  position: fixed; inset: 0; background-color: rgba(10, 12, 16, 0.5); display: flex; align-items: center;
  justify-content: center; padding: 20px; z-index: 100;
}
.modal-card {
  position: relative;
  width: 100%; max-width: 440px; background-color: var(--card); border: 1px solid var(--rule);
  border-radius: var(--radius-lg); padding: 22px 24px; box-shadow: var(--shadow-raised);
  max-height: calc(100vh - 40px); overflow-y: auto;
}
/* the add-rule modal: two snippet previews (composed guard + verify call) need more line length */
.modal-card.wide { max-width: 620px; }
.modal-card h2 { font-size: 16px; margin-bottom: 4px; }
/* only the card's OWN heading reserves room for the ✕ — an h2.section-title nested in a card inside the
   modal is nowhere near it */
.modal-card > h2 { padding-right: 34px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* The explicit dismiss affordance every modal now carries (renderModal). Sits in the card's top-right
   padding corner; quiet until hovered, because it is a fallback for Escape / the overlay click, not the
   primary action. */
.modal-close-x {
  position: absolute; top: 8px; right: 10px; width: 30px; height: 30px; z-index: 1;
  display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1;
  border-radius: var(--radius-sm); color: var(--muted); border: 1px solid transparent;
  transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}
.modal-close-x:hover { color: var(--ink); background-color: var(--accent-bg); border-color: var(--rule); }

/* Add-rule dialog: the two numbered halves — "1 · what this rule declares" (prose a reviewer reads) and
   "2 · what the machine verifies" (the checkable condition). The rule between them is load-bearing: it
   is the whole point that these are different KINDS of statement, not two groups of form fields. */
.modal-section + .modal-section { border-top: 1px solid var(--rule); margin-top: 20px; padding-top: 16px; }
.modal-section-title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; }
.modal-section-desc { margin-top: 3px; margin-bottom: 12px; }

/* Guided authoring ("Guide me"): an OPTIONAL panel above the two sections, never a second dialog. It
   renders no field of its own — the ring below is how it points at a control that already exists, which
   is the whole reason it can be exited at any step without losing a keystroke. Sticky inside the
   scrolling .modal-card, because the control a step is about can be a full scroll away from the sentence
   explaining it. */
.ra-guide { position: sticky; top: 0; z-index: 3; }
.ra-guide:not(:empty) {
  background-color: var(--accent-bg); border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 10px 12px; margin-bottom: 14px;
}
.ra-guide-offer { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ra-guide-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.ra-guide-head .btn { margin-left: auto; align-self: flex-start; }
.ra-guide-count { color: var(--muted); font-size: 12.5px; }
.ra-guide-title { font-weight: 700; font-size: 14px; }
.ra-guide-say { font-size: 13.5px; }
/* An honesty line (an expressiveness limit, an irreversible declaration, a dry run that cannot run) is
   NOT a muted hint: it is the part of the step the author is most likely to be surprised by later, so it
   keeps full ink and carries a rule of its own. */
.ra-guide-note {
  font-size: 13px; color: var(--ink); margin-top: 6px;
  border-left: 2px solid var(--accent); padding-left: 8px;
}
.ra-guide-ack { margin-top: 6px; }
.ra-guide-needs { margin-top: 8px; }
.ra-guide-needs ul { margin: 4px 0 0 18px; font-size: 13px; color: var(--muted); }
.ra-guide-ladder { margin: 6px 0 0 18px; font-size: 13.5px; }
.ra-guide-ladder li { margin-top: 6px; }
.ra-guide-actions { margin-top: 10px; }
/* The one ring, moved from step to step. outline rather than border so nothing reflows as it moves. */
.ra-guide-target { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-sm); }

.keychip {
  font-family: var(--font-mono); font-size: 12.5px; word-break: break-all; background-color: var(--paper);
  border: 1px dashed var(--accent); border-radius: var(--radius-sm); padding: 10px 12px; margin: 10px 0;
}

/* ---------------------------------------------------------------------------------------------
   Get-started checklist
--------------------------------------------------------------------------------------------- */
.step-list { display: flex; flex-direction: column; gap: 12px; }
.step { display: flex; gap: 12px; }
.step-check {
  width: 24px; height: 24px; border-radius: 999px; border: 2px solid var(--rule); flex-shrink: 0; margin-top: 2px;
  display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; color: transparent;
}
.step.done .step-check { background-color: var(--decision-allow); border-color: var(--decision-allow); color: #fff; }
.step-body { flex: 1; }
.step-title { font-weight: 700; font-size: 14.5px; margin-bottom: 3px; }
.step-desc { color: var(--muted); font-size: 13.5px; margin-bottom: 8px; }
/* A completed step's title doubles as its own expand/collapse control (data-action=
   "getstarted-step-toggle"), so it needs button-reset layout instead of the plain heading a still-open
   .step-title div is for an in-progress step. The chevron reuses .workflow-chevron's exact metrics so
   this console's two accordions (Rules' workflow cards, this one) share one affordance. */
.step-title-toggle { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; }
.step-title-toggle:hover { color: var(--accent); }
.step-chevron { color: var(--muted); width: 14px; display: inline-block; text-align: center; flex-shrink: 0; }
/* The one instruction a step leads with, above its commands (mint_key's "one header, nothing else").
   Tinted rather than muted: it has to out-rank the terminal block underneath it, which is the whole
   point of demoting the session curl. */
.step-note {
  font-size: 13.5px; color: var(--ink); margin-bottom: 10px; padding: 8px 10px;
  background-color: var(--accent-bg); border: 1px solid var(--rule); border-radius: var(--radius-sm);
}
/* "Docs:" row -- links out to the published Norn documentation, always in a new tab. Underlined on
   purpose: the reset drops underlines site-wide, and an accent-coloured link inside a muted step body
   is not findable enough on its own. */
.doc-links { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; margin-top: 10px; font-size: 12.5px; }
.doc-links-label { color: var(--muted); }
.doc-link { color: var(--accent); text-decoration: underline; }
.doc-link:hover { opacity: 0.85; }
/* Promotion flow (Commit -> Conformance -> Adopt) reuses the get-started .step/.step-list shell, plus a
   FAILED variant .step.done doesn't need — a soundness-check failure is explained inline (see
   promote_commit_fail), never silently disabled. */
.step.failed .step-check { background-color: var(--decision-block); border-color: var(--decision-block); color: #fff; }

/* ---------------------------------------------------------------------------------------------
   Decisions: List/Tree/Flow segmented view switcher — one bordered/filled group directly under the
   page title, equal-width segments, the active one filled solid with the accent. role="tablist"/
   role="tab"/aria-selected in the markup; arrow/Home/End roving-tabindex focus movement lives in
   app.js's onDocKeydown.
--------------------------------------------------------------------------------------------- */
.view-switch {
  display: inline-flex; gap: 2px; padding: 3px; margin-bottom: 16px;
  border: 1px solid var(--rule); border-radius: var(--radius-md); background-color: var(--card);
}
.view-switch-tab {
  flex: 1 1 100px; text-align: center; padding: 8px 18px; border-radius: calc(var(--radius-md) - 3px);
  font-size: 13.5px; font-weight: 600; color: var(--muted); white-space: nowrap;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.view-switch-tab:hover { color: var(--ink); }
.view-switch-tab.active { background-color: var(--accent); color: var(--accent-ink); }
@media (max-width: 480px) { .view-switch { display: flex; } }

/* ---------------------------------------------------------------------------------------------
   Rules: workflow breakdown cards
--------------------------------------------------------------------------------------------- */
.workflow-card-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.workflow-card-head {
  display: flex; align-items: center; gap: 8px; font-size: 16.5px; font-weight: 700; letter-spacing: -0.01em; text-align: left;
  flex: 1; min-width: 0;
}
.workflow-card-head:hover { color: var(--accent); }
.workflow-chevron { color: var(--muted); width: 14px; display: inline-block; text-align: center; flex-shrink: 0; }
.workflow-card-meta { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 6px; }
.workflow-tools { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.chip.small-chip { padding: 3px 9px; font-size: 11.5px; }
.workflow-rules { margin-top: 12px; }
/* Workflow -> TOOL -> rules: the card's rules are grouped under the tool they guard, the same spine the
   decisions Tree draws. A left rule (not a nested card) carries the hierarchy so the page keeps ONE
   card border per workflow however many tools it has. */
.tool-group { margin-top: 16px; padding-left: 14px; border-left: 3px solid var(--muted); }
.tool-group-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.tool-group-ident { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0; }
.tool-name { font-size: 13.5px; font-weight: 700; word-break: break-all; }
.tool-group .table-wrap { margin-top: 8px; }
.tool-unguarded { margin-top: 8px; }
.tool-group table.data-table { font-size: 12.5px; }
.tool-group table.data-table td { padding: 8px 10px; }
.tool-fields { margin-top: 10px; }
.tool-fields-label { display: block; margin-bottom: 6px; }
.tool-fields-hint { margin-top: 6px; }
/* Same-route 'Add your first rule' next-step click (setup panel): the hash does not change, so this
   pulse is the only feedback the click gets, on whichever tool group's Add rule button it scrolled to. */
.setup-pulse { animation: setup-pulse-glow 2s ease-out 1; }
@keyframes setup-pulse-glow {
  0%   { box-shadow: 0 0 0 0 var(--accent); }
  20%  { box-shadow: 0 0 0 6px var(--accent-bg); }
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* "This tool already has rules" — a confirm-style block inside the card, never a second modal. */
.tool-add-notice {
  margin-top: 10px; padding: 12px 14px; border-radius: var(--radius-sm);
  background-color: var(--accent-bg); color: var(--ink);
}
.tool-add-notice-lead { font-size: 13px; font-weight: 600; }
.tool-add-notice-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; font-size: 13px; }
.tool-add-notice-list li { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tool-add-notice-promote { font-weight: 600; }
.tool-add-notice .btn-row { margin-top: 12px; }
/* WORKFLOW SURFACE PANEL: declared tools + field vocabulary, read-only — same .workflow-tools chip row
   the tools list already uses, reused for the field:type chips too. */
.workflow-surface { border-top: 1px solid var(--rule); margin-top: 12px; padding-top: 4px; }
/* Agent-app integration (OpenAPI) — native <details> so the generated YAML stays folded until asked
   for; the summary is styled as a plain link-ish row, not a button, to keep the card quiet. */
.integration-details { margin-top: 12px; border-top: 1px solid var(--rule); padding-top: 10px; }
.integration-details summary {
  cursor: pointer; font-size: 15px; font-weight: 700; color: var(--ink); list-style: none;
}
.integration-details summary:hover { color: var(--accent); }
.integration-details summary::before { content: "▸ "; color: var(--muted); }
.integration-details[open] summary::before { content: "▾ "; }
.integration-details summary::-webkit-details-marker { display: none; }

/* Structured guard builder: field + operator side by side, wrapping on narrow viewports. */
.guard-builder-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.guard-builder-row select, .guard-builder-row input { flex: 1 1 140px; }

/* Citation typeahead in the add-rule dialog. The list sits IN FLOW directly under #ra-cite rather
   than floating over it: the dialog scrolls, and an absolutely-positioned menu detaches from its box
   on the first scroll. Capped at roughly four rows with its own scroll so it never pushes the guard
   builder off screen. `[hidden]` needs saying out loud — the display:flex above would otherwise beat
   the UA stylesheet's own [hidden] rule. */
.cite-suggest {
  display: flex; flex-direction: column; margin-top: 4px; max-height: 168px; overflow-y: auto;
  border: 1px solid var(--rule); border-radius: var(--radius-sm); background-color: var(--paper);
}
.cite-suggest[hidden] { display: none; }
.cite-suggest-item {
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start; text-align: left;
  padding: 7px 11px; font-size: 13px; border-bottom: 1px solid var(--rule);
}
.cite-suggest-item:last-child { border-bottom: 0; }
.cite-suggest-item:hover, .cite-suggest-item:focus { background-color: var(--accent-bg); }
.cite-suggest-title { color: var(--muted); font-size: 12.5px; }
/* The live resolution marker under the box: green when the typed citation resolves against the
   library, the plain muted hint when it doesn't — free text stays allowed, so this is never an error
   state and never gets the red treatment. */
.cite-resolved { color: var(--decision-allow); }

/* ---------------------------------------------------------------------------------------------
   Data & erasure
--------------------------------------------------------------------------------------------- */
.truth-list {
  background-color: var(--decision-block-bg); border: 1px solid var(--decision-block);
  border-radius: var(--radius-md); padding: 12px 16px; margin-bottom: 16px;
}
.truth-list-title { font-weight: 700; font-size: 13.5px; color: var(--decision-block); margin-bottom: 6px; }
.truth-list ul { display: flex; flex-direction: column; gap: 5px; }
.truth-list li { font-size: 13px; padding-left: 16px; position: relative; }
.truth-list li::before { content: "▸"; position: absolute; left: 0; color: var(--decision-block); }
/* CALM variant of the same block, for a "this is what will happen" list whose contents are NOT
   destructive — the tutorial-reset dialog, where nothing is deleted. Identical geometry and rhythm,
   accent instead of block red, so a reassuring list never renders in the erasure dialog's warning
   colours. */
.truth-list.calm { background-color: var(--accent-bg); border-color: var(--accent); }
.truth-list.calm .truth-list-title { color: var(--accent); }
.truth-list.calm li::before { color: var(--accent); }
.erase-countdown { font-size: 18px; font-weight: 700; }

/* ---------------------------------------------------------------------------------------------
   Decisions: Tree (hand-rolled SVG, workflow -> tool -> decision -> reason)
   Count is carried by node HEIGHT alone (see app.js's treeColumnHeights/treeWaterFill): every node
   gets a MINIMUM height (enough for one line of .tree-label — see TREE_MIN_NODE_H/treeColumnFloor), so
   a tiny-count node never squishes its label away, and above that floor height is proportional to
   count — plus the existing .tree-link ribbon thickness between columns, which follows the same
   proportions. The inner .tree-node bar fills its .tree-node-track backdrop at a CONSTANT width (see
   treeNodeHtml); it used to also vary its width by count, but that duplicated the height signal and was
   reverted. .tree-svg opts this chart out of the app-wide `svg { max-width: 100% }` reset (see the Base
   section above) so it renders at its natural size and scrolls horizontally in .table-wrap instead of
   shrinking/squashing on narrow viewports.

   The geometry that decides whether a workflow/tool and a decision/reason read as PAIRS lives in app.js
   (band floors, ribbon floors, and children stacked against their parent's own top edge — see the block
   comment above TREE_H), not here: this chart is SVG, so line-height and padding have no purchase on it.
--------------------------------------------------------------------------------------------- */
.tree-svg { max-width: none; }
.tree-col-header { font-size: 11px; font-weight: 700; fill: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; }
.tree-label { font-size: 11px; fill: var(--ink); font-family: var(--font-sans); }
.tree-node-track { fill: var(--rule); }
.tree-node { fill: var(--accent); opacity: 0.85; }
.tree-node.dec-allow { fill: var(--decision-allow); opacity: 1; }
.tree-node.dec-review { fill: var(--decision-review); opacity: 1; }
.tree-node.dec-block { fill: var(--decision-block); opacity: 1; }
.tree-link { fill: var(--accent); opacity: 0.16; }
.tree-link.dec-allow { fill: var(--decision-allow); opacity: 0.2; }
.tree-link.dec-review { fill: var(--decision-review); opacity: 0.2; }
.tree-link.dec-block { fill: var(--decision-block); opacity: 0.2; }
.tree-node-g.clickable rect { cursor: pointer; }
.tree-node-g.clickable:hover rect, .tree-node-g.clickable:focus-visible rect { filter: brightness(1.15); }

/* ---------------------------------------------------------------------------------------------
   Decisions: a decision and its reason, on the detail page
   Everything resets to `margin: 0` (see Reset) and h2.section-title only adds 2px, so the Why card
   used to stack its heading, its "Rule that fired" label and the code itself as three touching lines
   — the pair the reader is trying to read together was the densest text on the page. .decision-pair
   gives the label and its value a real block rhythm; .decision-note does the same for the review and
   allow branches, which are prose rather than a pair (see viewDecisionDetail — the review branch must
   keep saying an action does NOT proceed, so it never shares the allow branch's wording or styling).
   Scoped to the Why card so the rest of the Console's cards keep their existing spacing.
--------------------------------------------------------------------------------------------- */
.decision-why .section-title { margin-bottom: 0; }
.decision-pair { margin-top: 14px; }
.decision-pair .pair-label {
  font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 6px; line-height: 1.4;
}
.decision-pair .pair-value { font-size: 15px; line-height: 1.6; }
.decision-pair .pair-value.mono { font-family: var(--font-mono); font-size: 14px; word-break: break-word; }
.decision-note { margin-top: 12px; font-size: 14.5px; line-height: 1.65; max-width: 68ch; }

/* ---------------------------------------------------------------------------------------------
   Decisions: Flow (remediation arcs)
--------------------------------------------------------------------------------------------- */
.arc-list { display: flex; flex-direction: column; gap: 10px; }
.arc-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* flex: 0 0 180px (not min-width:180px) is the point: a min-width is a FLOOR, so a 3-step arc
   (block>block>allow) whose chips need more room than a 2-step one grew past it and pushed
   .arc-bar-wrap's left edge — i.e. where every row's line/bar starts — out of line with the rows
   above and below it. A fixed flex-basis keeps this column's width (and so the bar's start x)
   identical on every row; flex-wrap on this same element still wraps an oversized chip run onto a
   second line inside that fixed width instead of widening the column. */
.arc-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: 0 0 180px; }
.arc-arrow { color: var(--muted); font-size: 13px; }
.arc-bar-wrap { flex: 1; min-width: 80px; height: 8px; background-color: var(--rule); border-radius: 999px; overflow: hidden; }
.arc-bar { height: 100%; background-color: var(--accent); border-radius: 999px; }
.arc-count { font-size: 12.5px; color: var(--muted); white-space: nowrap; min-width: 70px; text-align: right; }

/* ---------------------------------------------------------------------------------------------
   Misc
--------------------------------------------------------------------------------------------- */
.badge-p1 { display: inline-block; padding: 3px 10px; border-radius: 999px; background-color: var(--accent-bg);
  color: var(--accent); font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; }
.spacer { flex: 1; }
.sr-only-focusable:not(:focus) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

/* help panel */
.help-panel {
  position: fixed; top: 0; right: 0; height: 100%; width: 100%; max-width: 340px; background-color: var(--card);
  border-left: 1px solid var(--rule); box-shadow: var(--shadow-raised); z-index: 90; padding: 20px;
  overflow-y: auto;
}
.help-panel h2 { font-size: 15px; margin-bottom: 10px; }
.help-panel p { font-size: 13.5px; color: var(--muted); margin-bottom: 10px; }
.help-panel-backdrop { position: fixed; inset: 0; background-color: rgba(10,12,16,0.35); z-index: 89; }
/* the "About Norn" section appended below the per-page text — see renderHelpPanel() in app.js */
.help-about { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--rule); }
.help-panel .doc-links { font-size: 12.5px; color: inherit; margin-bottom: 0; }

/* Breakpoints for responsive rail */
/* The rail's own width is driven by data-rail, which boot() always sets — a width-keyed default for it
   lives in boot() (see LS_RAIL), not here, because a bare :root rule can never outrank :root[data-rail]. */
@media (max-width: 1199px) {
  .app-panel { width: 264px; }
}

@media (max-width: 899px) {
  #app, #app[data-shell="app"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "topbar" "main" "footer";
  }
  .app-rail {
    position: fixed; top: 0; bottom: 0; left: 0; width: var(--rail-w-expanded);
    z-index: 95; transform: translateX(-100%); transition: transform 0.16s ease;
    border-right: 1px solid var(--rule);
  }
  :root[data-rail-open] .app-rail { transform: none; }
  .rail-scrim { position: fixed; inset: 0; background-color: rgba(10,12,16,0.35); z-index: 94; }
  :root[data-rail-open] .rail-scrim { display: block; }
  .app-main { padding: 18px 16px 60px; }

  #app[data-panel="on"] .app-panel {
    position: fixed; top: auto; left: 0; right: 0; bottom: 0; height: 56px; width: auto;
    max-height: none; z-index: 80; border: none; border-radius: 0;
    border-top: 1px solid var(--rule); box-shadow: var(--shadow-raised);
  }
  #app[data-panel="on"] .panel-inner { position: static; max-height: none; padding: 0 16px;
    height: 100%; display: flex; align-items: center; gap: 12px; }
  #app[data-panel="on"] .app-main { padding-bottom: 84px; }
}

/* Left rail styling */
.rail-brand { display: flex; align-items: center; gap: 10px; padding: 4px 6px 12px; }
.rail-brand .brand-name { flex: 1; }
.rail-collapse { width: 28px; height: 28px; border-radius: var(--radius-sm); color: var(--muted);
                 display: flex; align-items: center; justify-content: center; }
.rail-collapse:hover { color: var(--ink); background-color: var(--accent-bg); }

.rail-nav ul { display: flex; flex-direction: column; gap: 2px; }
.nav-row-head { display: flex; align-items: center; gap: 2px; }
.nav-item {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 500; color: var(--muted);
  transition: background-color 0.12s ease, color 0.12s ease;
}
.nav-item:hover { color: var(--ink); background-color: var(--accent-bg); }
.nav-item.active { color: var(--accent); font-weight: 700; background-color: var(--accent-bg); }
:root[data-theme="dark"] .nav-item.active { color: var(--ink); background-color: var(--accent-bg); }
.nav-icon { flex-shrink: 0; }
.nav-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-badge { margin-left: auto; font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums;
             color: var(--accent); background-color: var(--accent-bg); border-radius: 999px; padding: 1px 8px; }
.nav-sub-toggle { width: 24px; height: 28px; color: var(--muted); font-size: 11px; border-radius: var(--radius-sm); }
.nav-sub-toggle:hover { color: var(--ink); background-color: var(--accent-bg); }
.nav-sub { margin: 2px 0 4px 0; padding-left: 26px; border-left: 1px solid var(--rule); margin-left: 19px; }
.nav-sub-item { display: block; padding: 6px 10px; border-radius: var(--radius-sm);
                font-size: 13px; color: var(--muted); }
.nav-sub-item:hover { color: var(--ink); background-color: var(--accent-bg); }
.nav-sub-item.active { color: var(--accent); font-weight: 700; }
.nav-group-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
                   color: var(--muted); padding: 14px 10px 6px; }

/* icons-only rail: labels, badges, group headings and sub-lists all fold away */
:root[data-rail="collapsed"] .nav-label,
:root[data-rail="collapsed"] .nav-badge,
:root[data-rail="collapsed"] .nav-sub,
:root[data-rail="collapsed"] .nav-sub-toggle,
:root[data-rail="collapsed"] .nav-group-label,
:root[data-rail="collapsed"] .brand-name { display: none; }
:root[data-rail="collapsed"] .nav-item { justify-content: center; padding: 9px 0; }
:root[data-rail="collapsed"] .rail-inner { padding: 12px 8px 20px; }
/* 64px of rail leaves 35px of inline space once the rail and brand padding are taken out, and the row
   composes 30px mark + 10px gap + 28px button. On one line the expand button lands outside the column,
   where .rail-inner's implied overflow-x clips it — and it is the only way to expand the rail again at
   >=1200px, so a user who collapses it gets stuck collapsed for good. Stack the row instead. */
:root[data-rail="collapsed"] .rail-brand { flex-direction: column; gap: 8px; padding: 4px 0 12px; }
/* Below 899px the rail is a full-width drawer, so the collapsed preference must not fold anything —
   .nav-badge and .nav-sub were missing here, which deleted the sub-navigation and the tutorial badge
   from the drawer for anyone whose stored preference was "collapsed". */
@media (max-width: 899px) {
  :root[data-rail="collapsed"] .nav-label,
  :root[data-rail="collapsed"] .nav-badge,
  :root[data-rail="collapsed"] .nav-sub,
  :root[data-rail="collapsed"] .nav-group-label,
  :root[data-rail="collapsed"] .nav-sub-toggle,
  :root[data-rail="collapsed"] .brand-name { display: revert; }
  :root[data-rail="collapsed"] .nav-item { justify-content: flex-start; padding: 8px 10px; }
  :root[data-rail="collapsed"] .rail-brand { flex-direction: row; gap: 10px; padding: 4px 6px 12px; }
}

/* Top bar menu styling */
.topbar-right { display: flex; align-items: center; gap: 8px; }
.topbar-burger { width: 34px; height: 34px; border-radius: var(--radius-sm); color: var(--muted); display: flex; align-items: center; justify-content: center; }
.topbar-burger:hover { color: var(--ink); background-color: var(--accent-bg); }
.menu-anchor { position: relative; }
.topbar-btn { display: inline-flex; align-items: center; gap: 8px; padding: 5px 10px 5px 6px;
              border-radius: 999px; border: 1px solid var(--rule); background-color: var(--card); font-size: 13px; }
.topbar-btn:hover { border-color: var(--accent); }
.topbar-btn-label { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chev { color: var(--muted); font-size: 10px; }
.menu-backdrop { position: fixed; inset: 0; z-index: 69; }
.menu-panel { position: absolute; right: 0; top: calc(100% + 8px); z-index: 70; width: 280px;
              background-color: var(--card); border: 1px solid var(--rule); border-radius: var(--radius-md);
              box-shadow: var(--shadow-raised); padding: 6px; }
.menu-head { padding: 10px 10px 12px; border-bottom: 1px solid var(--rule); }
.menu-head-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.menu-head-name { font-weight: 700; margin-top: 6px; }
.menu-section { padding: 10px; border-bottom: 1px solid var(--rule); }
.menu-row-label { font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.menu-opts { display: flex; gap: 4px; }
.menu-opt { flex: 1; padding: 6px 8px; border-radius: var(--radius-sm); border: 1px solid var(--rule);
            font-size: 12.5px; font-weight: 600; color: var(--muted); }
.menu-opt.active { background-color: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.menu-foot { margin-top: 6px; }
.menu-line { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; }
.menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 10px;
             border-radius: var(--radius-sm); font-size: 13.5px; font-weight: 600; text-align: left; }
.menu-item:hover { background-color: var(--accent-bg); }
.menu-item.danger { color: var(--decision-block); }
.menu-item.danger:hover { background-color: var(--decision-block-bg); }
.menu-panel .lang-select { width: 100%; }

/* Hide hamburger by default, show only below 900px */
.topbar-burger { display: none; }
@media (max-width: 899px) {
  .topbar-burger { display: flex; }
}

/* Setup panel styling */
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.panel-head .section-title { margin-bottom: 0; }
.setup-progress { width: 100%; height: 4px; background-color: var(--rule); border-radius: 999px; overflow: hidden; margin: 10px 0; }
.setup-progress span { display: block; height: 100%; background-color: var(--accent); border-radius: 999px; }
.setup-list { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; }
.setup-row { display: flex; align-items: center; gap: 10px; }
/* an undone row's content sits inside its link — the <a> must lay out exactly like the row itself,
   or the check/title/NEXT chip stack and cram instead of lining up */
.setup-row > a { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; color: inherit; }
.setup-next { flex-shrink: 0; }
.setup-row.step.done { color: var(--muted); }
.setup-row-title { flex: 1; font-size: 13.5px; }
.setup-next { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--accent); }
.setup-open { width: 100%; }

/* Pill styling reused for menu */
.pill { display: inline-block; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.pill.neutral { background-color: var(--accent-bg); color: var(--accent); }
.pill.status-ok { background-color: var(--decision-allow-bg); color: var(--decision-allow); }
.pill.status-warn { background-color: var(--decision-review-bg); color: var(--decision-review); }
.pill.status-off { background-color: var(--decision-block-bg); color: var(--decision-block); }
.pill.mono { font-family: var(--font-mono); }

/* The panel becomes a 56px bottom bar below 899px. It used to hide the heading and substitute
   `content: attr(data-summary)`, but nothing ever sets data-summary, so attr() resolved to the empty
   string and the pseudo-element drew nothing — which left renderSetupPanelDone(), whose whole body is
   that heading plus one hint, with an invisible title in all three locales. Keep the real heading and
   fit the row instead; .setup-open must also drop its 100% width or it swallows the bar as a flex item. */
@media (max-width: 899px) {
  .setup-list, .setup-progress { display: none; }
  #app[data-panel="on"] .panel-head { margin-bottom: 0; flex-shrink: 0; }
  #app[data-panel="on"] .panel-head .section-title { white-space: nowrap; }
  #app[data-panel="on"] .panel-inner .hint {
    flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  #app[data-panel="on"] .setup-open { width: auto; flex-shrink: 0; }
}

/* Setup panel, dismissed-but-unfinished: a small floating reopen chip (bottom-right, above nothing
   layout-affecting) so the tutorial stays one tap away after the × — see renderSetupPanel()'s fab
   branch. Placed last so it outranks both the desktop card and the mobile bottom-bar variants. */
#app .app-panel.as-fab,
#app[data-panel="on"] .app-panel.as-fab {
  position: fixed; top: calc(var(--topbar-h) + 16px); left: auto; right: 16px; bottom: auto;
  width: auto; height: auto;
  max-height: none; border: none; border-radius: 999px; background: none; box-shadow: none;
  overflow: visible; padding: 0; z-index: 60;
}
.app-panel.as-fab .setup-pill { box-shadow: var(--shadow-raised); cursor: pointer; padding: 8px 14px; }

/* Compliance library: on a control DETAIL surface, a "needs oracle" readiness gets one line naming the
   only way that gap ever closes. A quiet accent note, not a warning — nothing in this library dims or
   minimizes a standard; every control browses and cites exactly as the rulebook holds it (the old
   .chip.is-importing / .importing-badge dimming is gone, and no markup referenced it any more). */
.compliance-gap-hint {
  margin: 8px 0 12px; padding: 8px 12px;
  border-left: 2px solid var(--accent); background-color: var(--accent-bg);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
