/* ============================================================================
   Blind Bench — the design foundation

   One stylesheet, two roles. The extractor's pages (extract.html) and the
   reviewer's pages (index.html, study.html) are the same product doing two
   jobs, so everything either of them needs to look like the other lives here
   and nowhere else. extract.css adds only what is genuinely the extractor's.

   The reference is docs/DESIGN.md §5 and the two mockups the researcher chose
   there — "focus", for the shell and the sense of a deck of papers, and
   "document", for the way the table is set. The mockup files themselves are
   gone, as §5 instructs; the decisions they settled are recorded here, beside
   the rules they produced.

   What makes those two calm is not a colour: it is that they say
   almost nothing, fill almost nothing, and let space do the separating. The
   rebuild before this one was rejected as "a very cluttered dashboard", and
   the dashboard was in the details — uppercase letterspaced micro-labels,
   filled header bands, striped rows, a border around every group of three
   things, five type sizes and four weights. All of that is gone from here, on
   purpose, and putting any of it back is a regression whatever it is called.

   The rules this file is held to (DESIGN §6):
     · 3 font sizes, 2 weights. There is no 500 anywhere.
     · one accent. Amber (--warn) means a real disagreement or a real warning,
       and it means nothing else.
     · no uppercase letterspaced micro-labels. See .caption.
     · no filled table header bands, no row striping.
     · a hairline where there is a real boundary, and space everywhere else.

   This file contributes 0 words of instructional prose to any screen: it has
   no `content` string that is a sentence. The 25-word budget is spent by the
   pages, and each page declares its own count.

   COLOUR: every value here is measured, and the ratios in the token block are
   the real numbers, against --bg, --surface and --sunken in both themes.

   Text clears 4.5:1 on all three surfaces. Anything that identifies a control
   or its state clears 3:1. --rule is deliberately under 2:1: it divides content,
   it is never the only thing saying a control is there — that is --edge, which
   exists for exactly that reason and clears 3:1 on all three surfaces. And
   nothing goes higher than 11.9:1, because eyes ache at maximum contrast and
   people sit in front of this for hours.

   Before adding a colour: compute it against all three surfaces in both
   themes, write the three numbers beside it the way the ones below are
   written, and drop it if it cannot clear 4.5 as text or 3 as a boundary. A
   number nobody wrote down is a number nobody checked, which is how the last
   palette ended up with a 1.5:1 label in it.
   ========================================================================== */

