:root {
  --pitch: #101E19;
  --surface: #17281F;
  --raised: #1E3329;
  --line: #2C4A3B;
  --chalk: #E9F0E9;
  --muted: #8CA396;
  /* Interactive text at rest — nav links, the login/register tabs, sign out.
     --muted is fine for static secondary text you read once, but it lands at
     ~5.7:1 on --surface, and an unselected tab rendered in it looks disabled
     rather than clickable. This clears 8.7:1 on both backgrounds while still
     reading as clearly secondary to --chalk. */
  --link: #B4C7BB;
  --live: #F0B429;
  /* Survived. Referenced by .prov-note.ok, .fix.mine.ok, .yours.good, .pick.ok and
     .strip .s.safe, all of which were silently falling back before it was
     defined — an undefined custom property is invalid at computed-value time,
     so the box-shadow dropped entirely and the colours inherited.
     Desaturated to sit in the same family as the greens it is painted on
     rather than competing with --live, and clears 7:1 on --surface. */
  --good: #56C288;
  --dead: #5C6E68;
  --gone: #C0433A;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--pitch);
  color: var(--chalk);
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

h1, h2, h3, .disp {
  font-family: 'Oswald', 'Inter', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 500;
  margin: 0;
}

.mono { font-family: 'IBM Plex Mono', monospace; }

.booting { padding: 40px; color: var(--muted); }

/* ---- shell ---- */

.shell { min-height: 100vh; display: flex; flex-direction: column; }

/* Grid with named areas rather than a wrapping flex row. The three things here
   have different priorities and a wrap cannot express that: it just pushes
   whatever ran out of room onto the next line, which put an email address on
   its own row on a phone. */
.topbar {
  display: grid; gap: 6px 14px;
  grid-template-columns: 1fr auto auto;
  grid-template-areas: "title meta acct";
  /* Centred, not baseline. These three are 24px, 12px and a 34px circle, and
     sitting them on a shared text baseline hangs the title above everything
     else — the big type extends upward from the line while the small type and
     the bubble sit on it, which reads as the title being misaligned rather
     than as three things on a row. */
  align-items: center;
  /* Aligned with main's column rather than the window edge. main is capped at
     1100px and centred, so on a wide monitor a full-bleed header put the title
     hard against the left edge while the board below it started 400px in, and
     nothing lined up vertically. The max() means nothing changes below 1100px:
     the calc goes negative and 20px wins. The border stays full width, which is
     the point of doing it with padding rather than a wrapper. */
  padding-block: 16px;
  padding-inline: max(20px, calc((100% - 1100px) / 2 + 20px));
  border-bottom: 1px solid var(--line);
}
.topbar-title { grid-area: title; }
.topbar-meta  { grid-area: meta; }
.topbar-acct  { grid-area: acct; position: relative; justify-self: end; }
.topbar h1 { font-size: 24px; }
.topbar .meta {
  font-size: 12px; color: var(--muted);
  letter-spacing: 0.14em; text-transform: uppercase;
}
.topbar .meta b { color: var(--live); font-family: 'IBM Plex Mono', monospace; font-weight: 500; }

/* Title and account on one row, the pot on its own beneath it. The pot is the
   thing worth reading at a glance, so it gets the full width rather than being
   squeezed into whatever the other two leave.

   480 rather than the 620 this first used. One row costs roughly 160px of
   title, 215px of pot at its longest ("POT $1,200 · 12 STILL IN"), a 34px
   bubble and two gaps — about 480px including the padding. Breaking at 620
   split the header on plenty of windows that had room for it, which reads as a
   layout bug rather than a layout choice. */
@media (max-width: 480px) {
  .topbar {
    gap: 10px;
    /* padding-block only. The shorthand would reset padding-inline and undo the
       column alignment above, which below 1100px resolves to the same 20px that
       main uses. */
    padding-block: 13px;
  }
  /* Larger than it was, not smaller. Stacking the meta gave back enough width
     to grow the title rather than shrink it, and the title is the thing worth
     reading first.

     Fluid rather than fixed so it never wraps. At 22px the row needs about
     284px of the 280px a 320px screen leaves, so the title broke to two lines
     there — the layout held, it just went taller and untidy. The clamp holds
     22px at 360px and above, which is every current phone and the width this
     was designed against, and eases to ~19.5px by 320px, where it fits. No
     second breakpoint and no step change on the way down. */
  .topbar h1 { font-size: clamp(19px, 6.1vw, 22px); }

  /* The pot and the survivor count stack into a block beside the bubble instead
     of taking a row of their own. As a run of text they are ~190px wide; broken
     over two lines, ~85px — which is the whole difference between fitting on
     one row and not.

     Budget at 360px, the narrowest phone worth designing for: 328px usable
     against roughly 145px of title, 85px of meta, a 34px bubble and two 10px
     gaps. About 285px, so a longer pot still fits — "POT $1,200" over
     "12 STILL IN" has room. */
  .topbar-meta {
    display: flex; flex-direction: column; align-items: flex-end;
    gap: 1px; line-height: 1.3; text-align: right; letter-spacing: 0.1em;
  }
  /* Only earns its place when the two are side by side. */
  .meta-sep { display: none; }
}

/* ---- the account bubble ----
   An email address is long, never changes, and is not what anyone came to read
   — but you do occasionally need to check which account you are in, so it
   collapses to initials and stays one tap away rather than being dropped. */
.acct-btn {
  width: 34px; height: 34px; padding: 0; border-radius: 50%;
  background: var(--raised); color: var(--chalk); border: 1px solid var(--line);
  font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: 0.04em;
  cursor: pointer; display: grid; place-items: center;
}
.acct-btn:hover, .acct-btn.on { border-color: var(--live); color: var(--live); }

/* Covers the viewport so the next click anywhere closes the menu, which is
   what a dropdown is expected to do — and does it without an interop listener
   on document that would have to be torn down again. */
.acct-backdrop { position: fixed; inset: 0; z-index: 20; }

.acct-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 21;
  min-width: 210px; max-width: min(280px, calc(100vw - 32px));
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .45);
}
.acct-who {
  padding: 11px 13px; border-bottom: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 3px;
}
.acct-who b { font-size: 13px; }
.acct-who span {
  font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--muted);
  overflow-wrap: anywhere;
}
/* Both a button (sign out, an action) and a link (change password, a place).
   They should be indistinguishable in the menu, so the anchor needs its
   underline removing and the button its browser defaults. */
.acct-item {
  display: block; width: 100%; text-align: left;
  background: none; border: none; cursor: pointer;
  padding: 11px 13px; color: var(--link); text-decoration: none;
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.1em; font-size: 12px;
}
.acct-item + .acct-item { border-top: 1px solid var(--line); }
.acct-item:hover { background: var(--raised); color: var(--chalk); }

