/* ---- static/primitives.css ---- */
/* ==========================================================================
   Pabst Interference — PRIMITIVES
   --------------------------------------------------------------------------
   The nine values this identity is made of, and nothing else. No semantics
   live here: a primitive says WHAT A COLOUR IS, never what it means. Roles are
   bound in tokens.css, per property.

   THIS FILE IS SHARED. A byte-identical copy lives in the projections suite
   (ff-algo). The two properties bind these to different roles - red is the ink
   the badge is printed in here, and the data exception there - but they are
   printed with the same inks. If these two files ever diverge, the family is
   over; tests/test_primitives.py fails the build when they do.

   PROVENANCE. Every value except the two ambers is sampled from
   img/pabst-interference-logo.png (836x842, md5 5136bb34a4847a6aa7f04344a9a33e66),
   measured pixel by pixel at alpha >= 128 over 552,796 opaque pixels.
   See SITE_REDESIGN_PLAN.md 1.1-1.3.
   ========================================================================== */

:root {
  /* --- Navy. 44.21% of the mark. The rosette, the nameplate, the script. --- */
  --navy-900: #021562;   /* 0.916% modal. #001763 is the runner-up at 0.536%   */
                         /* and dRGB 3.0 away - visually identical, and this   */
                         /* value is the one already committed in ff-algo.     */

  /* --- Red. 9.93%. Two values because THE MARK IS LIT (plan 1.2): the   --- */
  /* --- ring's left arc is lighter than its right arc, same object.      --- */
  --red-500: #D43845;    /* 0.921% modal. The red where light strikes.         */
  --red-700: #AE040D;    /* 0.212%. The red in shadow. Also the only red that  */
                         /* passes 4.5:1 as text (5.91:1 on paper).            */

  /* --- White. 40.42%. Also a lit/shadowed pair, and NOT cream:          --- */
  /* --- mean(R-B) across the family is -0.3402 on 0-255. Neutral, and    --- */
  /* --- if it leans at all it leans cool.                                --- */
  --white-100: #F5F5F5;  /* 5.241%. The field in light.                        */
  --white-200: #E5E5E5;  /* 6.428%. The field in shadow. The most common       */
                         /* single colour in the entire mark.                  */

  /* --- Silver. 5.11%. The gadrooned rosette edge, "BEER", tail outlines. --- */
  --silver-500: #929196; /* 0.093% modal, exact.                               */

  /* --- Gold. NOT IN THE MARK: zero pixels at hue 35-60 with sat > 0.35. --- */
  /* --- Provenance is the PBR register, not this league's artifact. The  --- */
  /* --- projections suite uses it for one 2px rail hairline; the league  --- */
  /* --- site binds no role to it at all.                                 --- */
  --gold-500: #C9A227;

  /* --- Amber. NOT IN THE MARK. Semantic only, and it exists because a   --- */
  /* --- brief constraint requires a non-brand colour for negatives so    --- */
  /* --- red never has to mean "bad". Chosen at hue 30.5 deg: red (355)   --- */
  /* --- and gold (46) are its only near neighbours in hue, and both are  --- */
  /* --- zoned out of every region amber appears in.                      --- */
  --amber-700: #8A4A08;  /* light theme. 5.44:1 on --white-200.                */
  --amber-300: #F0A050;  /* dark theme.  7.66:1 on --navy-900.                 */
}

/* ---- static/tokens.css ---- */
/* ==========================================================================
   Pabst Interference — LEAGUE SITE SEMANTICS
   --------------------------------------------------------------------------
   What each primitive MEANS here. The projections suite binds the same
   primitives differently; that is the point (plan 3.1).

   THE USAGE RULE IS PART OF THE DEFINITION. A token used against its rule is
   a bug, not a variation. The nine rules are at the bottom of this file and
   ship verbatim, because a rule that lives only in a plan is a rule nobody
   reads while writing CSS.
   ========================================================================== */

