/* ==========================================================================
   compare.css - the /compare page only. It CONSUMES tokens.css and defines
   none of its own colours, type sizes or spacing steps.

   Naming follows components.css: flat, lowercase, hyphenated, one level of
   nesting in the name. Anything that already exists there (.notice, .beat,
   .code-figure, .eyebrow, .closer…) is reused, not re-implemented.

   ── The chart palette ───────────────────────────────────────────────────────
   Three fills, and the value is ALWAYS written beside the bar, so colour is
   never the only channel carrying meaning:

     --gold    RayClay. The subject of the page, not the winner - on several of
               these charts it is nowhere near the winner.
     --clay    drawn by a browser engine (Electron, NW.js, Bun ship one; Tauri
               and Wails borrow the platform's). "The problem", per the palette
               semantics - and the real distinction the macOS download figures
               turn on.
     --violet  drawn by a native renderer (Qt, Flutter).

   The TEXT-safe tokens are used for the fills, not the *-solid ones, for the
   reason already recorded on .ladder-rc: gold-solid #ffd23f is 1.2:1 on the
   light surface and a 0.6%-wide bar in it is invisible. Measured with the
   dataviz palette validator against --bg-raised in both themes:
     dark  #ffd23f / #f07a70 / #a684e8 - worst CVD pair ΔE 18.4, all >= 3:1
     light #8a6a00 / #a12f2b / #5a32a8 - worst CVD pair ΔE 6.6 (the 6-8 floor
           band, legal only WITH secondary encoding: every bar is direct-labelled
           and every chart has a table view), all >= 3:1
   ========================================================================== */

/* ==========================================================================
   1. Intro
   ========================================================================== */

/* A reference page, not the home page: the display step would push the first
   chart two screens down. --step-4 is the site's default h1 size. */
.compare-hero .hero-title {
  max-width: 24ch;
  font-size: var(--step-4);
}

.compare-hero .hero-sub {
  max-width: 62ch;
  margin-top: var(--sp-5);
}

/* The three notes on reading a chart. They opened the page until 2026-09-06,
   above the first bar and under a heading that instructed the reader; they sit
   at the end now, beside the table of the machines they are about. */
.compare-notes {
  margin-top: var(--sp-10);
}

/* ==========================================================================
   2. The controls bar

   The two controls are progressive enhancement and nothing else on the page
   depends on them: with no JavaScript the charts are already rendered in the
   blended, linear view and every other reading is text inside the table under
   each chart. So the bar hides itself and a sentence explains where the rest
   of the numbers are - a control that does nothing must not be on the page.
   The reveal rides the same [data-js] flag as #theme-toggle.

   ── Two forms, and the narrow one is not a squeezed copy of the wide one ────
   The seven chips plus their two legends need about 810px. Below that they used
   to sit in one horizontally-scrolling strip with no affordance at all: at 375px
   the reader saw MACHINE with three of five options and no hint that a SCALE
   control existed, and at 768px they saw a control captioned SCALE with exactly
   one option under it, which reads as "there is no other scale". A sticky bar
   cannot simply wrap either - two rows of 44px targets is a quarter of an iPhone
   SE viewport, permanently.

   So below 940px the bar is a DISCLOSURE: one 44px row naming the current state,
   opening a panel that holds every option at full size, wrapped, nothing clipped.
   Same pattern as the site's own mobile nav, same markup underneath - only the
   presentation changes, and the radios are the radios either way.
   ========================================================================== */

.controls-bar {
  /* 8px + a 44px target + 8px = 62px. It is sticky under a 60px header, so every
     pixel here is a pixel of the viewport the reader never gets back - 122px of
     chrome is 18% of an iPhone SE, and --tap is the floor on the rest. */
  padding-block: var(--sp-2);
  background: var(--bg-raised);
  border-block: 1px solid var(--border);
}

/* The bar sticks only for as long as there is a chart to control. .charts-region
   wraps the bar, the legend and all six chart groups, and a sticky element is
   released at the bottom of its containing block - so the bar scrolls away with
   the last chart instead of following the reader through 6,000px of prose it
   controls nothing in. No scroll listener, no measurement. */