@font-face {
  font-family: "Inter";
  src: url("fonts/inter.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("fonts/geistmono.woff2") format("woff2");
  font-weight: 400 600;
  font-display: swap;
}

/* ============================================================================
   Tokens

   The nine surface and ink values are DESIGN §7 verbatim. Everything after
   them is derived, and carries the number it was measured at.
   ========================================================================== */

:root {
  color-scheme: light dark;

  /* --- the three surfaces -------------------------------------------------
     A raised thing has to be raised by an amount you can measure. The palette
     before this one put --surface #FFFFFF on --bg #FAF9F6, which is 1.05:1 —
     five percent — and a card five percent lighter than the page it sits on is
     not a card, it is a hairline with a fill behind it. The researcher said so
     in those words: "the background overlaps a bit with the elements, with the
     cards". So the step is now 1.19 to the page and 1.28 to the recessed pane
     the extractor's card actually sits on.
     The band is 1.17 to about 1.30 and it is bounded at both ends: under 1.17
     we are back where we started, and much over it the light page stops being
     a warm off-white and starts reading grey and dingy, which DESIGN §7 chose
     against. The rest of the lift is elevation — see --sh-raise. */
  --bg:      #EDEBE4;   /* warm off-white, never #fff — 1.19 under --surface */
  --surface: #FFFFFF;   /* the one pure value in the palette, and only ever a surface */
  --sunken:  #E6E3DA;   /* 1.28 under --surface, 1.08 under --bg — and it stays recessed */

  --ink:     #33383E;   /*  9.91 / 11.82 /  9.21 on bg / surface / sunken — never #000 */
  --ink-2:   #4C5259;   /*  6.62 /  7.90 /  6.16 */
  --ink-3:   #5F646C;   /*  4.99 /  5.96 /  4.64 — the floor of the system */
  --rule:    #BAB6AF;   /*  1.69 /  2.02 /  1.57 — a hairline, never an affordance */
  --accent:  #0C6973;   /*  5.36 /  6.39 /  4.98 */
  /* Darker than it looks as though it needs to be, and the reason is the one
     place amber sits on amber: the armed "remove arm" is --warn on --warn-wash,
     and when that column is the lit one the wash is over --sunken too. That
     stack measures 4.58 now. The amber before it measured 4.37 and failed. */
  --warn:    #834F00;   /*  5.71 /  6.81 /  5.31 */

  /* The edge of a control: an input's underline, a field's border, a radio's
     ring. WCAG 1.4.11 asks 3:1 of anything that says a control is there, and
     --rule at 2.02 at its strongest cannot carry that, so this is the only
     other neutral. */
  --edge:    #827F77;   /*  3.35 /  4.00 /  3.12 */

  /* Ink on a filled --accent. Not #fff. */
  --on-accent: var(--bg);   /* 5.36 on --accent */

  /* Two washes, and no more. A wash carries --ink or --ink-2 and never --ink-3:
     --ink-3 through the accent wash on --sunken is 4.11, which fails. It never
     carries --accent either — that pair is 4.41 on --sunken — and the only
     colour-on-its-own-wash in the product is the amber above, which is why the
     amber above is the shade it is. */
  --accent-wash: rgba(12, 105, 115, .09);  /* --ink through it: 8.74 / 10.39 / 8.16 */
  --warn-wash:   rgba(131, 79, 0, .11);    /* --ink through it: 8.50 / 10.05 / 7.94 */

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Three sizes. A fourth is a budget breach, not a convenience. */
  --fs-title: clamp(23px, 1.9vw, 27px);
  --fs:       15px;
  --fs-sm:    13px;

  /* One spacing scale, for every gap and every pad between things. Control
     metrics (a 28px button, a 15px icon) and optical nudges inside a single
     line are not gaps and are not on it. Everything else is, or it is wrong. */
  --s1: 4px;
  --s2: 8px;
  --s3: 14px;
  --s4: 22px;
  --s5: 34px;
  --s6: 52px;

  --r-sm: 6px;
  --r:    9px;
  --r-lg: 13px;

  /* --- elevation ----------------------------------------------------------
     Two steps and no third. --sh-raise is a surface resting on the page: the
     extractor's card, and anything else that is a real object at rest.
     --sh-pop is a surface genuinely floating over it: the sheet, the gate, the
     disclosure panel, the toast.

     Why both this and the wider surface step above: contrast alone is the
     blunt instrument. Push two neutrals far enough apart that the card is
     unmistakable on contrast alone and the page has gone grey. Half the lift
     is the step in lightness, half is the shadow, and neither has to shout.

     The tint is a warm near-black in light and a cold one in dark, and it is a
     shadow and nothing else — never text, never a fill. In dark a shadow is
     nearly invisible against a dark page, so a raised surface there leans on
     the lightness step plus a hairline along its top edge that catches the
     light; that highlight is carried inside the token, so a raised surface is
     one declaration in both themes and cannot be half-applied. */
  --sh-raise: 0 1px 2px rgba(32, 30, 24, .05), 0 8px 22px -12px rgba(32, 30, 24, .20);
  --sh-pop:   0 2px 4px rgba(32, 30, 24, .06), 0 20px 48px -18px rgba(32, 30, 24, .26);

  /* study.js reads --bar off the root to place its sticky toolbar. */
  --bar: 52px;

  /* --- shims -------------------------------------------------------------
     site/assets/extract.css is not this phase's file and still asks for these
     names. They are aliases onto the palette above so the extractor's page
     keeps rendering; delete them the moment extract.css is rewritten.
     The three call colours are gone as colours: a call's identity is its
     label, never its hue (DESIGN §6, one accent). --no-wash stays warm
     because its only remaining users are an invalid arm name and an armed
     delete, and both are real warnings. */
  --page:        var(--bg);
  --action:      var(--accent);
  --action-soft: var(--accent-wash);
  --rule-2:      var(--edge);
  --on-ink:      var(--on-accent);
  --r-xs:        var(--r-sm);
  --yes:         var(--accent);
  --no:          var(--ink-2);
  --maybe:       var(--ink-3);
  --yes-wash:    var(--accent-wash);
  --no-wash:     var(--warn-wash);
  --maybe-wash:  var(--sunken);
}

:root[data-theme="dark"] {
  /* The dark theme widened the other way round. In light the card is already
     pure white, so the only place to go was down and --bg had to darken; here
     --bg is the value DESIGN §7 chose to be "not a void" and taking it further
     down is exactly the void it was chosen against — so --bg is untouched and
     the card comes up instead. 1.22 to the page, which is what the extractor's
     card sits on in this theme (extract.css puts the dark work pane on --bg). */
  --bg:      #1A1D21;   /* not a void, and not moved */
  --surface: #292D33;   /* 1.22 over --bg */
  --sunken:  #22262B;   /* between the two: 1.10 under --surface, 1.11 over --bg */

  --ink:     #D3D7DC;   /* 11.70 /  9.57 / 10.52 on bg / surface / sunken — never #fff */
  --ink-2:   #AAB1B7;   /*  7.80 /  6.38 /  7.02 */
  --ink-3:   #91999F;   /*  5.85 /  4.79 /  5.26 */
  --rule:    #474D52;   /*  1.97 /  1.62 /  1.78 */
  --accent:  #5FBECB;   /*  7.82 /  6.40 /  7.04 */
  --warn:    #D3A141;   /*  7.20 /  5.89 /  6.48 */

  --edge:    #747A82;   /*  3.91 /  3.20 /  3.51 */

  --on-accent: var(--bg);   /* 7.82 on --accent */

  --accent-wash: rgba(95, 190, 203, .13);  /* --ink through it: 9.18 / 7.45 / 8.17 */
  --warn-wash:   rgba(211, 161, 65, .13);  /* --ink through it: 9.35 / 7.61 / 8.34 */

  --sh-raise: inset 0 1px 0 rgba(255, 255, 255, .055),
              0 1px 2px rgba(6, 7, 9, .45), 0 10px 26px -14px rgba(6, 7, 9, .6);
  --sh-pop:   inset 0 1px 0 rgba(255, 255, 255, .06),
              0 2px 6px rgba(6, 7, 9, .5), 0 22px 52px -18px rgba(6, 7, 9, .8);
}

/* ============================================================================
   Base
   ========================================================================== */

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.008em;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

/* Headings are on the same three sizes as everything else. A heading that
   needed its own size was a heading doing a label's job. */
h1 { font-size: var(--fs-title); font-weight: 600; letter-spacing: -0.024em; line-height: 1.2; margin: 0; }
h2, h3 { font-size: var(--fs); font-weight: 600; letter-spacing: -0.01em; line-height: 1.4; margin: 0; }

p { margin: 0; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
a { color: inherit; text-decoration: none; }
::selection { background: var(--accent-wash); color: var(--ink); }

/* --- focus ring ----------------------------------------------------------
   One ring, everywhere, visible on all three surfaces in both themes (accent
   is 4.98 at its worst). Never removed without putting something with at
   least 3:1 in its place — see .cell:focus. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ============================================================================
   Atoms
   ========================================================================== */

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.spacer { flex: 1; }
.icon { width: 15px; height: 15px; flex: none; stroke-width: 1.7; }

/* A quiet label. This replaces .eyebrow, which was 11px, weight 500, uppercase
   and letterspaced — the single loudest tell that a page is an admin panel.
   The transform and tracking are reset explicitly so nobody reintroduces them
   by adding a rule further down. Used by: the home section heads, the
   meta-analysis line above a paper's name, the label beside a field group.
   .eyebrow is the same rule under its old name, kept because 404.html still
   says it. index.html was rewritten and now says .caption. */
.caption, .eyebrow {
  display: inline-block;
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--ink-3);
  text-transform: none;
  letter-spacing: normal;
}

.fine { font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-3); }
.fine b { color: var(--ink-2); font-weight: 600; }

/* An empty field is drawn, never left as a gap (DESIGN §4). */
.na { color: var(--ink-3); }

/* Structure for a screen reader without adding a thing to look at. Stays
   focusable, so a visually hidden radio still takes the keyboard. */
.sr-only, .vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.count {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Progress, wordless. The fill reads against its own track at 3.17 (light) and
   3.96 (dark), so the bar is legible as a bar and not only as a colour. */
.track { height: 3px; border-radius: 3px; background: var(--rule); overflow: hidden; }
.track span {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width .45s cubic-bezier(.2, .7, .3, 1);
}

kbd {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  padding: 1px 5px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  color: var(--ink-3);
}

/* ============================================================================
   PRIMITIVES

   Everything below this line is shared by both roles. One line per primitive
   saying which screens use it, so the two pages cannot quietly drift apart.
   ========================================================================== */

/* --- buttons -------------------------------------------------------------
   Every screen. .btn is the outlined default; .primary is the one forward
   action on a screen and the only filled thing in the interface; .quiet is a
   bar control with no box at all until you touch it. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  height: 32px;
  padding: 0 var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: transparent;
  font-size: var(--fs);
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: background .13s ease, border-color .13s ease, color .13s ease, opacity .13s ease;
}
/* A class that sets `display` outranks the browser's own `[hidden] { display: none }`, so a button
   given the hidden attribute stays on the screen. The same trap is already written out for .sync
   and .sheet; it belongs here too, because every button in the product is defined in this rule. */
.btn[hidden] { display: none; }
.btn:hover { background: var(--sunken); }
.btn:active:not([disabled]) { transform: translateY(.5px); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}
.btn.primary:hover { background: var(--accent); opacity: .88; }

.btn.quiet {
  border-color: transparent;
  color: var(--ink-2);
}
.btn.quiet:hover { background: var(--sunken); color: var(--ink); }

.btn.sm { height: 28px; padding: 0 var(--s2); font-size: var(--fs-sm); gap: var(--s1); }

/* The smallest quiet button there is: a boxless toggle that says which of a
   couple of things is showing. The extractor's file tabs (main / supplementary)
   are its only user; it lives here rather than in extract.css because it is a
   button and every button in the product is defined in one place. */
.mini {
  min-height: 26px;
  padding: var(--s1) var(--s2);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font-size: var(--fs-sm);
  color: var(--ink-3);
  cursor: pointer;
  white-space: nowrap;
}
.mini:hover { background: var(--sunken); color: var(--ink-2); }
.mini[aria-pressed="true"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--accent);
  border-radius: var(--r-sm) var(--r-sm) 2px 2px;
}