:root {
  /* Surfaces */
  --paper:        var(--white-200);   /* the ground. nothing else is this      */
  --card:         var(--white-100);   /* a raised surface                      */
  --recess:       #D8D8DA;            /* an inset surface: controls only       */
  --plate:        var(--navy-900);    /* the ribbon's ground. NOT a surface -  */
                                      /* it is a printed object, so it does    */
                                      /* not invert in dark mode.              */

  /* Ink */
  --ink:          var(--navy-900);    /* body text IS the brand navy, 12.96:1  */
  --t1:           #021562;
  --t2:           #23305F;
  --t3:           #464B60;
  --t4:           #646470;
  --on-plate:     var(--white-100);   /* 14.98:1 on the ribbon                 */

  /* An INSET ribbon is the inverse: white ground, navy ink - the rail's active
     item and the active week. These two DO NOT INVERT, and that is the whole
     reason they exist as tokens.

     The ribbon is a printed object, so its ground stays --white-100 in both
     themes (see the dark block below). Putting an inverting token on it is
     therefore a bug that only appears in dark mode: --t3 is #464B60 in light
     and #9BA3C4 in dark, and the second is a light grey on a white ground.
     That shipped in Stage 1 and stayed invisible because the one page it was
     on had no reading under its active item. */
  --inset-ink:       #021562;         /* 14.98:1 on --white-100               */
  --inset-ink-muted: #464B60;         /*  7.91:1 on --white-100               */

  /* The mark's red. Chrome only - never a state, never in a data region. */
  --sash:         var(--red-500);     /* red AS A MARK.  3.75:1 - never text   */
  --sash-ink:     var(--red-700);     /* red AS TEXT.    5.91:1                */

  /* Structure */
  --rule:         rgb(146 145 150 / 42%);   /* decorative hairline   1.41:1    */
  --rule-soft:    rgb(146 145 150 / 24%);   /* separators inside a card        */
  --rule-loud:    #757479;                  /* a rule that CARRIES MEANING.    */
                                            /* 3.68:1 - clears WCAG 1.4.11's   */
                                            /* 3:1 for an informative graphic. */
  --wash:         rgb(146 145 150 / 11%);   /* banding, skeletons     1.09:1   */

  /* The raking light (plan 3.6). The mark is lit from the left: the ring's
     left arc is #F5F5F5 and its right arc #E5E5E5, same object, same y. Every
     raised surface carries that light as two 1px lines - which is also the
     only thing giving cards an edge, since --card on --paper is 1.16:1. */
  --edge-lit:     var(--white-100);
  --edge-shade:   rgb(146 145 150 / 42%);

  /* Semantics. There is exactly one, and it is negative. */
  --deficit:      var(--amber-700);

  /* Controls get their own tokens so they can be tuned without moving every
     inset surface. */
  --control:        var(--recess);
  --control-edge:   rgb(146 145 150 / 55%);
  --control-focus:  var(--navy-900);
  --card-hover:     #EDEDEF;          /* one step toward the ground            */

  /* Scale. 14px base, ratio 1.25. The 18px step is declared for parity with
     the projections suite and is deliberately unused here. */
  --fs-cap:  11px;
  --fs-body: 14px;
  --fs-sub:  16px;
  --fs-lg:   22px;
  --fs-xl:   28px;
  --fs-display: 44px;

  /* Space. Every gap is a multiple of --u. */
  --u: 4px;
  --pad-tight: 12px;
  --pad: 16px;
  --gap: 16px;
  --section-gap: 32px;
  --focal-air: 48px;

  /* Radius. Small, like print on a can. Never 0 - except the ribbon, which
     has notches instead. */
  --r-ctl: 2px;
  --r-card: 4px;
  --r-over: 6px;

  /* The ribbon */
  --ribbon-h: 24px;
  --ribbon-pad: 12px;
  --ribbon-notch: 8px;

  --rail-h: 64px;

  /* Motion. transform and opacity only, and all of it behind a query. */
  --fast: 140ms;
  --ease: cubic-bezier(0.23, 1, 0.32, 1);

  --font-plate: "Oswald Plate", "DIN Alternate", "Roboto Condensed",
                "Liberation Sans Narrow", "Arial Narrow", system-ui, sans-serif;
  --font-body:  system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-data:  ui-monospace, "SF Mono", "Cascadia Code", "DejaVu Sans Mono", monospace;
}