/* A button, because signing out is an action, styled flat so it does not
   compete with the pot for attention. */
.linkish {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--link); font-size: 12px;
  letter-spacing: 0.14em; text-transform: uppercase;
  font-family: 'Oswald', sans-serif;
}
.linkish:hover { color: var(--chalk); }

.act-link {
  color: var(--live); text-decoration: none; white-space: nowrap;
  border: 1px solid var(--live); padding: 6px 12px;
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.09em; font-size: 12px;
}
.act-link:hover { background: var(--live); color: #10211A; }

/* Same column as the topbar and main. The first link's own padding is part of
   the alignment, so the inline padding is 2px short of the topbar's 20px —
   otherwise BOARD would sit 18px right of SURVIVOR POOL rather than under it. */
.nav {
  display: flex; border-bottom: 1px solid var(--line); overflow-x: auto;
  padding-inline: max(2px, calc((100% - 1100px) / 2 + 2px));
}
.nav a {
  padding: 13px 18px; color: var(--link); text-decoration: none;
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.1em; font-size: 13px; white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.nav a.active { color: var(--chalk); border-bottom-color: var(--live); }
.nav a:hover { color: var(--chalk); }

/* 1100 rather than 1000 so the picks page fits its rail plus two columns of
   fixtures. Below ~660px of card the fixture grid drops to one column, and at
   1000px it landed just the wrong side of that. */
main { padding: 20px; max-width: 1100px; margin: 0 auto; width: 100%; }

/* ---- pieces ---- */

.card {
  background: var(--surface); border: 1px solid var(--line);
  padding: 16px; margin-bottom: 14px;
}
.card > h3 { font-size: 14px; color: var(--muted); margin-bottom: 12px; }

.banner {
  border-left: 3px solid var(--live); background: var(--raised);
  padding: 13px 15px; margin-bottom: 14px;
}
.banner.warn { border-left-color: var(--gone); }
.banner h3 { font-size: 14px; margin-bottom: 5px; }
.banner p { margin: 0; font-size: 13px; color: var(--muted); }

.empty { color: var(--muted); font-size: 13px; }

select, input, button {
  background: var(--pitch); color: var(--chalk); border: 1px solid var(--line);
  padding: 8px 10px; font-family: 'Inter', sans-serif; font-size: 14px;
  border-radius: 0;
}
select:disabled { color: var(--dead); border-color: var(--line); }

/* The open dropdown is painted by the platform from the select's own colours,
   and it does not inherit them from an ancestor — so without this the option
   list comes out with default (light) assumptions over a dark page and is
   barely readable. Applies to every select in the app, not just the one that
   showed the problem. */
select option { background: var(--pitch); color: var(--chalk); }
select option:disabled { color: var(--dead); }
button.act {
  background: var(--live); color: #10211A; border: none; cursor: pointer;
  font-family: 'Oswald', sans-serif; text-transform: uppercase; letter-spacing: 0.09em;
}
/* Outlined rather than filled, because this one repeats. There is a solid amber
   .act per page — save, sign in, grant — and it can afford to shout. This sits
   once per fixture, so sixteen of them filled would turn a Sunday's fixture list
   into a wall of amber and drown the one control that actually commits. Quiet at
   rest, solid on hover, same colour either way. */
button.act.mini {
  background: none; color: var(--live); border: 1px solid var(--live);
  font-size: 11px; padding: 5px 9px; letter-spacing: 0.08em; line-height: 1.3;
  white-space: nowrap;
}
button.act.mini:hover { background: var(--live); color: #10211A; }
select:focus-visible, input:focus-visible, button:focus-visible {
  outline: 2px solid var(--live); outline-offset: 1px;
}

/* FocusOnNavigate puts tabindex="-1" on the page's h1 after each navigation so
   a screen reader announces where it landed. That is worth doing. Drawing a box
   round the heading is not: it is not interactive, there is nothing to act on,
   and three of these pages have a visible h1 — so the browser's default ring
   appears round COMMISSIONER or RULES on arrival and reads as a rendering
   fault. Suppressed only for this case; every actual control keeps its ring
   above. */
main h1:focus { outline: none; }

/* ---- log in / register ---- */

.auth { max-width: 380px; }
.auth .tabs { display: flex; gap: 0; margin-bottom: 16px; border-bottom: 1px solid var(--line); }
.auth .tabs button {
  background: none; border: none; border-bottom: 2px solid transparent;
  padding: 9px 14px; cursor: pointer; color: var(--link);
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.1em; font-size: 13px;
}
.auth .tabs button.on { color: var(--chalk); border-bottom-color: var(--live); }
.auth .tabs button:hover { color: var(--chalk); }

.auth label {
  display: block; margin-bottom: 13px;
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); font-family: 'Oswald', sans-serif;
}
.auth label input { display: block; width: 100%; margin-top: 5px; }
.auth .hint {
  display: block; margin-top: 4px; font-size: 11px;
  letter-spacing: 0; text-transform: none; font-family: 'Inter', sans-serif;
}
.auth button[type="submit"] { width: 100%; padding: 10px; cursor: pointer; }

.error { color: var(--gone); font-size: 13px; margin: 0 0 12px; }
.notice { color: var(--live); font-size: 13px; margin: 0 0 12px; }

/* ---- my picks: rail beside the round's fixtures ---- */

/* Present for screen readers and for the focus target App.razor looks for,
   taken out of the visual flow entirely. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* With the heading hidden this row is just the round selector and its status,
   which fit side by side at any width — so there is nothing here that needs to
   wrap or stack. */
.head { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
        margin-bottom: 16px; }
.head-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.split { display: grid; gap: 14px; grid-template-columns: 300px 1fr; align-items: start; }
.rail { position: sticky; top: 14px; }
/* Two columns stop fitting here. Below it they stack, which is why the fixture
   layout is driven by its container rather than the viewport. */
@media (max-width: 760px) {
  .split { grid-template-columns: 1fr; }
  .rail { position: static; }
}

/* A different round has been asked for and has not arrived. What is on screen
   is still the previous round, correct and complete — the swap is atomic, so
   this never covers a half-updated page. It says the round is on its way out
   and takes it out of reach, so no control can be worked against a round you
   have already left.

   Dimmed rather than replaced by a spinner: the list keeps its height, so
   nothing below it moves and the page does not collapse and re-expand. */
.switching { opacity: .45; pointer-events: none; transition: opacity .12s ease-out; }
@media (prefers-reduced-motion: reduce) { .switching { transition: none } }

.chip { font-family: 'Oswald', sans-serif; font-size: 10.5px; letter-spacing: 0.12em;
        text-transform: uppercase; padding: 3px 8px; border: 1px solid var(--line);
        color: var(--muted); white-space: nowrap; }
/* The roster filter. Buttons carrying the same chip look as everything else,
   so a control that selects reads the same as a label that reports — which is
   fine here, because the count on each IS the report. */
.rostfilter { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 6px; }
button.chip { background: none; cursor: pointer; }
button.chip:hover { border-color: var(--link); color: var(--link); }
.chip.on, button.chip.on:hover { border-color: var(--live); color: var(--live); }
.chip b { color: var(--chalk); font-weight: 400; margin-left: 3px; }
.chip.on b { color: var(--live); }

.chip.open { border-color: var(--live); color: var(--live); }
.chip.closed { border-color: var(--dead); color: var(--dead); }
.chip.def { border-color: var(--live); color: var(--live); }

.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--live);
       display: inline-block; margin-right: 5px; vertical-align: middle;
       animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }
