/* ============================================================================
   O3DC — SHAPE
   Loaded after styles.css, which keeps every token and every component that still
   does its job: modals, join/submit forms, the discussion sheet, the table view.
   This file only changes the register of the page around them.

   HISTORY, so nobody re-litigates it
   Iteration 2 rebuilt this page as a full-width numbered essay. It was rejected on
   measurement, correctly: the same 135 entries came to 53,470px tall against
   31,910px as a sidebar grid — 68% more scrolling, because an essay caps its text at
   a reading measure and a directory of 135 dense items wants the whole width.

   What that iteration got right and this one keeps: Mona Sans instead of the system
   stack, a claim rendered from the data instead of a category label, headline numbers
   that each carry a sentence, and chrome heights MEASURED rather than assumed.

   THE PROBLEM THIS FILE SOLVES
   "One huge wall of similar-looking stuff." That is a content problem wearing a
   layout costume: all 135 entries have the identical field set, so they render as 135
   identical objects. Two answers, both from real data, neither a quality score:

     1. THE LENS. A global Claims <-> Caveats switch. Every entry flips from what it
        advertises to the known weakness its own documentation omits.
     2. THE PASSPORT. A fixed metadata grid where ABSENCE is printed as plainly as
        presence — a repository exists for 88 of 135, a project site for 103, a
        consortium for 36 — so cards differ in how full they are, and the difference
        is information rather than decoration.

   ORDERING RULE FOR THIS FILE. Base component rules first, responsive overrides
   LAST. A media query adds no specificity, so at equal specificity the last rule
   wins: an override placed above its base silently loses. That already cost a round
   here — .nums kept two columns at 2000px while grid-column from the very same block
   applied fine, because the base .nums sat below the media query.
   ========================================================================== */

/* ---------- type ----------
   Mona Sans, OFL, vendored with its licence in assets/licenses/. VARIABLE (200-900),
   so every weight is stated literally: a fixed-weight face absorbs any weight
   request, a variable font resolves what you ask for, and an unstated <b> quietly
   renders at 700. */
@font-face {
  font-family: "Mona Sans";
  src: url("fonts/mona-sans-variable.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
}
:root {
  --sans: "Mona Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  /* Measures in rem, never ch: ch scales with font-size, so one rule gives blocks at
     different sizes different pixel widths and near-alignment reads as a mistake. */
  /* 46rem, not 40. This is the OPENING GRID's left track as much as it is a reading
     measure, and at >= 1700px those two wants pull apart: a narrow track makes the left
     column tall and hands the slack to the figure, which needs none of it. At 46rem the
     two columns finish level on their own (measured 560 vs 562 at 2200px), which is what
     the deleted `align-self: stretch` was faking.
     KNOWN COST, stated rather than hidden: this is a long line. Measured chars per line
     at 2200px — opening lede 95 -> 112, category blurb 98 -> 115, footer 103 -> 115.
     It was already past a comfortable measure at 40rem, so this worsens an existing
     compromise rather than introducing one; the lede also drops 4 lines -> 3, which cuts
     the number of line-returns. If it needs clawing back later the lever is the FONT SIZE
     of the small blocks (footer and .field__note at 11.5-12px are the worst of them),
     not this width — nine blocks at four sizes share it, and giving one its own width
     makes near-alignment read as a mistake. */
  --measure: 46rem;
  --chrome-h: 0px;      /* published by publishChrome(); this is the first-paint fallback */
}
body { font-family: var(--sans); }
h1, h2, h3, .num__v, .wordmark { font-family: var(--sans); }

/* ---------- provenance strip ----------
   What the data is, before whose it is. Spans the full shell width. */
.strip {
  grid-column: 1 / -1;
  background: var(--panel2); border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11px; color: var(--fg2);
  padding: 7px 20px; display: flex; gap: 9px; align-items: baseline; flex-wrap: wrap;
}
.strip b { color: var(--fg); font-weight: 700; }
.strip .dotlead { width: 6px; height: 6px; border-radius: 50%; background: var(--acc);
  flex: none; align-self: center; }
.strip .mini { min-height: 1.4em; }     /* reserved: the text swaps as tiers resolve */

/* ---------- shell ----------
   The strip spans both columns, so aside and main AUTO-PLACE around it. Do not pin
   them with `grid-row: 2`: that is right for the two-column desktop grid and puts
   them in the SAME CELL under styles.css's single-column mobile rule, where they
   overlap and push ~100px off the right edge. Auto-placement gets both cases right.

   The strip is not sticky — it scrolls away — so the sidebar sticks to 0 rather than
   to the strip's height, which would leave a permanent gap once it scrolled out. */
aside { top: 0; }
/* Jumping to a category must clear the sticky filter row, whose height changes when
   the controls wrap. Measured and published by publishChrome(). */
.cat { scroll-margin-top: calc(var(--chrome-h) + 18px); }

/* ---------- sidebar calls to action ----------
   Two bordered boxes with green text, which read as unstyled buttons rather than as a
   decision. They now use the hero's two weights at rail scale, so the pair looks
   deliberate in both places and a reader who scrolled past the hero still meets the
   same primary/secondary relationship. Full width of the rail, because a short button
   in a 228px column leaves an awkward ragged edge. */
aside .navjoin { display: block; width: 100%; text-align: center;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  padding: 9px 12px; border-radius: 7px; cursor: pointer;
  margin: 0 0 6px; transition: background-color .14s, border-color .14s, color .14s; }
/* Targeted by id, NOT by :first-of-type. `:first-of-type` means "first element of its
   tag among siblings" — the aside is full of divs, so it matched .logo and this rule
   silently applied to nothing while the button kept its old look. Position-based
   selectors break the moment the markup around them changes; an id cannot. */
aside #navSubmit { background: var(--acc); border: 1px solid var(--acc);
  color: var(--onacc); }