/* --------------------------------------------------------------------------
   Dark. The blue moves from the ink to the ground - the same move the
   projections suite makes, so the two properties invert identically. The
   palette GAINS NO COLOUR in the dark.

   The ribbon does not invert. It stays navy with white caps in both themes,
   because it is a printed object rather than a surface - which means the
   signature reads the same either way.
   -------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #050B28;
    --card: #0A1440;
    --recess: #030818;
    --ink: var(--white-200);
    --t1: #E5E5E5;
    --t2: #C9CEE6;
    --t3: #9BA3C4;
    --t4: #7B84A6;
    --sash-ink: #FF8A80;
    --deficit: var(--amber-300);
    --rule: rgb(123 127 149 / 42%);
    --rule-soft: rgb(123 127 149 / 24%);
    --rule-loud: #9A9FB4;
    --wash: rgb(123 127 149 / 11%);
    --edge-lit: #16214F;
    --edge-shade: rgb(0 0 0 / 45%);
    --control: #030818;
    --control-edge: rgb(123 127 149 / 55%);
    --control-focus: var(--white-200);
    --card-hover: #101B4E;
  }
}

:root[data-theme="dark"] {
  --paper: #050B28;
  --card: #0A1440;
  --recess: #030818;
  --ink: var(--white-200);
  --t1: #E5E5E5;
  --t2: #C9CEE6;
  --t3: #9BA3C4;
  --t4: #7B84A6;
  --sash-ink: #FF8A80;
  --deficit: var(--amber-300);
  --rule: rgb(123 127 149 / 42%);
  --rule-soft: rgb(123 127 149 / 24%);
  --rule-loud: #9A9FB4;
  --wash: rgb(123 127 149 / 11%);
  --edge-lit: #16214F;
  --edge-shade: rgb(0 0 0 / 45%);
  --control: #030818;
  --control-edge: rgb(123 127 149 / 55%);
  --control-focus: var(--white-200);
  --card-hover: #101B4E;
}

/* ==========================================================================
   THE RULES. These ship verbatim. Tests enforce 2, 3 and 7.

    1. NAVY IS CHROME AND VOICE, NEVER A DIRECTION.
       The rail, the ribbons, body text, links and every measured value are
       --ink / --plate. Navy never means good, never means better, never up.

    2. RED HAS EXACTLY ONE JOB HERE: IT IS THE INK THE BADGE IS PRINTED IN.
       The page sash, the ring, the ribbon's outer edge. That is the list.
       Red is NOT a state: not hover, not active, not selected, not focus.
       Red NEVER appears inside a standings table, a matchup score, a roster
       or a projection. Therefore red can never be read as "loss" or "down",
       because it is never in the region where losses are displayed.

    3. NEGATIVE IS AMBER (--deficit), AND AMBER IS NOTHING ELSE - AND IT
       COLOURS WORDS, NOT NUMBERS THAT ALREADY HAVE A BAR.
       Where geometry carries direction, amber is redundant and highlights
       only the losers. On / the centred-zero bar is the direction device and
       the figure stays --t1. Amber is for ORDER NOT SET, 0 KEPT, a passed
       deadline, an elimination. The one exception is earned: below 640px the
       bar column is CUT, so the sign is all that is left and amber returns.

    4. POSITIVE HAS NO COLOUR. There is no green. A win is navy.
       Direction is POSITION AND GEOMETRY: bars grow from a centred tick.

    5. GOLD DOES NOT EXIST ON THIS PROPERTY. Zero pixels in the mark.

    6. SILVER IS STRUCTURE, NEVER TEXT. Solid #929196 is 2.48:1 on paper; at
       the 42% the hairline composites to, 1.41:1. A RULE THAT CARRIES MEANING
       IS NOT A HAIRLINE - it takes --rule-loud (3.68:1 on paper, 4.25:1 on
       card) so it clears WCAG 1.4.11.

    7. --deficit IS A GLYPH COLOUR AND NEVER LANDS ON --plate (2.38:1 there).

    8. THE TOKEN NAMES ARE PART OF THE SYSTEM. --deficit, not --down: "down"
       is a noun in this sport and a token that means something else in the
       domain's own vocabulary is a trap for every future reader.

    9. RED AS TEXT IS --sash-ink; RED AS A MARK IS --sash. One job, two
       values, split by the contrast a size class needs. The brighter red
       never carries a letter.
   ========================================================================== */

/* ---- static/site.css ---- */
/* ==========================================================================
   Pabst Interference — layout and components.
   One file. There are no inline <style> blocks anywhere, because 117,857 B of
   them is how the last system decayed (plan 4.5).
   ========================================================================== */