@media (prefers-reduced-motion: reduce) { .dot { animation: none } }

/* The picks, pinned, on narrow screens only. This REPLACES the rail below this
   width rather than echoing it — see .rail-card — so there is one control, not
   a duplicate and a read-only copy of it. Hidden by default rather than
   shown-then-hidden, so it never flashes on a desktop first paint. */
.strip { display: none; }
@media (max-width: 760px) {
  .strip {
    /* Wraps rather than scrolling sideways. As one scrolling row, a hand of six
       put picks 4 onward off the right edge of a phone — and a strip whose whole
       job is showing the hand at a glance cannot be one you have to swipe
       through. auto-fit gives two columns on a narrow phone and three on a wide
       one without naming either. */
    display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 8px; padding: 10px 12px;
    background: var(--surface); border: 1px solid var(--line);
    margin-bottom: 14px; position: sticky; top: 0; z-index: 5;
  }
  .rail-card { display: none; }
}
.strip .s {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  border: 1px solid var(--line); padding: 4px 8px;
  font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--chalk);
  white-space: nowrap;
}
/* The number holds its width; the team name is what gives way when the cell is
   too narrow for it. */
.strip .s .n { opacity: .7; flex: 0 0 auto; }
.strip .s .v { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* A native select opens the platform picker on tap, which is the right control
   on a phone — no bespoke sheet needed. Background is set rather than left
   transparent: the dropdown that opens is painted from it, and transparent
   makes that list unreadable. */
.strip .s select {
  background: var(--pitch); color: var(--chalk); border: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 11.5px;
  /* Takes the rest of the cell instead of a fixed cap: the cell is now a grid
     track, so the width it may use is already decided for it. min-width:0
     is what lets it shrink below its content — a flex item will not. */
  padding: 2px 4px; flex: 1 1 auto; min-width: 0;
}
.strip .s select:focus-visible { outline: 2px solid var(--live); outline-offset: 2px; }
.strip .s.live { border-color: var(--live); color: var(--live); }
.strip .s.safe { border-color: var(--good); color: var(--good); }
.strip .s.risk { border-color: var(--gone); color: var(--gone); }
/* Same rule as the rail: dashed until the commissioner has confirmed it, and
   drawn as an outline further down so it does not shift the chip. */
/* Matches .pick.spent .pick-no above — the same dead pick, so the same colour
   whichever width you are on. */
.strip .s.spent { border-color: var(--gone); color: var(--gone); text-decoration: line-through; }

.pick { display: grid; grid-template-columns: 32px 1fr; gap: 11px;
        align-items: start; padding: 11px 0; border-bottom: 1px solid var(--line);
        /* Shared by the number and the select below, so their centres line up
           exactly rather than approximately. */
        --control: 36px; }
.pick:last-of-type { border-bottom: none; }
.pick-no { font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--live);
           border: 1px solid var(--live); text-align: center; padding: 3px 0; line-height: 1.3; }
/* Red, not grey. A pick does not quietly run out — it is lost to a named team
   in a named week, which is what .pick-why beside it spells out. Grey read as
   "used up"; --gone matches the row that spent it. */
.pick.spent .pick-no { color: var(--gone); border-color: var(--gone); text-decoration: line-through; }
.pick.no .pick-no { color: var(--gone); border-color: var(--gone); }
.pick.ok .pick-no { color: var(--good); border-color: var(--good); }
/* Dashed until a result is confirmed, exactly as the reads beside it are. A
   solid red number means the pick is gone; a dashed one means it is losing.
   Drawn as an outline further down, so the two states are the same size. */
/* Centred on the CONTROL, not on the row. The body carries the lock time under
   the select, so aligning to the row's start left the number sitting high above
   the box it labels. Only where there IS a select: a spent or locked pick is
   plain text, and its number belongs at the top of it. */
.pick:has(.pick-body select) .pick-no {
  height: var(--control); padding: 0; display: grid; place-items: center;
}
.pick-body select { width: 100%; margin-bottom: 6px; height: var(--control); }
.pick-why { font-size: 12.5px; color: var(--muted); }
.pick-why b { color: var(--chalk); font-weight: 500; }
.pick-lock { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--muted); }

/* The "would survive · 14–10" label in the rail.
   Named prov-note, NOT prov. `prov` is a MODIFIER — it appears alongside .pip,
   .yours, .auto, .pick and .strip .s to mean "not settled yet" — and while this
   rule owned the bare name, every one of those quietly inherited a label's
   padding, font and 4px top margin. That margin against align-items:center is
   what knocked the pick chips a couple of pixels out of line with their
   neighbours, and it had been doing it to the fixture markers all along. */
.prov-note { display: inline-block; margin-top: 4px; font-size: 11px;
             font-family: 'Oswald', sans-serif; letter-spacing: 0.1em;
             text-transform: uppercase;
             border: 1px dashed var(--gone); color: var(--gone); padding: 2px 7px; }
.prov-note.ok { border-color: var(--good); color: var(--good); }

.gate { text-align: center; padding: 26px 16px; }
.gate .big { font-family: 'Oswald', sans-serif; text-transform: uppercase;
             letter-spacing: 0.08em; font-size: 15px; color: var(--live); margin-bottom: 8px; }
.gate p { color: var(--muted); font-size: 13px; margin: 0 auto; max-width: 34ch; }

/* ---- fixtures ---- */

/* As many columns as fit — two in a desktop card, one on a phone — with no
   breakpoint to keep in step. */
.fixlist { display: grid; grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
           column-gap: 40px; }
.fixlist .day { grid-column: 1 / -1; }

.day { font-family: 'Oswald', sans-serif; text-transform: uppercase; letter-spacing: 0.12em;
       font-size: 11.5px; color: var(--muted); margin: 16px 0 6px;
       padding-bottom: 5px; border-bottom: 1px solid var(--line); }
