/* ============================================================================
   BLACKROAD.CSS — the reusable layer behind chatgpt-atlas.html and console.html.

   Those two files each inline their own copy of the same tokens, the same reset
   and most of the same components. This file is that shared part, extracted once
   so a new page links it instead of pasting 130 lines of <style> and drifting.

   HOW TO USE
     <link rel="stylesheet" href="blackroad.css">
     ...then a page-local <style> holding ONLY what is specific to that page.
     Never redeclare something this file owns — even with the same value. If two
     files set --radius, there are two sources of truth and one of them is wrong.

   THE ONE RULE
     Monochrome. Black on white, four greys, nothing else. State is carried by
     WEIGHT, FILL, BORDER-STYLE and DOTS — never by hue. There is no red for
     error and no green for success, so a component can never lean on colour to
     say what its markup should say.

   THE VOCABULARY
     .badge  states a value   — you read it
     .chip   is pressable     — you click it
     solid   = confirmed      — measured, verified, active
     hairline= ordinary       — the default
     dashed  = asserted       — claimed but not verified. Never decorative.

   NOTE ON THE GREY RAMP
     chatgpt-atlas.html sets --faint:#a8a8a5 and --muted:#777775. Those are the
     original values and they fail WCAG AA: --faint is 2.30:1 on --paper-soft,
     and in this estate the quietest tier is also the smallest (mono labels at
     8.5–10px), so it failed the 4.5:1 bar by a factor of two. The ramp below is
     the corrected one, set by contrast rather than by eye:
       --faint  #6e6e6b   4.94:1 on soft / 5.11:1 on white
       --muted  #5a5a58   6.68:1 on soft / 6.91:1 on white
     Four distinct steps remain (4.9 / 6.7 / 9.4 / 20) so the hierarchy survives.
     Pages built against the old values will read very slightly darker in the
     quiet tiers. That is the intended direction.
   ========================================================================== */


/* ══ 1. TOKENS ══════════════════════════════════════════════════════════════
   The whole palette and every measurement. Nothing below invents a value. */

:root{
  /* surfaces */
  --paper:#fff;
  --paper-soft:#fbfbfb;
  --surface:#fff;
  --surface-2:#f7f7f6;

  /* the ink ramp — see the note above before changing either grey */
  --ink:#080808;
  --ink-2:#2e2e2e;
  --muted:#5a5a58;
  /* --faint corrected 2026-10-05. #6e6e6b measured 4.44:1 on --selected
     (#efefed) and failed AA there, on every page of the site. The header note
     above checks the ramp against --paper-soft and white only, so the two
     INTERACTION grounds -- --hover #f4f4f2 and --selected #efefed -- were never
     in the sum. A selected row is only selected sometimes, which is why nobody
     saw it and why the grounds now live in check-contrast.py rather than in a
     comment. #6c6c6a clears 4.5 on all five light grounds (5.26 / 5.09 / 4.91 /
     4.78 / 4.57) and stays quieter than --muted, so the four-step ramp holds.
     The Canon stylesheet at ~/Road/Websites/Canon/Sites/roados-os reached the
     same conclusion independently on 2026-09-02 and checked all five. */
  --faint:#6c6c6a;

  /* lines. --line is structural (panel edges, bar bottoms, column dividers);
     --line-soft is the hairline BETWEEN rows inside a panel. Both exist because
     every page had already hardcoded #f1f1ef for row rules — a value in use
     everywhere and owned by nothing is the definition of a missing token. */
  --line:#e8e8e5;
  --line-strong:#d9d9d5;
  --line-soft:#f1f1ef;

  /* interaction */
  --hover:#f4f4f2;
  --selected:#efefed;
  --sel-ink:#dcdcd8;          /* simulated text selection inside a mockup */

  /* The fill that means on / active / confirmed, and the text that sits on it.
     These flip with the theme, so they can never be literals: #111 in a dark
     theme is a hole, not an emphasis. */
  --solid:#111;
  --solid-hover:#000;
  --on-solid:#fff;
  --on-solid-2:#9a9a97;      /* secondary text on a solid fill */
  --glass:rgba(255,255,255,.86);
  --glass-2:rgba(255,255,255,.72);
  --glass-line:rgba(215,215,211,.92);
  --scrim:rgba(8,8,8,.26);

  --shadow:0 1px 1px rgba(0,0,0,.025),0 12px 40px rgba(0,0,0,.035);
  --shadow-float:0 28px 90px rgba(0,0,0,.18),0 2px 10px rgba(0,0,0,.08);
  --ring:0 0 0 3px rgba(0,0,0,.04);

  --radius:18px;
  --radius-sm:12px;
  --radius-xs:8px;

  /* the one set of bar heights. Pages had drifted across 52/56/58/60/62/64. */
  --menubar:28px;             /* macOS-style system bar */
  --tabstrip:38px;
  --chrome:52px;              /* URL / toolbar row */
  --topbar:56px;              /* app header */
  --dock:82px;
  --panel:340px;              /* side panel width */

  --sans:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Helvetica Neue",Arial,sans-serif;
  --mono:"SFMono-Regular","SF Mono",ui-monospace,Menlo,Monaco,Consolas,monospace;
}


/* ══ 2. RESET ═══════════════════════════════════════════════════════════════
   The eight declarations every page in this language already agreed on. */

*{box-sizing:border-box}

/* A reset that omits the base font is not a reset: when local copies of this
   rule were removed in favour of a shared file, 197 pages fell back to the UA
   serif with an 8px body margin. A page needing another surface (a dark editor)
   declares its own html{} after this link — same specificity, later wins. */
html{
  background:var(--paper);color:var(--ink);font-family:var(--sans);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  color-scheme:light;
  -webkit-text-size-adjust:100%;text-size-adjust:100%;
}
body{margin:0}
a{color:inherit;text-decoration:none}

/* Block margins are the UA's opinion, not ours, and they leak. `.err` and
   `.help` are <p>, so each carried 11.5px top AND bottom that no rule in this
   file asked for — the error message sat a ragged distance below its field and
   the gap changed with the element's font-size. Spacing belongs to the
   container (a flex `gap`) or to an explicit component rule; never to a UA
   default nobody can see in the source.
   :where() keeps specificity at 0, so every component rule below still wins —
   .page p, .msg-body p and .sec-note are unaffected. */
:where(p,h1,h2,h3,h4,h5,h6,ul,ol,dl,dd,figure,blockquote,pre,fieldset){margin:0}

/* :where() because a reset is a DEFAULT. Bare `button` is specificity 0,0,1 and
   would beat every .btn rule below — for as long as it was unwrapped, buttons
   could set height and radius but never a border or a background, and drew as
   bare text. Only a page relying on this layer alone ever showed the bug. */
:where(button){font:inherit;color:inherit;border:0;background:none;cursor:pointer}
:where(input,textarea,select){font:inherit;color:inherit}

svg{display:block;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
/* Safety net: an unstyled <svg><use> defaults to 300×150, not to its box. */
svg:has(> use){width:1em;height:1em}

::selection{background:var(--solid);color:var(--on-solid)}
:focus-visible{outline:2px solid var(--solid);outline-offset:2px}
img,video,canvas{max-width:100%;height:auto}

/* The sprite itself is never visible; only its <use> references are. */
.icon-sprite{position:absolute;width:0;height:0;overflow:hidden}


/* ══ 3. LAYOUT ══════════════════════════════════════════════════════════════ */

/* Full-height application shell: bars stack, one child flexes. */
.app{display:flex;flex-direction:column;height:100vh}
.app-body{flex:1;min-height:0}

/* Content pane beside a fixed-width panel. The minmax(0,1fr) matters: without
   it a long unbroken token in the left pane widens the whole grid. */
.split{display:grid;grid-template-columns:minmax(0,1fr) var(--panel);min-height:0}
.split.left-panel{grid-template-columns:var(--panel) minmax(0,1fr)}

/* Document shell, for pages that scroll rather than fill the viewport. */
.wrap{max-width:1100px;margin:0 auto;padding:0 24px 100px}
/* One stride between top-level blocks of a document page. Previously .section
   owned its own bottom margin, so a bare .features or .tiers grid sitting
   before a heading had nothing separating them and the two collided. */
.wrap>*+*{margin-top:44px}
.scroll{min-height:0;overflow-y:auto}
/* An inset reading surface, so a scrolling pane sits back from its chrome. */
.ground{background:var(--paper-soft)}
/* Scrollbars are chrome, and this language has none. Content still scrolls. */
.scroll.bare{scrollbar-width:none}
.scroll.bare::-webkit-scrollbar{display:none}
.stack{display:flex;flex-direction:column;gap:10px}
.row{display:flex;align-items:center;gap:10px}
.row.baseline{align-items:baseline}
.row.top{align-items:flex-start}
.push{margin-left:auto}


/* ══ 4. CHROME — bars that frame content rather than being content ═════════ */

/* Frosted system bar. The blur is what separates chrome from content; without
   it a translucent bar reads as a faded panel. */
.menubar,.glassbar{
  display:flex;align-items:center;gap:14px;height:var(--menubar);padding:0 12px;flex:none;
  background:var(--glass);
  border-bottom:1px solid var(--glass-line);
  box-shadow:0 1px 1px rgba(0,0,0,.025);
  backdrop-filter:blur(28px) saturate(150%);
  -webkit-backdrop-filter:blur(28px) saturate(150%);
}
.glassbar{height:auto;padding:11px 24px}
.glassbar.sticky{position:sticky;top:0;z-index:30}

/* Opaque app header. */
.topbar{
  display:flex;align-items:center;gap:12px;height:var(--topbar);padding:0 16px;flex:none;
  background:var(--surface);border-bottom:1px solid var(--line);
}
.titlebar{
  display:flex;align-items:center;gap:11px;height:40px;padding:0 13px;flex:none;
  border-bottom:1px solid var(--line);background:var(--glass-2);
}
.titlebar b,.topbar b{font-size:12.5px;font-weight:680;letter-spacing:-.02em}
.footbar{
  display:flex;align-items:center;flex-wrap:wrap;gap:14px;row-gap:6px;
  min-height:36px;padding:7px 13px;flex:none;
  border-top:1px solid var(--line);background:var(--glass-2);
  font-size:11px;color:var(--muted);
}

/* The browser URL row. Its own component because it is a different height and
   rhythm from an app header — the kit was building it as a .topbar plus three
   inline style overrides, which is the signal that a component is missing. */
.chromebar{
  display:flex;align-items:center;gap:10px;height:var(--chrome);padding:0 12px;flex:none;
  background:var(--surface);border-bottom:1px solid var(--line);
}

/* Browser tab strip. */
.tabstrip{
  display:flex;align-items:center;gap:6px;height:var(--tabstrip);padding:0 8px;flex:none;
  background:var(--surface-2);border-bottom:1px solid var(--line);
}
.tab{
  display:flex;align-items:center;gap:7px;height:30px;max-width:200px;padding:0 10px;
  border-radius:var(--radius-xs) var(--radius-xs) 0 0;font-size:12px;
}
.tab.is-active{background:var(--surface);box-shadow:0 -1px 0 var(--line) inset}
.tab:not(.is-active):hover{background:var(--selected)}
.tab .fav{width:6px;height:6px;border-radius:50%;background:var(--solid);flex:none}
.tab .tn{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tab .tx{display:grid;place-items:center;width:16px;height:16px;margin-left:2px;border-radius:5px;color:var(--faint)}
.tab .tx:hover{background:var(--line);color:var(--ink)}
.tab .tx svg{width:10px;height:10px}

/* URL / search field inside a bar. */
.urlbar{
  flex:1;display:flex;align-items:center;gap:9px;height:34px;padding:0 12px 0 13px;
  border:1px solid var(--line);border-radius:10px;background:var(--surface-2);
}
.urlbar:focus-within{background:var(--surface);border-color:var(--line-strong);box-shadow:var(--ring)}
.urlbar input{flex:1;min-width:0;border:0;outline:0;background:transparent;font-size:12.5px;color:var(--ink-2)}
.urlbar svg{width:13px;height:13px;color:var(--faint);flex:none}

/* Dock. Centred in its own box so it reads as a dock even when the box is
   inset to the content's edges rather than to the viewport's. */
.dock{
  display:flex;align-items:center;justify-content:center;gap:8px;height:66px;padding:8px 13px;
  border:1px solid var(--glass-line);border-radius:20px;
  background:var(--glass);
  box-shadow:0 18px 50px rgba(0,0,0,.11),0 2px 6px rgba(0,0,0,.04);
  backdrop-filter:blur(28px) saturate(150%);
  -webkit-backdrop-filter:blur(28px) saturate(150%);
}
.dock-item{
  position:relative;display:grid;place-items:center;width:48px;height:48px;
  border:1px solid transparent;border-radius:14px;color:var(--ink);
  transition:transform .18s ease,background .18s ease,border-color .18s ease,box-shadow .18s ease;
}
.dock-item:hover{border-color:var(--line);background:var(--surface);box-shadow:0 8px 24px rgba(0,0,0,.07);transform:translateY(-5px) scale(1.04)}
.dock-item:active{transform:translateY(-1px) scale(.96)}
.dock-item[aria-pressed="true"]{border-color:var(--line);background:var(--surface);box-shadow:0 5px 16px rgba(0,0,0,.06)}
.dock-item[aria-pressed="true"]::after{content:"";position:absolute;bottom:-6px;width:4px;height:4px;border-radius:50%;background:var(--solid)}
.dock-item svg{width:21px;height:21px}
.dock-divider{width:1px;height:32px;margin:0 2px;background:var(--line)}

/* Window dots. Only use these where each one does something — a dot that
   lights up on hover and does nothing is the same lie as a close button you
   cannot press. Give each a real label and a real handler, or omit all three. */
.lights{display:flex;gap:6px;flex:none}
.lights button{
  width:11px;height:11px;padding:0;border:0;border-radius:50%;
  background:var(--line-strong);box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);
}
:hover>.lights .l1,:hover .lights .l1{background:var(--solid)}
:hover>.lights .l2,:hover .lights .l2{background:var(--muted)}
:hover>.lights .l3,:hover .lights .l3{background:var(--line-strong)}


/* ══ 5. CONTROLS — pressable things ════════════════════════════════════════ */

.btn{
  display:inline-flex;align-items:center;gap:7px;height:33px;padding:0 13px;
  border:1px solid var(--line);border-radius:10px;background:var(--surface);
  font-size:12.5px;font-weight:560;
  transition:background .16s ease,border-color .16s ease;
}
.btn:hover{background:var(--hover);border-color:var(--line-strong)}
.btn:active{transform:scale(.98)}
.btn svg{width:14px;height:14px}
.btn.dark{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.btn.dark:hover{background:var(--solid-hover)}
.btn.sm{height:27px;padding:0 10px;font-size:11.5px}
.btn.pill{border-radius:999px}

/* A chip is something you press. Pressed state is fill, not hue. */
.chip{
  display:inline-flex;align-items:center;gap:7px;height:29px;padding:0 12px;
  border:1px solid var(--line);border-radius:999px;background:var(--surface);
  font-size:12px;font-weight:560;color:var(--ink-2);
  transition:background .16s ease,color .16s ease,border-color .16s ease;
}
.chip:hover{background:var(--hover);border-color:var(--line-strong)}
.chip[aria-pressed="true"],.chip.is-active{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.chip svg{width:12px;height:12px}
.chip .n{font:650 10px/1 var(--mono);font-variant-numeric:tabular-nums;opacity:.72}

/* Square icon buttons: .icon-btn in a bar, .nav-btn for back/forward/reload. */
.icon-btn,.nav-btn{
  display:grid;place-items:center;width:30px;height:30px;flex:none;
  border-radius:var(--radius-xs);color:var(--ink-2);
  transition:background .16s ease;
}
.icon-btn:hover,.nav-btn:hover{background:var(--hover)}
.icon-btn svg,.nav-btn svg{width:16px;height:16px}
.nav-btn[disabled],.nav-btn.disabled{color:var(--faint);pointer-events:none}
.icon-btn.sq{width:26px;height:20px;border-radius:6px}
.icon-btn.sq svg{width:15px;height:15px}

.send{
  display:grid;place-items:center;width:30px;height:30px;flex:none;
  border-radius:9px;background:var(--solid);color:var(--on-solid);
  transition:transform .16s ease,background .16s ease;
}
.send:hover{background:var(--solid-hover);transform:scale(1.04)}
.send svg{width:14px;height:14px}


/* ══ 6. DATA DISPLAY — things you read, not press ══════════════════════════ */

/* A badge states a value. Fill and border-style carry the meaning:
     .solid    confirmed / active / measured
     (default) ordinary
     .asserted claimed but not verified — dashed, never decorative */
/* inline-FLEX, not inline-grid. place-items:center on a grid centres each item
   in its own cell, and an svg plus a text node are two items — so they became
   two implicit rows and "AI READ" rendered outside its own 19px pill. Any
   component that can hold an icon beside text is flex with a gap. */
.badge{
  display:inline-flex;align-items:center;justify-content:center;gap:5px;
  height:19px;padding:0 8px;flex:none;
  border:1px solid var(--line-strong);border-radius:999px;background:var(--surface);
  color:var(--muted);font:700 9px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
}
.badge.solid{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.badge.asserted{border-style:dashed;color:var(--faint);cursor:help}
.badge.quiet{background:var(--surface-2);border-color:var(--line);color:var(--faint)}
.badge svg{width:10px;height:10px}

/* A label above a thing. */
.kicker{color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}

/* Status dot with a halo, for "live" in a header. */
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--solid);box-shadow:0 0 0 3px var(--selected);flex:none}

/* A numbered citation inside prose. Solid because a citation that resolves is
   a confirmed thing; if it does not resolve, do not render one. */
.cite{
  display:inline-grid;place-items:center;min-width:15px;height:15px;padding:0 3px;margin:0 1px;
  border-radius:4px;background:var(--solid);color:var(--on-solid);font:700 9px/1 var(--mono);vertical-align:2px;
}
/* Highlight inside running text, and simulated text selection. */
.hl{background:var(--selected);padding:1px 3px;border-radius:3px}
.sel{background:var(--selected);box-shadow:0 0 0 2px var(--sel-ink)}

.avatar{
  display:grid;place-items:center;width:28px;height:28px;flex:none;
  border-radius:50%;background:var(--solid);color:var(--on-solid);font-size:11px;font-weight:700;
}
.avatar.sq{border-radius:var(--radius-xs);border:1px solid var(--line);background:var(--surface);color:var(--ink)}
/* The brand mark. One per page, and it is the root identity. */
.mark{
  display:grid;place-items:center;width:40px;height:40px;flex:none;
  border-radius:11px;background:var(--solid);color:var(--on-solid);
  font-size:20px;font-weight:760;letter-spacing:-.04em;
  box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.14);
}
.mark.sm{width:23px;height:23px;border-radius:7px;font-size:11px;box-shadow:none}
.mark.xs{width:18px;height:18px;border-radius:5px;font-size:10px;font-weight:760;box-shadow:0 3px 12px rgba(0,0,0,.12)}

/* Key/value pairs, mono so values line up and are selectable. */
.kv{display:grid;grid-template-columns:auto 1fr;gap:3px 11px;margin:0}
.kv dt{color:var(--faint);font:600 9.5px/1.5 var(--mono);letter-spacing:.04em}
.kv dd{margin:0;color:var(--ink-2);font:600 10.5px/1.5 var(--mono);overflow-wrap:anywhere}


/* ══ 7. SURFACES ═══════════════════════════════════════════════════════════ */

.card{border:1px solid var(--line);border-radius:11px;background:var(--surface);padding:10px 11px}
.card-head{display:flex;align-items:center;gap:7px}
.card-head b{font-size:12.5px;font-weight:650;letter-spacing:-.015em}
.card-head svg{width:14px;height:14px;color:var(--muted);stroke-width:1.6}
.card-body{margin-top:4px;color:var(--ink-2);font-size:12px;line-height:1.5}

.panel{
  display:flex;flex-direction:column;min-height:0;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);
  box-shadow:var(--shadow);overflow:hidden;
}
/* A floating window: same chrome, lifted off the page. */
.panel.float{
  border-color:var(--glass-line);background:var(--glass);
  box-shadow:var(--shadow-float);
  backdrop-filter:blur(28px) saturate(150%);
  -webkit-backdrop-filter:blur(28px) saturate(150%);
}
.panel.right{border:0;border-left:1px solid var(--line);border-radius:0;box-shadow:none}
.panel.left{border:0;border-right:1px solid var(--line);border-radius:0;box-shadow:none}

