/* Iron Tuna — shared design system.
 *
 * Single source of truth for the site's tokens and its chrome (header, nav,
 * footer). Linked BEFORE each page's inline <style>, so a page can still
 * override anything here and a rule left behind in an inline block is
 * harmless rather than a conflict.
 *
 * THE SYSTEM (October 2026). A flat, editorial, ledger-like market page:
 * paper white ground, one navy band for the header ribbon, the KPI band and
 * the footer, one teal accent for links and primary actions, one reserved
 * green for the one good figure (a positive market edge), and amber only for
 * a disclosure label. Grouping is whitespace and hairline rules; the only
 * enclosed surfaces are forms, ledgers and tables (the tinted panel with a
 * 3px navy top rule) and one sticky action panel. Nothing moves on its own.
 *
 * Owned by this file, and removed from the page-level <style> blocks:
 *   - the :root palette, the type scale and the spacing steps
 *   - base resets (*, html, body, a)
 *   - .wrap
 *   - header.site: the key-phrase line, the ribbon, the brand panel, .nav,
 *     the mobile disclosure nav and the bottom tab bar
 *   - footer.site
 *   - the :focus-visible ring
 *   - the ledger, the figure row, the KPI band and the state objects
 *
 * The header and footer MARKUP is generated by tools/build-chrome.mjs. Change
 * the nav or footer link set there, not by hand in 94 files.
 *
 * TOKEN NAMES ARE KEPT, VALUES MOVED. Ninety pages reach for --teal, --panel,
 * --mast and the rest in their own <style>; renaming them is how the undefined
 * --red shipped in August (tools/test-css-tokens.mjs). So the names below are
 * the August names and the values are the October system. Where a name no
 * longer describes its value (--gold, --mast-accent) the comment says what the
 * token IS now.
 */

/* Geist, self-hosted. One family, one variable file, every weight the site
   uses (400 text, 600 labels and headings, 700 the heaviest headline). No
   italic file: the system sets no italic accent words. font-display:swap with
   a metric-matched fallback so the page does not reflow when the file lands. */
@font-face {
  font-family: 'Geist';
  src: url('/fonts/Geist-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Fallback';
  src: local('Helvetica Neue'), local('HelveticaNeue'), local('Arial'), local('Liberation Sans');
  size-adjust: 99%; ascent-override: 95%; descent-override: 24%; line-gap-override: 0%;
}

:root {
  /* ── surfaces ─────────────────────────────────────────────────────────────
     Paper for the page. --panel is the tinted panel ledgers, tables and form
     cards sit on; --elev is the slightly deeper tint for strips. */
  --bg: #ffffff;
  --panel: #eef2f7;
  --elev: #e3e9f1;
  --line: #e2e4e8;      /* dividers */
  --line2: #cdd1d7;     /* control borders */

  /* ── text ─────────────────────────────────────────────────────────────────
     Ink, secondary, and the label/metadata grey. All three clear 4.5:1 on
     --bg, --panel and --elev. */
  --text: #111418;
  --sec: #454c57;
  --muted: #5f6672;

  /* ── the one accent ───────────────────────────────────────────────────────
     Teal, for links and primary actions and nothing else. --brand is the same
     value under the name front.html's modules were written against. */
  --teal: #0b4f6c;
  --teal-hover: #083a50;
  --teal-tint: #e6eff3;
  --brand: #0b4f6c;

  /* ── the one reserved semantic colour ─────────────────────────────────────
     Green for the one good figure in this domain: a positive market edge, the
     market projecting MORE than the consensus. Never for status, checks or
     approval. On navy it is mint. */
  --green: #0a6a4c;
  --mint: #9fe0c4;

  /* ── disclosure and warning ───────────────────────────────────────────────
     --gold / --goldink are the AMBER DISCLOSURE LABEL now (demo data, an
     estimate, a snapshot of a market that has moved). Ninety pages still name
     them, which is why the names stay. --danger is the warning ink, on
     --warn-bg, for an inline note. */
  --gold: #fff2c7;
  --goldink: #6b4500;
  --danger: #8a3b00;
  --warn-bg: #fdebdd;
  --focus: #1d6fd8;

  /* ── the navy band ────────────────────────────────────────────────────────
     The header ribbon, the KPI band and the footer. White text; labels at 74%
     white (which is #bdc4cf flattened onto the navy). --mast-accent is the
     mint, the only coloured figure allowed on navy; --mast-btn is the flat
     darker teal for a primary button on navy. */
  --mast: #001e47;
  --mast-ink: #bdc4cf;
  --mast-ink-hi: #ffffff;
  --mast-dim: #9aa6b8;
  --mast-accent: #9fe0c4;
  --mast-btn: #1f7a6f;
  --mast-line: rgba(255, 255, 255, .16);

  /* ── type ─────────────────────────────────────────────────────────────────
     One scale: 13, 14, 16, 19, 23, 28, 34, 44, 56, 68. Base 16, line height
     1.5. The ladder names stay for the pages that use them; the values are the
     steps. 12px exists only for the disclosure label. */
  --fs-12: 12px; --fs-13: 13px; --fs-14: 14px; --fs-16: 16px; --fs-19: 19px;
  --fs-23: 23px; --fs-28: 28px; --fs-34: 34px; --fs-44: 44px; --fs-56: 56px; --fs-68: 68px;
  --fs-3xs: 13px;
  --fs-2xs: 13px;
  --fs-xs:  14px;
  --fs-sm:  16px;
  --fs-md:  16px;
  --fs-lg:  19px;
  --fs-xl:  23px;
  --fs-2xl: 28px;
  --fs-3xl: 34px;
  --fs-4xl: 44px;

  /* One family. --font-display and --font-mono are the same family so a page
     that still names them sets Geist; the site has no second face and no
     monospace. */
  --font-body: 'Geist', 'Geist Fallback', 'Helvetica Neue', Helvetica, Arial, system-ui, sans-serif;
  --font-display: var(--font-body);
  --font-mono: var(--font-body);

  /* ── geometry ─────────────────────────────────────────────────────────────
     Buttons 2 to 4px; inputs 6px; cards, panels and ledgers 10px. */
  --radius-sm: 4px;
  --radius-md: 10px;
  --radius-lg: 10px;
  --radius-input: 6px;

  /* Spacing steps: 4 6 8 12 16 20 24 28 32 40 48 56 64 72 88 96. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 88px;
  --sp-6: 6px; --sp-20: 20px; --sp-28: 28px; --sp-40: 40px; --sp-56: 56px; --sp-72: 72px; --sp-96: 96px;

  /* The page gutter: 32px, 16px on a phone. */
  --gutter: 32px;
  --container: 1240px;

  /* The gutter between a page's body column and a rail beside it. */
  --rail-gap: 56px;

  /* The ribbon alone. The navy bars above and below it add 3px each. */
  --header-h: 60px;
  --line-h: 3px;
}
@media (max-width: 860px) { :root { --gutter: 16px; --header-h: 44px } }

/* ── base ───────────────────────────────────────────────────────────────── */
* { box-sizing: border-box }
html { scroll-behavior: auto }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important }
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-16);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}
a { color: var(--teal); text-decoration: none }
a:hover { color: var(--teal-hover); text-decoration: underline }
b, strong { font-weight: 600 }
/* Headlines: 600 to 700, tracked tighter above 40px. */
h1, h2, h3, h4 { font-weight: 600; line-height: 1.15; margin: 0 0 var(--space-3); color: var(--text); letter-spacing: -.01em }
h1 { font-size: var(--fs-44); letter-spacing: -.02em }
h2 { font-size: var(--fs-28) }
h3 { font-size: var(--fs-19) }
.wrap { max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter) }

/* Visible keyboard focus (WCAG 2.4.7): a 3px blue ring on every surface.
   Listed per element so it beats any `outline:none` in a page's own rules. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 3px solid var(--focus); outline-offset: 2px; border-radius: 2px;
}

/* Keyboard users must be able to jump the nav. Visible only when focused. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--bg); color: var(--text);
  padding: var(--space-3) var(--space-4); border: 1px solid var(--teal);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0 }

/* ── header ─────────────────────────────────────────────────────────────────
   In the style of a ticket or sports site, in this site's colours:

     .site-line    an empty 3px navy bar above the ribbon (12px, and carrying
                   a key phrase, until 2026-10-09).
     ::after       the matching 3px navy bar under the ribbon. Both bars take
                   the ribbon's own colour since 2026-10-09 (they were
                   near-black), so the three read as one navy band.
     .site-ribbon  the flat navy ribbon, 60px (54 from 1024 to 1239px, 50
                   from 861 to 1023px, 44 on a phone), no border and no shadow.
                   The logo sits on a white panel inset 3px from the screen's
                   left edge, ending in a slant. Nav links are 14px bold capitals; the current page
                   gets a 3px white bar at the foot of the ribbon. On the
                   right, the one flat primary button.

   The proportions are the 2026-10-10 mock's, scaled by 1.2 (the mock was a
   screenshot shrunk to 1024px, which put its links at 11px, under the type
   scale's floor) and then, later the same day, the mark and the ribbon around
   it cut by 30% at every width: above 860px the panel's padding, its slant
   and the bar's height all scaled with the mark, so the brand panel keeps its
   shape at 0.84 of the mock; on a phone the bar dropped to 53px around the
   45px silhouette. The links and the button did not shrink with it.
   Later again on 2026-10-10 the mark was halved (80px to 40px, and the
   panel's padding and slant with it) and the ribbon cut to 60px: the 44px
   primary button and the phone's 44px Menu target set the floor under the
   bar, so it fell by less than the mark. At the two narrower widths the mark
   stops at 33px and 28px rather than a strict half, which would have put the
   wordmark under 25px and unreadable.
     .tabbar       on a phone, a 68px bottom bar with four labelled icon tabs.

   The ribbon is sticky and FLAT: no blur, no translucency. A backdrop-filter
   would also make the ribbon the containing block for the fixed tab bar. */
header.site { position: sticky; top: 0; z-index: 50; background: var(--mast) }
.site-line { margin: 0; height: var(--line-h); background: var(--mast) }
header.site::after { content: ""; display: block; height: var(--line-h); background: var(--mast) }
header.site .site-ribbon, header.site > .wrap {
  display: flex; align-items: stretch; gap: var(--space-4);
  height: var(--header-h); max-width: none; padding: 0 var(--gutter) 0 0;
}
/* The brand panel: white, inset 3px from the screen's left edge so the navy
   frames it on three sides, ending in a slant.

   THE MARK IS /iron-tuna-logo.svg: the tuna and the heavy italic steel
   wordmark of the 2026-10-10 mock, traced from it as two filled paths. It is
   the background of the <svg class="brand-logo"> every page already carries,
   and that SVG's own letters are hidden, so no page's markup changed and the
   element keeps its role="img" and aria-label. The SVG's width is set here
   because its own viewBox is the old wordmark's shape; 2.846 is the new
   mark's aspect ratio. */
.brand {
  --logo-h: 40px;
  position: relative; display: inline-flex; align-items: center; flex: 0 0 auto;
  background: #fff; margin-left: var(--line-h);
  padding: 0 31px 0 22px;
  clip-path: polygon(0 0, 100% 0, calc(100% - 15px) 100%, 0 100%);
}
.brand:hover { text-decoration: none }
.brand b { color: var(--mast) }
.brand-logo {
  display: block; height: var(--logo-h); width: calc(var(--logo-h) * 2.846);
  background: url(/iron-tuna-logo.svg) no-repeat center / contain;
}
.brand-logo > * { display: none }
.nav { display: flex; align-items: stretch; gap: 0; margin-left: var(--space-1) }
.nav a {
  display: inline-flex; align-items: center; position: relative;
  color: var(--mast-ink-hi); font-size: var(--fs-14); font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase; white-space: nowrap; padding: 0 14px;
}
.nav a:hover { background: rgba(255, 255, 255, .08); text-decoration: none; color: #fff }
/* The page you are on: a 3px white bar at the foot of the ribbon. */
.nav a[aria-current="page"]::after { content: ""; position: absolute; left: var(--space-3); right: var(--space-3); bottom: 0; height: 3px; background: #fff }
/* The right side: "Sign in" as bold capitals, the primary button, a thin
   white-outline account button. The product has no in-season sign-in today, so
   the generator emits the button alone; the two neighbours are styled for the
   day it does. */
.nav-signin { align-self: center; color: #fff; font-size: var(--fs-13); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; white-space: nowrap }
.nav-signin:hover { text-decoration: underline; color: #fff }
header.site a.cta {
  align-self: center; display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 var(--space-4); border-radius: 3px; background: var(--mast-btn); color: #fff;
  font-size: var(--fs-14); font-weight: 700; letter-spacing: .02em; text-transform: uppercase; white-space: nowrap;
  margin-left: auto; flex: 0 0 auto;
}
header.site a.cta:hover { background: #196559; text-decoration: none; color: #fff }
.nav-account {
  align-self: center; display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; border: 1px solid rgba(255, 255, 255, .6); border-radius: 2px;
  color: #fff; background: transparent;
}
.nav-account:hover { border-color: #fff; background: rgba(255, 255, 255, .08) }

/* Below 1240px the full-size ribbon (about 1120px of content) does not fit.
   From 1024 to 1239px it drops to the mock at its own 1024px size, less the
   same 30%, then halved again (54px ribbon, a 33px mark, 13px links); from 861 to 1023px a
   smaller mark and tighter links keep the ribbon on one line without a
   sideways scroll. */
@media (min-width: 861px) and (max-width: 1239px) {
  :root { --header-h: 54px }
  .brand { --logo-h: 33px; padding: 0 23px 0 18px; clip-path: polygon(0 0, 100% 0, calc(100% - 13px) 100%, 0 100%) }
  .nav a { font-size: var(--fs-13); padding: 0 8px }
  header.site a.cta { font-size: var(--fs-13); min-height: 40px; padding: 0 var(--space-3) }
}
@media (min-width: 861px) and (max-width: 1023px) {
  :root { --header-h: 50px }
  header.site .site-ribbon, header.site > .wrap { gap: var(--space-3) }
  .brand { --logo-h: 28px; padding: 0 18px 0 10px; clip-path: polygon(0 0, 100% 0, calc(100% - 13px) 100%, 0 100%) }
  .nav a { padding: 0 6px }
}

/* ── nav dropdown ───────────────────────────────────────────────────────── */
/* Inert: the nav is five flat links and no page emits .nav-dd markup any more.
   The rules stay until the navigation has settled, so a revert is one generator
   run. */
.nav-dd { position: relative; display: inline-block }
.nav-dd .nav-dd-menu {
  display: flex; flex-direction: column; visibility: hidden; opacity: 0;
  position: absolute; top: 100%; left: 0;
  background: var(--bg); border: 1px solid var(--line2);
  border-radius: var(--radius-sm); padding: var(--space-2); z-index: 40; min-width: 180px;
}
.nav-dd:hover .nav-dd-menu, .nav-dd:focus-within .nav-dd-menu { visibility: visible; opacity: 1 }
.nav-dd .nav-dd-menu a { display: block; margin: 0; padding: var(--space-3) var(--space-4); text-transform: none; letter-spacing: 0; font-size: var(--fs-16); color: var(--text) }
.nav-dd .nav-dd-menu a:hover { background: var(--panel); text-decoration: none }

/* ── the phone chrome ───────────────────────────────────────────────────────
   One breakpoint, 860px. The ribbon becomes a top bar with the logo and a
   Menu button; the five links open as a panel under it (a real disclosure,
   aria-expanded on the button). The bottom tab bar carries the four places a
   reader goes most. */
.nav-toggle { display: none }
.tabbar { display: none }
@media (max-width: 860px) {
  header.site .site-ribbon, header.site > .wrap { gap: 10px; padding-right: calc(var(--gutter) - var(--space-3)) }
  /* The phone ribbon is navy edge to edge since 2026-10-10 (the evening
     change; that morning's had the logo on a white panel and Menu on a white
     parallelogram beside it, and the two white blocks crowded a 390px bar).
     The mark is the same SVG drawn as a white silhouette, 28px tall (45px
     until the evening of 2026-10-10, 64px on the panel before that), in a
     44px bar, and the mark is a mask rather than a
     background because the file's own fills are navy and steel. Menu is white
     capitals and three white bars, with no box: the one hit target keeps its
     44px minimum. */
  .brand { --logo-h: 28px; background: transparent; clip-path: none; margin-left: 0; padding: 0 0 0 var(--gutter); flex: 0 0 auto }
  .brand-logo {
    background: #fff;
    -webkit-mask: url(/iron-tuna-logo.svg) no-repeat center / contain;
    mask: url(/iron-tuna-logo.svg) no-repeat center / contain;
  }
  header.site a.cta { min-height: 36px; padding: 0 var(--space-3); letter-spacing: .03em }
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    align-self: center; margin-left: auto; flex: 0 0 auto;
    min-height: 44px; min-width: 44px; padding: 0 var(--space-3);
    background: transparent; border: 0; border-radius: 2px;
    color: #fff; font: inherit; font-size: var(--fs-13); font-weight: 700;
    letter-spacing: .05em; text-transform: uppercase; cursor: pointer;
  }
  .nav-toggle:hover { background: rgba(255, 255, 255, .08) }
  .nav-toggle-bars { display: block; width: 16px; height: 10px; position: relative }
  .nav-toggle-bars::before, .nav-toggle-bars::after,
  .nav-toggle-bars span { content: ""; position: absolute; left: 0; right: 0; height: 2px; background: currentColor }
  .nav-toggle-bars::before { top: 0 }
  .nav-toggle-bars span { top: 4px }
  .nav-toggle-bars::after { top: 8px }
  /* The panel drops out of the band and is read against it. */
  .nav {
    position: absolute; top: 100%; left: 0; right: 0; margin: 0;
    display: none; flex-direction: column; align-items: stretch;
    background: var(--mast); border-top: 1px solid var(--mast-line); border-bottom: 1px solid var(--mast-line);
    padding: var(--space-2) var(--gutter) var(--space-4); max-height: calc(100vh - var(--header-h) - 2 * var(--line-h));
    overflow-y: auto; gap: 0;
  }
  header.site[data-nav-open="1"] .nav { display: flex }
  .nav a { font-size: var(--fs-16); letter-spacing: 0; text-transform: none; font-weight: 600; padding: 0; min-height: 48px; border-bottom: 1px solid var(--mast-line) }
  .nav a:last-child { border-bottom: 0 }
  .nav a:hover { background: transparent; text-decoration: underline }
  .nav a[aria-current="page"]::after { left: auto; right: 0; top: 0; bottom: 0; width: 3px; height: auto }
  .nav-dd { display: block }
  .nav-dd .nav-dd-menu { position: static; visibility: visible; opacity: 1; display: flex; background: transparent; border: 0; padding: 0 0 0 var(--space-4) }
  /* No header button: the top bar is the logo and Menu, and the league form
     has a tab below. */
  header.site a.cta { display: none }

  /* The bottom tab bar: 68px, four labelled icon tabs, the active one in the
     accent and bold. The page gets the bar's height as bottom padding so
     nothing ends behind it. */
  body { padding-bottom: 68px }
  .tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 49; height: 68px;
    display: grid; grid-template-columns: repeat(4, 1fr);
    background: #fff; border-top: 1px solid var(--line2);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .tabbar a {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    color: var(--muted); font-size: var(--fs-13); font-weight: 400; min-height: 44px;
  }
  .tabbar a:hover { text-decoration: none; background: var(--panel) }
  .tabbar a svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round }
  .tabbar a[aria-current="page"] { color: var(--teal); font-weight: 700 }
}

/* ── footer ─────────────────────────────────────────────────────────────────
   Navy, on every page. White links; the blurb, the disclosure and the legal
   line in the 74% white. */
footer.site {
  background: var(--mast); color: var(--mast-ink); margin-top: var(--space-9);
  padding: var(--space-7) 0 var(--space-6); font-size: var(--fs-14); line-height: 1.5;
}
footer.site a { color: #fff }
footer.site a:hover { color: #fff; text-decoration: underline }
.foot-nav { margin-bottom: var(--space-5); padding-bottom: var(--space-5); border-bottom: 1px solid var(--mast-line) }
.foot-nav ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-5);
}
.foot-nav a { font-size: var(--fs-14); font-weight: 600 }
.foot-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-5); margin-bottom: var(--space-5) }
.foot-col h2 { font-size: var(--fs-13); color: var(--mast-ink); margin: 0 0 var(--space-3); font-weight: 600 }
.foot-col ul { list-style: none; margin: 0; padding: 0 }
.foot-col li { margin-bottom: var(--space-2) }
.foot-note { font-size: var(--fs-13); line-height: 1.5; max-width: 72ch }
.foot-note p { margin: 0 0 var(--space-3) }
.foot-note b { color: #fff; font-weight: 600 }
.foot-legal { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; color: var(--mast-dim) }
/* The operator's way in. At the end of the legal line, in the line's own dim
   grey, so a reader scanning the footer does not take it for a destination. */
.foot-legal .foot-admin { margin-left: auto; font-size: var(--fs-13); font-weight: 600; color: var(--mast-dim) }
.foot-21 { color: var(--mast-ink) }
.foot-21 b { color: #fff }

/* ── the NFL clock strip ────────────────────────────────────────────────────
   Rendered by /it-season.js on every in-season page from one /api/season read.
   A figure row rather than a chip: label, week, state, hairline between. */
.its-strip {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  padding: var(--space-3) 0; margin: var(--space-4) 0 var(--space-5);
  font-size: var(--fs-14); color: var(--sec);
}
.its-strip .its-phase { font-size: var(--fs-13); color: var(--muted) }
.its-strip .its-phase.its-off { color: var(--muted) }
.its-strip .its-week { font-weight: 600; color: var(--text); font-size: var(--fs-16) }
.its-strip .its-state { color: var(--text) }
.its-strip .its-state.its-live { color: var(--text); font-weight: 600 }
.its-strip .its-counts, .its-strip .its-next, .its-strip .its-note { color: var(--muted); font-size: var(--fs-13) }
.its-strip .its-next { margin-left: auto }

/* ── the in-season section ─────────────────────────────────────────────────
   Shared by the tools under /weekly-intel … /my-league. Labels are sentence
   case at 13 or 14px with no tracking; prose stops at 72ch. */
.is-eyebrow { font-size: var(--fs-13); color: var(--muted); margin: var(--space-6) 0 var(--space-2); letter-spacing: 0; text-transform: none; font-family: var(--font-body) }
.is-lede { font-size: var(--fs-19); color: var(--sec); margin: 0 0 var(--space-5); max-width: 60ch; line-height: 1.5 }
.is-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-5) }
@media (max-width: 860px) { .is-grid { grid-template-columns: 1fr } }
.is-card {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-5);
}
.is-card h3 { margin: 0 0 var(--space-2); font-size: var(--fs-19) }
.is-card p { margin: 0; font-size: var(--fs-16); color: var(--sec); line-height: 1.5 }
/* The disclosure label: amber, 12px, sentence case. The one thing on the site
   allowed under 13px. */
