/* ═══════════════════════════════════════════════════════════════════════
   Explorer — components

   The second of the two shared stylesheets. tokens.css says what the values
   ARE; this says what the controls are made of. A screen should link both and
   then write almost no CSS of its own.

       <link rel="stylesheet" href="tokens.css">
       <link rel="stylesheet" href="components.css">

   PART ONE below was lifted verbatim out of mockup.html on 10 Sep 2026, so
   the review screen and the gallery are the same code rather than two copies
   that agree today. mockup.html still carries its own inline copy until the
   direction is approved — changing the page while it is being looked at is
   how a reviewer ends up judging something that no longer exists. The swap is
   recorded in WORKQUEUE.md and is a five-line change once that word comes.

   PART TWO is new: the controls a review screen did not happen to need, which
   the other twelve screens do.

   THE RULE FOR BOTH: no colour, size, weight or duration as a literal. If a
   value here is not a layout constant, it belongs in tokens.css. That is what
   lets a light theme be a token block rather than a rewrite.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   PART ONE — verbatim from mockup.html. Do not edit here and there.
   ═══════════════════════════════════════════════════════════════════════ */
/* Every value here comes from tokens.css. A literal below that is not a
   layout constant is a bug. */
*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{margin:0; background:var(--bg); color:var(--text);
  font-family:var(--ui); font-size:var(--t-sm); line-height:1.45;
  font-feature-settings:"ss03" 1; font-variant-numeric:tabular-nums;
  -webkit-font-smoothing:antialiased}
:focus-visible{outline:2px solid var(--ring); outline-offset:1px; border-radius:2px}
.mono{font-family:var(--mono)}
.num{text-align:right; font-family:var(--mono); white-space:nowrap}
.muted{color:var(--text-muted)}
h1,h2,h3,h4{margin:0; font-weight:var(--w-semi); letter-spacing:-.01em}
p{margin:0; text-wrap:pretty}
.spacer{margin-left:auto}

/* ───────── shell ───────── */
.app{display:grid; grid-template-columns:var(--nav-w) 1fr; height:100vh; overflow:hidden}
nav.side{background:var(--surface); border-right:1px solid var(--line);
  display:flex; flex-direction:column; overflow:hidden}
.brand{display:flex; align-items:center; gap:var(--s3); padding:var(--s4) var(--s4) var(--s3)}
.brand .mark{width:26px;height:26px;flex:0 0 auto;border-radius:var(--r-ctl);
  background:linear-gradient(145deg,var(--a9),var(--a6));display:grid;place-items:center;
  font-weight:var(--w-semi);font-size:var(--t-dense);color:var(--n1)}
.brand b{display:block;font-size:var(--t-dense);font-weight:var(--w-semi);white-space:nowrap}
.brand small{display:block;font-size:var(--t-micro);color:var(--text-muted)}
.jump{margin:0 var(--s4) var(--s4); padding:var(--s2) var(--s3);
  display:flex; align-items:center; gap:var(--s2); width:calc(100% - var(--s4)*2);
  background:var(--surface-alt); border:1px solid var(--line-control); border-radius:var(--r-ctl);
  color:var(--text-muted); font:inherit; font-size:var(--t-dense); cursor:pointer; text-align:left}
.jump:hover{background:var(--hover); color:var(--text-2)}
.jump kbd{margin-left:auto}
kbd{font-family:var(--ui); font-size:var(--t-micro); font-weight:var(--w-medium);
  color:var(--text-muted); background:var(--bg); border:1px solid var(--line);
  border-bottom-width:2px; border-radius:3px; padding:1px 5px; line-height:1.5}
.navscroll{flex:1; overflow-y:auto; padding-bottom:var(--s4)}
.navsec{padding:var(--s1) var(--s5) var(--s1) var(--s4); margin-top:var(--s4);
  font-size:var(--t-micro); font-weight:var(--w-semi); letter-spacing:.04em;
  color:var(--text-muted); text-transform:uppercase}
a.item{position:relative; display:flex; align-items:center; gap:var(--s3);
  margin:1px var(--s3); padding:0 var(--s3); height:30px; border-radius:var(--r-ctl);
  color:var(--text-2); text-decoration:none; font-size:var(--t-dense); white-space:nowrap}
a.item:hover{background:var(--surface-alt); color:var(--text)}
a.item[aria-current="page"]{background:var(--a3); color:var(--text); font-weight:var(--w-medium)}
a.item[aria-current="page"]::before{content:""; position:absolute; left:-6px; top:7px; bottom:7px;
  width:2px; border-radius:2px; background:var(--a9)}
a.item .ico{width:16px; flex:0 0 auto; opacity:.75; display:grid; place-items:center}
a.item[aria-current="page"] .ico{opacity:1; color:var(--a11)}
/* A count means "things waiting for you", and renders only when non-zero.
   Catalogue size is not a count and gets muted text instead. */
.count{margin-left:auto; font-size:var(--t-micro); font-weight:var(--w-semi);
  font-family:var(--mono); padding:1px 6px; border-radius:var(--r-pill);
  background:var(--n4); color:var(--text-2)}
.count[data-tone="attention"]{background:var(--at3); color:var(--at11)}
.sizehint{margin-left:auto; font-size:var(--t-micro); color:var(--text-muted); font-family:var(--mono)}
.side footer{border-top:1px solid var(--line); padding:var(--s3) var(--s4)}
.conn{display:flex; align-items:center; gap:var(--s2); width:100%; background:none; border:0;
  color:var(--text-2); font:inherit; font-size:var(--t-xs); cursor:pointer;
  padding:var(--s1) var(--s2); border-radius:var(--r-ctl)}
.conn:hover{background:var(--surface-alt)}
.dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto;background:var(--ok9)}
.fresh{margin-left:auto; color:var(--text-muted); font-size:var(--t-micro)}

main{display:flex; flex-direction:column; min-width:0; overflow:hidden}
.pagehead{display:flex; align-items:center; gap:var(--s4); padding:0 var(--gutter);
  height:var(--head-h); flex:0 0 auto; border-bottom:1px solid var(--line); background:var(--bg)}
.pagehead h1{font-size:var(--t-xl); line-height:1.2; white-space:nowrap}
.pagehead .sub{color:var(--text-muted); font-size:var(--t-dense); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.content{flex:1; overflow-y:auto; padding:var(--gutter)}
.content > * + *{margin-top:var(--s6)}

/* ───────── controls ───────── */
.btn{display:inline-flex; align-items:center; gap:var(--s2); height:var(--h-ctl);
  padding:0 var(--s4); background:var(--surface-alt); color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-ctl); font:inherit;
  font-size:var(--t-dense); font-weight:var(--w-medium); cursor:pointer; white-space:nowrap}
.btn:hover{background:var(--hover); border-color:var(--line-strong)}
.btn:active{background:var(--pressed)}
.btn.primary{background:var(--a9); border-color:var(--a9); color:var(--a-ink); font-weight:var(--w-semi)}
.btn.danger{background:var(--dg9); border-color:var(--dg9); color:var(--dg-ink); font-weight:var(--w-semi)}
.btn.quiet{background:none; border-color:transparent; color:var(--text-2)}
.btn.quiet:hover{background:var(--surface-alt); color:var(--text)}
.btn.sm{height:var(--h-ctl-sm); padding:0 var(--s3); font-size:var(--t-xs)}
.btn[disabled]{opacity:.45; cursor:not-allowed}
.btn[aria-pressed="true"]{background:var(--a3); border-color:var(--a6); color:var(--text)}
.sel > button{display:flex; align-items:center; gap:var(--s2); height:var(--h-ctl);
  padding:0 var(--s2) 0 var(--s3); min-width:150px; background:var(--surface-alt); color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-ctl); font:inherit;
  font-size:var(--t-dense); cursor:pointer; text-align:left}