aside #navSubmit:hover { filter: brightness(1.08); background: var(--acc); }
aside #navJoin { background: none; border: 1px solid var(--line2); color: var(--acc); }
aside #navJoin:hover { border-color: var(--acc); background: var(--consbg); color: var(--acc); }
/* The community links below are plain .navlink and must stay plain — do not fold them
   into the rule above by widening the selector. */
aside .navlink:not(.navjoin) { padding: 5px 9px; }

/* ---------- sidebar categories as disclosures ----------
   The active item was a card: a panel fill and a border, which said "selected" but
   said nothing about the section, and stayed lit after the reader had scrolled past it.
   It is now a disclosure that follows the scroll — an IntersectionObserver band owns
   which one is open — and the open one carries two facts the section header does not:
   the counts in words, and the most recently pushed entry in that category.

   HEIGHT IS CONSTANT BY CONSTRUCTION. Exactly one item is open at any moment and every
   summary is exactly two nowrap lines, so opening one while closing another is height
   neutral and the rail never grows or shrinks. A rail that resized while the reader
   scrolled would jitter the thing being read — the same failure as a hover-driven key
   that reflows the chart it describes. Do not let a summary wrap. */
.navcat { display: block; width: 100%; text-align: left; background: none; border: 0;
  border-left: 2px solid transparent; border-radius: 0;
  padding: 6px 9px 6px 10px; margin: 0; cursor: pointer;
  font: inherit; color: var(--fg2); }
.navcat:hover { color: var(--fg); }
.navcat:focus-visible { outline: 2px solid var(--acc); outline-offset: -2px; }
.navcat__row { display: flex; align-items: center; gap: 7px; }
.navcat__name { font-size: 12.8px; line-height: 1.3; flex: 1; min-width: 0; }
.navcat .n { font-family: var(--mono); font-size: 10.5px; color: var(--fg3);
  background: var(--chip2); border: 1px solid var(--line); border-radius: 20px;
  padding: 0 6px; flex: none; }
.navcat__mark { width: 12px; height: 12px; flex: none; color: var(--fg3);
  transition: transform .16s var(--ease, ease); }

/* open state: an accent rule and a rotated marker, no fill */
.navcat.on { color: var(--fg); border-left-color: var(--acc); }
.navcat.on .navcat__mark { transform: rotate(90deg); color: var(--acc); }
.navcat.on .n { color: var(--fg2); border-color: var(--line2); }

.navcat__sum { display: block; height: 0; overflow: hidden;
  transition: height .18s var(--ease, ease); }