.is-tag {
  display: inline-block; font-size: var(--fs-12); font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--goldink); background: var(--gold);
  border: 0; border-radius: 2px; padding: 2px 6px; margin-bottom: var(--space-2); line-height: 1.5;
}
.is-tag.is-live { color: var(--text); background: var(--elev) }
.is-table { width: 100%; border-collapse: collapse; font-size: 15px }
.is-table th, .is-table td { padding: var(--space-4); border-bottom: 1px solid var(--line); text-align: left }
.is-table th { color: var(--muted); font-size: var(--fs-13); font-weight: 600; text-transform: none; letter-spacing: 0 }
.is-table td.num, .is-table th.num { text-align: right; font-variant-numeric: tabular-nums }
.is-table tbody tr.on > td, .is-table tbody tr[aria-selected="true"] > td { background: var(--teal-tint) }
.is-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch }
.is-status { font-size: var(--fs-13); font-weight: 600; text-transform: none; letter-spacing: 0; color: var(--muted) }
.is-status.live { color: var(--text) }
.is-status.final { color: var(--text) }
.is-note { font-size: var(--fs-14); color: var(--muted); line-height: 1.5; margin: var(--space-4) 0 0; max-width: 72ch }
.is-empty { color: var(--muted); font-size: var(--fs-16); margin: var(--space-4) 0 }

/* ── the sheet plate ──────────────────────────────────────────────────────────
   The tinted panel: ledgers, tables and form cards sit on it, with the 3px navy
   top rule and 10px corners. Every board in the section is this same object. */
.is-plate {
  border: 0; border-top: 3px solid var(--mast); border-radius: var(--radius-md); background: var(--panel);
  overflow: hidden; margin: var(--space-4) 0 var(--space-5);
}
.is-plate-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-5);
  border-bottom: 1px solid var(--line2); padding: var(--space-3) var(--space-4);
  font-size: var(--fs-13); color: var(--muted);
}
.is-plate-head > span, .is-plate-head > div { padding: 0; border: 0 }
.is-plate-head > :first-child { flex: 1; min-width: 16ch; color: var(--text); font-size: var(--fs-16); font-weight: 600 }
.is-plate-head > span:not(:first-child) { white-space: nowrap }
.is-plate-foot {
  display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-5); align-items: center;
  padding: var(--space-3) var(--space-4); border-top: 1px solid var(--line2);
}
.is-plate-foot p { margin: 0; flex: 1; min-width: 22ch; font-size: var(--fs-13); color: var(--muted); line-height: 1.5 }
.is-plate-state { margin: 0; padding: var(--space-6) var(--space-4); font-size: var(--fs-16); color: var(--muted); line-height: 1.5 }
.is-plate .is-table { margin: 0 }
.is-plate .is-table th, .is-plate .is-table td { border-bottom-color: var(--line2) }
.is-plate .is-table tbody tr:last-child td { border-bottom: 0 }

/* The one number a row exists to print: 28px on a row, tabular, 600. */
.is-val { font-size: var(--fs-28); font-weight: 600; letter-spacing: -.01em; color: var(--text); font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1.1 }
.is-table .is-who { color: var(--text); font-weight: 600 }
.is-table .is-who small { font-weight: 400; color: var(--muted); font-size: var(--fs-13) }
.is-rank { font-size: var(--fs-13); font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums }

/* Tags. Every one is a word, not a colour: the market's read is stated. */
.is-tag.is-accent { color: var(--text); background: var(--elev) }
.is-tag.is-flat { color: var(--sec); background: var(--elev) }
.is-tag.is-plain { color: var(--muted); background: var(--elev) }
.is-table .is-tag { margin: 0 }

/* ── section labels and lane cards ───────────────────────────────────────── */
.is-label { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); flex-wrap: wrap; margin: var(--space-8) 0 var(--space-3) }
.is-label .is-eyebrow { margin: 0 }
.is-rule { height: 1px; border: 0; margin: 0 0 var(--space-5); background: var(--line) }

.is-lane { display: flex; flex-direction: column; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md); padding: var(--space-5) }
.is-lane-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3) }
.is-lane-head h3 { font-weight: 600; font-size: var(--fs-23); line-height: 1.15; letter-spacing: -.01em; text-transform: none; margin: 0 }
.is-lane-head span { font-size: var(--fs-13); color: var(--muted) }
.is-lane > p { margin: var(--space-3) 0 0; font-size: var(--fs-16); color: var(--sec); line-height: 1.5 }
.is-lane ul { list-style: none; margin: var(--space-4) 0; padding: 0; flex: 1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--space-4); align-content: start }
.is-lane > .is-btn { align-self: stretch }
@media (max-width: 480px) { .is-lane ul { grid-template-columns: 1fr } }
.is-lane li { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); padding: var(--space-2) 0; border-bottom: 1px solid var(--line); font-size: var(--fs-14); color: var(--sec) }
.is-lane li:last-child { border-bottom: 0 }

/* ── the page head's action row ──────────────────────────────────────────────
   One primary action, one link into the page's own evidence, a hint. Square
   buttons; the row reads as actions, not as a third paragraph. */
.is-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin: 0 0 var(--space-5) }
.is-actions .is-btn { border-radius: var(--radius-sm) }
.is-actions .is-hint { margin-left: var(--space-1) }
@media (max-width: 560px) { .is-actions .is-btn { flex: 1 1 auto } .is-actions .is-hint { flex: 1 1 100% } }

/* ── the readings strip ───────────────────────────────────────────────────────
   One card per lane inside the plate: a 13px label over the lane's one figure. */
.is-readings { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); background: var(--panel) }
.is-readings[hidden] { display: none }
.is-reading { display: flex; flex-direction: column; gap: 6px; padding: var(--space-4) var(--space-5); border-left: 1px solid var(--line2); min-width: 0 }
.is-reading:first-child { border-left: 0 }
.is-reading-k { font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.is-reading-k a { color: inherit }
.is-reading b { font-size: var(--fs-16); line-height: 1.3; color: var(--text); overflow-wrap: anywhere }
.is-reading-v { font-size: var(--fs-28); font-weight: 600; line-height: 1.1; letter-spacing: -.01em; color: var(--text); font-variant-numeric: tabular-nums; margin: 2px 0 }
.is-reading p { margin: 0; font-size: var(--fs-14); color: var(--sec); line-height: 1.5 }
.is-reading-go { margin-top: auto; padding-top: var(--space-2); font-size: var(--fs-14); font-weight: 600; color: var(--teal) }
.is-reading.blank b { color: var(--sec); font-weight: 600 }
@media (max-width: 860px) {
  .is-readings { grid-template-columns: 1fr }
  .is-reading { border-left: 0; border-top: 1px solid var(--line2) }
  .is-reading:first-child { border-top: 0 }
}

/* ── buttons ─────────────────────────────────────────────────────────────────
   Squared rectangles, 4px, 44px minimum. Primary: teal fill, white text.
   Secondary: white with a control border. Tertiary: text-only teal. One
   primary per view. */
.is-btn, .btn-primary, .btn-secondary, .btn-tertiary {
  display: inline-flex; align-items: center; justify-content: center; font: inherit;
  font-size: var(--fs-16); font-weight: 600; letter-spacing: 0; text-transform: none;
  min-height: 44px; padding: 0 var(--space-5); border-radius: var(--radius-sm); border: 1px solid transparent;
  cursor: pointer; white-space: nowrap; text-align: center; line-height: 1.2;
}
.is-btn.primary, .btn-primary { background: var(--teal); color: #fff; border-color: var(--teal) }
.is-btn.primary:hover, .btn-primary:hover { background: var(--teal-hover); border-color: var(--teal-hover); color: #fff; text-decoration: none }
.is-btn.sec, .btn-secondary { background: #fff; color: var(--text); border-color: var(--line2) }
.is-btn.sec:hover, .btn-secondary:hover { background: var(--panel); color: var(--text); text-decoration: none }
.is-btn.ghost, .btn-tertiary { background: transparent; color: var(--teal); border-color: transparent; padding-left: var(--space-2); padding-right: var(--space-2) }
.is-btn.ghost:hover, .btn-tertiary:hover { color: var(--teal-hover); text-decoration: underline }
.is-btn.big { min-height: 52px; padding: 0 var(--space-6) }
.is-btns { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; margin: var(--space-5) 0 0 }

/* ── segmented controls ──────────────────────────────────────────────────────
   Real radios with the input visually hidden. The chosen segment is ink-filled
   so the state is shape and colour both. */
.is-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--line2); border-radius: var(--radius-sm); overflow: hidden; background: var(--bg) }
.is-seg label {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  padding: 0 14px; min-height: 44px; cursor: pointer; font-size: var(--fs-14); font-weight: 600;
  letter-spacing: 0; color: var(--sec); border-left: 1px solid var(--line2); white-space: nowrap;
}
.is-seg label:first-child { border-left: 0 }
.is-seg input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0 }
.is-seg label:hover { background: var(--panel); color: var(--text) }
.is-seg label:has(input:checked) { background: var(--text); color: #fff }
.is-seg label:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: -3px }

/* Filter chips: ink-filled when pressed, with aria-pressed carrying the state. */
.chip { display: inline-flex; align-items: center; min-height: 36px; padding: 0 12px; border: 1px solid var(--line2); border-radius: var(--radius-sm); background: var(--bg); color: var(--sec); font: inherit; font-size: var(--fs-14); font-weight: 600; cursor: pointer }
.chip:hover { background: var(--panel); color: var(--text); text-decoration: none }
.chip[aria-pressed="true"], .chip.on { background: var(--text); border-color: var(--text); color: #fff }

/* ── forms ───────────────────────────────────────────────────────────────────
   A form is one of the three enclosed surfaces: the tinted panel with the navy
   rule. Inputs 48px, 16px text, a visible 14px/600 label above, an optional 13px
   hint below, 6px corners, accent border plus a 3px tint ring on focus.
   Validation is a plain sentence under the field. */
.is-form { display: flex; flex-direction: column; gap: var(--space-4); background: var(--panel); border: 0; border-top: 3px solid var(--mast); border-radius: var(--radius-md); padding: var(--space-5) }
.is-field { display: flex; flex-direction: column; gap: 6px }
.is-field > label { font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text) }
.is-field > label small { font-weight: 400; color: var(--muted); font-size: var(--fs-13) }
.is-input { min-height: 48px; padding: 0 var(--space-3); border-radius: var(--radius-input); border: 1px solid var(--line2); background: #fff; color: var(--text); font: inherit; font-size: var(--fs-16); width: 100% }
textarea.is-input { min-height: 96px; padding: var(--space-3) }
.is-input:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-tint) }
.is-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4) }
.is-submit { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap }
.is-hint { font-size: var(--fs-13); color: var(--muted); margin: 0 }
.is-hint.err { color: var(--danger) }
.is-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-5) }
/* A 20px checkbox for an acknowledgment gate. */
.is-check { display: flex; align-items: flex-start; gap: var(--space-3); font-size: var(--fs-16); min-height: 44px }
.is-check input { width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--teal); flex: 0 0 auto }

/* An inline warning note. */
.is-warn { background: var(--warn-bg); color: var(--danger); border-radius: var(--radius-sm); padding: var(--space-3) var(--space-4); font-size: var(--fs-14); margin: var(--space-3) 0 }

/* ── the league importer ─────────────────────────────────────────────────────
   it-inseason-ui.js importer(): the draft app's intake, on the in-season forms.
   Scoped to .is-imp; nothing restyles the fields around it. */
.is-imp { border: 1px solid var(--line2); border-radius: var(--radius-md); background: var(--bg); padding: var(--space-4) }
.is-imp-head { display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--space-3) }
.is-imp-head b { font-size: var(--fs-16) }
.is-imp-head span { font-size: var(--fs-14); color: var(--muted); line-height: 1.5 }
.is-imp-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-3) }
.is-imp-tab {
  font: inherit; font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none;
  min-height: 36px; padding: 0 12px; border-radius: var(--radius-sm); border: 1px solid var(--line2);
  background: var(--bg); color: var(--sec); cursor: pointer;
}
.is-imp-tab:hover { background: var(--panel); color: var(--text) }
.is-imp-tab.active { background: var(--text); border-color: var(--text); color: #fff }
.is-imp-body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3) }
.is-imp-hint { font-size: var(--fs-14); color: var(--sec); line-height: 1.5; margin: 0 }
.is-imp-text { font-size: var(--fs-14); resize: vertical }
.is-imp-presets { display: flex; flex-wrap: wrap; gap: 6px }
.is-imp-presets .is-btn { min-height: 36px; padding: 0 12px; font-size: var(--fs-14) }
.is-imp-status { font-size: var(--fs-14); color: var(--text); margin: var(--space-3) 0 0; font-weight: 600 }
.is-imp-status.busy { color: var(--muted); font-weight: 400 }
.is-imp-preview { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--line2) }
.is-imp-from { display: block; font-size: var(--fs-13); color: var(--muted); margin-bottom: 6px }
.is-imp-chip {
  display: inline-block; margin: 0 5px 5px 0; padding: 4px 8px; border-radius: var(--radius-sm);
  border: 1px solid var(--line2); background: var(--bg); font-size: var(--fs-13); color: var(--sec);
}
.is-imp-chip b { color: var(--text); font-variant-numeric: tabular-nums }
.is-imp-preview .is-btns { margin-top: var(--space-2) }

/* ── the three boxes ─────────────────────────────────────────────────────────
   it-inseason-ui.js intake(): scoring, rosters and the FAAB budget, side by
   side on /my-league. The drop zone is position:relative and the veil covers
   it, so a dragged file lands in the form rather than navigating away. */
.is-intake-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--space-4) }
.is-intake-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  background: var(--bg); border: 1px solid var(--line2); border-radius: var(--radius-md);
  padding: var(--space-4);
}
.is-intake-top { display: flex; align-items: center; gap: 10px }
.is-intake-n { display: none }
.is-intake-top b { font-size: var(--fs-16); line-height: 1.2; color: var(--text) }
.is-intake-hint { font-size: var(--fs-14); color: var(--muted); line-height: 1.5; margin: 0 }
.is-intake-drop { position: relative; display: flex }
.is-intake-lab { font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text) }
.is-intake-text { font-size: var(--fs-14); line-height: 1.5; resize: vertical; display: block }
.is-intake-text::placeholder { color: var(--muted); opacity: .75 }
.is-intake-veil {
  position: absolute; inset: 0; display: none; place-items: center;
  border: 2px dashed var(--teal); border-radius: var(--radius-input);
  background: var(--teal-tint); pointer-events: none;
}
.is-intake-veil span { font-size: var(--fs-14); font-weight: 600; color: var(--teal) }
.is-intake-drop.over .is-intake-veil { display: grid }
.is-intake-acts { display: flex; flex-wrap: wrap; gap: var(--space-2) }
.is-intake-acts .is-btn { min-height: 36px; padding: 0 12px; font-size: var(--fs-14) }
.is-intake-chips { display: flex; flex-wrap: wrap; gap: 6px }
.is-intake-quick {
  font: inherit; font-size: var(--fs-14); font-weight: 600; min-height: 36px; padding: 0 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--line2); background: var(--bg); color: var(--sec); cursor: pointer;
}
.is-intake-quick:hover { background: var(--panel); color: var(--text) }
.is-intake-out:empty { display: none }
.is-intake-out { margin-top: auto }
.is-intake-out .is-imp-status { margin-top: 0 }
.is-intake-out .is-imp-preview { margin-top: var(--space-2) }
.is-intake-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--line2);
}
.is-intake-foot-l { font-size: var(--fs-14); color: var(--sec); line-height: 1.5 }
.is-intake-note { font-size: var(--fs-13); color: var(--muted); margin: 0; line-height: 1.5 }
.is-intake-foot > :first-child ~ .is-intake-note { margin-left: auto }
@media (max-width: 640px) { .is-intake-foot > :first-child ~ .is-intake-note { margin-left: 0 } }

/* ── the detail behind a setting ───────────────────────────────────────────── */
.is-more { border: 1px solid var(--line2); border-radius: var(--radius-sm); background: var(--bg); padding: var(--space-3) }
.is-more > summary { cursor: pointer; font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text) }
.is-more > summary small { font-weight: 400; color: var(--muted); margin-left: 6px }
.is-more[open] > summary { margin-bottom: var(--space-3) }
.is-grid4 { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3) }
.is-slots { display: grid; grid-template-columns: repeat(auto-fit, minmax(74px, 1fr)); gap: var(--space-2) }
.is-slot { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-13); font-weight: 600; letter-spacing: 0; color: var(--muted) }
.is-slot .is-input { min-height: 40px; padding: 0 9px; text-align: center; font-variant-numeric: tabular-nums }
.is-slot.wide { font-size: var(--fs-14) }
.is-slot.wide .is-input { text-align: left }
.is-slots + .is-hint { margin-top: var(--space-3) }

/* ── the desk byline ──────────────────────────────────────────────────────
   Stamped by tools/build-bylines.mjs. Kept here so a page with no inline copy
   is not left rendering its byline as an unstyled paragraph. */
.byline { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; font-size: var(--fs-14); color: var(--muted); margin: 2px 0 10px }
.byline b { color: var(--sec); font-weight: 600 }
.byline .dot { opacity: .5 }
.byline b a { color: inherit; text-decoration: none }
.byline b a:hover { color: var(--teal); text-decoration: underline }

@media (max-width: 640px) {
  .is-row2 { grid-template-columns: 1fr }
  .is-plate-head > :first-child { flex-basis: 100% }
}

/* ── the player lookup menu ──────────────────────────────────────────────────
   Positioned in viewport coordinates by /player-search.js and parented to
   <body>, so these rules cannot depend on an ancestor. Shared here because the
   search field is on the homepage and on /player. */
.pl-menu { position: fixed; z-index: 60; background: var(--bg); border: 1px solid var(--line2); border-radius: var(--radius-input); overflow: hidden; max-height: min(60vh, 420px); overflow-y: auto; -webkit-overflow-scrolling: touch }
.pl-menu[hidden] { display: none }
.pl-opt { display: flex; align-items: center; gap: 11px; width: 100%; min-height: 44px; padding: 6px 13px; border: 0; border-bottom: 1px solid var(--line); background: none; font: inherit; text-align: left; cursor: pointer; color: var(--text) }
.pl-opt:last-child { border-bottom: 0 }
.pl-opt:hover, .pl-opt.on { background: var(--panel); text-decoration: none }
.pl-meta { min-width: 0 }
.pl-opt b { display: block; font-weight: 600; font-size: var(--fs-16); line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
.pl-opt b em { font-style: normal; background: var(--teal-tint) }
.pl-meta span { display: block; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); margin-top: 1px }
.pl-face { flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--radius-sm); background: var(--elev); overflow: hidden; position: relative; display: grid; place-items: center }
.pl-face img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center }
.pl-face i { font-style: normal; font-size: var(--fs-13); font-weight: 600; color: var(--muted) }
.pl-empty { padding: 12px 14px; font-size: var(--fs-14); color: var(--muted) }
/* The homepage box also offers destinations (rankings, trades, DFS, ...): a
   small label over each group, and an arrow glyph in the face's slot. */
.pl-group { padding: 10px 13px 4px; font-size: var(--fs-12); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted) }
.pl-face.pl-tool { color: var(--teal) }

/* ── the search field ────────────────────────────────────────────────────────
   The one pill on a page, and it is a field, not a button. 52px, 16px text,
   the accent border and a 3px tint ring on focus. The idle border is 2px of
   --sec rather than the 1px --line2 every other control wears: on the paper
   ground the hairline read as a faint oval and the field did not stand out
   from the hero copy around it (2026-10-10). Box-sizing keeps it at 52px. */
.search-field { display: flex; align-items: center; gap: var(--space-3); min-height: 52px; padding: 0 var(--space-3) 0 var(--space-4); border: 2px solid var(--sec); border-radius: 999px; background: #fff }
.search-field:focus-within { border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-tint) }
.search-field svg { width: 20px; height: 20px; flex: 0 0 auto; color: var(--muted) }
.search-field input { flex: 1; min-width: 0; border: 0; outline: 0; background: none; font: inherit; font-size: var(--fs-16); color: var(--text); min-height: 48px }
.search-field input::placeholder { color: var(--muted); opacity: 1 }
.search-field input::-webkit-search-decoration, .search-field input::-webkit-search-cancel-button { -webkit-appearance: none }
.search-field button { min-height: 44px; padding: 0 var(--space-4); border-radius: var(--radius-sm) }