.sel > button:hover{border-color:var(--line-strong); background:var(--hover)}
.sel > button .val{flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.sel > button .chev{opacity:.6; font-size:10px}

[popover]{margin:0; padding:0; border:0; background:none; color:inherit; inset:auto; overflow:visible}
.menu{min-width:210px; max-height:min(340px,60vh); overflow-y:auto; background:var(--surface-alt);
  border:1px solid var(--line-control); border-radius:var(--r-over); box-shadow:var(--lift);
  padding:var(--s1);
  /* popovertarget establishes an implicit, per-pair anchor. Giving them all
     one anchor-name would make every menu anchor to whichever came last. */
  position-area:block-end span-inline-end; margin-block-start:var(--s1);
  position-try-fallbacks:flip-block, flip-inline;
  opacity:0; transform:translateY(-4px);
  transition:opacity var(--fast) var(--ease), transform var(--fast) var(--ease),
             display var(--fast) allow-discrete, overlay var(--fast) allow-discrete}
.menu:popover-open{opacity:1; transform:none}
@starting-style{ .menu:popover-open{opacity:0; transform:translateY(-4px)} }
.menu .opt{display:flex; align-items:center; gap:var(--s3); width:100%;
  padding:var(--s2) var(--s3); border-radius:var(--r-ctl); background:none; border:0;
  color:var(--text-2); font:inherit; font-size:var(--t-dense); cursor:pointer; text-align:left}
.menu .opt:hover{background:var(--hover); color:var(--text)}
.menu .opt[aria-selected="true"]{color:var(--text); font-weight:var(--w-medium)}
.menu .opt .tick{width:14px; opacity:0; color:var(--a11); font-size:11px}
.menu .opt[aria-selected="true"] .tick{opacity:1}
.menu .head{padding:var(--s2) var(--s3) var(--s1); font-size:var(--t-micro); font-weight:var(--w-semi);
  letter-spacing:.04em; text-transform:uppercase; color:var(--text-muted)}
.menu .sep{height:1px; background:var(--line); margin:var(--s1) 0}

/* ───────── surfaces ───────── */
.card{background:var(--surface); border:1px solid var(--line); border-radius:var(--r-card)}
.card > header{display:flex; align-items:baseline; gap:var(--s3);
  padding:var(--s4) var(--s5); border-bottom:1px solid var(--line)}
.card > header h3{font-size:var(--t-sm)}
.card > header .sub{font-size:var(--t-xs); color:var(--text-muted)}
.card > .body{padding:var(--s5)}

/* ───────── table ───────── */
.tablewrap{border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden;
  background:var(--surface); display:flex; flex-direction:column}
.toolbar{display:flex; align-items:center; gap:var(--s3); min-height:var(--toolbar-h);
  padding:var(--s2) var(--s4); border-bottom:1px solid var(--line); background:var(--surface)}
.toolbar .title{font-size:var(--t-dense); font-weight:var(--w-semi); min-width:0;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* The progress meter is HERE, at the top. The question it answers — how far
   through am I — must be readable without scrolling to the bottom. */
.meter{display:flex; align-items:center; gap:var(--s2); font-size:var(--t-xs); color:var(--text-2)}
.bar{flex:0 0 110px; height:5px; border-radius:3px; background:var(--n5); overflow:hidden; display:flex}
.bar i{display:block; height:100%; transition:width var(--fast) var(--ease)}
.bar .a{background:var(--ok9)} .bar .r{background:var(--n9)}
.tscroll{flex:1; min-height:0; overflow:auto; max-height:52vh}
table{border-collapse:separate; border-spacing:0; width:100%; font-size:var(--body-size)}
thead th{position:sticky; top:0; z-index:2; background:var(--surface-alt); text-align:left;
  font-size:var(--t-xs); font-weight:var(--w-semi); color:var(--text-muted);
  padding:0 var(--cell-x); height:var(--head-row-h); white-space:nowrap;
  border-bottom:1px solid var(--line-control)}
thead th.num{text-align:right}
thead th button{background:none;border:0;color:inherit;font:inherit;cursor:pointer;padding:0;
  display:inline-flex;align-items:center;gap:4px}
thead th button:hover{color:var(--text-2)}
tbody td{padding:0 var(--cell-x); height:var(--row-h); border-bottom:1px solid var(--line);
  vertical-align:middle; line-height:1.2}
tbody td > *{vertical-align:middle}
tbody tr:last-child td{border-bottom:0}
td.ell{max-width:1px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.chip{display:inline-flex; align-items:center; gap:5px; height:20px; padding:0 var(--s2);
  border-radius:3px; font-size:var(--t-micro); font-weight:var(--w-medium); white-space:nowrap}
.chip::before{content:""; width:5px; height:5px; border-radius:50%; background:currentColor}
.chip.ok{background:var(--ok3); color:var(--ok11)}
.chip.at{background:var(--at3); color:var(--at11)}
.chip.dg{background:var(--dg3); color:var(--dg11)}
/* Provenance is not status. Where a row came from is neutral; only whether
   a human must decide is amber. */
.chip.n{background:var(--n4); color:var(--text-2)}
.chip.n::before{background:var(--n9)}

/* Hover must never hide state, so the judged rules carry a class that
   outranks :hover, and hover is a brightening filter over whatever tint
   is already there rather than a competing background. */
tbody tr.row:hover{filter:brightness(1.16)}
tbody tr.row.is-cursor td:first-child{box-shadow:inset 2px 0 0 var(--a9)}
tbody tr.row.judged-approved td{background:var(--ok3)}
tbody tr.row.judged-rejected td{background:var(--n3)}
tbody tr.row.judged-rejected .what{opacity:.5; text-decoration:line-through}
.judge{display:inline-flex; gap:var(--s2)}
.judge button{width:26px;height:26px;display:grid;place-items:center; background:var(--surface-alt);
  border:1px solid var(--line-control); border-radius:var(--r-ctl); color:var(--text-2);
  cursor:pointer; font-size:var(--t-xs); line-height:1}
.judge button:hover{background:var(--hover); color:var(--text)}
tr.judged-approved .judge .yes{background:var(--ok9); border-color:var(--ok9); color:var(--ok-ink)}
tr.judged-rejected .judge .no{background:var(--n9); border-color:var(--n9); color:var(--n1)}
.arrow{color:var(--text-muted); padding:0 var(--s1)}
.was{color:var(--text-muted)} .now{color:var(--text); font-weight:var(--w-medium)}
.delta{font-size:var(--t-micro); font-family:var(--mono)}
.delta.big{color:var(--at11); font-weight:var(--w-semi)}
.bulk{display:flex; align-items:center; gap:var(--s3); padding:var(--s3) var(--s4);
  background:var(--surface-alt); border-top:1px solid var(--line-control)}

/* Mark the WINNER, not the loser. The first draft tinted and bolded the
   divergent value, so the eye landed on the number that is wrong. */
.diff td{font-size:var(--t-dense)}
.diff td.truth{background:color-mix(in oklab, var(--ok3) 60%, transparent);
  box-shadow:inset 2px 0 0 var(--ok9); font-weight:var(--w-medium)}
.diff td.other{color:var(--text-muted)}
.diff .why{font-size:var(--t-micro); color:var(--text-muted); display:block}

/* ───────── dialog ───────── */
dialog{padding:0; border:1px solid var(--line-control); border-radius:var(--r-over);
  background:var(--surface); color:var(--text); max-width:min(580px,92vw); box-shadow:var(--lift);
  opacity:0; transform:scale(.97);
  transition:opacity var(--med) var(--ease), transform var(--med) var(--ease),
             display var(--med) allow-discrete, overlay var(--med) allow-discrete}
dialog[open]{opacity:1; transform:none}
@starting-style{ dialog[open]{opacity:0; transform:scale(.97)} }
dialog::backdrop{background:oklch(10% 0 0/0);
  transition:background var(--med) var(--ease), display var(--med) allow-discrete,
             overlay var(--med) allow-discrete}
dialog[open]::backdrop{background:oklch(10% 0 0/.55)}
@starting-style{ dialog[open]::backdrop{background:oklch(10% 0 0/0)} }
dialog .dhead{padding:var(--s5) var(--s6) var(--s3)}
dialog h2{font-size:var(--t-base)}
dialog .dbody{padding:0 var(--s6) var(--s5); font-size:var(--t-dense); color:var(--text-2)}
dialog .dbody > * + *{margin-top:var(--s3)}
dialog .dfoot{display:flex; gap:var(--s3); justify-content:flex-end; padding:var(--s4) var(--s6);
  border-top:1px solid var(--line); background:var(--bg); border-radius:0 0 var(--r-over) var(--r-over)}
.consequence{background:var(--at3); border:1px solid color-mix(in oklab,var(--at9) 35%,transparent);
  border-radius:var(--r-ctl); padding:var(--s3) var(--s4); color:var(--at11); font-size:var(--t-dense)}
.ack{display:flex; gap:var(--s3); align-items:flex-start; font-size:var(--t-dense); color:var(--text)}
.ack input{margin-top:3px; flex:0 0 auto}
.result-row{display:flex; align-items:center; gap:var(--s3); padding:var(--s2) 0;
  border-bottom:1px solid var(--line); font-size:var(--t-dense)}
.result-row:last-child{border-bottom:0}

/* ───────── palette ───────── */
#palette{width:min(620px,92vw); background:var(--surface); border:1px solid var(--line-control);
  border-radius:10px; box-shadow:var(--lift); overflow:hidden; position:fixed; inset:0;
  margin:12vh auto auto; height:max-content}
#palette::backdrop{background:oklch(10% 0 0/.45)}
#palette input{width:100%; height:46px; border:0; border-bottom:1px solid var(--line);
  border-radius:0; background:none; font-size:15px; padding:0 var(--s5)}
#palette input:focus{outline:none}
.presults{max-height:min(420px,56vh); overflow-y:auto; padding:var(--s2)}
.presults .head{padding:var(--s3) var(--s3) var(--s1); font-size:var(--t-micro);
  font-weight:var(--w-semi); letter-spacing:.04em; text-transform:uppercase; color:var(--text-muted)}
.pres{display:flex; align-items:center; gap:var(--s3); padding:var(--s2) var(--s3);
  border-radius:var(--r-ctl); cursor:pointer; font-size:var(--t-dense); color:var(--text-2)}
.pres[data-sel="1"]{background:var(--a3); color:var(--text)}
.pres .ico{width:16px; opacity:.7; text-align:center}
.pres .meta{margin-left:auto; font-size:var(--t-micro); color:var(--text-muted); font-family:var(--mono)}

/* ───────── states ───────── */
.empty{display:flex; gap:var(--s4); padding:var(--s6); align-items:flex-start}
.empty .e-ico{width:18px; flex:0 0 auto; margin-top:1px; opacity:.8}
.empty h4{font-size:var(--t-dense); margin-bottom:2px}
.empty p{font-size:var(--t-dense); color:var(--text-muted); max-width:60ch}
.empty.good .e-ico{color:var(--ok11)}
.empty.warn .e-ico{color:var(--at11)}
.empty.err  .e-ico{color:var(--dg11)}
.empty.busy .e-ico{color:var(--in11)}
.empty .acts{margin-top:var(--s3); display:flex; gap:var(--s2)}
.lockbar{display:flex; align-items:center; gap:var(--s3); padding:var(--s2) var(--gutter);
  background:var(--at3); color:var(--at11); font-size:var(--t-xs); font-weight:var(--w-medium);
  border-bottom:1px solid color-mix(in oklab,var(--at9) 30%,transparent)}
.lockbar .btn{margin-left:auto}

@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){
    animation:none!important;
    /* animation:none also cancels the UA plus-lighter keyframe, leaving old
       and new stacked at full opacity under a stale blend mode — a
       double-brightness flash for the users who asked for less motion. */
    mix-blend-mode:normal!important}
}

