/* ==========================================================================
   advancerate.io — app.css
   Hand-written, no component library. Refined brutalism per
   .kiro/steering/ui-aesthetic.md: paper ground, near-black ink, ONE loud
   accent (signal red). Hard 1–2px rules, offset SOLID shadows, no gradients,
   no glassmorphism, no blurry shadows, ~no border-radius. Type does the heavy
   lifting; numbers are monospaced tabular figures so the ledger columns align.

   Organization
     0. Fonts (self-hosted, SIL OFL — see fonts/LICENSE.md)
     1. Design tokens (palette, spacing scale, type scale, rules/shadows)
     2. Reset + document base
     3. Typography
     4. Focus + a11y utilities (bold, deliberate focus rings)
     5. Masthead + wordmark
     6. Layout + explorer grid
     7. Filters (form controls)
     8. Buttons
     9. Results table — the centerpiece (ledger/terminal hybrid)
    10. Low-confidence flag + explanatory messages
    11. Draft-date timeline
    12. Methodology + archetype rules
    13. Error fragment
    14. Colophon (footer)
    15. Motion + responsive
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Fonts — self-hosted woff2, never hotlinked (aesthetic steering).
      Space Grotesk = distinctive grotesque for headings/body.
      Space Mono    = monospace for ALL numbers (naturally tabular figures).
      font-display: swap keeps text visible while the face loads; the system
      fallbacks below mean it stays readable even if a font 404s.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-400.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-500.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-700.woff2") format("woff2");
}
@font-face {
  font-family: "Space Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/space-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "Space Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/space-mono-700.woff2") format("woff2");
}

/* --------------------------------------------------------------------------
   1. Design tokens
      Palette is tight and intentional: paper + ink as the core, TWO neutrals
      (a darker paper for panels/zebra, a warm gray for secondary text), and
      ONE loud accent. Semantic states (low-confidence) reuse the accent — no
      new hue. All text pairings are checked for WCAG AA contrast on paper.
   -------------------------------------------------------------------------- */
:root {
  /* Palette --------------------------------------------------------------- */
  --paper: #f4f1ea;        /* ground */
  --paper-2: #eae6dc;      /* neutral 1: panels, table zebra, insets */
  --ink: #141414;          /* near-black ink: borders, headings, body */
  --ink-2: #5a5751;        /* neutral 2: secondary/meta text (AA on paper) */
  --accent: #cc2a1e;       /* the one loud accent: signal red */
  --accent-ink: #f4f1ea;   /* text placed ON the accent (AA on accent) */

  /* Type families --------------------------------------------------------- */
  --font-grotesk: "Space Grotesk", "Helvetica Neue", Arial, system-ui, sans-serif;
  --font-mono: "Space Mono", ui-monospace, "SFMono-Regular", "Menlo", monospace;

  /* Type scale (documented). Base is 16px; steps are a tight ~1.2–1.35 ramp.
     Numbers/data use --fs-num in the mono face. Headings run big and tight. */
  --fs-xs: 0.75rem;    /* 12px — fine print, flags */
  --fs-sm: 0.8125rem;  /* 13px — meta, rules, labels */
  --fs-base: 0.9375rem;/* 15px — body */
  --fs-num: 0.9375rem; /* 15px — tabular figures in the table */
  --fs-md: 1.0625rem;  /* 17px — lead paragraphs */
  --fs-lg: 1.375rem;   /* 22px — section headings */
  --fs-xl: 2rem;       /* 32px — page title */
  --fs-2xl: 2.75rem;   /* 44px — wordmark */

  --lh-tight: 1.05;
  --lh-snug: 1.25;
  --lh-body: 1.55;

  /* Spacing scale (documented). A 4px base step, used everywhere so rhythm is
     consistent and deliberate rather than ad hoc. */
  --sp-1: 0.25rem;  /* 4px  */
  --sp-2: 0.5rem;   /* 8px  */
  --sp-3: 0.75rem;  /* 12px */
  --sp-4: 1rem;     /* 16px */
  --sp-5: 1.5rem;   /* 24px */
  --sp-6: 2rem;     /* 32px */
  --sp-7: 3rem;     /* 48px */
  --sp-8: 4rem;     /* 64px */

  /* Structure: hard edges only. Rules are solid ink; shadows are offset and
     SOLID (never blurred). Radius is effectively off. */
  --rule: 2px solid var(--ink);
  --hair: 1px solid var(--ink);
  --rule-accent: 2px solid var(--accent);
  --shadow-hard: 4px 4px 0 var(--ink);
  --shadow-hard-sm: 3px 3px 0 var(--ink);
  --shadow-accent: 4px 4px 0 var(--accent);
  --radius: 0;

  --measure: 68ch;         /* readable prose width */
  --content-max: 78rem;    /* overall page max width */
}

/* --------------------------------------------------------------------------
   2. Reset + document base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body.page {
  margin: 0;
  background-color: var(--paper);
  /* A faint structural grid — decoration IS structure. Two hairline sets,
     no gradient, no image. Cheap, honest, and it reads as graph paper. */
  background-image:
    linear-gradient(to right, rgba(20, 20, 20, 0.04) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(20, 20, 20, 0.04) 1px, transparent 1px);
  background-size: 2rem 2rem;
  color: var(--ink);
  font-family: var(--font-grotesk);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  font-weight: 400;
  min-height: 100vh;
}

img,
svg {
  max-width: 100%;
  height: auto;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--paper-2);
  padding: 0.1em 0.35em;
  border: var(--hair);
}

/* --------------------------------------------------------------------------
   3. Typography — real hierarchy, tight and confident. Not size soup.
   -------------------------------------------------------------------------- */
h1,
h2,
h3 {
  font-family: var(--font-grotesk);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: -0.02em;
  margin: 0 0 var(--sp-4);
}

h1 {
  font-size: var(--fs-xl);
  text-transform: uppercase;
}

h2 {
  font-size: var(--fs-lg);
}

h3 {
  font-size: var(--fs-md);
  letter-spacing: -0.01em;
}

p {
  margin: 0 0 var(--sp-4);
  max-width: var(--measure);
}

a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
  text-decoration-color: var(--accent);
}

a:hover {
  background: var(--accent);
  color: var(--accent-ink);
  text-decoration-color: var(--accent-ink);
}

em {
  font-style: italic;
}

/* --------------------------------------------------------------------------
   4. Focus + accessibility utilities
      Lean into focus: a bold, high-contrast ring fits the style and makes
      keyboard nav obvious. Applied to every interactive element.
   -------------------------------------------------------------------------- */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  /* solid, never blurred */
  box-shadow: var(--shadow-hard-sm);
}

/* Visible skip-friendly focus fallback for browsers without :focus-visible. */
a:focus,
button:focus,
input:focus,
select:focus {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* Screen-reader-only utility: visually hidden but present in the a11y tree. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   5. Masthead + wordmark
      The wordmark is the signature: a heavy mono lockup, the accent carried on
      "RATE", the ".io" set small in the muted neutral. Boxed with a hard rule
      and a solid offset shadow.
   -------------------------------------------------------------------------- */
.masthead {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: var(--rule);
}

.wordmark {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-2xl);
  line-height: 1;
  letter-spacing: -0.04em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  padding: var(--sp-2) var(--sp-3);
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
}

.wordmark:hover {
  background: var(--paper);
  color: var(--ink);
  box-shadow: var(--shadow-accent);
}

.wordmark__rate {
  color: var(--accent);
}

.wordmark__dot {
  font-size: 0.45em;
  color: var(--ink-2);
  vertical-align: baseline;
  letter-spacing: 0;
}

.masthead__tagline {
  font-size: var(--fs-md);
  color: var(--ink-2);
  margin: var(--sp-4) 0 var(--sp-3);
  max-width: none;
}

.masthead__nav {
  display: flex;
  gap: var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  /* Breathing room below the wordmark box (clears its 4px offset shadow) so the
     nav does not crowd the lockup. */
  margin-top: var(--sp-4);
  /* Align the first nav link's text with the wordmark's TEXT, not the box edge:
     indent by the wordmark's left border (2px) + inner padding (--sp-3), so the
     "A" in "ADVANCE RATES" sits under the "A" in the ADVANCE wordmark. */
  padding-left: calc(var(--sp-3) + 2px);
}

/* The current view (Advance rates / Rosters) reads as selected: solid ink fill,
   like the roster sort/week active pill, so the two views are an obvious toggle. */
.masthead__nav a[aria-current="page"] {
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  padding: 0 var(--sp-1);
}

.masthead__nav a[aria-current="page"]:hover {
  background: var(--accent);
  color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   6. Layout + explorer grid
      Filters sit in a fixed-ish left column; the results ledger takes the
      rest. Deliberate whitespace, boxed regions, visible structure.
   -------------------------------------------------------------------------- */
.layout {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5);
}

.explorer__title {
  margin-bottom: var(--sp-5);
}

/* Roster surface: the scope bar, query-summary chips, and roster list stacked
   full-width (the roster view has no lens rail / side-by-side results, so it is
   a simple vertical flow rather than the advance-rate grid). */
.roster-surface {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  min-width: 0;
}

.explorer__grid {
  display: grid;
  grid-template-columns: minmax(16rem, 22rem) 1fr;
  gap: var(--sp-6);
  align-items: start;
}

/* The explorer surface: a persistent top scope bar spanning full width, a
   left Lens rail, and the query-summary chips + results ledger to its right.
   The query <form> uses display:contents so its children (scope bar + rail
   body) participate directly in this grid (Requirements 6.14, 6.15). */