:root[data-js] .controls-bar {
  position: sticky;
  /* Sits directly under the sticky header. Without JS the nav never collapses,
     the header is taller than --header-h, and sticking here would overlap it -
     which is why this is inside the [data-js] guard rather than above it. */
  top: calc(var(--header-h) - 1px);
  z-index: 15;
}

.chart-controls {
  display: none;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
}

:root[data-js] .chart-controls {
  display: flex;
}

/* With no JS the bar holds nothing, so it is not a bar - an empty ruled strip
   above the legend reads as a rendering fault. */
:root:not([data-js]) .controls-bar {
  display: none;
}

:root[data-js] .controls-fallback {
  display: none;
}

/* ---- the narrow form: a disclosure ---------------------------------------- */

.controls-toggle {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: var(--tap);
  padding: var(--sp-1) var(--sp-4);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  font: inherit;
  font-size: var(--step--1);
  color: var(--fg-strong);
  text-align: left;
  cursor: pointer;
}

.controls-toggle:hover {
  border-color: var(--fg-muted);
}

.controls-toggle-label {
  flex: none;
}

.controls-state {
  font-weight: 600;
  color: var(--fg-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.controls-chevron {
  margin-left: auto;
  color: var(--fg-muted);
  transition: transform var(--dur-fast) var(--ease);
}

.controls-bar[data-open="true"] .controls-chevron {
  transform: rotate(180deg);
}

/* The panel overlays the page rather than pushing it: the bar is sticky, so a
   panel that took up flow space would shove every chart down by its height at
   the exact moment the reader is looking at one. */
.controls-panel {
  display: none;
}

.controls-bar[data-open="true"] .controls-panel {
  display: grid;
  gap: var(--sp-5);
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  z-index: 1;
  padding: var(--sp-5) var(--gutter) var(--sp-6);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-panel);
}

/* ---- the wide form: everything inline, no disclosure ---------------------- */

/* THE DISCLOSURE BREAKPOINT: 940px. Also at the note at the top of this section
   and in assets/js/compare.js, which does NOT read the number - it probes whether
   the toggle is displayed - so moving it here orphans that comment rather than
   breaking the behaviour. */
@media (min-width: 940px) {
  .controls-toggle {
    display: none;
  }

  .controls-panel,
  .controls-bar[data-open="true"] .controls-panel {
    display: flex;
    position: static;
    align-items: center;
    gap: var(--sp-3) var(--sp-6);
    padding: 0;
    border: 0;
    box-shadow: none;
  }
}

/* ONE ROW: `Machine [chips] | Scale [chips]`, label at the far left of each set,
   everything on one baseline.

   This was a <fieldset> with a floated <legend>, and the float is what the owner
   was looking at on 2026-08-27: "Machine is offset from the buttons, they are not
   centered". A <legend> is not a flex item. Every engine lifts it into the
   fieldset's own legend slot at the top border, so `align-items: center` on the
   fieldset could never reach it - and .eyebrow's own `margin-bottom` pushed it up
   again on top of that. float made it sit on the same line; nothing made it sit on
   the same CENTRE line.

   So the group is a plain flex container with `role="radiogroup"` and an
   `aria-labelledby` pointing at the visible label. That is not a downgrade from
   fieldset/legend: a <fieldset> maps to the generic `group` role, while these are
   native radios and `radiogroup` is the role that actually describes them. The
   accessible name is the same word on screen, so the two cannot drift. */
.control-set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  min-width: 0;
}

/* The label wears .eyebrow for its case, tracking and weight - components.css owns
   those - and overrides only what makes it read as the heading of its group rather
   than as one more chip. The size step is what the owner asked for: "Machine on the
   row on the far left, then, smaller font, laid out are the buttons."

   `margin: 0` is load-bearing, not tidying. .eyebrow carries a bottom margin for its
   normal job in a section rail, and inside a centred flex row that margin is exactly
   the offset that made the old bar look wrong. */