.day:first-of-type { margin-top: 0; }

/* Four fixed columns: when, who, the line, your pick. Fixed rather than auto so
   the lines align down the page and an unpicked fixture leaves an aligned gap. */
/* minmax(0, 1fr), NOT 1fr. A bare 1fr is minmax(auto, 1fr), and that auto
   minimum means content can push the track wider than its share — which is
   what happened the moment an "auto" tag joined a long team name: the line and
   the scores slid right, and the columns stopped agreeing down the page. With a
   zero minimum the track holds its width and the content fits itself in. */
/* 38px holds four monospace characters on a line, which is narrower than most
   of what goes in it — "8:15 PM" has always wrapped to "8:15" over "PM". Two
   short lines is therefore the normal look of this column, not a failure mode,
   and the live clock is shortened to match it rather than the column being
   widened to take the clock at full length. See KickoffLabel. */
.fix { display: grid; grid-template-columns: 38px minmax(0, 1fr) 66px 34px;
       gap: 14px; align-items: center; padding: 9px 14px 9px 11px;
       border-bottom: 1px solid rgba(44, 74, 59, .5); }

/* A fixture you hold a pick on claims its whole row. Two columns put your
   marker beside the next fixture's kickoff, so proximity alone cannot say
   which row it belongs to. */
.fix.mine { background: var(--raised); box-shadow: inset 3px 0 0 var(--live); }
.fix.mine.ok { box-shadow: inset 3px 0 0 var(--good); }
.fix.mine.no { box-shadow: inset 3px 0 0 var(--gone); }

/* Deliberately NOT white-space: pre-line. The live clock stacks structurally,
   via display:block on .ft-clock, rather than on a newline character inside a
   string — with pre-line the newlines Razor emits between elements would become
   line breaks too, and the layout would depend on how the markup is indented.

   overflow-wrap is the safety net. LiveClock shortens every state ESPN is known
   to send, but an unrecognised one passes through whole, and a long unbroken
   word would otherwise spill across the team names. Breaking mid-word is ugly;
   overlapping the next column is broken. */
/* Centred, because this column is two lines more often than it is one and the
   two never match in width. The live dot indents the first line by 11px of a
   38px track, so flush-left left "3RD" a third of the column right of the
   "10:27" under it — near enough to alignment to read as a mistake. Centring
   makes the two lines sit on each other instead. It applies to the kickoff
   times as well, which stack the same way ("10:00" over "PM") and have the same
   mismatch. */
.fix-time { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--muted);
            line-height: 1.35; overflow-wrap: anywhere; text-align: center; }

/* The live state as a label over its value, which is the pairing used for every
   other piece of context on the site — see .day, .chip and .jrc-week. Stacked in
   one face they read as two loose strings; set as a label and a value they read
   as one fact with a detail under it.

   The period carries the pulse that used to belong to a separate dot beside it.
   Same signal and the same --live amber, but in space the label was already
   occupying rather than 11px stolen from a 38px column — which is what stopped
   the two lines from ever lining up. The clock is the block, so IT starts the
   second line, and sits in the brighter ink because it is the part that moves.

   Reusing the .dot keyframes deliberately: one definition of what "live" looks
   like on this site, so the picks page and the commissioner's list cannot drift
   into pulsing at different rates. */
/* --live, and not a colour of its own, which was tried and rejected. The token
   is called live because it was made for the pulsing dot that marked a game in
   progress — in this very cell. A quarter and a clock are that same fact said
   more precisely, so this is the most literal use of it in the stylesheet, not
   an overload of it.
   The worry was that amber also marks what is YOURS, and would fight the row on
   a fixture your pick is losing. In practice the gutter is its own column with
   nothing else in it, so position separates the two before colour is asked to. */
.ft-period { font-family: 'Oswald', sans-serif; text-transform: uppercase;
             letter-spacing: 0.1em; font-size: 10px; color: var(--live);
             animation: pulse 1.6s ease-in-out infinite; }
.ft-clock { display: block; font-variant-numeric: tabular-nums; color: var(--chalk); }
@media (prefers-reduced-motion: reduce) { .ft-period { animation: none } }
.fix-teams { display: flex; flex-direction: column; align-items: stretch; gap: 1px; }
/* Away over home, as on any scoreboard: names align down one column and scores
   down another, so the eye runs a column instead of hunting a different x. */
/* The transparent border is reserved space, not decoration: the picked side
   turns it amber, and without it here the name would jump 10px sideways the
   moment you picked.
   The name takes the slack rather than the row being spaced apart, so the score
   sits at the right edge whether or not a tag has joined the line — with
   space-between, a third child moved the score every time. */
.side { display: flex; align-items: baseline; gap: 10px; min-width: 0;
        padding-left: 8px; border-left: 2px solid transparent; }
.side.on { border-left-color: var(--live); }
/* Absorbs the row's spare width, and gives way first when there is none —
   truncating a long name is a smaller loss than pushing the line and the score
   out of alignment with every other fixture. */
.fix .team { font-family: 'Oswald', sans-serif; text-transform: uppercase;
             letter-spacing: 0.04em; font-size: 13.5px;
             flex: 1 1 auto; min-width: 0;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fix .team.lost { color: var(--dead); }
.fix .score { flex: 0 0 auto; }
.fix .score { font-family: 'IBM Plex Mono', monospace; font-size: 13px;
              font-variant-numeric: tabular-nums; }
.fix .score.lost { color: var(--dead); }
.fix-teams .chip { align-self: flex-start; margin-top: 4px; font-size: 9.5px; }

/* The abbreviation takes a set width and right-aligns in it, so every minus
   sign starts at the same x whether the team is GB or WSH. */
.line { font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--muted);
        font-variant-numeric: tabular-nums; white-space: nowrap;
        display: flex; align-items: baseline; gap: 5px; padding-left: 10px; }
.line b { flex: 0 0 26px; text-align: right; color: var(--link); font-weight: 400; }
.line.pk { color: var(--dead); }

/* Stacked, not in a row. Several picks can land on one fixture — hedging both
   sides, or doubling up on the same team — and the column is only wide enough
   for one marker, so a row would push the second past the card's edge.
   Sized so two markers fit inside the height the two team rows already take,
   which keeps the row from growing and the two-up columns even. */
.fix-right { display: flex; flex-direction: column; align-items: flex-start;
             gap: 3px; justify-content: center; }
.yours { font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; line-height: 1.25;
         border: 1px solid var(--live); color: var(--live); padding: 1px 6px; }
.yours.good { border-color: var(--good); color: var(--good); }
.yours.bad { border-color: var(--gone); color: var(--gone); }
.yours.prov { border-style: dashed; }