.explorer__surface {
  display: grid;
  grid-template-columns: minmax(16rem, 22rem) 1fr;
  grid-template-areas:
    "scope scope"
    "chips chips"
    "rail  results"
    "timeline timeline";
  gap: var(--sp-5);
  align-items: start;
}

.query-form {
  display: contents;
}

.query-form__body {
  grid-area: rail;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.query-form__actions {
  margin-top: 0;
}

.chips {
  grid-area: chips;
}

.explorer__surface .results {
  grid-area: results;
}

.explorer__surface .timeline {
  grid-area: timeline;
}

/* Fallback page (input error without JS): simple stacked flow, the query form
   above the panel. No grid areas to satisfy. */
.explorer__fallback {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.explorer__fallback .query-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* --------------------------------------------------------------------------
   7. Filters — form controls as hard-edged instrument panels.
   -------------------------------------------------------------------------- */
.filters {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.filters__group {
  border: var(--rule);
  background: var(--paper);
  margin: 0;
  padding: var(--sp-4);
  box-shadow: var(--shadow-hard-sm);
}

.filters__legend {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0 var(--sp-2);
  margin-left: calc(-1 * var(--sp-1));
  background: var(--ink);
  color: var(--paper);
}

.field {
  margin-top: var(--sp-3);
}

.field:first-of-type {
  margin-top: var(--sp-2);
}

.field__label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  margin-bottom: var(--sp-1);
}

/* One control style for every input/select — no per-widget theming, no
   component-library look. Hard border, square, mono text so typed values and
   options read like data.

   Layout stability: the border is a CONSTANT 2px at rest, focus, and hover —
   only its COLOR changes. A width change (1px hairline -> 2px rule) would grow
   the box and reflow neighbours, so we never touch border-width on state. */
.field__control {
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink);
  background: var(--paper);
  border: var(--rule);
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-3);
  line-height: 1.3;
  appearance: none;
  -webkit-appearance: none;
}

/* Hover emphasis is a COLOR shift to the accent, never a width change — the box
   stays exactly the same size so nothing around it moves. */
.field__control:hover {
  border-color: var(--accent);
}

/* Closed <select> controls: strip the native chrome (appearance:none, above)
   and draw our own hard-edged chevron so the control matches the text/number
   inputs instead of reading as a raw browser default. The open option list is
   native and left as-is. The chevron is an inline SVG background (no layout
   cost) and we reserve right padding so the value never overlaps it. Multi-
   selects (list boxes) keep their native rows, so they are excluded. */
select.field__control:not([multiple]) {
  padding-right: calc(var(--sp-3) * 2 + 0.75rem);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'%3E%3Cpath d='M1 1.5 L7 7.5 L13 1.5' fill='none' stroke='%23141414' stroke-width='2' stroke-linecap='square'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  cursor: pointer;
}

.filters__actions {
  margin-top: var(--sp-1);
}

/* --------------------------------------------------------------------------
   8. Buttons — solid ink block with an offset shadow that "presses" on click.
      .button--ghost is the quieter, secondary affordance.
   -------------------------------------------------------------------------- */
.button {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper);
  background: var(--ink);
  border: var(--rule);
  border-radius: var(--radius);
  padding: var(--sp-2) var(--sp-4);
  cursor: pointer;
  box-shadow: var(--shadow-hard-sm);
  transition: transform 60ms linear, box-shadow 60ms linear;
}

.button:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.button:active {
  /* honest, snappy press: slide into the shadow, no easing flourish */
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 var(--ink);
}

.button--ghost {
  color: var(--ink);
  background: var(--paper);
  font-size: var(--fs-xs);
  padding: var(--sp-1) var(--sp-2);
  box-shadow: none;
}

.button--ghost:hover {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

/* --------------------------------------------------------------------------
   9. Results table — THE CENTERPIECE.
      Dense, ledger-like, spreadsheet/terminal hybrid. Visible rules between
      every row, boxed with a hard border and solid shadow. Numbers are mono
      tabular figures, right-aligned, so columns align to the decimal.
   -------------------------------------------------------------------------- */
.results {
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
  overflow: hidden;
}

.results__caption {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--paper);
  background: var(--ink);
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  /* Ink header bar carries the lens title left + the roster on-ramp right. */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
}

/* "View rosters" on-ramp — a quiet outlined link on the ink header bar (paper
   ink for AA on ink), snapping to the accent on hover/focus like the buttons.
   Sits on the "answer" data-panel bar, echoing the results-ledger weight. */
.results__view-rosters {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: var(--paper);
  text-decoration: none;
  border: 1px solid var(--paper);
  padding: 2px var(--sp-2);
}

.results__view-rosters:hover {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

.results__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-num);
}

/* Column headers: mono, uppercase, a heavy ink underline rule. */
.results__table thead th {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: left;
  color: var(--ink);
  background: var(--paper-2);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule);
}

/* Numeric column headers align RIGHT to sit over their right-aligned values
   (the base thead rule left-aligns by default; this restores match for the
   Advance rate / Entries / Exposure columns). The Draft value header is a
   labelled stack, so it keeps the default left alignment (no .num). */
.results__table thead th.num {
  text-align: right;
}

.results__table tbody tr {
  /* the visible rule between rows — the ledger line */
  border-bottom: var(--hair);
}

.results__table tbody tr:last-child {
  border-bottom: 0;
}

/* Subtle zebra using neutral 1 — reads as ruled paper, not a UI stripe. */
.results__table tbody tr:nth-child(even) {
  background: rgba(20, 20, 20, 0.02);
}

.results__table th,
.results__table td {
  padding: var(--sp-3) var(--sp-4);
  vertical-align: baseline;
}

.results__label {
  text-align: left;
  font-weight: 500;
}

.results__label-name {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-base);
}

/* Archetype rule set beneath the label in the muted neutral — context without
   shouting (Requirement 6.7). */
.results__rule {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--ink-2);
  margin-top: var(--sp-1);
  max-width: 44ch;
}

/* Numeric cells: mono, tabular, right-aligned so decimals stack. */
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  text-align: right;
  white-space: nowrap;
}

/* The advance-rate figure is the hero number — bump weight and size a touch. */
.rate {
  font-weight: 700;
  font-size: 1.05em;
  letter-spacing: -0.01em;
}

.count {
  color: var(--ink);
}

.count__value {
  font-variant-numeric: tabular-nums;
}

/* Click-to-pin label link (archetype Breakdown only): the row label IS the
   affordance. An underlined ink link that flips to the accent on hover/focus —
   the same link treatment as the removable chips, and a real <a href> so it
   works with JS disabled. Pinning filters the population to that construction
   and resets the axis to Overall (the de-conflict, 6.18). */
.results__pin {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 2px;
  cursor: pointer;
}

.results__pin:hover,
.results__pin:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* Low-confidence ROW: a persistent accent left-edge so the whole row reads as
   flagged even before you reach the badge. Reuses the accent, no new hue. */
.results__row--low {
  box-shadow: inset 4px 0 0 var(--accent);
}

/* --------------------------------------------------------------------------
   10. Low-confidence flag + explanatory messages
       The flag is a persistent, obvious accent chip — square, bordered, mono,
       always visible beside the count (Requirement 6.6). It reuses the accent
       rather than introducing a warning-yellow. White-on-accent passes AA.
   -------------------------------------------------------------------------- */
.flag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  padding: 0.1em 0.4em;
  margin-left: var(--sp-2);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  vertical-align: baseline;
}

.flag--low {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Explanatory rows (rate is None) and the empty-slice message — never a blank
   cell/table (Requirements 2.5, 4.12, 6.9). */
.message,
.no-results {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  font-style: normal;
}

.message {
  padding: var(--sp-3) var(--sp-4);
}

.no-results {
  margin: 0;
  padding: var(--sp-5) var(--sp-4);
  border-top: var(--hair);
  background: var(--paper-2);
  max-width: none;
}

/* --------------------------------------------------------------------------
   11. Draft-date timeline — the advance rate across draft dates (Req 11).
        A DATA panel: full-width ink header bar (the "answer" tier of the two
        header motifs), boxed with a hard border + solid shadow, sitting
        full-width below the results ledger. Server-rendered inline SVG only —
        hard strokes, ONE accent line for the series, tabular-mono tick labels;
        no charting library, no JS.
   -------------------------------------------------------------------------- */
.timeline {
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
  overflow: hidden;
}

/* Full-width ink header bar — matches .results__caption so the two data panels
   read as a stacked pair. */
.timeline__caption {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--paper);
  background: var(--ink);
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
}

.timeline__plot {
  padding: var(--sp-4);
  /* Positioning context for the absolutely-placed hover readout below, so the
     readout overlays the chart instead of taking layout space. */
  position: relative;
}

.timeline__svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Axes: hard 2px ink strokes. Grid rules: hairline ink, low emphasis. */
.timeline__axis {
  stroke: var(--ink);
  stroke-width: 2;
}

.timeline__grid {
  stroke: var(--ink);
  stroke-width: 1;
  opacity: 0.18;
}

/* Series lines: THIN ink, distinguished by DASH PATTERN (set inline per series)
   — never a color rainbow (11.13). The loud accent is reserved for the baseline
   reference line so the structural comparison reads first. */
.timeline__line {
  stroke: var(--ink);
  stroke-width: 1.25;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* Legend: name each line by a dash swatch (multi-series). Replaces colliding
   direct end-labels. */
.timeline__legend-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
  padding: 0 var(--sp-4) var(--sp-2);
  list-style: none;
}