/* ── the ledger ──────────────────────────────────────────────────────────────
   A list is a ledger: full-width rows with hairline rules, 8 to 12px of row
   padding, a name column then three or four figure columns, each a 13px label
   over a 17px figure, right-aligned. The row's key figure is 28px. */
.ledger { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--line) }
.ledger-row, .ledger > li { display: grid; grid-template-columns: minmax(0, 1fr) repeat(var(--cols, 3), minmax(84px, auto)); gap: var(--space-4) var(--space-5); align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line) }
a.ledger-row { color: inherit }
a.ledger-row:hover { background: var(--panel); text-decoration: none; color: inherit }
.ledger-name { min-width: 0 }
.ledger-name b { display: block; font-weight: 600; font-size: var(--fs-16); line-height: 1.3; color: var(--text); overflow-wrap: anywhere }
.ledger-name span { display: block; font-size: var(--fs-13); color: var(--muted); margin-top: 2px }
.ledger-fig { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap }
.ledger-fig small, .ledger-fig .k { display: block; font-size: var(--fs-13); color: var(--muted); line-height: 1.3 }
.ledger-fig b, .ledger-fig .v { display: block; font-size: 17px; font-weight: 600; color: var(--text); line-height: 1.3 }
.ledger-fig.key b, .ledger-fig.key .v { font-size: var(--fs-28); line-height: 1.1; letter-spacing: -.01em }
.ledger-fig .good { color: var(--green) }
@media (max-width: 860px) {
  .ledger-row, .ledger > li { grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2) var(--space-4) }
  .ledger-fig:not(.key):not(.keep) { display: none }
}

/* ── the figure row ──────────────────────────────────────────────────────────
   Label left, value right, hairlines between rows; a total row gets a 2px ink
   top rule and a 28px figure. */
.figs { margin: 0; padding: 0; list-style: none }
.figs > li, .fig-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); padding: 10px 0; border-bottom: 1px solid var(--line); font-size: var(--fs-16) }
.figs > li span:first-child, .fig-row span:first-child { color: var(--sec) }
.figs > li span:last-child, .fig-row span:last-child { font-weight: 600; font-variant-numeric: tabular-nums; text-align: right }
.figs > li.total, .fig-row.total { border-top: 2px solid var(--text); border-bottom: 0; padding-top: var(--space-3) }
.figs > li.total span:last-child, .fig-row.total span:last-child { font-size: var(--fs-28); line-height: 1.1 }

/* ── the KPI band ────────────────────────────────────────────────────────────
   Navy, white figures, labels in the 74% white. Written hidden and shown only
   with every figure present. */
.kpi { background: var(--mast); color: #fff; padding: var(--space-7) 0 }
.kpi[hidden] { display: none }
.kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-5) }
.kpi-grid > div { border-left: 1px solid var(--mast-line); padding-left: var(--space-5) }
.kpi-grid > div:first-child { border-left: 0; padding-left: 0 }
.kpi b { display: block; font-size: var(--fs-44); font-weight: 600; letter-spacing: -.02em; line-height: 1.05; font-variant-numeric: tabular-nums; color: #fff }
.kpi b.good { color: var(--mint) }
.kpi span { display: block; margin-top: var(--space-2); font-size: var(--fs-14); color: var(--mast-ink) }
@media (max-width: 860px) {
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5) var(--space-4) }
  .kpi-grid > div { border-left: 0; padding-left: 0 }
  .kpi b { font-size: var(--fs-34) }
}

/* ── the sticky action panel ────────────────────────────────────────────────
   A detail page is 1fr of content plus a 420px aside at a 56px gap. The aside
   is the one sticky enclosed surface: the tinted panel with the navy rule and
   a 52px primary action. */
.detail { display: grid; grid-template-columns: minmax(0, 1fr) 420px; gap: var(--space-7) var(--rail-gap); align-items: start }
.detail-aside { position: sticky; top: calc(var(--header-h) + 2 * var(--line-h) + var(--space-4)); background: var(--panel); border-top: 3px solid var(--mast); border-radius: var(--radius-md); padding: var(--space-5) }
.detail-aside .is-val, .detail-aside .key-figure { font-size: var(--fs-44); letter-spacing: -.02em }
.detail-aside .is-btn { width: 100%; min-height: 52px; margin-top: var(--space-4) }
@media (max-width: 860px) { .detail { grid-template-columns: minmax(0, 1fr) } .detail-aside { position: static } }

/* ── the three states ───────────────────────────────────────────────────────
   Empty: one plain sentence saying why, then one next step. Loading: skeleton
   blocks that match the final geometry, no shimmer. Page error: an h2, one
   line, one button home. */
.state-empty { font-size: var(--fs-16); color: var(--sec); margin: var(--space-4) 0 }
.state-empty a { font-weight: 600 }
.skeleton { display: block; background: var(--elev); border-radius: var(--radius-sm); min-height: 1em }
.skeleton.row { height: 44px; margin: 0 0 1px }
.page-error { text-align: left; padding: var(--space-8) 0 }
.page-error h2 { font-size: var(--fs-28); margin-bottom: var(--space-2) }
.page-error p { color: var(--sec); margin: 0 0 var(--space-5) }

/* Everything a reader does not see is not drawn, and the attribute means what
   it says even against a class that sets display. */
[hidden] { display: none !important }
.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0 }

/* ══════════════════════════════════════════════════════════════════════════
   THE LAYOUT LAYER
   ══════════════════════════════════════════════════════════════════════════
   Everything above this line is a component. What the site did not have was a
   way to COMPOSE components, and that is what made the pages read as a wall:
   every section on every in-season page was an <h2>, a paragraph and a grid of
   identical gray rectangles, at one width, on one surface, in one weight. Ten
   of those in a column is not ten sections, it is one long section a reader
   cannot navigate.

   What is added here is four things and no new colors:

     1. A WIDTH for boards. .wrap is 820px, which is a reading measure and the
        right one for prose. A nine-column salary table squeezed into it scrolls
        sideways on a desktop monitor, which is why the tool pages looked
        cramped. .wrap.wide is 1200px and is opted into per page.
     2. A CHAPTER HEAD, so sections have a rank instead of all being h2.
     3. TWO SURFACES beyond white — a tinted band and a dark board — so the
        page has a rhythm and the primary answer can be the darkest thing on it.
     4. NUMBERS AS TYPE. The display face at 34-44px, tabular, is the site's
        strongest visual asset and it was being used for one thing.
   ══════════════════════════════════════════════════════════════════════════ */

.wrap.wide { max-width: 1200px }

/* ── the chapter head ───────────────────────────────────────────────────────
   Eyebrow, title, and the link out of the section, on one baseline with a rule
   under it. The rule is the ink color and carries a short accent tick, so a
   section boundary is visible at a glance from across the page rather than
   being inferred from the size of a heading. */
.is-sec { margin: var(--space-8) 0 var(--space-5); position: relative; border-bottom: 1px solid var(--line); padding-bottom: var(--space-3) }
.is-sec-top { display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap }
.is-sec h2, .is-sec .is-sec-h {
  font-weight: 600; font-size: var(--fs-28);
  letter-spacing: -.01em; line-height: 1.15; margin: 0; text-transform: none; color: var(--text);
}
/* No numbered section chips: the number is not drawn. */
.is-sec .is-sec-n { display: none }
.is-sec .is-sec-more { margin-left: auto; font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--teal); white-space: nowrap }
.is-sec p { margin: var(--space-3) 0 0; font-size: var(--fs-md); color: var(--sec); line-height: 1.6; max-width: 72ch }

/* ── the opening band, and the ribbon under it ───────────────────────────────
   The homepage opens on a dark band that continues the masthead, with a ribbon
   of destinations directly under it. Every section page opened instead on an
   h1 sitting on the same white ground as the tables below it, so the first
   thing a reader met was a heading with no band around it — and each page then
   grew its own answer to that, which is exactly how the chrome drifted the
   first time. This is the one copy.

   It uses the masthead palette declared at the top of this file, so a page
   opts in with class names alone and declares no color of its own.

   .is-hero   the dark opening band: a dateline, the page's thesis, its lede
              and its actions. Full-bleed, with its own .wrap inside.
   .is-ribbon the sticky bar under it. It carries whatever governs the whole
              page — the page's own switches where it has them, its section
              anchors where it does not. It sticks BELOW the site header
              (sticky at 0, z-index 50) and under the header's nav dropdown,
              at the same z-index as the rankings ribbon. */

.is-hero { background: var(--mast); color: var(--mast-ink-hi) }
.is-hero > .wrap { padding-top: clamp(26px, 4.4vw, 52px); padding-bottom: clamp(24px, 4vw, 46px) }
/* The section label and the NFL clock are ONE dateline, not two stacked
   kickers: same line, same size, divided by a hairline. */
.is-hero-date { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-3); margin: 0 0 var(--space-4) }
.is-hero .is-eyebrow {
  color: var(--mast-accent); margin: 0;
  padding-right: var(--space-3); border-right: 1px solid rgba(255, 255, 255, .16);
}
.is-hero h1 {
  color: var(--mast-ink-hi); font-size: clamp(34px, 4vw, 56px); line-height: 1.05; font-weight: 600;
  max-width: 24ch; margin: 0 0 var(--space-4); letter-spacing: -.025em; text-wrap: balance;
}
/* The clause the page is actually FOR is the only colored words in the band.
   Same device as the homepage headline. */
.is-hero h1 em { font-style: normal; color: #fff }
.is-hero .is-lede { color: var(--mast-ink); max-width: 68ch; margin: 0 0 var(--space-5) }
.is-hero .is-actions { margin: 0 }
.is-hero .is-hint { color: var(--mast-dim) }
.is-hero .is-btn.primary { background: var(--mast-btn); border-color: var(--mast-btn) }
.is-hero .is-btn.ghost { background: transparent; color: var(--mast-ink-hi); border-color: rgba(255, 255, 255, .6) }
.is-hero .is-btn.ghost:hover { background: rgba(255, 255, 255, .1); border-color: #fff; color: #fff }
.is-hero .is-btn:focus-visible, .is-hero-how:focus-visible { outline: 3px solid #fff; outline-offset: 3px }
/* Deliberately quieter than the buttons beside it: the third thing to do on
   the page, not a third button competing with the first two. */
.is-hero-how {
  display: inline-block; margin-top: var(--space-5);
  font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--mast-ink); border-bottom: 1px solid var(--mast-line); padding-bottom: 2px;
}
.is-hero-how:hover { color: #fff; border-bottom-color: #fff; text-decoration: none }
/* The NFL clock, the same strip off the same one /api/season read, painted on
   the dark ground: a dateline rather than the grey plate it sits in on white. */
.is-hero .its-strip {
  background: transparent; border: 0; border-radius: 0; padding: 0; margin: 0;
  color: var(--mast-ink); font-size: var(--fs-xs); gap: 6px var(--space-3);
}
.is-hero .its-strip .its-phase { color: var(--mast-accent) }
.is-hero .its-strip .its-week, .is-hero .its-strip .its-state { color: #fff }
.is-hero .its-strip .its-counts, .is-hero .its-strip .its-next, .is-hero .its-strip .its-note { color: var(--mast-dim) }
.is-hero .its-strip .its-next { margin-left: 0 }
@media (max-width: 560px) {
  .is-hero h1 { max-width: none }
  .is-hero .is-eyebrow { border-right: 0; padding-right: 0 }
  .is-hero .is-actions .is-btn { flex: 1 1 100% }
}

/* An anchor on a page with a ribbon has to clear BOTH sticky bars — the site
   header and the ribbon under it — or it lands with its own chapter head
   hidden behind them. Scoped to the pages that actually carry one, so an
   anchor on the other ninety keeps landing where it always has. */
html:has(.is-ribbon) { scroll-padding-top: calc(var(--header-h) + 2 * var(--line-h) + 60px) }
@media (max-width: 860px) { html:has(.is-ribbon) { scroll-padding-top: 85px } }

.is-ribbon {
  position: sticky; top: calc(var(--header-h) + 2 * var(--line-h)); z-index: 45;
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.is-ribbon-in {
  max-width: 1200px; margin: 0 auto; padding: 9px var(--space-5);
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px var(--space-3);
}
.is-ribbon-lab { font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted); flex: 0 0 auto }
.is-ribbon .rk-seg { background: var(--bg); flex: 0 0 auto }
.is-ribbon .rk-seg button { padding: 8px 13px; font-size: 14.5px }
/* A ribbon of section anchors, for a page whose whole-page control is simply
   where on it you are. Same band, same height, same weight as the switches. */
.is-ribbon-jump { display: flex; align-items: stretch; flex-wrap: wrap; gap: 2px; min-width: 0 }
.is-ribbon-jump a {
  display: flex; align-items: center; white-space: nowrap; font-size: var(--fs-14); font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--sec);
  padding: 10px 11px; border-radius: var(--radius-sm);
}
.is-ribbon-jump a:hover, .is-ribbon-jump a:focus-visible { color: var(--text); background: var(--panel); text-decoration: none }
/* On a phone the ribbon SCROLLS SIDEWAYS rather than wrapping: a wrapping row
   of this many items comes out three deep, and a sticky bar that tall takes a
   third of the screen away from the thing it exists to navigate. */
@media (max-width: 860px) {
  .is-ribbon { top: calc(var(--header-h) + var(--line-h)) }
  .is-ribbon-in {
    padding: 8px 14px; gap: var(--space-2);
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  }
  .is-ribbon-in::-webkit-scrollbar { display: none }
  .is-ribbon .rk-seg { flex-wrap: nowrap }
  .is-ribbon .rk-seg button { padding: 8px 10px; font-size: 14px }
  .is-ribbon-jump { flex-wrap: nowrap }
  .is-ribbon-jump a { padding: 9px 9px; font-size: var(--fs-13) }
}

/* ── surfaces ───────────────────────────────────────────────────────────────
   A tinted band breaks the column without a new color: it is --panel edge to
   edge with hairlines top and bottom, and the cards inside it flip to white so
   the contrast between card and ground survives the change of ground. The
   plate, the lane and the form are the same --panel as the band and needed
   the same flip; the rule named only the card, so a plate inside a band (the
   standing argument on /fantasy) was panel on panel with a hairline for a
   border. Those four are every --panel surface the system has. */
.is-band { background: var(--panel); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: var(--space-6) 0 var(--space-7); margin: var(--space-7) 0 }
.is-band > .wrap > .is-sec:first-child { margin-top: 0 }
/* A band that is the FIRST thing under the ribbon has no gap above it to float
   in: the ribbon's own hairline is the seam. */
.is-band.is-band-top { margin-top: 0 }
.is-band .is-card, .is-band .is-plate, .is-band .is-lane, .is-band .is-form { background: var(--bg) }

/* The dark board. One per page at most: the answer the page exists to give.
   It is the only inverted surface in the light system, which is exactly why it
   works as a focal point -- and its own tokens are restated locally so nothing
   inside it has to know it is on a dark ground. */
.is-board {
  --board-line: var(--mast-line);
  background: var(--mast); color: #fff; border-radius: var(--radius-md);
  border: 0; overflow: hidden; margin: var(--space-5) 0;
}
.is-board a { color: #fff; text-decoration: underline }
.is-board .is-board-head {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); align-items: baseline;
  justify-content: space-between; padding: var(--space-4) var(--space-5) 0;
}
.is-board .is-board-head h3 { margin: 0; font-weight: 600; font-size: var(--fs-23); letter-spacing: -.01em; text-transform: none; line-height: 1.15; color: #fff }
.is-board .is-board-head .is-board-sub { font-size: var(--fs-13); letter-spacing: 0; text-transform: none; color: var(--mast-ink) }
.is-board table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm) }
.is-board th, .is-board td { padding: 10px var(--space-3); border-bottom: 1px solid var(--board-line); text-align: left }
.is-board th:first-child, .is-board td:first-child { padding-left: var(--space-5) }
.is-board th:last-child, .is-board td:last-child { padding-right: var(--space-5) }
.is-board th { font-size: var(--fs-13); text-transform: none; letter-spacing: 0; color: var(--mast-ink); font-weight: 600 }
.is-board td.num, .is-board th.num { text-align: right; font-variant-numeric: tabular-nums }
.is-board tbody tr:last-child td { border-bottom: 0 }
.is-board b { color: #fff }
.is-board .is-status { color: var(--mast-ink) }
.is-board-foot { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; padding: var(--space-4) var(--space-5); border-top: 1px solid var(--board-line) }

/* ── numbers as type ────────────────────────────────────────────────────────
   A row of figures in the display face. This is the component the site was
   missing most: a page whose whole claim is "here is the number" had no way to
   print a number larger than its body text. */
.is-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: var(--space-4); margin: var(--space-4) 0 }
.is-stat b {
  display: block; font-weight: 600;
  font-size: var(--fs-34); line-height: 1.05; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--text);
}
.is-stat.key b { color: var(--text) }
.is-stat span { display: block; margin-top: 5px; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.is-board .is-stats { padding: var(--space-4) var(--space-5) 0; margin: 0 }
.is-board .is-stat b { color: #fff }
.is-board .is-stat.key b { color: var(--mint) }
.is-board .is-stat span { color: var(--mast-ink) }

/* A cap / budget meter. Two elements, no library. */
.is-meter { height: 6px; border-radius: 2px; background: var(--elev); overflow: hidden }
.is-meter i { display: block; height: 100%; background: var(--teal); border-radius: 2px }
.is-meter.tight i { background: var(--danger) }
.is-board .is-meter { background: rgba(255, 255, 255, .12) }
.is-board .is-meter i { background: var(--mint) }

/* ── the action deck ────────────────────────────────────────────────────────
   The thing an in-season page owes a reader in the first screen: the decisions
   that are open right now, each one already carrying its answer, each one a
   link into the tool that finishes it. It is deliberately NOT .is-card -- a
   navigation tile and a decision are different objects and looked identical
   before, which is most of why the pages read as undifferentiated. */
.is-deck { display: grid; grid-template-columns: repeat(auto-fit, minmax(238px, 1fr)); gap: var(--space-4); margin: var(--space-5) 0 }
.is-act {
  display: flex; flex-direction: column; gap: 6px; position: relative; overflow: hidden;
  background: var(--bg); border: 1px solid var(--line2); border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
  color: var(--sec); text-decoration: none;
}
/* The 4px colored bar down the left edge is gone, here and on the front page's
   deck: the kicker carries the color instead (teal, or the gold ink for the
   one-time action, muted for the flat one), which is how every other card on
   the site says what kind of thing it is. */
.is-act:hover { border-color: var(--line2); background: var(--panel); text-decoration: none }
.is-act:hover .is-act-go { text-decoration: underline }
.is-act-k { font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.is-act.gold .is-act-k { color: var(--muted) }
.is-act.flat .is-act-k { color: var(--muted) }
.is-act-h { font-size: var(--fs-23); line-height: 1.2; font-weight: 600; color: var(--text); letter-spacing: -.01em }
.is-act-h em { font-style: normal; color: var(--teal) }
.is-act p { margin: 0; font-size: var(--fs-16); color: var(--sec); line-height: 1.55 }
.is-act-go { margin-top: auto; padding-top: var(--space-2); font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--teal) }

/* ── the story card ─────────────────────────────────────────────────────────
   Every piece the desk publishes, on every surface that lists them (the front
   page's newsroom, the Fantasy page's, the desk index): one card, read at a
   glance. The player faces the search script puts at the head of a headline
   (.it-story-focus) sit on their own row above it like a thumbnail; the
   headline is one size, the teaser is clamped to two lines, and the byline
   row carries the analyst and the time. It replaces a grid of full deks in
   four columns that read as a wall of text. */
.nr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-5); margin: var(--space-2) 0 var(--space-6) }
.nr-card {
  display: flex; flex-direction: column; gap: 10px; min-width: 0;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: var(--space-5) 22px; color: inherit; text-decoration: none;
}
.nr-card:hover { border-color: var(--line2); box-shadow: 0 1px 2px rgba(17, 20, 24, .06); text-decoration: none }
.nr-card:hover h3 { color: var(--teal) }
.nr-kicker { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.nr-nfl-logo { display: block; width: 18px; height: 18px; object-fit: contain; flex: 0 0 auto }
.nr-card h3 { margin: 0; font-size: var(--fs-19); line-height: 1.3; font-weight: 600; color: var(--text); letter-spacing: -.005em; overflow-wrap: anywhere }
.nr-card h3 a { color: inherit }
.nr-card h3 a:hover { text-decoration: none }
.nr-card h3 .it-story-focus { display: flex; margin: 0 0 10px; vertical-align: baseline }
.nr-card p { margin: 0; color: var(--sec); font-size: var(--fs-md); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden }
.nr-by { display: flex; align-items: center; gap: 8px; font-size: var(--fs-xs); color: var(--sec); margin-top: auto; padding-top: 4px; min-width: 0 }
.nr-by a { color: inherit; font-weight: 700 }
.nr-av { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%; background: var(--elev); color: var(--text); font-weight: 600; font-size: var(--fs-12) }
.nr-time { margin-left: auto; color: var(--muted); white-space: nowrap; flex: 0 0 auto }
.nr-riv, .nr-st { flex: 0 0 auto; font-size: var(--fs-12); font-weight: 600; letter-spacing: 0; text-transform: none; border-radius: 2px; padding: 1px 6px; border: 0; background: var(--elev); color: var(--sec) }
.nr-st.ok { color: var(--text); background: var(--elev) }
.nr-st.held { color: var(--goldink); background: var(--gold) }
.nr-card.pending h3 { color: var(--sec); font-weight: 600 }

/* ── the lead story ─────────────────────────────────────────────────────────
   THE BUG THIS EXISTS FOR. The Fantasy page printed the desk's newest six
   pieces as six identical cards, so the piece published four minutes ago and
   the one published two days ago were the same size, the same weight and the
   same two clamped lines. "Newest first" was the only thing on the page saying
   which was which, and a reader scanning a grid does not read the order, they
   read the size. A newsroom front is a lead and a column beside it: the newest
   piece takes the lead card at the width its picture and its whole deck
   deserve, and the rest run as headlines down the right, spread over the
   lead's height so the last rule closes level with it. The spread is CSS, not
   a measured height, so it survives any headline length. Under 900px the
   column drops under the lead and the whole well is one reading order.

   Used by /fantasy. The card grid above is still what /dfs and the desk index
   run, because a section that lists a whole week has no lead to give. */
.nr-well { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--space-5); align-items: stretch; margin: var(--space-2) 0 var(--space-6) }
.nr-well-solo { grid-template-columns: minmax(0, 1fr) }
/* The lead sits at its own height, and the column stretches to the row. When
   the lead is the taller of the two the rail spreads its five items over that
   height and the last rule closes level with the card, which is the alignment
   this well is for. When the column is taller — six short headlines beside a
   story with a two-line deck — the lead simply ends where its copy ends
   rather than holding 170px of white above a stranded byline. */
.nr-lead {
  align-self: start;
  display: flex; flex-direction: column; gap: 12px; min-width: 0;
  background: var(--bg); border: 1px solid var(--line2); border-radius: var(--radius-md);
  padding: var(--space-6) 26px; color: inherit;
}
.nr-lead:hover { border-color: var(--line2); box-shadow: 0 1px 2px rgba(17, 20, 24, .06) }
.nr-lead h3 { margin: 0; font-size: var(--fs-34); line-height: 1.15; font-weight: 600; letter-spacing: -.02em; color: var(--text); overflow-wrap: anywhere }
.nr-lead h3 a { color: inherit }
.nr-lead h3 a:hover { color: var(--teal); text-decoration: none }
/* The faces are the lead's picture, so they take their own row above the
   headline at the size a lead earns, not the card's inline thumbnail. */
.nr-lead h3 .it-story-focus { display: flex; margin: 0 0 14px }
.nr-lead h3 .it-story-focus .it-player-face { width: 72px; height: 72px; min-width: 72px; border-radius: 12px }
/* The lead's deck runs whole. Two clamped lines are what make six cards scan;
   a lead clamped to two lines is only a bigger card. */
.nr-lead p { margin: 0; color: var(--sec); font-size: var(--fs-19); line-height: 1.5 }
.nr-lead-go { font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--teal) }
.nr-lead .nr-by { margin-top: 0; padding-top: var(--space-4); border-top: 1px solid var(--line) }
.nr-flag { flex: 0 0 auto; font-size: var(--fs-12); font-weight: 600; letter-spacing: 0; text-transform: none; border-radius: 2px; padding: 1px 6px; color: var(--goldink); background: var(--gold); border: 0 }