/* An explicit pixel height, not em. `em` here resolves against the inherited 15.5px
   body size rather than the 9.5px the lines are actually set at, so the reserved box
   had no relationship to its contents and would have clipped or gaped on any type
   change. 2 lines x 13.8px + 2 x 3px padding = 34px. */
.navcat.on .navcat__sum { height: 34px; }
.navcat__counts, .navcat__fresh {
  display: block; padding-top: 3px;
  font-family: var(--mono); font-size: 9.5px; line-height: 1.45; color: var(--fg3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.navcat__counts { color: var(--fg2); }

@media (prefers-reduced-motion: reduce) {
  .navcat__mark, .navcat__sum { transition: none; }
}

/* ---------- opening ---------- */
header.open { padding: 30px 0 24px; border-bottom: 1px solid var(--line); margin-bottom: 22px; }
/* Two tracks, and the LEFT one is sized to the reading measure rather than to 1fr.
   With `1fr | 380px` the prose stopped at its 40rem cap while the track kept growing,
   so at 2000px there was ~550px of dead air between the lede and the number block:
   the text was not stretched, the GAP was. Sizing the left track to the measure and
   letting the right track absorb the slack removes it by construction at every
   width, with no breakpoint doing arithmetic. */
.open__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 26px;
  align-items: start; max-width: 1780px; }
.open__kicker { font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--acc); margin-bottom: 14px; }
/* No max-width. The left TRACK is already sized to the reading measure, so a cap on
   the h1 as well made a short display title break mid-column for no reason — the
   column is the constraint, and one constraint is enough. `balance` distributes the
   remaining lines instead of leaving a one-word orphan. */
/* `max-width: none` is load-bearing, not redundant. styles.css carries a BARE ELEMENT
   rule — `h1 { ... max-width: var(--measure-head) }` — left over from the hero this
   build replaced. Dropping the declaration here did not remove the cap, it handed it
   back to that rule, which is why a 640px column still broke the title at 416px. Same
   trap as `header { padding: 44px 0 22px }` earlier: an element selector written for
   one instance silently applies to the next one. */
.open h1 { font-size: clamp(26px, 2.7vw, 38px); font-weight: 800; letter-spacing: -.03em;
  line-height: 1.09; margin: 0; max-width: none; text-wrap: balance; }
/* Emphasis by text-decoration, not border-bottom: a bottom border on a multi-line
   inline draws once PER LINE BOX, which gave three ragged underlines ending
   mid-phrase. styles.css also sets `h1 em` to a clipped gradient with
   -webkit-text-fill-color: transparent, neutralised here explicitly rather than by
   hoping source order saves us. */
.open h1 em {
  font-style: normal; background: none; -webkit-text-fill-color: currentColor;
  color: var(--fg);
  text-decoration: underline; text-decoration-color: var(--acc);
  text-decoration-thickness: 3px; text-underline-offset: 5px; text-decoration-skip-ink: none;
}
.open__lede { margin: 16px 0 0; max-width: var(--measure); font-size: 14.5px;
  line-height: 1.6; color: var(--fg2); }
.open__lede b { color: var(--fg); font-weight: 700; }

/* Each figure carries a SENTENCE, never a bare noun: "135 / 10 / 15 / 82 / 3" tells a
   reader nothing they can repeat. Presentation is the ledger block further down. */

/* ---------- hero calls to action ----------
   Two weights, because the two asks are not equal: submitting a benchmark is open to
   anyone and is the thing we actually want, joining is a commitment. Filled for the
   first, outlined for the second. Measured: --onacc on --acc is 8.03:1, and --acc as
   text is 9.54:1 on the dark ground and 5.82:1 on the light one. */
.cta { margin: 20px 0 0; }
.cta__row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  min-height: 40px; }          /* reserved across the auth-pending -> buttons swap */
.cta__btn { font-family: var(--sans); font-size: 13.5px; font-weight: 600;
  padding: 10px 17px; border-radius: 8px; cursor: pointer; white-space: nowrap;
  transition: background-color .14s, border-color .14s, color .14s; }