.timeline__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink);
}

.timeline__swatch {
  flex: 0 0 auto;
}

.timeline__swatch-line {
  stroke: var(--ink);
  stroke-width: 1.25;
}

.timeline__legend-label--low {
  color: var(--ink-2);
}

/* Series points: solid ink markers; low-confidence points are de-emphasised
   (hollow, reusing the accent — no new hue). */
.timeline__point {
  fill: var(--ink);
  stroke: var(--paper);
  stroke-width: 1;
}

.timeline__point--low {
  fill: var(--paper);
  stroke: var(--accent);
  stroke-width: 1.5;
}

/* Reference line: the population's overall advance rate, dashed accent. */
.timeline__reference {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 5 4;
  opacity: 0.8;
}

/* Tick/label text: mono tabular figures, small and quiet. */
.timeline__tick {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--ink-2);
}

.timeline__tick--ref {
  fill: var(--accent);
}

/* Hover/focus readout populated by timeline.js. It is ABSOLUTELY positioned as
   an overlay pinned to the bottom of the plot, so it is out of layout flow
   entirely — appearing or clearing it can never move the legend (or anything
   else). pointer-events:none so it never blocks hovering the points beneath it.
   Empty -> hidden (nothing shown, still no layout effect). */
.timeline__readout {
  position: absolute;
  left: var(--sp-4);
  right: var(--sp-4);
  bottom: var(--sp-3);
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}

.timeline__readout:empty {
  display: none;
}

/* Caption/legend: the baseline meaning + the low-confidence explanation. */
.timeline__legend {
  margin: 0;
  padding: 0 var(--sp-4) var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}

.timeline__empty {
  margin: 0;
  padding: var(--sp-5) var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   12. Methodology + archetype rules
       A boxed reference panel. The archetype rules render as a hard-ruled
       definition list — name in ink, rule in mono — so it reads like the
       legend on a technical chart (Requirements 3.8, 6.7).
   -------------------------------------------------------------------------- */
.methodology {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5) var(--sp-7);
  border-top: var(--rule);
}

.methodology__title {
  font-size: var(--fs-lg);
}

.methodology__note {
  font-size: var(--fs-md);
  color: var(--ink);
  max-width: var(--measure);
}

.methodology__subtitle {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: var(--sp-5);
}

.archetype-rules {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: 0;
  margin: var(--sp-4) 0 0;
  border: var(--rule);
  border-bottom: 0;
}

.archetype-rules__item {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule);
  border-right: var(--hair);
}

.archetype-rules__name {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-base);
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

.archetype-rules__name::before {
  /* a tiny accent tick — human touch, one hue */
  content: "▸ ";
  color: var(--accent);
}

.archetype-rules__rule {
  margin: var(--sp-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}

/* --------------------------------------------------------------------------
   13. Error fragment — a loud, bordered alert in the accent. Message only,
       never a stack trace (Requirements 4.4, 4.11, 8.2).
   -------------------------------------------------------------------------- */
.error {
  border: var(--rule-accent);
  background: var(--paper);
  box-shadow: var(--shadow-accent);
  padding: var(--sp-4);
}

.error__lead {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent);
  margin: 0 0 var(--sp-2);
}

.error__message {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink);
  margin: 0 0 var(--sp-2);
}

.error__hint {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  margin: 0;
}

/* --------------------------------------------------------------------------
   14. Colophon (footer) — a little personality, boxed off with a top rule.
   -------------------------------------------------------------------------- */
.colophon {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5) var(--sp-8);
  border-top: var(--rule);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}

.colophon p {
  max-width: var(--measure);
}

.colophon__fine {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

/* --------------------------------------------------------------------------
   15. Motion + responsive
       Motion is minimal and snappy (button press only). Honor reduced-motion.
       At narrow widths the explorer grid stacks; the ledger stays intact and
       scrolls horizontally if it must, never collapsing its columns.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

@media (max-width: 60rem) {
  .explorer__grid {
    grid-template-columns: 1fr;
  }

  /* Stack the surface: scope bar, chips, lens rail, then results in order. */
  .explorer__surface {
    grid-template-columns: 1fr;
    grid-template-areas:
      "scope"
      "chips"
      "rail"
      "results"
      "timeline";
  }
}

@media (max-width: 40rem) {
  :root {
    --fs-xl: 1.625rem;
    --fs-2xl: 2rem;
  }

  .layout,
  .masthead,
  .methodology,
  .colophon {
    padding-left: var(--sp-4);
    padding-right: var(--sp-4);
  }

  /* Keep the ledger a ledger on small screens: let it scroll rather than
     reflow into unaligned stacks. */
  .results {
    overflow-x: auto;
  }

  .results__table {
    min-width: 32rem;
  }
}

/* --------------------------------------------------------------------------
   14. Archetype upgrade (task 13.9): the multi-event selector, per-category
       controls, the methodology grouped-by-category layout, and the Exposure
       column. Same tokens, same hard edges — no new palette.
   -------------------------------------------------------------------------- */

/* Event_Set as a wrapping set of checkbox toggle-chips — clearer than a native
   multi-select and no ctrl-click. Each chip is a hard-bordered box; checked, it
   fills with the accent (one hue, used as a signal). No state changes border
   WIDTH, so nothing reflows. */
/* Shared option primitive for the custom menus (no native <select> anywhere).
   The native input is the source of truth; :has() drives the visible state. */
.option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.2;
  cursor: pointer;
  user-select: none;
}