.nr-rail { display: flex; flex-direction: column; min-width: 0 }
.nr-rail-h { margin: 0 0 var(--space-3); font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.nr-rail ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; justify-content: space-between; gap: var(--space-3); flex: 1 1 auto }
.nr-rail li { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding-bottom: var(--space-3); border-bottom: 1px solid var(--line) }
.nr-rail li:last-child { padding-bottom: 0; border-bottom: 0 }
.nr-rail li a { display: flex; align-items: center; color: var(--text); font-weight: 600; font-size: var(--fs-md); line-height: 1.4; overflow-wrap: anywhere }
.nr-rail li a:hover { color: var(--teal); text-decoration: none }
.nr-rail .it-story-focus { flex: 0 0 auto; margin: 0 10px 0 0 }
.nr-rail .it-story-focus .it-player-face { width: 30px; height: 30px; min-width: 30px; border-radius: 6px }

/* ── the boast ──────────────────────────────────────────────────────────────
   Ken, 2026-09-21: "'You're welcome.' should be more prominent. We want to
   boast about our successes."

   The Monday scorecard is the one piece the desk is allowed to crow in. The
   worker offers its writer the opening "YOU'RE WELCOME:" only when a single
   call clears the headline bar, only for that player, and never more than
   once (_voiceBlock, _worker.js) -- so the phrase is already rationed, and
   printing it as the first two words of a 30px headline spent the whole of
   that rationing on punctuation. It is the banner over the lead now, in the
   display face, and the call itself -- which is the part that is actually the
   boast -- runs under it at headline size.

   The site never writes the phrase itself. The well splits it off only when
   the desk's own headline opens with it AND a headline is left underneath;
   anything else paints as an ordinary lead. */
.nr-lead.is-boast { border-color: var(--line2); background: var(--panel) }
/* Specific enough to beat `.nr-lead p`, which sets the dek's 18px: the banner
   and the deck are both paragraphs inside the lead card. */
.nr-lead p.nr-boast {
  margin: 0 0 -4px; font-weight: 700;
  font-size: var(--fs-44); line-height: 1.05; letter-spacing: -.02em;
  text-transform: none; color: var(--text);
}
.nr-lead.is-boast h3 { font-size: var(--fs-23); font-weight: 600; letter-spacing: -.01em }
.nr-lead.is-boast .nr-by { border-top-color: var(--line2) }

@media (max-width: 900px) { .nr-well { grid-template-columns: minmax(0, 1fr) } }
/* The lead's picture is sized against the headline beside it, not the
   viewport: at phone width 72px faces are a fifth of the screen. */
@media (max-width: 620px) { .nr-lead h3 .it-story-focus .it-player-face { width: 56px; height: 56px; min-width: 56px; border-radius: 10px } }

/* Cards get a hairline lift on hover so a grid of them reads as a set of
   things you can open rather than a set of boxes. */
.is-card:hover { border-color: var(--line2); box-shadow: 0 1px 2px rgba(17, 20, 24, .06) }
/* A featured card is the white card with its tag on it; the 3px teal bar it
   used to wear down the left edge was the same device as the deck's, and it
   went with the deck's. */
.is-card.feat { background: var(--bg) }

/* ── the empty state ────────────────────────────────────────────────────────
   One object for "this feed has nothing yet": a mark, a reason line, a detail
   line and the one place the reader can go instead. A section that quietly
   printed a sentence under its ruled heading read as a section with nothing
   in it, which is a different claim from "nothing has been published yet".
   `.in-plate` drops the border when it sits inside an .is-plate that has one. */
.is-vacant {
  display: flex; align-items: center; gap: var(--space-4); margin: 0;
  padding: var(--space-5) var(--space-4); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius-md);
}
.is-vacant.in-plate { border: 0; border-radius: 0 }
.is-vacant[hidden] { display: none }
.is-vacant-ic { display: none }
.is-vacant-msg { flex: 1; min-width: 0 }
.is-vacant b { display: block; font-size: var(--fs-md); color: var(--text); line-height: 1.4 }
.is-vacant span { display: block; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; margin-top: 2px }
.is-vacant span:empty { display: none }
.is-vacant .is-btn { flex: 0 0 auto }
@media (max-width: 560px) { .is-vacant { flex-wrap: wrap } .is-vacant .is-btn { flex: 1 1 100% } }

/* ══ THE THREE-COLUMN FRONT ════════════════════════════════════════════════
   Ken, 2026-09-21: take the hero box off /fantasy, put a rail down the left,
   run the news two or three at a time, and put the newest player updates down
   the right one player at a time. /dfs wears the same front from 2026-09-22.

   IT LIVES HERE AND NOT IN THE PAGES because the first version of it was
   inlined in fantasy.html, and a second page wearing a second copy is exactly
   how the chrome drifted the last time — the comment over .is-hero a hundred
   lines up says so about the band this front replaced.

   WHAT EACH PAGE WEARS. /fantasy runs the full three columns: rail, front,
   player rail, with `.sl-main.sl-narrow` on the middle so the shared objects
   that assume 1,200px (.is-band, .is-split) fold into a 640px column. /dfs
   runs the rail and one wide column, because its answer is a salary-cap board
   that is allowed out past 1,200px on a wide screen and would be ruined by a
   second rail taking 300px off it; its player rail sits beside the stories at
   the top of the column instead (.sl-front).

   No new colors: every value here is a token declared at the top of this file.
   ══════════════════════════════════════════════════════════════════════════ */
/* ── the nameplate ─────────────────────────────────────────────────────────
   Ken, 2026-09-21: "remove the current hero — the whole box." It was the dark
   .is-hero band carrying "Your league, re-scored every week", a 52px headline,
   a full-measure lede, two buttons and a how-it-works link: about 300px of
   page before anything a reader came for.

   What replaces it is a NAMEPLATE rather than a box — the lane's name, the
   sentence that says what the lane is for, and the NFL clock as its dateline,
   on one tinted strip closed with a rule. The page under it is a three-column
   front, and a front has to start above the fold to read as one.

   The ribbon went with the hero: this page's whole-page control is now the
   rail down the left, and two bars of navigation for one page is one too
   many. The scroll-padding the ribbon used to earn is restated here, because
   `html:has(.is-ribbon)` in site.css no longer matches this page. */
html:has(.sl-shell){scroll-padding-top:calc(var(--header-h) + var(--space-4))}
@media(max-width:860px){html:has(.sl-shell){scroll-padding-top:68px}}
/* THE SAME NUMBER AS .sl-shell, and it has to be: the nameplate sits directly
   over the shell and a reader sees one left edge or two. #331 widened the
   shell to 1,440px and left this at 1,340px, which set the plate 50px inside
   the rail beneath it on a 1,500px screen. When one moves, both move. */
.sl-in{max-width:1440px;margin:0 auto;padding:0 var(--space-5)}
.sl-mast{background:var(--panel);border-top:3px solid var(--mast);border-bottom:1px solid var(--line)}
.sl-mast .sl-in{padding-top:var(--space-5);padding-bottom:var(--space-4)}
.sl-date{display:flex;flex-wrap:wrap;align-items:center;gap:4px var(--space-3);margin:0 0 var(--space-3)}
/* No hairline after the label: the NFL clock it divided the label from came
   off on 2026-10-10 (Ken: remove the NFL clock). */
.sl-date .is-eyebrow{color:var(--muted)}
/* max-width:none is not decoration. Several pages set their own
   `h1{max-width:22ch}` for a headline that used to sit on a reading column;
   that rule is (0,0,1) and this one is (0,1,1), but a rule only overrides what
   it names — so /dfs's nameplate broke to four lines inside a 600px measure on
   a 1,440px screen until this line existed. A nameplate is the width of its
   plate. */
.sl-mast h1{font-weight:600;text-transform:none;max-width:none;
  font-size:var(--fs-44);line-height:1.1;letter-spacing:-.02em;margin:0;color:var(--text);text-wrap:balance}
/* The clause the page is FOR is the only colored words on the plate, the same
   device the homepage headline uses. */
.sl-mast h1 em{font-style:normal;color:var(--text)}
.sl-deck{margin:var(--space-3) 0 0;max-width:82ch;font-size:var(--fs-md);line-height:1.6}
.sl-mast .is-actions{margin:var(--space-4) 0 0}
@media(max-width:560px){.sl-mast h1{font-size:var(--fs-34)}}

/* ── the shell ──────────────────────────────────────────────────────────────
   Rail, front, rail. The left rail is where you are going, the middle is what
   the desk published, the right is the newest thing known about one player.
   Both rails stick under the masthead; the middle is the only column that
   scrolls past them.

   It collapses in two steps, and the grid AREAS do the reordering so the
   player rail comes up next to the navigation rather than stranding itself
   1,200px below the fold on a phone. A page that runs only two columns
   (/dfs) overrides grid-template-areas and leaves the rest alone. */
.sl-shell{max-width:1440px;margin:0 auto;padding:var(--space-5) var(--space-5) var(--space-7);
  display:grid;grid-template-columns:224px minmax(0,1fr) 312px;gap:var(--space-5) var(--rail-gap);
  grid-template-areas:"nav main upd";align-items:start}
/* Both rails carry a shade so each column reads as chrome rather than as a
   third body of text sharing the page's white (#331). */
.sl-left{grid-area:nav;min-width:0;position:sticky;top:calc(var(--header-h) + var(--space-4));
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-md);
  padding:var(--space-4) var(--space-4) var(--space-5)}
.sl-left .sl-nav li{border-bottom-color:var(--line2)}
.sl-left .sl-nav-more{border-top-color:var(--line2)}
.sl-main{grid-area:main;min-width:0}
.sl-right,.sl-updrail{min-width:0;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-md);
  padding:var(--space-4)}
.sl-right{grid-area:upd;position:sticky;top:calc(var(--header-h) + var(--space-4))}
@media(max-width:1180px){
  .sl-shell{grid-template-columns:200px minmax(0,1fr);grid-template-areas:"nav upd" "nav main"}
  .sl-right{position:static}
}
/* A shade is a column's edge. With one column there is no edge to draw, so
   both rails drop it rather than becoming two grey boxes stacked on white. */
@media(max-width:860px){
  .sl-shell{grid-template-columns:minmax(0,1fr);grid-template-areas:"nav" "upd" "main";padding:var(--space-4) var(--space-4) var(--space-6)}
  .sl-left{position:static;background:none;border:0;border-radius:0;padding:0}
  .sl-right,.sl-updrail{background:none;border:0;border-radius:0;padding:0}
}

/* ── the left rail ──────────────────────────────────────────────────────────
   Five destinations, each with the one line that says what it answers. Two of
   them leave the page and are marked with an arrow, because a rail that sends
   you somewhere else without saying so is a trapdoor. Under them, quieter, the
   rest of this page's own chapters — the ribbon's old job. */
.sl-nav{border-top:1px solid var(--line2);padding-top:var(--space-3)}
.sl-nav-h{margin:0 0 var(--space-2);font-size:var(--fs-13);
  font-weight:400;letter-spacing:0;text-transform:none;color:var(--muted)}
.sl-nav ul{list-style:none;margin:0;padding:0}
.sl-nav li{border-bottom:1px solid var(--line)}
.sl-nav li:last-child{border-bottom:0}
.sl-nav a{display:block;padding:10px 0;color:var(--text)}
.sl-nav a:hover{text-decoration:none}
.sl-nav a:hover b{color:var(--teal)}
.sl-nav b{display:block;font-size:var(--fs-16);font-weight:600;line-height:1.25}
/* A no-break space, not a plain one: at the rail's width the arrow was
   breaking onto a line of its own under "Season Long Rankings". */
.sl-nav .sl-out b::after{content:"\00a0\2197";font-weight:400;color:var(--muted)}
.sl-nav span{display:block;margin-top:2px;font-size:var(--fs-2xs);color:var(--muted);line-height:1.45}
.sl-nav-more{margin-top:var(--space-4);border-top:1px solid var(--line);padding-top:var(--space-3)}
.sl-nav-more p{margin:0 0 var(--space-2);font-size:var(--fs-13);
  font-weight:400;letter-spacing:0;text-transform:none;color:var(--muted)}
.sl-nav-more a{display:block;padding:4px 0;font-size:var(--fs-14);font-weight:600;color:var(--sec)}
.sl-nav-more a:hover{color:var(--teal);text-decoration:none}
/* On a phone the rail is a sideways-scrolling row of chips, for the same
   reason the site ribbon is: a wrapping rail of this many items comes out
   three deep and takes a third of the screen from the thing it navigates. */
@media(max-width:860px){
  .sl-nav{border-top:0;padding-top:0}
  .sl-nav ul{display:flex;gap:var(--space-2);overflow-x:auto;scrollbar-width:none}
  .sl-nav ul::-webkit-scrollbar{display:none}
  .sl-nav li{flex:0 0 auto;border-bottom:0}
  .sl-nav a{padding:8px 13px;border:1px solid var(--line2);border-radius:var(--radius-sm);background:var(--bg);white-space:nowrap}
  .sl-nav span{display:none}
  .sl-nav-more{display:flex;gap:var(--space-4);overflow-x:auto;scrollbar-width:none;align-items:baseline}
  .sl-nav-more::-webkit-scrollbar{display:none}
  .sl-nav-more p{margin:0;flex:0 0 auto}
  .sl-nav-more a{white-space:nowrap}
}

/* ── the stories ────────────────────────────────────────────────────────────
   Ken, 2026-09-21: "two or three at a time." The well that used to run here
   was a lead plus a five-headline column, which is a front page for a 1,200px
   column and not for a 640px one. Three cards, the newest at lead weight. */
.sl-stories{display:flex;flex-direction:column;gap:var(--space-4);margin:0 0 var(--space-5)}
.sl-story{display:flex;flex-direction:column;gap:10px;min-width:0;background:var(--bg);
  border:1px solid var(--line2);border-radius:var(--radius-md);padding:var(--space-5)}
.sl-story:hover{border-color:var(--line2);box-shadow:0 1px 2px rgba(17,20,24,.06)}
.sl-story h3{margin:0;font-size:var(--fs-19);line-height:1.3;font-weight:600;letter-spacing:-.01em;color:var(--text);overflow-wrap:anywhere}
.sl-story h3 a{color:inherit}
.sl-story h3 a:hover{color:var(--teal);text-decoration:none}
.sl-story p{margin:0;color:var(--sec);font-size:var(--fs-md);line-height:1.55}
.sl-story .nr-by{margin-top:auto;padding-top:var(--space-3);border-top:1px solid var(--line)}
.sl-story h3 .it-story-focus{display:flex;margin:0 0 10px}
.sl-story h3 .it-story-focus .it-player-face{width:44px;height:44px;min-width:44px;border-radius:8px}
.sl-story.lead{padding:var(--space-6) 26px}
.sl-story.lead h3{font-size:var(--fs-28);line-height:1.15;font-weight:600;letter-spacing:-.015em}
.sl-story.lead p{font-size:var(--fs-19)}
.sl-story.lead h3 .it-story-focus .it-player-face{width:60px;height:60px;min-width:60px;border-radius:10px}
.sl-story-go{font-size:var(--fs-14);font-weight:600;letter-spacing:0;text-transform:none;color:var(--teal)}
/* The boast. The desk may open a headline with "YOU'RE WELCOME:" only when one
   of its own pre-kickoff calls cleared the bar (_voiceBlock, _worker.js), so
   the phrase is already rationed; printing it as the first two words of the
   headline spent that rationing on punctuation. Same treatment as .nr-lead. */
.sl-story.is-boast{border-color:var(--line2);background:var(--panel)}
.sl-story p.sl-boast{margin:0 0 -6px;font-weight:700;
  font-size:var(--fs-34);line-height:1.05;letter-spacing:-.02em;text-transform:none;color:var(--text)}
.sl-story.is-boast h3{font-size:var(--fs-23);font-weight:600;letter-spacing:-.01em}
.sl-story.is-boast .nr-by{border-top-color:var(--line2)}

/* ── AI Edge ────────────────────────────────────────────────────────────────
   The signal engine's own week, written out. Every sentence is built from a
   detected insight and the numbers that insight carries; the page cannot say
   anything /api/signals did not find, which is the same contract buildTake
   works under in the worker. */
.sl-edge{background:var(--bg);border:1px solid var(--line2);border-radius:var(--radius-md);padding:var(--space-5) 26px}
.sl-edge-k{display:block;margin:0 0 var(--space-2);font-size:var(--fs-13);
  font-weight:400;letter-spacing:0;text-transform:none;color:var(--muted)}
.sl-edge h3{margin:0 0 var(--space-3);font-size:var(--fs-23);line-height:1.25;font-weight:600;letter-spacing:-.01em;color:var(--text)}
.sl-edge p{margin:0 0 var(--space-4);font-size:var(--fs-md);line-height:1.65;color:var(--sec)}
.sl-edge p:last-child{margin-bottom:0}
.sl-edge b{color:var(--text)}
.sl-edge .sl-edge-note{font-size:var(--fs-sm);color:var(--muted);border-top:1px solid var(--line);padding-top:var(--space-3)}

/* ── the player rail ────────────────────────────────────────────────────────
   One player at a time, newest first: what changed about him, the numbers it
   changed by, and the card that holds the rest. It rotates on a timer that
   stops the moment a reader touches it, and it never rotates at all for a
   reader who has asked for reduced motion. */
.sl-upd{background:var(--bg);border:1px solid var(--line2);border-radius:var(--radius-md);overflow:hidden}
.sl-upd-h{display:flex;align-items:center;gap:var(--space-2);padding:9px var(--space-4);
  border-bottom:1px solid var(--line);background:var(--panel)}