/* How the round scores, stated where you are about to act on it and gone by
   the second screen. Deliberately NOT in the rail: pinning text you read once
   spends the one element that stays on screen on something that stops being
   useful immediately. Full width of the card — it is a two-line label, not
   running prose, and a reading measure just left a hole beside it. */
.roundrule { color: var(--muted); font-size: 12.5px; margin: -2px 0 14px; }

/* ---- playoff rounds ----
   A playoff round differs from a regular week in one way: the choice belongs to
   the entrant and is made per FIXTURE rather than per pick. That changes what
   the controls are, NOT where they sit — the grid, the breakpoints and the
   two-up fixture list are all untouched below, and the row simply gets taller
   where it carries a picker.

   An earlier version gave the picker a fifth column. That made the row 116px
   wider than a regular week's, which then would not fit beside the rail, and
   every breakpoint after it was compensation — the layout flipped between two
   shapes three times on the way from a phone to a desktop. Nothing here sets a
   width. */

/* Counting, not narrating. Same metrics as .pick-no, so a numbered pick looks
   the same wherever it appears. Display is deliberately NOT set here: .strip
   owns whether the strip is visible, and setting it on .pips overrode that at
   equal specificity and showed the strip and the rail card at once. */
.pips { flex-wrap: wrap; gap: 6px; align-items: center; }
.pip { width: 32px; height: 32px; display: grid; place-items: center;
       font-family: 'IBM Plex Mono', monospace; font-size: 12px; line-height: 1;
       border: 1px solid var(--live); color: var(--live);
       box-sizing: border-box; flex: 0 0 auto; }
.pip.spent { border-color: var(--gone); color: var(--gone); text-decoration: line-through; }
/* Losing, but nothing is decided — dashed, like every other provisional read.
   Green is held back until the whole round is in, because in a playoff round a
   pick that is safe at 4:40 can still be taken by the 8:15. */
.pip.no { border-color: var(--gone); color: var(--gone); }
.pip.ok { border-color: var(--good); color: var(--good); }

/* Dashed everywhere means provisional. One property, and it changes nothing
   about the box — a 1px border is a 1px border whatever its style. */
.pip.prov, .pick.prov .pick-no, .strip .s.prov { border-style: dashed; }

/* The rail panel is a row of pips rather than a stack of per-pick controls. */
.rail-card .pips { display: flex; }
/* And so is the strip, at the width the strip takes over. */
@media (max-width: 760px) { .strip.pips { display: flex; } }

/* ---- your team, in BOTH round types ----
   The rail does the choosing in a regular week and the buttons below do it in a
   playoff round, but the marking is identical, so a pick looks like a pick from
   September to January. Eighteen weeks of the regular season is the training;
   the playoffs should not be a new thing to learn. */
.side.on .team { color: var(--live); }
/* Still your pick, so still amber — but the team is behind or beaten, so it
   dims to sit with the greyed score next to it. Deliberately NOT red: red is
   already the outcome, said twice on that row by the edge stripe and the chip,
   and in a playoff round it also means "you have not picked this yet". */
.side.on.lostside .team { color: var(--live); opacity: .6; }

/* ---- playoff only ---- */

/* Nothing chosen and the fixture is still open. Every game in a playoff round
   has to be picked, so an unpicked one is unfinished business rather than a
   neutral state — which the board said nothing about before. Only ever on an
   OPEN fixture, so it is never in the same row as the red of a lost pick. */
.side.todo .team { color: var(--gone); }
.side.todo .mini { border-color: var(--gone); color: var(--gone); }
.side.todo .mini:hover { border-color: var(--live); color: var(--live); }

/* A team you were given rather than chose. Still amber, because it is still the
   team you are riding on and the bar beside it says so — muting it made the row
   disagree with itself. Italic and the tag carry "you did not choose this". */
.side.given .team { font-style: italic; }

/* Where the score will be. A fixture that is still open cannot have a score —
   a score means it kicked off, which means it locked — so the control borrows
   that slot and hands it back at kickoff, and the row never grows.
   The chosen side shows no button at all: the bar and the amber name already
   say it, and a wider "Picked" label shoved the longest names sideways to
   repeat them. */
.mini { font-family: 'Oswald', sans-serif; font-size: 9px; letter-spacing: 0.08em;
        text-transform: uppercase; background: none; cursor: pointer;
        border: 1px solid var(--line); color: var(--link); padding: 1px 6px;
        white-space: nowrap; flex: 0 0 auto; }
.mini:hover { border-color: var(--live); color: var(--live); }
.mini:focus-visible { outline: 2px solid var(--live); outline-offset: 2px; }

/* Not your choice. Dashed while it is still only a forecast — the round has not
   been recomputed, so nothing has been assigned yet — and solid once the engine
   has actually written it.
   Sized to the 34px marker column it now sits in, alongside the pick chip: this
   is information about the PICK, which is what that column is for, and on the
   scoreboard line it was taking width from the team name. */
/* --link, not --dead. This marks the one fixture in the round you did NOT
   choose, which is the last thing that should be hard to spot — and --dead is
   2.86:1 on this surface, which is why it was disappearing. --link clears
   8.7:1 and is the app's colour for text that is secondary but not inert. */
.auto { font-family: 'Oswald', sans-serif; font-size: 8.5px; letter-spacing: 0.04em;
        text-transform: uppercase; border: 1px solid var(--link); color: var(--link);
        padding: 0 3px; font-style: normal; line-height: 1.5; }
.auto.prov { border-style: dashed; }

/* ---- the commissioner's roster ----
   Everyone who could hold a pick, including the people holding none — which is
   who you are looking for when somebody has just paid in. */
.roster { display: flex; flex-direction: column; }
/* The roster heading and its season picker on one line. Baseline rather than
   centre, so the year in the heading and the year in the select sit on the same
   line of text — they are the same fact said twice, and should look it. */
.rosthead { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }

.roster-row { display: grid; grid-template-columns: 1fr auto auto; gap: 14px;
              align-items: center; padding: 9px 0;
              border-bottom: 1px solid rgba(44, 74, 59, .5); }
.roster-row:last-child { border-bottom: none; }
.roster-name { font-family: 'Oswald', sans-serif; text-transform: uppercase;
               letter-spacing: 0.04em; font-size: 14px;
               display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.roster-pips { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; }
/* A pick that has been used is not removable, so it reads differently from one
   still in hand — the minus button going dead needs a reason on screen. */
.roster-pips .pip.used { border-color: var(--line); color: var(--dead); }
.roster-act { display: flex; gap: 6px; }
.roster-act button { border: 1px solid var(--line); padding: 2px 9px; font-size: 15px;
                     line-height: 1.2; color: var(--link); }
.roster-act button:hover:not(:disabled) { border-color: var(--live); color: var(--live); }
.roster-act button:disabled { color: var(--dead); cursor: not-allowed; }

/* What the feed last reported, on the Commissioner page, beside the result the
   commissioner is about to confirm. Deliberately quiet: it is evidence, not the
   decision, and the two must not be mistaken for each other. */
.feed { display: flex; align-items: baseline; gap: 8px; margin-top: 3px;
        font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--muted); }