/* Figure with a caption. The caption is the claim about the figure, so it is
   never decorative text. */
.fig{margin:20px 0;padding:16px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
.fig .cap{margin-top:8px;color:var(--faint);font-size:11px;line-height:1.5}

/* Fake app-window chrome hosting a screenshot or a recreated screen. */
.screen{border:1px solid var(--line-strong);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);background:var(--surface)}
.screen-bar{display:flex;align-items:center;gap:10px;height:38px;padding:0 14px;background:var(--surface-2);border-bottom:1px solid var(--line)}
.screen-bar .dots{display:flex;gap:6px}
.screen-bar .dots i{width:9px;height:9px;border-radius:50%;background:var(--line-strong)}
.screen-bar .url{
  display:flex;align-items:center;gap:7px;height:24px;max-width:60%;padding:0 12px;
  border:1px solid var(--line);border-radius:var(--radius-xs);background:var(--surface);
  font:600 10.5px/1 var(--mono);color:var(--muted);
}

/* THE HONEST EMPTY STATE. A missing source is not an error, so it gets no error
   treatment — and monochrome means it could not have hue anyway. Use this
   instead of filler: a panel either shows what it has, or names the source it
   does not have. It may never borrow a number from somewhere else. */
.void{border:1px dashed var(--line-strong);border-radius:11px;padding:13px;background:var(--surface)}
.void-tag{
  display:inline-block;padding:1px 8px;border-radius:999px;background:var(--selected);
  color:var(--muted);font:700 9px/1.6 var(--mono);letter-spacing:.1em;text-transform:uppercase;
}
.void-tag.deny{background:var(--solid);color:var(--on-solid)}
.void-line{margin:8px 0 0;color:var(--ink);font-size:12.5px;font-weight:540;line-height:1.55}
.void-fill{margin:6px 0 0;color:var(--muted);font:600 10.5px/1.55 var(--mono)}


/* ══ 8. DOCUMENT — long-form reading ══════════════════════════════════════ */

.page{max-width:760px;margin:0 auto;padding:44px 40px 60px}
.page h1{margin:0 0 14px;font-size:26px;font-weight:720;letter-spacing:-.03em;line-height:1.25}
.page h2{margin:28px 0 12px;font-size:16px;font-weight:670;letter-spacing:-.015em}
.page h3{margin:22px 0 8px;font-size:13.5px;font-weight:660;letter-spacing:-.01em}
.page p{margin:0 0 16px;font-size:14.5px;line-height:1.75;color:var(--ink-2)}
.page .byline{margin:0 0 26px;color:var(--faint);font-size:12px}
.page strong{color:var(--ink);font-weight:660}
.page ul{margin:0 0 16px;padding-left:0;list-style:none}
.page li{position:relative;margin-bottom:7px;padding-left:17px;font-size:14.5px;line-height:1.7;color:var(--ink-2)}
.page li::before{content:"";position:absolute;left:3px;top:10px;width:4px;height:4px;border-radius:50%;background:var(--solid)}
.page code,code.inline{
  padding:1px 5px;border:1px solid var(--line);border-radius:5px;background:var(--surface);
  font:600 11px/1.5 var(--mono);color:var(--muted);overflow-wrap:anywhere;
}
/* Breaking a line inside pre changes what it means, so it scrolls instead. */
.page pre{max-width:100%;overflow-x:auto;padding:12px 13px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-2);font:500 11.5px/1.6 var(--mono)}

/* Page masthead. Added because both starter pages were building one out of
   .foot plus four inline overrides — a component everyone reinvents inline is a
   component that is missing. One h1 per page, and it is the page's name. */
.masthead{display:flex;align-items:center;gap:14px;padding:52px 0 28px}
.masthead.block{display:block}
.masthead h1{font-size:29px;font-weight:720;letter-spacing:-.04em;line-height:1.1}
.masthead .lede{margin-top:18px;max-width:660px;font-size:15.5px;font-weight:500;letter-spacing:-.02em;color:var(--ink-2);line-height:1.5}

/* Document footer: a rule, the mark, and the provenance of the page. */
.foot{
  margin-top:56px;padding-top:24px;border-top:1px solid var(--line);
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  color:var(--muted);font-size:12px;line-height:1.5;
}

/* Section framing for indexed documents. */
.section{scroll-margin-top:68px}
.sec-head{display:flex;align-items:baseline;gap:11px;flex-wrap:wrap}
.sec-idx{flex:none;min-width:22px;color:var(--faint);font:650 11px/1.3 var(--mono);font-variant-numeric:tabular-nums}
.sec-head h2{margin:0;font-size:19px;font-weight:700;letter-spacing:-.03em}
.sec-note{margin:11px 0 0;max-width:760px;color:var(--muted);font-size:12.5px;line-height:1.65}
/* Where a section's numbers came from, sat beside its heading. Truncates rather
   than wrapping, because a path is the one thing that may be any length and it
   must not push the heading around. */
.sec-src{
  color:var(--faint);font:600 9.5px/1.5 var(--mono);
  flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* Tables. Mono cells because the content is usually data, and a number that
   shifts column between rows cannot be compared down the column. */
.cwrap{overflow-x:auto;border:1px solid var(--line);border-radius:11px;background:var(--surface)}
table{width:100%;border-collapse:collapse;font-size:12.5px}
th,td{padding:9px 12px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line-soft)}
tr:last-child td{border-bottom:0}
th{
  background:var(--surface-2);color:var(--faint);white-space:nowrap;
  font:700 9px/1.4 var(--mono);letter-spacing:.1em;text-transform:uppercase;
}
td{color:var(--ink-2)}
td.num,th.num{font-variant-numeric:tabular-nums;text-align:right}
td.mono{font:500 12px/1.5 var(--mono)}


/* ══ 9. LISTS — rails, rows, indexes ══════════════════════════════════════ */

.rail{
  min-height:0;overflow-y:auto;scrollbar-width:none;
  padding:10px 8px;background:rgba(255,255,255,.42);
}
.rail::-webkit-scrollbar{display:none}
.rail-label{padding:8px 8px 6px;color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}

/* One selectable row. Depth is set with --d and records LINEAGE, never rank —
   so size, weight and colour are identical at every depth and only the indent
   moves. Names clamp to two lines: one line was not a style choice, it was
   short demo names being forgiving, and on real data six rows read identically
   with the distinguishing part truncated away. */
.listrow{
  display:flex;align-items:flex-start;gap:7px;width:100%;min-height:30px;
  padding:6px 9px 6px calc(9px + var(--d,0) * 13px);
  border-radius:9px;color:var(--ink-2);text-align:left;
  transition:background .16s ease,color .16s ease,transform .16s ease;
}
.listrow:hover{background:var(--hover)}
.listrow:active{transform:scale(.985)}
.listrow.is-active{background:var(--solid);color:var(--on-solid)}
.listrow.is-active .num,.listrow.is-active .sig{color:var(--on-solid-2)}
/* Its own column, tabular, so #1 and #1000 do not shuffle the names beside them
   and every name starts at the same x. */
.listrow .num{
  flex:0 0 auto;min-width:21px;align-self:flex-start;margin-top:3px;
  color:var(--faint);font:650 9.5px/1 var(--mono);font-variant-numeric:tabular-nums;
}
.listrow .sig{flex:none;width:9px;align-self:flex-start;margin-top:2px;color:var(--faint);font:600 12px/1 var(--mono)}
.listrow .nm{
  flex:1;min-width:0;overflow:hidden;
  font-size:12.5px;font-weight:540;letter-spacing:-.012em;line-height:1.32;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
}
.listrow .count{
  display:grid;place-items:center;min-width:17px;height:17px;padding:0 5px;flex:none;
  border-radius:999px;background:var(--solid);color:var(--on-solid);font:700 9px/1 var(--mono);
}
.listrow.is-active .count{background:var(--surface);color:var(--ink)}

/* Hairline-separated index rows (not selectable, just listed). */
.index{margin:0;padding:0;list-style:none;border-top:1px solid var(--line-soft)}
.index>li{
  display:flex;align-items:flex-start;gap:11px;
  padding:10px 11px 10px 0;border-bottom:1px solid var(--line-soft);
  transition:background .16s ease;
}
.index>li:hover{background:var(--hover)}
.index .num{flex:0 0 33px;padding-left:4px;margin-top:2px;color:var(--faint);font:650 10.5px/1.5 var(--mono);font-variant-numeric:tabular-nums}
.index .txt{min-width:0;font-size:13px;line-height:1.6;color:var(--ink-2)}
.index .txt b:first-child{color:var(--ink);font-size:13.5px;font-weight:650;letter-spacing:-.015em}

/* Breadcrumb that is clickable at every level, so you never climb one at a time. */
.crumbs{display:flex;align-items:center;gap:3px;flex-wrap:wrap;padding:5px 13px 6px;border-bottom:1px solid var(--line)}
.crumb{padding:1px 6px;border-radius:6px;color:var(--muted);font:600 9.5px/1.6 var(--mono);transition:background .16s ease,color .16s ease}
.crumb:hover{background:var(--solid);color:var(--on-solid)}
.crumb-sep{color:var(--faint);font:600 9.5px/1 var(--mono)}


/* ══ 10. MESSAGES ══════════════════════════════════════════════════════════ */

.msg{display:flex;gap:9px;align-items:flex-start;margin-bottom:14px}
.msg-col{min-width:0;flex:1}
.msg-head{display:flex;align-items:baseline;gap:7px}
.msg-head b{font-size:12px;font-weight:650;letter-spacing:-.015em}
.msg-head time{color:var(--faint);font:600 9.5px/1 var(--mono)}
.msg-body{margin-top:2px;font-size:12.5px;line-height:1.55;color:var(--ink-2)}
.msg-body p{margin:0 0 10px}
.msg-body p:last-child{margin-bottom:0}
.msg-body strong{color:var(--ink);font-weight:660}
.msg-body ul{margin:0 0 10px;padding-left:0;list-style:none}
.msg-body li{position:relative;margin-bottom:6px;padding-left:16px}
.msg-body li::before{content:"";position:absolute;left:2px;top:8px;width:4px;height:4px;border-radius:50%;background:var(--solid)}

/* Own message: a bubble, right-aligned. The other side gets no bubble, so the
   two are distinguishable without colour. */
.msg.is-you{justify-content:flex-end}
.msg.is-you .bub{
  max-width:88%;padding:9px 12px;
  border:1px solid var(--line);border-radius:14px 14px 4px 14px;background:var(--surface-2);
  font-size:12.5px;line-height:1.55;
}
/* System line: quieter, mono, with a dot instead of an avatar. */
.msg.is-system{align-items:center;gap:7px;padding-left:2px}
.msg.is-system .dot{width:5px;height:5px;border-radius:50%;background:var(--faint);flex:none}
.msg.is-system .msg-body{margin:0;color:var(--muted);font:600 10.5px/1.5 var(--mono)}
/* The leading icon sits on the first line's optical centre, not the block's —
   centred against a two-line body it floats between the lines and reads as
   belonging to neither. */
.msg.is-raised>.raise-ico{width:16px;height:16px;flex:none;margin-top:2px}
/* A raised concern is never styled as a lesser message. */
.msg.is-raised{
  gap:9px;padding:9px 11px;
  border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--surface-2);
}

/* What the message is about, pinned above the thread. */
.context-chip{
  display:flex;align-items:center;gap:8px;padding:8px 10px;
  border:1px solid var(--line);border-radius:10px;background:var(--surface-2);
}
.context-chip svg{width:14px;height:14px;color:var(--faint);flex:none}
.context-chip span{color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}