/* ═══════════════════════════════════════════════════════════════════════
   PART TWO — controls the review screen did not happen to need.

   Everything above this line was lifted verbatim out of mockup.html, so the
   mockup and the gallery cannot drift. Everything below is new, and is written
   to the same rule: no literal that is not a layout constant.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── fields ────────────────────────────────────────────────────────────
   A field is label + control + (help | error). The error REPLACES the help
   text rather than appearing under it, because a field that grows taller
   when it goes wrong shoves the rest of the form down mid-typing.         */
.field{display:flex; flex-direction:column; gap:var(--s1); min-width:0}
.field > label{font-size:var(--t-xs); font-weight:var(--w-medium); color:var(--text-2)}
.field .help{font-size:var(--t-micro); color:var(--text-muted)}
.field .err{font-size:var(--t-micro); color:var(--dg11); display:flex; gap:5px; align-items:flex-start}
.field .err::before{content:"!"; flex:0 0 auto; width:12px; height:12px; margin-top:1px;
  border-radius:50%; background:var(--dg9); color:var(--dg-ink); font-size:9px;
  font-weight:var(--w-semi); display:grid; place-items:center; line-height:1}

.input{height:var(--h-ctl); min-width:0; padding:0 var(--s3); background:var(--surface-alt);
  color:var(--text); border:1px solid var(--line-control); border-radius:var(--r-ctl);
  font:inherit; font-size:var(--t-dense)}
.input::placeholder{color:var(--text-muted)}
.input:hover{border-color:var(--line-strong)}
.input:disabled{opacity:.45; cursor:not-allowed}
.input[aria-invalid="true"]{border-color:var(--dg9)}
.input.mono{font-family:var(--mono)}
textarea.input{height:auto; min-height:calc(var(--h-ctl) * 2); padding:var(--s2) var(--s3);
  /* field-sizing lets a textarea grow with its content instead of keeping a
     scrollbar inside a box the size of two lines. Chromium only for now; the
     min-height above is the whole fallback.                                */
  field-sizing:content; max-height:180px; resize:vertical}