/* A link inside running text. The accent's other legitimate job. */
.linkish {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
}
.linkish:hover { text-decoration: underline; }

/* --- segmented single choice ---------------------------------------------
   Extractor: in / out / not sure. Reviewer: right / wrong / not sure, and the
   step-2 view switches. One selected option, marked by a 2px accent underline
   (5.48 against the page) and by --ink text. It is text, not a pill: a pill is
   a dashboard tell and pills next to pills are how a page starts to look like
   a control panel.

   Markup, either shape:
     <div class="seg" role="radiogroup">
       <button role="radio" aria-checked="true">In</button> …
     <div class="seg">
       <input type="radio" class="vh" id="x"><label for="x">In</label> …

   .segbar, .call and .confidence are the same control under the names study.js
   already uses. */

.seg, .segbar, .call, .confidence {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  flex-wrap: wrap;
}
/* Stacked, for the calls at the foot of a column in .fields. */
.seg.stack { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }

.seg > button, .seg > label,
.segbar button, .call button, .confidence button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  min-height: 28px;
  padding: var(--s1) var(--s2);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font-size: var(--fs);
  font-weight: 400;
  color: var(--ink-3);
  cursor: pointer;
  white-space: nowrap;
  transition: background .13s ease, color .13s ease, box-shadow .13s ease;
}
.seg > button:hover, .seg > label:hover,
.segbar button:hover, .call button:hover:not([disabled]), .confidence button:hover {
  background: var(--sunken);
  color: var(--ink-2);
}
.seg > button[aria-checked="true"], .seg > button[aria-pressed="true"],
.seg > input:checked + label,
.segbar button[aria-pressed="true"], .segbar button[aria-checked="true"],
.call button[aria-checked="true"], .confidence button[aria-checked="true"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--accent);
  border-radius: var(--r-sm) var(--r-sm) 2px 2px;
}
.seg > input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 2px; }
.call button[disabled] { opacity: .5; cursor: not-allowed; }
/* Five steps and the two words that scale them are one control, so they have to
   share a width: inline-flex left the digits bunched at one end of a caption
   that ran the whole way across. */
.confidence { display: flex; }
.confidence button { flex: 1; font-family: var(--mono); }
.confidence-scale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--s2);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
@media (forced-colors: active) {
  .seg > button[aria-checked="true"], .seg > button[aria-pressed="true"],
  .segbar button[aria-pressed="true"], .call button[aria-checked="true"],
  .confidence button[aria-checked="true"] { border-bottom: 2px solid Highlight; }
}

/* A single icon-sized choice: right / wrong / not sure at the end of a row.
   All three look identical when chosen — the icon says which, so colour is
   never the only carrier of the meaning (DESIGN §7). */
.tick {
  width: 30px; height: 28px;
  display: inline-grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: background .12s ease, color .12s ease, box-shadow .12s ease;
}
.tick:hover { background: var(--sunken); color: var(--ink); }
.tick:active { transform: translateY(.5px); }
.tick[aria-checked="true"] {
  color: var(--accent);
  box-shadow: inset 0 -2px 0 var(--accent);
  border-radius: var(--r-sm) var(--r-sm) 2px 2px;
}
@media (forced-colors: active) {
  .tick[aria-checked="true"] { border-bottom: 2px solid Highlight; }
}

/* --- the transposed grid, DESIGN §2 --------------------------------------
   Fields are rows. Arms are columns. Eleven fields fit in a narrow pane where
   eleven columns never could, the label gets the whole width of a column so it
   can be written out, and the shape matches the table in the paper being
   copied from — which is the best thing an interface can do for transcription
   accuracy.

   Extractor (extract-study.html): one column per arm, cells are .cell inputs,
   adding an arm adds a column.
   Reviewer (study.html): one column per extraction, cells are text, and the
   calls sit in tfoot at the foot of each column.

   Set like a table in a journal article: one rule under the head and nothing
   else. No vertical rules, no row rules, no zebra, no filled band. */

table.fields {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.fields thead th {
  font-size: var(--fs);
  font-weight: 600;
  color: var(--ink);
  text-align: right;
  vertical-align: bottom;
  padding: 0 var(--s2) var(--s2);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
/* The field-label column has no heading: the rows name themselves. */
.fields thead th:first-child { text-align: left; padding-left: 0; font-weight: 400; color: var(--ink-3); }

.fields tbody th {
  position: relative;
  font-size: var(--fs);
  font-weight: 400;
  color: var(--ink-2);
  text-align: left;
  padding: var(--s2) var(--s3) var(--s2) 0;
}
.fields tbody td {
  padding: var(--s1) var(--s2);
  font-family: var(--mono);
  font-size: var(--fs);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink);
}
.fields tbody tr:first-child th,
.fields tbody tr:first-child td { padding-top: var(--s3); }

/* The one warm thing on the reviewer's screen: this row is one the three
   extractions do not agree on, and it is the row they must not walk past. It
   is marked on both the label and the values, so it points at the row without
   hinting at a winner. */
.fields tr.differs th { color: var(--ink); box-shadow: inset 2px 0 0 var(--warn); padding-left: var(--s2); }
.fields tr.differs td { background: var(--warn-wash); }
/* --ink-3 through a wash is 4.28 at worst and would fail, so anything quiet
   inside a washed row steps up one. */
.fields tr.differs .na { color: var(--ink-2); }
@media (forced-colors: active) {
  .fields tr.differs th { box-shadow: none; border-left: 2px solid Highlight; }
  .fields tr.differs td { background: none; border-bottom: 2px solid Highlight; }
}

.fields tfoot td { padding: var(--s3) var(--s1) 0; vertical-align: top; }

/* --- the reviewer's half of it: .fields.cmp ------------------------------
   Same table, three extractions instead of arms, and a right / wrong / not
   sure at the foot of each column. Only study.html sets .cmp, so nothing here
   can reach the extractor's grid. */

/* Eleven fields and three columns have to sit in one pane beside an open PDF,
   so the reviewer's rows are tighter than the extractor's — nothing is being
   typed into them, and a cell that is only read does not need a cell's height. */
.fields.cmp tbody th,
.fields.cmp tbody td { padding-top: var(--s1); padding-bottom: var(--s1); }
/* The amber bar on a contested row needs somewhere to be, and it has to be
   somewhere that does not move the label: a column of field names that steps
   in and out by eight pixels as you read down it is a column you cannot read
   down. Every row is inset, so only the bar appears. */
.fields.cmp tbody th { padding-left: var(--s2); }

/* Which extraction the keyboard is on. The column is bracketed: a rule under
   its head and a rule over its calls, in the accent, and nothing washed in
   between — a wash down the column would sit on top of the amber of every row
   they disagree on, and two washes over one cell is a colour neither meant. */
.fields.cmp thead th[aria-current="true"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--accent);
}
.fields.cmp tfoot td[data-active="true"] { box-shadow: inset 0 2px 0 var(--accent); }
@media (forced-colors: active) {
  .fields.cmp thead th[aria-current="true"] { box-shadow: none; border-bottom: 2px solid Highlight; }
  .fields.cmp tfoot td[data-active="true"] { box-shadow: none; border-top: 2px solid Highlight; }
}