.feed .st { color: var(--link); }
.feed .sc { color: var(--chalk); font-variant-numeric: tabular-nums; }
.disclaimer { display: flex; align-items: flex-start; gap: 9px;
              border-top: 1px solid var(--line); margin-top: 14px; padding-top: 12px;
              color: var(--muted); font-size: 12px; }
.disclaimer .mark { font-family: 'IBM Plex Mono', monospace; font-size: 11px;
                    color: var(--live); border: 1px solid var(--live);
                    padding: 1px 6px; flex: 0 0 auto; }

/* ---- jobs: is the machinery alive ----
   Prefixed, like the board. */
.jobstat { display: flex; flex-direction: column; }
/* minmax on the note, which grew when it started carrying the expected cadence
   as well as the problem — "nothing for over 3h, expected every 60m" is a lot
   to ask of a track that cannot shrink below its content. */
.jobstat-row { display: grid; grid-template-columns: 110px 90px minmax(0, 1fr); gap: 14px;
               align-items: baseline; padding: 8px 0 8px 10px;
               border-bottom: 1px solid rgba(44, 74, 59, .5);
               border-left: 3px solid var(--line); }
.jobstat-row:last-child { border-bottom: none; }
.jobstat-row.ok { border-left-color: var(--good); }
.jobstat-row.stale { border-left-color: var(--live); }
.jobstat-row.bad, .jobstat-row.never { border-left-color: var(--gone); }
.jobstat-name { font-family: 'Oswald', sans-serif; text-transform: uppercase;
                letter-spacing: 0.06em; font-size: 13px; }
.jobstat-when { font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--chalk);
                font-variant-numeric: tabular-nums; }
.jobstat-row.stale .jobstat-when { color: var(--live); }
.jobstat-row.bad .jobstat-when, .jobstat-row.never .jobstat-when { color: var(--gone); }
.jobstat-note { font-size: 12.5px; color: var(--muted); }

/* minmax(0, …) on the flexible track, not a bare 1fr: a bare fr has an automatic
   minimum of its content, so a long detail blob — {"season": 2025, "sourceYear":
   2026} — widens the track rather than wrapping in it, and takes the page
   sideways with it. */
.jr { display: grid; grid-template-columns: 168px 88px 56px 56px minmax(0, 1fr); gap: 14px;
      align-items: baseline; padding: 7px 0 7px 10px;
      border-bottom: 1px solid rgba(44, 74, 59, .5);
      border-left: 3px solid transparent; font-size: 12.5px; }
.jr.failed { border-left-color: var(--gone); background: var(--raised); }
.jr.stranded { border-left-color: var(--live); background: var(--raised); }
.jr.head { padding-bottom: 7px; border-bottom: 1px solid var(--line);
           font-family: 'Oswald', sans-serif; font-size: 10px; letter-spacing: 0.12em;
           text-transform: uppercase; color: var(--dead); }
.jr-when, .jr-took, .jr-changed { font-family: 'IBM Plex Mono', monospace;
                                  font-variant-numeric: tabular-nums; color: var(--muted); }
.jr-changed { text-align: right; }
.jr-job { font-family: 'Oswald', sans-serif; text-transform: uppercase;
          letter-spacing: 0.06em; font-size: 12px; }
.jr-out { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.jr-err { color: var(--gone); }
.jr-detail { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--dead); }
.tag.ok { border-color: var(--good); color: var(--good); }
.tag.bad { border-color: var(--gone); color: var(--gone); }
.tag.warn { border-color: var(--live); color: var(--live); }

@media (max-width: 720px) {
  .jr { grid-template-columns: minmax(0, 1fr) auto auto;
        grid-template-areas: "when job took" "out out changed"; }
  .jr-when { grid-area: when; } .jr-job { grid-area: job; } .jr-took { grid-area: took; }
  /* The count is the way in to what a run changed, and it used to be hidden
     here — which quietly made the change log unreachable on a phone, on the
     page whose whole purpose is checking on things while away from a desk.
     It moves to the end of the second row rather than being dropped. */
  .jr-changed { grid-area: changed; text-align: right; align-self: baseline; }
  .jr-out { grid-area: out; }
  .jr.head { display: none; }
}

/* The count is the way in to what a run did, so it looks like a control rather
   than a figure — but only where there is something behind it. */
.jr-open { background: none; border: none; cursor: pointer; padding: 0;
           font-family: 'IBM Plex Mono', monospace; font-size: 12.5px;
           color: var(--link); font-variant-numeric: tabular-nums; }
.jr-open:hover { color: var(--live); }
.jr-open:focus-visible { outline: 2px solid var(--live); outline-offset: 2px; }

/* What one run changed, indented under it. */
.jrc { background: var(--surface); border-left: 3px solid var(--line);
       margin: 0 0 4px 10px; padding: 8px 12px; }
/* Same minmax(0, …) reasoning as .jr — a line like "Commanders -2.5" is short,
   but nothing here guarantees a value is, and a bare fr would widen rather than
   wrap. */
.jrc-row { display: grid;
           grid-template-columns: 84px 190px 84px minmax(0, 1fr) 14px minmax(0, 1fr);
           gap: 10px; align-items: baseline; padding: 3px 0; font-size: 12px; }
/* The round. Muted, because it is context rather than news: the eye should land
   on what changed and use this to place it. */
.jrc-week { font-family: 'Oswald', sans-serif; text-transform: uppercase;
            letter-spacing: 0.06em; font-size: 11px; color: var(--muted);
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Vanishes from layout, so was/arrow/became remain grid items of .jrc-row and
   keep their own columns — the arrows line up down the list. It becomes a real
   box only on narrow screens, where the three collapse onto one line. */
.jrc-move { display: contents; }
.jrc-what { font-family: 'Oswald', sans-serif; text-transform: uppercase;
            letter-spacing: 0.04em; font-size: 12px; }
.jrc-field { color: var(--dead); }
.jrc-was, .jrc-became, .jrc-new { font-family: 'IBM Plex Mono', monospace;
                                  font-variant-numeric: tabular-nums; }
/* The old value recedes and the new one is the news. */
.jrc-was { color: var(--dead); }
.jrc-became, .jrc-new { color: var(--chalk); }
.jrc-arrow { color: var(--dead); }

/* Two lines per change, not five.

   The previous version gave was, arrow and became `grid-column: 1 / -1` each,
   which reads as "put these on one full-width line" and does the opposite: they
   are three separate grid items, so each took a row of its own, and the
   `display: inline` beside it was ignored because grid items are blockified.
   A four-change run filled the screen.

   Now the round and the fixture share the first line, and the field and the
   whole move share the second. */
@media (max-width: 720px) {
  .jrc-row {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "week what" "field move";
    gap: 1px 8px;
    padding: 5px 0;
  }
  .jrc-week  { grid-area: week; }
  .jrc-what  { grid-area: what; overflow-wrap: anywhere; }
  .jrc-field { grid-area: field; }
  /* A real box here, so its three children lay out inside it rather than each
     claiming a row of the outer grid. */
  .jrc-move  { grid-area: move; display: block; }
  .jrc-new   { grid-area: move; }
  .jrc-arrow { margin: 0 4px; }
}

/* How fresh the scores on the picks page are. Quiet until it is not — the
   number only matters when it starts getting large. */
.freshness { font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--dead); }
.freshness.stale { color: var(--live); }