.cta__btn--primary { background: var(--acc); border: 1px solid var(--acc); color: var(--onacc); }
.cta__btn--primary:hover { filter: brightness(1.08); }
.cta__btn--ghost { background: none; border: 1px solid var(--line2); color: var(--acc); }
.cta__btn--ghost:hover { border-color: var(--acc); background: var(--consbg); }
.cta__btn:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }
/* The slot holds its height even when #headNote is display:none, so neither the auth
   swap nor a signed-in member shifts the numbers underneath. */
.cta__noteslot { min-height: 3.1em; margin-top: 10px; }
.cta__note { font-size: 11.5px; line-height: 1.5; color: var(--fg3);
  max-width: var(--measure); display: block; }

/* ---------- headline numbers, as a ledger rather than four boxes ----------
   Was a bordered card with a 2x2 grid inside it. On a page that had already given up
   card fills for the 135 entries, four more boxes were the heaviest thing in the
   opening and they were competing with the calls to action for the same attention.
   Now: a hanging figure, right-aligned against its label, hairline rule between rows,
   no fill and no border — the same ledger treatment as the counts that now lead the
   rows of the figure. */
.nums { display: flex; flex-direction: column; background: none; border: 0;
  border-radius: 0; overflow: visible; gap: 0; }
.num { background: none; display: grid; grid-template-columns: 4.4rem minmax(0, 1fr);
  gap: 0 14px; align-items: baseline; padding: 11px 0 12px;
  border-top: 1px solid var(--line); }
.num:first-child { border-top: 0; padding-top: 2px; }
.num__v { font-size: 21px; font-weight: 800; letter-spacing: -.03em; line-height: 1.1;
  color: var(--fg); font-variant-numeric: tabular-nums; text-align: right; }
.num__v small { font-size: 11.5px; font-weight: 700; color: var(--fg3); }
.num__t { margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--fg3); }
.num__t b { color: var(--fg2); font-weight: 700; }

/* ---------- the lens switch ----------
   The signature control. Both faces of every entry are in the DOM, so the flip is a
   CSS state change and never re-renders 135 cards. Deliberately a two-state SWITCH
   rather than a hover lens or press-and-hold spotlight: a pointer-position reveal has
   no touch path and no keyboard path, and the payoff has to survive a screenshot. */
.lens { display: flex; border: 1px solid var(--line2); border-radius: 7px; overflow: hidden; flex: none; }
.lens button { background: var(--panel); border: 0; color: var(--fg2); font-family: var(--sans);
  font-size: 12.5px; font-weight: 600; padding: 8px 13px; cursor: pointer; white-space: nowrap; }
.lens button:hover { color: var(--fg); }
/* Active after hover: both are 0-2-0, so order decides which wins. */
.lens button[aria-pressed="true"] { background: var(--acc); color: var(--onacc); }
.lens button[aria-pressed="true"]:hover { color: var(--onacc); }
.lens button:focus-visible { outline: 2px solid var(--acc); outline-offset: -2px; }

/* ---------- entries: the passport ----------
   .pass and .card are both 0-1-0, so order decides and this file loads second. The
   passport keeps .card only for the components that already target it. */
/* NOT CARDS. The page already carries a card for the numbers and a card for the
   figure; putting 135 more boxes under them made it read as a dashboard of tiles
   rather than an index. Entries are now columns of a ruled table: a vertical rule
   between columns, whitespace between rows, no fill and no box.

   .pass also carries .card, whose background, border, radius, padding and shadow all
   have to be undone explicitly — both selectors are 0-1-0, so this file winning is a
   matter of load order, and the shadow in particular would otherwise float under a
   block that no longer has edges. */
.grid { align-items: start; column-gap: 0; row-gap: 26px; }
/* --line2, not --line. The rule is now the only thing separating one entry from the
   next, so it has to read as a line: measured against the page ground, --line is
   1.27:1 on dark and 1.19:1 on light — a smudge — while --line2 gives 1.72:1 and
   1.50:1. When a border stops being decoration and starts carrying structure, it
   needs re-measuring against its new job. */
.pass { background: none; border: 0; border-left: 1px solid var(--line2);
  border-radius: 0; box-shadow: none;
  padding: 2px 22px 0 18px; display: flex; flex-direction: column;
  gap: 9px; transition: border-color .15s; }