/* The call is three words stacked at the foot of their own column, right
   ranged so they hang off the same edge as the eleven numbers above them. */
.judge-col { text-align: right; }
.judge-col .seg.stack > button { justify-content: flex-end; }
/* Once the call is made, the two answers not taken step back — the "document" mockup, and the
   reason it gives is the count: three calls times three extractions times two arms is eighteen
   buttons standing at rest on a page whose job is reading eleven numbers. They return on hover and
   on focus, so changing your mind is one movement and one Tab. They are controls and not data, so
   DESIGN §1 allows it; every value on the page is still on the page. */
.judge-col .seg.stack:has([aria-checked="true"]) > button[aria-checked="false"] {
  opacity: 0;
  transition: opacity .13s ease;
}
/* :has() carries the specificity of its argument, so the rule that brings them back has to be at
   least as specific as the one that took them away, or the fade wins over its own escape hatch and
   a mouse can never change a call again. The hover target is the whole cell, not the three words. */
.judge-col:hover .seg.stack:has([aria-checked="true"]) > button,
.judge-col .seg.stack:has([aria-checked="true"]):focus-within > button { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .judge-col .seg.stack:has([aria-checked="true"]) > button[aria-checked="false"] { transition: none; }
}
@media (forced-colors: active) {
  .judge-col .seg.stack:has([aria-checked="true"]) > button[aria-checked="false"] { opacity: 1; }
}
/* The escape hatch above is :hover and :focus-within, and a touch screen has neither. On a tablet
   the two answers not taken faded out and there was no gesture that brought them back — a call,
   once made, could not be changed at all. The reason they fade is the count of buttons standing at
   rest on a reading screen, which is a pointer-and-a-big-display problem; where there is no
   pointer, it is not one. */
@media (hover: none) {
  .judge-col .seg.stack:has([aria-checked="true"]) > button[aria-checked="false"] { opacity: 1; }
}
/* An extraction that produced no row for this arm. There is no call to make,
   and saying so once here is why the eleven fields above are not all amber. */
.judge-col .na { display: inline-block; padding: var(--s1) var(--s2); }

/* A row header the d key has landed on. It is a scroll target, not a control,
   so it gets the ring and nothing else. */
.fields tbody th:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.fields tbody th { scroll-margin: 96px 0 120px; }

/* --- an inline field inside a table cell ---------------------------------
   The extractor's grid, and the arm name in a column head. Borderless, so a
   table of eleven of them still reads as a table and not as a form. The
   hairline underneath is --edge and not --rule, because it is the only thing
   saying "type here" and that has to clear 3:1. */

.cell {
  width: 100%;
  min-width: 0;
  padding: 6px var(--s2);
  border: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: transparent;
  box-shadow: inset 0 -1px 0 var(--edge);
  font-family: var(--mono);
  font-size: var(--fs);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink);
  transition: background .12s ease, box-shadow .12s ease;
}
/* An arm's name is a word, not a number. */
.cell.text { font-family: var(--sans); text-align: left; letter-spacing: inherit; }
/* The em dash that says "the paper does not report this" is content, not
   decoration, so it is held to 4.5 like any other text — and it steps up to
   --ink-2 on focus, because --ink-3 through the accent wash is 3.72. */
/* The note's placeholder is held to the same 4.5, or it is left at the browser
   default, which measures 3.38 on --surface in dark — the case a light-only
   check never sees. */
.cell::placeholder, textarea.note::placeholder { color: var(--ink-3); opacity: 1; }
/* The same step, for the same reason, on the other wash a cell can be wearing.
   A flagged arm name is warn-washed while the caret is somewhere else in its
   column, which puts the wash over --sunken too: --ink-3 through that is 4.00
   and 3.80, and the placeholder is the only thing in the cell to read. */
.cell:focus::placeholder,
.cell[aria-invalid="true"]::placeholder { color: var(--ink-2); }
.cell:hover { background: var(--sunken); }
/* The ring is inside the cell so the grid does not jump by two pixels as you
   tab across it, and the wash says which column the caret is in — in a run of
   identical boxes that is how a value ends up one field out. */
.cell:focus {
  outline: 0;
  background: var(--accent-wash);
  box-shadow: inset 0 -2px 0 var(--accent);
}
/* An arm with numbers and no name, or a name another arm already has. Focus
   wins, so the marking is never on top of the ring while it is being fixed. */
.cell[aria-invalid="true"]:not(:focus) {
  background: var(--warn-wash);
  box-shadow: inset 0 -2px 0 var(--warn);
}
@media (forced-colors: active) {
  .cell:focus { box-shadow: none; outline: 2px solid Highlight; outline-offset: -2px; }
  .cell[aria-invalid="true"]:not(:focus) { box-shadow: none; border-bottom: 2px solid Mark; }
}

/* --- the shell -----------------------------------------------------------
   study.html and extract-study.html. One bar, then the paper on the left and
   the work on the right, with a divider the reader can drag. study.js and
   extract.js both write --pdf-w onto #split, and both toggle .collapsed. */

.bar, .topbar, .ws-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--s3);
  height: var(--bar);
  padding: 0 var(--s4);
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

/* The workspace bar's contents are words and letters, and words do not shrink. Left to itself a
   flex row of them simply grows past the window and takes the whole page's horizontal scrollbar
   with it — which on these two screens is not cosmetic: the split's first track is a percentage of
   a viewport that has just become wider than the screen, so the paper grows with the overflow it
   caused. Nothing may leave the bar; the media queries at the foot of this file decide what stops
   being drawn, least useful first.

   Only .ws-bar. The .topbar on the two list pages carries a disclosure that opens downwards out of
   it, and clipping is exactly what must not happen to that. */
.ws-bar {
  padding: 0 var(--s3);
  gap: var(--s2);
  overflow: hidden;
}
/* Nothing in it may refuse to shrink. Each of these has its own way of coping once it is allowed
   to — .back ellipsises, .index scrolls — and a flex item's default min-width:auto is what stops
   any of them getting the chance. */
.ws-bar > * { min-width: 0; }

.brand {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs);
  font-weight: 600;
  letter-spacing: -0.015em;
}
.brand .glyph {
  width: 15px; height: 15px;
  flex: none;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  background: linear-gradient(90deg, transparent 50%, currentColor 50%);
}