.sl-upd-h b{font-size:var(--fs-13);font-weight:600;letter-spacing:0;text-transform:none;color:var(--text)}
.sl-upd-n{margin-left:auto;font-size:var(--fs-2xs);color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
.sl-upd-body{padding:var(--space-4)}
.sl-upd-k{font-size:var(--fs-13);font-weight:400;letter-spacing:0;text-transform:none;color:var(--muted)}
.sl-upd-name{margin:6px 0 1px;font-size:var(--fs-19);font-weight:600;line-height:1.2;letter-spacing:-.01em}
.sl-upd-name a{color:var(--text)}
.sl-upd-name a:hover{color:var(--teal);text-decoration:none}
.sl-upd-meta{font-size:var(--fs-13);font-weight:400;letter-spacing:0;text-transform:none;color:var(--muted)}
.sl-upd-body p{margin:var(--space-3) 0 0;font-size:var(--fs-sm);line-height:1.6;color:var(--sec)}
.sl-upd-body p b{color:var(--text)}
.sl-upd-foot{display:flex;align-items:center;gap:6px;padding:8px var(--space-4);border-top:1px solid var(--line)}
.sl-upd-foot button{appearance:none;background:var(--bg);border:1px solid var(--line2);border-radius:var(--radius-sm);min-height:36px;
  color:var(--sec);font:inherit;font-size:var(--fs-2xs);font-weight:700;padding:4px 9px;cursor:pointer;line-height:1.4}
.sl-upd-foot button:hover{border-color:var(--teal);color:var(--text)}
.sl-upd-dots{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap;justify-content:flex-end}
.sl-upd-dots i{display:block;width:5px;height:5px;border-radius:50%;background:var(--line2)}
.sl-upd-dots i.on{background:var(--teal)}
.sl-upd-note{margin:var(--space-3) 0 0;font-size:var(--fs-2xs);color:var(--muted);line-height:1.5}

/* ── what the column does to the shared objects ─────────────────────────────
   Three of them assume they are 1,200px wide because until now every page
   that used them was. Inside a 640px column the band has to stop being
   full-bleed and the split has to stop being a split. */
.sl-main .is-band{margin:var(--space-6) 0;padding:var(--space-5);border:0;border-top:3px solid var(--mast);border-radius:var(--radius-md)}
.sl-main .is-band > .wrap{max-width:none;padding:0}
.sl-main .is-band > .wrap > .is-sec:first-child{margin-top:0}
.sl-main.sl-narrow .is-split{grid-template-columns:minmax(0,1fr)}
.sl-main.sl-narrow .is-rail{position:static}
.sl-main > .is-sec:first-child{margin-top:0}

/* ── the front, for a page whose column is wide ─────────────────────────────
   /dfs runs one rail, so the player rail cannot be a column of its own down
   the page: below the stories the boards need the whole width. It sits beside
   them instead, in a grid of its own at the top of the column, and everything
   under it gets the full measure. */
.sl-front{display:grid;grid-template-columns:minmax(0,1fr) 312px;gap:var(--rail-gap);align-items:start;margin-bottom:var(--space-5)}
.sl-front .sl-stories{margin-bottom:0}
@media(max-width:980px){.sl-front{grid-template-columns:minmax(0,1fr);gap:var(--space-5)}}

/* ── a two-column page body ────────────────────────────────────────────────
   Main and a rail. Collapses to one column early, because a 300px rail beside
   a table is worse than a rail under it. */
.is-split { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--rail-gap); align-items: start }
@media (max-width: 980px) { .is-split { grid-template-columns: minmax(0, 1fr) } }
.is-rail { display: flex; flex-direction: column; gap: var(--space-4); position: sticky; top: var(--space-4) }
@media (max-width: 980px) { .is-rail { position: static } }

/* ══ THE SCOREBOARD DRESS ═════════════════════════════════════════════════
   Worn by adding `sl-dress` to <body>. /fantasy and /dfs wear it; nothing
   else does, and nothing below matches a page that has not asked for it.

   WHY IT EXISTS. Ken, 2026-09-21, of /fantasy: it "looks very AI generated."
   It did, and /dfs did worse. The reason was uniformity. Both pages ran the
   same three beats down their whole length — a 40px Bebas heading in caps, a
   paragraph explaining the heading, a white rounded card — at one weight and
   one density, and the boards inside those cards ran 100px rows for eleven
   columns of numbers. Pages whose whole job is ranking priced players were
   reading like brochures about pages that rank priced players.

   THE FOUR MOVES. Every rule below is one of them:

     1 · A NAMEPLATE, NOT A TINTED STRIP. The plate goes dark on --mast and
         the inks declared for it, the NFL clock becomes its dateline, and
         past 1,000px the four children of .sl-in read as a masthead rather
         than as a column with an empty right half beside it.
     2 · THREE TYPE WEIGHTS, NOT ONE. Bebas stays on the nameplate and comes
         off everything else. Section heads drop to a heavy 19px label with
         the number as a chip; the standfirst under each drops to a caption.
     3 · DENSITY. 42px rows, zebra, a rank gutter, a hover row, headers that
         do not wrap and one line per player. Three times the rows per screen.
     4 · STRUCTURE. The stories become a lead with two under it; both rails
         lose the grey ground and take a dark header bar instead.

   WHY IT IS HERE RATHER THAN IN THE TWO PAGES. It was written inline in
   fantasy.html first and lifted the day /dfs asked for the same thing. The
   comment over .sl-shell a few hundred lines up says why: the shell is in
   this file BECAUSE two pages wear it, and a second page wearing a second
   copy is how the chrome drifted the last time. When a value here changes it
   changes on both pages, which is the point.

   WHAT IS NOT HERE. Anything only one page owns: /fantasy's plates, its
   segmented controls, its deck, its AI Edge card, its rivalry columns and
   its tool shelf are in fantasy.html; /dfs's rail buttons, its dark lineup
   board and its slate setup are in dfs.html. The test is whether the other
   page has the markup at all.

   No new colors: every value below is a token declared at the top of this
   file, plus the two geometry tokens on body.sl-dress itself.
   ══════════════════════════════════════════════════════════════════════════ */
body.sl-dress{
  /* One step off --bg and lighter than --panel: a standings stripe has to be
     visible on a laptop screen at an angle and invisible in a screenshot. */
  --sl-stripe:#f5f8f9;
  /* Squarer than --radius-md. A results table with 12px corners reads as a
     card about a table; 5px reads as the table. */
  --sl-r:4px;
}

/* ── 1 · the nameplate ──────────────────────────────────────────────────── */
body.sl-dress .sl-mast{background:var(--mast);border-top:0;border-bottom:0}
body.sl-dress .sl-mast .sl-in{padding-top:var(--space-4);padding-bottom:var(--space-5)}
/* 40px, and a measure. The old clamp topped out at 52px with max-width:none,
   so on a 1,440px screen the headline ran the full plate and the deck under
   it started 120px down the page. */
body.sl-dress .sl-mast h1{font-size:var(--fs-44);line-height:1.08;letter-spacing:-.02em;
  max-width:24ch;color:var(--mast-ink-hi)}