.pass:hover { border-left-color: var(--fg3); }
.pass__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.pass h3 { font-size: 14.5px; font-weight: 700; letter-spacing: -.02em; line-height: 1.3; margin: 0; }
.pass__by { font-size: 11.5px; color: var(--fg3); margin-top: 3px; }

/* the two lens faces: one displayed, both always in the markup */
.pass__claim, .pass__note { font-size: 13px; line-height: 1.5; margin: 0; }
.pass__claim { color: var(--fg2); }
.pass__note  { display: none; color: var(--fg); }
.pass__note b { display: block; font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; margin-bottom: 4px;
  /* --warn direct, not a themed -ink step: measured 11.27:1 on dark and 4.83:1 on
     light, so both clear AA at 9px without a substitute. */
  color: var(--warn); }
html[data-lens="caveats"] .pass__claim { display: none; }
html[data-lens="caveats"] .pass__note  { display: block; }
/* The mode is legible from the card's own edge, not only from the switch. Measured
   with transitions neutralised: dark #20402e -> #f7b955 at 10.43:1, light
   #bed3c6 -> #9a6206 at 5.09:1.

   TESTING TRAP: `border-color` is in .pass's transition list, and a CSS transition
   NEVER ADVANCES under Chrome's --virtual-time-budget — getComputedStyle returns the
   pre-change colour indefinitely, and waiting does not help because the wait is
   virtual time too. Four probes called this rule broken while it was correct. Assert
   a transitioned property only after injecting `* { transition: none }`. */
html[data-lens="caveats"] .pass { border-left-color: var(--warn); }
/* With the box gone the mode still reads at a glance: every column rule in the grid
   turns amber at once, and the "KNOWN CAVEAT" label appears on every entry. */

/* ---------- per-entry disclosure: BOTH faces, on demand ----------
   The lens is global and lives in the filter row, so reading one entry's caveat meant
   flipping all 135 and giving up every description. This adds the OTHER face per entry,
   in addition rather than instead, on an explicit press.

   Not on hover and not as a floating panel, deliberately. A pointer-position reveal has
   no touch path and no keyboard path — the reasoning that made the lens a switch — and
   it cannot appear in a SCREENSHOT, which decides it here: the caveats are the asset
   this site is promoted on. A press also avoids the oscillation trap, where expanding on
   hover moves the text out from under the pointer, which collapses it, which moves it
   back. Nothing here changes height on hover; the control is always visible. */
.pass__faces { display: flex; flex-direction: column; gap: 9px; }
/* The SELECTED face stays on top in both modes. Two children only, and a display:none
   child is out of flex layout, so while collapsed this reverses nothing at all. */
html[data-lens="caveats"] .pass__faces { flex-direction: column-reverse; }
/* 0-3-0, so it beats the 0-2-1 lens rules on specificity and cannot be broken by a
   later edit reordering this file. */
.pass.is-open .pass__claim, .pass.is-open .pass__note { display: block; }
/* The description's caption ships on every entry but stays hidden while the description
   is the PRIMARY face: the default face needs no caption, and captioning all 135 would
   be a visible change to shipped design. It appears only when the description is the
   revealed SECONDARY, where an unlabelled paragraph sitting under a caveat reads as
   more caveat. Neutral --fg3, not --warn: only the caveat gets the amber. */
.pass__claim b { display: none; }
html[data-lens="caveats"] .pass.is-open .pass__claim b { display: block;
  font-family: var(--mono); font-size: 9px; font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; margin-bottom: 4px; color: var(--fg3); }
.pass__flip { align-self: flex-start; background: none; border: 0; padding: 0;
  display: flex; align-items: center; gap: 6px; cursor: pointer;
  font-family: var(--mono); font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--fg3); }
/* Glyph carries open/closed, the dotted rule carries "this is a control". Both are
   redundant with the label, so neither is the only signal. */
.pass__flip::before { content: "+"; font-size: 12px; line-height: 1; width: 9px;
  text-align: center; }