.who {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
/* The code is spelled out immediately to its right, so the initialled disc was
   the same information twice in a bar that DESIGN §6 caps at five controls. */
.who .avatar { display: none; }

.ws-title { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
.ws-title strong { font-size: var(--fs); font-weight: 600; letter-spacing: -0.015em; }
.ws-title span {
  font-size: var(--fs-sm);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.steps { display: flex; align-items: center; gap: var(--s1); }
.step {
  display: flex;
  align-items: center;
  gap: var(--s1);
  height: 30px;
  padding: 0 var(--s2);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-3);
  font-size: var(--fs);
  /* The step name must not wrap: a two-line label breaks the bar's height and
     leaves the underline under half a word. */
  white-space: nowrap;
  cursor: pointer;
  transition: background .13s ease, color .13s ease, box-shadow .13s ease;
}
.step:hover:not([aria-disabled="true"]) { background: var(--sunken); color: var(--ink-2); }
.step[aria-current="true"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--accent);
  border-radius: var(--r-sm) var(--r-sm) 2px 2px;
}
.step[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.step .n { font-family: var(--mono); font-size: var(--fs-sm); color: var(--ink-3); }
.step.done .n { color: var(--ink-2); }
.step-sep { width: var(--s2); height: 1px; background: var(--rule); flex: none; }

/* --- the two panes -------------------------------------------------------
   THE PANEL DECLARES A FLOOR, THE PAPER DECLARES NONE. That asymmetry is the
   whole of the responsive behaviour, and it is why there is no JavaScript in it.

   This used to be `var(--pdf-w, 46%) 1px 1fr`, where --pdf-w was a pixel width
   the divider wrote and localStorage remembered. A pixel width is only ever
   correct for the window it was measured in: drag the divider out on a 1600px
   screen, then halve the window — or dock it, or open devtools, or plug into a
   smaller display — and the first track keeps its 1160px while `1fr` resolves
   to what is left, which is nothing. The work pane, which is the entire data
   entry surface, disappeared. Reloading fixed it, because the width was
   re-clamped on boot and never again; nobody could be expected to guess that.

   Now the third track carries `minmax(min(--panel-min, 100%), 1fr)` and the
   first carries `minmax(0, …)`. When the window is too narrow for both, the
   grid algorithm takes the space off a track that may shrink to zero before it
   takes any off one that may not, so the paper narrows and the panel is
   untouchable. --pdf-w is a percentage now, so it also means the same thing on
   every display instead of the one it was set on.

   min(…, 100%) rather than a bare --panel-min: under 400px the floor would
   overflow the grid and put a horizontal scrollbar under the whole page. */
/* THE WORKSPACE OWNS THE VIEWPORT, so the document behind it must not scroll at all.

   .split is exactly one screen minus the bar, so on paper there is nothing for the window to
   scroll — but "nothing to scroll" is not the same as "cannot scroll", and any element that
   overshoots by a pixel, any rubber-band handed up from a pane, any sticky foot, gives the window
   a second scrollbar that fights the pane's own. Two scrollable things under one gesture is the
   whole of the confusion.

   :has(> .split) rather than a class, because it is precisely the two workspace pages that have
   one — study.html and extract-study.html — and neither list page does, where the window scrolling
   IS the design. Scoped above the breakpoint for the same reason: below it the two panes stack and
   the window is meant to be the scroller again. */
@media (min-width: 821px) {
  html:has(> body > .split), body:has(> .split) { overflow: hidden; }
}

.split {
  display: grid;
  --panel-min: 400px;
  /* --pdf-w is what the divider set and storage remembers; --pdf-open is what
     the role opens at before anyone has dragged anything. A role that wants a
     wider paper sets --pdf-open and nothing else — it must never restate the
     template, which is how extract.css came to own a copy of this rule that
     then outranked the narrow-screen override further down this file. */
  grid-template-columns:
    minmax(0, var(--pdf-w, var(--pdf-open, 46%)))
    1px
    minmax(min(var(--panel-min), 100%), 1fr);
  height: calc(100vh - var(--bar));
  height: calc(100dvh - var(--bar));
}
.split.collapsed { grid-template-columns: 0 1px minmax(0, 1fr); }

/* The line between the panes is a boundary and also a control — it is focusable,
   it answers the arrow keys and it drags — so it is drawn in --edge (3.3:1) and
   not in --rule, which this file calls a hairline and never an affordance. It
   takes the accent the moment the pointer or the focus lands on it. */
.divider {
  background: var(--edge);
  cursor: col-resize;
  position: relative;
  transition: background .15s ease;
}
.divider::after { content: ""; position: absolute; inset: 0 -5px; }
.divider:hover, .divider:focus-visible, .divider.dragging { background: var(--accent); }
.split.collapsed .divider { cursor: default; }

/* The paper viewer is the only filled area in the product, because a viewer is
   a real object with a real edge. */
.reader {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--sunken);
  overflow: hidden;
}
.reader-head {
  flex: none;
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--rule);
}
.reader-head .line { display: flex; align-items: center; gap: var(--s2); }
.reader-head .now {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reader-head .now small { color: var(--ink-3); margin-left: var(--s1); }
.reader iframe { flex: 1; width: 100%; border: 0; background: var(--sunken); }
.reader-empty {
  flex: 1;
  display: grid;
  place-items: center;
  padding: var(--s5);
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-sm);
}

/* Every paper in the folder, always in reach. The bar under a letter says the
   paper has been decided; which way it was decided is written out in full in
   the list below, so this does not need three colours to say it and DESIGN §6
   does not give it three to spend. */
.index { display: flex; gap: var(--s1); margin-top: var(--s2); flex-wrap: wrap; }
.index button {
  position: relative;
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.index button:hover { background: var(--surface); color: var(--ink); }
.index button:active { transform: translateY(.5px); }
.index button[aria-current="true"] { background: var(--accent); color: var(--on-accent); }
.index button[data-call]::after {
  content: "";
  position: absolute;
  bottom: 2px; left: 50%;
  transform: translateX(-50%);
  width: 12px; height: 2px;
  border-radius: 2px;
  background: var(--ink-2);
}
.index button[aria-current="true"][data-call]::after { background: var(--on-accent); }
@media (forced-colors: active) {
  .index button[data-call]::after { background: CanvasText; }
}

/* The work pane is a container, not just a column: the divider can squeeze it
   to 440px on a wide screen, so what is inside answers to the pane's width and
   not the window's. */
.work {
  overflow-y: auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  container-type: inline-size;
  container-name: pane;
  /* THE SCROLL STOPS HERE. Without this a wheel that reaches the end of this pane does not stop:
     the browser hands the rest of the gesture to the ancestors, and the page rubber-bands under a
     workspace that is supposed to be exactly one screen tall. Reading down a column of numbers and
     having the whole screen give way at the bottom of it is what the researcher called "un casino",
     and the impression it leaves — that the scroll goes on for ever — is literally what chaining
     does: the gesture keeps being consumed by something.

     `contain` and not `none`: the pane keeps its own end-of-scroll bounce, which is the feedback
     that says "this is the bottom". Only the handover to anything outside is refused. */
  overscroll-behavior: contain;
}
.work-inner {
  --pane-pad-x: var(--s5);
  --pane-pad-y: var(--s5);
  max-width: 820px;
  width: 100%;
  /* Centred, so closing the paper does not leave the document hugging the left
     edge of a 1280px pane with 460px of nothing beside it. */
  margin-inline: auto;
  padding: var(--pane-pad-y) var(--pane-pad-x) var(--s6);
  flex: 1;
}

/* --- a quiet disclosure --------------------------------------------------
   For help and only for help: the brief, the protocol, keyboard hints, the
   note. Never for data — DESIGN §1 forbids a control that hides a value.
   .disc is the inline one; .sheet is the panel the bar summons on both roles'
   workspaces. */

.disc { position: relative; }
.disc > summary { list-style: none; cursor: pointer; }
.disc > summary::-webkit-details-marker { display: none; }
.disc[open] > summary { color: var(--ink); }
/* .disc-panel, not .panel: study.js and extract.js already use .panel for a
   section of the working document, and two things with one name is how the two
   roles' pages start to disagree about what a class means. */
.disc-panel {
  position: absolute;
  top: calc(100% + var(--s2));
  right: 0;
  z-index: 10;
  width: min(340px, 80vw);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--sh-pop);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-2);
}
.disc-panel p + p { margin-top: var(--s2); }
.disc-panel b { font-weight: 600; color: var(--ink); }

