/* Portal-specific components on top of the vendored brand system (SF-266).
   Extends — never overrides — tokens.css + style.css. Token vars only;
   no raw colors, sizes, or fonts (brand SYSTEM.md rule). */

/* ---- status regions (loading / error / empty) ---------------------- */
.state {
  font-family: var(--f-mono);
  font-size: var(--text-sm);
  color: var(--ink-2);
  padding: var(--space-6) 0;
  line-height: var(--leading-snug);
}
.state-error { color: var(--blind); }
.state-empty, .state-loading { color: var(--ink-3); }
.state[hidden] { display: none; }
[hidden] { display: none !important; }

/* ---- sortable table headers ---------------------------------------- */
/* The button inherits the thead label treatment from style.css; only the
   chrome reset and the sort arrow live here. */
.sort-button {
  background: none;
  border: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.sort-button:hover { color: var(--ink); }
.sort-button .arrow { opacity: 0; }
th[aria-sort="ascending"] .sort-button .arrow,
th[aria-sort="descending"] .sort-button .arrow { opacity: 1; }

/* ---- verified badge -------------------------------------------------- */
/* Square, mono, gain-colored: color carries the one story meaning. */
.badge-verified {
  display: inline-block;
  font-family: var(--f-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--success-text-high);
  background: var(--success-bg-subtle);
  border: var(--border-hairline) solid var(--success-border);
  border-radius: var(--radius-none);
  padding: 0 var(--space-2);
  white-space: nowrap;
}

/* ---- tables: portal extras ------------------------------------------ */
.table-wrap { overflow-x: auto; margin: 0 0 var(--space-5); }
.table-wrap table { margin: 0; }
/* min-width keeps narrow viewports from crushing the column to a sliver —
   the .table-wrap x-scroll takes over instead. Derived from the space scale
   (2 × the largest step) so it stays tied to the token system. */
td.cell-wrap { white-space: normal; overflow-wrap: break-word; min-width: calc(2 * var(--space-12)); }
table .btn { white-space: nowrap; }

/* ---- spec page run region ------------------------------------------- */
.run-region { margin: 0 0 var(--space-5); }
.quiet { font-family: var(--f-mono); font-size: var(--text-micro); color: var(--ink-3); }

/* ---- screen-reader-only text ----------------------------------------- */
/* Non-visual a11y utility (standard clip pattern); carries meaning that
   color alone must not — e.g. the sota row highlight. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---- benchmark tab strip --------------------------------------------- */
/* Same visual language as .rail .crumbs (hairline underline for the active
   item) rather than a new pattern — one nav idiom for the whole portal. */
.tabstrip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  border-bottom: var(--border-hairline) solid var(--line);
  margin: 0 0 var(--space-5);
  font-family: var(--f-mono);
  font-size: var(--text-sm);
}
.tabstrip a {
  display: inline-block;
  padding: var(--space-3) 0;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: var(--border-strong) solid transparent;
}
.tabstrip a:hover { color: var(--ink); }
.tabstrip a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--accent-solid);
}
/* WHY: benchmark.html ships the nav empty and initTabStrip's failure path is
   deliberately silent, so on an API error (or zero registered benchmarks) the
   bare element still painted its border-bottom — a hairline rule floating above
   the masthead — and still announced an empty "Benchmarks" navigation landmark. */
.tabstrip:empty { display: none; }

/* ---- responsive ------------------------------------------------------ */
@media (max-width: 639px) {
  /* sf:// deep-links are not useful on narrow screens; collapse the column. */
  .col-run { display: none; }
}

/* ---- mark column (OME-769) ------------------------------------------- */
/* A column rather than a span inside the spec cell, so the browser handles the
   alignment — see the WHY in benchmark.js's COLUMNS. Narrow and never wrapping:
   it holds one mark or nothing, and currently always nothing (the SOTA medal is
   descoped to OME-771, the frontier mark comes with OME-770). */
.col-mark {
  width: 1%;
  white-space: nowrap;
}