.option__input:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* --- Events: wrapping checkbox toggle-CHIPS (multi-select). ---------------- */
.eventset {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.eventset .option {
  padding: var(--sp-2) var(--sp-3);
  border: var(--rule);
  background: var(--paper);
  color: var(--ink);
  white-space: nowrap;
}

.eventset .option:hover {
  border-color: var(--accent);
}

.eventset .option__input {
  appearance: none;
  -webkit-appearance: none;
  width: 0.85rem;
  height: 0.85rem;
  margin: 0;
  flex: 0 0 auto;
  border: var(--hair);
  background: var(--paper);
}

.eventset .option:has(.option__input:checked) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.eventset .option:has(.option__input:checked) .option__input {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
}

/* --- Archetype: stacked radio option-ROWS (single-select menu). ------------ */
.optionset {
  border: none;
  margin: 0 0 var(--sp-3);
  padding: 0;
  min-width: 0;
}

.optionset:last-child {
  margin-bottom: 0;
}

.optionset__legend {
  padding: 0;
  margin-bottom: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.optionset .option {
  width: 100%;
  box-sizing: border-box;
  padding: var(--sp-1) var(--sp-2);
}

/* The native radio is visually removed; the checkmark prefix marks selection. */
.optionset .option__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* A leading glyph column keeps every row aligned; the selected row shows a tick
   and goes accent (color only — no width change). */
.optionset .option__text::before {
  content: "\2002\2002";
  white-space: pre;
}

.optionset .option:has(.option__input:checked) .option__text::before {
  content: "\2713\2002";
}

.optionset .option:has(.option__input:checked) {
  color: var(--accent);
  font-weight: 700;
}

.optionset .option:hover {
  background: var(--ink);
  color: var(--paper);
}

/* --- Compact single-select radio CHIPS (e.g. the slot picker). ------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.chips .option {
  padding: var(--sp-1) var(--sp-2);
  border: var(--rule);
  background: var(--paper);
  color: var(--ink);
  white-space: nowrap;
}

.chips .option:hover {
  border-color: var(--accent);
}

.chips .option__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.chips .option:has(.option__input:checked) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Space the disclosed Breakdown parameter from the picker above it. */
.rail__param {
  margin-top: var(--sp-4);
}

/* Small mono hint under a control (e.g. "None selected → every event"). */
.field__hint {
  margin: var(--sp-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}

/* Exposure cell: tabular mono figures so the column aligns like the rest of
   the ledger; the null dash sits quietly in neutral 2. */
.exposure {
  font-variant-numeric: tabular-nums;
}

.exposure__none {
  color: var(--ink-2);
}

/* Draft-value cell (Requirement 12.11): ONE cell stacking three labelled lines
   — vs ADP (raw signed points) + the ADP-value and CLV field percentiles. Small
   mono label on the left, the tabular figure right-aligned, so the three lines
   read like a mini ledger and align to the sign/decimal. Vertical-align top so a
   multi-line cell sits flush with the single-line rate/exposure cells. */
.draftvalue {
  vertical-align: top;
}

.draftvalue__stack {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.draftvalue__line {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
}

/* Small neutral label ("vs ADP" / "Value" / "CLV") on the left of each line. */
.draftvalue__line .draftvalue__label {
  margin-right: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

.draftvalue__num {
  font-variant-numeric: tabular-nums;
}

/* Null placeholder for any missing draft-value line — quiet neutral 2 dash, like
   a null exposure, rather than a fabricated number (Requirements 12.7, 12.17). */
.draftvalue__none {
  color: var(--ink-2);
}

/* Percentile value color treatment (Requirement 12.17): a hard-edged filled
   swatch encodes the 0-100 field percentile on a graduated bad->good scale via
   the --pct custom property — hue 0 (red) at 0, 60 (amber) at 50, 120 (green) at
   100 (--pct * 1.2). The mapping lives here, not in the template. The NUMERAL
   stays near-black ink so it always clears WCAG AA on the paper ground; the vivid
   swatch (a graphic element, not text) carries the color signal legibly without a
   separate reference. */
.pct-value {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

.pct-value::before {
  content: "";
  width: 0.72em;
  height: 0.72em;
  flex: 0 0 auto;
  border: var(--hair);
  background: hsl(calc(var(--pct) * 1.2) 62% 45%);
}

/* Methodology grouped by category — each category is a boxed section carrying
   its default label, above the ruled grid of its definitions. */
.archetype-categories {
  display: grid;
  gap: var(--sp-5);
  margin: var(--sp-4) 0 0;
}

.archetype-category {
  border: var(--rule);
  padding: var(--sp-4);
}

.archetype-category__name {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-lg);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 0;
}

.archetype-category__default {
  margin: var(--sp-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}

.archetype-category__default-label {
  color: var(--accent);
  font-weight: 700;
}

/* Inside a boxed category the rule grid already has its own border; drop the
   doubled outer border so the nesting reads clean. */
.archetype-category .archetype-rules {
  border-top: var(--hair);
  border-left: 0;
  border-right: 0;
}

/* --------------------------------------------------------------------------
   16. Lens / Scope IA (Requirements 6.14-6.20)
       The query is split into two hard-edged regions: a persistent top SCOPE
       bar (Event_Set, draft dates, per-category label constraints, min sample)
       and a left LENS rail (the breakdown dimension + only its parameter
       control). Above the results, query-summary CHIPS name the active lens and
       each applied scope filter, each removable. Same tokens, same brutalism —
       boxed sections, hard rules, offset solid shadows, mono labels.
   -------------------------------------------------------------------------- */

/* Scope bar: a full-width instrument strip across the top. Fields wrap in a
   row so it reads as a control panel, not a stacked form. */
.scopebar {
  grid-area: scope;
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard-sm);
  padding: var(--sp-4);
}

/* Control-panel header (Scope + Lens): one shared inset ink tab, anchored FLUSH
   to the box's top-left inner corner (negative margins negate the box padding)
   so the tab's top sits on the box's top edge — aligning with the data panel's
   full-width header bar in the same row. This is the "controls" tier of the two
   header motifs (see ui-aesthetic.md); the data panel uses .results__caption. */
.scopebar__title,
.rail__title {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--paper);
  background: var(--ink);
  margin: calc(-1 * var(--sp-4)) 0 var(--sp-4) calc(-1 * var(--sp-4));
  padding: var(--sp-1) var(--sp-3);
  max-width: none;
}

/* Faceted pill bar: a wrapping row of uniform, compact filter pills. Each
   facet is a native <details> — its <summary> IS the pill (display name +
   current-value summary + caret) and its panel is an absolutely-positioned
   popover, so opening a facet OVERLAYS the bar and never shifts it. Wrapping is
   fine now because every pill is small and uniform, not a variable-width row of
   heterogeneous controls. */
.facets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  align-items: flex-start;
}

/* Thematic subgroups (Requirement 14.8): the flat pill row is split into
   labelled sections — Contest, Players, Builds, Draft value, plus a Confidence
   settings slot. Each group is a small stack: a mono section label over its own
   wrapping row of pills. Groups sit side by side and wrap as a block, so the
   grouping never changes the immediate-apply model and adds no reflow beyond the
   existing pill wrapping. */
.facetgroup {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Section label: a muted mono caption with a hard underline rule, echoing the
   ledger/terminal structure without competing with the pills below it. */
.facetgroup__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-2);
  padding-bottom: var(--sp-1);
  border-bottom: var(--rule);
}

/* The pills inside one group keep the same uniform wrapping row as before. */
.facetgroup__pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: flex-start;
}

.facet {
  /* the popover positions against its own pill */
  position: relative;
  /* FIXED width: the value truncates inside, so picking a value never resizes
     the pill and the bar never reflows (the full value lives in the chips row). */
  width: 11.5rem;
}

/* The pill. Hard-bordered, compact, mono. The border is a CONSTANT 2px at
   rest, hover, focus, and open — only its COLOR changes, so nothing reflows
   (no 1px→2px width jump). */
.facet__summary {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.2;
  padding: var(--sp-2) var(--sp-3);
  border: var(--rule);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  /* strip the native disclosure marker; we draw our own caret */
  list-style: none;
}

.facet__summary::-webkit-details-marker {
  display: none;
}

.facet__summary::marker {
  content: "";
}

/* Hover/focus emphasis is a COLOR shift to the accent — never a width change. */
.facet__summary:hover {
  border-color: var(--accent);
}

.facet__summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  box-shadow: var(--shadow-hard-sm);
}

/* Facet display name: the muted uppercase label on the pill (fixed size). */
.facet__name {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

/* Current-value summary: the live setting, in ink so it reads as data. It
   grows to fill and TRUNCATES with an ellipsis, so a long value never widens
   the fixed pill. */
.facet__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
  color: var(--ink);
}

.facet__caret {
  flex: 0 0 auto;
  font-size: 0.7em;
  color: var(--ink-2);
}

/* Open facet: mark the pill in the accent (COLOR only — border width holds). */
.facet[open] > .facet__summary {
  border-color: var(--accent);
  color: var(--accent);
}

.facet[open] > .facet__summary .facet__name,
.facet[open] > .facet__summary .facet__value,
.facet[open] > .facet__summary .facet__caret {
  color: var(--accent);
}

/* The popover panel: absolutely positioned so it OVERLAYS the page rather than
   pushing the bar. Hard border + offset SOLID shadow, sensible min-width. */
.facet__panel {
  position: absolute;
  top: calc(100% + var(--sp-2));
  left: 0;
  z-index: 20;
  min-width: 14rem;
  padding: var(--sp-3);
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
}

/* Events popover: scrollable so ANY number of events never grows the bar or
   the popover past a sensible height. */
.facet__panel--events {
  min-width: 18rem;
  max-height: 16rem;
  overflow-y: auto;
}

/* Archetype popover: every per-category option group stacked, scrollable so any
   number of categories/labels never grows the popover past a sensible height. */
.facet__panel--archetype {
  min-width: 15rem;
  max-height: 20rem;
  overflow-y: auto;
}

/* Players popover (Requirement 14.7): the ONE unified control — an include set,
   an all-of/any-of combine toggle, and an exclude set — stacked in a single
   panel, scrollable if the slots outgrow it. */
.facet__panel--players {
  min-width: 16rem;
  max-height: 22rem;
  overflow-y: auto;
}

/* Each part of the unified Players control (include / combine / exclude) is a
   borderless fieldset with a small mono legend, separated by a hairline rule so
   the three parts read as one instrument, not three facets. */
.playerset {
  border: none;
  margin: 0 0 var(--sp-3);
  padding: 0 0 var(--sp-3);
  border-bottom: var(--hair);
}

.playerset:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}

.playerset__legend {
  padding: 0;
  margin-bottom: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

/* The combine-mode radios reuse the archetype option-row look. */
.playerset--mode .option {
  width: 100%;
  box-sizing: border-box;
}

/* The Apply button applies the toggled events (a submit fires the form). */
.facet__apply {
  margin-top: var(--sp-3);
}

/* Draft-date range inside its popover: the two date inputs + a separator. */
.facet__daterange {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.facet__daterange .field__control {
  width: auto;
  flex: 1 1 0;
  min-width: 0;
}

/* Percentile range inside its popover: two 0-100 numeric inputs + a separator,
   laid out like the draft-date range so every range facet reads the same. Shared
   by both draft-value percentile facets (ADP value, CLV). */
.facet__pctrange {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Player-combination slots inside their popover: stacked inputs + Apply. */
.facet__combo {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.facet__daterange-sep {
  font-family: var(--font-mono);
  color: var(--ink-2);
}

/* Min-sample: a small numeric input, a few digits wide, right-aligned. Spinners
   are suppressed by appearance:none above; the Firefox hint keeps it a plain
   textfield. */
.facet__num {
  width: 8ch;
  text-align: right;
  -moz-appearance: textfield;
}

/* Lens rail: the left column. Reuses the filter-group box DNA so the two
   regions read as one instrument. */
.rail {
  grid-area: rail;
}

.rail__group {
  border: var(--rule);
  background: var(--paper);
  margin: 0;
  padding: var(--sp-4);
  box-shadow: var(--shadow-hard-sm);
}

/* .rail__title shares the control-panel inset-tab style defined above with
   .scopebar__title (the old border-straddling .rail__legend is retired). */

/* Query-summary chips: hard-edged mono tags. The active-lens chip carries the
   accent as a signal; scope chips are ink-on-paper with a bold removable x. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
}

.chips__title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.2;
  padding: 0.2em 0.5em;
  border: var(--hair);
  background: var(--paper);
  box-shadow: var(--shadow-hard-sm);
}

.chip--lens {
  border: var(--rule-accent);
  color: var(--ink);
  font-weight: 700;
}

.chip__label {
  white-space: nowrap;
}

/* The remove affordance: a bold x that flips to the accent on hover/focus. It
   is a real link (HTMX-enhanced) so it works with JS disabled. */
.chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4em;
  height: 1.4em;
  font-weight: 700;
  text-decoration: none;
  color: var(--ink);
  border-left: var(--hair);
  padding-left: var(--sp-2);
  margin-left: var(--sp-1);
}

.chip__remove:hover {
  background: var(--accent);
  color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   16. Roster explorer — the roster-card surface (roster-explorer spec 5.3).
       A grid of hand-built data instruments: each card is a boxed section with
       a full-width ink header bar (the data-panel motif), a dense draft-order
       pick table with tabular figures and proper <th scope> headers, a compact
       playoff-funnel indicator, archetype badges, and the SAME colored
       Draft_Value percentiles as the results ledger (.pct-value, reused above).
       Hard 1-2px rules, offset solid shadow, ~no radius, AA contrast, visible
       focus. No component library.
   -------------------------------------------------------------------------- */

/* The list wrapper: a boxed data panel matching .results / .timeline so the
   surface reads as one instrument. */
.rosters {
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
}

/* Header bar: full-width ink strip (the "answer" tier), title left + count
   right, matching .results__caption. */
.rosters__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  background: var(--ink);
  color: var(--paper);
  padding: var(--sp-3) var(--sp-4);
}

.rosters__caption {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--paper);
  margin: 0;
}

.rosters__count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--paper);
  margin: 0;
  white-space: nowrap;
}