@font-face {
  font-family: "Oswald Plate";
  src: url("/static/fonts/oswald-plate.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
  /* Subset to caps, digits and punctuation - the ribbon's actual character
     set. 5,652 B against 21,472 for the full latin cut. */
  unicode-range: U+0020-0039, U+003A-003B, U+0041-005A, U+00B7, U+2013-2014,
                 U+2019, U+0023, U+0025-0026, U+0028-0029, U+002B, U+002E-002F,
                 U+0021, U+003F;
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: var(--fs-body)/1.5 var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

/* ---------- the sash: the mark's diagonal, flattened to the page rule ------ */
/* The one gradient in the system. It is not decoration - it is the artifact's
   own light direction (plan 1.2), and it is exactly two palette values.       */
.sash {
  height: 6px;
  background: linear-gradient(100deg,
              var(--red-500) 0%, var(--red-500) 52%, var(--red-700) 100%);
}

/* ---------- the ribbon: the signature ------------------------------------- */
.ribbon {
  display: inline-flex;
  align-items: center;
  min-height: var(--ribbon-h);
  padding: 0 var(--ribbon-pad);
  background: var(--plate);
  color: var(--on-plate);
  font-family: var(--font-plate);
  font-size: var(--fs-cap);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* chevron-notched ends, from the mark's nameplate */
  clip-path: polygon(var(--ribbon-notch) 0, calc(100% - var(--ribbon-notch)) 0,
                     100% 50%, calc(100% - var(--ribbon-notch)) 100%,
                     var(--ribbon-notch) 100%, 0 50%);
  /* the gadroon: the silver keyline inside the mark's rosette edge. The one
     inset shadow in the system, and it is a keyline, not depth. */
  box-shadow: inset 0 0 0 1px rgb(146 145 150 / 55%);
  margin: 0;
}

/* ---------- the rail ------------------------------------------------------ */
.rail {
  display: flex;
  align-items: center;
  gap: 20px;
  min-height: var(--rail-h);
  padding: 0 20px;
  background: var(--plate);
}
.rail__mark {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto;
  text-decoration: none;   /* it is a wordmark, not a link with a rule under it */
}
.rail__mark img { display: block; height: 30px; width: auto; }
.rail__wordmark {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 12px;
  background: var(--white-100);
  color: var(--plate);
  font-family: var(--font-plate);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: 1;
  clip-path: polygon(8px 0, calc(100% - 8px) 0, 100% 50%,
                     calc(100% - 8px) 100%, 8px 100%, 0 50%);
}

.rail__nav {
  display: flex;
  gap: 2px;
  margin-left: auto;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.rail__nav::-webkit-scrollbar { display: none; }

.rail__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 14px;
  min-height: 44px;                    /* the tap target, at every width */
  justify-content: center;
  color: #C9CEE6;
  text-decoration: none;
  font-family: var(--font-plate);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1.15;
}
/* Every rail item carries its own live reading, the way the projections
   suite's rail does. This is what makes the ribbon structural rather than
   decorative: deleting it deletes information. */
.rail__reading {
  font-family: var(--font-data);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  color: #8A93BE;
  font-weight: 400;
}
.rail__link:hover { color: var(--white-100); }

/* The theme toggle is a real <button> that borrows the rail's link styling.
   It used to be styled inline from site.js with `color: inherit`, which
   inherited --ink - navy - and painted a navy button on the navy rail at
   1.00:1. Invisible, and caught by the Layer 2 contrast walk rather than by
   anyone looking at it. Styles belong in the stylesheet. */
.rail__toggle {
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  color: #C9CEE6;
}
.rail__toggle:hover { color: var(--white-100); }

/* A rail item whose route is not built yet. Present and quiet, not missing -
   5.5 says the rail never hides an item that will come back - and not a link,
   because a nav that 404s is worse than one that says "soon". */
.rail__link.is-unbuilt { color: #7C86B4; cursor: default; }
.rail__link[aria-current="page"] {
  color: var(--inset-ink);
  position: relative;
  /* isolation, not just position, and it is load-bearing. z-index:-1 on the
     ::before is contained by the nearest STACKING CONTEXT; position:relative
     with z-index:auto does not create one, so the pseudo painted behind
     .rail's own navy background and the active item vanished entirely. It
     looked fine in light mode and was invisible in dark. Caught by opening the
     screenshot - the contrast walk was happy, because the colour it found was
     correct, it just never reached the screen. */
  isolation: isolate;
}
/* The active item's notched ground is a PSEUDO-ELEMENT, not a background-image
   and not a clip-path on the link itself.
     - clip-path on the <a> would cut its focus ring off, which is exactly the
       defect the old site had on 24 interactive rules (plan 4.6);
     - a background-IMAGE would mean the white ground is decorative: if the
       data-URI ever fails to paint, the text is navy on the navy rail at
       1.00:1. The Layer 2 contrast walk caught that, correctly - it reads
       painted colour, which is what a reader actually gets.
   A clipped ::before keeps the notch, keeps the focus ring on the <a>, and
   makes the white ground a real painted colour. */
.rail__link[aria-current="page"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--white-100);
  clip-path: polygon(7px 0, calc(100% - 7px) 0, 100% 50%,
                     calc(100% - 7px) 100%, 7px 100%, 0 50%);
}
/* --inset-ink-muted, NOT --t3: --t3 inverts and this white ground does not. */
.rail__link[aria-current="page"] .rail__reading { color: var(--inset-ink-muted); }

/* ---------- page shell ---------------------------------------------------- */
.wrap { max-width: 1120px; margin: 0 auto; padding: 32px 20px 48px; }
.section + .section { margin-top: var(--section-gap); }
.rule { border: 0; border-top: 1px solid var(--rule); margin: var(--section-gap) 0; }

/* ---------- the focal block ----------------------------------------------- */
.focal { margin: var(--focal-air) 0; }
.focal__label {
  font-family: var(--font-plate);
  font-size: var(--fs-cap);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--t3);
  margin: 0;
}
.focal__value {
  font-size: var(--fs-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.05;
  margin: 6px 0 0;
  text-wrap: balance;
}
.focal__flag {
  font-size: var(--fs-sub);
  font-weight: 600;
  color: var(--deficit);
  margin: 8px 0 0;
}
.focal__meta { font-size: var(--fs-cap); color: var(--t4); margin: 4px 0 0; }

/* ---------- cards: the raking light --------------------------------------- */
/* --card on --paper is 1.16:1, so with no shadow and no border a card has no
   visible edge at all. Two 1px lines carry the mark's own light instead:
   lit on top and left, shadowed on bottom and right. */
.card {
  background: var(--card);
  border-radius: var(--r-card);
  padding: var(--pad);
  border-top: 1px solid var(--edge-lit);
  border-left: 1px solid var(--edge-lit);
  border-bottom: 1px solid var(--edge-shade);
  border-right: 1px solid var(--edge-shade);
}

/* ---------- the standings table ------------------------------------------- */
.standings { width: 100%; border-collapse: collapse; table-layout: fixed; }

/* Column widths live in a <colgroup>. table-layout:fixed takes its widths from
   the FIRST ROW, so declaring them on td silently truncated every long team
   name - found by rendering, not by assertion (plan 7.7). */
.col-rank { width: 44px; }
.col-mgr  { width: 120px; }
.col-wl   { width: 66px; }
.col-pf   { width: 84px; }
.col-bar  { width: 140px; }
.col-diff { width: 68px; }

.standings thead th {
  background: var(--plate);
  color: var(--on-plate);
  font-family: var(--font-plate);
  font-size: var(--fs-cap);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: left;
  padding: 8px 10px;
  box-shadow: inset 0 1px 0 rgb(146 145 150 / 35%),
              inset 0 -1px 0 rgb(146 145 150 / 35%);
}
.standings thead th:first-child {
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 8px 100%, 0 50%);
}
.standings thead th:last-child {
  clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%,
                     calc(100% - 8px) 100%, 0 100%);
}
.standings th.num, .standings td.num { text-align: right; }