.pass__flip[aria-expanded="true"]::before { content: "\2212"; }
.pass__flip-t { border-bottom: 1px dotted var(--line2); padding-bottom: 1px; }
.pass__flip:hover { color: var(--fg); }
.pass__flip:hover .pass__flip-t { border-bottom-color: var(--fg3); }
.pass__flip:focus-visible { outline: 2px solid var(--acc); outline-offset: 3px; }
/* Which noun the button names depends on the mode, and the lens flip has to stay a pure
   CSS state change — re-rendering 135 entries on a flip is exactly what keeping both
   faces in the DOM exists to avoid. So both labels ship and CSS picks one. */
.pass__flip .f-caveats { display: none; }
html[data-lens="caveats"] .pass__flip .f-claims  { display: none; }
html[data-lens="caveats"] .pass__flip .f-caveats { display: inline; }
/* Open state names a VERB, not the noun again. Repeating it put "KNOWN CAVEAT" on the
   amber block label and "KNOWN CAVEAT" on the button one line below it — the same words
   twice, which is noise and reads as a mislabel.
   The two nouns are wrapped so ONE rule hides both: hiding them individually needs
   0-3-1 to beat the lens rules above, and a later edit reordering this file would
   silently break a 0-3-0 attempt. A hidden parent wins whatever the children say. */
.pass__flip .f-hide { display: none; }
.pass__flip[aria-expanded="true"] .flip-noun { display: none; }
.pass__flip[aria-expanded="true"] .f-hide { display: inline; }

/* the passport grid: four slots, ALWAYS present, absence printed as an em dash */
.pass__slots { display: grid; grid-template-columns: auto minmax(0,1fr); gap: 3px 10px;
  margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 10.5px; align-items: baseline; }
.pass__k { color: var(--fg3); letter-spacing: .04em; }
.pass__v { color: var(--fg); min-width: 0; overflow-wrap: anywhere; }
.pass__v.is-none { color: var(--fg3); }        /* absence is recessive but present */
.pass__v a { color: var(--fg); border-bottom: 1px solid var(--line2); }
.pass__v a:hover { color: var(--acc); border-bottom-color: var(--acc); text-decoration: none; }
.pass__foot { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding-top: 9px; }
.pass__foot button { font-family: var(--mono); font-size: 10.5px; color: var(--fg2);
  background: var(--chip); border: 1px solid var(--line); border-radius: 5px;
  padding: 3px 8px; cursor: pointer; }
.pass__foot button:hover { border-color: var(--acc); color: var(--acc); }
.pass .upd { margin-left: auto; }

/* ---------- the figure ----------
   Carded and capped. At full width on a wide display its ten bars stretched to
   ~1600px, giving a ten-row chart more visual weight than the 135-entry catalogue it
   introduces — and the stretch bought no resolution, because bar LENGTH already
   encodes the count and the largest category is only 25. */
.field { margin: 0; max-width: 1120px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 11px;
  padding: 16px 18px 14px; }
.field .field__h { font-size: clamp(15px, 1.2vw, 17px); font-weight: 700; letter-spacing: -.02em;
  min-height: 0; margin-bottom: 7px; }
.field .field__lede { max-width: var(--measure); font-size: 12.5px; margin-bottom: 12px; }
.field .field__key { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--line);
  align-items: baseline; }
.field__scope { font-family: var(--mono); font-size: 10px; color: var(--fg3); margin-left: auto; }
.field .field__rows { gap: 2px; }
.field .frow { padding: 2px 6px; }
/* The count leads, right-aligned so the digits line up against the names. At the far
   right of the row a five-entry category left ~800px of empty rule between its short
   bar and its number, and the reader met the mark before its unit. */
.frow__n { text-align: right; font-weight: 700; color: var(--fg2); font-size: 12px; }
.field .fbar { height: 15px; }
.field .fseg { font-size: 9.5px; }
/* The count sits on a FLAT chip of its segment's own colour, because the 45deg hatch
   ran straight through the digits and made them hard to read at 9.5px.
   `background-color: inherit` takes the parent's resolved fill and nothing else —
   background-image is not inherited — so each segment gets a flat patch in exactly its
   own colour with no new token and no drop-shadow crutch.

   It also makes the contrast figures TRUE. The 5.89:1 and 4.85:1 measured for the
   dormant label were computed against the flat fill, but the hatch was painted on top,
   so the real contrast varied stripe to stripe and was worse than the number claimed.
   Now the measurement describes what is actually behind the text. */