body.sl-dress .sl-mast h1 em{color:#fff}
body.sl-dress .sl-deck{color:var(--mast-ink);font-size:var(--fs-19);line-height:1.55;max-width:74ch;
  margin-top:var(--space-2)}
body.sl-dress .sl-date{margin-bottom:var(--space-3);gap:4px var(--space-3)}
body.sl-dress .sl-date .is-eyebrow{font-size:var(--fs-13);letter-spacing:0;color:var(--mast-ink);
  border-right-color:var(--mast-line)}
/* The clock is the dateline on the band, not the grey plate it is on white.
   Same treatment .is-hero gives it in site.css, restated because that
   selector does not match this page. */
body.sl-dress .sl-mast .its-strip{background:transparent;border:0;border-radius:0;padding:0;margin:0;
  color:var(--mast-ink);font-size:var(--fs-xs);gap:6px var(--space-3)}
body.sl-dress .sl-mast .its-strip .its-phase{color:var(--mast-ink)}
body.sl-dress .sl-mast .its-strip .its-week,
body.sl-dress .sl-mast .its-strip .its-state{color:#fff}
body.sl-dress .sl-mast .its-strip .its-counts,
body.sl-dress .sl-mast .its-strip .its-next,
body.sl-dress .sl-mast .its-strip .its-note{color:var(--mast-dim)}
body.sl-dress .sl-mast .its-strip .its-next{margin-left:0}
body.sl-dress .sl-mast .is-actions{margin-top:var(--space-4);gap:var(--space-2)}
body.sl-dress .sl-mast .is-hint{color:var(--mast-dim)}
body.sl-dress .sl-mast .is-hint b{color:#fff}
/* A pill on a masthead reads as a chat bubble. Buttons square up, and the
   ghost one has to be re-inked because --sec on --line2 is not on this
   ground at all. */
body.sl-dress .is-actions .is-btn{border-radius:var(--radius-sm);font-size:var(--fs-16);font-weight:600;letter-spacing:0}
body.sl-dress .sl-mast .is-btn.primary{background:var(--mast-btn);border-color:var(--mast-btn)}
body.sl-dress .sl-mast .is-btn.ghost{color:#fff;border-color:rgba(255,255,255,.6)}
body.sl-dress .sl-mast .is-btn.ghost:hover{border-color:#fff;color:#fff;background:rgba(255,255,255,.08)}
/* Stacked, the plate is the headline over a 74ch lede over two buttons, and
   past 1,000px that is 300px of band with an empty right half beside it. The
   four children are the same four children; the wide screen just reads them
   as a masthead — clock across the top, name on the left, what the page does
   and the one action on the right — instead of as a column. */
@media(min-width:1000px){
  body.sl-dress .sl-mast .sl-in{display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);
    column-gap:var(--space-7);align-items:start}
  body.sl-dress .sl-mast .sl-date{grid-column:1/-1;grid-row:1}
  body.sl-dress .sl-mast h1{grid-column:1;grid-row:2/4;max-width:none;align-self:center}
  body.sl-dress .sl-deck{grid-column:2;grid-row:2;margin:0}
  body.sl-dress .sl-mast .is-actions{grid-column:2;grid-row:3;margin:var(--space-4) 0 0}
  /* A plate with no action row — /dfs has none — leaves row 3 empty and the
     deck stranded at the top of a column the headline fills. With nothing
     under it, the deck takes both rows and centres against the headline. */
  body.sl-dress .sl-mast .sl-in:not(:has(.is-actions)) .sl-deck{grid-row:2/4;align-self:center}
}
@media(max-width:560px){body.sl-dress .sl-mast h1{font-size:var(--fs-34);max-width:none}}

/* ── 2 · section heads ──────────────────────────────────────────────────── */
/* A section head is a label. It was a 28px display line with a 56px teal nub
   under a 2px black rule, which is a headline, and the page carried nine of
   them plus a nameplate. The rule thins, the number becomes a chip, the title
   becomes a label and the teal moves into the chip. */
body.sl-dress .is-sec{margin:var(--space-7) 0 var(--space-4);padding-bottom:var(--space-2);
  border-bottom:1px solid var(--line)}
body.sl-dress .is-sec-top{align-items:center;gap:var(--space-3)}
body.sl-dress .is-sec h2{font-size:var(--fs-23);font-weight:600;line-height:1.2;
  letter-spacing:-.01em;text-transform:none;margin:0}
body.sl-dress .is-sec .is-sec-n{display:none}
body.sl-dress .is-sec .is-sec-more{margin-left:auto;font-size:var(--fs-14);font-weight:600;
  letter-spacing:0;text-transform:none;white-space:nowrap}
/* The standfirst under each head is context, not a second lede. */
body.sl-dress .is-sec p{margin-top:var(--space-2);font-size:var(--fs-14);line-height:1.5;
  color:var(--muted);max-width:72ch}
body.sl-dress .is-sec p a{font-weight:700}

/* The density pass. 42px rows, one line each, and headers that do not wrap:
   "Iron Tuna" and "Market delta" were both breaking to two lines, which set
   the header band 60px tall before a single row was drawn. */
body.sl-dress .is-table{font-size:15px}
body.sl-dress .is-table th{padding:8px;font-size:var(--fs-13);font-weight:600;letter-spacing:0;
  white-space:nowrap;color:var(--muted);background:var(--bg);border-bottom:1px solid var(--line2)}
body.sl-dress .is-table td{padding:0 8px;height:42px;white-space:nowrap;vertical-align:middle;
  border-bottom:1px solid var(--line)}
body.sl-dress .is-table tbody tr:nth-child(even)>td{background:var(--sl-stripe)}
body.sl-dress .is-table tbody tr:hover>td{background:var(--panel)}
body.sl-dress .is-table .is-rank{width:34px;padding-right:0;text-align:center;font-size:var(--fs-13);
  color:var(--muted)}
/* A name column that wraps is what made the rows 110px tall. Nowrap is the
   whole fix and it needs no max-width: capping the cell clipped the position
   tag behind it, because .rk-share is opacity:0 rather than display:none and
   still takes its 46px of the line. Twelve rows of names cannot run the
   column out on their own, and .is-scroll is there for the day one does. */
body.sl-dress .is-table .is-who{font-size:15px;font-weight:600}
body.sl-dress .is-table .rk-share{margin-left:5px;padding:0 4px;border-radius:3px;
  font-size:var(--fs-3xs);line-height:1.5}
body.sl-dress .is-table .is-who small{margin-left:5px;font-size:var(--fs-13);font-weight:400;
  letter-spacing:0;text-transform:none;color:var(--muted)}
/* /player-search.js injects the face at 34px with white-space:normal on the
   cell. Both are right for a story and wrong for a standings row. */
body.sl-dress .is-table .it-player-face{width:26px;height:26px;min-width:26px;border-radius:4px;
  margin-right:8px}
body.sl-dress .is-table td.it-player-cell{white-space:nowrap}
/* The one number the row exists for, at the weight of a score rather than the
   size of a headline: 24px Bebas beside 15px Inter was two type systems in
   one row. */
body.sl-dress .is-val{font-size:15px;font-weight:600;letter-spacing:0}
body.sl-dress .is-table .is-tag{margin:0;padding:2px 6px;font-size:var(--fs-12);letter-spacing:0;
  white-space:nowrap;border-radius:2px}
body.sl-dress .is-vacant.in-plate{padding:var(--space-4)}

/* ── 4 · the newsroom ───────────────────────────────────────────────────── */
/* A lead across the column with two under it. Three boxes of equal weight in
   a vertical stack is a feed; a front has a lead. */
body.sl-dress .sl-stories{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);
  margin-bottom:var(--space-5)}
body.sl-dress .sl-story.lead{grid-column:1/-1}
@media(max-width:720px){body.sl-dress .sl-stories{grid-template-columns:1fr}}
body.sl-dress .sl-story{border-radius:var(--sl-r);border-color:var(--line2);padding:var(--space-4);gap:8px}
body.sl-dress .sl-story h3{font-size:var(--fs-19);line-height:1.3}
body.sl-dress .sl-story p{font-size:var(--fs-sm);line-height:1.45;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
body.sl-dress .sl-story.lead{padding:var(--space-5)}
body.sl-dress .sl-story.lead h3{font-size:var(--fs-28);line-height:1.15}
body.sl-dress .sl-story.lead p{font-size:var(--fs-md);-webkit-line-clamp:2}
body.sl-dress .nr-kicker{font-size:var(--fs-13);letter-spacing:0}

/* ── 6 · the two calls, the clock and the AI Edge ───────────────────────── */
body.sl-dress .is-card{border-radius:var(--sl-r);border-color:var(--line2);
  padding:var(--space-4) var(--space-5)}
body.sl-dress .is-card h3{font-size:var(--fs-19)}
body.sl-dress .is-card p{font-size:var(--fs-sm);line-height:1.55}
body.sl-dress .is-tag{font-size:var(--fs-12);letter-spacing:0;padding:2px 7px;border-radius:2px}
body.sl-dress .is-note{font-size:var(--fs-2xs);line-height:1.55;margin-top:var(--space-3)}
body.sl-dress .is-btns{margin-top:var(--space-4)}
body.sl-dress .is-btns .is-btn{border-radius:var(--radius-sm);font-size:var(--fs-14);
  font-weight:600;letter-spacing:0;text-transform:none}
body.sl-dress .is-stats{gap:var(--space-3);margin:var(--space-3) 0}
body.sl-dress .is-vacant{border-radius:var(--sl-r);border-color:var(--line2)}
body.sl-dress .is-vacant b{font-size:var(--fs-sm)}
body.sl-dress .is-vacant span{font-size:var(--fs-2xs)}
body.sl-dress .is-vacant .is-btn{border-radius:var(--radius-sm);font-size:var(--fs-14);
  font-weight:600;letter-spacing:0;text-transform:none}
/* Both rails lose the grey ground and take a dark header bar instead. A rail
   is chrome; a tinted box of prose beside a white column is a third article. */
body.sl-dress .sl-left,body.sl-dress .sl-right,body.sl-dress .sl-updrail{background:var(--bg);border-color:var(--line2);
  border-radius:var(--sl-r);padding:0;overflow:hidden}
body.sl-dress .sl-nav{border-top:0;padding-top:0}
body.sl-dress .sl-nav-h{margin:0;padding:9px var(--space-4);background:var(--mast);
  font-size:var(--fs-13);font-weight:600;letter-spacing:0;
  color:var(--mast-ink-hi)}
body.sl-dress .sl-nav ul{padding:0 var(--space-4)}
body.sl-dress .sl-nav a{padding:9px 0}
body.sl-dress .sl-nav b{font-size:var(--fs-14);font-weight:600}
body.sl-dress .sl-nav span{font-size:var(--fs-13);line-height:1.35;margin-top:1px}
body.sl-dress .sl-nav-more{margin-top:0;padding:var(--space-3) var(--space-4) var(--space-4);
  background:var(--panel);border-top-color:var(--line2)}
body.sl-dress .sl-nav-more p{font-size:var(--fs-13);letter-spacing:0}
body.sl-dress .sl-nav-more a{font-size:var(--fs-2xs);padding:3px 0}
body.sl-dress .sl-upd{border:0;border-radius:0}
body.sl-dress .sl-upd-h{background:var(--mast);border-bottom:0;padding:9px var(--space-4)}
body.sl-dress .sl-upd-h b{font-size:var(--fs-13);font-weight:600;
  letter-spacing:0;color:var(--mast-ink-hi)}
body.sl-dress .sl-upd-n{color:var(--mast-dim)}
body.sl-dress .sl-upd-name{font-size:var(--fs-19)}
body.sl-dress .sl-upd-k{font-size:var(--fs-13);letter-spacing:0}
body.sl-dress .sl-upd-body p{font-size:var(--fs-2xs);line-height:1.55}
body.sl-dress .sl-right>.is-vacant,body.sl-dress .sl-updrail>.is-vacant{border:0;border-radius:0}
body.sl-dress .sl-upd-note{padding:0 var(--space-4) var(--space-4);margin-top:var(--space-3)}
/* With one column there is no rail edge to draw, so the header bars come off
   with the borders the shell already drops at 860px. */
@media(max-width:860px){
  body.sl-dress .sl-left,body.sl-dress .sl-right,body.sl-dress .sl-updrail{background:none;border:0;border-radius:0;overflow:visible}
  body.sl-dress .sl-nav-h{background:none;color:var(--muted);padding:0 0 var(--space-2)}
  body.sl-dress .sl-nav ul,body.sl-dress .sl-nav-more{padding-left:0;padding-right:0}
  body.sl-dress .sl-nav-more{background:none}
  body.sl-dress .sl-upd{border:1px solid var(--line2);border-radius:var(--sl-r)}
  body.sl-dress .sl-upd-note{padding:0}
}

/* ══ THE PAPER PASS (2026-10-10) ═══════════════════════════════════════════
   The homepage moved onto paper on 2026-10-08 (docs/design/brief.md): a white
   ground, one centred headline over one sentence, the entry points as text
   links, hairline rules for grouping, outlined 10px cards, and nothing tinted
   or boxed that is not a form, a ledger or the one sticky panel. /fantasy and
   /dfs still wore the September scoreboard dress above it: a navy nameplate
   with buttons on it, navy header bars on both rails, grey bands with white
   cards inside them, coloured top rules on the deck, chips for labels, 800
   weights and 4px corners. Two doors off the front page read as a different
   site from the page that links to them.

   Every rule here restates one of those objects in the homepage's materials
   and nothing else: no markup moves, no id or control changes, and the
   dress's density pass (42px rows, names that do not wrap) is kept, because
   a table is still a table. It comes AFTER the dress at the same specificity
   so it wins without !important; a page's own inline <style> still outranks
   it, which is why each page carries a matching block for the objects only
   it has the markup for. */
body.sl-dress{--sl-r:var(--radius-md)}

/* ── 1 · the nameplate, as the homepage hero ─────────────────────────────
   White and centred: the lane's label, the headline at the hero scale one
   step down, one sentence, the actions as the homepage's entry links, and a
   hairline to close it. The ≥1000px masthead grid comes off with the band. */
body.sl-dress .sl-mast{background:var(--bg);border:0;border-bottom:1px solid var(--line)}
body.sl-dress .sl-mast .sl-in{padding-top:var(--space-7);padding-bottom:var(--space-7);text-align:center}
@media(min-width:1000px){
  body.sl-dress .sl-mast .sl-in{display:block}
  body.sl-dress .sl-mast .sl-in:not(:has(.is-actions)) .sl-deck{margin:var(--space-4) auto 0}
}
body.sl-dress .sl-date{justify-content:center;margin:0 0 var(--space-4)}
body.sl-dress .sl-date .is-eyebrow{color:var(--muted)}
body.sl-dress .sl-mast h1{margin:0 auto;max-width:20ch;font-size:clamp(34px,4.2vw,56px);font-weight:700;
  line-height:1.05;letter-spacing:-.03em;color:var(--text);text-wrap:balance}
/* No coloured accent words: the sentence stands in roman, in ink. */
body.sl-dress .sl-mast h1 em{color:var(--text)}
body.sl-dress .sl-deck{margin:var(--space-4) auto 0;max-width:56ch;font-size:var(--fs-19);line-height:1.5;color:var(--sec)}
/* The actions are the homepage's entry links: 16px, 600, teal, an arrow. The
   markup keeps its button classes and ids so the script that hides "Save my
   league" for a reader who has saved one still finds it. */
body.sl-dress .sl-mast .is-actions{justify-content:center;gap:var(--space-2) var(--space-6);margin:var(--space-5) 0 0}
body.sl-dress .sl-mast .is-actions .is-btn{min-height:0;padding:0;border:0;border-radius:0;background:none;
  color:var(--teal);font-size:var(--fs-16);font-weight:600;line-height:1.5}
body.sl-dress .sl-mast .is-actions .is-btn::after{content:"\00a0\2192"}
body.sl-dress .sl-mast .is-actions .is-btn:hover{background:none;color:var(--teal-hover);text-decoration:underline}
body.sl-dress .sl-mast .is-hint{flex:1 1 100%;margin:var(--space-2) 0 0;color:var(--muted);font-size:var(--fs-14)}
body.sl-dress .sl-mast .is-hint b{color:var(--text)}
@media(max-width:560px){
  body.sl-dress .sl-mast .sl-in{padding-top:var(--space-6);padding-bottom:var(--space-6)}
  body.sl-dress .sl-mast h1{font-size:var(--fs-34)}
}

/* ── 2 · the rails ───────────────────────────────────────────────────────
   No box and no navy bar: a 13px label, the list, hairlines between rows.
   The player rail keeps an outline because it is the one sticky panel the
   system allows one; its header is a hairline, not a band. */
body.sl-dress .sl-left{background:none;border:0;border-radius:0;padding:0;overflow:visible}
body.sl-dress .sl-nav{border-top:1px solid var(--line);padding-top:var(--space-3)}
body.sl-dress .sl-nav-h{padding:0 0 var(--space-2);background:none;color:var(--muted);font-weight:400}
body.sl-dress .sl-nav ul{padding:0}
body.sl-dress .sl-left .sl-nav li{border-bottom-color:var(--line)}
body.sl-dress .sl-nav b{font-size:var(--fs-16)}
body.sl-dress .sl-nav-more{margin-top:var(--space-4);padding:var(--space-3) 0 0;background:none;border-top:1px solid var(--line)}
body.sl-dress .sl-nav-more p{color:var(--muted);font-weight:400}
body.sl-dress .sl-nav-more a{font-size:var(--fs-14)}
body.sl-dress .sl-right,body.sl-dress .sl-updrail{background:none;border:0;border-radius:0;padding:0;overflow:visible}
body.sl-dress .sl-upd{border:1px solid var(--line);border-radius:var(--radius-md)}
body.sl-dress .sl-upd-h{background:none;border-bottom:1px solid var(--line);padding:var(--space-3) var(--space-4)}
body.sl-dress .sl-upd-h b{color:var(--text)}
body.sl-dress .sl-upd-n{color:var(--muted)}
body.sl-dress .sl-right>.is-vacant,body.sl-dress .sl-updrail>.is-vacant{border:1px solid var(--line);border-radius:var(--radius-md)}
body.sl-dress .sl-upd-note{padding:0}

/* ── 3 · section heads ───────────────────────────────────────────────────
   The homepage's: the title at 28px and the link out at 14px on one
   baseline, the standfirst at 16px under them, and air above the head
   instead of a rule under it. */
body.sl-dress .is-sec{margin:var(--space-8) 0 var(--space-4);padding-bottom:0;border-bottom:0}
body.sl-dress .is-sec-top{align-items:baseline;gap:var(--space-4)}
body.sl-dress .is-sec h2{font-size:var(--fs-28);line-height:1.15;letter-spacing:-.01em}
body.sl-dress .is-sec p{margin-top:var(--space-2);font-size:var(--fs-16);line-height:1.5;color:var(--sec);max-width:60ch}
body.sl-dress .is-sec p a{font-weight:600}
@media(max-width:860px){body.sl-dress .is-sec{margin-top:var(--space-7)}}

/* ── 4 · no tinted bands ─────────────────────────────────────────────────
   The homepage groups with whitespace and hairlines. A grey band with white
   cards inside it is a box in a box, and it was the first thing on both
   pages after the stories. */
body.sl-dress .sl-main .is-band{margin:0;padding:0;background:none;border:0;border-radius:0}
/* The head that opened a band had no gap above it because the band's own
   padding was the gap; without the band it needs the section's. */
body.sl-dress .sl-main .is-band>.wrap>.is-sec:first-child{margin-top:var(--space-8)}
@media(max-width:860px){body.sl-dress .sl-main .is-band>.wrap>.is-sec:first-child{margin-top:var(--space-7)}}

/* ── 5 · cards and labels ────────────────────────────────────────────────
   Every card is the homepage's outlined card: a hairline, 10px corners,
   white, 24px of padding, a 19px heading over 16px text. No coloured top
   rule, no tint and no lift; hover darkens the hairline and nothing else.
   The kicker on a card is a 13px muted label, the way the homepage labels a
   figure, not a chip. (The amber disclosure chip inside a table row keeps
   its own rule, which outranks this one.) */
body.sl-dress .is-card,body.sl-dress .is-act,body.sl-dress .sl-story,body.sl-dress .sl-edge,body.sl-dress .is-vacant{
  border:1px solid var(--line);border-radius:var(--radius-md);background:var(--bg);box-shadow:none}
body.sl-dress .is-card,body.sl-dress .is-act,body.sl-dress .sl-edge,body.sl-dress .sl-story,body.sl-dress .sl-story.lead{padding:var(--space-5)}
body.sl-dress .is-card:hover,body.sl-dress .is-act:hover,body.sl-dress .sl-story:hover{border-color:var(--line2);background:var(--bg);box-shadow:none}
body.sl-dress .is-act.gold,body.sl-dress .is-act.flat{border-color:var(--line)}
body.sl-dress .is-act-k,body.sl-dress .sl-edge-k{font-size:var(--fs-13);font-weight:400;color:var(--muted)}
body.sl-dress .is-act-h{font-size:var(--fs-19);line-height:1.3}
body.sl-dress .is-act-h em{color:inherit}
body.sl-dress .is-act p,body.sl-dress .is-card p,body.sl-dress .sl-edge p{font-size:var(--fs-16);line-height:1.5;color:var(--sec)}
body.sl-dress .is-act-go{font-size:var(--fs-14)}
body.sl-dress .sl-story.is-boast{background:var(--bg);border-color:var(--line)}
body.sl-dress .is-tag{background:none;padding:0;margin-bottom:var(--space-1);border-radius:0;
  font-size:var(--fs-13);font-weight:400;color:var(--muted)}
body.sl-dress .is-note{font-size:var(--fs-14)}
body.sl-dress .is-btns .is-btn,body.sl-dress .is-vacant .is-btn{font-size:var(--fs-16)}

/* ══ THE RANKINGS SECTION ══════════════════════════════════════════════════
   The ribbon's own block is GENERATED by tools/build-ranks.mjs and lives
   between the two sentinels below — the identical block is injected into
   front.html's inline <style>, which links no shared sheet. Everything after
   the closing sentinel is hand-written and belongs to this file alone. */
/* ranks:css */
/* ── the section ribbon (generated by tools/build-ranks.mjs) ─────────────────
   Under the header on every page that carries the sentinel. The front page is
   not one of them: it shows the choose-your-game band in this slot. Five
   destinations; the Rankings menu drops every position down on hover and on
   keyboard focus. Do not hand-edit — run the tool. */
.rk-ribbon {
  --rkr-ink: var(--ink, var(--text, #111418));
  --rkr-mut: var(--ink-2, var(--sec, #454c57));
  --rkr-line: var(--line, #e2e4e8);
  --rkr-brand: var(--brand, #0b4f6c);
  --rkr-card: var(--card, #ffffff);
  --rkr-panel: var(--panel, var(--brand-tint, #eef2f7));
  background: var(--rkr-card); border-bottom: 1px solid var(--rkr-line);
  border-top: 1px solid var(--rkr-line); position: relative;
  /* Above a page's sticky header (z-index 40), or a dropped menu that reaches
     down past the content is painted behind it. Still under the player-search
     menu, which is fixed and parented to <body>. */
  z-index: 45;
}
/* NO OVERFLOW ON THIS ROW. A menu cannot hang out of a scroll container:
   overflow-x:auto with overflow-y:visible computes to overflow-y:auto — the
   spec is explicit about it — so the dropdown would be clipped at the band's
   46px and turned into a scrollbar. The homepage's own sticky ribbon learned
   this the hard way with the player-lookup menu it carried. The band wraps
   instead of scrolling here; on a phone, where the menus are off anyway, the
   media query below turns the sideways scroll back on. */
.rk-ribbon-in {
  max-width: 1240px; margin: 0 auto; padding: 0 32px;
  display: flex; align-items: stretch; flex-wrap: wrap; gap: 2px;
}
.rk-ribbon .rkr-item { position: relative; display: flex; align-items: center; flex: 0 0 auto }
.rk-ribbon .rkr-link {
  display: flex; align-items: center; white-space: nowrap; text-decoration: none;
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--rkr-mut); padding: 0 12px; border-bottom: 3px solid transparent; min-height: 48px;
}
.rk-ribbon .rkr-link:hover, .rk-ribbon .rkr-link:focus-visible {
  color: var(--rkr-ink); border-bottom-color: var(--rkr-brand); text-decoration: none;
}
.rk-ribbon .rkr-link[aria-current="page"] { color: var(--rkr-ink); border-bottom-color: var(--rkr-brand) }
.rk-ribbon .rkr-has-menu > .rkr-link::after { content: "\25BE"; margin-left: 6px; font-size: .85em; opacity: .7 }
.rk-ribbon .rkr-menu {
  display: flex; flex-direction: column; visibility: hidden; opacity: 0;
  transition: opacity .12s ease, visibility 0s linear .3s;
  position: absolute; top: 100%; left: 0; z-index: 60; min-width: 232px;
  background: var(--rkr-card); border: 1px solid var(--rkr-line); border-top: 2px solid var(--rkr-brand);
  border-radius: 0 0 4px 4px; padding: 6px;
}
/* Bridges the 0px gap between the trigger and the menu so the pointer can cross
   it without the menu closing under it. */
.rk-ribbon .rkr-menu::before { content: ""; position: absolute; top: -8px; left: 0; right: 0; height: 10px }
.rk-ribbon .rkr-has-menu:hover .rkr-menu,
.rk-ribbon .rkr-has-menu:focus-within .rkr-menu { visibility: visible; opacity: 1; transition-delay: 0s }
.rk-ribbon .rkr-menu a {
  display: block; padding: 10px 12px; border-radius: 4px; white-space: nowrap; text-decoration: none;
  font-size: 16px; font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--rkr-ink);
}
.rk-ribbon .rkr-menu a:hover, .rk-ribbon .rkr-menu a:focus-visible {
  background: var(--rkr-panel); color: var(--rkr-ink); text-decoration: none;
}
.rk-ribbon .rkr-menu a[aria-current="page"] { color: var(--rkr-brand); font-weight: 600 }
/* A hover menu is unreachable on touch, so below the desktop breakpoint the
   trigger is simply a link to /rankings, whose position tiles sit under its
   hero. Nothing is lost; the menu was a shortcut. */
@media (max-width: 860px) {
  .rk-ribbon .rkr-menu { display: none }
  /* And with the menu gone, so is the caret: an arrow that opens nothing is a
     promise the band cannot keep. The trigger is a plain link to the hub. */
  .rk-ribbon .rkr-has-menu > .rkr-link::after { content: none }
  .rk-ribbon .rkr-link { padding: 0 10px; font-size: 13px; min-height: 44px }
  .rk-ribbon-in {
    padding: 0 16px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  }
  .rk-ribbon-in::-webkit-scrollbar { display: none }
}
/* /ranks:css */

/* ── the sixteen rankings pages: the head above the board ─────────────────
   Eyebrow, title, lede, week strip, chips, controls, board. Seven things stack
   before the first row, and until 7 Oct 2026 the h1 and h2 were the browser's
   own: 2em and 1.5em with the body's 1.6 line-height and 0.67em margins, so a
   two-word title sat in a 58px box with 24px above and below it, and the first
   row started 600px down a desktop screen. Headings get a heading's leading
   here, and every gap between the eyebrow and the board steps down one notch.

   Scoped with :has() to a <main> that carries the board directly, which is
   these sixteen pages and nothing else: /rankings has .rk-table, and the other
   in-season tools keep the shared .is-* measure. A browser without :has()
   keeps the looser spacing and loses nothing. */
main:has(> .rk-board) > .is-eyebrow { margin: var(--space-5) 0 var(--space-1) }
main:has(> .rk-board) > h1 { margin: 0 0 var(--space-2); line-height: 1.15; letter-spacing: -.01em }
main:has(> .rk-board) > .is-lede { font-size: var(--fs-md); margin: 0 0 var(--space-4); max-width: 68ch }
main:has(> .rk-board) > .its-strip { margin: 0 0 var(--space-4) }
main:has(> .rk-board) > .rk-chips { margin: 0 0 var(--space-4) }
main:has(> .rk-board) > h2 { font-size: var(--fs-lg); line-height: 1.25; margin: var(--space-6) 0 var(--space-2) }
main:has(> .rk-board) > h2 + .is-note, main:has(> .rk-board) > h2 + .is-grid { margin-top: 0 }
main:has(> .rk-board) > .is-note { max-width: 76ch }
main:has(> .rk-board) > .is-grid { margin-bottom: var(--space-7) }

/* ── the position chips ───────────────────────────────────────────────────
   Every position in the category, on every page in it. This is the whole
   navigation on touch, where the ribbon's hover menus do not exist, and the
   fastest way between two positions on a desktop. */
.rk-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-5) }
.rk-chips .rkc-chip {
  display: inline-flex; align-items: center; min-height: 36px; padding: 0 13px;
  border: 1px solid var(--line2); border-radius: var(--radius-sm); background: var(--bg);
  font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--sec); text-decoration: none;
}
.rk-chips .rkc-chip:hover { background: var(--panel); color: var(--text); text-decoration: none }
.rk-chips .rkc-chip.on { background: var(--text); border-color: var(--text); color: #fff }

/* ── the two-column board ─────────────────────────────────────────────────
   FANTASY CONSENSUS and BETTING ODDS are the point of the page, so they are
   grouped under one spanning header rather than left as two columns among
   eleven. The gap between them gets the color; nothing else on the row does. */
.rk-board { margin: 0 0 var(--space-6) }
.rk-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin: 0 0 var(--space-3) }
/* The stamp line between the controls and the table is a caption for the
   table, so it sits on the table rather than floating between the two. */
.rk-board > p.is-note { margin: 0 0 var(--space-2) }
.rk-seg2 { display: inline-flex; border: 1px solid var(--line2); border-radius: var(--radius-sm); overflow: hidden }
.rk-seg2 button {
  appearance: none; border: 0; background: var(--bg); cursor: pointer; min-height: 36px; padding: 0 12px;
  font: inherit; font-size: var(--fs-14); font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--sec);
}
.rk-seg2 button + button { border-left: 1px solid var(--line2) }
.rk-seg2 button:hover { background: var(--panel); color: var(--text) }
.rk-seg2 button[aria-pressed="true"] { background: var(--text); color: #fff }
.rk-tools .rk-find { flex: 1 1 200px; min-width: 160px }
.rk-tools .rk-find input {
  width: 100%; min-height: 36px; padding: 0 12px; font: inherit; font-size: var(--fs-sm);
  border: 1px solid var(--line2); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);
}

.rk-vs th.rk-grp { text-align: center; border-bottom: 1px solid var(--line2) }
.rk-vs th.rk-grp.fan { color: var(--text) }
.rk-vs th.rk-grp.mkt { color: var(--text) }
/* A group name is a sort too (it-ranks.js headHtml). The rule under it is
   what marks it active; the arrow belongs to the Rank column beneath, which
   sorts on the same key, so one arrow per sort and not two stacked. */
.rk-vs th.rk-grp[aria-sort] { border-bottom: 2px solid var(--text) }
.rk-vs thead th.rk-grp[aria-sort]::after { content: none }
.rk-vs td.rk-fan, .rk-vs th.rk-fan { background: transparent }
.rk-vs td.rk-mkt, .rk-vs th.rk-mkt { background: var(--panel) }
.rk-vs td.rk-pts { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text) }
.rk-vs td.rk-rnk { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); font-size: var(--fs-xs) }
.rk-vs td.rk-gap { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap }
.rk-vs td.rk-gap b { display: block; font-size: var(--fs-sm) }
.rk-vs td.rk-gap span { display: block; font-size: var(--fs-13); font-weight: 600; letter-spacing: 0; text-transform: none }
.rk-vs td.rk-gap.up b { color: var(--green) }
.rk-vs td.rk-gap.up span { color: var(--text) }
.rk-vs td.rk-gap.down b, .rk-vs td.rk-gap.down span { color: var(--text) }
.rk-vs td.rk-gap.flat b, .rk-vs td.rk-gap.flat span { color: var(--muted) }
/* Not scoped to .rk-vs: the stats board uses the same cell, and a position that
   runs on after the name ("Alpha BackRB") is the bug this exists to stop. */
.is-table td.rk-who b { color: var(--text) }
.is-table td.rk-who small { display: block; color: var(--muted); font-size: var(--fs-2xs) }

/* ── the two read-lines under a name ───────────────────────────────────────
   One sentence on the player, one on the opportunity in front of him. Both
   boards that print them are listed: `.rk-vs td.rk-who` is the sixteen
   published rankings pages (it-ranks.js) and `.rk-table td.p` is the /rankings
   tool. The stats board also shares td.rk-who and is deliberately NOT here: it
   prints what was played, which has no opportunity to grade.

   The cell takes a floor width so the sentences wrap to a readable measure
   instead of stretching an eleven-column table, and a ceiling so they do not
   run the width of a desk monitor.

   THE SPECIFICITY IS LOAD-BEARING on /rankings. Its own sheet carries
   `.rk-table td.p span { display:block; font-size:13.5px; color:var(--muted) }`
   for the position under the name, inline and therefore later than this file,
   so every rule below has to out-specify it — three classes, not two — and the
   key has to name its own `display`, which on the other sixteen pages it
   inherits from nothing.

   The two keys borrow the board's own two colours, and mean the same thing
   they mean on the column groups above: teal is the fantasy read of the
   player, gold is the market's read of his week. */
.rk-vs td.rk-who, .rk-table td.p { min-width: 15rem }
.rk-vs td.rk-who .rk-read, .rk-table td.p .rk-read {
  display: block; max-width: 34rem; margin-top: 4px;
  font-size: var(--fs-2xs); line-height: 1.4; letter-spacing: normal;
  color: var(--sec); white-space: normal; text-transform: none;
}
.rk-vs td.rk-who .rk-read-k, .rk-table td.p .rk-read-k {
  display: inline; margin-right: 5px; font-size: var(--fs-13); font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--muted);
}
.rk-vs td.rk-who .rk-read-pl .rk-read-k, .rk-table td.p .rk-read-pl .rk-read-k { color: var(--sec) }
.rk-vs td.rk-who .rk-read-op .rk-read-k, .rk-table td.p .rk-read-op .rk-read-k { color: var(--sec) }
.rk-vs .rk-basis { font-size: var(--fs-13); text-transform: none; letter-spacing: 0; color: var(--muted) }

/* ── the row's density ─────────────────────────────────────────────────────
   Until 7 Oct 2026 every column shared the width evenly, which left the
   player cell at its 15rem floor while the gap column held 230px for one
   nowrap verdict, so the two sentences broke into five short lines each and a
   row stood 160px tall on a desktop, 250px on the season board. Four moves,
   all of them width or leading rather than type size:

     1. The player column takes the table's slack (width:100% in an auto
        table layout hands every other column its own content width and the
        rest to this one), so the sentences wrap at a measure instead of a
        sliver. The verdict column is allowed to wrap and given a floor.
     2. Cells align to the TOP: the rank, team, fixture and every number sit on
        the player's name line, and the two sentences hang under the name as
        the row's annotation. Centred, they floated in the middle of a tall
        row and made the whole row read as empty.
     3. Column heads drop one type step and lose their nowrap-driving spacing;
        the header text, not the data, was setting most columns' width.
     4. Cell padding steps down from 12px to 10px vertical, 8px horizontal.

   The pre-render in _worker.js is the same markup, so this covers it too. */
.is-table.rk-vs { font-size: 16px }
.rk-vs th, .rk-vs td { padding: 10px var(--space-2) }
.rk-vs th { font-size: var(--fs-13); letter-spacing: 0; white-space: nowrap; vertical-align: bottom; line-height: 1.25 }
.rk-vs th.rk-grp { white-space: normal; padding-bottom: 6px }
.rk-vs td { vertical-align: top; line-height: 1.5 }
.rk-vs td.rk-who { width: 100%; padding-top: 8px }
.rk-vs td.rk-gap { white-space: normal; min-width: 9.5rem }
.rk-vs td.rk-gap b { white-space: nowrap; line-height: 1.5 }
.rk-vs td.rk-gap span { line-height: 1.3; margin-top: 2px }
/* Points, verdict, slots: three short lines, each its own block, so the verdict
   never breaks mid-phrase and the slots never arrive behind a dangling dot. */
.rk-vs td.rk-gap .rk-slots { margin-top: 1px; font-weight: 700; color: var(--muted) }
.rk-vs td.rk-pts .rk-basis { margin-top: 1px }
/* On the pooled boards the position rides beside the name rather than on a
   line of its own; the margin keeps it from running on ("Alpha BackRB"). */
.is-table.rk-vs td.rk-who small {
  display: inline-block; margin-left: 6px; font-size: var(--fs-13); font-weight: 400;
  letter-spacing: 0; text-transform: none; vertical-align: 0;
}
.rk-vs td.rk-who .rk-read:first-of-type { margin-top: 6px }

/* ── the legibility pass (9 Oct 2026) ─────────────────────────────────────
   The player cell's width:100% took every spare pixel, and the Note column,
   last in line, was left at its 7rem floor: a two-sentence note set one word
   to a line and stood a row 330px tall. Four moves:

     1. The note gets a real measure (.rk-note: a floor and a ceiling) in the
        reads' own smaller, secondary type, so it reads as annotation beside
        the numbers rather than a column of single words.
     2. The verdict is a tag, not a sentence: one nowrap pill per row, tinted
        green only when the market is higher (the one figure green is for),
        so the column scans as a column.
     3. The two groups read as blocks: each spanning head carries a rule in
        its own weight, and the odds pair keeps its panel up through the
        group head, so "Betting Odds" sits on the shaded pair it names.
     4. Rows take a hover tint, and the board's rank (# column) is set at the
        weight of the name, since it is the number the board publishes. */
.rk-vs td.rk-note {
  min-width: 15rem; max-width: 20rem;
  font-size: var(--fs-2xs); line-height: 1.45; color: var(--sec);
}
.rk-vs td.rk-gap span:not(.rk-slots) {
  display: inline-block; margin-top: 3px; padding: 1px 7px; border-radius: 3px;
  background: var(--panel); white-space: nowrap; font-size: var(--fs-2xs);
}
.rk-vs td.rk-gap.up span:not(.rk-slots) { background: color-mix(in srgb, var(--green) 11%, var(--bg)); color: var(--green) }
.rk-vs td.rk-gap.flat span:not(.rk-slots) { background: transparent; border: 1px solid var(--line2) }
.rk-vs td.rk-gap .rk-slots { margin-top: 3px; font-size: var(--fs-2xs) }
.rk-vs th.rk-grp { font-size: var(--fs-13); font-weight: 700; color: var(--text); padding-top: 8px }
.rk-vs th.rk-grp.fan { border-bottom: 2px solid var(--line2) }
.rk-vs th.rk-grp.mkt { border-bottom: 2px solid var(--teal); background: var(--panel) }
.rk-vs tbody > tr:not(.rk-weeks):hover > td { background: var(--teal-tint) }
.rk-vs tbody > tr:not(.rk-weeks):hover > td.rk-mkt { background: color-mix(in srgb, var(--teal) 14%, var(--bg)) }
.rk-vs tbody > tr:not(.rk-weeks) > td:first-child,
.rk-vs tbody > tr:not(.rk-weeks) > td.num:nth-child(2) { font-weight: 700; color: var(--text) }
.rk-vs td.rk-pts .rk-basis { font-weight: 400 }

/* ── the board in the front page's dress (9 Oct 2026) ─────────────────────
   After the legibility pass the board still read small and busy: 13px reads
   under every name stood each row four lines tall, the verdict sat in a grey
   pill, and the odds pair was a slab of panel grey down the page. The front
   page's own grammar is white paper, one hairline card, 19px semibold titles
   and teal for the one thing to click, so the board takes that:

     1. The table sits in one bordered card with the front page's radius.
     2. Type steps up a size throughout: the name is set like a card title,
        the projections at the same size, ranks and team one step down.
     3. The Player and Opportunity lines fold away under the name behind a
        teal "Player & opportunity" toggle (it-ranks.js, readsBlock), and are
        set at body size when opened.
     4. The verdict is words under the number, not a pill; the odds pair keeps
        a faint teal wash rather than panel grey, and the group heads are
        underlined in their own colour.
   The phone card layout below overrides what it needs to and is unchanged. */
.rk-board .is-scroll:has(> table.rk-vs) {
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg);
}
.is-table.rk-vs { font-size: 17px }
.rk-vs th, .rk-vs td { padding: 16px 12px; border-bottom-color: var(--line) }
.rk-vs > tbody > tr:last-child > td { border-bottom: 0 }
.rk-vs > * > tr > :first-child { padding-left: 20px }
.rk-vs > * > tr > :last-child { padding-right: 20px }
.rk-vs > thead th { background: var(--bg); color: var(--muted); font-size: var(--fs-14); font-weight: 600; padding-top: 12px; padding-bottom: 12px }
.rk-vs > thead tr:last-child th { border-bottom: 1px solid var(--line2) }
.rk-vs thead th[data-key] { cursor: pointer }
.rk-vs thead th[data-key]:hover { color: var(--text) }
.rk-vs thead th[aria-sort] { color: var(--text) }
.rk-vs thead th[aria-sort="ascending"]::after { content: " \2191" }
.rk-vs thead th[aria-sort="descending"]::after { content: " \2193" }
.rk-vs th.rk-grp { font-size: var(--fs-16); font-weight: 600; color: var(--text); padding: 18px 12px 8px; background: var(--bg); white-space: nowrap }
.rk-vs th.rk-grp.fan { border-bottom: 2px solid var(--text) }
.rk-vs th.rk-grp.mkt { border-bottom: 2px solid var(--teal); color: var(--teal); background: var(--bg) }
.rk-vs td.rk-mkt, .rk-vs th.rk-mkt { background: color-mix(in srgb, var(--teal) 4%, var(--bg)) }
.rk-vs tbody > tr:not(.rk-weeks):hover > td { background: color-mix(in srgb, var(--panel) 55%, var(--bg)) }
.rk-vs tbody > tr:not(.rk-weeks):hover > td.rk-mkt { background: color-mix(in srgb, var(--teal) 8%, var(--bg)) }

.rk-vs tbody > tr:not(.rk-weeks) > td.rk-rank { font-size: 17px; font-weight: 700; color: var(--text); white-space: nowrap }
/* The name line does not wrap: the hidden Link button (opacity 0, still in
   flow) and a pooled board's position tag were each breaking to a line of
   their own and opening a gap above the toggle. The fold wraps as prose. */
.rk-vs td.rk-who { padding-top: 14px; white-space: nowrap }
.rk-vs td.rk-who .rk-more { white-space: normal }
.rk-vs td.rk-who > a { color: var(--text) }
.rk-vs td.rk-who > a:hover { color: var(--teal); text-decoration: none }
.rk-vs td.rk-who > a b { font-size: var(--fs-19); font-weight: 600; letter-spacing: -.01em }
.is-table.rk-vs td.rk-who small { font-size: var(--fs-14); color: var(--muted) }
.rk-vs td.rk-team { font-size: var(--fs-16); font-weight: 600; color: var(--sec) }
.rk-vs td.rk-opp { font-size: var(--fs-16); color: var(--sec); white-space: nowrap }
.rk-vs td.rk-pts { font-size: var(--fs-19); font-weight: 600 }
.rk-vs td.rk-rnk { font-size: var(--fs-14); font-weight: 500; padding-top: 19px }
.rk-vs .rk-basis { font-size: var(--fs-13); font-weight: 400; color: var(--muted); margin-top: 2px }
.rk-vs td.rk-gap b { font-size: 18px; font-weight: 600 }
.rk-vs td.rk-gap span:not(.rk-slots) {
  display: block; margin-top: 2px; padding: 0; border: 0; border-radius: 0; background: none;
  font-size: var(--fs-14); font-weight: 500; color: var(--sec); white-space: nowrap;
}
.rk-vs td.rk-gap.up span:not(.rk-slots) { background: none; color: var(--green) }
.rk-vs td.rk-gap.flat span:not(.rk-slots) { background: none; border: 0; color: var(--muted) }
.rk-vs td.rk-gap .rk-slots { margin-top: 1px; font-size: var(--fs-13); font-weight: 500; color: var(--muted) }
.rk-vs td.rk-note { font-size: var(--fs-14); line-height: 1.5; color: var(--sec) }

/* The fold. The summary is the front page's teal link with a chevron that
   turns when the lines are open; the lines themselves are body text. The
   /rankings tool folds its lines the same way (.rk-table td.p), and its
   inline `.rk-table td.p span` rule is out-specified here as above. */
.rk-vs .rk-more, .rk-table td.p .rk-more { margin-top: 6px }
.rk-vs .rk-more > summary, .rk-table td.p .rk-more > summary {
  display: inline-flex; align-items: center; gap: 7px; list-style: none; cursor: pointer;
  font-size: var(--fs-14); font-weight: 600; color: var(--teal); user-select: none;
}
.rk-vs .rk-more > summary::-webkit-details-marker, .rk-table td.p .rk-more > summary::-webkit-details-marker { display: none }
.rk-vs .rk-more > summary::after, .rk-table td.p .rk-more > summary::after {
  content: ""; width: 6px; height: 6px; margin-top: -3px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); transition: transform .15s ease;
}
.rk-vs .rk-more[open] > summary::after, .rk-table td.p .rk-more[open] > summary::after { transform: rotate(-135deg); margin-top: 3px }
.rk-vs .rk-more > summary:hover, .rk-table td.p .rk-more > summary:hover { color: var(--teal-hover); text-decoration: underline }
.rk-vs .rk-more > summary:focus-visible, .rk-table td.p .rk-more > summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 2px }
.rk-vs td.rk-who .rk-more .rk-read, .rk-table td.p .rk-more .rk-read {
  max-width: 38rem; margin-top: 8px; font-size: var(--fs-16); line-height: 1.5; color: var(--sec);
}
.rk-vs td.rk-who .rk-more .rk-read-k, .rk-table td.p .rk-more .rk-read-k {
  display: block; margin: 0 0 1px; font-size: var(--fs-13); font-weight: 600; color: var(--muted);
}

/* ── the board as cards, on a phone ────────────────────────────────────────
   At 390px the table showed rank, name, team and half the fixture; both
   projections, both ranks and the gap (the reason the page exists) sat
   off-screen behind a sideways scroll nothing announced. Below 640px each row
   becomes a card, by CSS alone, so the desktop table and the pre-render are
   the same markup:

     line 1   rank, face, name, position, link   (the name cell is
              display:contents, so its children join the row's flex line)
     line 2   team and fixture, under the name
     strip    Consensus | Betting odds | Gap, labelled from CSS
     prose    the Player and Opportunity reads, then the note

   The two forced line breaks are the row's own ::before and ::after, ordered
   into place. The column heads stay as a wrapping row of sort chips, since
   tapping a head is the only way to re-sort the board. Every selector names
   the child path so the week drawer's own table (.rk-wk) is left alone. */
@media (max-width: 640px) {
  table.rk-vs, table.rk-vs > thead, table.rk-vs > tbody { display: block }
  table.rk-vs > thead > tr:first-child { display: none }
  table.rk-vs > thead > tr:last-child {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    padding: 12px 14px; border-bottom: 1px solid var(--line2);
  }
  table.rk-vs > thead > tr:last-child::before {
    content: "Sort"; margin-right: 2px; font-size: var(--fs-13); font-weight: 600; color: var(--muted);
  }
  table.rk-vs > thead > tr > th { display: none }
  table.rk-vs > thead > tr > th[data-key] {
    display: block; padding: 3px 10px; border: 1px solid var(--line2); border-radius: 999px;
    background: var(--bg); color: var(--sec); text-align: left; cursor: pointer;
  }
  table.rk-vs > thead > tr > th.rk-fan::before { content: "Consensus " }
  table.rk-vs > thead > tr > th.rk-mkt::before { content: "Odds " }
  table.rk-vs > thead > tr > th[aria-sort] { background: var(--text); border-color: var(--text); color: #fff }

  table.rk-vs > tbody > tr:not(.rk-weeks) {
    display: flex; flex-wrap: wrap; align-items: center;
    padding: 16px 14px; border-bottom: 1px solid var(--line);
  }
  table.rk-vs > tbody > tr:not(.rk-weeks) > td {
    padding: 0; border: 0; min-width: 0; max-width: none; width: auto;
    background: transparent; line-height: 1.4;
  }
  table.rk-vs > tbody > tr:not(.rk-weeks):hover > td { background: transparent }
  table.rk-vs > tbody > tr:not(.rk-weeks)::before,
  table.rk-vs > tbody > tr:not(.rk-weeks)::after { content: ""; flex: 0 0 100%; height: 0 }

  /* line 1 */
  table.rk-vs > tbody > tr > td.rk-rank { order: 1; flex: 0 0 3.25rem; font-size: var(--fs-16); text-align: left }
  table.rk-vs > tbody > tr > td.rk-who { display: contents }
  table.rk-vs td.rk-who > .it-player-face { order: 2; width: 28px; height: 28px; min-width: 28px; margin: 0 8px 0 0 }
  table.rk-vs td.rk-who > a { order: 3 }
  table.rk-vs td.rk-who > a b { font-size: 18px }
  table.rk-vs td.rk-who > small { order: 4 }
  table.rk-vs td.rk-who > .rk-share { order: 5; margin-left: auto }
  table.rk-vs > tbody > tr > td.rk-opener { order: 6; margin-left: 8px }
  table.rk-vs > tbody > tr > td.rk-opener .rk-open { width: 28px; height: 28px }
  table.rk-vs > tbody > tr:not(.rk-weeks)::before { order: 7 }

  /* line 2: under the name, not under the rank */
  table.rk-vs > tbody > tr > td.rk-team,
  table.rk-vs > tbody > tr > td.rk-opp { order: 8; margin-top: 2px; font-size: var(--fs-14); color: var(--sec); text-align: left }
  table.rk-vs > tbody > tr > td.rk-team { margin-left: 3.25rem }
  table.rk-vs > tbody > tr > td.rk-team::after { content: "\00b7"; margin: 0 6px; color: var(--muted) }
  table.rk-vs > tbody > tr > td.rk-opp.num::before { content: "Games "; color: var(--muted) }
  table.rk-vs > tbody > tr:not(.rk-weeks)::after { order: 9 }

  /* the strip */
  table.rk-vs > tbody > tr > td.rk-fan,
  table.rk-vs > tbody > tr > td.rk-mkt,
  table.rk-vs > tbody > tr > td.rk-gap {
    order: 10; align-self: stretch; margin-top: 12px; padding: 8px 8px 9px;
    border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  }
  table.rk-vs > tbody > tr > td.rk-pts { flex: 0 0 19%; text-align: left; padding-right: 0 }
  table.rk-vs > tbody > tr > td.rk-rnk { flex: 0 0 10%; text-align: left; padding-left: 4px; padding-right: 4px }
  table.rk-vs > tbody > tr > td.rk-mkt { background: color-mix(in srgb, var(--teal) 5%, var(--bg)); border-color: transparent }
  table.rk-vs > tbody > tr > td.rk-pts .rk-basis { font-size: var(--fs-12); white-space: nowrap }
  table.rk-vs > tbody > tr > td.rk-gap { flex: 1 1 0; text-align: right; min-width: 0; padding-left: 4px; padding-right: 6px }
  table.rk-vs > tbody > tr > td.rk-pts::before,
  table.rk-vs > tbody > tr > td.rk-rnk::before,
  table.rk-vs > tbody > tr > td.rk-gap::before {
    display: block; margin-bottom: 3px; font-size: var(--fs-13); font-weight: 600; color: var(--muted);
    content: "\00a0"; white-space: nowrap;
  }
  table.rk-vs > tbody > tr > td.rk-fan.rk-pts::before { content: "Consensus" }
  table.rk-vs > tbody > tr > td.rk-mkt.rk-pts::before { content: "Odds" }
  table.rk-vs > tbody > tr > td.rk-gap::before { content: "Gap" }
  table.rk-vs > tbody > tr > td.rk-pts { font-size: 17px }
  table.rk-vs > tbody > tr > td.rk-rnk { font-size: var(--fs-13); padding-top: 11px }
  table.rk-vs > tbody > tr > td.rk-gap b { display: block; font-size: 17px }
  table.rk-vs > tbody > tr > td.rk-gap span:not(.rk-slots) { display: block; margin: 2px 0 0; padding: 0; white-space: normal }
  table.rk-vs > tbody > tr > td.rk-gap .rk-slots { display: block; margin-top: 3px }

  /* the prose */
  table.rk-vs td.rk-who > .rk-more { order: 11; flex: 0 0 100%; margin-top: 12px }
  table.rk-vs td.rk-who .rk-more .rk-read { max-width: none; font-size: var(--fs-16) }
  table.rk-vs > tbody > tr > td.rk-note {
    order: 12; flex: 0 0 100%; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--line);
  }

  table.rk-vs > tbody > tr > td.rk-note.rk-empty { display: none }

  /* The week drawer: its own six-column table, fitted to the card's width
     rather than scrolled. Heads wrap ("Fantasy / Consensus") and the cell
     padding steps down, which is all it takes to clear 390px. */
  table.rk-vs > tbody > tr.rk-weeks, table.rk-vs > tbody > tr.rk-weeks > td { display: block }
  table.rk-vs > tbody > tr.rk-weeks > td { padding: var(--space-3) var(--space-2) }
  table.rk-vs .rk-wk { font-size: var(--fs-13) }
  table.rk-vs .rk-wk th, table.rk-vs .rk-wk td { padding: 6px 5px; white-space: normal; vertical-align: bottom }
  table.rk-vs .rk-wk td { white-space: nowrap }
  table.rk-vs .rk-wk tfoot td:first-child { white-space: normal }
}

/* ── the week-by-week drawer ──────────────────────────────────────────────
   Season-long only: a rest-of-season total is a sum, and the thing a reader
   actually wants from it is which weeks it came from. The button is a real
   <button> with aria-expanded, and the drawer is a row of the same table so
   the columns keep lining up. */
.rk-open {
  appearance: none; border: 1px solid var(--line2); background: var(--bg); border-radius: var(--radius-sm);
  width: 32px; height: 32px; line-height: 1; cursor: pointer; color: var(--sec); font: inherit; font-size: 14.5px;
}
.rk-open:hover { border-color: var(--teal); color: var(--text) }
.rk-open[aria-expanded="true"] { background: var(--text); border-color: var(--text); color: #fff }
.rk-weeks td { background: var(--panel); padding: var(--space-4) }
.rk-weeks h4 { margin: 0 0 var(--space-2); font-size: var(--fs-13); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--muted) }
.rk-wk { width: 100%; border-collapse: collapse; font-size: var(--fs-xs) }
.rk-wk th, .rk-wk td { padding: 6px 10px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap }
.rk-wk th { color: var(--muted); font-size: var(--fs-13); text-transform: none; letter-spacing: 0; font-weight: 600 }
.rk-wk td.num { text-align: right; font-variant-numeric: tabular-nums }
.rk-wk td.fan { background: transparent; color: var(--text); font-weight: 600 }
.rk-wk td.mkt { background: var(--panel); color: var(--text); font-weight: 600 }
.rk-wk tr.off td { color: var(--muted) }
.rk-wk tfoot td { border-bottom: 0; font-weight: 800; color: var(--text) }

/* ── the game preview cards ───────────────────────────────────────────────── */
.rk-games { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-4) }
.rk-game { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg); padding: var(--space-4) }
.rk-game h3 { margin: 0 0 var(--space-1); font-size: var(--fs-lg); color: var(--text) }
.rk-game .rk-kick { font-size: var(--fs-13); color: var(--muted); text-transform: none; letter-spacing: 0 }
.rk-game dl { display: grid; grid-template-columns: 1fr auto; gap: 4px var(--space-3); margin: var(--space-3) 0 0 }
.rk-game dt { font-size: var(--fs-xs); color: var(--sec) }
.rk-game dd { margin: 0; font-size: var(--fs-xs); font-variant-numeric: tabular-nums; font-weight: 700; color: var(--text); text-align: right }
.rk-game .rk-agree { color: var(--text) }
.rk-game .rk-diverge { color: var(--text) }