.input.search{padding-left:26px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238a8fa0' stroke-width='1.5'%3E%3Ccircle cx='7' cy='7' r='4.5'/%3E%3Cpath d='M10.5 10.5 14 14'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:7px center; background-size:14px}

/* Number inputs in a data tool are read digit by digit and compared down a
   column, so they are mono and right-aligned wherever they hold a quantity. */
.input.qty{font-family:var(--mono); text-align:right; width:80px}

/* ── native form controls ──────────────────────────────────────────────
   accent-color is the whole styling budget for these. A hand-built checkbox
   loses the OS high-contrast rendering and the platform's own focus ring,
   and gains nothing a data tool needs.                                     */
input[type="checkbox"],input[type="radio"]{accent-color:var(--a9); width:14px; height:14px; margin:0}

/* ── switch ────────────────────────────────────────────────────────────
   For a setting that takes effect immediately. A checkbox is for something
   that takes effect when the form is submitted. Using the wrong one of these
   two is the most common control mistake in an internal tool.              */
.switch{display:inline-flex; align-items:center; gap:var(--s3); cursor:pointer;
  font-size:var(--t-dense); color:var(--text-2)}
.switch input{position:absolute; opacity:0; width:0; height:0}
.switch .track{position:relative; flex:0 0 auto; width:30px; height:18px; border-radius:var(--r-pill);
  background:var(--n5); border:1px solid var(--line-control); transition:background var(--fast) var(--ease)}
.switch .track::after{content:""; position:absolute; top:2px; left:2px; width:12px; height:12px;
  border-radius:50%; background:var(--n10); transition:transform var(--fast) var(--ease),
  background var(--fast) var(--ease)}
.switch input:checked + .track{background:var(--a9); border-color:var(--a9)}
.switch input:checked + .track::after{transform:translateX(12px); background:var(--a-ink)}
.switch input:focus-visible + .track{outline:2px solid var(--ring); outline-offset:2px}
.switch input:disabled + .track{opacity:.45}
.switch:has(input:disabled){cursor:not-allowed; color:var(--text-muted)}

/* ── segmented control ─────────────────────────────────────────────────
   Two to four mutually exclusive options, all worth showing at once. Past
   four, use a select — a segmented control that wraps is a worse select.   */
.seg{display:inline-flex; height:var(--h-ctl); padding:2px; gap:2px; background:var(--surface-alt);
  border:1px solid var(--line-control); border-radius:var(--r-ctl)}
.seg button{height:100%; padding:0 var(--s3); background:none; border:0; border-radius:2px;
  color:var(--text-muted); font:inherit; font-size:var(--t-xs); cursor:pointer; white-space:nowrap}
.seg button:hover{color:var(--text-2)}
.seg button[aria-pressed="true"]{background:var(--n5); color:var(--text); font-weight:var(--w-medium)}
.seg.sm{height:var(--h-ctl-sm)}

/* ── tabs ──────────────────────────────────────────────────────────────  */
.tabs{display:flex; gap:var(--s5); border-bottom:1px solid var(--line)}
.tabs [role="tab"]{position:relative; background:none; border:0; padding:var(--s3) 0;
  color:var(--text-muted); font:inherit; font-size:var(--t-dense); cursor:pointer; white-space:nowrap}
.tabs [role="tab"]:hover{color:var(--text-2)}
.tabs [role="tab"][aria-selected="true"]{color:var(--text); font-weight:var(--w-medium)}
.tabs [role="tab"][aria-selected="true"]::after{content:""; position:absolute; left:0; right:0;
  bottom:-1px; height:2px; background:var(--a9); border-radius:1px 1px 0 0}
.tabs [role="tab"] .count{margin-left:var(--s2)}

/* ── alerts ────────────────────────────────────────────────────────────
   A page-level statement about the whole screen. NOT for field errors -
   those belong on the field, where the person's eye already is.           */
.alert{display:flex; gap:var(--s3); padding:var(--s4); border-radius:var(--r-card);
  border:1px solid; font-size:var(--t-dense); align-items:flex-start}
.alert .a-ico{flex:0 0 auto; width:16px; height:16px; display:grid; place-items:center; margin-top:1px}
.alert h4{font-size:var(--t-dense); margin-bottom:2px}
.alert p{color:var(--text-2)}
.alert .acts{margin-top:var(--s3); display:flex; gap:var(--s2)}
.alert.info{background:var(--in3); border-color:color-mix(in oklab,var(--in9) 35%,transparent)}
.alert.info .a-ico{color:var(--in11)}
.alert.ok{background:var(--ok3); border-color:color-mix(in oklab,var(--ok9) 35%,transparent)}
.alert.ok .a-ico{color:var(--ok11)}
.alert.warn{background:var(--at3); border-color:color-mix(in oklab,var(--at9) 35%,transparent)}
.alert.warn .a-ico{color:var(--at11)}
.alert.danger{background:var(--dg3); border-color:color-mix(in oklab,var(--dg9) 35%,transparent)}
.alert.danger .a-ico{color:var(--dg11)}

/* ── progress ──────────────────────────────────────────────────────────
   Determinate whenever a total is known. An indeterminate bar on a job that
   knows it has 47 of 200 chunks left is a lie of omission.                */
.prog{display:flex; flex-direction:column; gap:var(--s1); min-width:180px}
.prog .ptop{display:flex; align-items:baseline; gap:var(--s3); font-size:var(--t-xs); color:var(--text-2)}
.prog .ptop .pn{margin-left:auto; font-family:var(--mono); color:var(--text-muted)}
.prog .ptrack{height:5px; border-radius:3px; background:var(--n5); overflow:hidden}
.prog .pfill{height:100%; background:var(--a9); border-radius:3px;
  transition:width var(--med) var(--ease)}
.prog.ok .pfill{background:var(--ok9)}
.prog.warn .pfill{background:var(--at9)}
.prog.danger .pfill{background:var(--dg9)}

.spinner{width:14px; height:14px; flex:0 0 auto; border-radius:50%;
  border:2px solid var(--n6); border-top-color:var(--a9); animation:spin 700ms linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* ── skeleton ──────────────────────────────────────────────────────────
   Only for content whose SHAPE is known before it arrives. A skeleton of
   three rows where six will land reads as a wrong answer, briefly.         */
.skel{border-radius:3px; background:linear-gradient(90deg,var(--n3) 25%,var(--n4) 37%,var(--n3) 63%);
  background-size:400% 100%; animation:shim 1.4s ease infinite; height:11px}
@keyframes shim{0%{background-position:100% 0}100%{background-position:-100% 0}}

/* ── toast ─────────────────────────────────────────────────────────────
   Bottom-left, out of the way of the primary action, which on every screen
   here sits bottom-right. A toast that covers the button you just pressed
   is the reason people press it twice.                                     */
.toaster{position:fixed; left:var(--s5); bottom:var(--s5); z-index:60;
  display:flex; flex-direction:column-reverse; gap:var(--s2)}
.toast{display:flex; align-items:center; gap:var(--s3); min-width:280px; max-width:420px;
  padding:var(--s3) var(--s4); background:var(--surface-alt); color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-over); box-shadow:var(--lift);
  font-size:var(--t-dense)}
.toast .t-ico{flex:0 0 auto; width:14px; display:grid; place-items:center}
.toast.ok .t-ico{color:var(--ok11)}
.toast.danger .t-ico{color:var(--dg11)}
.toast.at .t-ico{color:var(--at11)}
.toast .btn{margin-left:auto}
.toast .close{background:none; border:0; color:var(--text-muted); cursor:pointer; padding:0 2px;
  font:inherit}
.toast .close:hover{color:var(--text)}

/* ── tooltip ───────────────────────────────────────────────────────────
   Never the only place a fact appears. This carries the full name behind an
   abbreviated column, not an instruction.                                  */
.tip{position:absolute; padding:var(--s1) var(--s3); background:var(--n5); color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-ctl); font-size:var(--t-xs);
  box-shadow:var(--lift); max-width:280px; position-area:block-start; margin:var(--s1);
  opacity:0; transition:opacity var(--fast) var(--ease), display var(--fast) allow-discrete,
  overlay var(--fast) allow-discrete}
.tip:popover-open{opacity:1}
@starting-style{ .tip:popover-open{opacity:0} }

/* ── keyboard sheet ────────────────────────────────────────────────────  */
.keys{display:grid; grid-template-columns:auto 1fr; gap:var(--s2) var(--s4); align-items:center;
  font-size:var(--t-dense)}
.keys dt{display:flex; gap:var(--s1); justify-content:flex-end}
.keys dd{margin:0; color:var(--text-2)}

/* ── date range ────────────────────────────────────────────────────────
   Two native date inputs beat a hand-built calendar for a range someone
   usually types. The presets are what actually gets clicked.               */
.daterange{display:flex; flex-direction:column; gap:var(--s3); padding:var(--s4); min-width:300px}
.daterange .presets{display:flex; flex-wrap:wrap; gap:var(--s2)}
.daterange .pair{display:flex; align-items:center; gap:var(--s2)}
.daterange .pair .input{flex:1; min-width:0}
/* The native picker follows the page. This was pinned to `dark`, which
   means a light-theme user opens a black calendar out of a white page -
   and it is the one piece of UI the stylesheet cannot restyle, so it has
   to be told the truth rather than a preference. `light dark` lets the
   browser pick from the theme actually in force. */
.daterange input[type="date"]{color-scheme:light dark}

/* ── stat tiles ────────────────────────────────────────────────────────
   The big number on a card. Used sparingly: a screen of these is a
   dashboard, and a dashboard is what you build when you have not decided
   what the screen is for.                                                 */
.cards3{display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:var(--s5)}
.statlabel{font-size:var(--t-xs); color:var(--text-muted); font-weight:var(--w-medium)}
.stat{font-size:var(--t-stat); line-height:var(--lh-stat); font-weight:var(--w-semi);
  font-variant-numeric:tabular-nums; letter-spacing:-.02em; margin:2px 0}

/* ── sidebar theme row ─────────────────────────────────────────────────  */
.themerow{display:flex; padding:0 0 var(--s3)}
.themerow .seg{width:100%}
.themerow .seg button{flex:1}

/* ── screens ───────────────────────────────────────────────────────────
   Exactly one is visible. [hidden] is honoured by the reset, and using it
   rather than a display rule means the attribute and the appearance can
   never disagree.                                                         */
.content[hidden]{display:none}

/* ── board ─────────────────────────────────────────────────────────────
   The frame for "our own version of Monday.com". Deliberately generic: a
   board has groups, a group has items, an item has sub-items and a value per
   column. Go live is the first board; HR and Compliance are meant to be the
   second and third without this CSS changing.

   The one borrowed idea worth borrowing is the coloured group spine — it is
   what lets you find "the Monday stores" on a long board without reading a
   single date. The one worth leaving behind is the wall of colour: here only
   STATUS is coloured, and everything else is quiet, because on this board
   most cells are prose and prose in eleven colours is unreadable.          */
.board{display:flex; flex-direction:column; gap:var(--s6)}

.bgroup{border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden;
  background:var(--surface)}
.bgroup > header{display:flex; align-items:center; gap:var(--s3); padding:var(--s3) var(--s4);
  border-bottom:1px solid var(--line); background:var(--surface-alt)}
.bgroup > header .spine{width:3px; height:16px; border-radius:2px; background:var(--n9); flex:0 0 auto}
.bgroup > header h3{font-size:var(--t-sm)}
.bgroup > header .when{font-size:var(--t-xs); color:var(--text-muted)}

/* The go-live progress bar. FOUND UNSTYLED, not newly broken: these rules
   existed only as `.bitem .steps ...`, and the board's markup is
   `.bgroup > header > .steps` - so they never matched anything. The track had
   no height and no background, which means it has been invisible for as long
   as the screen has existed and only the "3/8" beside it ever showed.
   Removing the dead `.bitem` block is what made that visible. */
.bgroup > header .steps{display:flex; align-items:center; gap:var(--s3);
  min-width:0}