.standings tbody td,
.standings tbody th {
  padding: 12px 16px;
  height: 44px;
  border-bottom: 1px solid var(--rule-soft);
}
/* The team cell is a <th scope="row"> for the row-header relationship screen
   readers need. Two browser defaults have to be undone for that, and both were
   visible: th centres its text, and skipping it in the td-only border rule left
   a gap in every row line that read as a stray vertical rule. */
.standings tbody th { text-align: left; font-weight: inherit; }
/* No row banding. With four visible tiers the rows separate themselves, and
   banding is a device for 40-row tables. */

/* The three levers - size, weight, tier - on every column. */
.cell-rank { font-family: var(--font-data); font-variant-numeric: tabular-nums;
             font-weight: 500; color: var(--t3); }
.cell-team { font-size: var(--fs-sub); font-weight: 600; color: var(--t1); }
.cell-mgr  { font-size: var(--fs-cap); color: var(--t4);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-wl   { font-family: var(--font-data); font-variant-numeric: tabular-nums;
             font-weight: 600; color: var(--t1); }
.cell-pf   { font-family: var(--font-data); font-variant-numeric: tabular-nums;
             font-weight: 400; color: var(--t3); }
.cell-diff { font-family: var(--font-data); font-variant-numeric: tabular-nums;
             font-weight: 500; color: var(--t1); }
/* Rule 3: the figure STAYS --t1 here even when negative, because the bar beside
   it already carries the direction and colouring both would highlight only the
   losers. The mobile block below is where amber earns its way back. */

/* The differential bar: grows left or right of a centred zero tick. The tick
   sits ABOVE the fill so it stays visible. No green, no red. */
.bar { position: relative; height: 12px; }
.bar__tick { position: absolute; top: -3px; bottom: -3px; left: 50%;
             width: 1px; background: var(--silver-500); z-index: 2; }
.bar__fill { position: absolute; top: 1px; bottom: 1px; background: var(--t1); }
.bar__fill--neg { background: var(--deficit); }

/* The award: the mark's two ribbon tails, inline after the leader's name.
   Inline rather than hanging, because hanging needs a 56px row and overlaps
   row 2 at 320px. */
.tails { display: inline-block; vertical-align: -3px; margin-left: 8px; }

/* ---------- the derived-dates strip --------------------------------------- */
.facts { display: flex; flex-wrap: wrap; gap: 28px; }
.fact__label {
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--t3);
}
.fact__value { font-size: var(--fs-body); color: var(--t1); }
.fact__value--flag { color: var(--deficit); font-weight: 600; }