/* ══ THE PRODUCT PANEL ══════════════════════════════════════════════════════
   The front page sells three readings inside one picture (front.html, .fp-viz):
   the projection against the market, the FAAB Advisor pricing a claim off every
   rival's remaining budget, and the model against the book on a game line. A
   reader who clicked one of them used to land on a page with no picture at all,
   so the thing they clicked did not survive the click. This is that card, on
   its own page, one per page, directly under the lede.

   HTML AND CSS, NOT ONE INLINE SVG. The front page draws its version as a
   single SVG because the three cards and the mark have to hold one composition;
   here there is only one card, and an SVG's type does not reflow — at 320px the
   front page's own labels fall under 7px, which is why that panel hides itself
   on a phone. Every label below is real HTML at a real font size, so the panel
   works at every width and a screen reader gets the aria-label rather than a
   pile of stray <text> nodes.

   THE NUMBERS ARE ILLUSTRATIVE and each panel says so on its foot line. Nothing
   here fetches: a picture of the product that waits on /api is a picture that
   paints late, and on these three pages the live numbers are already directly
   below it.

   The plate's colours are the front page's hero band, restated: #2dd4a3 is the
   teal that only works on the dark ground, gold is a fill and a figure. */
.it-viz {
  margin: var(--space-5) 0; padding: clamp(16px, 2.4vw, 22px) clamp(16px, 2.4vw, 24px) clamp(14px, 2vw, 18px);
  border-radius: var(--radius-md); border: 0; border-top: 3px solid var(--mast);
  background: var(--panel);
  color: var(--text); overflow: hidden;
}
.it-viz-kicker { margin: 0; font-size: var(--fs-16); font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text) }
.it-viz-sub { margin: 4px 0 0; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
/* The chart takes the width it is given and the readings sit beside it; under
   620px the readings drop below the chart rather than squeezing it to a strip. */
.it-viz-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(150px, auto); gap: clamp(14px, 2.4vw, 22px); align-items: end; margin-top: clamp(14px, 2vw, 18px) }
.it-viz-body.solo { grid-template-columns: minmax(0, 1fr) }
.it-viz-foot { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--line2); font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.it-viz-foot i { font-style: normal; color: var(--goldink); background: var(--gold); padding: 1px 6px; border-radius: 2px; font-size: var(--fs-12) }

/* ── the line chart ───────────────────────────────────────────────────────
   preserveAspectRatio="none" so the shape stretches to whatever box it lands
   in, and non-scaling-stroke so the line stays 2px while it does. That is what
   lets the callout be positioned in per-cent: the x of a point in the viewBox
   is the same per-cent of the box at every width. */
.it-viz-chart { position: relative; height: clamp(104px, 15vw, 138px) }
.it-viz-chart svg { display: block; width: 100%; height: 100% }
.it-viz-grid { stroke: var(--line2); stroke-width: 1; vector-effect: non-scaling-stroke; fill: none }
.it-viz-line { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke }
.it-viz-line.teal { stroke: var(--teal) }
.it-viz-line.gold { stroke: var(--mast) }
/* The point the panel is about: a gold disc with a halo, centred on its own
   coordinates, and the flag beside it. */