.bgroup > header .steps .ptrack{flex:0 0 86px; height:5px; border-radius:3px;
  background:var(--n5); overflow:hidden}
.bgroup > header .steps .pfill{height:100%; background:var(--ok9);
  border-radius:3px}
.bgroup > header .steps .txt{font-size:var(--t-xs); color:var(--text-muted);
  font-family:var(--mono)}
.bgroup[data-when="past"] > header .spine{background:var(--n9)}
.bgroup[data-when="next"] > header .spine{background:var(--at9)}
.bgroup[data-when="later"] > header .spine{background:var(--in9)}
.bgroup[data-when="next"] > header h3::after{content:"next"; margin-left:var(--s3);
  font-size:var(--t-micro); font-weight:var(--w-semi); color:var(--at11);
  background:var(--at3); padding:1px var(--s2); border-radius:var(--r-pill);
  text-transform:uppercase; letter-spacing:.04em}

/* The checklist a row opens into. */
.bsub{grid-column:1 / -1; padding:var(--s2) 0 var(--s4) var(--s7); display:flex;
  flex-direction:column; gap:var(--s1)}
.bstep{display:flex; align-items:center; gap:var(--s3); padding:var(--s1) var(--s3);
  border-radius:var(--r-ctl); font-size:var(--t-dense); min-height:var(--h-ctl-sm)}
.bstep:hover{background:var(--surface-alt)}
.bstep label{display:flex; align-items:center; gap:var(--s3); cursor:pointer; flex:1; min-width:0}
.bstep.done label > span{color:var(--text-muted); text-decoration:line-through}
.bstep .owner{font-size:var(--t-micro); color:var(--text-muted); flex:0 0 auto}
.bstep .file{font-size:var(--t-micro); font-family:var(--mono); color:var(--a11);
  flex:0 0 auto; text-decoration:none}
.bstep .file:hover{text-decoration:underline}

/* ── data grid ─────────────────────────────────────────────────────────
   The big-table component. A GRID OF DIVS, not a <table>, and the reason is
   measured rather than fashionable: see docs/decisions/how-11648-rows-render.md.

   Short version. `content-visibility: auto` lets the browser skip laying out
   rows nobody can see, and it is worth about 7x on 11,648 rows. It does
   NOTHING on a <tr> - measured three separate times, and slower than plain in
   all three. That is not a bug to route around: a table row cannot size itself
   independently of its table, because column widths are negotiated across
   every row, so the containment the property needs is something a <tr> cannot
   be given. Divs are the only shape where the containment is coherent.

   The columns still line up across 11,648 independently-contained rows. That
   used to say `subgrid` was doing the work. IT WAS NOT, AND IT CANNOT.

   `content-visibility: auto` applies layout containment, and a contained box
   establishes an independent formatting context, which per CSS Grid Level 2
   is exactly what a subgrid may not be. The declaration was not ignored - it
   was computed, rejected and fell back to `none`, silently, with no warning
   anywhere. Every row became a single-column grid, so each cell took the full
   track and the row stacked one cell per line. Nine grids across seven
   screens, for as long as both properties have sat in this rule.

   The tell was that the HEADERS were fine. They sit in `display:contents`
   wrappers, so each `.dhead` is a direct child of `.dgrid` itself and never
   needed subgrid at all. Headers correct, data stacked, is the fingerprint.

   So the track list is passed down as a CUSTOM PROPERTY instead: each grid
   sets `--cols`, and both the grid and its rows read it. Custom properties
   inherit THROUGH containment - inheritance is not layout - so every row gets
   the identical track list without ever needing to be a subgrid, and the 7x
   rendering win stays. Rows 0 and 500 measured identical on a 600-row check.

   THE ONE RULE THIS IMPOSES: every track in `--cols` must be a fixed length
   or a `minmax()` with one. An `auto` or bare `fr` track is sized from the
   contents of a row, and a contained row cannot see its siblings, so those
   would genuinely drift - which is the job subgrid would have done. All nine
   grids here use px or minmax(px,1fr). Check before adding a tenth.

   WHAT USING THIS BUYS YOU AND WHAT IT COSTS YOU:
     Buys - every row is really in the document, so Ctrl+F finds all 11,648,
            select-all copies everything, the scrollbar is honest, Home/End
            work. None of that survives JS virtualisation.
     Costs - ARIA is now YOUR JOB. A <table> hands the screen reader its
            structure for free; this hands it nothing until you supply
            role=grid/row/columnheader/gridcell, aria-rowcount and
            aria-rowindex. And aria-rowindex must be the row's place in the
            WHOLE set, not its position in the DOM, or a filtered view
            announces "row 3 of 11,648" for the four-hundredth match.         */
.dgrid{display:grid; grid-template-columns:var(--cols);
  font-size:var(--body-size); min-width:0}
.dgrid .dhead{position:sticky; top:0; z-index:2; background:var(--surface-alt);
  display:flex; align-items:center; gap:var(--s2); padding:0 var(--cell-x);
  height:var(--head-row-h); font-size:var(--t-xs); font-weight:var(--w-semi);
  color:var(--text-2); border-bottom:1px solid var(--line); white-space:nowrap}
.dgrid .dhead.num{justify-content:flex-end}
.dgrid .dhead button{background:none; border:0; color:inherit; font:inherit;
  cursor:pointer; padding:0; display:inline-flex; align-items:center; gap:var(--s1)}
.dgrid .dhead button:hover{color:var(--text)}

.drow{display:grid; grid-template-columns:var(--cols); grid-column:1/-1;
  /* The whole point. contain-intrinsic-size must stay close to the real row
     height - if it is wrong the scrollbar jumps as rows are revealed, which
     reads as the page fighting you. */
  content-visibility:auto; contain-intrinsic-size:auto var(--row-h)}
.drow > .dcell{display:flex; align-items:center; padding:0 var(--cell-x);
  height:var(--row-h); border-bottom:1px solid var(--line-soft);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0}
.drow > .dcell.num{justify-content:flex-end; font-family:var(--mono)}
.drow > .dcell.mono{font-family:var(--mono)}
.drow:hover > .dcell{background:var(--hover)}
.drow[aria-selected="true"] > .dcell{background:var(--a3)}
.drow.is-cursor > .dcell:first-child{box-shadow:inset 2px 0 0 var(--a9)}
.drow > .dcell .sub{color:var(--text-muted); font-size:var(--t-micro); margin-left:var(--s2)}

/* A row whose two systems disagree. The tint is the quiet signal; the chip in
   the row is the one that carries the meaning, because colour alone is not a
   fact anyone can act on. */
.drow.differs > .dcell{background:color-mix(in oklab, var(--at3) 45%, transparent)}
.drow.differs:hover > .dcell{background:color-mix(in oklab, var(--at3) 70%, transparent)}

.dgridwrap{border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden;
  display:flex; flex-direction:column; min-height:0; background:var(--surface)}
.dscroll{flex:1; min-height:0; overflow:auto; max-height:58vh; contain:paint}

/* D7-A. One tile per system, side by side, because the finding is usually a
   COMPARISON: one side working and the other silent. Stacked or summed, that
   reads as "some calls happened" and the shape is lost. */
.reqsys{display:grid; grid-template-columns:repeat(auto-fit, minmax(190px,1fr));
  gap:var(--s3); margin:0 0 var(--s4)}
.reqsys .sys{border:1px solid var(--line); border-radius:var(--r-ctl);
  padding:var(--s3) var(--s4); background:var(--surface)}
.reqsys .sys .nm{font-size:var(--t-micro); text-transform:uppercase;
  letter-spacing:.04em; color:var(--text-muted)}
.reqsys .sys .big{font-size:var(--t-lg); font-weight:var(--w-semi);
  font-variant-numeric:tabular-nums; color:var(--text)}
.reqsys .sys .sub{font-size:var(--t-xs); color:var(--text-muted)}
/* Silence is a STATE, not a small number. A system a batch should have
   written to and did not call at all is the finding, and it must not read as
   a quiet success. */
.reqsys .sys.silent{border-color:var(--at9); background:var(--at3)}
.reqsys .sys.silent .big{color:var(--at11)}
.reqsys .sys.bad{border-color:var(--dg9); background:var(--dg3)}
.reqsys .sys.bad .big{color:var(--dg11)}