.fseg > b { background-color: inherit; padding: 0 3px; border-radius: 2px; font-weight: 700; }
.field .field__note { font-size: 11px; margin-top: 10px; }

/* ---------- section heads and keys ---------- */
.cathead h2 { font-weight: 800; letter-spacing: -.03em; }
/* the category's own maintenance bar, separated from the grid or it reads as chrome
   belonging to the first card rather than to the section */
.gsec__bar { margin: 10px 0 18px; max-width: 520px; }
/* Two keys about repository freshness sit within ~200px of each other: the figure's
   three fills, and the entry badge's five shapes. The three are a coarsening of the
   five, so both are correct — each key names the mark it describes rather than one
   being deleted. */
.keyscope { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase;
  letter-spacing: .12em; color: var(--fg3); font-weight: 700; margin-right: 2px; }

/* loading placeholder, shaped like what actually arrives */
.sk-ent { height: 190px; border: 1px solid var(--line); border-radius: 0 10px 10px 0;
  border-left: 2px solid var(--line2); background: var(--panel);
  animation: skpulse 1.4s ease-in-out infinite; opacity: .5; }

/* ============================ RESPONSIVE OVERRIDES ==========================
   Last in the file on purpose — see the ordering rule at the top.

   THREE ARRANGEMENTS, chosen by how much room the figure actually needs. Its bar
   segments stop holding their own numeric labels below roughly 680px of column: about
   150px goes to the category name and 34px to the count, so a narrower column
   squeezes the bars, not the labels. That measurement, not a round number, sets the
   1700px breakpoint.

     < 1180px    one column: claim, numbers, figure.
     1180-1699   claim | numbers, with the figure spanning underneath. The numbers
                 work from 320px up, so they take the narrow column.
     >= 1700px   claim | FIGURE, numbers under the claim. Stat tiles tile happily at
                 any width; a ten-row chart does not, which is why they swap.
   ========================================================================== */
@media (min-width: 1180px) {
  .open__grid { grid-template-columns: minmax(0, var(--measure)) minmax(320px, 1fr); gap: 40px 46px; }
  .open__grid > div:first-child { grid-column: 1; grid-row: 1; }
  .nums  { grid-column: 2; grid-row: 1; }
  .field { grid-column: 1 / -1; grid-row: 2; }
}
@media (min-width: 1700px) {
  /* The figure takes the whole right column and the numbers move under the claim, so
     both columns finish at roughly the same height. Numbers full-width below instead
     left ~360px of dead space beneath the lede — the original fault, moved to the
     other side of the grid.

     NO `align-self: stretch` HERE — it was there, and it was a bug. "Roughly the same
     height" holds just past this breakpoint and then stops: .open__grid caps at 1780px
     and the left track is sized to the reading measure, so beyond ~2100px of viewport
     the left column's height is FIXED while the figure keeps getting SHORTER — a wider
     column unwraps the category names from two lines to one. Stretching the card to the
     row therefore padded a BORDERED panel with a growing void under its own legend:
     measured 90px at 1700px, 169px at 2200px, and 169px from there on (a quarter of the
     card) once the grid stops growing.
     Letting the card take its content height moves that leftover OUTSIDE the border,
     where it reads as page margin instead of as an unfinished panel. The base
     .open__grid already sets `align-items: start`, so the fix is one declaration fewer
     rather than one more — do not "restore" the stretch to level the columns up. */
  /* Row gap drops to 10px HERE ONLY. Either side of this breakpoint it separates
     different things: at 1180-1699 it is the space between the whole opening and the
     figure sitting full-width beneath it, which wants the full 40px; from 1700 up the
     figure has moved beside the claim, so the row gap is the space between the calls to
     action and the four numbers directly under them — one block of the same column, and
     40px there reads as a break in the middle of the left column. */
  .open__grid { gap: 10px 46px; }
  .field { grid-column: 2; grid-row: 1 / span 2; max-width: none; }
  .nums  { grid-column: 1; grid-row: 2; }
}
@media (max-width: 900px) {
  aside { height: auto; top: auto; }
  header.open { padding-top: 20px; }
  .field { max-width: none; }
}