/* The same disclosure where there is no bar to hang it under: it opens in the
   flow instead of floating over it, so nothing on the home page moves out from
   under the reader's cursor. */
.disc.inline { margin-top: var(--s4); }
.disc.inline > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) 0;
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.disc.inline > summary:hover { color: var(--ink-2); }
.disc.inline > summary svg { color: currentColor; }
.disc.inline .disc-panel {
  position: static;
  width: auto;
  max-width: 62ch;
  margin-top: var(--s2);
  padding: var(--s3) var(--s4);
  box-shadow: none;
  background: var(--sunken);
  border-color: transparent;
}

.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--ink) 22%, transparent);
  animation: fade .2s ease;
}
.sheet-backdrop[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } }

.sheet {
  position: fixed;
  inset: 0 0 0 auto;
  z-index: 61;
  width: min(520px, 100%);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-left: 1px solid var(--rule);
  box-shadow: var(--sh-pop);
  animation: slide .26s cubic-bezier(.2, .8, .25, 1);
}
/* .sheet sets display, so the hidden attribute has to be given its meaning back. */
.sheet[hidden] { display: none; }
@keyframes slide { from { transform: translateX(24px); opacity: 0; } }

.sheet > header {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s3) var(--s3) var(--s4);
  border-bottom: 1px solid var(--rule);
}
.sheet-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: var(--s4); }
.sheet-foot {
  flex: none;
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}

/* --- the sync indicator --------------------------------------------------
   The top bar on every screen of both roles. Success is silence; only a real
   failure earns a colour, and it earns the warning colour because that is what
   it is. */
.sync {
  display: flex;
  align-items: center;
  gap: var(--s1);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  white-space: nowrap;
}
.sync .led {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.sync[data-state="saving"] .led { animation: pulse 1.1s ease infinite; }
/* Both are real warnings and both earn the warning colour. They say different things because the
   remedy is different — see `rejected` in core.js — but neither is a state to read past. */
.sync[data-state="failed"],
.sync[data-state="rejected"] { color: var(--warn); }
@keyframes pulse { 50% { opacity: .25; } }

/* --- the toast -----------------------------------------------------------
   Every screen. Says one thing and leaves. */
.toast {
  position: fixed;
  left: 50%;
  bottom: var(--s5);
  transform: translate(-50%, 16px);
  z-index: 80;
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  border-radius: var(--r);
  background: var(--ink);
  color: var(--bg);          /*  9.91 light / 11.70 dark */
  font-size: var(--fs-sm);
  box-shadow: var(--sh-pop);
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s ease, transform .22s cubic-bezier(.2, .8, .25, 1);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* --- a block of the document ---------------------------------------------
   .card is not a card any more. It was a bordered, shadowed, rounded box, and
   a page of them is the dashboard the researcher rejected. It is now a block
   with space above it and one hairline, which is all a boundary ever needs. */
.card {
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
}

/* --- the raised surfaces -------------------------------------------------
   Four things in the product are real objects: the extractor's card, the sheet,
   the gate and the disclosure panel. Each gets a surface, a hairline and one of
   the two elevation tokens, and nothing else in either role's page gets either.
   A page of shadowed boxes is the dashboard that was rejected; a card with no
   lift at all is what the researcher has just rejected. The list stays short.

   The card rests, so it takes --sh-raise. The other three float over the page
   and take --sh-pop, declared where each of them is.

   .papercard belongs to extract.css, which is not this phase's file. Its one
   declaration is here because elevation is a token and the tokens live here: a
   box-shadow written out by hand over there is how the two roles' pages start
   to lift by different amounts. extract.css sets no box-shadow on .papercard,
   so nothing is being overridden; move this line across the moment that file is
   next opened. */
.papercard { box-shadow: var(--sh-raise); }

.gate-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--ink) 22%, transparent);
}
.gate {
  width: min(400px, 100%);
  padding: var(--s5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-pop);
  animation: rise .3s cubic-bezier(.2, .8, .25, 1);
}
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
.gate h2 { margin: var(--s3) 0 var(--s2); }
.gate p { margin-bottom: var(--s4); color: var(--ink-2); font-size: var(--fs-sm); line-height: 1.6; }
.gate label { display: block; margin-bottom: var(--s1); font-size: var(--fs-sm); color: var(--ink-3); }
.gate input {
  width: 100%;
  height: 38px;
  padding: 0 var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: var(--bg);
  margin-bottom: var(--s3);
}
.gate input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}
.gate .btn { width: 100%; height: 38px; }

/* ============================================================================
   The list of meta-analyses — index.html and extract.html

   One component, two roles: the reviewer's rows can be locked, the extractor's
   never are. Everything else about them is identical, on purpose, because one
   person may hold both roles and the two must not feel like two products.

   A list, not a grid of tiles. Ten cards in a masonry with a border and a
   shadow each is the "cluttered dashboard" verdict in one screenshot; ten
   lines separated by a hairline is a table of contents, which is what it is.

   The version before this one was already a list and still failed, and the
   researcher said why: "you cannot quite tell which study is which — the
   separation between one study and another is not entirely clear". It stacked
   number, title and detail in one left-aligned column, so ten rows read as one
   grey paragraph and the hairlines between them did nothing. What fixes that is
   not more chrome, it is columns and room:

     · the number lives in a gutter of its own, mono and tabular, so ten of them
       line up into something the eye can run down. The researcher calls these
       by number — "Kommu 3", "Maliha 9" — so the number is the anchor.
     · the outcome is the title, and nothing in the row is set larger or heavier.
       Never the team: Kommu and Maliha each published two meta-analyses off one
       folder of papers, so a list led by team name prints the same word twice.
     · the state is a column at the right, the same shape on every row.
     · a row is 22px of air top and bottom. Density is not the goal here; ten
       rows on a 900px page is not a scrolling problem.
   ========================================================================== */

.page { max-width: 860px; margin: 0 auto; padding: var(--s6) var(--s4) 120px; }

.hero { margin-bottom: var(--s5); max-width: 60ch; }
.hero h1 { margin: 0 0 var(--s3); text-wrap: balance; }
.hero h1 em { font-style: normal; }

/* A guard rail, and only ever a guard rail: the screening that has been closed,
   the step that has had to start again. It is one line, it is never advice, and
   there is none of it on a screen where nothing has gone wrong. (.hero-note went
   with the forty-five-word blinding note that used to sit under the home page's
   heading; it is a disclosure now — see home.js.) */