.control-legend {
  margin: 0;
  font-size: var(--step-0);
  color: var(--fg-strong);
  white-space: nowrap;
}

.control-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-width: 0;
}

/* In the panel the chips must be allowed to SHRINK to the panel width before they
   can wrap inside it: `flex: none` sizes a flex item to its max-content width, so
   the row stayed 420px wide inside a 335px panel and "All three" simply left the
   screen. Only the inline form, which has the room, refuses to wrap. */
/* THE DISCLOSURE BREAKPOINT: 940px. Also at the note at the top of this section
   and in assets/js/compare.js, which does NOT read the number - it probes whether
   the toggle is displayed - so moving it here orphans that comment rather than
   breaking the behaviour. */
@media (min-width: 940px) {
  .control-set,
  .control-options {
    flex: none;
    flex-wrap: nowrap;
  }

  /* The rule between the two sets, drawn by the second one so it can only ever
     appear BETWEEN them. A third group would get one for free; a group deleted
     takes its own with it. Decorative, so it is a pseudo-element and not a
     character in the markup that a screen reader would read out as "or".

     Only in the inline form: in the narrow disclosure the panel is a stacked grid,
     where a vertical rule between rows would be pointing the wrong way. */
  .control-set + .control-set::before {
    content: "";
    align-self: stretch;
    width: 1px;
    min-height: var(--tap);
    margin-inline-end: var(--sp-2);
    background: var(--border-strong);
  }
}

.chip {
  position: relative;
  display: inline-flex;
  flex: none;
}

/* The input keeps its size so it stays focusable and hit-testable; the span is
   the visible control. No :has() anywhere - input:checked + span is enough. */
.chip input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.chip span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding-inline: var(--sp-4);
  background: var(--bg);
  border: 1px solid var(--border);
  /* The same radius every other control on the site wears. These were --r-full,
     which is what the owner meant by "these pill buttons" on 2026-08-27. */
  border-radius: var(--r-md);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--track-ui);
  color: var(--fg-muted);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}

.chip input:hover + span {
  color: var(--fg-strong);
  border-color: var(--border-strong);
}

.chip input:checked + span {
  background: var(--gold-solid);
  border-color: var(--gold-solid);
  color: var(--on-accent);
}

/* The input is `opacity: 0`, and that hides its own outline along with it, so the
   ring has to be drawn on the sibling. Same token as base.css's :focus-visible, so
   retuning the ring reaches the seven chips here too. */