.it-viz-dot { position: absolute; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px; border-radius: 50%; background: var(--mast); box-shadow: 0 0 0 3px #fff }
.it-viz-flag { position: absolute; transform: translate(-50%, -100%); margin-top: -12px; white-space: nowrap; font-size: var(--fs-13); font-weight: 600; letter-spacing: 0; color: var(--text) }
.it-viz-axis { display: flex; justify-content: space-between; margin: 8px 0 0; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }

/* ── the readings beside it ─────────────────────────────────────────────── */
.it-viz-tiles { display: grid; gap: 10px }
.it-viz-tile { border-radius: var(--radius-sm); border: 1px solid var(--line2); background: #fff; padding: 9px 12px }
.it-viz-tile.gold { border-color: var(--line2) }
.it-viz-tile span { display: block; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.it-viz-tile b { display: block; margin-top: 2px; font-size: var(--fs-28); font-weight: 600; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--text) }
.it-viz-tile.gold b { color: var(--green) }

/* ── the bars ─────────────────────────────────────────────────────────────
   One column per manager. The bar's height is a per-cent of the middle row,
   which is why the row is a definite height and the value and the name sit in
   rows of their own rather than inside the bar. */
.it-viz-bars, .it-viz-names { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: clamp(8px, 1.8vw, 20px); text-align: center }
.it-viz-bars { border-bottom: 1px solid var(--line2) }
.it-viz-bar { display: grid; grid-template-rows: 16px minmax(0, 1fr); height: clamp(112px, 15vw, 142px) }
.it-viz-bar i { align-self: end; justify-self: center; display: block; width: min(100%, 72px); height: var(--h); border-radius: 2px 2px 0 0; background: var(--teal) }
.it-viz-bar b { font-size: var(--fs-13); font-weight: 600; color: var(--sec); line-height: 16px }
.it-viz-names { margin-top: 7px; font-size: var(--fs-13); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--muted) }
.it-viz-bar.you i { background: var(--mast) }
.it-viz-bar.you b { color: var(--text) }
.it-viz-names span:first-child { color: var(--text) }

@media (max-width: 620px) {
  .it-viz-body { grid-template-columns: minmax(0, 1fr) }
  .it-viz-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

/* ── /players — the directory every player card hangs off ────────────────────
   Rendered at the edge into [data-players-index] (playersIndexHtml, _worker.js).
   Multi-column because it is 409 names and a single column is nine screens of
   scrolling; column-count rather than grid so a section breaks wherever it has
   to instead of being padded to a rectangle. */
.pl-index { margin: var(--space-6) 0 }
.pl-sec { margin: 0 0 var(--space-6); break-inside: avoid }
.pl-sec h2 {
  margin: 0 0 var(--space-3); padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--line); font-size: var(--fs-md);
}
.pl-sec h2 .is-status { margin-left: 6px }
.pl-list { margin: 0; padding: 0; list-style: none; column-width: 15rem; column-gap: var(--space-5) }
.pl-list li { break-inside: avoid; padding: 3px 0; font-size: var(--fs-16) }
.pl-list li span { color: var(--muted); font-size: var(--fs-xs) }

/* ── the board's pre-render, and its deep links ──────────────────────────────
   [data-rk-prerender] is the top of the board served WITH the page (see
   ranksPrerender in _worker.js). /it-ranks.js removes it the moment the live
   board has painted, so nothing here needs a hidden state: it either is the
   board for a few hundred milliseconds, or it is the board for a crawler that
   will never run the script. It is styled as the real one because it is the
   real one's rows. */
[data-rk-prerender] .rk-basis { color: var(--muted); font-size: var(--fs-xs) }

/* The row, or the game card, a shared link names. Both selectors, because a
   link can arrive two ways: ?player= / ?game=, which the script marks, and
   #p-<slug> / #g-<slug>, which the browser marks on its own and which keeps
   working with no script running at all. */
tbody tr[id^="p-"]:target, tbody tr[id^="g-"]:target,
tbody tr.rk-hit { background: var(--teal-tint) }
tbody tr.rk-hit > td:first-child,
tbody tr[id^="p-"]:target > td:first-child,
tbody tr[id^="g-"]:target > td:first-child { box-shadow: inset 3px 0 0 var(--teal) }
.rk-game[id^="g-"]:target, .rk-game.rk-hit,
.ww-game[id^="g-"]:target, .ww-game.rk-hit {
  border-color: var(--teal); box-shadow: 0 0 0 3px var(--teal-tint);
}
/* On a wrap card the control sits in the body rather than in a heading row,
   because the heading there is the recap's own link and must stay one target. */
.ww-body > .gm-share { float: right; margin-left: var(--space-2) }
/* The card's heading carries the copy control, so it is a row rather than a
   line of text. */
.rk-game h3 { display: flex; align-items: center; gap: var(--space-2) }

/* The copy-a-link control, one per row. Quiet until the row is hovered or
   something in it takes focus, so 250 rows do not read as 250 buttons — and
   always shown where there is no hover to reveal it with. */
.rk-share {
  margin-left: 6px; padding: 0 5px; min-height: 24px; border: 1px solid var(--line2); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--muted); font: inherit; font-size: var(--fs-14);
  line-height: 1.6; cursor: pointer; opacity: 0;
}
.rk-who:hover .rk-share, tr:hover .rk-share, .rk-share:focus-visible { opacity: 1 }
.rk-share:hover { border-color: var(--teal); color: var(--text) }
.rk-share[data-copied] { opacity: 1; border-color: var(--teal); color: var(--teal) }
@media (hover: none) { .rk-share { opacity: 1 } }

/* The same control on a game card, where there is one per card rather than one
   per row and it can simply sit in the header. */
.gm-share {
  margin-left: auto; padding: 1px 7px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--panel); color: var(--muted); font: inherit; font-size: var(--fs-xs); cursor: pointer;
}
.gm-share:hover { border-color: var(--teal); color: var(--text) }
.gm-share[data-copied] { border-color: var(--teal); color: var(--teal) }

/* ══ THE LANE FRONT (2026-10-11) ═══════════════════════════════════════════
   Worn by adding `ln-front` to <body>, beside `sl-dress`. /fantasy and /dfs
   wear it, and /trade-finder (no rail, no `sl-dress`: the hero, the section
   heads, the cards and the tile dress) since the same day; nothing else does.

   WHY IT EXISTS. Ken, 2026-10-11, of /fantasy: it still "looks very AI".
   The homepage (front.html) had moved onto paper on 2026-10-08 and the two
   lane pages were still a tinted nameplate with an eyebrow and two buttons
   over nine labelled sections of outlined cards at one weight, each with a
   kicker, a heading, a paragraph and an arrow. This block is the homepage's
   composition restated for a page that keeps a rail down the left: a white
   centred hero (one headline, one sentence, the search field, text-link
   entry points), section heads at 28px with the one link on the right,
   illustrated tiles for the places a reader goes, outlined cards only where
   a live sentence has to sit, empty states as a sentence, the desk as the
   homepage's story cards, and whitespace and hairlines doing the grouping.

   WHY IT IS HERE. The first copy was inline in fantasy.html; /dfs asked for
   the same thing the next day, and a second page wearing a second copy is
   how the chrome drifted the last time (see the shell's own comment above).
   front.html keeps its sheet inline because it is not a lane page and has
   no rail; the values below are its, under its names. When one moves, both
   move.

   The tokens are declared on the body, not on :root: a content page may not
   carry a palette of its own (tools/test-reading-view.mjs), and these are
   spacing and type tokens under front.html's names, not colours this file
   does not already set. */
body.ln-front{
  --ink:#111418;--ink-2:#454c57;--ink-3:#5f6672;--card:#ffffff;--good:#0a6a4c;
  --fs-hero:clamp(44px,5.2vw,68px);
  --sp-4:4px;--sp-8:8px;--sp-12:12px;--sp-16:16px;--sp-24:24px;--sp-32:32px;--sp-48:48px;--sp-64:64px;
  --sec-gap:72px;
}
@media(max-width:860px){body.ln-front{--sec-gap:48px}}
body.ln-front .hm-sec{margin:var(--sec-gap) 0 0}
body.ln-front .sl-main>.hm-sec:first-child{margin-top:0}
body.ln-front .hm-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-16);flex-wrap:wrap;margin:0 0 var(--sp-16)}
body.ln-front .hm-sec-head h2{margin:0;font-size:var(--fs-28);font-weight:600;letter-spacing:-.01em;line-height:1.15;text-transform:none}
body.ln-front .hm-sec-head a{font-size:var(--fs-14);font-weight:600;white-space:nowrap}
body.ln-front .hm-sub{margin:0 0 var(--sp-24);font-size:var(--fs-16);color:var(--ink-2);max-width:60ch;line-height:1.5}
body.ln-front .hm-sub a{font-weight:600}

/* ── 1 · the hero ─────────────────────────────────────────────────────────
   White, centred: the headline, one sentence, the search field (the one pill
   on the page, and it is a field), the entry points as text links, a note,
   a hairline. The element keeps the class .sl-mast because the browser gate
   finds a lane page's nameplate by it; every dress rule on .sl-mast is
   undone here. */
body.ln-front .sl-mast{background:none;border:0}
body.ln-front .hm-hero{padding:var(--sp-64) 0 0;text-align:center}
body.ln-front .hm-hero h1{margin:0 auto var(--sp-16);font-size:var(--fs-hero);font-weight:700;letter-spacing:-.03em;line-height:1.02;max-width:20ch;text-wrap:balance;color:var(--ink);text-transform:none}
@media(max-width:480px){body.ln-front .hm-hero h1{font-size:var(--fs-34)}}
body.ln-front .hm-lede{margin:0 auto var(--sp-32);font-size:var(--fs-19);color:var(--ink-2);max-width:56ch;line-height:1.5}
body.ln-front .hm-find{max-width:640px;margin:0 auto}
body.ln-front .hm-entry{display:flex;justify-content:center;flex-wrap:wrap;gap:var(--sp-8) var(--sp-32);margin:var(--sp-24) 0 0;padding:0;list-style:none}
body.ln-front .hm-entry a{font-size:var(--fs-16);font-weight:600}
body.ln-front .hm-entry li[hidden]{display:none}
body.ln-front .hm-fine{margin:var(--sp-12) 0 0;font-size:var(--fs-14);color:var(--ink-3)}
body.ln-front .hm-fine b{color:var(--ink);font-weight:600}
body.ln-front .hm-rule{border:0;height:1px;background:var(--line);margin:var(--sp-48) 0 0}
@media(max-width:860px){body.ln-front .hm-hero{padding-top:var(--sp-48)}body.ln-front .hm-rule{margin-top:var(--sp-32)}}
/* The league strip rides between the rule and the shell, at the shell's width. */
body.ln-front .ln-state{padding-top:var(--sp-16)}
body.ln-front .ln-state:empty,body.ln-front .ln-state:has(> [hidden]:only-child){display:none}

/* ── 2 · the shell: the rail, and one wide column ─────────────────────────
   The shell above draws three columns. The player rail now sits in the left
   rail under the navigation, so the shell is the rail and the front. Inside
   min-width, because the shell's own two-step collapse is written (0,1,0)
   and an unconditional (0,2,0) here would hold the phone at two columns. */
@media(min-width:861px){
  body.ln-front .sl-shell{grid-template-columns:232px minmax(0,1fr);grid-template-areas:"nav main";column-gap:var(--sp-48)}
}
@media(min-width:1181px){body.ln-front .sl-shell{grid-template-columns:248px minmax(0,1fr);column-gap:var(--sp-64)}}
body.ln-front .sl-shell{padding-top:var(--sp-32)}
/* The rail on paper: no shade, no box. Whitespace and hairlines group it,
   the same way the homepage groups everything. */
body.ln-front .sl-left{background:none;border:0;border-radius:0;padding:0}
body.ln-front .sl-nav{border-top:0;padding-top:0}
body.ln-front .sl-nav-h{font-size:var(--fs-14);color:var(--ink-3);margin-bottom:var(--sp-8)}
body.ln-front .sl-nav li{border-bottom-color:var(--line)}
body.ln-front .sl-nav li:first-child{border-top:1px solid var(--line)}
body.ln-front .sl-nav a{padding:10px 0}
body.ln-front .sl-nav b{font-size:var(--fs-16)}
body.ln-front .sl-nav span{font-size:var(--fs-13);color:var(--ink-3)}
body.ln-front .sl-nav-more{border-top-color:var(--line)}
body.ln-front .sl-nav-more p{color:var(--ink-3)}
/* The player rail, under the navigation. */
body.ln-front .ln-rail-upd{margin-top:var(--sp-32)}
body.ln-front .sl-upd{border-color:var(--line);border-radius:10px}
body.ln-front .sl-upd-h{background:var(--card);border-bottom-color:var(--line)}
body.ln-front .sl-upd-note{font-size:var(--fs-13);color:var(--ink-3);line-height:1.5;margin:var(--sp-12) 0 0;padding:0}
@media(max-width:860px){
  body.ln-front .sl-nav li:first-child{border-top:0}
  /* The updates module is a column object; between a chip row and the front
     on a phone it would be the first thing on the screen. */
  body.ln-front .ln-rail-upd{display:none}
}

/* ── 3 · the tiles ────────────────────────────────────────────────────────
   front.html's, as they are: six portrait position tiles, and the four
   smaller function tiles that sit under a lane on the homepage, here four
   across as the lane's own tool row. Each a link, its illustration in its
   own palette, the name over a dark fade at the foot. */
body.ln-front .hm-tiles{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:var(--sp-24);margin:0;padding:0;list-style:none}
body.ln-front .hm-tile{position:relative;display:block;aspect-ratio:3/4;border-radius:10px;overflow:hidden;color:#fff;background:var(--t1)}
body.ln-front .hm-tile:hover{color:#fff;text-decoration:none}
body.ln-front .hm-tile:hover .hm-tile-name{text-decoration:underline}
body.ln-front .hm-tile svg{position:absolute;inset:0;width:100%;height:100%;display:block}
body.ln-front .hm-tile-cap{position:absolute;left:0;right:0;bottom:0;padding:var(--sp-48) var(--sp-16) var(--sp-16);background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.72))}
body.ln-front .hm-tile-name{display:block;font-size:var(--fs-19);font-weight:600;line-height:1.2}
body.ln-front .hm-tile-live{display:block;font-size:var(--fs-13);color:rgba(255,255,255,.86);margin-top:2px;font-variant-numeric:tabular-nums}
@media(max-width:1180px){body.ln-front .hm-tiles{gap:var(--sp-12)}body.ln-front .hm-tile-name{font-size:var(--fs-14);overflow-wrap:anywhere}body.ln-front .hm-tile-cap{padding:var(--sp-32) var(--sp-12) var(--sp-12)}}
@media(max-width:860px){
  body.ln-front .hm-tiles{display:flex;overflow-x:auto;margin:0 calc(-1 * var(--space-4));padding:0 var(--space-4);gap:var(--sp-12);scrollbar-width:none;scroll-snap-type:x proximity;scroll-padding-inline:var(--space-4)}
  body.ln-front .hm-tiles::-webkit-scrollbar{display:none}
  body.ln-front .hm-tiles li{flex:0 0 44%;scroll-snap-align:start}
  body.ln-front .hm-tile{width:100%}
}
body.ln-front .hm-lane-kit{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--sp-24);margin:0;padding:0;list-style:none}
body.ln-front .hm-lane-fn{position:relative;display:block;aspect-ratio:4/3;border-radius:10px;overflow:hidden;color:#fff;background:var(--t1)}
body.ln-front .hm-lane-fn:hover{color:#fff;text-decoration:none}
body.ln-front .hm-lane-fn:hover .hm-lane-fn-name{text-decoration:underline}
body.ln-front .hm-lane-fn svg{position:absolute;inset:0;width:100%;height:100%;display:block}
body.ln-front .hm-lane-fn-cap{position:absolute;left:0;right:0;bottom:0;padding:var(--sp-48) var(--sp-16) var(--sp-16);background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.72))}
body.ln-front .hm-lane-fn-name{display:block;font-size:var(--fs-19);font-weight:600;line-height:1.2}
body.ln-front .hm-lane-fn-sub{display:block;font-size:var(--fs-14);color:rgba(255,255,255,.86);margin-top:2px}
@media(max-width:1100px){body.ln-front .hm-lane-kit{gap:var(--sp-12)}body.ln-front .hm-lane-fn-name{font-size:var(--fs-16)}body.ln-front .hm-lane-fn-sub{font-size:var(--fs-13)}}
@media(max-width:600px){body.ln-front .hm-lane-kit{grid-template-columns:repeat(2,minmax(0,1fr))}body.ln-front .hm-lane-fn-cap{padding:var(--sp-24) var(--sp-12) var(--sp-12)}}
/* The rest of a lane as an index: two columns of links on hairlines. */
body.ln-front .ln-index{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 var(--sp-48);margin:var(--sp-32) 0 0;padding:0;list-style:none;border-top:1px solid var(--line)}
body.ln-front .ln-index a{display:block;padding:12px 0;border-bottom:1px solid var(--line);color:var(--ink)}
body.ln-front .ln-index a:hover{text-decoration:none}
body.ln-front .ln-index a:hover b{text-decoration:underline}
body.ln-front .ln-index b{display:block;font-size:var(--fs-16);font-weight:600;line-height:1.3}
body.ln-front .ln-index span{display:block;margin-top:2px;font-size:var(--fs-14);color:var(--ink-3);line-height:1.45}
@media(max-width:700px){body.ln-front .ln-index{grid-template-columns:1fr}}

/* ── 4 · the cards ────────────────────────────────────────────────────────
   front.html's How-it-works cards: outlined, 10px, a heading over a
   sentence. The only enclosed surface on a lane page that is not a ledger or
   a form, used only where a live sentence has to sit somewhere. A card that
   is a link is the whole card; nothing inside it says "→". */
body.ln-front .hm-how-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-24)}
body.ln-front .hm-how-grid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
body.ln-front .hm-how-grid.four{grid-template-columns:repeat(4,minmax(0,1fr))}
body.ln-front .hm-input{display:block;border:1px solid var(--line);border-radius:10px;padding:var(--sp-24);background:var(--card);color:var(--ink);min-width:0}
body.ln-front a.hm-input:hover{text-decoration:none;border-color:var(--line2);color:var(--ink)}
body.ln-front a.hm-input:hover h3{text-decoration:underline}
body.ln-front .hm-input .k{display:block;margin:0 0 var(--sp-8);font-size:var(--fs-14);color:var(--ink-3)}
body.ln-front .hm-input h3{margin:0 0 var(--sp-8);font-size:var(--fs-19);font-weight:600;line-height:1.3;letter-spacing:-.005em;color:var(--ink)}
body.ln-front .hm-input h3 em{font-style:normal;color:var(--teal)}
body.ln-front .hm-input p{margin:0;font-size:var(--fs-16);color:var(--ink-2);line-height:1.5}
body.ln-front .hm-input p+p{margin-top:var(--sp-8)}
body.ln-front .hm-input .is-note{font-size:var(--fs-14);color:var(--ink-3)}
body.ln-front .hm-input .is-btns{margin-top:var(--sp-16)}
body.ln-front .hm-input .is-btn{border-radius:4px}
@media(max-width:1100px){body.ln-front .hm-how-grid,body.ln-front .hm-how-grid.four{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:700px){body.ln-front .hm-how-grid,body.ln-front .hm-how-grid.two,body.ln-front .hm-how-grid.four{grid-template-columns:1fr;gap:var(--sp-16)}}

/* ── 5 · what is empty says so, in a sentence ─────────────────────────────
   Not a box with an icon: a line of text where the thing would be, and the
   link to where it is. */
body.ln-front .is-vacant{border:0;border-radius:0;background:none;padding:var(--sp-8) 0;gap:var(--sp-16)}
body.ln-front .is-vacant b{font-size:var(--fs-16);font-weight:600;color:var(--ink)}
body.ln-front .is-vacant span{font-size:var(--fs-14);color:var(--ink-3)}
body.ln-front .is-vacant .is-btn{border-radius:4px}

/* ── 6 · the desk ─────────────────────────────────────────────────────────
   front.html's story cards: white, 10px, a soft lift off the paper, the
   newest as the wide lead and the two under it in a row. */
body.ln-front .sl-stories{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-24);margin:0}
body.ln-front .sl-story{border:0;border-radius:10px;padding:var(--sp-24);gap:var(--sp-12);
  box-shadow:0 1px 2px rgba(17,20,24,.06),0 4px 14px rgba(17,20,24,.10);transition:box-shadow .15s ease}
body.ln-front .sl-story:hover{border:0;box-shadow:0 2px 4px rgba(17,20,24,.08),0 8px 24px rgba(17,20,24,.14)}
body.ln-front .sl-story.lead{grid-column:1/-1;padding:var(--sp-32)}
body.ln-front .sl-story.lead h3{font-size:var(--fs-28);font-weight:700;line-height:1.25;letter-spacing:-.01em}
body.ln-front .sl-story.lead p{font-size:var(--fs-16);line-height:1.5}
body.ln-front .sl-story h3{font-size:var(--fs-19);line-height:1.3}
body.ln-front .sl-story p{font-size:var(--fs-14);line-height:1.5}
body.ln-front .sl-story .nr-kicker{font-size:var(--fs-13);color:var(--ink-3);font-weight:400;letter-spacing:0;text-transform:none}
body.ln-front .sl-story .nr-flag{display:none}
body.ln-front .sl-story .nr-by{border-top-color:var(--line)}
body.ln-front .sl-story.is-boast{background:var(--card)}
@media(max-width:700px){body.ln-front .sl-stories{grid-template-columns:1fr;gap:var(--sp-16)}
  body.ln-front .sl-story.lead{padding:var(--sp-24)}body.ln-front .sl-story.lead h3{font-size:var(--fs-23)}}