/* ---------- table view: declared column widths ----------
   FIRST, the reason this looked like a width problem. The table had NINE headers and
   EIGHT cells per row — `date` was computed in tableRows() and never emitted — so from
   "Last updated" rightwards every cell sat one column left of its header: the Added date
   under "Last updated", the consortium under "Added", and the 150-character description
   under the header reading "Consortium". The consortium's own data is SHORT (max 12
   chars over 135 rows), so the "cramped wordy consortium column" was the description,
   mislabelled. Fixed in tableRows(); the widths below fix the rest.

   SECOND, the widths. Auto layout was negotiating from content and negotiating badly:
   `td.nm`'s nowrap sized the name column to its LONGEST name (63 chars) at 382px for a
   median of 11, the repo column took 344px for a median of 17, and the only genuinely
   wordy column — 151 chars in every row — was left 155px. Total demand came to 1568px
   inside a 1144px box, so the view scrolled horizontally at a 1500px viewport, not only
   when narrowed.

   `table-layout: fixed` ends the negotiation: declared widths are honoured, the table
   fits its box at any width at or above min-width, and no single long token can inflate
   a column. The percentages come from the measured median/max characters per column.
   Do not restore a nowrap or a max-width on any cell here — either one hands width back
   to the content and this all unwinds. */
.tablewrap table { table-layout: fixed; }
/* A declared width cannot be widened by a nowrap header, so an unwrapped header would
   simply overflow its own cell. Let both wrap; the dates keep their nowrap because a
   date broken across two lines is worse than a wide column. */
.tablewrap th    { white-space: normal; }
.tablewrap td.nm { white-space: normal; }
/* Any cell can hold one token longer than its declared width — a probe found the
   maintainer column over by 10px on a single long name. `break-word` breaks ONLY when a
   word cannot otherwise fit, so ordinary wrapping is untouched; the repo column needs
   `anywhere` because owner/repo has no break opportunity at all. A declared width makes
   this necessary: under auto layout the column would simply have grown instead. */
.tablewrap td { overflow-wrap: break-word; }
.tablewrap td.rp { overflow-wrap: anywhere; }
.tablewrap td.tx { color: var(--fg2); }
/* The last column holds only the Discuss button, so it gets the tighter padding and the
   button gets a tighter one too — at min-width the default 13px each side left less room
   than the button needs. */
.tablewrap th:last-child, .tablewrap td:last-child { padding-left: 6px; padding-right: 10px; }
.tablewrap .disc { padding: 3px 6px; }
/* Widths measured, then CORRECTED by measurement: a first pass gave Added and Consortium
   8% each and a probe found both overflowing their cells — Added by 35px because it
   carries a date AND the New pill on one nowrap line, Consortium by 18px because its
   value is a bordered tag with its own padding, not bare text. Character counts alone
   underestimate any cell holding a chip. */
.tablewrap thead th:nth-child(1) { width: 13%; }   /* Benchmark     median  11, max  63 */
.tablewrap thead th:nth-child(2) { width: 10%; }   /* Category      median  30, max  38 */
.tablewrap thead th:nth-child(3) { width: 11%; }   /* Maintainer    median  43, max 121 */
.tablewrap thead th:nth-child(4) { width: 10%; }   /* Repository    median  17, max  49 */
.tablewrap thead th:nth-child(5) { width:  8%; }   /* Last updated  a date, nowrap       */
.tablewrap thead th:nth-child(6) { width: 10%; }   /* Added         a date + New pill    */
.tablewrap thead th:nth-child(7) { width: 10%; }   /* Consortium    a tag, not bare text */
.tablewrap thead th:nth-child(8) { width: 21%; }   /* the wordy one 151 in EVERY row     */
.tablewrap thead th:nth-child(9) { width:  7%; }   /* Discuss button                     */
/* The Added cell may wrap so the New pill can drop to a second line; the DATE inside it
   may not, because `white-space: normal` lets a date break at its own hyphens and
   "2026-" / "08-24" is worse than a wrapped pill. Hence the nowrap span. */
.tablewrap td.dt--added { white-space: normal; }
.tablewrap .nb { white-space: nowrap; }