.chip input:focus-visible + span {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.key {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

.key-swatch {
  width: 1rem;
  height: .625rem;
  border-radius: var(--r-xs);
  flex: none;
}

.key-rayclay .key-swatch { background: var(--gold); }
.key-browser .key-swatch { background: var(--clay); }
.key-native .key-swatch { background: var(--violet); }

/* The two non-RayClay fills differ in hue and barely in luminance, and this strip
   is 23,000px away from the last chart it explains. So the renderer is also
   written in words in every table, and this line says where to find it. */
.key-foot {
  flex-basis: 100%;
  max-width: var(--measure);
  color: var(--fg-dim);
}

.legend-bar {
  padding-block: var(--sp-4);
}

.controls-fallback {
  max-width: var(--measure);
  margin-top: var(--sp-3);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

/* The sticky controls bar sits under the header, so an anchor landing has to
   clear both. base.css sets this for the header alone; only this page has a
   second sticky row, so only this page raises it - by the height of the form
   actually on screen, which is one 44px row narrow and one 44px row wide. */
:root[data-js] {
  scroll-padding-top: calc(var(--header-h) + 3.5rem + var(--sp-4));
}

/* ==========================================================================
   3. Chart groups
   ========================================================================== */

/* Sets its own vertical rhythm outright rather than overriding .section, which it
   used to carry as well. Both selectors set padding-block at the SAME specificity
   (0,1,0), so which one won was decided purely by the order of the <link> tags. It
   happened to be the wanted one. Nothing declared that dependency and nothing tested
   it, so a reordering would have silently changed the spacing of five sections. */
.chart-section {
  padding-block: clamp(2.5rem, 1.5rem + 4vw, 4.5rem);
  border-bottom: 1px solid var(--border);
}

.chart-group-title {
  font-size: var(--step-3);
}

.chart-group-note {
  max-width: var(--measure);
  margin-top: var(--sp-3);
  color: var(--fg-muted);
}

/* ==========================================================================
   4. One chart

   Anatomy: head (title, subtitle, axis statement) → optional caveat → the bars
   → a <details> holding the same numbers as a real table. The bars are
   aria-hidden decoration; the value beside each one is the actual content.
   ========================================================================== */

/* .is-static is a JS-only hook and deliberately carries no style: it marks the
   one chart that exists on a single machine (the macOS GPU split), so compare.js
   leaves its bars and its axis alone whatever the controls say. */
.chart {
  margin-top: var(--sp-8);
  padding: clamp(1.25rem, 3vw, 2rem);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
}

.chart-head {
  padding-bottom: var(--sp-4);
  margin-bottom: var(--sp-5);
  border-bottom: 1px solid var(--border);
}

.chart-title {
  font-size: var(--step-2);
}

.chart-sub {
  margin-top: var(--sp-2);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.chart-axis {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.axis-kind {
  font-weight: 600;
  color: var(--fg);
}

.axis-range {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.axis-warn:not(:empty) {
  padding: .1em .5em;
  background: var(--bg-well);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--fg-strong);
  font-weight: 600;
}

/* A caveat that qualifies THIS chart sits between the head and the bars, so it
   cannot be scrolled past on the way to the number it qualifies.

   THE CARD FILLS THE CHART; THE TEXT KEEPS A READABLE MEASURE. This was
   `max-width: var(--measure)`, which capped the CARD itself at 68ch and left it
   ending at barely half the width of the bars beside it - a callout that read as
   truncated rather than deliberate and, being that narrow, far taller than it
   needed to be. These notes run 550 to 1800 characters, so the height was real.

   Two rules do it, and they regulate each other rather than needing a breakpoint:

     the CARD is a grid of its paragraphs, at least 26rem each. A note of two
     paragraphs becomes two columns of one paragraph each; a note of one gets a
     single full-width cell; below about 700px either becomes one column.

     each PARAGRAPH then sets its own columns, at least 19rem. In a full-width
     cell that is two columns of about 60ch. In a 26rem grid cell there is only
     room for one, so a two-paragraph note does not sub-divide again.

   The rule between columns is not decoration: in multi-column text the eye
   returns to the wrong column without a vertical separator. */
.chart-note {
  display: grid;
  /* min() ON THE FLOOR, or the floor becomes the overflow. A bare minmax(26rem, 1fr)
     track cannot shrink below 416px, so at a 375px viewport this card pushed the whole
     document sideways - the one layout failure this site's own quality floor names by
     name. min(26rem, 100%) lets the track collapse to the container on a phone and
     changes nothing above it. */
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
  gap: var(--sp-5) var(--sp-8);
  margin-bottom: var(--sp-6);
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-well);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.chart-note > p {
  margin: 0;
  columns: 19rem 2;
  column-gap: var(--sp-8);
  column-rule: 1px solid var(--border);
  orphans: 2;
  widows: 2;
}

/* The opening clause of a paragraph is its headline, so it spans that
   paragraph's columns instead of sitting at the top of the first one. A
   <strong> later in the sentence is just emphasis and stays inline. */
.chart-note > p > strong:first-child {
  display: block;
  column-span: all;
  margin-bottom: var(--sp-2);
}

/* The three-machine view is the one that invites a comparison the data does not
   support, so it is the one that carries the warning - and the warning has to be
   ON the chart. It used to live once, in the legend strip, which is 312px above
   the first chart and 23,000px above the last. Every chart now carries its own
   copy and only shows it in the view that needs it; with JS off there is no
   three-machine view, so there is nothing to warn about and nothing is shown. */
.chart .view-note {
  display: none;
}

.chart[data-view="all"] .view-note {
  display: block;
  max-width: var(--measure);
  margin-bottom: var(--sp-5);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

/* ==========================================================================
   5. Bars

   MOBILE FORM (the default, below 760px): three lines per framework -
   name and value on one line, then the track at the FULL width of the card.
   A long framework name and a long number therefore never compete with the
   bar for horizontal space, and the bar keeps ~330px at 375px wide instead of
   the ~140px a three-column row would leave it.

   DESKTOP FORM (760px and up): one line per framework, name | track | value,
   with the value column fixed so every track ends at the same x and the eight
   bars really do share one scale.
   ========================================================================== */

.bars {
  display: grid;
  gap: var(--sp-4);
  padding-left: 0;
  margin: 0;
  list-style: none;
}

.bar-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "name  value"
    "track track"
    "rigs  rigs";
  align-items: baseline;
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
  padding-left: var(--sp-3);
  border-left: 3px solid transparent;
}

/* RayClay is the subject of every chart, so it is marked on every chart - with
   a gold rule and a gold fill, and with its rank spelled out in words beside
   the value. It is marked whether it won or lost; on this page it often lost. */
.bar-row.is-rayclay {
  border-left-color: var(--gold);
}

/* Name, and under it whatever qualifies THIS row: RayClay's rank in words, and
   the machine count when a framework is missing on one. Keeping both out of the
   value cell is what lets every number in a chart align on one right edge. */
.bar-name {
  grid-area: name;
  display: grid;
  gap: var(--sp-1);
  min-width: 0;
}

.bar-label {
  font-size: var(--step-0);
  font-weight: 500;
  letter-spacing: var(--track-ui);
  color: var(--fg);
}

.is-rayclay .bar-label {
  font-weight: 700;
  color: var(--fg-strong);
}

.bar-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
}

/* `.bar-meta:empty` used to sit here to collapse the row on a framework with neither
   a rank nor a note. It could never fire: :empty needs no children and no text at
   all, whitespace included, and the generator always wrote a space and an empty
   .bar-note into it. The generator omits the whole element now, so there is nothing
   left for the rule to match and it is gone rather than left looking like a guard. */

.bar-row > .bar-track {
  grid-area: track;
}

/* A bar is an <svg> holding ONE <rect>: production's CSP is style-src 'self',
   which blocks inline style attributes outright, so the bar's length cannot be
   a custom property in the markup - it is the rect's width presentation
   attribute, which is not CSS. The unfilled track is this element's own
   background, which is why there is no second rect. */
.bar-track {
  display: block;
  width: 100%;
  height: 16px;
  background: var(--bg-well);
  border-radius: var(--r-xs);
  overflow: hidden;
}

/* 2px rounded data-end. No minimum width: a bar that is 0.6% of the longest is
   0.6% of the longest, and on the download chart that is the whole argument. */
.bar-fill {
  rx: 2px;
  fill: var(--clay);
  transition: width var(--dur) var(--ease);
}

[data-family="native"] .bar-fill { fill: var(--violet); }
[data-family="rayclay"] .bar-fill { fill: var(--gold); }

.bar-row > .bar-value {
  grid-area: value;
}

.bar-value {
  text-align: right;
}

.bar-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--fg-strong);
}

/* RayClay's rank, spelled out, on the one row that is the subject of the page.
   NOT in gold: --gold is 4.28:1 on the light card, which is AA-large only and
   this text is 13px. Ink on the well is 13.96:1 light and 14.22:1 dark, and the
   gold rule beside it already carries the identity. */
.bar-rank {
  padding: .05em .5em;
  background: var(--bg-well);
  border-radius: var(--r-sm);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--fg-strong);
}

.bar-note {
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.bar-note:empty {
  display: none;
}

/* ---- the three-machine view ---------------------------------------------
   Pre-rendered and hidden, never built at runtime. Each machine gets its own
   labelled track: position and a written name carry which machine it is, so
   nothing here depends on telling three colours apart.
   -------------------------------------------------------------------------- */

.bar-rigs {
  display: none;
  grid-area: rigs;
  padding-left: 0;
  margin: 0;
  list-style: none;
}

.chart[data-view="all"] .bar-rigs {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}

.chart[data-view="all"] .bar-row > .bar-track,
.chart[data-view="all"] .bar-row > .bar-nobar,
.chart[data-view="all"] .bar-row > .bar-value {
  display: none;
}

.rig-row {
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  grid-template-areas:
    "rigname rigvalue"
    "rigtrack rigtrack";
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
}

.rig-name {
  grid-area: rigname;
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.rig-row > .bar-track {
  grid-area: rigtrack;
  height: 10px;
}

/* A machine whose best framework reads exactly 0 has no "x the best" to draw, so
   it gets this instead of a bar. An empty track would have been a lie in bar form:
   a true 0.00 ms/min and the largest reading on the same machine rendered
   identically, under an axis caption that claimed to describe them both.
   (This note used to cite "62.49 ms/min" as the second of those. That reading
   exists nowhere in the extract; it was removed from the page on 2026-08-24 and
   the generator now refuses to publish a CPU figure no chart prints.) */
/* Sized to its words, not stretched across the track: a full-width dashed box
   reads as a bar at maximum length, which is the misreading this replaced.
   The look is written once; the two placements below differ only in where they
   sit and whether they start visible. `min-height` is deliberately absent: these
   are as tall as the words in them, and the 16px copied from the bar track made
   the shorter of the two boxes taller than the text it holds. */
.rig-nobar,
.bar-nobar {
  justify-self: start;
  align-items: center;
  padding-inline: var(--sp-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-xs);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.rig-nobar {
  grid-area: rigtrack;
  display: flex;
}

/* The top-level row's version of .rig-nobar, for a framework with no value in the
   view being shown. Flutter WITHHELD its macOS unfocused reading, and this row used to
   draw a full-width empty track for it - pixel-identical to RayClay's legitimate true
   0% on the Linux view, so "refused" and "measured zero" looked the same. The page's
   own caveat already promised there was no bar here; now there is not one.
   .is-nobar is the JS-off state shipped in the markup; compare.js toggles it. */
.bar-nobar {
  display: none;
}

.bar-row.is-nobar > .bar-track { display: none; }
.bar-row.is-nobar > .bar-nobar { display: flex; }

.rig-value {
  grid-area: rigvalue;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--step--1);
  color: var(--fg);
  text-align: right;
}

@media (min-width: 760px) {
  .bars {
    gap: var(--sp-5);
  }

  .bar-row {
    grid-template-columns: clamp(7rem, 14vw, 11rem) minmax(0, 1fr) minmax(6.5rem, auto);
    grid-template-areas:
      "name track value"
      "rigs rigs  rigs";
    align-items: center;
  }

  .bar-value {
    white-space: nowrap;
  }

  .rig-row {
    grid-template-columns: 5rem minmax(0, 1fr) minmax(11rem, auto);
    grid-template-areas: "rigname rigtrack rigvalue";
  }

  /* In the three-machine view the track and the value cell are gone, so the
     desktop row becomes two columns: the framework name, and its three machines
     beside it. Indenting the machines under an empty name cell instead - which is
     what this used to do - left a ~180x90px hole at the top-left of every one of
     the 96 rows, and the name was not vertically related to the rows it labelled. */
  .chart[data-view="all"] .bar-row {
    grid-template-columns: clamp(7rem, 14vw, 11rem) minmax(0, 1fr);
    grid-template-areas: "name rigs";
    align-items: start;
    column-gap: var(--sp-5);
  }

  .chart[data-view="all"] .bar-rigs {
    gap: var(--sp-2);
    padding-left: 0;
  }
}

/* ==========================================================================
   6. The table under every chart

   Bars are aria-hidden; this is where the numbers actually live, and it is a
   real <table> so it is selectable, findable with Ctrl+F, and readable with
   CSS off. Wide tables scroll inside this box and never the page.
   ========================================================================== */

.chart-data {
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
}

.chart-data summary {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--track-ui);
  color: var(--fg-muted);
  cursor: pointer;
}

.chart-data summary:hover {
  color: var(--fg-strong);
}

/* WHY THIS TABLE NEEDS THE SCROLL SHADOW. These tables are the JS-off fallback for
   every per-machine reading, and at 375px they clip 269-488px of themselves - the
   Windows and Blended columns are simply not there. On any platform with overlay
   scrollbars there was nothing at all to say so. The shadow is drawn by
   components.css section 8.0, which names .table-scroll; all this rule owes it is
   --scroll-cover, the colour the table is painted on. */
.table-scroll {
  margin-top: var(--sp-2);
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  scrollbar-width: thin;
  /* Same ink as every other scroll container here. Without it the sixteen data
     tables drew a near-white UA scrollbar in the dark theme, which is the defect
     components.css section 8 was written to remove everywhere else. */
  scrollbar-color: var(--fg-dim) transparent;
  --scroll-cover: var(--bg);   /* the shadow itself: components.css section 8.0 */
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step--1);
}

/* The table's title, kept OUT of the scrolling box: a <caption> takes the table's
   width, so at 375px this text was 741px wide and had to be swiped to read. The
   table points at it with aria-labelledby, so the accessible name is unchanged. */
.table-caption {
  max-width: var(--measure);
  margin-top: var(--sp-4);
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.tbl-meta {
  display: block;
  margin-top: var(--sp-1);
  color: var(--fg-dim);
}

.tbl-note {
  color: var(--fg-dim);
}

/* The bar colours separate "browser engine" from "native renderer" by hue alone
   (1.20:1 in light, 1.09:1 in dark), which is invisible in greyscale and to a
   deuteranope, and the only legend is thousands of pixels up the page. So the
   distinction is also written here, in words, in every table. */
.tbl-family {
  display: block;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--step--1);
  color: var(--fg-muted);
  white-space: nowrap;
}

.data-table th,
.data-table td {
  padding: var(--sp-2) var(--sp-4);
  border-bottom: 1px solid var(--border);
  text-align: right;
  white-space: nowrap;
}

/* One table on this page holds words rather than numbers: the machine
   specifications. Right alignment is correct for a column of figures and wrong for
   a column of model names, which it leaves ragged down the left where the eye
   starts. Scoped, because every other table here IS figures. */
.spec-table th,
.spec-table td {
  text-align: left;
}

.data-table thead th {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--fg-muted);
  vertical-align: bottom;
}