/* ---------- staleness ----------------------------------------------------- */
/* The one thing this site must never do is what it used to: render a
   confident, wrong, two-season-old table with no indication. */
.ribbon--stale { background: var(--deficit); color: var(--white-100); }

/* ---------- footer -------------------------------------------------------- */
.foot { margin-top: var(--section-gap); font-size: var(--fs-cap); color: var(--t4); }

/* ---------- states -------------------------------------------------------- */
a { color: var(--ink); }
:focus-visible {
  outline: 2px solid var(--control-focus);
  outline-offset: 2px;
}
.rail :focus-visible { outline-color: var(--white-100); }

@media (prefers-reduced-motion: no-preference) {
  .rail__link, a { transition: color var(--fast) var(--ease); }
  .rail__link:active { transform: scale(0.98); }
}

/* ---------- mobile -------------------------------------------------------- */
@media (max-width: 640px) {
  .wrap { padding: 22px 14px 36px; }
  .rail { min-height: 52px; padding: 0 12px; gap: 10px; }
  .rail__mark img { height: 26px; }
  .rail__wordmark { font-size: 15px; height: 24px; padding: 0 10px; }
  .rail__link { font-size: 12px; padding: 6px 10px; }
  .rail__reading { display: none; }   /* readings drop; LABELS STAY FULL */
  .focal { margin: var(--section-gap) 0; }
  .focal__value { font-size: var(--fs-xl); }

  /* Release table-layout AND hide the colgroup together. Leaving fixed widths
     declared while the cells are display:none leaves the surviving columns
     unmapped, and two of them draw on top of each other - "12-2" and "+194"
     compositing into one illegible cell. Found by looking (plan 7.7). */
  .standings { table-layout: auto; }
  .standings colgroup { display: none; }
  .col-mgr, .col-pf, .col-bar { width: 0; }
  .standings .cell-mgr, .standings .cell-pf, .standings .cell-bar,
  .standings th.col-mgr, .standings th.col-pf, .standings th.col-bar { display: none; }
  .cell-team { white-space: normal; }
  .standings tbody td, .standings tbody th { padding: 12px 8px; }
  /* Rule 3's earned exception: the DIFFERENTIAL column is cut at this width, so
     the sign is the only direction device left and amber comes back. This is
     the ONLY place --deficit touches a plotted value. */
  .cell-diff--neg { color: var(--deficit); }
  .facts { gap: 16px; flex-direction: column; }
}

/* ==========================================================================
   Stage 3 — /rosters and /week
   ========================================================================== */

/* ---------- page head with an aside ---------------------------------------- */
.head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap); flex-wrap: wrap;
}
.head__aside { display: flex; align-items: center; gap: var(--gap); flex-wrap: wrap; }
.head__meta {
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--t3);
}
.is-deficit { color: var(--deficit); }

/* A link styled as a control. It is a real <a> to a real URL, so the filter
   works with JavaScript off and the state is shareable - and it keeps the 44px
   hit area the old site's 20px controls never had. */
.control {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 var(--pad); background: var(--control); color: var(--t1);
  border: 1px solid var(--control-edge); border-radius: var(--r-ctl);
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; text-decoration: none;
}
.control:hover { background: var(--card-hover); }

/* ---------- the card grid -------------------------------------------------- */
/* Ten rosters and five matchups are sets of PEERS with no ranking, which is the
   one job a grid is genuinely for. What makes this not the generic move: no
   icons, no avatars, no team colour chips, 4px radius rather than 12, no
   shadow, no hover-lift, and the raking edge from 3.6 doing the separating. */
.grid {
  display: grid; gap: var(--gap);
  grid-template-columns: 1fr;
}
@media (min-width: 720px)  { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1120px) { .grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 900px)  { .grid--wide { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1120px) { .grid--wide { grid-template-columns: repeat(2, 1fr); } }

/* ---------- a roster card -------------------------------------------------- */
/* NO ribbon on the card title. The first draft put one on each of ten cards,
   which with the section ribbon and the rail made thirteen navy bars on one
   screen - wallpaper, not a signature. The title is 16/600 type. */
.roster__team { font-size: var(--fs-sub); font-weight: 600; color: var(--t1); margin: 0; }
.roster__mgr  { font-size: var(--fs-cap); color: var(--t4); margin: 2px 0 var(--pad-tight); }
.roster__group + .roster__group { margin-top: var(--pad-tight); }
.roster__pos {
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--t3);
  margin: 0 0 4px; padding-bottom: 3px; border-bottom: 1px solid var(--rule-soft);
}
.roster__list { list-style: none; margin: 0; padding: 0; }
.roster__player {
  display: flex; align-items: baseline; gap: 8px;
  padding: 3px 0; font-size: var(--fs-body);
}
.roster__name { flex: 1 1 auto; min-width: 0; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
.roster__club { font-family: var(--font-data); font-size: var(--fs-cap); color: var(--t4); }
/* The keeper mark. Rule 4 says a positive has no colour, so emphasis is
   WEIGHT, not hue - and rule 6 forbids silver as text, which is what the first
   version used: #929196 on --card is 2.87:1, illegible at 11px and against its
   own token rule. The glyph takes the ink and the name takes 600. */
.roster__kept { color: var(--t1); font-size: 10px; }
.roster__player.is-keeper .roster__name { font-weight: 600; }
.roster__count {
  margin: var(--pad-tight) 0 0; padding-top: var(--pad-tight);
  border-top: 1px solid var(--rule-soft);
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--t3);
}