/* ══ 11. FORMS ═════════════════════════════════════════════════════════════ */

.field{
  width:100%;height:32px;padding:0 11px;
  border:1px solid var(--line);border-radius:10px;background:var(--surface);
  font-size:12.5px;outline:none;transition:border-color .16s ease;
}
.field:focus{border-color:var(--line-strong)}
.field::placeholder{color:var(--faint)}
.field.pill{border-radius:999px}
textarea.field{height:auto;min-height:72px;padding:9px 11px;line-height:1.55;resize:vertical}

/* Composer: a field and a send button sharing one bordered box. */
.composer{
  display:flex;align-items:center;gap:8px;flex:none;
  padding:7px 7px 7px 12px;
  border:1px solid var(--line-strong);border-radius:14px;background:var(--surface);
}
.composer:focus-within{box-shadow:var(--ring)}
.composer input{flex:1;min-width:0;border:0;outline:0;background:transparent;font-size:12.5px}
.composer input::placeholder{color:var(--faint)}
/* The one line stating what a surface may NOT do is the last thing that should
   disappear when there is less room. Keep it; let it wrap. */
.note{margin:8px 2px 0;color:var(--faint);font-size:10px;line-height:1.5}


/* ══ 12. RESPONSIVE FLOOR ══════════════════════════════════════════════════
   A floor, not a layout: these rules stop a page breaking on a narrow screen
   and restyle nothing that already works. 900px is this language's one
   breakpoint. Opt out of the whole block with <body class="mobile-off">. */

@media (max-width:900px){
  /* A hash, a URL or a receipt id forces the document wider than the screen.
     Breaking inside one beats scrolling the page sideways. */
  body:not(.mobile-off){overflow-wrap:break-word}
  /* Safari zooms the viewport when a control under 16px takes focus and never
     zooms back. max() keeps any page already asking for more. */
  body:not(.mobile-off) :is(input,select,textarea){font-size:max(16px,1em)}
  body:not(.mobile-off) .split{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto}
  body:not(.mobile-off) .panel.right{border-left:0;border-top:1px solid var(--line)}
  body:not(.mobile-off) .page{padding:28px 20px 44px}
  body:not(.mobile-off) .wrap{padding:0 18px 72px}
  body:not(.mobile-off) .dock{gap:5px;padding:7px 10px;height:60px}
  body:not(.mobile-off) .dock-item{width:42px;height:42px}
}

@media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important;animation-duration:.01ms!important}}

/* Print: chrome is not content. */
@media print{
  .menubar,.glassbar,.topbar,.tabstrip,.dock,.composer,.lights{display:none!important}
  .panel,.card,.void,.cwrap{box-shadow:none;break-inside:avoid}
  html{background:var(--surface)}
}


/* ══ 13. FORMS, IN FULL ════════════════════════════════════════════════════
   Validity is the hardest thing to say without colour. There is no red here, so
   an invalid field is said three ways at once: a thicker ink border, a dashed
   message, and aria-invalid. Never rely on the border alone — it is the weakest
   of the three and the one a colourblind or low-vision reader loses first. */

.formgroup{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.formgroup>label,.lbl{
  display:flex;align-items:center;gap:6px;
  color:var(--ink);font-size:12px;font-weight:620;letter-spacing:-.01em;
}
/* Required is a glyph AND the word, because a lone asterisk is not a label. */
.lbl .req{color:var(--muted);font:700 9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase}
.help{color:var(--muted);font-size:11.5px;line-height:1.5}

/* The invalid state. Ink border + dashed note + aria-invalid, all three. */
.field[aria-invalid="true"],.field.is-invalid{border-color:var(--ink);border-width:1.5px}
.err{
  display:flex;align-items:flex-start;gap:6px;
  padding:6px 9px;border:1px dashed var(--ink);border-radius:var(--radius-xs);background:var(--surface);
  color:var(--ink);font-size:11.5px;line-height:1.45;font-weight:540;
}
.err svg{width:13px;height:13px;flex:none;margin-top:1px}
.field[disabled],.field:disabled{background:var(--surface-2);color:var(--faint);cursor:not-allowed}

select.field{
  appearance:none;padding-right:30px;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 15px) 14px,calc(100% - 10px) 14px;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}

/* A field with something bolted to its edge — a prefix, a unit, a button. */
.inputgroup{display:flex;align-items:stretch;border:1px solid var(--line);border-radius:10px;background:var(--surface);overflow:hidden}
.inputgroup:focus-within{border-color:var(--line-strong);box-shadow:var(--ring)}
.inputgroup .field{border:0;border-radius:0;flex:1;min-width:0}
.inputgroup .field:focus{box-shadow:none}
.inputgroup .affix{
  display:grid;place-items:center;padding:0 11px;flex:none;
  background:var(--surface-2);border-right:1px solid var(--line);
  color:var(--muted);font:600 11px/1 var(--mono);
}
.inputgroup .affix:last-child{border-right:0;border-left:1px solid var(--line)}
.inputgroup .affix svg{width:14px;height:14px}

/* Search: the icon sits inside the control, so the control is still one target. */
.search{display:flex;align-items:center;gap:8px;height:32px;padding:0 12px;border:1px solid var(--line);border-radius:999px;background:var(--surface)}
.search:focus-within{border-color:var(--line-strong);box-shadow:var(--ring)}
.search svg{width:14px;height:14px;color:var(--faint);flex:none}
.search input{flex:1;min-width:0;border:0;outline:0;background:transparent;font-size:12.5px}
.search input::placeholder{color:var(--faint)}

/* Checkbox and radio. Custom because the native control cannot be made to match
   a 1px hairline language — but the real input stays in the DOM and keeps every
   keyboard and AT behaviour; it is moved under the box, not replaced. */
.check{display:flex;align-items:flex-start;gap:9px;cursor:pointer;font-size:12.5px;line-height:1.5;color:var(--ink-2)}
.check input{position:absolute;opacity:0;width:0;height:0}
.check .box{
  display:grid;place-items:center;width:16px;height:16px;flex:none;margin-top:1px;
  border:1px solid var(--line-strong);border-radius:5px;background:var(--surface);
  transition:background .14s ease,border-color .14s ease;
}
.check .box.round{border-radius:50%}
.check .box svg{width:10px;height:10px;color:var(--on-solid);opacity:0;stroke-width:2.6}
.check input:checked+.box{background:var(--solid);border-color:var(--solid)}
.check input:checked+.box svg{opacity:1}
/* The radio dot: a filled centre rather than a tick. */
.check input:checked+.box.round::after{content:"";width:6px;height:6px;border-radius:50%;background:var(--surface)}
.check input:focus-visible+.box{outline:2px solid var(--solid);outline-offset:2px}
.check input:disabled+.box{background:var(--surface-2);border-color:var(--line)}
.check input:disabled~span{color:var(--faint)}

/* Switch. Reads as on/off by FILL and knob position — two channels, so the
   state survives greyscale print and a forced-colors mode. */
.switch{display:flex;align-items:center;gap:9px;cursor:pointer;font-size:12.5px;color:var(--ink-2)}
.switch input{position:absolute;opacity:0;width:0;height:0}
.switch .track{
  position:relative;width:34px;height:20px;flex:none;
  border:1px solid var(--line-strong);border-radius:999px;background:var(--surface-2);
  transition:background .18s ease,border-color .18s ease;
}
.switch .track::after{
  content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
  background:var(--surface);box-shadow:0 1px 3px rgba(0,0,0,.2);
  transition:transform .18s ease;
}
.switch input:checked+.track{background:var(--solid);border-color:var(--solid)}
.switch input:checked+.track::after{transform:translateX(14px)}
.switch input:focus-visible+.track{outline:2px solid var(--solid);outline-offset:2px}

/* Range. The thumb is ink, the track is a hairline — same vocabulary as .meter. */
.range{appearance:none;width:100%;height:20px;background:transparent;cursor:pointer}
.range::-webkit-slider-runnable-track{height:3px;border-radius:999px;background:var(--line-strong)}
.range::-moz-range-track{height:3px;border-radius:999px;background:var(--line-strong)}
.range::-webkit-slider-thumb{appearance:none;width:15px;height:15px;margin-top:-6px;border-radius:50%;background:var(--solid);box-shadow:0 0 0 2px var(--surface)}
.range::-moz-range-thumb{width:15px;height:15px;border:0;border-radius:50%;background:var(--solid);box-shadow:0 0 0 2px var(--surface)}
.range:focus-visible{outline:2px solid var(--solid);outline-offset:3px}

fieldset{margin:0 0 16px;padding:13px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
legend{padding:0 7px;color:var(--faint);font:700 9px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase}

/* Drop zone — dashed because its content is not yet asserted, which is the same
   word dashed carries everywhere else in this language. */
.dropzone{
  display:grid;place-items:center;gap:7px;padding:26px 18px;text-align:center;
  border:1px dashed var(--line-strong);border-radius:var(--radius-sm);background:var(--paper-soft);
  color:var(--muted);font-size:12.5px;transition:background .16s ease,border-color .16s ease;
}
.dropzone:hover,.dropzone.is-over{background:var(--hover);border-color:var(--ink)}
.dropzone svg{width:20px;height:20px;color:var(--faint)}

/* Token / tag input. */
.tokens{display:flex;align-items:center;flex-wrap:wrap;gap:6px;padding:6px 8px;border:1px solid var(--line);border-radius:10px;background:var(--surface);min-height:34px}
.tokens:focus-within{border-color:var(--line-strong);box-shadow:var(--ring)}
.tokens input{flex:1;min-width:80px;border:0;outline:0;background:transparent;font-size:12.5px}
.token{
  display:inline-flex;align-items:center;gap:5px;height:21px;padding:0 4px 0 9px;
  border:1px solid var(--line);border-radius:999px;background:var(--surface-2);
  font-size:11px;font-weight:560;color:var(--ink-2);
}
.token button{display:grid;place-items:center;width:15px;height:15px;border-radius:50%;color:var(--faint)}
.token button:hover{background:var(--line-strong);color:var(--ink)}
.token button svg{width:8px;height:8px}

/* A keyboard key. */
kbd,.kbd{
  display:inline-grid;place-items:center;min-width:19px;height:19px;padding:0 5px;
  border:1px solid var(--line-strong);border-bottom-width:2px;border-radius:5px;background:var(--surface);
  font:650 10px/1 var(--mono);color:var(--ink-2);
}


/* ══ 14. NAVIGATION ════════════════════════════════════════════════════════ */

/* Sidebar nav. */
.sidenav{display:flex;flex-direction:column;gap:1px;padding:10px 8px}
.navitem{
  display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;
  border-radius:9px;color:var(--ink-2);text-align:left;font-size:12.5px;font-weight:540;
  transition:background .16s ease,color .16s ease;
}
.navitem:hover{background:var(--hover)}
.navitem[aria-current]{background:var(--solid);color:var(--on-solid);font-weight:600}
.navitem svg{width:15px;height:15px;flex:none}
.navitem .count{margin-left:auto;color:var(--faint);font:650 9.5px/1 var(--mono);font-variant-numeric:tabular-nums}
.navitem[aria-current] .count{color:var(--on-solid-2)}
.navgroup{padding:12px 10px 5px;color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}

/* Content tabs — the underline is ink, not a colour. */
.tabs{display:flex;align-items:center;gap:2px;border-bottom:1px solid var(--line)}
.tabs button{
  position:relative;padding:9px 12px;color:var(--muted);
  font-size:12.5px;font-weight:560;letter-spacing:-.012em;
  transition:color .16s ease;
}
.tabs button:hover{color:var(--ink)}
.tabs button[aria-selected="true"]{color:var(--ink);font-weight:650}
.tabs button[aria-selected="true"]::after{
  content:"";position:absolute;left:10px;right:10px;bottom:-1px;height:2px;border-radius:2px 2px 0 0;background:var(--solid);
}
.tabs .count{margin-left:6px;color:var(--faint);font:650 9.5px/1 var(--mono)}

/* Segmented control — one pressed segment, fill carries it. */
.segmented{display:inline-flex;padding:2px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}
.segmented button{
  padding:5px 12px;border-radius:8px;color:var(--muted);
  font-size:12px;font-weight:560;transition:background .16s ease,color .16s ease;
}
.segmented button[aria-pressed="true"]{background:var(--surface);color:var(--ink);font-weight:620;box-shadow:0 1px 2px rgba(0,0,0,.06)}

/* Pagination. */
.pager{display:flex;align-items:center;gap:4px}
.pager button{
  display:flex;align-items:center;justify-content:center;gap:5px;
  min-width:28px;height:28px;padding:0 7px;
  border:1px solid transparent;border-radius:8px;
  color:var(--ink-2);font:600 11.5px/1 var(--mono);font-variant-numeric:tabular-nums;
  transition:background .16s ease,border-color .16s ease;
}
.pager button:hover{background:var(--hover);border-color:var(--line)}
.pager button[aria-current="page"]{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.pager button[disabled]{color:var(--faint);pointer-events:none}
.pager .gap{color:var(--faint);font:600 11px/1 var(--mono);padding:0 2px}

/* Stepper. A done step is filled, the current step is ringed, a future step is
   hairline — three states, no hue, and the connector carries progress too. */
.stepper{display:flex;align-items:flex-start;gap:0;counter-reset:step}
.step{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:7px;text-align:center;position:relative}
.step::before{
  content:"";position:absolute;top:11px;left:calc(-50% + 14px);right:calc(50% + 14px);height:1px;background:var(--line-strong);
}
.step:first-child::before{display:none}
.step.is-done::before,.step.is-current::before{background:var(--solid)}
.step .dot{
  display:grid;place-items:center;width:23px;height:23px;z-index:1;
  border:1px solid var(--line-strong);border-radius:50%;background:var(--surface);
  color:var(--faint);font:700 10px/1 var(--mono);
}
.step.is-done .dot{background:var(--solid);border-color:var(--solid);color:var(--on-solid)}
.step.is-current .dot{border-color:var(--solid);border-width:2px;color:var(--ink)}
.step .nm{font-size:11.5px;font-weight:560;color:var(--muted);line-height:1.4}
.step.is-current .nm{color:var(--ink);font-weight:650}
.step svg{width:11px;height:11px;stroke-width:2.6}

/* Dropdown / context menu. */
.menu{
  min-width:188px;padding:5px;
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);
  box-shadow:var(--shadow-float);
}
.menu button,.menuitem{
  display:flex;align-items:center;gap:9px;width:100%;padding:6px 9px;
  border-radius:7px;color:var(--ink-2);text-align:left;font-size:12.5px;font-weight:540;
  transition:background .14s ease;
}
.menu button:hover,.menuitem:hover{background:var(--hover)}
.menu button svg,.menuitem svg{width:14px;height:14px;color:var(--muted);flex:none}
.menu button kbd{margin-left:auto}
.menu-sep{height:1px;margin:4px 0;background:var(--line-soft)}
.menu-label{padding:7px 9px 4px;color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}

/* Command palette. */
.palette{
  width:min(560px,calc(100vw - 48px));
  border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--surface);
  box-shadow:var(--shadow-float);overflow:hidden;
}
.palette-q{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--line)}
.palette-q svg{width:16px;height:16px;color:var(--faint);flex:none}
.palette-q input{flex:1;border:0;outline:0;background:transparent;font-size:14px}
.palette-q input::placeholder{color:var(--faint)}
.palette-list{max-height:320px;overflow-y:auto;scrollbar-width:none;padding:5px}
.palette-list::-webkit-scrollbar{display:none}
.palette-foot{display:flex;align-items:center;gap:12px;padding:8px 13px;border-top:1px solid var(--line);background:var(--surface-2);color:var(--faint);font-size:10.5px}