.data-table th[scope="row"] {
  text-align: left;
  font-weight: 600;
  color: var(--fg-strong);
}

.data-table td {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.data-table tbody tr:last-child th,
.data-table tbody tr:last-child td {
  border-bottom: 0;
}

/* ==========================================================================
   7. Where RayClay loses
   ========================================================================== */

.loss-list {
  display: grid;
  gap: var(--sp-6);
  max-width: var(--measure);
  margin-top: var(--sp-8);
  padding-left: 0;
  list-style: none;
}

/* A .split-card with the accent moved to the leading edge and painted --clay,
   which this site reserves for "the problem" and which is the whole point of the
   section. The tighter block padding and the smaller radius are deliberate: these
   sit in a list of five, one after another, where .split-card's --r-xl and even
   --sp-6 read as five separate panels rather than five entries. */
.loss {
  padding: var(--sp-5) var(--sp-6);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-left: 3px solid var(--clay);
  border-radius: var(--r-lg);
}

.loss-title {
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: var(--track-sub);
}

.loss-body {
  margin-top: var(--sp-3);
  font-size: var(--step-0);
  color: var(--fg-muted);
}

/* No `.loss-body strong` rule here: base.css's `:where(strong, b)` matches the
   element directly, so it beats .loss-body's inherited --fg-muted without help. */

/* ==========================================================================
   8. Method
   ========================================================================== */

.method-head {
  margin-top: var(--sp-10);
  font-size: var(--step-2);
}

.method-head + .section-body,
.method-head + .table-scroll {
  margin-top: var(--sp-4);
}

.method-list {
  max-width: var(--measure);
}

#method .table-scroll,
#method .code-figure {
  max-width: 46rem;
}