/* ---------- the week strip ------------------------------------------------- */
.weeks {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px;
  margin: var(--pad) 0 6px;
}
.weeks__item {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 44px; padding: 0 8px;
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
  font-size: var(--fs-body); color: var(--t2); text-decoration: none;
  border-radius: var(--r-ctl);
}
.weeks__item:hover { background: var(--card-hover); }
/* The active week is an inset ribbon, not a pill - and it uses the same
   clipped-::before-with-isolation arrangement as the rail's active item, for
   the same three reasons (S1-1). */
.weeks__item.is-current {
  color: var(--on-plate); position: relative; isolation: isolate;
  font-weight: 600;
}
.weeks__item.is-current::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--plate);
  clip-path: polygon(6px 0, calc(100% - 6px) 0, 100% 50%,
                     calc(100% - 6px) 100%, 6px 100%, 0 50%);
}
/* The playoff boundary CARRIES A FACT - it is playoff_week_start, derived - so
   it is --rule-loud at 3.68:1, not a hairline. WCAG 1.4.11 wants 3:1 for an
   informative graphic (rule 6). */
.weeks__split {
  width: 2px; align-self: stretch; min-height: 32px;
  background: var(--rule-loud); margin: 0 6px;
}
.weeks__legend { font-size: var(--fs-cap); color: var(--t4); margin: 0 0 var(--section-gap); }

/* ---------- a matchup card ------------------------------------------------- */
.matchup__side { display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; }
.matchup__team { font-size: var(--fs-sub); font-weight: 500; color: var(--t2); }
.matchup__pts {
  grid-row: span 2; align-self: center;
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg); font-weight: 500; color: var(--t2);
}
.matchup__mgr { font-size: var(--fs-cap); color: var(--t4); }
/* The winner leads on size and weight. NO GREEN, NO RED - rule 2 keeps red out
   of a matchup score entirely, and rule 4 gives a positive no colour at all. */
.matchup__side.is-leading .matchup__team { font-weight: 600; color: var(--t1); }
.matchup__side.is-leading .matchup__pts  { font-size: var(--fs-xl); font-weight: 600; color: var(--t1); }
.matchup__split { border: 0; border-top: 1px solid var(--rule-soft); margin: var(--pad-tight) 0; }

@media (max-width: 640px) {
  .head { align-items: flex-start; flex-direction: column; gap: var(--pad-tight); }
  .weeks__item { min-width: 36px; padding: 0 4px; }
  .matchup__side.is-leading .matchup__pts { font-size: var(--fs-lg); }
}

/* ==========================================================================
   Stage 4 — /draft and /season
   ========================================================================== */

.note { font-size: var(--fs-cap); color: var(--t4); margin: var(--pad-tight) 0 0; max-width: 62ch; }

/* ---------- the keeper table ---------------------------------------------- */
.keepers { width: 100%; border-collapse: collapse; }
.keepers thead th {
  background: var(--plate); color: var(--on-plate);
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; text-align: left;
  padding: 8px 10px;
}
.keepers thead th:first-child {
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 8px 100%, 0 50%);
}
.keepers thead th:last-child {
  clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%);
}
.keepers tbody td, .keepers tbody th {
  padding: 12px 10px; height: 44px;
  border-bottom: 1px solid var(--rule-soft);
}
/* Both browser defaults on a row header, undone explicitly - th centres its
   text and a td-only border rule leaves a gap that reads as a stray vertical
   rule (S1-4). Two tables have needed this now. */