/* Toolbar + button group. */
.toolbar{display:flex;align-items:center;gap:6px;padding:6px 8px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}
.toolbar .sep{width:1px;height:20px;margin:0 2px;background:var(--line)}
.btngroup{display:inline-flex}
.btngroup .btn{border-radius:0;margin-left:-1px}
.btngroup .btn:first-child{border-radius:10px 0 0 10px;margin-left:0}
.btngroup .btn:last-child{border-radius:0 10px 10px 0}
.btngroup .btn[aria-pressed="true"]{background:var(--solid);color:var(--on-solid);border-color:var(--solid);position:relative;z-index:1}


/* ══ 15. FEEDBACK ══════════════════════════════════════════════════════════
   Severity without colour. An alert says its level with an ICON and a WORD;
   the border weight is a third, supporting channel. Never the border alone. */

.alert{
  display:flex;align-items:flex-start;gap:10px;
  padding:11px 13px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);
  font-size:12.5px;line-height:1.55;color:var(--ink-2);
}
.alert svg{width:15px;height:15px;flex:none;margin-top:1px;color:var(--ink)}
.alert b{display:block;margin-bottom:2px;color:var(--ink);font-size:12.5px;font-weight:650}
.alert.is-strong{border-color:var(--ink);border-width:1.5px}   /* needs action now */
.alert.is-quiet{background:var(--surface-2);border-style:dashed;color:var(--muted)}

.toast{
  display:flex;align-items:center;gap:10px;
  padding:10px 13px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);
  background:var(--solid);color:var(--on-solid);font-size:12.5px;font-weight:540;
  box-shadow:var(--shadow-float);
}
.toast svg{width:15px;height:15px;flex:none}
.toast button{margin-left:auto;color:var(--on-solid-2)}
.toast button:hover{color:var(--on-solid)}
.toast-stack{position:fixed;z-index:60;right:16px;bottom:16px;display:flex;flex-direction:column;gap:8px}

/* Progress. Determinate fills; indeterminate sweeps. */
.progress{height:4px;border-radius:999px;background:var(--line);overflow:hidden}
.progress>i{display:block;height:100%;border-radius:999px;background:var(--solid);transition:width .3s ease}
.progress.indeterminate>i{width:34%;animation:br-sweep 1.1s ease-in-out infinite}
@keyframes br-sweep{0%{transform:translateX(-110%)}100%{transform:translateX(330%)}}

/* Meter. The unfilled track is a lighter step of the SAME ramp, so state reads
   across the whole bar rather than only where the fill ends. */
.meter{display:flex;flex-direction:column;gap:5px}
.meter-head{display:flex;align-items:baseline;gap:8px;font-size:12px}
.meter-head b{font-weight:620;color:var(--ink)}
.meter-head .v{margin-left:auto;color:var(--muted);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}
.meter-track{height:7px;border-radius:999px;background:var(--seq-1)}
.meter-track>i{display:block;height:100%;border-radius:999px;background:var(--seq-4)}
.meter.is-high .meter-track>i{background:var(--seq-6)}
.meter.is-low .meter-track>i{background:var(--seq-3)}

.spinner{
  width:15px;height:15px;flex:none;border-radius:50%;
  border:2px solid var(--line-strong);border-top-color:var(--ink);
  animation:br-spin .7s linear infinite;
}
@keyframes br-spin{to{transform:rotate(360deg)}}

/* Skeleton. A placeholder must not be mistaken for content, so it never holds
   lorem text — only blocks, and it carries aria-busy on its container. */
.skel{border-radius:6px;background:linear-gradient(90deg,var(--line-soft) 25%,var(--hover) 37%,var(--line-soft) 63%);background-size:400% 100%;animation:br-shimmer 1.3s ease infinite}
.skel.line{height:11px;margin-bottom:7px}
.skel.line.short{width:58%}
.skel.block{height:72px}
.skel.circle{width:28px;height:28px;border-radius:50%}
@keyframes br-shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}


/* ══ 16. OVERLAYS ══════════════════════════════════════════════════════════ */

.scrim{position:fixed;inset:0;z-index:50;background:var(--scrim);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
.modal{
  position:fixed;z-index:51;left:50%;top:50%;transform:translate(-50%,-50%);
  width:min(520px,calc(100vw - 40px));max-height:calc(100vh - 80px);
  display:flex;flex-direction:column;
  border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--surface);
  box-shadow:var(--shadow-float);overflow:hidden;
}
.modal-head{display:flex;align-items:center;gap:11px;padding:14px 16px;border-bottom:1px solid var(--line);flex:none}
.modal-head b{font-size:13.5px;font-weight:680;letter-spacing:-.02em}
.modal-body{padding:16px;overflow-y:auto;min-height:0;font-size:13px;line-height:1.6;color:var(--ink-2)}
.modal-foot{display:flex;align-items:center;gap:8px;padding:12px 16px;border-top:1px solid var(--line);background:var(--surface-2);flex:none}
.modal-foot .btn:last-child{margin-left:auto}

.drawer{
  position:fixed;z-index:51;top:0;bottom:0;right:0;width:min(420px,calc(100vw - 40px));
  display:flex;flex-direction:column;
  border-left:1px solid var(--line-strong);background:var(--surface);box-shadow:var(--shadow-float);
}
.drawer.left{right:auto;left:0;border-left:0;border-right:1px solid var(--line-strong)}
.drawer.bottom{top:auto;left:0;right:0;width:auto;max-height:76vh;border:0;border-top:1px solid var(--line-strong);border-radius:var(--radius) var(--radius) 0 0}

.popover{
  min-width:200px;max-width:300px;padding:11px 13px;
  border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--surface);
  box-shadow:var(--shadow-float);font-size:12.5px;line-height:1.55;color:var(--ink-2);
}
/* Tooltip. Pure CSS, so it needs no JS — but a tooltip is never the ONLY place
   information lives: it must be reachable on focus, not hover alone. */
.tip{position:relative;display:inline-flex}
.tip>.tip-body{
  position:absolute;z-index:55;bottom:calc(100% + 7px);left:50%;transform:translateX(-50%);
  padding:5px 9px;border-radius:7px;background:var(--solid);color:var(--on-solid);
  font-size:11px;font-weight:540;line-height:1.4;white-space:nowrap;
  opacity:0;pointer-events:none;transition:opacity .14s ease;
}
.tip:hover>.tip-body,.tip:focus-within>.tip-body{opacity:1}


/* ══ 17. DISCLOSURE ════════════════════════════════════════════════════════ */

.accordion{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);overflow:hidden}
.accordion details+details{border-top:1px solid var(--line-soft)}
.accordion summary,.disclose summary{
  display:flex;align-items:center;gap:9px;padding:11px 13px;cursor:pointer;list-style:none;
  font-size:12.5px;font-weight:600;color:var(--ink);transition:background .16s ease;
}
.accordion summary::-webkit-details-marker,.disclose summary::-webkit-details-marker{display:none}
.accordion summary:hover,.disclose summary:hover{background:var(--hover)}
.accordion summary::after,.disclose summary::after{
  content:"";margin-left:auto;width:6px;height:6px;flex:none;
  border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);
  transform:rotate(45deg) translateY(-2px);transition:transform .18s ease;
}
.accordion details[open]>summary::after,.disclose[open]>summary::after{transform:rotate(-135deg) translateY(-2px)}
.accordion .acc-body,.disclose .acc-body{padding:0 13px 13px;font-size:12.5px;line-height:1.6;color:var(--ink-2)}
.disclose{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}


/* ══ 18. DATA DISPLAY, EXTENDED ════════════════════════════════════════════ */

/* Sticky-header table for long data. No zebra striping: a hairline per row is
   enough, and stripes add ink that carries no information. */
.table-sticky thead th{position:sticky;top:0;z-index:1}
.table-sort th button{display:inline-flex;align-items:center;gap:5px;color:inherit;font:inherit}
.table-sort th[aria-sort] button::after{content:"";width:0;height:0;border:3px solid transparent}
.table-sort th[aria-sort="ascending"] button::after{border-bottom-color:var(--ink);margin-bottom:3px}
.table-sort th[aria-sort="descending"] button::after{border-top-color:var(--ink);margin-top:3px}
tr.is-selected>td{background:var(--selected)}
tbody tr:hover>td{background:var(--hover)}

/* Tree. The guide line is the only thing that moves with depth; weight and
   colour stay identical, so indentation reads as containment, never as rank. */
.tree{margin:0;padding:0;list-style:none;font-size:12.5px}
.tree ul{margin:0;padding:0 0 0 15px;list-style:none;border-left:1px solid var(--line)}
.tree li>.node{
  display:flex;align-items:center;gap:7px;width:100%;padding:4px 8px;
  border-radius:7px;color:var(--ink-2);text-align:left;transition:background .14s ease;
}
.tree li>.node:hover{background:var(--hover)}
.tree li>.node[aria-current]{background:var(--solid);color:var(--on-solid)}
.tree .node svg{width:13px;height:13px;color:var(--muted);flex:none}
.tree .node[aria-current] svg{color:var(--on-solid-2)}
.tree .twisty{width:0;height:0;flex:none;border:3.5px solid transparent;border-left-color:var(--faint);transition:transform .16s ease}
.tree [open]>.node>.twisty,.tree .node[aria-expanded="true"]>.twisty{transform:rotate(90deg) translateX(-1px)}

/* Timeline. */
.timeline{position:relative;margin:0;padding:0 0 0 21px;list-style:none}
.timeline::before{content:"";position:absolute;left:4px;top:5px;bottom:5px;width:1px;background:var(--line-strong)}
.timeline>li{position:relative;padding-bottom:16px}
.timeline>li::before{
  content:"";position:absolute;left:-21px;top:4px;width:9px;height:9px;
  border:1px solid var(--line-strong);border-radius:50%;background:var(--surface);
}
.timeline>li.is-done::before{background:var(--solid);border-color:var(--solid)}
.timeline>li.is-now::before{border:2px solid var(--solid);background:var(--surface)}
.timeline .when{color:var(--faint);font:600 9.5px/1.4 var(--mono);letter-spacing:.04em}
.timeline .what{margin-top:3px;font-size:12.5px;line-height:1.5;color:var(--ink-2)}
.timeline .what b{color:var(--ink);font-weight:620}

/* Diff. Added and removed cannot be green and red here, so they are a SIGN
   glyph in a gutter plus a fill step — the sign is the primary channel. */
.diff{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);overflow:hidden;font:500 11.5px/1.7 var(--mono)}
.diff .dl{display:flex;gap:0}
.diff .dl>.g{
  flex:none;width:26px;padding:0 7px;text-align:center;
  color:var(--faint);background:var(--surface-2);border-right:1px solid var(--line-soft);
  user-select:none;
}
.diff .dl>.c{flex:1;min-width:0;padding:0 11px;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--ink-2)}
.diff .dl.add{background:var(--surface-2)}
.diff .dl.add>.g{color:var(--ink);font-weight:700;background:var(--selected)}
.diff .dl.del{background:var(--surface)}
.diff .dl.del>.g{color:var(--ink);font-weight:700}
.diff .dl.del>.c{color:var(--muted);text-decoration:line-through;text-decoration-color:var(--line-strong)}
.diff .dl.hunk{background:var(--surface-2);color:var(--faint)}
.diff .dl.hunk>.c{color:var(--faint)}

/* Code block with line numbers. */
.code{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-2);overflow:hidden}
.code-head{display:flex;align-items:center;gap:9px;padding:7px 11px;border-bottom:1px solid var(--line);background:var(--surface);color:var(--muted);font:600 10px/1 var(--mono)}
.code-head .push{margin-left:auto}
.code pre{margin:0;padding:11px 13px;border:0;border-radius:0;background:transparent;overflow-x:auto;font:500 11.5px/1.7 var(--mono);color:var(--ink-2);counter-reset:ln}
.code.numbered pre>span{display:block;counter-increment:ln}
.code.numbered pre>span::before{
  content:counter(ln);display:inline-block;width:26px;margin-right:13px;
  text-align:right;color:var(--faint);user-select:none;
}

/* Log stream. */
.log{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);overflow:hidden;font:500 11px/1.65 var(--mono)}
.log>div{display:flex;gap:10px;padding:3px 11px;border-bottom:1px solid var(--line-soft)}
.log>div:last-child{border-bottom:0}
.log .t{flex:none;color:var(--faint);font-variant-numeric:tabular-nums}
.log .lv{flex:none;width:34px;color:var(--muted);font-weight:700;font-size:9px;letter-spacing:.08em;text-transform:uppercase;padding-top:2px}
.log .m{flex:1;min-width:0;color:var(--ink-2);overflow-wrap:anywhere}
.log>div.is-strong{background:var(--surface-2)}
.log>div.is-strong .lv,.log>div.is-strong .m{color:var(--ink);font-weight:600}

/* File row + avatar group. */
.filerow{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}
.filerow .ft{display:grid;place-items:center;width:30px;height:30px;flex:none;border:1px solid var(--line);border-radius:7px;background:var(--surface-2);color:var(--muted)}
.filerow .ft svg{width:14px;height:14px}
.filerow .fn{min-width:0;flex:1}
.filerow .fn b{display:block;font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.filerow .fn span{color:var(--faint);font:600 10px/1.4 var(--mono)}
.avatars{display:flex}
.avatars .avatar{margin-left:-7px;box-shadow:0 0 0 2px var(--surface)}
.avatars .avatar:first-child{margin-left:0}
.avatars .more{background:var(--surface-2);color:var(--muted);border:1px solid var(--line);font:650 9.5px/1 var(--mono)}

/* Divider with a label in it. */
.rule{display:flex;align-items:center;gap:11px;margin:18px 0;color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase}
.rule::before,.rule::after{content:"";flex:1;height:1px;background:var(--line)}


/* ══ 19. FIGURES & CHARTS ══════════════════════════════════════════════════
   Monochrome changes what colour can do, so the encoding rules change with it:

     MAGNITUDE  → the --seq ramp below. One hue, light→dark. This is a correct
                  sequential scale and needs no apology.
     IDENTITY   → NOT available from colour. Four greys cannot be told apart
                  reliably by a CVD or low-vision reader, and ordered greys also
                  imply a ranking the categories do not have. Identity is carried
                  by TEXTURE (45° / 135° / dots / solid) plus a legend and direct
                  labels. Cap at FOUR series — a fifth is small multiples or an
                  "Other" bucket, never a fifth grey.
     POLARITY   → solid above the baseline, textured below, with a 1px ink zero
                  line. The sign is the glyph, not the shade.
     STATUS     → never encoded here at all. Status is an icon plus a word.

   Mark specs are fixed: bars ≤24px with a 4px rounded data-end and a square
   baseline, 2px lines with round caps, markers ≥8px with a 2px surface ring,
   a 2px surface gap between touching marks, hairline SOLID gridlines. */

:root{
  /* Sequential ramp — ordered, light→dark, measured against white. */
  --seq-1:#e8e8e5;  --seq-2:#c9c9c4;  --seq-3:#9a9a97;
  --seq-4:#6e6e6b;  --seq-5:#3a3a38;  --seq-6:#111111;
  --c-grid:#edede9;      /* one step off surface, recessive */
  --c-axis:#d9d9d5;
  --c-ink:#111;
}

/* Texture: the identity channel. 45° and its 135° mirror only — horizontal or
   vertical hatching reads as gridlines or as bars and misstates the chart. */
/* The period is 4px, not 6px. At 6px an 11px legend swatch fits barely one
   stripe and renders as an empty box — the swatches read as unticked
   checkboxes, which silently destroys the identity channel the whole chart
   depends on. 4px gives ~3 stripes in a 14px swatch and still reads cleanly at
   bar height. Any swatch smaller than 14px loses the texture again: that is the
   floor, not a preference. */
.tx-1{background:var(--seq-5)}
.tx-2{background:repeating-linear-gradient(45deg,var(--seq-5) 0 2px,var(--surface) 2px 4px)}
.tx-3{background:repeating-linear-gradient(135deg,var(--seq-4) 0 2px,var(--surface) 2px 4px)}
.tx-4{background:radial-gradient(var(--seq-4) 1.1px,var(--surface) 1.2px);background-size:4px 4px}

/* Chart container. A figure, because it owns a caption and a table twin. */
.chart{margin:0;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);padding:15px 16px 13px}
.chart-head{display:flex;align-items:baseline;gap:10px;margin-bottom:3px}
.chart-head b{font-size:12.5px;font-weight:680;letter-spacing:-.02em}
.chart-head .sub{color:var(--muted);font-size:11.5px}
.chart-head .push{margin-left:auto}
.chart figcaption,.chart .cap{margin-top:9px;color:var(--faint);font-size:11px;line-height:1.5}