/* Sort control: a row of querystring links; the active order is inverted (ink
   fill) so the applied state is unmistakable. */
.rosters__sort {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule);
  background: var(--paper-2);
}

.rosters__sort-label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.rosters__sort-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.rosters__sort-option {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
  color: var(--ink);
  background: var(--paper);
  border: var(--hair);
  padding: var(--sp-1) var(--sp-2);
}

.rosters__sort-option:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Applied sort: solid ink fill, so the current order reads first. */
.rosters__sort-option--active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.rosters__sort-option--active:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* Scores toggle (v1.1): which stage's points every card shows. Same control
   language as the sort row — a row of querystring links, the active week
   inverted (ink fill) — so the two read as sibling controls. */
.rosters__weeks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule);
  background: var(--paper-2);
}

.rosters__weeks-label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.rosters__weeks-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.rosters__week-option {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
  color: var(--ink);
  background: var(--paper);
  border: var(--hair);
  padding: var(--sp-1) var(--sp-2);
}

.rosters__week-option:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.rosters__week-option--active,
.rosters__week-option--active:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* Card grid: dense, auto-filled columns so the surface reads like a board of
   instruments; one column when narrow. */
.rosters__grid {
  display: grid;
  /* min(21rem, 100%) so a single card never forces a track wider than the
     container (guards against horizontal overflow on a narrow viewport). */
  grid-template-columns: repeat(auto-fill, minmax(min(21rem, 100%), 1fr));
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-4);
  min-width: 0;
}

/* --- one card ----------------------------------------------------------- */
.roster {
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard-sm);
  display: flex;
  flex-direction: column;
}

/* A card whose Entry did not reach the selected week: kept in place (the
   population is stable across weeks) but dimmed, with an empty points column,
   so it reads as "not applicable this week" rather than being dropped. */
.roster--dim {
  opacity: 0.55;
}

/* The pick-table caption: a small mono note (e.g. "Did not reach Week 17"). */
.roster__picks-caption {
  caption-side: top;
  text-align: left;
  padding: var(--sp-1) var(--sp-4) 0;
}

.roster__picks-note {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

/* When a playoff week is selected, the players that COUNTED (nonzero that week —
   the optimal lineup) are emphasized and the rest de-emphasized, so the Pts
   column visibly sums to the week's team total. Mirrors the detail view's mark;
   the accent edge is a factual "this scored" affordance, not a status color. */
.roster__pick--counted {
  box-shadow: inset 3px 0 0 var(--accent);
}

.roster__pick--counted .roster__player,
.roster__pick--counted .roster__pts {
  font-weight: 700;
}

.roster__pick--bench {
  color: var(--ink-2);
}

/* Card header: full-width ink bar (data-panel motif). */
.roster__head {
  background: var(--ink);
  color: var(--paper);
  padding: var(--sp-3) var(--sp-3) var(--sp-2);
}

/* The header title links to the Entry's roster detail view; keep it inheriting
   the ink-bar paper ink, with a bold focus ring per the aesthetic. */
.roster__detail-link {
  display: flex;
  flex-direction: column;
  gap: 0.1em;
  text-decoration: none;
  color: inherit;
}

.roster__detail-link:hover .roster__event,
.roster__detail-link:focus-visible .roster__event {
  text-decoration: underline;
  text-decoration-thickness: 2px;
}

.roster__detail-link:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.roster__title {
  display: flex;
  flex-direction: column;
  gap: 0.1em;
  margin: 0 0 var(--sp-2);
}

.roster__event {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-base);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  color: var(--paper);
  line-height: var(--lh-tight);
}

.roster__entry {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--paper-2);
  letter-spacing: 0.02em;
}

/* Slot / draft date: a two-cell mono line under the title. */
.roster__ident {
  display: flex;
  gap: var(--sp-4);
  margin: 0;
}

.roster__ident-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}

.roster__ident dt {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper-2);
  margin: 0;
}

.roster__ident dd {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--paper);
}

/* Outcome block wrapper (v1.1): holds the one consolidated scoreboard. */
.roster__outcome {
  padding: var(--sp-3) var(--sp-3);
  border-bottom: var(--hair);
}

/* --- the consolidated outcome scoreboard (v1.1, Requirements 9, 10, 11) ---
   ONE progression across Regular Season -> Week 15 -> Week 16 -> Week 17.
   Reached stages fill with ink and show their score; unreached stages are a
   quiet ghosted cell (never a zero). A visible divider separates the regular
   season from the playoff weeks — crossing it IS making the playoffs, so there
   is no separate chip and no status-color card edge. */
.scoreboard {
  margin: 0;
}

.scoreboard__stages {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  border: var(--hair);
  width: 100%;
}

/* One stage cell: label over score, hard right rule between cells. */
.scoreboard__stage {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: 0.15em;
  min-width: 0;
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  border-right: var(--hair);
  background: var(--paper);
  text-align: left;
}

.scoreboard__stage:last-child {
  border-right: 0;
}

/* A clickable stage (card scoreboard): the anchor fills the ENTIRE cell — both
   the padding (negative margins) and the extra height a shorter cell is
   stretched to (flex: 1) — so a click anywhere in the cell triggers the week
   toggle. It links to the page's week toggle and inherits the cell's ink/paper
   color at rest. */
.scoreboard__stage--link {
  cursor: pointer;
}

.scoreboard__stage-link {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.15em;
  margin: calc(-1 * var(--sp-2)) calc(-1 * var(--sp-2)) calc(-1 * var(--sp-1));
  padding: var(--sp-2) var(--sp-2) var(--sp-1);
  color: inherit;
  text-decoration: none;
}

/* Hover / keyboard-focus: the WHOLE cell inverts to the accent with light text,
   uniformly across every stage. The child label/score/place carry their own
   colors, so they must be forced to the on-accent ink here — otherwise a
   paper-background stage (dark text) would keep dark text on the accent and read
   inconsistently versus the deepest stage (whose text is already light). */
.scoreboard__stage--link:hover,
.scoreboard__stage-link:focus-visible {
  background: var(--accent);
}

.scoreboard__stage--link:hover .scoreboard__label,
.scoreboard__stage--link:hover .scoreboard__score,
.scoreboard__stage--link:hover .scoreboard__place,
.scoreboard__stage--link:hover .scoreboard__empty-mark,
.scoreboard__stage--link:hover .scoreboard__pct,
.scoreboard__stage--link:hover .scoreboard__pct-none,
.scoreboard__stage-link:focus-visible .scoreboard__label,
.scoreboard__stage-link:focus-visible .scoreboard__score,
.scoreboard__stage-link:focus-visible .scoreboard__place,
.scoreboard__stage-link:focus-visible .scoreboard__empty-mark,
.scoreboard__stage-link:focus-visible .scoreboard__pct,
.scoreboard__stage-link:focus-visible .scoreboard__pct-none {
  color: var(--accent-ink);
}

.scoreboard__stage-link:focus-visible {
  outline-offset: -3px;
}

/* The stage whose scores the cards currently show (the week toggle). A bold
   accent underline inside the cell — an active-state affordance (like the
   inverted toggle button), visible on both paper and the deepest stage's ink
   fill, so the emphasized stage and the pick table's points always agree. */
.scoreboard__stage--selected {
  box-shadow: inset 0 -3px 0 var(--accent);
}

.scoreboard__label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: var(--lh-tight);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

/* The stage score: tabular figures. The regular-season TOTAL is grotesk/bold to
   distinguish it from the mono single-week playoff scores (Requirement 10.3). */
.scoreboard__score {
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--ink);
}

.scoreboard__score--reg {
  font-family: var(--font-grotesk);
  font-weight: 700;
}

.scoreboard__score--week {
  font-family: var(--font-mono);
}

/* An unreached stage: ghosted, no score — never a fabricated zero. */
.scoreboard__stage--empty {
  background: var(--paper-2);
}

.scoreboard__stage--empty .scoreboard__label {
  color: var(--ink-2);
  opacity: 0.7;
}

.scoreboard__empty-mark {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  opacity: 0.6;
}

/* Reached stages read solid; the DEEPEST reached stage is emphasized with the
   ink fill so "how far did it get" reads at a glance (Requirement 9.2). */
.scoreboard__stage--reached {
  background: var(--paper);
}

.scoreboard__stage--deepest {
  background: var(--ink);
}

.scoreboard__stage--deepest .scoreboard__label {
  color: var(--paper-2);
}

.scoreboard__stage--deepest .scoreboard__score,
.scoreboard__stage--deepest .scoreboard__empty-mark {
  color: var(--paper);
}

/* A finalist's "Nth of M" on the Week 17 stage. Week 17 is always the deepest
   reached stage for a finalist, so this sits on the ink fill — render it in
   paper for legible, high-contrast text (never accent-on-ink, which is
   unreadable). */