/* OME-923 part B: the Pareto-frontier mark — no row is no-worse on both axes and better on one.
   Drawn in CSS rather than set as a glyph: IBM Plex Mono is not guaranteed to carry
   U+25C6, and a font fallback would render this mark differently machine to machine.
   Colour is --info, deliberately NOT --gain: gold is rationed to the win and the
   highest-score row already holds it, so a gold mark here would collapse two separate
   claims into one look. Not --success either, because green means verified and OME-820
   withdrew that signal. */
.pareto-mark {
  display: inline-block;
  width: 8px;
  height: 8px;
  background: var(--info-text-low);
  transform: rotate(45deg);
}
/* Matches the markbox convention: the deep text-low tone reads on a light ground, the
   vivid solid reads on a dark one. */
[data-theme="dark"] .pareto-mark {
  background: var(--info-solid);
}

/* Forced-colors (Windows High Contrast) overrides `background` on non-text elements, which
   would erase this mark completely and leave the row with NO per-row signal — precisely the
   failure "never colour alone" exists to prevent, since the shape is the carrier here. Opt out
   of the override and paint with a system colour so the diamond survives. */
@media (forced-colors: active) {
  .pareto-mark {
    forced-color-adjust: none;
    background: CanvasText;
  }
}

/* ---- rail pinning: the one deliberate override in this file ---------- */
/* WHY: style.css ships `.rail { position: relative }` plus a `.rail.stuck`
   variant its own comment documents as "(JS toggles .stuck + a spacer to hold
   its place)" — the system expects the host page to supply that scroll
   listener. This portal never has, so the rail scrolled away while
   `.rail .toggle` (position: fixed, no background/border per the system) stayed
   pinned top-right — leaving a bare "◐ light" glyph floating over page content,
   and over benchmark.html's rightmost table cells at ~1000px viewports.
   Restoring `sticky` keeps the rail under the fixed toggle at all scroll
   offsets, which is the behavior this portal had before the v2 migration.
   AIDEV-NOTE: this knowingly breaks the "extends — never overrides" rule at the
   top of this file. The system-faithful fix is to implement the `.stuck` toggle
   + spacer in JS; that is new untested behavior, so it is tracked separately
   rather than folded into OME-768. Drop this block when that lands. */
.rail { position: sticky; top: 0; }

/* WHY: the byte-matched style.css gives .badge-verified a ::before glyph masked
   from /assets/remix/System/check-line.svg, an icon set this portal does not
   vendor — so every verified badge fired a 404. createVerifiedBadge() already
   renders a literal "✓ verified", so the pseudo-element is redundant here as
   well as broken; suppress it rather than vendoring an icon set for one glyph.
   AIDEV-NOTE: `content: none` alone does NOT stop the fetch — Chrome still
   resolves the mask URL and fires the 404 (verified in DevTools). The mask
   itself has to be cleared, so keep all three declarations. If this portal ever
   vendors the remix icons, drop this block instead of leaving two checkmarks.
   Found in review of PR #558. */
.badge-verified::before {
  content: none;
  -webkit-mask: none;
  mask: none;
}

/* ---- fragment targets clear the rail (OME-839) --------------------- */
/* WHY: the rail is sticky at top:0 and its JS re-pins it as `fixed` once scrolled
   past (style.css:44), so a bare fragment jump parks the target under it. Measured
   in Chrome on `index.html#benchmarks`: the heading landed at y=7 with the rail's
   bottom at y=44 — 37px of a 57px heading hidden. Nothing in the vendored CSS sets
   scroll-padding, so this is additive, and it fixes every future in-page anchor
   rather than the one this ticket adds. --rail-h is the same token the rail's own
   height uses, so the two cannot drift. */
html { scroll-padding-top: calc(var(--rail-h) + var(--space-4)); }