/* Legend. Always present for two or more series; a single series gets none —
   the title already names what is plotted and a one-swatch box restates it. */
.legend{display:flex;align-items:center;flex-wrap:wrap;gap:7px 14px;margin-top:11px}
.legend>span{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:11px;font-weight:540}
/* 14px is the floor at which a 4px-period texture is still legible — below it
   the swatch goes blank and the legend stops carrying identity.
   Named .key, not .sw: two-letter names get claimed by pages for their own
   swatches, and a page-local .sw{background:var(--surface)} loaded after this file wins
   on source order and silently blanks every texture in the legend. */
.legend .key{width:14px;height:14px;flex:none;border:1px solid var(--line-strong);border-radius:3px}
.legend .ln{width:14px;height:2px;flex:none;border-radius:2px;background:var(--seq-6)}

/* Column chart. Bars are capped and the band's leftover stays as air. */
.bars{display:flex;align-items:flex-end;gap:10px;height:132px;padding-top:6px;border-bottom:1px solid var(--c-axis);position:relative}
.bars .grid{position:absolute;inset:0 0 0 0;pointer-events:none}
.bars .grid i{position:absolute;left:0;right:0;height:1px;background:var(--c-grid)}
.bars .col{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:5px;height:100%;position:relative}
.bars .bar{
  width:100%;max-width:24px;                      /* never fill the slot */
  border-radius:4px 4px 0 0;                      /* rounded data-end, square base */
  background:var(--seq-4);
}
.bars .col.is-emphasis .bar{background:var(--seq-6)}
.bars .col .v{position:absolute;bottom:calc(100% + 0px);font:650 10px/1 var(--mono);color:var(--ink);font-variant-numeric:tabular-nums}
.bars-x{display:flex;gap:10px;margin-top:7px}
.bars-x span{flex:1;min-width:0;text-align:center;color:var(--faint);font:600 9.5px/1.3 var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Stacked bar. The 2px surface gap is what separates the segments — never a
   stroke, which would add ink that is not data.
   Named .stackbar, not .stack: .stack is already the vertical layout utility in
   section 3, and a second .stack here inherited its flex-direction:column and
   rendered every segment on its own line. One class may not do two jobs. */
.stackbar{display:flex;flex-direction:row;height:22px;border-radius:5px;overflow:hidden;gap:2px;background:transparent}
.stackbar>i{display:block;height:100%}
.stackbar>i:first-child{border-radius:5px 0 0 5px}
.stackbar>i:last-child{border-radius:0 5px 5px 0}

/* Horizontal bar rows — the right default for many or long-named categories. */
.hbars{display:flex;flex-direction:column;gap:9px}
.hbar{display:grid;grid-template-columns:116px minmax(0,1fr) auto;align-items:center;gap:11px}
.hbar .k{color:var(--ink-2);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hbar .t{height:14px;border-radius:0 4px 4px 0;background:var(--seq-2)}
.hbar .t>i{display:block;height:100%;border-radius:0 4px 4px 0;background:var(--seq-5)}
.hbar .v{color:var(--muted);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Line / sparkline. 2px, round caps; the end marker carries a 2px surface ring. */
.spark{display:block;width:100%;height:34px;overflow:visible}
.spark path{fill:none;stroke:var(--seq-6);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.spark path.area{fill:var(--seq-1);stroke:none}
.spark path.ctx{stroke:var(--seq-2)}
.spark circle{fill:var(--seq-6);stroke:var(--surface);stroke-width:2}   /* the surface ring */

/* Heatmap — a grid is the right form for magnitude across two keys. */
.heat{display:grid;gap:2px}
.heat i{aspect-ratio:1;border-radius:3px;background:var(--seq-1)}
.heat i[data-v="1"]{background:var(--seq-1)} .heat i[data-v="2"]{background:var(--seq-2)}
.heat i[data-v="3"]{background:var(--seq-3)} .heat i[data-v="4"]{background:var(--seq-4)}
.heat i[data-v="5"]{background:var(--seq-5)} .heat i[data-v="6"]{background:var(--seq-6)}
/* Scale legend for a sequential ramp. */
.scale{display:flex;align-items:center;gap:7px;margin-top:10px;color:var(--faint);font:600 9.5px/1 var(--mono)}
.scale .ramp{display:flex;gap:2px}
.scale .ramp i{width:17px;height:9px;border-radius:2px}

/* Stat tile. Value uses PROPORTIONAL figures — tabular-nums gives every digit
   the width of a 0, which looks loose at display sizes. Tabular is for columns
   of numbers that must align vertically, not for a standalone value. */
.stat{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);padding:13px 15px}
.stat .lab{color:var(--muted);font-size:11.5px;font-weight:540;line-height:1.35}
.stat .val{margin-top:5px;color:var(--ink);font-size:27px;font-weight:700;letter-spacing:-.035em;line-height:1.05}
.stat .delta{display:inline-flex;align-items:center;gap:4px;margin-top:6px;color:var(--muted);font-size:11px;font-weight:600}
.stat .delta svg{width:11px;height:11px;stroke-width:2.4}
.stat .delta.is-strong{color:var(--ink)}
.stat .spark{margin-top:9px;height:26px}
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:10px}

/* Hero figure. Exactly one per view, ≥48px, same sans as everything else — a
   display or serif face here reads as decoration bolted onto the brand. */
.hero-fig{font-size:clamp(48px,7vw,72px);font-weight:760;letter-spacing:-.05em;line-height:1;color:var(--ink)}
.hero-lab{margin-top:7px;color:var(--muted);font-size:13px;font-weight:540}

/* Every chart's accessibility twin. Not optional: the table is where the values
   a chart only implies actually live. */
.chart-table{margin-top:11px}
.chart-table[hidden]{display:none}

@media (prefers-reduced-motion:reduce){
  .skel,.progress.indeterminate>i,.spinner{animation:none}
  .skel{background:var(--line-soft)}
}

/* Forced colors and print: texture is the channel that survives both. */
@media (forced-colors:active){
  .badge.solid,.chip[aria-pressed="true"],.navitem[aria-current]{forced-color-adjust:none}
  .bars .bar,.hbar .t>i,.stack>i{border:1px solid CanvasText}
}


/* ══ 20. UTILITIES ═════════════════════════════════════════════════════════
   A short, closed list. Utilities are where a design system goes to die, so
   these cover spacing-between and visibility only — never colour or type. */

.gap-0{gap:0}.gap-1{gap:4px}.gap-2{gap:8px}.gap-3{gap:12px}.gap-4{gap:16px}.gap-5{gap:24px}
.pad-0{padding:0}.pad-1{padding:6px}.pad-2{padding:12px}.pad-3{padding:16px}.pad-4{padding:24px}
.mt-0{margin-top:0}.mt-1{margin-top:4px}.mt-2{margin-top:8px}.mt-3{margin-top:12px}
.mt-4{margin-top:16px}.mt-5{margin-top:24px}.mt-6{margin-top:36px}
.wrapped{flex-wrap:wrap}
.grow{flex:1;min-width:0}
.none{flex:none}
.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.center{text-align:center}
.cols-2{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
.cols-3{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:10px}
/* Visible to screen readers only — for the label a sighted user gets from layout. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}


/* ══ 21. MORE COMPONENTS ═══════════════════════════════════════════════════ */

/* Banner — page-width, sits above content. Not an alert: an alert is about one
   thing you did, a banner is about the whole page or session. */
.banner{
  display:flex;align-items:center;gap:11px;padding:10px 16px;
  border-bottom:1px solid var(--line);background:var(--surface-2);
  font-size:12.5px;color:var(--ink-2);
}
.banner.is-strong{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.banner.is-strong .btn{background:transparent;border-color:var(--muted);color:var(--on-solid)}
.banner.is-strong .btn:hover{background:var(--hover)}
.banner svg{width:15px;height:15px;flex:none}
.banner .push .btn{margin-left:6px}

/* Callout — a block aside inside prose. The ink rule on the left is the signal;
   it never gets a tinted background, because tint is colour by another name. */
.callout{
  padding:11px 15px;border-left:2px solid var(--ink);background:var(--surface-2);
  border-radius:0 var(--radius-sm) var(--radius-sm) 0;
  font-size:13px;line-height:1.6;color:var(--ink-2);
}
.callout b{display:block;margin-bottom:3px;color:var(--ink);font-weight:650}
.callout.is-quiet{border-left-color:var(--line-strong);background:transparent}

blockquote,.quote{
  margin:0;padding-left:13px;border-left:2px solid var(--line-strong);
  color:var(--muted);font-size:13px;line-height:1.6;font-style:italic;
}
.quote cite{display:block;margin-top:5px;font-style:normal;font:600 10px/1.4 var(--mono);color:var(--faint)}

/* Footnote / fine print. */
.fine{color:var(--faint);font-size:10.5px;line-height:1.5}
sup.fn,.fn-ref{font:700 8px/1 var(--mono);vertical-align:3px;color:var(--ink);cursor:help}

/* A link that reads as a link without a colour: underline on hover, never by
   default (an estate full of paths would otherwise be a field of underlines). */
.link{color:var(--ink);font-weight:580;border-bottom:1px solid var(--line-strong);transition:border-color .16s ease}
.link:hover{border-bottom-color:var(--ink)}
.backlink{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:12px;font-weight:560}
.backlink:hover{color:var(--ink)}
.backlink svg{width:13px;height:13px}

/* Copy-to-clipboard. The confirmation replaces the icon in place rather than
   firing a toast — the feedback belongs where the action happened. */
.copy{
  display:inline-flex;align-items:center;gap:6px;height:23px;padding:0 8px;
  border:1px solid var(--line);border-radius:7px;background:var(--surface);
  color:var(--muted);font:600 10px/1 var(--mono);letter-spacing:.04em;
  transition:background .16s ease,color .16s ease,border-color .16s ease;
}
.copy:hover{background:var(--hover);color:var(--ink);border-color:var(--line-strong)}
.copy[data-copied="true"]{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.copy svg{width:11px;height:11px}

/* Presence. A dot alone is colour-dependent nonsense in monochrome, so presence
   is FILL (here) / RING (idle) / HOLLOW (away) plus a word. */
.presence{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:11px;font-weight:560}
.presence::before{content:"";width:8px;height:8px;flex:none;border-radius:50%;border:1.5px solid var(--ink);background:var(--ink)}
.presence.is-idle::before{background:var(--surface)}
.presence.is-away::before{border-color:var(--line-strong);background:var(--surface)}

/* A count riding an icon button. */
.has-count{position:relative}
.has-count .dot-count{
  position:absolute;top:-3px;right:-3px;
  display:grid;place-items:center;min-width:15px;height:15px;padding:0 4px;
  border:2px solid var(--surface);border-radius:999px;background:var(--solid);color:var(--on-solid);
  font:700 8.5px/1 var(--mono);
}

/* Calendar / date grid. */
.cal{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:2px;font:600 11px/1 var(--mono)}
.cal th{padding:4px 0;border:0;background:transparent;color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.08em;text-align:center}
.cal td{padding:0;border:0;text-align:center;vertical-align:middle}
.cal td button{
  display:grid;place-items:center;width:100%;height:30px;border-radius:7px;
  color:var(--ink-2);font:inherit;font-variant-numeric:tabular-nums;
  transition:background .14s ease;
}
.cal td button:hover{background:var(--hover)}
.cal td button[aria-pressed="true"]{background:var(--solid);color:var(--on-solid)}
.cal td button[aria-current="date"]{box-shadow:inset 0 0 0 1.5px var(--ink)}
.cal td button[disabled]{color:var(--line-strong);pointer-events:none}
.cal-head{display:flex;align-items:center;gap:9px;margin-bottom:9px}
.cal-head b{font-size:12.5px;font-weight:650;letter-spacing:-.015em}

/* Rating. Fill is the value; the number beside it is the real answer. */
.rating{display:inline-flex;align-items:center;gap:3px}
.rating svg{width:13px;height:13px;color:var(--line-strong)}
.rating svg.on{color:var(--ink);fill:var(--ink)}
.rating .n{margin-left:5px;color:var(--muted);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Horizontal gallery / card strip. */
.strip{display:flex;gap:10px;overflow-x:auto;scrollbar-width:none;padding-bottom:2px}
.strip::-webkit-scrollbar{display:none}
.strip>*{flex:0 0 auto}
.thumb{
  display:grid;place-items:center;width:112px;aspect-ratio:4/3;
  border:1px solid var(--line);border-radius:10px;background:var(--surface-2);
  color:var(--faint);
}
.thumb svg{width:18px;height:18px}

/* Split-pane handle. */
.handle{
  flex:none;width:5px;cursor:col-resize;position:relative;background:transparent;
}
.handle::after{content:"";position:absolute;inset:0 2px;background:var(--line);transition:background .16s ease}
.handle:hover::after{background:var(--ink)}
.handle.row{width:auto;height:5px;cursor:row-resize}
.handle.row::after{inset:2px 0}

/* Scroll shadow — tells you there is more, without a scrollbar. */
.shadow-y{
  background:
    linear-gradient(var(--surface) 30%,transparent) top/100% 14px no-repeat,
    linear-gradient(transparent,var(--surface) 70%) bottom/100% 14px no-repeat,
    radial-gradient(farthest-side at 50% 0,rgba(8,8,8,.07),transparent) top/100% 6px no-repeat,
    radial-gradient(farthest-side at 50% 100%,rgba(8,8,8,.07),transparent) bottom/100% 6px no-repeat;
  background-attachment:local,local,scroll,scroll;
}

/* Changelog entry. */
.release{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--line-soft)}
.release:last-child{border-bottom:0}
.release .ver{
  flex:0 0 auto;align-self:flex-start;
  padding:2px 8px;border:1px solid var(--line-strong);border-radius:999px;background:var(--surface);
  font:700 10px/1.5 var(--mono);color:var(--ink);font-variant-numeric:tabular-nums;
}
.release .when{color:var(--faint);font:600 9.5px/1.4 var(--mono)}
.release h4{margin:0 0 4px;font-size:13px;font-weight:650;letter-spacing:-.015em}

/* Pricing / comparison. The recommended column is fill and a label, not a hue. */
.tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px}
.tier{display:flex;flex-direction:column;gap:9px;padding:17px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
.tier.is-pick{border-color:var(--ink);border-width:1.5px;box-shadow:var(--shadow)}
.tier .nm{font-size:12.5px;font-weight:650;letter-spacing:-.015em}
.tier .price{font-size:27px;font-weight:720;letter-spacing:-.04em;line-height:1}
.tier .price small{font-size:11.5px;font-weight:560;color:var(--muted);letter-spacing:0}
.tier ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
.tier li{position:relative;padding-left:18px;font-size:12px;line-height:1.5;color:var(--ink-2)}
.tier li::before{content:"";position:absolute;left:2px;top:6px;width:7px;height:4px;border-left:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(-45deg)}
.tier li.is-off{color:var(--faint)}
.tier li.is-off::before{border:0;left:3px;top:8px;width:6px;height:1.5px;background:var(--line-strong);transform:none}

/* Feature grid. */
.features{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}
.feature svg{width:19px;height:19px;color:var(--ink)}
.feature h4{margin:9px 0 4px;font-size:13px;font-weight:650;letter-spacing:-.015em}
.feature p{color:var(--muted);font-size:12.5px;line-height:1.55}

/* Hero + call to action. */
.hero{padding:52px 0 40px;max-width:720px}
.hero h1{font-size:clamp(30px,4.4vw,44px);font-weight:740;letter-spacing:-.045em;line-height:1.08}
.hero p{margin-top:16px;color:var(--ink-2);font-size:16px;font-weight:500;line-height:1.55;letter-spacing:-.015em}
.hero .row{margin-top:22px}
.cta{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  padding:22px 24px;border:1px solid var(--ink);border-radius:var(--radius);background:var(--solid);color:var(--on-solid);
}
.cta b{font-size:15px;font-weight:680;letter-spacing:-.025em}
.cta p{margin-top:3px;color:var(--on-solid-2);font-size:12.5px;line-height:1.5}
.cta .btn{background:var(--surface);color:var(--ink);border-color:var(--on-solid)}
.cta .btn:hover{background:var(--selected)}

/* Cookie / consent bar. Both choices are equally reachable — a consent bar that
   hides the refusal is a dark pattern, and this system does not ship one. */
.consent{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:13px 16px;border:1px solid var(--line-strong);border-radius:var(--radius-sm);
  background:var(--surface);box-shadow:var(--shadow-float);font-size:12.5px;color:var(--ink-2);
}
.consent .btn{flex:none}


/* ══ 22. CHARTS II ═════════════════════════════════════════════════════════
   Same encoding rules as section 19: magnitude on the --seq ramp, identity by
   texture, polarity by side-of-baseline. These are the forms the first pass
   left out. */

/* Diverging bar. The zero line is 1px ink and the sign is the primary channel:
   solid to the right, textured to the left. Never two hues. */
.diverge{display:flex;flex-direction:column;gap:7px}
.dvg{display:grid;grid-template-columns:104px 1fr 1fr auto;align-items:center;gap:0 10px}
.dvg .k{color:var(--ink-2);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dvg .neg,.dvg .pos{height:14px;position:relative}
.dvg .neg{border-right:1px solid var(--ink)}
.dvg .neg>i{position:absolute;right:0;top:0;height:100%;border-radius:4px 0 0 4px}
.dvg .pos{border-left:1px solid var(--ink);margin-left:-1px}
.dvg .pos>i{position:absolute;left:0;top:0;height:100%;border-radius:0 4px 4px 0;background:var(--seq-5)}
.dvg .v{color:var(--muted);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Dumbbell — before → after per item. One hue, two shades, a connecting rule. */
.dumbbells{display:flex;flex-direction:column;gap:10px}
.dumb{display:grid;grid-template-columns:104px 1fr auto;align-items:center;gap:10px}
.dumb .k{color:var(--ink-2);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dumb .t{position:relative;height:13px}
.dumb .t::before{content:"";position:absolute;top:50%;left:var(--a);right:calc(100% - var(--b));height:2px;transform:translateY(-50%);background:var(--seq-2)}
.dumb .a,.dumb .b{position:absolute;top:50%;width:11px;height:11px;border-radius:50%;transform:translate(-50%,-50%);box-shadow:0 0 0 2px var(--surface)}
.dumb .a{left:var(--a);background:var(--seq-3)}
.dumb .b{left:var(--b);background:var(--seq-6)}
.dumb .v{color:var(--muted);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Scatter. Markers carry a surface ring so overlaps stay countable. */
.scatter{position:relative;aspect-ratio:16/9;border-left:1px solid var(--c-axis);border-bottom:1px solid var(--c-axis)}
.scatter .grid{position:absolute;inset:0;pointer-events:none}
.scatter .grid i{position:absolute;left:0;right:0;height:1px;background:var(--c-grid)}
.scatter .pt{
  position:absolute;width:9px;height:9px;border-radius:50%;
  background:var(--seq-5);box-shadow:0 0 0 2px var(--surface);transform:translate(-50%,50%);
}
.scatter .pt.is-emphasis{background:var(--seq-6);width:11px;height:11px}

/* Box plot. */
.box{position:relative;height:22px}
.box .whisk{position:absolute;top:50%;height:1px;background:var(--seq-4);transform:translateY(-50%)}
.box .iqr{position:absolute;top:2px;bottom:2px;border:1px solid var(--seq-5);border-radius:3px;background:var(--seq-1)}
.box .med{position:absolute;top:0;bottom:0;width:2px;background:var(--seq-6)}
.box .cap{position:absolute;top:5px;bottom:5px;width:1px;background:var(--seq-4)}

/* Gauge — a single ratio against a limit, when a meter needs more presence. */
.gauge{position:relative;width:104px;aspect-ratio:2/1;overflow:hidden}
.gauge svg{width:104px;height:104px;overflow:visible}
.gauge .track{fill:none;stroke:var(--seq-1);stroke-width:9;stroke-linecap:round}
.gauge .fill{fill:none;stroke:var(--seq-6);stroke-width:9;stroke-linecap:round}
.gauge .val{
  position:absolute;left:0;right:0;bottom:0;text-align:center;
  font-size:19px;font-weight:700;letter-spacing:-.03em;color:var(--ink);
}

/* Small multiples — the answer when a 5th series would otherwise appear. */
.multiples{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:10px}
.multiple{border:1px solid var(--line);border-radius:10px;background:var(--surface);padding:10px 11px}
.multiple .nm{color:var(--muted);font-size:11px;font-weight:580}
.multiple .n{margin-top:2px;font-size:16px;font-weight:700;letter-spacing:-.03em}

/* Chart hover tooltip. */
.chart-tip{
  position:absolute;z-index:5;pointer-events:none;
  padding:7px 10px;border-radius:8px;background:var(--solid);color:var(--on-solid);
  font-size:11px;line-height:1.45;white-space:nowrap;box-shadow:var(--shadow-float);
}
.chart-tip b{display:block;font-weight:680;font-size:11.5px}
.chart-tip .r{display:flex;align-items:center;gap:6px;margin-top:3px;color:var(--line-strong)}
.chart-tip .r i{width:9px;height:9px;flex:none;border-radius:2px}
.chart-tip .r b{display:inline;margin-left:auto;padding-left:14px;color:var(--on-solid);font-variant-numeric:tabular-nums}

/* Filter row — one row, above the charts, never beside them. */
.chart-filters{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:12px}
.chart-filters .push{margin-left:auto}

/* The chart that has nothing to show says which source is empty. */
.chart-empty{
  display:grid;place-items:center;gap:7px;min-height:118px;padding:18px;text-align:center;
  border:1px dashed var(--line-strong);border-radius:var(--radius-sm);
  color:var(--muted);font-size:12px;
}
.chart-empty svg{width:19px;height:19px;color:var(--faint)}


/* ══ 23. DARK ═════════════════════════════════════════════════════════════
   A selected theme, not an automatic inversion. Every step below was measured
   against the DARK grounds rather than flipped from the light values — an
   inverted #6e6e6b lands at 3.1:1 on a near-black page and fails the same bar
   the light ramp was built to clear.

   The ground is true black. An off-black page (#121212) cost 2.6 points of
   contrast at the top of the ramp for no benefit: on an OLED panel the darker
   ground is also the one that actually switches pixels off, and white-on-black
   is the pairing this language is named for.

   Measured, against --paper-soft #080808 / the harder --surface-2 #161616:
     --ink    #ffffff   20.03 / 18.10
     --ink-2  #d1d1cf   13.10 / 11.83
     --muted  #959593    6.67 /  6.03
     --faint  #868684    5.49 /  4.96
   Four distinct steps, all clearing AA on both grounds, and now tracking the
   light ramp almost exactly (19.4 / 13.1 / 6.7 / 4.9) rather than sitting a
   step below it. --faint is the one slot NOT snapped to its light twin: the
   nearest match (#7e7e7c, 4.92) measured 4.45 on the inset ground and failed,
   so it is stepped up until both grounds clear.

   The --seq chart ramp REVERSES direction: in light, more magnitude is darker;
   on a dark page, more magnitude has to be lighter, or the scale reads upside
   down. Flipping the surface without flipping the ramp is the classic dark-mode
   chart bug.

   Both signals are honoured: the OS preference, and an explicit
   data-theme on the root, which must win in both directions so a page-level
   toggle can override the system. */

@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){ color-scheme:dark;
  --paper:#000000; --paper-soft:#080808; --surface:#0e0e0e; --surface-2:#161616;
  /* --faint 2026-10-05: #868684 measured 4.26:1 on the dark --selected
     (#242422) -- the same omission as the light ramp, in the same place.
     #8b8b88 clears 4.5 on all six dark grounds (worst 4.55 on --selected). */
  --ink:#ffffff; --ink-2:#d1d1cf; --muted:#959593; --faint:#8b8b88;
  --line:#242422; --line-strong:#383835; --line-soft:#1a1a19;
  --hover:#191918; --selected:#242422; --sel-ink:#3a3a38;
  --solid:#ffffff; --solid-hover:#f2f2f0; --on-solid:#000000; --on-solid-2:#5c5c59;
  --glass:rgba(8,8,8,.86); --glass-2:rgba(8,8,8,.72); --glass-line:rgba(52,52,49,.92);
  --scrim:rgba(0,0,0,.62);
  --shadow:0 1px 1px rgba(0,0,0,.4),0 12px 40px rgba(0,0,0,.55);
  --shadow-float:0 28px 90px rgba(0,0,0,.8),0 2px 10px rgba(0,0,0,.6);
  --ring:0 0 0 3px rgba(255,255,255,.09);
  --seq-1:#1f1f1d; --seq-2:#383835; --seq-3:#5a5a57;
  --seq-4:#868684; --seq-5:#b8b8b5; --seq-6:#ffffff;
  --c-grid:#1b1b1a; --c-axis:#2e2e2c;
}}

:root[data-theme="dark"]{ color-scheme:dark;
  --paper:#000000; --paper-soft:#080808; --surface:#0e0e0e; --surface-2:#161616;
  /* --faint 2026-10-05: #868684 measured 4.26:1 on the dark --selected
     (#242422) -- the same omission as the light ramp, in the same place.
     #8b8b88 clears 4.5 on all six dark grounds (worst 4.55 on --selected). */
  --ink:#ffffff; --ink-2:#d1d1cf; --muted:#959593; --faint:#8b8b88;
  --line:#242422; --line-strong:#383835; --line-soft:#1a1a19;
  --hover:#191918; --selected:#242422; --sel-ink:#3a3a38;
  --solid:#ffffff; --solid-hover:#f2f2f0; --on-solid:#000000; --on-solid-2:#5c5c59;
  --glass:rgba(8,8,8,.86); --glass-2:rgba(8,8,8,.72); --glass-line:rgba(52,52,49,.92);
  --scrim:rgba(0,0,0,.62);
  --shadow:0 1px 1px rgba(0,0,0,.4),0 12px 40px rgba(0,0,0,.55);
  --shadow-float:0 28px 90px rgba(0,0,0,.8),0 2px 10px rgba(0,0,0,.6);
  --ring:0 0 0 3px rgba(255,255,255,.09);
  --seq-1:#1f1f1d; --seq-2:#383835; --seq-3:#5a5a57;
  --seq-4:#868684; --seq-5:#b8b8b5; --seq-6:#ffffff;
  --c-grid:#1b1b1a; --c-axis:#2e2e2c;
}


/* The surface ring on a dot and the knob on a switch are literal white in the
   mark specs; in dark they must be the surface they sit on. */


/* ══ 24. AGENT & PROVENANCE ════════════════════════════════════════════════
   The components an agent surface needs that an ordinary app does not. The
   whole cluster obeys one rule: a claim is rendered differently from a fact.
   Solid = measured. Hairline = ordinary. Dashed = asserted and not verified.
   Nothing here may show a number it did not get from a source. */

/* A receipt. The hash is the point, so it is never truncated out of the DOM —
   only visually, with the full value in title/copy. */
.receipt{
  display:flex;flex-direction:column;gap:8px;padding:12px 13px;
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);
}
.receipt-head{display:flex;align-items:center;gap:8px}
.receipt-head b{font-size:12.5px;font-weight:650;letter-spacing:-.015em}
.receipt-head svg{width:14px;height:14px;color:var(--muted);flex:none}
.receipt .hash{
  font:600 10.5px/1.5 var(--mono);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.receipt.is-broken{border-color:var(--ink);border-width:1.5px}
/* The chain link between two receipts. */
.chain{display:flex;flex-direction:column;gap:0}
.chain>.receipt+.receipt{margin-top:18px;position:relative}
.chain>.receipt+.receipt::before{
  content:"";position:absolute;left:21px;top:-18px;height:18px;width:1px;background:var(--line-strong);
}
.chain>.receipt+.receipt::after{
  content:"";position:absolute;left:18px;top:-8px;width:7px;height:7px;
  border-right:1.5px solid var(--line-strong);border-bottom:1.5px solid var(--line-strong);
  transform:rotate(45deg);
}

/* Provenance. Four states, and "unknown" is a real one — presuming a source is
   how provenance becomes fiction. */
.prov{
  display:inline-flex;align-items:center;gap:5px;height:19px;padding:0 8px;flex:none;
  border:1px solid var(--line-strong);border-radius:999px;background:var(--surface);
  color:var(--muted);font:600 9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;
}
.prov svg{width:10px;height:10px}
.prov.is-measured{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.prov.is-derived{border-style:solid}
.prov.is-asserted{border-style:dashed;color:var(--faint)}
.prov.is-unknown{border-style:dotted;color:var(--faint);background:var(--surface-2)}

/* A tool call an agent made, with what it cost. */
.toolcall{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);overflow:hidden}
.toolcall>summary{
  display:flex;align-items:center;gap:9px;padding:9px 12px;cursor:pointer;list-style:none;
  font:600 11.5px/1 var(--mono);color:var(--ink);
}
.toolcall>summary::-webkit-details-marker{display:none}
.toolcall>summary:hover{background:var(--hover)}
.toolcall>summary svg{width:13px;height:13px;color:var(--muted);flex:none}
.toolcall .ms{margin-left:auto;color:var(--faint);font:600 10px/1 var(--mono);font-variant-numeric:tabular-nums}
.toolcall .io{padding:0 12px 11px;border-top:1px solid var(--line-soft)}
.toolcall .io pre{margin:9px 0 0;padding:9px 11px;border-radius:var(--radius-xs);background:var(--surface-2);
  font:500 11px/1.6 var(--mono);color:var(--ink-2);overflow-x:auto}
.toolcall.is-refused>summary{border-left:2px solid var(--ink)}

/* Citations under an answer. A citation that does not resolve is not rendered. */
.cites{display:flex;flex-direction:column;gap:5px;margin-top:9px;padding-top:9px;border-top:1px solid var(--line-soft)}
.cites a,.cites .src{display:flex;align-items:flex-start;gap:8px;font-size:11.5px;line-height:1.5;color:var(--muted)}
.cites .n{
  flex:none;display:grid;place-items:center;min-width:15px;height:15px;margin-top:1px;padding:0 3px;
  border-radius:4px;background:var(--solid);color:var(--on-solid);font:700 9px/1 var(--mono);
}
.cites a:hover{color:var(--ink)}

/* Confidence. The band is the estimate; the number beside it is the answer. A
   band with no number is a vibe, not a measurement. */
.confidence{display:flex;align-items:center;gap:9px}
.confidence .band{position:relative;flex:1;height:5px;border-radius:999px;background:var(--seq-1)}
.confidence .band>i{position:absolute;top:0;bottom:0;border-radius:999px;background:var(--seq-4)}
.confidence .band>b{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--seq-6)}
.confidence .v{flex:none;color:var(--ink-2);font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Context window / budget gauge. */
.budget{display:flex;flex-direction:column;gap:5px}
.budget-head{display:flex;align-items:baseline;gap:8px;font-size:11.5px;color:var(--muted)}
.budget-head b{color:var(--ink);font-weight:620}
.budget-head .v{margin-left:auto;font:650 11px/1 var(--mono);font-variant-numeric:tabular-nums}
.budget .track{display:flex;height:9px;border-radius:999px;overflow:hidden;gap:1px;background:var(--seq-1)}
.budget .track>i{height:100%}
.budget .track>i:first-child{background:var(--seq-5)}
.budget .track>i:nth-child(2){background:var(--seq-3)}
.budget .track>i:nth-child(3){background:var(--seq-2)}

/* Streaming. The caret is the only animated thing in this system. */
.streaming::after{
  content:"";display:inline-block;width:2px;height:1em;margin-left:2px;
  background:var(--ink);vertical-align:-2px;animation:br-caret 1s steps(2) infinite;
}
@keyframes br-caret{0%,50%{opacity:1}50.01%,100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.streaming::after{animation:none}}

/* An approval an agent is blocked on. Both answers are equally reachable; the
   scope of what is being approved is spelled out, never summarised away. */
.approval{
  display:flex;flex-direction:column;gap:10px;padding:14px 15px;
  border:1.5px solid var(--ink);border-radius:var(--radius-sm);background:var(--surface);
}
.approval-head{display:flex;align-items:center;gap:9px}
.approval-head b{font-size:13px;font-weight:680;letter-spacing:-.02em}
.approval-head svg{width:15px;height:15px;flex:none}
.approval .scope{
  padding:9px 11px;border-radius:var(--radius-xs);background:var(--surface-2);
  font:600 10.5px/1.65 var(--mono);color:var(--ink-2);
}
.approval .scope dfn{font-style:normal;font-weight:700;color:var(--ink)}
.approval .row{gap:8px}

/* A run: what an agent did, start to finish. */
.run{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}
.run .state{
  display:grid;place-items:center;width:20px;height:20px;flex:none;border-radius:50%;
  border:1px solid var(--line-strong);color:var(--muted);
}
.run .state svg{width:11px;height:11px}
.run.is-done .state{background:var(--solid);border-color:var(--solid);color:var(--on-solid)}
.run.is-running .state{border:2px solid var(--ink);color:var(--ink)}
.run.is-refused .state{border-style:dashed}
.run .nm{flex:1;min-width:0;font-size:12.5px;font-weight:560}
.run .nm span{display:block;color:var(--faint);font:600 9.5px/1.4 var(--mono)}
.run .ms{color:var(--muted);font:650 10.5px/1 var(--mono);font-variant-numeric:tabular-nums}

/* The engine is a PART, not an identity — small, secondary, swappable. The
   agent's name is what persists. */
.engine{
  display:inline-flex;align-items:center;height:17px;padding:0 7px;flex:none;
  border:1px solid var(--line);border-radius:999px;background:var(--surface);
  color:var(--faint);font:600 9px/1 var(--mono);letter-spacing:.04em;cursor:help;
}


/* ══ 25. REPOSITORY & CODE ═════════════════════════════════════════════════ */

.branch{
  display:inline-flex;align-items:center;gap:5px;height:19px;padding:0 8px;flex:none;
  border:1px solid var(--line);border-radius:999px;background:var(--surface-2);
  color:var(--ink-2);font:600 10px/1 var(--mono);
}
.branch svg{width:11px;height:11px;color:var(--muted)}

.commit{display:flex;align-items:center;gap:11px;padding:10px 0;border-bottom:1px solid var(--line-soft)}
.commit:last-child{border-bottom:0}
.commit .msg{flex:1;min-width:0}
.commit .msg b{display:block;font-size:12.5px;font-weight:580;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.commit .msg span{color:var(--faint);font:600 9.5px/1.5 var(--mono)}
.commit .sha{color:var(--muted);font:650 10.5px/1 var(--mono)}

/* An issue or pull request row. State is a filled/hollow glyph plus a word. */
.thread-row{display:flex;align-items:flex-start;gap:10px;padding:11px 0;border-bottom:1px solid var(--line-soft)}
.thread-row:last-child{border-bottom:0}
.thread-row>svg{width:15px;height:15px;flex:none;margin-top:1px;color:var(--ink)}
.thread-row.is-closed>svg{color:var(--faint)}
.thread-row .ti{flex:1;min-width:0}
.thread-row .ti b{font-size:13px;font-weight:600;letter-spacing:-.012em}
.thread-row .ti .meta{margin-top:3px;color:var(--faint);font:600 9.5px/1.4 var(--mono)}
.thread-row .num{color:var(--faint);font:650 11px/1.4 var(--mono);font-variant-numeric:tabular-nums}

/* Diffstat — the bar is counts, so added and removed are fill vs texture. */
.diffstat{display:inline-flex;align-items:center;gap:7px;font:650 10.5px/1 var(--mono);font-variant-numeric:tabular-nums}
.diffstat .bar{display:flex;gap:1.5px}
.diffstat .bar i{width:7px;height:7px;border-radius:1.5px;background:var(--line-strong)}
.diffstat .bar i.add{background:var(--seq-6)}
.diffstat .bar i.del{background:repeating-linear-gradient(135deg,var(--seq-4) 0 1.5px,var(--surface) 1.5px 3px)}

/* A CI check. */
.check-run{display:flex;align-items:center;gap:9px;padding:8px 11px;border:1px solid var(--line);border-radius:9px;background:var(--surface)}
.check-run>svg{width:14px;height:14px;flex:none}
.check-run .nm{flex:1;min-width:0;font-size:12px;font-weight:560}
.check-run .ms{color:var(--faint);font:650 10px/1 var(--mono);font-variant-numeric:tabular-nums}
.check-run.is-pass>svg{color:var(--ink)}
.check-run.is-fail{border-color:var(--ink);border-width:1.5px}
.check-run.is-skip{border-style:dashed}
.check-run.is-skip .nm{color:var(--faint)}

/* Tabbed code — same chrome as .code, one source per tab. */
.codetabs{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-2);overflow:hidden}
.codetabs .tabs{background:var(--surface);border-bottom:1px solid var(--line)}
.codetabs .tabs button{padding:8px 12px;font:600 11px/1 var(--mono)}

/* A terminal transcript. The prompt glyph is not selectable, so copying the
   block copies commands you can actually run. */
.term{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-2);padding:11px 13px;
  font:500 11.5px/1.75 var(--mono);color:var(--ink-2);overflow-x:auto}
.term .in{display:block;color:var(--ink);font-weight:620}
.term .in::before{content:"$ ";color:var(--faint);user-select:none}
.term .out{display:block;color:var(--muted)}
.term .err{display:block;color:var(--ink);font-weight:600;border:0;padding:0;background:none}

/* An API endpoint. The method is mono and boxed because it is a token, not prose. */
.endpoint{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--line);border-radius:9px;background:var(--surface)}
.endpoint .verb{
  flex:none;display:grid;place-items:center;min-width:46px;height:20px;padding:0 7px;
  border:1px solid var(--line-strong);border-radius:5px;
  font:700 9.5px/1 var(--mono);letter-spacing:.06em;color:var(--ink);
}
.endpoint .verb.is-write{background:var(--solid);color:var(--on-solid);border-color:var(--solid)}
.endpoint .path{flex:1;min-width:0;font:600 11.5px/1 var(--mono);color:var(--ink-2);overflow-wrap:anywhere}

/* Schema rows. */
.schema{width:100%;font-size:12px}
.schema td{padding:7px 10px;vertical-align:top}
.schema .f{font:650 11px/1.5 var(--mono);color:var(--ink);white-space:nowrap}
.schema .ty{font:600 10.5px/1.5 var(--mono);color:var(--muted);white-space:nowrap}
.schema .req{color:var(--ink);font:700 8.5px/1.5 var(--mono);letter-spacing:.06em}
.schema .d{color:var(--ink-2);line-height:1.5}


/* ══ 26. DATA ENTRY II & TABLES II ═════════════════════════════════════════ */

/* Combobox. The listbox is a real listbox: arrow keys move aria-activedescendant,
   so the highlight below must never be the only cue. */
.combo{position:relative}
.combo-list{
  position:absolute;z-index:40;left:0;right:0;top:calc(100% + 5px);
  max-height:216px;overflow-y:auto;padding:5px;
  border:1px solid var(--line-strong);border-radius:var(--radius-sm);background:var(--surface);
  box-shadow:var(--shadow-float);
}
.combo-opt{
  display:flex;align-items:center;gap:8px;width:100%;padding:6px 9px;border-radius:7px;
  text-align:left;font-size:12.5px;color:var(--ink-2);
}
.combo-opt:hover,.combo-opt[aria-selected="true"]{background:var(--hover)}
.combo-opt[aria-selected="true"]{font-weight:620;color:var(--ink)}
.combo-opt[aria-selected="true"]::after{content:"";margin-left:auto;width:6px;height:11px;
  border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(45deg)}
.combo-opt mark{background:var(--selected);color:inherit;border-radius:2px}

/* Number stepper. */
.stepper-n{display:inline-flex;align-items:stretch;border:1px solid var(--line);border-radius:9px;background:var(--surface);overflow:hidden}
.stepper-n button{width:28px;color:var(--muted);font-size:14px;line-height:1}
.stepper-n button:hover{background:var(--hover);color:var(--ink)}
.stepper-n input{width:46px;border:0;border-left:1px solid var(--line);border-right:1px solid var(--line);
  background:transparent;text-align:center;font:650 12px/1 var(--mono);font-variant-numeric:tabular-nums;outline:0}

/* One-time code. Each box is one character and they are one input to AT. */
.otp{display:flex;gap:7px}
.otp input{
  width:38px;height:44px;text-align:center;
  border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);
  font:650 17px/1 var(--mono);color:var(--ink);outline:0;
}
.otp input:focus{border-color:var(--ink);box-shadow:var(--ring)}

/* Reveal. The control says which state it will move you TO. */
.reveal{position:relative;display:flex;align-items:center}
.reveal .field{padding-right:38px}
.reveal button{position:absolute;right:5px;display:grid;place-items:center;width:26px;height:26px;border-radius:7px;color:var(--muted)}
.reveal button:hover{background:var(--hover);color:var(--ink)}
.reveal button svg{width:14px;height:14px}

/* Edit in place. */
.inline-edit{display:inline-flex;align-items:center;gap:6px;padding:2px 6px;margin:-2px -6px;border-radius:6px;cursor:text}
.inline-edit:hover{background:var(--hover);box-shadow:inset 0 0 0 1px var(--line)}
.inline-edit svg{width:12px;height:12px;color:var(--faint);opacity:0;transition:opacity .14s ease}
.inline-edit:hover svg{opacity:1}

/* Upload with real progress. "Uploading" is a claim; the bar must come from the
   request, never from a timer pretending. */
.upload{display:flex;align-items:center;gap:10px;padding:9px 11px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}
.upload .fn{flex:1;min-width:0}
.upload .fn b{display:block;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.upload .progress{margin-top:5px}
.upload .pct{color:var(--muted);font:650 10.5px/1 var(--mono);font-variant-numeric:tabular-nums}

/* Bulk-selection toolbar. It states the count and it states the scope — "all"
   on a paginated table has bitten everyone at least once. */
.bulkbar{
  display:flex;align-items:center;gap:11px;padding:8px 12px;
  border:1px solid var(--ink);border-radius:var(--radius-xs);background:var(--surface-2);
  font-size:12px;color:var(--ink-2);
}
.bulkbar b{color:var(--ink);font-variant-numeric:tabular-nums}
.bulkbar .scope{color:var(--faint);font:600 10px/1 var(--mono)}

/* Expandable row + density. */
tr.is-expanded>td{border-bottom:0}
tr.detail>td{padding-top:0;background:var(--surface-2)}
.table-tight td,.table-tight th{padding:5px 10px}
.table-loose td,.table-loose th{padding:13px 14px}
.table-pin td:first-child,.table-pin th:first-child{position:sticky;left:0;z-index:2;background:var(--surface)}
.table-pin th:first-child{background:var(--surface-2)}
/* The pin's opaque background is what stops rows scrolling under it — and it
   also painted over the row's own state, so a selected row read as highlighted
   everywhere except its first cell. State wins; the pin follows it. */
.table-pin tr.is-selected>td:first-child{background:var(--selected)}
.table-pin tbody tr:hover>td:first-child{background:var(--hover)}
.table-pin tr.detail>td:first-child{background:var(--surface-2)}
.rowmenu{opacity:0;transition:opacity .14s ease}
tr:hover .rowmenu,tr:focus-within .rowmenu{opacity:1}

.tablefoot{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:9px 12px;border-top:1px solid var(--line);
  color:var(--muted);font-size:11.5px;
}
.tablefoot .n{font-variant-numeric:tabular-nums}


/* ══ 27. ACCESSIBILITY & PRINT ═════════════════════════════════════════════ */

/* Skip link. Hidden until focused, then it is a real, visible, first-in-order
   control — not an off-screen link that never comes back. */
.skip{
  position:absolute;z-index:100;left:8px;top:-60px;
  padding:9px 14px;border-radius:var(--radius-xs);
  background:var(--solid);color:var(--on-solid);font-size:12.5px;font-weight:620;
  transition:top .14s ease;
}
.skip:focus{top:8px}

/* A polite live region. Visually it is just text; the attribute does the work,
   so never style this as an alert the user has to dismiss. */
.live{color:var(--muted);font-size:11.5px}
.live:empty{display:none}

/* Target size. Anything interactive smaller than 24px gets its hit area back
   without changing how it looks — WCAG 2.2 target size, which hairline UIs fail
   constantly. */
.tap{position:relative}
.tap::after{content:"";position:absolute;left:50%;top:50%;width:24px;height:24px;transform:translate(-50%,-50%)}

@media print{
  /* Chrome is not content and does not print. */
  .menubar,.glassbar,.topbar,.tabstrip,.chromebar,.dock,.composer,.lights,
  .skip,.toast-stack,.scrim,.modal,.drawer,.popover,.chart-filters,.rowmenu{display:none!important}
  html{background:#fff}
  .wrap{max-width:none;padding:0}
  .panel,.card,.void,.cwrap,.chart,.receipt,.stat{box-shadow:none;break-inside:avoid}
  .section{break-inside:avoid-page}
  h1,h2,h3,.sec-head{break-after:avoid}
  /* A printed link whose destination is invisible is a dead end on paper. */
  a[href^="http"]::after{content:" (" attr(href) ")";font:500 9px/1 var(--mono);color:var(--muted)}
  .page-break{break-before:page}
}


/* ══ 28. APP SHELL ═════════════════════════════════════════════════════════
   The console layout: a fixed sidebar beside a scrolling workspace. Modelled
   on BlackRoadOS-Home-Prototype/blackroados-home, rebuilt on these tokens so
   it themes, prints and passes the same checks as everything else. */

.shell{display:grid;grid-template-columns:var(--side,236px) minmax(0,1fr);height:100vh}
.shell-side{
  display:flex;flex-direction:column;min-height:0;
  border-right:1px solid var(--line);background:var(--surface);
}
.shell-main{display:flex;flex-direction:column;min-height:0;min-width:0;background:var(--paper-soft)}
/* The one scrolling region. Everything else in the shell is fixed, so the
   topbar stays put and only the workspace moves. */
.shell-body{flex:1;min-height:0;overflow-y:auto;padding:22px 24px 40px}

.brand{display:flex;align-items:center;gap:10px;padding:16px 14px 12px;flex:none}
.brand .nm{font-size:13.5px;font-weight:700;letter-spacing:-.03em}

/* The operator card sits at the bottom of the rail, not in the topbar: it is
   who you ARE, not what you are doing. */
.side-foot{margin-top:auto;padding:10px;border-top:1px solid var(--line);flex:none}
.profile{
  display:flex;align-items:center;gap:9px;width:100%;padding:8px 9px;
  border-radius:10px;text-align:left;transition:background .16s ease;
}
.profile:hover{background:var(--hover)}
.profile .who{min-width:0;flex:1}
.profile .who b{display:block;font-size:12px;font-weight:650;letter-spacing:-.015em}
.profile .who span{display:block;color:var(--faint);font:600 9.5px/1.4 var(--mono)}
.profile>svg:last-child{width:13px;height:13px;color:var(--faint);flex:none}

/* Page head: kicker, name, and the one sentence that states a measured fact.
   Added because the console template was building it from three inline margins
   — the same signal as .masthead. One h1 per page and it is the page's name. */
.pagehead{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}
.pagehead .who{flex:1;min-width:0}
.pagehead h1{margin-top:7px;font-size:27px;font-weight:720;letter-spacing:-.04em;line-height:1.12}
.pagehead .lede{margin-top:8px;max-width:620px;color:var(--muted);font-size:12.5px;line-height:1.6}
.pagehead .lede em{font-style:normal;color:var(--ink);font-weight:600}

/* One stride for the workspace, so a page is a stack of blocks rather than a
   pile of hand-set margins. */
.shell-body>*+*{margin-top:14px}

/* Workspace header. Search is the widest thing in it because search is the
   thing people actually came for. */
.workbar{
  display:flex;align-items:center;gap:12px;height:var(--topbar);padding:0 18px;flex:none;
  border-bottom:1px solid var(--line);background:var(--surface);
}
.workbar .search{flex:1;max-width:460px}
.sysstate{display:flex;align-items:center;gap:8px;font-size:11.5px}
.sysstate .who{line-height:1.25}
.sysstate .who small{display:block;color:var(--faint);font:600 9px/1.3 var(--mono);letter-spacing:.06em;text-transform:uppercase}
.sysstate .who strong{display:block;font-size:11.5px;font-weight:650}

/* Panel with its own header row. */
.panel-head{
  display:flex;align-items:center;gap:10px;padding:12px 14px;flex:none;
  border-bottom:1px solid var(--line);
}
.panel-head b{font-size:12.5px;font-weight:680;letter-spacing:-.02em}
.panel-head .sub{color:var(--faint);font:600 9.5px/1 var(--mono)}
.panel-body{padding:13px 14px}
.panel-body.flush{padding:0}

/* The two workspace grids. Both collapse to one column at the 900px floor. */
.grid-main{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);gap:14px;align-items:start}
.grid-thirds{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;align-items:start}
@media (max-width:900px){
  body:not(.mobile-off) .grid-main,
  body:not(.mobile-off) .grid-thirds{grid-template-columns:minmax(0,1fr)}
  /* The rail becomes a strip of icons rather than vanishing: a nav that
     disappears at a breakpoint is a nav you cannot reach. */
  body:not(.mobile-off) .shell{--side:60px}
  body:not(.mobile-off) .shell-side .navitem span:not(.count),
  body:not(.mobile-off) .brand .nm,
  body:not(.mobile-off) .profile .who,
  body:not(.mobile-off) .profile>svg:last-child{display:none}
  body:not(.mobile-off) .shell-side .navitem{justify-content:center}
  body:not(.mobile-off) .shell-body{padding:16px 14px 32px}
  /* A toolbar that cannot fit must WRAP, not push the page sideways. The
     workbar was a fixed-height no-wrap row, so on a narrow screen its contents
     widened the document and the whole page scrolled horizontally — which hides
     the right-hand controls entirely rather than merely stacking them. */
  body:not(.mobile-off) .workbar{height:auto;flex-wrap:wrap;row-gap:8px;padding:10px 14px}
  body:not(.mobile-off) .workbar .search{max-width:none;flex:1 1 100%;order:1}
}


/* ══ 29. SHEET — the hierarchical grid ═════════════════════════════════════
   A work-management sheet: numbered rows, typed columns, parent/child rollup,
   and a frozen first column. The hard parts in monochrome are the ones every
   tool in this category solves with colour:

     STATUS       not red/amber/green. A five-step HARVEY BALL (empty → full)
                  is ordered, countable, colourblind-safe and prints. The word
                  stays beside it; the ball alone is never the only channel.
     AT RISK      weight, not hue: a 1.5px ink left-edge on the row.
     HIERARCHY    indent + a guide line. Parent rows gain WEIGHT because their
                  values are rollups, not because they outrank their children.
     SELECTION    fill, and it must survive the frozen column (see .table-pin).

   Row height is deliberately tight: a sheet is read down a column, so vertical
   rhythm matters more than padding. */

.sheet-wrap{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);overflow:auto}
.sheet{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
.sheet th,.sheet td{
  padding:0;height:30px;border-bottom:1px solid var(--line-soft);border-right:1px solid var(--line-soft);
  text-align:left;vertical-align:middle;background:var(--surface);
}
.sheet thead th{
  position:sticky;top:0;z-index:3;height:28px;
  background:var(--surface-2);border-bottom:1px solid var(--line);
  color:var(--faint);font:700 9px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
  white-space:nowrap;
}
.sheet th .ch{display:flex;align-items:center;gap:6px;padding:0 9px}
.sheet th .ch svg{width:11px;height:11px;color:var(--faint);flex:none}
.sheet th .ch button{margin-left:auto;opacity:0;color:var(--muted)}
.sheet th:hover .ch button{opacity:1}

/* The row-number gutter. Frozen, like the first data column. */
.sheet .rn{
  position:sticky;left:0;z-index:2;width:38px;min-width:38px;
  background:var(--surface-2);border-right:1px solid var(--line);
  color:var(--faint);font:650 10px/1 var(--mono);font-variant-numeric:tabular-nums;text-align:center;
}
.sheet thead .rn{z-index:4}
.sheet .pin{position:sticky;left:38px;z-index:2;background:var(--surface);border-right:1px solid var(--line)}
.sheet thead .pin{z-index:4;background:var(--surface-2)}

.sheet tbody tr:hover>td{background:var(--hover)}
.sheet tbody tr:hover>.rn,.sheet tbody tr:hover>.pin{background:var(--hover)}
.sheet tr.is-selected>td,.sheet tr.is-selected>.rn,.sheet tr.is-selected>.pin{background:var(--selected)}
/* At risk is WEIGHT, never hue. The flag in the cell says it in words. */
.sheet tr.is-flagged>.rn{box-shadow:inset 2px 0 0 var(--ink)}

/* A cell. Text truncates; numbers and dates are tabular so a column compares. */
.cell{display:flex;align-items:center;gap:7px;height:100%;padding:0 9px;min-width:0}
.cell.num,.cell.date{font:600 11px/1 var(--mono);font-variant-numeric:tabular-nums}
.cell.num{justify-content:flex-end}
.cell .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.cell.is-empty{color:var(--faint)}
.cell.is-formula{color:var(--muted);font-style:italic}
/* An edited-but-unsaved cell is dashed — asserted, not yet committed. */
.cell.is-dirty{box-shadow:inset 0 0 0 1px var(--line-strong);border-radius:4px}
td.is-editing{box-shadow:inset 0 0 0 2px var(--ink)}

/* Hierarchy. Only the indent and the twisty move with depth; size, weight and
   colour are identical at every level — indentation records containment, never
   rank. Parent rows are bold because their numbers are ROLLUPS. */
.cell.tree{padding-left:calc(9px + var(--d,0) * 16px)}
.cell .twisty{
  width:0;height:0;flex:none;border:3.5px solid transparent;border-left-color:var(--muted);
  transition:transform .16s ease;cursor:pointer;
}
.cell .twisty.is-open{transform:rotate(90deg) translateX(-1px)}
.cell .spacer{width:7px;flex:none}
tr.is-parent .cell.tree .t{font-weight:680}
tr.is-parent .cell.num{font-weight:700}

/* Row affordances — attachment, comment, lock. Present only when true. */
.rowmarks{display:flex;align-items:center;gap:5px;margin-left:auto;flex:none}
.rowmarks svg{width:11px;height:11px;color:var(--faint)}
.rowmarks .n{color:var(--faint);font:650 9px/1 var(--mono)}

/* Harvey ball — the status symbol. Five ordered steps, no hue, prints, and
   survives full-severity CVD. data-fill is 0…4. */
.ball{
  display:inline-block;width:12px;height:12px;flex:none;border-radius:50%;
  border:1.25px solid var(--ink);
  /* A SOLID gradient sized by percentage. Written as a 50/50 ink-to-transparent
     gradient first, which made every ball render half-full no matter the step:
     background-size was scaling an image that was already half empty. The fill
     fraction belongs to the size, so the image has to be one flat colour. */
  background:linear-gradient(var(--ink),var(--ink)) no-repeat;
  background-size:0% 100%;
}
.ball[data-fill="1"]{background-size:25% 100%}
.ball[data-fill="2"]{background-size:50% 100%}
.ball[data-fill="3"]{background-size:75% 100%}
.ball[data-fill="4"]{background-size:100% 100%}
/* A ball never travels alone: the word is the primary channel. */
.status-cell{display:flex;align-items:center;gap:7px}
.status-cell .w{color:var(--ink-2);font-size:11.5px}

/* Priority — a filled/hollow chevron stack rather than a colour. */
.prio{display:inline-flex;gap:1px;flex:none}
.prio i{width:0;height:0;border:3px solid transparent;border-bottom-color:var(--line-strong)}
.prio[data-level="1"] i:nth-child(-n+1),
.prio[data-level="2"] i:nth-child(-n+2),
.prio[data-level="3"] i:nth-child(-n+3){border-bottom-color:var(--ink)}

/* Progress inside a cell. */
.cellbar{flex:1;min-width:34px;height:5px;border-radius:999px;background:var(--seq-1)}
.cellbar>i{display:block;height:100%;border-radius:999px;background:var(--seq-5)}

/* Sheet toolbar + summary strip. */
.sheet-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 11px;border-bottom:1px solid var(--line);background:var(--surface)}
.sheet-sum{display:flex;gap:22px;flex-wrap:wrap;padding:11px 13px;border-top:1px solid var(--line);background:var(--surface-2)}
.sheet-sum .f{min-width:0}
.sheet-sum .f small{display:block;color:var(--faint);font:600 9px/1.4 var(--mono);letter-spacing:.08em;text-transform:uppercase}
.sheet-sum .f b{display:block;margin-top:2px;font-size:15px;font-weight:700;letter-spacing:-.025em}


/* ══ 30. TIMELINE — the Gantt half ═════════════════════════════════════════
   Rows line up with the sheet's 30px rhythm so the two halves scroll together.
   Encoding, all of it non-colour:
     planned      hairline outline          baseline, what was promised
     actual       solid fill                what is happening
     complete     a darker inner fill       portion done, inside the actual bar
     milestone    a diamond, zero width     a date, not a duration
     critical     1.5px ink outline         the path that moves the end date
     today        a solid 1px ink rule      a fact, so never dashed */

.gantt{position:relative;min-width:0;overflow-x:auto;background:var(--surface)}
.gantt-head{
  position:sticky;top:0;z-index:3;display:flex;height:28px;
  background:var(--surface-2);border-bottom:1px solid var(--line);
}
.gantt-head .u{
  flex:1;min-width:0;display:grid;place-items:center;
  border-right:1px solid var(--line-soft);
  color:var(--faint);font:700 9px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;
}
.gantt-grid{position:absolute;inset:28px 0 0 0;display:flex;pointer-events:none}
.gantt-grid i{flex:1;border-right:1px solid var(--c-grid)}
.gantt-row{position:relative;height:30px;border-bottom:1px solid var(--line-soft)}

.gbar{
  position:absolute;top:8px;height:14px;left:var(--s);right:var(--e);
  border-radius:4px;background:var(--seq-5);
}
.gbar>i{display:block;height:100%;border-radius:4px 0 0 4px;background:var(--seq-6)}
.gbar .lab{position:absolute;left:calc(100% + 7px);top:0;line-height:14px;white-space:nowrap;
  color:var(--muted);font:650 10px/14px var(--mono);font-variant-numeric:tabular-nums}
.gbar.is-planned{background:transparent;border:1px solid var(--line-strong);height:10px;top:10px}
.gbar.is-critical{box-shadow:0 0 0 1.5px var(--ink)}
.gbar.is-summary{height:7px;top:11px;border-radius:2px;background:var(--seq-3);
  clip-path:polygon(0 0,100% 0,100% 100%,calc(100% - 5px) 40%,5px 40%,0 100%)}
.gbar.is-milestone{
  width:12px;height:12px;right:auto;top:9px;border-radius:2px;
  background:var(--ink);transform:rotate(45deg);
}

/* Dependency connector: finish-to-start, drawn with borders so it needs no SVG. */
.gdep{position:absolute;pointer-events:none;border-left:1px solid var(--line-strong);border-bottom:1px solid var(--line-strong)}
.gdep::after{content:"";position:absolute;right:-1px;bottom:-3.5px;width:0;height:0;
  border:3.5px solid transparent;border-left-color:var(--line-strong)}

.today{position:absolute;top:0;bottom:0;width:1px;background:var(--ink);z-index:2;pointer-events:none}
.today::before{
  content:attr(data-label);position:absolute;top:6px;left:5px;
  padding:1px 5px;border-radius:4px;background:var(--solid);color:var(--on-solid);
  font:700 8.5px/1.5 var(--mono);letter-spacing:.06em;white-space:nowrap;
}

/* Grid + timeline, scrolling as one. */
.split-sheet{display:grid;grid-template-columns:var(--sheet,minmax(0,1.1fr)) minmax(0,1fr);
  border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;background:var(--surface)}
.split-sheet>.sheet-wrap{border:0;border-right:1px solid var(--line);border-radius:0}


/* ══ 31. BOARD & VIEWS ═════════════════════════════════════════════════════ */

.viewtabs{display:flex;align-items:center;gap:2px}
.viewtabs button{display:flex;align-items:center;gap:6px;padding:6px 11px;border-radius:8px;
  color:var(--muted);font-size:12px;font-weight:560;transition:background .16s ease,color .16s ease}
.viewtabs button:hover{background:var(--hover)}
.viewtabs button[aria-selected="true"]{background:var(--solid);color:var(--on-solid);font-weight:620}
.viewtabs svg{width:13px;height:13px}

.board{display:flex;gap:12px;overflow-x:auto;align-items:flex-start;padding-bottom:4px}
.board::-webkit-scrollbar{display:none}
.lane{flex:0 0 252px;display:flex;flex-direction:column;min-height:0;
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface-2)}
.lane-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line)}
.lane-head b{font-size:12px;font-weight:680;letter-spacing:-.015em}
.lane-head .n{margin-left:auto;color:var(--faint);font:650 10px/1 var(--mono);font-variant-numeric:tabular-nums}
.lane-body{display:flex;flex-direction:column;gap:8px;padding:10px}
.kcard{padding:10px 11px;border:1px solid var(--line);border-radius:10px;background:var(--surface);cursor:grab}
.kcard:hover{border-color:var(--line-strong)}
.kcard b{display:block;font-size:12.5px;font-weight:600;letter-spacing:-.012em;line-height:1.35}
.kcard .meta{display:flex;align-items:center;gap:8px;margin-top:8px;color:var(--faint);font:600 9.5px/1 var(--mono)}
.kcard .meta .push{margin-left:auto}


/* ══ 32. SINGLE-CARD PAGE ══════════════════════════════════════════════════
   Sign-in, approval, a one-decision screen. Centred, narrow, and nothing else
   on the page — if a screen asks for one decision, it should contain one. */

.authpage{display:grid;place-items:center;min-height:100vh;padding:24px;background:var(--paper-soft)}
.authcard{
  width:min(400px,100%);display:flex;flex-direction:column;gap:14px;
  padding:26px 24px;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--surface);box-shadow:var(--shadow);
}
.authcard .head{display:flex;flex-direction:column;align-items:center;gap:11px;text-align:center}
.authcard h1{font-size:19px;font-weight:700;letter-spacing:-.03em}
.authcard .sub{color:var(--muted);font-size:12.5px;line-height:1.55}
.authfoot{margin-top:4px;text-align:center;color:var(--faint);font-size:11px;line-height:1.5}

/* The quorum row: 2-of-N device approval. Each slot is a filled or hollow dot
   plus the device's name — a count you can read without counting. */
.quorum{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.quorum .slot{
  display:inline-flex;align-items:center;gap:6px;height:25px;padding:0 10px;
  border:1px solid var(--line-strong);border-radius:999px;background:var(--surface);
  color:var(--muted);font:600 10.5px/1 var(--mono);
}
.quorum .slot::before{content:"";width:8px;height:8px;border-radius:50%;border:1.5px solid var(--ink);background:transparent}
.quorum .slot.is-signed{border-color:var(--ink);color:var(--ink)}
.quorum .slot.is-signed::before{background:var(--ink)}
.quorum .n{margin-left:auto;color:var(--ink);font:700 11px/1 var(--mono)}