/* The group verdict bar, at the FOOT of an open group. D5's own named con is
   "a one-press verdict on 255 rows makes a wrong judgement 255 rows wrong",
   so the control that can do that is deliberately not next to the header you
   press to read the group - it sits under the rows, where you arrive having
   scrolled past them. */
.pr-act{display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap;
  padding:var(--s3) var(--s4); background:var(--surface-alt);
  border-top:1px solid var(--line-control)}
.pr-act .why{font-size:var(--t-xs); color:var(--text-muted); flex:1 1 240px}
.pr-act .big{color:var(--at11); font-weight:var(--w-semi)}

/* ── D1-C, panels you resize ────────────────────────────────────────────
   THE GRID DOES NOT OWN THE VIEWPORT HEIGHT ANY MORE; A PANEL DOES, AND THE
   PERSON SETS IT. That is the whole decision. `.dscroll`'s vh cap above is
   now only the fallback for a screen that has no divider - where one exists,
   JavaScript sets an explicit height and the cap never applies.

   The divider is a real separator, not a decorated border: it takes focus,
   it takes arrow keys, and it reports its size to a screen reader. A control
   that can only be dragged excludes anybody who cannot drag.                */
.pdiv{flex:0 0 auto; height:11px; cursor:row-resize; position:relative;
  background:var(--surface-alt); border-top:1px solid var(--line-control);
  border-bottom:1px solid var(--line-control); touch-action:none}
.pdiv::after{content:""; position:absolute; left:50%; top:50%;
  width:44px; height:3px; margin:-2px 0 0 -22px; border-radius:2px;
  background:var(--line-strong, var(--line-control))}
.pdiv:hover::after, .pdiv:focus-visible::after{background:var(--ring)}
.pdiv:focus-visible{outline:2px solid var(--ring); outline-offset:-2px}
/* While dragging, the pointer is captured by the divider but the cursor can
   be anywhere; without this the text under it selects as the mouse moves. */
body.resizing{cursor:row-resize; user-select:none}

/* The steal from D1-A: totals that do not scroll away. C's own con is that
   "the list is shorter than the other two, since the detail always takes a
   share" - so the count has to live somewhere the shorter list still shows
   it. Aligned to the SAME --cols as the grid where the figures are per
   column, so a total sits under the column it totals.                       */
.dfoot{flex:0 0 auto; display:grid; grid-template-columns:var(--cols, 1fr);
  align-items:center; gap:0; padding:0;
  background:var(--surface-alt); border-top:1px solid var(--line-control);
  font-size:var(--t-xs); font-variant-numeric:tabular-nums;
  color:var(--text-2); min-height:30px}
.dfoot > span{padding:var(--s1) var(--s2); overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap}
.dfoot .num{text-align:right}
.dfoot .lbl{color:var(--text-muted); text-transform:uppercase;
  letter-spacing:.04em; font-size:var(--t-micro)}
.dfoot b{font-weight:var(--w-semi); color:var(--text)}
/* A foot that is one sentence rather than a set of column totals. */
.dfoot.plain{grid-template-columns:1fr; text-align:left}

/* ── export builder ────────────────────────────────────────────────────
   Two columns: what you saved on the left, what you are building on the
   right. The saved list is first because the common case is running last
   month's export again, not designing a new one.                          */
.exp{display:grid; grid-template-columns:260px 1fr; gap:var(--s6); align-items:start}
@media (max-width:1100px){ .exp{grid-template-columns:1fr} }

.presets{display:flex; flex-direction:column; gap:var(--s1)}
.preset{display:flex; align-items:flex-start; gap:var(--s3); width:100%; text-align:left;
  padding:var(--s3); background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-ctl); color:var(--text-2); font:inherit;
  font-size:var(--t-dense); cursor:pointer}
.preset:hover{border-color:var(--line-control); color:var(--text)}
.preset[aria-pressed="true"]{border-color:var(--a6); background:var(--a2); color:var(--text)}
.preset .pn{font-weight:var(--w-medium); display:block}
.preset .pd{display:block; font-size:var(--t-micro); color:var(--text-muted); margin-top:2px;
  white-space:normal}
.preset .star{margin-left:auto; flex:0 0 auto; color:var(--at11); font-size:var(--t-xs)}
.preset .lock{font-size:var(--t-micro); color:var(--text-muted)}

/* Field picker: two lists and the buttons between them. A multi-select with
   ctrl-click would be fewer pixels and is the control everyone gets wrong -
   you cannot see what you chose without scrolling back through what you
   didn't. */
.picker{display:grid; grid-template-columns:1fr 42px 1fr; gap:var(--s3); align-items:stretch}
.picker .col{display:flex; flex-direction:column; min-width:0}
.picker .col > h5{font-size:var(--t-xs); color:var(--text-2); margin:0 0 var(--s2);
  font-weight:var(--w-medium)}
.picker .list{flex:1; min-height:170px; max-height:230px; overflow-y:auto;
  border:1px solid var(--line-control); border-radius:var(--r-ctl);
  background:var(--surface-alt); padding:var(--s1)}
.picker .mid{display:flex; flex-direction:column; justify-content:center; gap:var(--s2)}
.fld{display:flex; align-items:center; gap:var(--s2); width:100%; text-align:left;
  padding:var(--s1) var(--s2); border:0; border-radius:3px; background:none;
  color:var(--text-2); font:inherit; font-size:var(--t-dense); cursor:pointer;
  min-height:var(--h-ctl-sm)}
.fld:hover{background:var(--hover); color:var(--text)}
.fld[aria-selected="true"]{background:var(--a3); color:var(--text)}
.fld .k{font-family:var(--mono); font-size:var(--t-micro); color:var(--text-muted);
  margin-left:auto; flex:0 0 auto}
.fld .grip{color:var(--text-muted); cursor:grab; flex:0 0 auto}

.filterrow{display:grid; grid-template-columns:1fr 150px 1fr 32px; gap:var(--s2);
  align-items:center}
.filterrow + .filterrow{margin-top:var(--s2)}

.exp-preview{border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden}
.exp-preview .toolbar{border-bottom:1px solid var(--line)}

/* ── icons ─────────────────────────────────────────────────────────────
   Stroke, never fill, so an icon's weight matches the type beside it. The
   sprite and the geometry rules live in icons.js.                          */
.i{width:16px; height:16px; flex:0 0 auto; display:inline-block; vertical-align:-3px;
  fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round;
  stroke-linejoin:round}
.i.lg{width:18px; height:18px}

/* ═══════════════════════════════════════════════════════════════════════
   GALLERY HARNESS - not part of the app. Everything below is the scaffolding
   components.html uses to lay specimens out, and must never be linked by a
   real screen.
   ═══════════════════════════════════════════════════════════════════════ */
.gal-body{display:grid; grid-template-columns:210px 1fr; height:100vh; overflow:hidden}
.gal-nav{background:var(--surface); border-right:1px solid var(--line); overflow-y:auto;
  padding:var(--s4) 0 var(--s8)}
.gal-nav h1{font-size:var(--t-dense); padding:0 var(--s4) var(--s4); letter-spacing:-.01em}
.gal-nav a{display:block; padding:var(--s1) var(--s4); color:var(--text-muted);
  text-decoration:none; font-size:var(--t-dense); border-left:2px solid transparent}
.gal-nav a:hover{color:var(--text); background:var(--surface-alt)}
.gal-nav a.on{color:var(--text); border-left-color:var(--a9); background:var(--a2)}
.gal-main{overflow-y:auto; scroll-behavior:smooth}
.gal-inner{padding:var(--s7) var(--s8) 40vh; max-width:1040px}
.gal-sec{scroll-margin-top:var(--s5)}
.gal-sec + .gal-sec{margin-top:var(--s9)}
.gal-sec > h2{font-size:var(--t-lg); padding-bottom:var(--s3); border-bottom:1px solid var(--line)}
.gal-sec > .note{margin-top:var(--s3); font-size:var(--t-dense); color:var(--text-muted);
  max-width:var(--measure)}
.spec{margin-top:var(--s5); border:1px solid var(--line); border-radius:var(--r-card);
  background:var(--surface); overflow:hidden}
.spec > .sh{display:flex; align-items:baseline; gap:var(--s3); padding:var(--s2) var(--s4);
  border-bottom:1px solid var(--line); background:var(--surface-alt)}