.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
  margin: var(--s4) 0;
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-2);
}
.notice svg { flex: none; margin-top: 3px; color: var(--ink-3); }
.notice b { color: var(--ink); font-weight: 600; }
.notice > div { flex: 1; }
.notice .btn { flex: none; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
/* index.html has no heading to put beside its count, and a lone caption on the
   left is an orphan. Pushed right it sits over the state column instead, and
   both landing pages end up with the same tally in the same place. */
.section-head.end { justify-content: flex-end; }

.ma-list { display: flex; flex-direction: column; }

/* Three columns and no more: the number, the study, its state. The negative
   inline margin is what makes the whole row the target — hairline and hover
   wash run wider than the words, so a row reads as one object and the pointer
   never has to find the text inside it. */
.ma-row {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) 138px;
  column-gap: var(--s3);
  align-items: start;
  padding: var(--s4) var(--s3);
  margin-inline: calc(var(--s3) * -1);
  border-top: 1px solid var(--rule);
  text-align: left;
  color: inherit;
}
.ma-row:last-child { border-bottom: 1px solid var(--rule); }

/* The anchor of the row. Mono and tabular so ten of them stack into a column,
   --ink-2 so it stays quiet, and the same size as the title so it can never
   compete with it — position and face make it unmissable, not weight. */
.ma-n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  line-height: 1.4;
}

.ma-title { text-wrap: balance; transition: color .13s ease; }
.ma-meta {
  margin-top: 2px;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--ink-3);
}
/* The separator between "Kommu" and "9 papers". It takes the line's own --ink-3
   and is never dimmed to --rule: it is text, so it is held to 4.5 like any other
   text and --rule is 1.69. home.js and extract.js hide it from the screen
   reader, which was reading one out between every pair. */
.ma-meta .sep { padding: 0 .4em; }
/* Kommu and Maliha each measured two outcomes off one folder of papers. Say
   so, or the reviewer opens the second one and wonders why they are reading the
   same PDFs again. */
.ma-twin { white-space: nowrap; }

/* The state column. Right-aligned and the same shape on every row, so the eye
   can run straight down it: one short line, and under it a track wherever there
   is progress to show. */
.ma-state {
  text-align: right;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--ink-3);
  /* "Not ready for review yet" is the longest thing this column ever holds and it takes two
     lines. Balanced, it breaks in the middle; left alone it drops "yet" onto a line of its own,
     which reads as a mistake on nine rows out of ten. */
  text-wrap: balance;
}
.ma-state .track { margin-top: var(--s2); }
/* Filed is the one state worth spotting from across the list, so it carries a
   mark as well as a word — the word is what says it, the accent only helps you
   find it. */
.ma-state .done {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  color: var(--ink-2);
}
.ma-state .done .icon { color: var(--accent); }

/* The whole row is the target, and the wash is what says so. Not a border and
   not a lift: this page had its boxes taken away for a reason, and a fill is the
   only thing that reads as "this row" across 860px without putting one back.
   --sunken recesses in light and lifts in dark; both say the same thing. */
.ma-row[href] { cursor: pointer; }
.ma-row[href]:hover { background: var(--sunken); }
.ma-row[href]:hover .ma-title { color: var(--accent); }

/* A locked row has nothing behind it, so it is not a control: no pointer, no
   wash, and home.js makes it a div rather than a link so the keyboard walks
   past it. Quieter, and still readable — --ink-3 clears 4.5 on all three
   surfaces, which is the whole reason locked rows are not dimmed with opacity. */
.ma-row.locked .ma-n,
.ma-row.locked .ma-title { color: var(--ink-3); }

/* Phones only. The state column cannot hold "Not ready for review yet" beside a
   title once the page is narrower than the reading measure, so it drops under
   the title and keeps the number's gutter. */
@media (max-width: 560px) {
  .ma-row { grid-template-columns: 30px minmax(0, 1fr); }
  .ma-state { grid-column: 2; text-align: left; margin-top: var(--s2); }
}

/* ============================================================================
   The workspace
   ========================================================================== */

.panel { margin-bottom: var(--s5); }
.panel > header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
/* There was a .lede here: the paragraph under every heading that explained what
   the reviewer was about to do. Three of them were most of the prose the last
   attempt was rejected for. It is gone rather than unused, so that reaching for
   it means writing it again and noticing. */

/* --- the protocol and the brief ------------------------------------------
   The same body serves the block in step 1 and the sheet the bar summons.
   study.js and extract.js both write class="card protocol"; .protocol has no
   rule of its own on purpose, because .card now carries everything it used to
   override. Grep for it and find this comment rather than nothing. */

.protocol-body dl { margin: 0; }
.protocol-body .field {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--rule);
}
.protocol-body .field:last-child { border-bottom: 0; }
/* Sentence case. This was 11px, weight 500, uppercase, letterspaced. */
.protocol-body dt { font-size: var(--fs-sm); color: var(--ink-3); }
.protocol-body dd { margin: 0; line-height: 1.6; }

.brief {
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-2);
}
.brief .caption, .brief .eyebrow { display: block; margin-bottom: var(--s1); }
.brief details { margin-top: var(--s2); }
.brief summary { cursor: pointer; color: var(--ink-3); list-style: none; }
.brief summary::-webkit-details-marker { display: none; }
.brief summary::before { content: "\203A\00a0"; }
.brief details[open] summary::before { content: "\2304\00a0"; }
.brief details p { margin-top: var(--s2); }

/* --- screening -----------------------------------------------------------
   A list of papers with a call against each. Hairlines between rows and
   nothing else: no fill on hover, because a band sliding under the cursor
   through ten rows is the striping this file is meant to have deleted. */

.progress-line { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); }
.progress-line .track { flex: 1; }

.papers { border-top: 1px solid var(--rule); }
.paper {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  /* Every row is inset by the width of the "you are here" bar, so landing on
     one moves the bar and not the paper's name. */
  padding: var(--s2) 0 var(--s2) var(--s2);
  border-bottom: 1px solid var(--rule);
}
.paper[data-active="true"] { box-shadow: inset 2px 0 0 var(--accent); }
@media (forced-colors: active) {
  .paper[data-active="true"] { box-shadow: none; border-left: 2px solid Highlight; }
}

.paper .letter {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.paper .name {
  border: 0;
  background: none;
  padding: 0;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  min-width: 0;
}
.paper .name b { display: block; font-weight: 600; }
.paper .name small { color: var(--ink-3); font-size: var(--fs-sm); }
.paper .name:hover b { color: var(--accent); }

.hint-keys { margin-top: var(--s3); font-size: var(--fs-sm); color: var(--ink-3); }

/* --- one page of the document -------------------------------------------
   Every step of the workspace is one of these: a strip of papers, a name in
   large type, the work, and one quiet line of key hints. Step 2's page is one
   paper — the same one-thing-at-a-time the researcher liked in step 1 — with
   its arms stacked down it.

   There is no toolbar. The bar it replaced held a filter, a "show the empty
   columns" switch, a by-paper/by-extraction switch and a progress meter; four
   controls, three of which decided what the reviewer was allowed to see. */

.docpage { display: flex; flex-direction: column; }

/* The papers, as letters. The progress indicator and the navigation at once,
   which is why it costs no words: a letter with a bar under it is a paper you
   have finished. Same control as the one in the reader head, same meaning. */
.papernav { margin: 0 0 var(--s4); }
.papernav button:hover { background: var(--sunken); }

.docpage-head {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.docpage-letter { font-size: var(--fs); color: var(--ink-3); flex: none; }

/* One arm of one paper: its name, how many fields the three disagree on, and
   the transposed grid underneath. */
.armblock + .armblock { margin-top: var(--s6); }
.armhead {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s2);
}
.armhead b { font-weight: 600; }
/* The at-a-glance half of the disagreement marking, and it is a word before it
   is a colour. */
.differs-n {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-sm);
  color: var(--warn);
  white-space: nowrap;
}
.differs-n::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
@media (forced-colors: active) {
  .differs-n::before { background: CanvasText; }
}