.scoreboard__place {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--ink);
  /* A triple-digit place ("100th of 539") must wrap inside the narrow stage
     cell rather than overflow and shove the columns out of alignment. */
  max-width: 100%;
  overflow-wrap: anywhere;
}

.scoreboard__stage--deepest .scoreboard__place {
  color: var(--paper);
}

/* The playoff-week field percentile on a reached playoff stage (Week 15/16/17):
   the entry's team score ranked among that week's survivors. Rendered with the
   SAME .pct-value treatment as the Draft_Value percentiles (a colored swatch +
   ink numeral, the --pct hue mapping inherited from .pct-value) — no new color
   scale, swatch, or convention. This rule only PLACES it: the third line in the
   stage box (below label/score, after place), mono tabular figures, hard-edged.
   On the deepest reached stage (ink fill) the numeral flips to paper so it still
   clears WCAG AA, mirroring .scoreboard__place. */
/* margin-top: auto bottom-aligns the percentile within the flex-column stage
   box, so every stage's percentile sits on one baseline row across the
   scoreboard regardless of how much sits above it (e.g. a finalist's "Nth of M"
   place on Week 17). The ordinal "Nth %ile" label may wrap on the narrowest
   cards; allow it (override .num's nowrap) — bottom-alignment keeps the wrapped
   label anchored to the cell floor so the row of percentiles still lines up. */
.scoreboard__pct {
  margin-top: auto;
  padding-top: 0.15em;
  max-width: 100%;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-tight);
  color: var(--ink);
  /* Override .num's nowrap: "100th %ile" must wrap inside the narrow cell (kept
     bottom-anchored by margin-top:auto) rather than overflow horizontally. The
     swatch + numeral wrap as a unit (flex-wrap) instead of widening the track. */
  white-space: normal;
  overflow-wrap: anywhere;
  flex-wrap: wrap;
}

.scoreboard__stage--deepest .scoreboard__pct {
  color: var(--paper);
}

/* A reached stage whose percentile is null: an explicit em-dash placeholder,
   never a fabricated zero — mirrors .scoreboard__empty-mark. Bottom-aligned to
   share the percentile baseline row with the real values. */
.scoreboard__pct-none {
  margin-top: auto;
  padding-top: 0.15em;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  line-height: var(--lh-tight);
  color: var(--ink-2);
  opacity: 0.6;
}

.scoreboard__stage--deepest .scoreboard__pct-none {
  color: var(--paper);
  opacity: 0.7;
}

/* The Regular-Season | playoffs divider: a hard vertical ink rule, thicker than
   the hairline cell rules, marking where the regular season ends and the
   playoffs begin (crossing it is making the playoffs; Requirement 9.3). A
   structural separator, not a status color. */
.scoreboard__divider {
  flex: 0 0 auto;
  width: 3px;
  align-self: stretch;
  background: var(--ink);
  padding: 0;
}

/* Archetype badges: hard-edged mono chips, category over label. */
/* Archetype badges kept to a SINGLE row (nowrap): the compact column badges sit
   side by side instead of wrapping to a second line. If the row is wider than
   the card at its narrowest, it scrolls horizontally rather than wrapping, so
   the badge strip stays one line tall and the card layout is predictable. */
.roster__badges {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-1);
  margin: 0;
  padding: var(--sp-3);
  border-bottom: var(--hair);
  list-style: none;
  overflow-x: auto;
}

.badge {
  display: inline-flex;
  flex-direction: column;
  flex: 0 0 auto;
  border: var(--hair);
  background: var(--paper-2);
  padding: 0.1em 0.4em;
}

.badge__cat {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.badge__label {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
}

/* Draft value: the two percentile lines, reusing .draftvalue__* + .pct-value. */
.roster__value {
  display: flex;
  gap: var(--sp-5);
  margin: 0;
  padding: var(--sp-3);
  border-bottom: var(--hair);
}

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

.roster__value dt.draftvalue__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  margin: 0;
}

.roster__value dd {
  margin: 0;
}

/* Draft-order pick table: dense ledger, tabular figures, ruled rows.
   table-layout: fixed + explicit numeric-column widths keep the table inside
   its card — long player names WRAP in the Player column instead of forcing the
   table (and its grid track) wider than the card and spilling past the panel
   edge. (No margin-top:auto: the table follows the value block directly rather
   than being bottom-anchored, which left a gap in shorter cards.) */
.roster__picks {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  table-layout: fixed;
}

/* Tight tabular widths for the numeric/short columns; the Player column (2)
   is unset and takes the remaining width, wrapping as needed. */
.roster__picks thead th:nth-child(1) { width: 2.2rem; } /* # */
.roster__picks thead th:nth-child(3) { width: 2.8rem; } /* Team */
.roster__picks thead th:nth-child(4) { width: 2.6rem; } /* Pos */
.roster__picks thead th:nth-child(5) { width: 3.2rem; } /* ADP */
.roster__picks thead th:nth-child(6) { width: 3.4rem; } /* Pts */

.roster__picks thead th {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: left;
  color: var(--ink);
  background: var(--paper-2);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: var(--rule);
}

.roster__picks thead th.num {
  text-align: right;
}

.roster__picks tbody tr {
  border-bottom: var(--hair);
}

.roster__picks tbody tr:last-child {
  border-bottom: 0;
}

.roster__picks tbody tr:nth-child(even) {
  background: rgba(20, 20, 20, 0.02);
}

.roster__picks th,
.roster__picks td {
  padding: var(--sp-1) var(--sp-3);
  vertical-align: baseline;
}

/* Player name (row header): grotesk, left-aligned, the identity anchor. Pinned
   to a SINGLE line (nowrap + ellipsis) so every 18-row card is the same height
   — name wrapping was the only source of card-height variance. The card
   template abbreviates the first name (A. St. Brown) so the surname survives,
   and carries the full name in a title attribute for hover/assistive tech; the
   ellipsis is a safety net for the rare still-too-long name. */
.roster__player {
  font-family: var(--font-grotesk);
  font-weight: 500;
  text-align: left;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Points legend under the pick table: a terse mono footnote clarifying that Pts
   is the best-ball optimal-lineup score (roster-dependent), not a raw total. */
.roster__picks-legend {
  margin: 0;
  padding: var(--sp-1) var(--sp-3) var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  letter-spacing: 0.02em;
}

/* Quiet the Pts abbr[title] marker: no dotted underline, but a help cursor to
   hint the tooltip. */
.roster__picks thead abbr[title] {
  text-decoration: none;
  cursor: help;
}

/* Team / position: mono, quiet. */
.roster__team,
.roster__pos {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Explicit null/unknown placeholder on a card — quiet neutral, never blank. */
.roster__none {
  color: var(--ink-2);
}

/* Pagination: prev / indicator / next, spaced across the panel foot. */
.rosters__pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border-top: var(--rule);
  background: var(--paper-2);
}

.rosters__page-indicator {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  color: var(--ink);
}

/* A disabled prev/next edge: a non-interactive ghosted button (no href), so the
   control still reads as present at the ends of the range. */
.rosters__pager-link.is-disabled {
  color: var(--ink-2);
  border-color: var(--ink-2);
  background: var(--paper);
  box-shadow: none;
  cursor: default;
  pointer-events: none;
  opacity: 0.55;
}

/* When the surface is served standalone (JS-disabled full page), let a wide
   card grid breathe but keep each pick table a ledger on small screens. */
@media (max-width: 40rem) {
  .rosters__grid {
    grid-template-columns: 1fr;
  }
}

/* --------------------------------------------------------------------------
   Roster detail view (v1.1, Requirement 12) — one Entry's per-week lineup.
   The same boxed data-instrument language as a card: an ink header bar, the
   shared outcome scoreboard, badges + draft value, a week selector (active week
   inverted like the sort control), and the selected week's pick table marking
   the players that counted. Tabular figures, hard edges, AA contrast, visible
   focus. No component library.
   -------------------------------------------------------------------------- */
.roster-detail {
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard);
}

/* Header: full-width ink bar (data-panel motif) + identity line. */
.roster-detail__head {
  background: var(--ink);
  color: var(--paper);
  padding: var(--sp-3) var(--sp-4) var(--sp-3);
}

.roster-detail__title {
  display: flex;
  flex-direction: column;
  gap: 0.1em;
  margin: 0 0 var(--sp-2);
}

.roster-detail__event {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-lg);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  color: var(--paper);
  line-height: var(--lh-tight);
}

.roster-detail__entry {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--paper-2);
  letter-spacing: 0.02em;
}

.roster-detail__ident {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin: 0 0 var(--sp-2);
}

.roster-detail__ident-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}

.roster-detail__ident dt {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper-2);
  margin: 0;
}

.roster-detail__ident dd {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}

/* "Back to rosters" — quiet paper-ink link on the ink bar. */
.roster-detail__back {
  margin: 0;
}

.roster-detail__back a {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper-2);
  text-decoration: none;
}

.roster-detail__back a:hover,
.roster-detail__back a:focus-visible {
  color: var(--paper);
  text-decoration: underline;
}

/* The scoreboard + badges + draft-value blocks reuse the card partials, spaced
   inside the detail panel. */
.roster-detail__outcome {
  padding: var(--sp-4);
  border-bottom: var(--hair);
}

.roster-detail .roster__badges,
.roster-detail .roster__stack,
.roster-detail .roster__value {
  padding: var(--sp-3) var(--sp-4);
}

/* Week selector: a row of querystring links; the active week is inverted (ink
   fill), matching the roster sort control. */
.roster-detail__weeks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--rule);
  background: var(--paper-2);
}