.keepers tbody th { text-align: left; font-weight: 600; font-size: var(--fs-body); color: var(--t1); }
.keepers__cell { font-size: var(--fs-body); white-space: nowrap; }
.keepers__dot { color: var(--t1); font-size: 9px; vertical-align: 2px; }
.keepers__dot.is-empty { color: var(--rule-loud); }
.keepers__pos {
  font-family: var(--font-data); font-size: var(--fs-cap); color: var(--t4); margin-left: 4px;
}
/* The one row that is not done is the page's actual news, and the only place
   --deficit appears here: it is a WORD, not a plotted value (rule 3). */
.keepers__none { color: var(--deficit); font-size: var(--fs-cap);
                 font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; }

/* ---------- the head-to-head matrix ---------------------------------------- */
/* The ONE element on the site allowed to scroll sideways, and it scrolls inside
   its own box - the page body never does (7.7 test 3). A 10x10 matrix cannot be
   cut down the way a standings table can. */
.scroller { overflow-x: auto; }
.h2h { border-collapse: collapse; font-variant-numeric: tabular-nums; }
.h2h th, .h2h td { padding: 8px 10px; border-bottom: 1px solid var(--rule-soft); }
.h2h__head, .h2h__corner {
  background: var(--plate); color: var(--on-plate);
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; text-align: center;
}
.h2h__corner { text-align: left; }
/* The row label sticks, so you always know whose row you are reading once the
   matrix is scrolled. */
.h2h__team {
  position: sticky; left: 0; z-index: 1;
  background: var(--card); text-align: left; font-weight: 600;
  font-size: var(--fs-body); color: var(--t1); white-space: nowrap;
}
.h2h__cell {
  text-align: center; font-family: var(--font-data); font-size: var(--fs-body);
  color: var(--t1); white-space: nowrap; min-width: 56px;
}
/* The losing half of a pair recedes to a lower tier. NOT amber: this is a
   record, not a deficit, and rule 3 keeps amber for words. */
.h2h__cell.is-behind { color: var(--t4); }
.h2h__cell.is-self { background: var(--wash); }
.h2h__total { font-weight: 600; }
/* A <th> in this table takes the plate, so it needs the plate's ink. Without
   the colour it inherits --t1 - navy on navy, 1.00:1. The same shape of bug as
   the rail's active item, and caught the same way. */
th.h2h__total { background: var(--plate); color: var(--on-plate); }

/* ---------- history -------------------------------------------------------- */
.history { list-style: none; margin: var(--pad-tight) 0 0; padding: 0; }
.history__row {
  display: flex; align-items: baseline; gap: var(--gap); flex-wrap: wrap;
  padding: var(--pad-tight) 0; border-bottom: 1px solid var(--rule-soft);
}
.history__season {
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; color: var(--t3); min-width: 48px;
}
.history__team { font-size: var(--fs-sub); font-weight: 600; color: var(--t1); }
.history__mgr  { font-size: var(--fs-cap); color: var(--t4); }
.history__rec  { margin-left: auto; font-family: var(--font-data);
                 font-size: var(--fs-cap); color: var(--t3); }

@media (max-width: 640px) {
  .keepers thead th.col-mgr, .keepers tbody td.col-mgr { display: none; }
  /* Three keeper names plus a team name does not fit 320px, and squeezing them
     is how the brief's zero-width column happens. The names are CUT and the
     dots stay: "who has finished" is the question this page answers in August,
     and the names themselves are one tap away on /rosters?keepers=1. */
  .keepers__name, .keepers__pos { display: none; }
  .keepers__cell { text-align: center; padding: 12px 4px; white-space: normal; }
  .keepers thead th { padding: 8px 4px; font-size: 10px; }
  .keepers tbody th { font-size: var(--fs-cap); }
  .keepers__none { font-size: 9px; }
  .history__rec { margin-left: 0; flex-basis: 100%; }
}

/* ---------- sign-in --------------------------------------------------------- */
.signin { max-width: 34ch; margin-top: var(--section-gap); }
.signin__error {
  color: var(--sash-ink); font-weight: 600; font-size: var(--fs-body);
  margin: 0 0 var(--pad);
}
.field + .field { margin-top: var(--pad-tight); }
.field__label {
  display: block; margin-bottom: 4px;
  font-family: var(--font-plate); font-size: var(--fs-cap); font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--t3);
}
/* Controls get their OWN tokens so they can be tuned without moving every inset
   surface, and an input is inset - it receives content. */
.field__input {
  width: 100%; min-height: 44px; padding: 0 var(--pad-tight);
  background: var(--control); color: var(--t1);
  border: 1px solid var(--control-edge); border-radius: var(--r-ctl);
  font: var(--fs-body) var(--font-body);
}
.control--submit {
  margin-top: var(--pad); border: 0;
  background: var(--plate); color: var(--on-plate); cursor: pointer;
}
.control--submit:hover { background: var(--navy-900); filter: brightness(1.15); }