/* ---- the board ----
   Every class here is prefixed. This stylesheet already carries .pick, .pips,
   .pk-shaped names and a .wk, and generic names have collided three times
   already — a cell class doubling as a row class, and a header inheriting its
   own column's styling. Prefixing is cheaper than remembering. */

/* Side by side while there is room, stacked when there is not. Both panels are
   short lists, so they only stack where they would have had to anyway. */
/* 290, not 320. With a 14px gap the pair needs twice this plus the gap before
   auto-fit will place them side by side, so 320 held out until about 654px and
   left every landscape phone and small tablet stacking when it did not need to.
   290 pairs them from roughly 594px.

   290 and not less because the binding constraint is a .mypick row, not a board
   row: 30px + 84px + three 11px gaps is 147px spoken for before the team name
   is drawn, and a long one takes it to about 227px. 290 leaves ~266px of card
   interior, which clears that; 260 would not, and the row would start wrapping.

   A portrait phone is still one column, and deliberately — half of 390px is
   below what that same row needs, so pairing there would trade scrolling for
   two cramped columns. */
.pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
        gap: 14px; align-items: start; margin-bottom: 26px; }
.pair .card { margin-bottom: 0; }

.stand { display: flex; flex-direction: column; }
.stand-row { display: grid; grid-template-columns: 1fr auto; gap: 14px; align-items: center;
             padding: 7px 0; border-bottom: 1px solid rgba(44, 74, 59, .5); }
.stand-row:last-child { border-bottom: none; }
.stand-name { font-family: 'Oswald', sans-serif; text-transform: uppercase;
              letter-spacing: 0.04em; font-size: 14px; }
.stand-count { font-family: 'IBM Plex Mono', monospace; font-size: 13px;
               font-variant-numeric: tabular-nums; color: var(--live); text-align: right; }
.stand-row.me .stand-name { color: var(--live); }
.stand-row.out .stand-name { color: var(--dead); text-decoration: line-through; }
/* The week that finished them, not just that they are finished. */
.stand-when { font-family: 'Oswald', sans-serif; font-size: 10.5px; letter-spacing: 0.1em;
              text-transform: uppercase; color: var(--dead); }

.tally { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px;
         padding-bottom: 11px; border-bottom: 1px solid var(--line); }
.tally-big { font-family: 'IBM Plex Mono', monospace; font-size: 26px; color: var(--live);
             font-variant-numeric: tabular-nums; line-height: 1; }
.tally-of { font-size: 13px; color: var(--muted); }

.mypick { display: grid; grid-template-columns: 30px 84px 1fr auto; gap: 11px;
          align-items: center; padding: 7px 0;
          border-bottom: 1px solid rgba(44, 74, 59, .5); font-size: 13px; }
.mypick:last-child { border-bottom: none; }
/* The same chip as everywhere else a pick number appears. */
.mypick-no { font-family: 'IBM Plex Mono', monospace; font-size: 12px; text-align: center;
             padding: 3px 0; border: 1px solid var(--live); color: var(--live);
             line-height: 1.3; }
.mypick.spent .mypick-no { border-color: var(--gone); color: var(--gone);
                           text-decoration: line-through; }
.mypick-still { grid-column: 2 / -1; color: var(--live); font-size: 12.5px; }
.mypick-week { font-family: 'Oswald', sans-serif; text-transform: uppercase;
               letter-spacing: 0.08em; font-size: 11.5px; color: var(--muted); }
.mypick-team { font-family: 'Oswald', sans-serif; text-transform: uppercase;
               letter-spacing: 0.04em; font-size: 13.5px; color: var(--muted); }

.curve-lab { font-family: 'Oswald', sans-serif; font-size: 10px; letter-spacing: 0.12em;
             text-transform: uppercase; color: var(--dead); margin-bottom: 6px; }
.curve svg { display: block; width: 100%; height: 120px; overflow: visible;
             margin-bottom: 16px; }
.curve-gl { stroke: var(--line); stroke-width: 1; }
.curve-ln { fill: none; stroke: var(--live); stroke-width: 2; stroke-linejoin: round; }
.curve-ar { fill: var(--live); opacity: .10; }
.curve-end { fill: var(--live); stroke: var(--surface); stroke-width: 2; }
.curve-ax { font-family: 'IBM Plex Mono', monospace; font-size: 9.5px; fill: var(--dead); }

/* Picks lost sits beside the column that breaks it down by team, and that
   column goes last because it is the one that grows. */
.wk { display: grid; grid-template-columns: 84px 62px 52px 1fr; gap: 8px 14px;
      grid-template-areas: "rd ppl pks to";
      align-items: start; padding: 10px 0;
      border-bottom: 1px solid rgba(44, 74, 59, .5); }
.wk-round { grid-area: rd; font-family: 'Oswald', sans-serif; text-transform: uppercase;
            letter-spacing: 0.08em; font-size: 12px; padding-top: 1px; }
.wk-players { grid-area: ppl; }
.wk-picks { grid-area: pks; }
/* baseline, not the default stretch. The ×N is IBM Plex Mono and the team name
   beside it is not, so a span carrying both has a taller content box than a
   span with only a name — and stretched items sit their text at the top of
   their own box rather than on a shared line. "Broncos ×2" therefore rode a
   little high against the single-pick teams next to it. Same reason .side and
   .jrc-row already do this. */