.spec > .sh b{font-size:var(--t-xs); font-weight:var(--w-semi)}
.spec > .sh span{font-size:var(--t-micro); color:var(--text-muted); font-family:var(--mono)}
.spec > .sb{padding:var(--s5); display:flex; flex-wrap:wrap; gap:var(--s5); align-items:flex-start}
.spec > .sb.col{flex-direction:column; align-items:stretch}
.spec > .sb.tight{gap:var(--s3)}
.state{display:flex; flex-direction:column; gap:var(--s2); align-items:flex-start}
.state > .sl{font-size:var(--t-micro); color:var(--text-muted); font-family:var(--mono);
  text-transform:lowercase}
/* A forced state is derived from the real rule at runtime - see forceStates()
   in components.html. Nothing here re-declares what :hover already says.    */
.state[data-forced] > .sl::after{content:" ·forced"; color:var(--at11)}

/* The self-check banner. Green is not the interesting outcome. */
.selfcheck{position:sticky; top:0; z-index:20; padding:var(--s3) var(--s8);
  font-size:var(--t-dense); border-bottom:1px solid var(--line); background:var(--surface-alt)}
.selfcheck.pass{color:var(--ok11)}
.selfcheck.fail{color:var(--dg11); background:var(--dg3)}
.selfcheck ul{margin:var(--s2) 0 0; padding-left:var(--s5); font-family:var(--mono);
  font-size:var(--t-micro); color:var(--text)}

/* ── what is running ───────────────────────────────────────────────────────
   A strip above the page title, on every screen, because a two-hour write is
   not something you should have to be on the right tab to notice.

   Built from the same tokens as everything else, so it inherits both themes
   without a second definition. Deliberately one line of facts and a bar: it
   is read at a glance while something important is happening, and a
   dashboard would be worse at that job, not better. */
#progressStrip{ margin: 0 0 var(--s4); }
/* NOTE: this component is .runrow, NOT .prog. components.css already
   defines .prog as a vertical mini progress bar (line ~399), and a
   second .prog here inherited its flex-direction:column - which stacked
   every field of every row and filled the screen with five batches.
   Reusing a class name that is already taken is the collision; a new
   name is the fix, not an override. */
.runrow{ display:flex; align-items:center; gap:var(--s4);
       padding:var(--s3) var(--s4); background:var(--surface);
       border:1px solid var(--line); border-radius:var(--r-card);
       margin-bottom:var(--s2); }
.runrow:last-child{ margin-bottom:0 }
.runrow .what{ min-width:220px; max-width:320px }
.runrow .ttl{ font-weight:var(--w-semi); color:var(--text);
            white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.runrow .sub{ font-size:var(--t-xs); color:var(--text-muted);
            font-family:var(--mono) }
.runrow .track{ flex:1; height:8px; border-radius:999px; overflow:hidden;
              background:var(--surface-alt) }
.runrow .fill{ display:block; height:100%; background:var(--a9);
             transition:width .6s ease }
.runrow .fill.stalled{ background:var(--at9) }
.runrow .fill.hasfail{ background:linear-gradient(90deg,
                     var(--a9) 0 72%, var(--dg9) 100%) }
.runrow .nums{ font-family:var(--mono); font-size:var(--t-xs);
             color:var(--text-2); white-space:nowrap;
             font-variant-numeric:tabular-nums }
.runrow .eta{ font-size:var(--t-xs); color:var(--text-muted);
            white-space:nowrap; min-width:132px; text-align:right;
            font-variant-numeric:tabular-nums }
.runrow .pchip{ font-size:var(--t-xs); padding:2px var(--s2);
              border-radius:999px; white-space:nowrap;
              background:var(--surface-alt); color:var(--text-muted) }
.runrow .pchip.run{ background:var(--a3); color:var(--a11) }
.runrow .pchip.stall{ background:var(--at3); color:var(--at11) }
.runrow .pchip.fail{ background:var(--dg3); color:var(--dg11) }
@media (max-width: 900px){
  .runrow{ flex-wrap:wrap; gap:var(--s2) }
  .runrow .what{ min-width:0; flex:1 1 100% }
  .runrow .eta{ text-align:left; min-width:0 }
}
@media (prefers-reduced-motion: reduce){ .runrow .fill{ transition:none } }

/* ═══════════════════════════════════════════════════════════════════════
   OVERVIEW — decisions D3 and D4, chosen 13 September 2026.

   D3-B is the ACTIVITY TIMELINE and it is the default. D3-C, the status
   board, is a second view of the same screen, because he asked for it as a
   toggle rather than a steal. They answer different questions and neither can
   answer the other's: the timeline says what happened and in what order, the
   board says whether anything is wrong right now.

   D4-B is NO TILES. The .cards3 stat grid is gone. Figures now live where
   they are relevant, with weight and tabular numerals doing the work a box
   used to do — plus a delta against yesterday, stolen from D4-C, because
   B's own trade-off was "a figure inside a heading is harder to spot
   changing day to day".
   ═══════════════════════════════════════════════════════════════════════ */

/* the view toggle */
.ov-views{display:flex; align-items:center; justify-content:space-between;
  gap:var(--s5); margin-bottom:var(--s5); flex-wrap:wrap}
.ov-checked{font-size:var(--t-xs); color:var(--text-muted);
  font-variant-numeric:tabular-nums}

/* D4 · figures without tiles.
   A row of label / value / movement. The value carries the weight; the
   label stays quiet; the delta is the only coloured thing, because change
   is the one thing the eye should catch. */
.ov-figs{display:flex; flex-wrap:wrap; gap:var(--s7) var(--s9);
  padding:var(--s6) 0 var(--s7); border-bottom:1px solid var(--line)}
.ov-fig{display:flex; flex-direction:column; gap:2px; min-width:8rem}
.ov-fig .k{font-size:var(--t-xs); color:var(--text-muted); font-weight:500}
.ov-fig .v{font-size:var(--t-xl); font-weight:650; line-height:1.05;
  font-variant-numeric:tabular-nums; letter-spacing:-.015em}
.ov-fig .d{font-size:var(--t-xs); font-variant-numeric:tabular-nums}
.ov-fig .d.up{color:var(--ok11)} .ov-fig .d.down{color:var(--at11)}
.ov-fig .d.flat{color:var(--text-muted)}

/* D3-B · the timeline.
   A rail with events hung off it. An entry that can be ACTED ON carries the
   control that clears it — stolen from D3-A, whose strength was that seeing
   and fixing are one step, which is exactly what B's own cons said it
   lacked. */
.ov-tl{list-style:none; margin:0; padding:0 0 0 var(--s7);
  border-left:2px solid var(--line)}
.ov-tl li{position:relative; padding:var(--s5) 0 var(--s5) var(--s6)}
.ov-tl li::before{content:""; position:absolute; left:calc(-1 * var(--s7) - 5px);
  top:1.35rem; width:9px; height:9px; border-radius:50%;
  background:var(--surface); border:2px solid var(--n7)}
.ov-tl li.warn::before{background:var(--at9); border-color:var(--at9)}
.ov-tl li.bad::before{background:var(--dg9); border-color:var(--dg9)}
.ov-tl li.good::before{background:var(--ok9); border-color:var(--ok9)}
.ov-tl .when{font-size:var(--t-xs); color:var(--text-muted);
  font-variant-numeric:tabular-nums}
.ov-tl .what{font-weight:600; margin:1px 0 2px}
.ov-tl .why{font-size:var(--t-sm); color:var(--text-muted)}
.ov-tl .act{margin-top:var(--s4)}

/* two kinds of nothing — the distinction C makes and B cannot.
   An empty timeline is ambiguous: nothing happened, or nothing is watching.
   These say which. */
.ov-empty{padding:var(--s8) var(--s6); text-align:center; color:var(--text-muted)}
.ov-empty b{display:block; color:var(--text); font-weight:600;
  margin-bottom:2px}

/* D3-C · the status board.
   ⛔ A CELL CANNOT GO MISSING. The cells are a fixed list on the server, so
   a subsystem that cannot be reached still draws — as `unknown`, which is a
   different answer from `ok` and looks it. That is the whole reason this
   view exists beside the timeline. */
.ov-board{display:grid; gap:var(--s5);
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}
.ov-cell{background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-card); padding:var(--s6); border-left:3px solid var(--n7)}
.ov-cell.ok{border-left-color:var(--ok9)}
.ov-cell.warn{border-left-color:var(--at9)}
.ov-cell.unknown{border-left-color:var(--n7); border-style:dashed;
  border-left-style:solid}
.ov-cell .n{font-size:var(--t-xs); color:var(--text-muted); font-weight:600;
  letter-spacing:.04em; text-transform:uppercase}