/* ---- masthead nav links (OME-839) ---------------------------------- */
/* Adopted from the leaderboard-mvp design, which defines these in its own board.css —
   they are NOT in the vendored style.css, so they belong here.
   WHY the group is a container and not three bare anchors: the theme toggle is
   `position: fixed; right: var(--space-5)` (style.css:58 — it pins itself because the
   rail's sticky is defeated by the page's overflow-x clipping), so it takes NO flex space
   and the rail will happily lay links out underneath it. Measured in Chrome, "◐ light"
   is 55px wide, and the collisions were real in both directions:
     1200px — the mockup's 32px end margin left `docs` overlapping the toggle by 23px;
      375px — the rail wraps (style.css:667) and `github` landed under the toggle.
   A container fixes both: --space-11 reserves the toggle's footprint on desktop, and
   `flex: 0 0 100%` moves the whole group onto its own wrapped line on mobile, where the
   toggle only ever shares row one. */
.rail .rail-nav { display: flex; gap: var(--space-4); margin-right: var(--space-11); }
.rail .rail-link { color: var(--ink-2); text-decoration: none; }
.rail .rail-link:hover { color: var(--ink); }
@media (max-width: 620px) {
  .rail .rail-nav { flex: 0 0 100%; margin-right: 0; gap: var(--space-3); }
}
/* ---- two-card summary strip (OME-820) ------------------------------ */
/* style.css sets `.stats { grid-template-columns: repeat(3,1fr) }` and is vendored,
   so it is not edited. The leaderboard summary lost its third metric ("Verified
   rows") when the verification UI was withdrawn — verified_by_screamingface became
   meaningless — see benchmark.js — and the metric was dropped. Without a modifier the
   bordered strip
   renders two cards across two thirds of its width, leaving an empty third.
   Scoped to a new class so every other three-card `.stats` strip is untouched.
   OME-821 restores the third metric and this class comes off with it.

   INVARIANT: this MUST stay inside a min-width query. style.css collapses `.stats`
   to one column at max-width 620px, but a media query adds NO specificity — both
   rules are a single class, and portal.css loads after style.css, so an
   unconditional `.stats--two` wins at every viewport and phones get two cramped
   side-by-side cards instead of a stack. 621px is the complement of that 620px
   breakpoint; if the vendored breakpoint moves, this must move with it. */
@media (min-width: 621px) {
  .stats--two { grid-template-columns: repeat(2, 1fr); }
}

/* ---- landing page ---------------------------------------------------------
   Everything else the landing page needs (.kv, .note, .o-mark, .btn, .eyebrow)
   comes from style.css. `.defs` carries no rule by design; it is a hook. */

/* The glossary's first key. Green because green means verified in this system
   and gold is reserved for the win. */
.kv .dt-repro { color: var(--success-text-low); }

/* The rail's last link, so nothing sits under the theme glyph. */
.rail .rail-link--end { margin-right: var(--space-7); }

/* The catalogue's Open link never breaks across lines. */
td.col-open { white-space: nowrap; }

/* The mark's key. `.meta` already carries the mono/micro/ink-2 treatment every small label on
   this page uses; this only lays it out. */
.legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* WHY a generated separator rather than markup: the frontier item hides itself on boards that
   can carry no mark, and a literal separator span would leave a dangling middot behind it. */
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* INVARIANT: `:not([hidden])` on BOTH sides. `display: none` does not remove an element from
   the sibling chain, so a bare `+`/`~` still matches a hidden predecessor — with the frontier
   item hidden, the legend rendered a LEADING middot (verified in Chrome, 2026-09-01). The
   general sibling combinator is deliberate: it stays correct if more than one item ever hides. */
.legend-item:not([hidden]) ~ .legend-item:not([hidden])::before {
  content: "·";
  color: var(--ink-3);
  margin-right: var(--space-2);
}
.legend-item[hidden] {
  display: none;
}

/* The scroll container is focusable (see benchmark.html), so it needs a visible focus ring like
   any other focus target — otherwise keyboard users land somewhere invisible. */
.table-wrap:focus-visible {
  outline: var(--border-strong) solid var(--focus);
  outline-offset: 2px;
}