.roster-detail__weeks-label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-2);
}

.roster-detail__weeks-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.roster-detail__week {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-decoration: none;
  color: var(--ink);
  background: var(--paper);
  border: var(--hair);
  padding: var(--sp-1) var(--sp-2);
}

.roster-detail__week:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.roster-detail__week--active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.roster-detail__week--active:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* The selected week's pick table: a dense ledger, tabular figures, ruled rows. */
.roster-detail__picks {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.roster-detail__picks-caption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  text-align: left;
}

.roster-detail__picks-week {
  font-weight: 700;
  color: var(--ink);
}

.roster-detail__picks-total {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.roster-detail__picks thead th {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: left;
  color: var(--ink);
  background: var(--paper-2);
  padding: var(--sp-2) var(--sp-4);
  border-top: var(--rule);
  border-bottom: var(--rule);
}

.roster-detail__picks thead th.num {
  text-align: right;
}

.roster-detail__picks tbody tr {
  border-bottom: var(--hair);
}

.roster-detail__picks tbody tr:last-child {
  border-bottom: 0;
}

.roster-detail__picks th,
.roster-detail__picks td {
  padding: var(--sp-1) var(--sp-4);
  vertical-align: baseline;
}

/* Counted players (the optimal lineup that produced the week score): emphasized
   with an accent left-edge on the row + an accent "Counted" tag, reusing the
   accent sparingly — NOT a status color, a factual "this scored" mark. */
.roster-detail__pick--counted {
  box-shadow: inset 3px 0 0 var(--accent);
  background: rgba(20, 20, 20, 0.03);
}

.roster-detail__pick--counted .roster__player,
.roster-detail__pick--counted .roster__pts {
  font-weight: 700;
}

.roster-detail__counted-mark {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-ink);
  background: var(--accent);
  border: var(--hair);
  border-color: var(--accent);
  padding: 0.05em 0.4em;
}

/* --------------------------------------------------------------------------
   18. Correlation & stacking indicators (correlation-stacking spec, Req 4, 3.3)
       Roster-card (and detail) same-team stacking read-outs: a team-cluster
       chip (BUF ×3), a QB-stack marker (3-Stack / Naked), and the compact
       Positional_Makeup line (QB2 · RB5 · WR8 · TE3). Plus the lens rail's
       "Stacks" breakdown group divider.

       No new visual language (Req 4.4): everything reuses the existing tokens —
       the same hard 1–2px ink rules, offset SOLID shadows, ~no radius, the
       grotesk-for-structure / mono-tabular-for-numbers split, and the muted
       neutral (--ink-2) for de-emphasised labels/placeholders.

       Color decision: the cluster chip is made to POP through STRUCTURE (a 2px
       ink border + offset solid shadow on the paper ground), not a new hue. The
       accent (signal red) already carries a specific meaning ON THIS CARD —
       interactive affordances (link/focus) and the "this pick counted" edge — so
       tinting a STATIC read-out red would misread as interactive/flagged. Ink on
       paper also clears WCAG AA trivially. This is the deliberate, restrained
       choice; the one-accent guidance is a suggestion and structure reads better
       here. All pairings below are AA on the paper ground.
   -------------------------------------------------------------------------- */

/* The stacking read-out block: sits between the archetype badges and the
   Positional_Makeup line in the card's ruled-section rhythm, so it gets the same
   block padding + hairline separator as the badges/value blocks. Inline row of
   indicators that wraps gracefully when the card is narrow. */
.roster__stack {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  padding: var(--sp-3);
  border-bottom: var(--hair);
}

/* Team-cluster chip (e.g. BUF ×3) — the primary "how stacked is this roster"
   signal. Reuses the brutalist chip DNA but reads as ONE horizontal line (team
   then ×N), distinct from the column-layout archetype .badge. Made to pop with a
   2px ink border + offset solid shadow (structure, not color). */
.stack-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-1);
  padding: 0.1em 0.5em;
  border: var(--rule);
  background: var(--paper);
  box-shadow: var(--shadow-hard-sm);
}

/* The NFL team abbreviation: structural grotesque, uppercase, bold — the label
   half of the chip. */
.stack-chip__team {
  font-family: var(--font-grotesk);
  font-weight: 700;
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink);
}

/* The ×N cluster size: a mono tabular ledger figure so it aligns like every
   other number. .num (from the template) already gives tabular-nums + nowrap;
   this keeps it ink and matches the team baseline. */
.stack-chip__size {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: var(--fs-sm);
  color: var(--ink);
}

/* Explicit "No stack" / absent state (Req 4.3, 3.3): a de-emphasised placeholder
   that is unmistakably NOT a real cluster chip and never looks like a TEAM ×1.
   Muted ink + a DASHED hairline border + no shadow read as "nothing here",
   while --ink-2 on paper stays AA-legible. Overrides the solid-chip rules
   above (single-line text, no team/size sub-parts). */
.stack-chip--none {
  padding: 0.1em 0.5em;
  border: 1px dashed var(--ink-2);
  background: transparent;
  box-shadow: none;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

/* QB-stack marker (e.g. 3-Stack, Naked) — a compact, flat mono tag. Deliberately
   LIGHTER than the cluster chip (a single hairline, no shadow) and unlike the
   column archetype .badge, so the three read as distinct at a glance. Uppercase
   + tabular figures keep a leading number (the "3" in 3-Stack) aligned. */
.qb-stack {
  display: inline-flex;
  align-items: baseline;
  padding: 0.1em 0.5em;
  border: var(--hair);
  background: var(--paper-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--ink);
}

/* Positional_Makeup line (QB2 · RB5 · WR8 · TE3) — a compact single line that
   replaces the removed count badges. It carries .num from the template (tabular
   figures); override the ledger's right-align/nowrap since this is a full-width
   prose-position line that should read left→right and wrap gracefully. Counts
   stay ink (legible ledger figures); the position letters + separators are the
   muted-but-AA-legible labels. */
.positional-makeup {
  margin: 0;
  padding: var(--sp-3);
  border-bottom: var(--hair);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  line-height: var(--lh-snug);
  letter-spacing: 0.02em;
  text-align: left;
  white-space: normal;
  color: var(--ink);
}

/* One position group (e.g. "QB2"): the letters and count sit tight together so
   the pair reads as a unit. */
.positional-makeup__item {
  white-space: nowrap;
}

/* Position letters: the muted label half (uppercase, --ink-2), the count that
   follows inherits the block's ink so the figure reads first. */
.positional-makeup__pos {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-2);
}

/* The · separator: de-emphasised so the groups, not the dots, carry the line. */
.positional-makeup__sep {
  color: var(--ink-2);
  opacity: 0.6;
}

/* Lens-rail "Stacks" breakdown group (_lensrail.html): the fieldset already
   reuses .optionset + .rail__param; add a top rule + breathing room so the
   Stacks category group reads as a section DISTINCT from the "Builds" group
   above it (rather than one continuous radio list). The Stacks scope facet
   keeps the existing .facetgroup/.facet styling — no change needed there. */
.rail__group--stacks {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: var(--hair);
}

/* --------------------------------------------------------------------------
   19. Draft board view (draft-board-view spec) — positional palette, the
       slot×round board grid, the viewing-column highlight, the modal overlay,
       and the roster-card entry control.

       Refined-brutalist idiom throughout: hard 1px ink cell borders, offset
       SOLID shadows, tabular mono figures, no gradients, no radius, no
       component library. Color is used to help POSITION scanning (Req 4), but
       every cell ALSO carries its position as text (template concern), and the
       viewing column is marked STRUCTURALLY (an inset ink edge + a "YOUR SLOT"
       tag), so nothing here is a color-only encoding (Req 3.2, 4.3).

       Class naming contract with the grid partial (task 6.1) and the
       positional_class web helper (task 5.2):
         positional_class(position) returns EXACTLY one of —
           QB -> "board-cell--qb"   RB -> "board-cell--rb"
           WR -> "board-cell--wr"   TE -> "board-cell--te"
           <anything else / empty>  -> "board-cell--default"
         and each filled <td> is emitted as
           class="board-cell board-cell--<pos>" data-position="QB"
         The data-position attribute is carried for semantics/testing; the FILL
         is driven by the modifier class below (the attribute selector is also
         defined so either hook works and they can never disagree).
   -------------------------------------------------------------------------- */

/* Positional palette — light, slightly desaturated tints tuned to the paper/ink
   ground. Ink (#141414) text sits on every fill FAR above WCAG AA (each tint is
   light enough to clear ~10:1 against ink — comfortably AA/AAA for the small
   bold mono position abbreviation, Req 4.4). Hard edges, no gradients (Req 4.5).
   The mapping is a small FIXED set so it is identical on every render (Req 4.2);
   any unmapped position (FLEX/K/DST/unknown/empty) falls to the neutral default
   (Req 4.6). */
:root {
  --pos-qb: #e7b2c4;              /* rose  — QB */
  --pos-rb: #a6d8b9;              /* green — RB */
  --pos-wr: #a9c8ea;             /* blue  — WR */
  --pos-te: #edc79a;             /* amber — TE */
  --pos-default: var(--paper-2); /* neutral — FLEX/K/DST/unmapped (Req 4.6) */
  --pos-ink: var(--ink);         /* text on EVERY positional fill (AA on all) */
}

/* The board is a semantic <table> (reads without CSS, Req 6.4). Boxed, dense,
   spreadsheet/terminal hybrid — the same instrument DNA as the results ledger.
   Hard 1px ink grid lines on every cell; numbers are tabular mono figures. */