.wk-to { grid-area: to; font-size: 12.5px; color: var(--muted);
         display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
.wk-to span { white-space: nowrap; }
.wk-to b { color: var(--link); font-weight: 400; }
.wk-to i { font-family: 'IBM Plex Mono', monospace; font-style: normal; color: var(--chalk); }
/* Both counts are losses, so both wear the loss colour; what differs is what is
   being counted, which the headings say. */
.wk-n { font-family: 'IBM Plex Mono', monospace; font-size: 15px; text-align: right;
        font-variant-numeric: tabular-nums; color: var(--gone); line-height: 1.2; }
.wk-n.zero { color: var(--dead); }

.wk.head { padding: 0 0 7px; border-bottom: 1px solid var(--line); align-items: end; }
/* Every header cell, explicitly. The cells set their own type and colour, and
   the header reuses their class names for grid placement — without this each
   one leaks in and no two headings match. */
.wk.head .wk-round, .wk.head .wk-players, .wk.head .wk-picks, .wk.head .wk-to {
  font-family: 'Oswald', sans-serif; font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--dead); line-height: 1.25;
  font-variant-numeric: normal; padding-top: 0; display: block;
}
.wk.head .wk-players, .wk.head .wk-picks { text-align: right; }

/* Inert while there is room for a table. MUST come before the media query
   below: a media query adds no specificity, so if this sat after it, the
   labels would be hidden at every width.

   The gap before the label is a margin and not a space in the markup. Razor
   drops whitespace between a tag and an @expression that follows it, so the
   leading space that worked when this read " Players" vanished the moment it
   became " @Plural(...)" — and rendered "1Player out". A margin cannot be
   swallowed by a template engine. */
.wk-lab { display: none; font-family: 'Inter', sans-serif; font-size: 11.5px;
          color: var(--muted); letter-spacing: 0; margin-left: 0.4em; }

/* Below this it stops being a table. A column heading only works while it sits
   above its column, and "picks lost to" cannot — the list needs the full width,
   so the heading would be left on a line of its own describing something under
   it. The headings come off and each figure carries its own label instead. */
@media (max-width: 640px) {
  .wk.head { display: none; }
  /* 18px between the two figures, not 12. They used to be a bare number each
     and now carry two words apiece, so at the old gap "1 Player out" and
     "5 Picks lost" ran together into one phrase. */
  .wk { grid-template-columns: 1fr auto auto;
        grid-template-areas: "rd ppl pks" "to to to"; gap: 5px 18px;
        align-items: baseline; }
  .wk-round { font-size: 11.5px; }
  .wk-to { font-size: 12px; padding-top: 1px; }
  .wk-lab { display: inline; }
  .wk-n { font-size: 13px; }
}

/* ---- the ladder: one row per entrant, one token per pick ---- */

.ladder-row {
  display: grid; grid-template-columns: 120px 1fr auto; gap: 12px;
  align-items: center; padding: 11px 0; border-bottom: 1px solid var(--line);
}
.ladder-row:last-child { border-bottom: none; }
.ladder-name {
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 15px;
  overflow: hidden; text-overflow: ellipsis;
}
.ladder-name.out { color: var(--dead); }
.tokens { display: flex; flex-wrap: wrap; gap: 4px; }
.token {
  width: 22px; height: 26px; border: 1px solid var(--live);
  display: flex; align-items: center; justify-content: center;
  font-family: 'IBM Plex Mono', monospace; font-size: 10px;
  color: var(--live); background: rgba(240, 180, 41, 0.09);
}
.token.dead {
  border-color: var(--line); color: var(--dead);
  background: transparent; text-decoration: line-through; opacity: 0.65;
}
.ladder-count { font-family: 'IBM Plex Mono', monospace; font-size: 13px; color: var(--muted); }
.ladder-count b { color: var(--live); font-weight: 500; }
.ladder-count.out b { color: var(--gone); }

/* ---- fixtures ---- */

.fixture {
  border: 1px solid var(--line); background: var(--raised);
  padding: 12px; margin-bottom: 9px;
  display: flex; flex-wrap: wrap; gap: 10px;
  align-items: center; justify-content: space-between;
}
.fixture .match {
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.05em; font-size: 15px;
}
.fixture .when {
  font-size: 11px; color: var(--muted); font-family: 'IBM Plex Mono', monospace;
}
.tag {
  font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
  padding: 2px 6px; border: 1px solid var(--line); color: var(--muted);
}
.tag.mnf { border-color: var(--live); color: var(--live); }
.tag.locked { border-color: var(--gone); color: var(--gone); }

table { width: 100%; border-collapse: collapse; }
th {
  text-align: left; font-family: 'Oswald', sans-serif; font-weight: 400;
  font-size: 11px; letter-spacing: 0.1em; color: var(--muted);
  text-transform: uppercase; padding: 7px 8px; border-bottom: 1px solid var(--line);
}
td { padding: 6px 8px; border-bottom: 1px solid var(--line); font-size: 13px; }

#blazor-error-ui {
  background: var(--gone); color: #fff; display: none;
  position: fixed; bottom: 0; left: 0; right: 0; padding: 10px 16px; z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; float: right; }

/* ---------------------------------------------------------------------------
   The rules page. Prose rather than data, so it wants a wider measure and more
   air than the tables elsewhere — but the same tokens, so it reads as part of
   the site rather than a document someone pasted in.
   --------------------------------------------------------------------------- */
/* Emphasis in the prose. <b> defaults to weight 700, and Inter is only loaded at
   400, 500 and 600 — so bold was asking for a weight that does not exist and
   getting a synthesised or nearest-match approximation that barely read as
   emphasis at all. Body text is already --chalk, the brightest text token, so
   weight alone had nothing left to give.

   600 is the heaviest weight actually loaded, and --live is what the rest of the
   site already uses to mean "this is the part that matters" — see .topbar .meta
   b. Following that rather than inventing a fourth convention. */
.rules-page b { font-weight: 600; color: var(--live); }

/* Scoped to .rules-page deliberately. A bare `.card > p` would set a measure on
   every card on the site — the jobs log, the commissioner panels, the board —
   none of which asked for it. Generic selectors are how the last round of
   collisions happened. */
.rules-page .card > p { max-width: 62ch; line-height: 1.65; }

.rules-list { max-width: 62ch; margin: 8px 0 0; padding-left: 20px; }
.rules-list li { margin-bottom: 6px; line-height: 1.6; }

/* The two rules people get wrong, and the one distinction that generates false
   bug reports. Marked rather than merely stated. */
.rules-note {
  border-left: 3px solid var(--live);
  background: var(--raised);
  padding: 10px 14px;
  margin: 12px 0;
}

.rules-states { margin-top: 10px; }
.rules-states th {
  font-family: 'Oswald', sans-serif; text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 11px; color: var(--muted);
  text-align: left; padding: 4px 10px 6px 0;
}
.rules-states td { padding: 8px 10px 8px 0; vertical-align: top; line-height: 1.55; }
.rules-states td:first-child { white-space: nowrap; width: 1%; }
.rules-states tr + tr td { border-top: 1px solid var(--line); }