/* ---- score/cost Pareto chart (OME-923 part C) ----------------------- */
.pareto-chart-section { margin-top: var(--space-7); }
.pareto-chart-figure { background: var(--bg); }
.pareto-chart__svg { min-width: 560px; }
.pareto-chart-scroll { overflow-x: auto; }
.pareto-chart-scroll:focus-visible {
  outline: var(--border-strong) solid var(--focus);
  outline-offset: 2px;
}

.pareto-chart__grid {
  stroke: var(--line);
  stroke-width: var(--border-hairline);
}
.pareto-chart__axis,
.pareto-chart__tick-mark {
  stroke: var(--ink-3);
  stroke-width: var(--border-hairline);
}
.pareto-chart__tick,
.pareto-chart__gutter-label { fill: var(--ink-2); }
.pareto-chart__axis-label { fill: var(--ink); font-weight: var(--weight-semibold); }
.pareto-chart__frontier-line {
  fill: none;
  stroke: var(--info-text-low);
  stroke-width: var(--border-strong);
}
.pareto-chart__dominated-point { fill: var(--ink-3); }
.pareto-chart__frontier-point { fill: var(--info-text-low); stroke: var(--bg); stroke-width: 3px; }
.pareto-chart__unpriced-point { fill: var(--bg); stroke: var(--ink-3); stroke-width: var(--border-strong); }
.pareto-chart__leader-ring { fill: none; stroke: var(--gain); stroke-width: var(--border-strong); }
.pareto-chart__gutter-line {
  stroke: var(--line-2);
  stroke-width: var(--border-hairline);
  stroke-dasharray: 4 4;
}
.pareto-chart__point:hover .pareto-chart__dominated-point,
.pareto-chart__point:hover .pareto-chart__frontier-point,
.pareto-chart__point:hover .pareto-chart__unpriced-point { stroke: var(--ink); }

.pareto-chart-caption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}
.pareto-chart-key { display: inline-flex; align-items: center; gap: var(--space-2); }
.pareto-chart-key__dominated,
.pareto-chart-key__frontier,
.pareto-chart-key__leader,
.pareto-chart-key__unpriced {
  display: inline-block;
  width: 10px;
  height: 10px;
  box-sizing: border-box;
}
.pareto-chart-key__dominated { border-radius: 50%; background: var(--ink-3); }
.pareto-chart-key__frontier { width: 8px; height: 8px; background: var(--info-text-low); transform: rotate(45deg); }
.pareto-chart-key__leader { border-radius: 50%; border: var(--border-strong) solid var(--gain); }
.pareto-chart-key__unpriced { border-radius: 50%; border: var(--border-strong) solid var(--ink-3); }

[data-theme="dark"] .pareto-chart__frontier-line { stroke: var(--info-solid); }
[data-theme="dark"] .pareto-chart__frontier-point { fill: var(--info-solid); }
[data-theme="dark"] .pareto-chart-key__frontier { background: var(--info-solid); }

@media (forced-colors: active) {
  .pareto-chart__frontier-line,
  .pareto-chart__frontier-point,
  .pareto-chart__dominated-point,
  .pareto-chart__unpriced-point,
  .pareto-chart__leader-ring { forced-color-adjust: auto; stroke: CanvasText; }
  .pareto-chart__frontier-point { fill: CanvasText; }
  /* The key's swatches are spans coloured by `background`, which forced-colors strips — the
     block above rescues the chart's own marks but not the legend that explains them, leaving
     four labels with nothing to map them to. Same omission the table mark had. */
  .pareto-chart-key__dominated,
  .pareto-chart-key__frontier,
  .pareto-chart-key__leader,
  .pareto-chart-key__unpriced {
    forced-color-adjust: none;
    background: CanvasText;
    border-color: CanvasText;
  }
  .pareto-chart-key__leader,
  .pareto-chart-key__unpriced {
    background: Canvas;
  }
}

@media (max-width: 620px) {
  .pareto-chart-section { margin-top: var(--space-5); }
  .pareto-chart-caption { align-items: flex-start; }
}