.ov-cell .h{font-weight:600; margin:3px 0 1px;
  font-variant-numeric:tabular-nums}
.ov-cell .s{font-size:var(--t-sm); color:var(--text-muted)}

/* ═══════════════════════════════════════════════════════════════════════
   D2-B — THE SLIDE-OVER DRAWER. Chosen 13 September 2026.

   Below the breakpoint the sidebar leaves the layout entirely and returns as
   a drawer over the page. Not a collapsing rail: that was option A, and
   --rail-w died with it.

   ⛔ STOLEN FROM A: THE TRIGGER CARRIES THE WAITING COUNT. B's own honest
   trade-off was "the nav is invisible, so the 645 waiting cannot be seen
   without opening it" - a drawer that hides the one number you opened the
   app for is a worse drawer. The badge puts the total on the button; the
   breakdown is still inside.

   The scrim uses --scrim, added to tokens.css with this decision, rather
   than a black at some alpha invented here.
   ═══════════════════════════════════════════════════════════════════════ */
.drawerbtn{display:none}                 /* wide layout: the nav is just there */

@media (max-width: 900px){
  .app{grid-template-columns:1fr}

  /* The drawer. transform rather than left/width so it animates on the
     compositor and cannot reflow the page behind it. */
  .side{
    position:fixed; inset:0 auto 0 0; width:min(var(--nav-w), 86vw);
    z-index:40; transform:translateX(-100%);
    transition:transform var(--med) var(--ease, ease);
    box-shadow:var(--lift);
  }
  .side[data-open="true"]{transform:translateX(0)}

  .scrim{
    position:fixed; inset:0; z-index:39; background:var(--scrim);
    opacity:0; pointer-events:none;
    transition:opacity var(--med) var(--ease, ease);
  }
  .scrim[data-open="true"]{opacity:1; pointer-events:auto}

  .drawerbtn{
    display:inline-flex; align-items:center; gap:var(--s3);
    position:relative; background:none; border:0; cursor:pointer;
    color:var(--text); padding:var(--s3) var(--s4); border-radius:var(--r-ctl);
    font:inherit;
  }
  .drawerbtn:hover{background:var(--n4)}
  .drawerbtn:focus-visible{outline:2px solid var(--ring); outline-offset:1px}

  /* the count stolen from option A */
  .drawerbtn .waiting{
    min-width:1.35rem; padding:0 var(--s2); height:1.35rem;
    border-radius:var(--r-pill); background:var(--at9); color:var(--at-ink, #1a1400);
    font-size:var(--t-xs); font-weight:700; line-height:1.35rem;
    text-align:center; font-variant-numeric:tabular-nums;
  }
  .drawerbtn .waiting[hidden]{display:none}
}

@media (prefers-reduced-motion: reduce){
  .side, .scrim{transition:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   D6-B — REVIEW, THEN CONFIRM. Chosen 13 September 2026.

   An apply is two steps, always. Including a one-row batch: it still writes
   to a live ERP and a live till system, and the cost of the second step is
   one click. That trade-off was named in B's own cons and accepted.

   ⛔ STOLEN FROM D6-A: THE DIALOG OPENS FOCUSED ON CANCEL, at the top of the
   impact statement. B's honest weakness was "nothing forces the reading; a
   hurried person clicks through both steps" - and nothing can force reading.
   What this removes is the muscle-memory path where Return commits, because
   Return on an open dialog now cancels.

   Built on <dialog> so the browser supplies the focus trap, the top layer
   and Escape. Re-implementing those by hand is how a modal ends up with
   focus behind its own scrim.
   ═══════════════════════════════════════════════════════════════════════ */
.confirm{
  border:1px solid var(--line); border-radius:var(--r-over);
  background:var(--surface); color:var(--text); box-shadow:var(--lift);
  padding:0; max-width:min(38rem, 92vw); width:100%;
}
.confirm::backdrop{background:var(--scrim)}
.confirm header{
  padding:var(--s6) var(--s7); border-bottom:1px solid var(--line);
}
.confirm header h3{margin:0; font-size:var(--t-lg)}
.confirm header .sub{color:var(--text-muted); font-size:var(--t-sm)}
.confirm .body{padding:var(--s6) var(--s7); max-height:52vh; overflow:auto}

/* The impact statement. First thing in the dialog and first thing the
   focused Cancel button is next to - so the eye lands on what will happen
   before it lands on a way to make it happen. */
.confirm .impact{
  border-left:3px solid var(--at9); background:var(--n3);
  padding:var(--s5) var(--s6); border-radius:0 var(--r-ctl) var(--r-ctl) 0;
  margin:0 0 var(--s6);
}
.confirm .impact b{display:block; font-size:var(--t-lg); font-weight:650;
  font-variant-numeric:tabular-nums}
.confirm .impact span{color:var(--text-muted); font-size:var(--t-sm)}

.confirm dl{display:grid; grid-template-columns:auto 1fr; gap:var(--s3) var(--s6);
  margin:0}
.confirm dt{color:var(--text-muted); font-size:var(--t-sm)}
.confirm dd{margin:0; font-variant-numeric:tabular-nums}

.confirm footer{
  display:flex; justify-content:flex-end; gap:var(--s4);
  padding:var(--s5) var(--s7); border-top:1px solid var(--line);
  background:var(--n2);
}

/* ═══════════════════════════════════════════════════════════════════════
   D5-A — THE GROUPED ACCORDION. Chosen 13 September 2026.

   645 problems across 6 kinds, grouped by kind, ONE OPEN AT A TIME. The
   group header carries its own count so the size of the job stays visible
   while collapsed - which was A's whole argument.

   ⛔ STOLEN FROM D5-B: A DECIDED-COUNT, ALWAYS ON SCREEN. 645 is not one
   sitting. B's strength was "'3 of 6 decided' is always on screen, so
   stopping halfway is safe", and A had no answer to that. Without it,
   coming back to a half-judged list means working out where you were.

   The other steal is structural rather than visual: A's own trade-off was
   "a one-press verdict on 255 rows makes a wrong judgement 255 rows wrong",
   so a whole-group verdict routes through the D6 confirm step. See
   prGroupVerdict().
   ═══════════════════════════════════════════════════════════════════════ */
.pr-acc{display:flex; flex-direction:column; gap:var(--s4)}
.pr-grp{border:1px solid var(--line); border-radius:var(--r-card);
  background:var(--surface); overflow:hidden}
.pr-grp > h3{margin:0}
.pr-grp .hd{
  display:flex; align-items:center; gap:var(--s5); width:100%;
  padding:var(--s5) var(--s6); background:none; border:0; cursor:pointer;
  color:var(--text); font:inherit; text-align:left;
}
.pr-grp .hd:hover{background:var(--n3)}
.pr-grp .hd:focus-visible{outline:2px solid var(--ring); outline-offset:-2px}
.pr-grp .hd .caret{transition:transform var(--fast) var(--ease);
  color:var(--text-muted); display:inline-flex}
.pr-grp[data-open="true"] .hd .caret{transform:rotate(90deg)}
.pr-grp .hd .nm{font-weight:600}
.pr-grp .hd .ct{font-variant-numeric:tabular-nums; color:var(--text-muted);
  font-size:var(--t-sm)}
.pr-grp .hd .spacer{flex:1}
/* the decided mark, stolen from B */
.pr-grp .hd .done{font-size:var(--t-xs); font-variant-numeric:tabular-nums;
  padding:.1rem .5rem; border-radius:var(--r-pill); background:var(--n3);
  color:var(--text-muted)}
.pr-grp .hd .done.all{background:var(--ok3); color:var(--ok11)}
.pr-grp .panel{border-top:1px solid var(--line)}
.pr-grp[data-open="false"] .panel{display:none}

/* The decided-count for the WHOLE job, which is the thing B had and A did
   not. Sticky, because "stopping halfway is safe" only holds if you can see
   where halfway is without scrolling back up. */
.pr-progress{position:sticky; top:0; z-index:2;
  display:flex; align-items:center; gap:var(--s5);
  padding:var(--s4) var(--s6); background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-card);
  margin-bottom:var(--s5); font-size:var(--t-sm)}
.pr-progress b{font-variant-numeric:tabular-nums}
.pr-progress .bar{flex:1; height:6px; border-radius:99px; background:var(--n4);
  overflow:hidden}
.pr-progress .bar i{display:block; height:100%; background:var(--ok9);
  width:0; transition:width var(--med) var(--ease)}