.board {
  border-collapse: collapse;
  /* table-layout: fixed makes the 12 slot columns share the width equally so a
     full pod fits the dialog instead of overflowing off the right edge; the
     round rail takes a fixed narrow column and player names wrap within their
     cell rather than widening the table. */
  table-layout: fixed;
  width: 100%;
  font-family: var(--font-grotesk);
  font-size: var(--fs-sm);
  /* all figures (slot/round axes, pick numbers) align like a ledger */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  background: var(--paper);
}

/* Every board cell and header carries a hard 1px ink rule — the grid IS the
   decoration. No radius anywhere. Tight padding keeps 12 columns inside the
   dialog width. */
.board th,
.board td {
  border: var(--hair);
  padding: var(--sp-1) 0.4rem;
  text-align: left;
  vertical-align: top;
}

/* The round rail is a fixed narrow column (fixed layout distributes the rest to
   the 12 equal slot columns). */
.board__corner,
.board__round {
  width: 2.4rem;
}

/* Column axis (Draft_Slot 1..12) and row axis (round 1..18) headers: mono,
   uppercase, on the neutral panel fill so they read as the ledger frame. */
.board__slot,
.board__round {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--paper-2);
  color: var(--ink);
  white-space: nowrap;
}

.board__slot {
  text-align: center;
}

.board__round {
  text-align: center;
  /* the round number reads as a tabular figure in the left rail */
  font-variant-numeric: tabular-nums;
}

/* The round number and the snake-direction arrow stack in the left rail. */
.board__round-num {
  display: block;
}

/* (Snake-flow direction is shown per tile — see .board-cell__dir — not on the
   round rail.) */

/* Top-left corner (no data): a solid ink block so the two axes read as framed,
   matching the data-panel header weight. */
.board__corner {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-align: center;
}

/* Filled cell base — position abbreviation + player name stacked, ink text on
   the positional fill. The fill is set by the modifier classes below. */
.board-cell {
  color: var(--pos-ink);
  background: var(--pos-default);
}

/* Positional fills. BOTH the modifier class (primary; positional_class emits
   it) and the [data-position] attribute selector are defined so the two hooks
   can never disagree — the template may set either or both. */
.board-cell--qb,
.board-cell[data-position="QB"] { background: var(--pos-qb); }

.board-cell--rb,
.board-cell[data-position="RB"] { background: var(--pos-rb); }

.board-cell--wr,
.board-cell[data-position="WR"] { background: var(--pos-wr); }

.board-cell--te,
.board-cell[data-position="TE"] { background: var(--pos-te); }

/* Explicit default treatment for FLEX/K/DST/unmapped/empty positions (Req 4.6)
   — the neutral panel fill, never an undefined/missing color. */
.board-cell--default { background: var(--pos-default); }

/* Cell head row: the position abbreviation (left) and the overall pick number
   (right) share one line so both read at a glance without stealing vertical
   space from the player name. */
.board-cell__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25em;
}

/* The position abbreviation: a small bold mono tag — the text encoding that
   makes the color redundant, never load-bearing (Req 4.3). */
.board-cell__pos {
  display: inline-block;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink);
}

/* The right side of the head row: the pick number + the snake-flow arrow,
   grouped so they sit together opposite the position tag. */
.board-cell__meta {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25em;
}

/* The overall pick number (1..216) in the cell's top-right — a quiet mono
   figure so the draft order is legible on every box without competing with the
   player name. Ink-2 keeps it subordinate; tabular figures align it. */
.board-cell__pick {
  font-family: var(--font-mono);
  font-size: 0.625rem;      /* 10px — a small ordinal marker */
  font-weight: 700;
  color: var(--ink-2);
}

/* Per-tile snake-flow arrow (→ / ← within a round, ↓ at a turn): where the next
   pick goes, Underdog-style. Decorative (order is also in the pick number), so
   aria-hidden in the template. */
.board-cell__dir {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ink-2);
}

/* Player name: the grotesque, tight; wraps within the narrow slot column. The
   first name is abbreviated (J. Smith-Njigba) and the type sized down a notch so
   a full 12-wide board fits the dialog; the full name is in the cell's title. */
.board-cell__player {
  display: block;
  font-family: var(--font-grotesk);
  font-weight: 500;
  font-size: 0.7rem;
  line-height: var(--lh-snug);
  color: var(--ink);
}

/* NFL identity line (team) — quiet mono meta; the template renders an explicit
   "unknown/—" here when the crosswalk is unresolved (Req 2.5), never a blank. */
.board-cell__team {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

/* Explicit empty position (a partial pod's missing pick, Req 8.2) — a quiet
   neutral cell that is unmistakably "no pick here", never a fabricated one. */
.board-cell--empty {
  background: var(--paper);
  color: var(--ink-2);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
}

/* --- Viewing-column highlight (Req 3.1, 3.2): perceivable WITHOUT color -----
   The Viewing_Entry's slot column is marked STRUCTURALLY with a heavy inset ink
   edge down both sides of the column (reusing the inset box-shadow device the
   results/roster tables use for their flagged rows), plus a "YOUR SLOT" tag on
   the column header and aria/scope text in the template. So the highlight does
   not depend on color perception. When the viewing slot is unknown the template
   emits none of these classes and the board renders normally (Req 3.3). */
.board-cell--viewing {
  box-shadow: inset 3px 0 0 var(--ink), inset -3px 0 0 var(--ink);
}

/* The viewing column header: ink block (inverted from the neutral axis) so the
   highlighted column is obvious at the top of the grid, color-independent. */
.board__slot--viewing {
  background: var(--ink);
  color: var(--paper);
  box-shadow: inset 0 3px 0 var(--ink);
}

/* "YOUR SLOT" tag rendered in the viewing column header — a compact mono marker
   (paper on ink for AA on the ink header), the non-color label of the highlight. */
.board__yourslot {
  display: block;
  margin-top: var(--sp-1);
  font-family: var(--font-mono);
  font-size: 0.625rem;      /* 10px — a small structural tag */
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper);
}

/* Non-standard pod notice (Req 8.3) + explicit empty-pod state (Req 8.4):
   terse mono lines in the muted neutral — honest, never a crash or a blank. */
.board__notice {
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--hair);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--accent);      /* a flagged, non-nominal state — reuses the accent */
}

.board__empty {
  margin: 0;
  padding: var(--sp-5) var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  background: var(--paper-2);
}

/* --- Modal overlay (Req 5) -------------------------------------------------
   The container (#draft-board-modal.modal) lives permanently in base.html and
   is EMPTY until a board is swapped in. Hidden while empty; the moment it holds
   the dialog fragment it becomes the overlay. Keying visibility off :empty (not
   a JS-set flag) means the overlay appears from the HTMX swap ALONE — so it
   works even if the modal enhancement script is absent (progressive
   enhancement, Req 6.2); the script only adds focus-trap/light-dismiss. */
.modal {
  display: none;
}

.modal:not(:empty) {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 100;
  align-items: flex-start;
  justify-content: center;
  padding: var(--sp-4) var(--sp-3);
  overflow-y: auto;                       /* scroll the 18-row grid if it's tall */
  /* Dim backdrop — a flat ink wash, NOT a blur/glass effect. */
  background: rgba(20, 20, 20, 0.55);
}

/* The dialog surface: a hard-edged paper panel with the offset SOLID shadow.
   Centered, capped, and scrollable so a full 12×18 board never overflows the
   viewport. This is the root element of the _draft_board.html fragment. */
.modal__dialog {
  position: relative;
  width: 100%;
  /* The 12-wide board is inherently wide, so the dialog uses most of the
     viewport (capped) rather than a narrow reading measure — a full pod fits
     without spilling off the right edge. */
  max-width: min(96vw, 84rem);
  max-height: calc(100vh - var(--sp-8));
  overflow: auto;
  margin: auto;
  background: var(--paper);
  border: var(--rule);
  box-shadow: var(--shadow-hard);
}

/* Dialog header — the full-width ink bar (data-panel motif): title left, the
   visible close control right (Req 5.4). Sticks to the top while the grid
   scrolls so the close control is always reachable. */
.modal__head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background: var(--ink);
  color: var(--paper);
}

.modal__title {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--paper);
}

/* Close control — an outlined paper-ink control on the ink bar (AA on ink),
   snapping to the accent on hover/focus like the other on-bar controls. It is a
   real <button>, so the global button:focus-visible ring (section 4) gives it a
   bold visible focus state (Req 5.8). */
.modal__close {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--paper);
  background: transparent;
  border: 1px solid var(--paper);
  border-radius: var(--radius);
  padding: var(--sp-1) var(--sp-2);
  cursor: pointer;
}

.modal__close:hover {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

/* The board sits in the dialog body, padded off the panel edges. */
.modal__body {
  padding: var(--sp-4);
}

/* --- Roster-card entry control (Req 1.1, 1.2) -----------------------------
   The Draft_Board control on each roster card: a compact, hard-edged control in
   the same family as the ghost buttons / on-ramp links (mono, uppercase, 1px
   ink border, square). A real <a href> (no-JS reachable) enhanced with hx-get;
   the global a:focus-visible ring covers keyboard focus. */
.roster__draft-board-link {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: var(--ink);
  background: var(--paper);
  text-decoration: none;
  border: var(--hair);
  border-radius: var(--radius);
  padding: var(--sp-1) var(--sp-2);
  box-shadow: var(--shadow-hard-sm);
}

.roster__draft-board-link:hover,
.roster__draft-board-link:focus-visible {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}