.docpage-foot { margin-top: var(--s5); }
.note-wrap { margin-top: var(--s4); }

/* --- pickers, notes, verdict --------------------------------------------- */

.pick { display: flex; flex-direction: column; }
.pick-opt {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s2);
  border: 0;
  border-top: 1px solid var(--rule);
  border-radius: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: background .13s ease;
}
.pick-opt:last-child { border-bottom: 1px solid var(--rule); }
.pick-opt:hover { background: var(--sunken); }
.pick-opt[aria-checked="true"] { background: var(--sunken); }
.pick-opt .radio {
  width: 16px; height: 16px;
  margin-top: 3px;
  flex: none;
  border: 1.5px solid var(--edge);
  border-radius: 50%;
  display: grid;
  place-items: center;
}
.pick-opt[aria-checked="true"] .radio { border-color: var(--accent); }
.pick-opt[aria-checked="true"] .radio::after {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
.pick-opt .lbl { flex: 1; }
/* --ink-3 would be 4.64 on --sunken, which passes, but this line is the one
   that explains the option, so it gets --ink-2 at 6.16. */
.pick-opt .sub { display: block; margin-top: 2px; font-size: var(--fs-sm); color: var(--ink-2); }

textarea.note {
  width: 100%;
  min-height: 80px;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: transparent;
  resize: vertical;
  line-height: 1.6;
}
textarea.note:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.recap .row {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--rule);
  font-size: var(--fs);
}
.recap .row:first-child { border-top: 1px solid var(--rule); }
.recap .row .k { flex: 1; color: var(--ink-2); }
.recap .row .v { color: var(--ink); }
.recap .row .v.todo { color: var(--ink-3); }

/* --- the foot of the pane ------------------------------------------------ */

.work-nav {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s5);
  border-top: 1px solid var(--rule);
  background: var(--bg);
}
.work-nav .status { flex: 1; font-size: var(--fs-sm); color: var(--ink-3); }

.finish .seal { display: none; }
.finish h2 { margin-bottom: var(--s2); }
.finish p { color: var(--ink-2); max-width: 60ch; margin-bottom: var(--s4); line-height: 1.6; }
.finish .actions { display: flex; gap: var(--s2); flex-wrap: wrap; }
/* DESIGN §4: what is still outstanding, per paper, each one a way straight to
   it. Being told what is missing and then having to hunt for it is the same as
   not being told. */
.straggle {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin: calc(var(--s4) * -1 + var(--s2)) 0 var(--s4);
  font-size: var(--fs-sm);
}
.finish .fine { margin-top: var(--s3); }
.finish .fine a { color: var(--accent); }
.finish .fine a:hover { text-decoration: underline; }

.empty {
  display: grid;
  place-items: center;
  gap: var(--s2);
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--ink-3);
}
.empty b { color: var(--ink-2); font-weight: 600; }

/* ============================================================================
   Responsive

   Two rules, and they are different in kind:

   · THE PANES take care of themselves. The grid above gives the panel a floor
     and the paper none, so every width from 820px up is already correct and
     there is no breakpoint between them. Nothing below re-sizes a pane.
   · THE BAR runs out of room before the panes do, because its contents are
     words rather than boxes, and words do not shrink. Those are the media
     queries here: each one drops the least useful thing left in the bar.

   The layout only changes SHAPE once, at 820px, where the paper stops being a
   column. That number is core.js's NARROW and is written in exactly these two
   places; see the comment there for why it is 820 and not a round number.
   ========================================================================== */

/* The five bar controls plus the three step names stop fitting before the panes
   do, and the outcome's subtitle is the one thing in the bar that is said again
   two lines further down. */
@media (max-width: 1023px) {
  .ws-title span { display: none; }
  .ws-title strong { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* === the one shape change — keep in step with NARROW in core.js === */
@media (max-width: 820px) {
  .split, .split.collapsed { grid-template-columns: minmax(0, 1fr); height: auto; }
  .divider { display: none; }
  .reader {
    position: fixed;
    inset: var(--bar) 0 0;
    z-index: 50;
    transform: translateX(-100%);
    transition: transform .26s cubic-bezier(.2, .8, .25, 1);
    background: var(--bg);
  }
  .reader.open { transform: none; }
  .reader iframe { min-height: 60vh; }
  .work { overflow: visible; display: block; }
  .work-inner { --pane-pad-x: var(--s3); --pane-pad-y: var(--s4); padding-bottom: var(--s5); }
  .work-nav { padding: var(--s3); }
  .protocol-body .field { grid-template-columns: 1fr; gap: 0; }
  /* the page scrolls in the window here, so a scroll target has to clear the bar */
  .fields tbody th { scroll-margin-top: calc(var(--bar) + var(--s4)); }
  .ws-bar .label, .ws-title { display: none; }
  .steps { flex: 1; justify-content: center; }
  .step { padding: 0 var(--s1); }
}

@media (max-width: 560px) {
  .page { padding: var(--s5) var(--s3) 90px; }
  .paper { grid-template-columns: 26px minmax(0, 1fr); row-gap: var(--s2); }
  .paper .call { grid-column: 2; }
}

/* --- what the work pane does when IT is narrow ---------------------------
   Not what the window does. These three rules used to sit in the 560px media
   query above, and so they fired when the WINDOW was 560px wide — which is a
   width this product is barely used at. The pane is a different measurement
   entirely: at the reviewer's default the paper takes 46%, .work-inner spends
   another 68px on padding, and on a perfectly ordinary 1280px laptop the table
   is left with about 520px for a field name and three extractions. It was being
   crushed at the width the tool is actually used at, and the rule written to
   rescue it could not fire there. .work is already a container — it was made
   one for exactly this reason and then queried by nothing.

   Three extractions and a field name in 560px: the labels give up their
   column's worth of padding, and the column heads give up their one-line rule,
   before anything gives up a horizontal scrollbar. A table you have to scroll
   sideways to compare across is not a comparison. */
@container pane (max-width: 620px) {
  .work-inner { --pane-pad-x: var(--s3); }
  .fields.cmp tbody th { padding-right: var(--s2); }
  .fields.cmp tbody td, .fields.cmp thead th { padding-left: var(--s1); padding-right: var(--s1); }
  .fields.cmp thead th { white-space: normal; }
  /* The field name is the one column that can afford to be quieter than the
     numbers it names, and at this width it is what is taking the room. */
  .fields.cmp tbody th { font-size: var(--fs-sm); }
}

/* Under this the three calls cannot stand side by side under their columns and
   the label column cannot hold "Median [Q1–Q3] (intervention)" at all. The
   table keeps its shape and takes a scroller of its own, which is the one place
   sideways scrolling is better than the alternative: the alternative is a
   column of single characters. The scroller is on a wrapper so the page itself
   never scrolls sideways. */
@container pane (max-width: 420px) {
  .armblock { overflow-x: auto; overscroll-behavior-x: contain; }
  .fields.cmp { min-width: 400px; }
}

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