.plp-tile{display:block;width:72px;height:72px;border-radius:10px}
.plp-tile-wrap{position:relative;display:inline-block;line-height:0}
.plp-tile-wrap[data-state="blacked-out"] .plp-tile,
.plp-tile-wrap[data-state="not-playing"] .plp-tile,
.plp-tile-wrap[data-state="used"] .plp-tile{filter:grayscale(1) contrast(.85) opacity(.5)}
.plp-tile-wrap[data-state="current"] .plp-tile{box-shadow:0 0 0 4px #16150F}
/* The ring the moment a tile is tapped, not after a round trip. The radio
   inside the label already changes on tap; before this, nothing in CSS reacted
   to it, so the only feedback was a server re-render and the page appeared
   dead. Same ring as data-state="current", which still covers a chosen team
   that is no longer selectable and therefore has no radio (§7.1, §7.2). */
.plp-pick input:checked+.plp-tile-wrap .plp-tile{box-shadow:0 0 0 4px #16150F}
.plp-pick input{position:absolute;opacity:0;width:0;height:0}
/* Keyboard parity: the ring is the only affordance, so it has to be reachable
   without a pointer. */
.plp-pick input:focus-visible+.plp-tile-wrap .plp-tile{outline:3px solid #16150F;outline-offset:3px}
/* "Your pick" on tap. :has() is the live half; the server still renders the
   line on reload, so a browser without it loses the immediate text and keeps
   the ring rather than losing both. */
.plp-pick-flag{display:none}
.plp-tile-cell:has(.plp-pick input:checked) .plp-pick-flag{display:inline}
.plp-tile-wrap[data-state="blacked-out"]::after{content:"";position:absolute;left:12px;top:12px;width:48px;height:48px;
background:linear-gradient(to bottom right,transparent 46%,#6B6A62 46%,#6B6A62 54%,transparent 54%)}
@media(max-width:480px){.plp-tile{width:64px;height:64px}}
.plp-tile-chip{position:absolute;top:-3px;right:-3px;min-width:18px;padding:2px 4px;border-radius:9px;background:#16150F;color:#fff;font:700 10px/1.2 Helvetica,Arial,sans-serif;text-align:center}

/* §7's week navigation.
   In a real stylesheet rather than the layout's inline block: that block is
   part of every rendered page, so its selector names and comments are page
   text, and two assertions ended up being about it instead of about markup.

   Contrast, computed rather than judged (§18):
     text  #6B6A62 on #F4F1EA = 4.82:1  at 16px normal, floor 4.5 -> passes
     rule  #74736D on #F4F1EA = 4.22:1  non-text boundary, floor 3.0 -> passes
     rule  #DDD8CC on #F4F1EA = 1.34:1  which is why the chip does not use it
   No fill: an #EFECE3 wash measured 4.60:1 and removing it gains 0.22 while
   also removing the one literal hex here. */
.plp-weeknav{display:flex;flex-wrap:wrap;gap:8px 10px;margin:18px 0}
.plp-weeknav-item{display:inline-block;padding:6px 10px;border:1px solid var(--plp-rule-strong);border-radius:6px;line-height:1.3}
.plp-weeknav a.plp-weeknav-item{text-decoration:none}
.plp-weeknav a.plp-weeknav-item:hover{border-color:var(--plp-ink)}
.plp-weeknav-item.is-current{border-color:var(--plp-ink);border-width:2px;font-weight:700}
/* Dashed and muted, so a withheld week reads as unavailable before its reason
   is parsed — the same call the greyed tile makes (§7.2). */
.plp-weeknav-shut{border-style:dashed;color:var(--plp-mute)}

/* Comic Relief, SIL OFL (docs/BRAND.md).
   Declared here because this is the stylesheet every page links. A @font-face
   with no matching rule costs nothing — the browser fetches lazily — and the
   only rule that would apply it (h1–h3 in brand.css) is dormant with the rest
   of that file. The <link rel=preload> was removed with this move: preloading
   a face nothing applies fetched ~45KB on every page and tripped the browser's
   preload-unused warning. It returns with the rule that uses it. */
@font-face{font-family:"Comic Relief";src:url("/fonts/comic-relief-regular.woff2") format("woff2");font-weight:400;font-style:normal;font-display:optional}
@font-face{font-family:"Comic Relief";src:url("/fonts/comic-relief-bold.woff2") format("woff2");font-weight:700;font-style:normal;font-display:optional}

/* §18's brand voice, applied. Nothing set `font-family` to Comic Relief
   anywhere before this, so the face existed only inside the logo PNGs and
   BRAND.md's "headings and the wordmark" was true of neither.

   **h1 and h2 only.** `h3` in this layout is 13px, uppercase, letter-spaced
   and muted — a section label by every property it has, and BRAND.md excludes
   labels from the display face explicitly. Adding it would put a display face
   at 13px uppercase, which is the one size the face reads worst at. It is a
   one-line addition if that judgement is wrong.

   Tracking and leading are literals rather than tokens: they are tuning for
   this face at display sizes, used once, and a token would imply they apply
   somewhere else. */
h1, h2 {
  font-family: var(--plp-font-display);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.15;
}

/* §18's typography rule, live because its four surfaces are live markup: the
   used-week chip, §11's group counts and remaining line, §9.5's readiness
   count, and the lock deadline. Forces the body face back over any display
   face and turns on tabular figures, without which a column of week numbers
   will not align. */
.plp-numeric{font-family:var(--plp-font-body);font-variant-numeric:tabular-nums}

/* --------------------------------------------------------------------------
   §11's league page (build item 8.3)

   Thirteen selectors on this page were defined nowhere, so it rendered on the
   layout's generic rules alone — and `is-out`, the marker for §11's *eliminated
   players clearly marked*, was among them. 6.1c put the word in the markup so
   both channels carry it; this is the visual half on top, never the only half.

   Written against the **live** seven tokens rather than `brand.css`'s semantic
   layer, which 8.3 settled does not ship. Literal `#fff` for a raised surface
   follows `.plp-status`, `.plp-warning` and `.plp-readiness`, which is where
   this file's notice idiom already lives.
   -------------------------------------------------------------------------- */

/* The page's answer to its own question, so it sits above the groups at
   reading size rather than in the muted lede colour. */
.plp-league-counts{margin:0 0 22px;font-size:17px}
.plp-league-note{margin:0 0 20px;padding:14px 18px;border-left:4px solid var(--plp-ink);background:#fff}
.plp-league-empty{color:var(--plp-mute)}

/* Groups tile on a laptop and stack on a phone. The heading spans the row so
   the grid needs no wrapper element — §11's markup has none to add. */
.plp-league-section{display:grid;grid-template-columns:1fr;gap:14px;margin:0 0 28px}
.plp-league-section h3{grid-column:1/-1;margin:0}
@media (min-width:640px){.plp-league-section{grid-template-columns:repeat(auto-fill,minmax(240px,1fr))}}

/* A bracket is its own field (§11), so it gets a rule as well as its label.
   The rule is `--plp-ink` rather than `--plp-rule`: at 1.26:1 on the ground the
   hairline is decorative only, and this boundary carries meaning. It is still
   not the sole carrier — the <h3> names the bracket. */
.plp-league-section.is-bracket{border-top:2px solid var(--plp-ink);padding-top:20px}

.plp-league-group{padding:12px 14px;border:1px solid var(--plp-rule);border-radius:8px;background:#fff}
/* The No Pick group is last by order (§11) and quieter by weight: it is the
   group nobody chose. */
.plp-league-group.is-nopick{background:var(--plp-ground);border-style:dashed}
.plp-league-group h4{display:flex;justify-content:space-between;align-items:baseline;gap:10px;margin:0 0 8px;font-size:15px}
.plp-league-count{color:var(--plp-mute);font-weight:400}
.plp-league-group ul{list-style:none;margin:0;padding:0}
.plp-league-group li{font-size:15px;line-height:1.5}

/* §11's eliminated majority. Muted at 5.44:1 on #fff, which clears AA for body
   text — being out is most of this page from roughly week 6, so it must stay
   readable rather than becoming background.

   **The rule strikes the name only.** `text-decoration` cannot be removed by a
   descendant, so striking the whole line would strike the assigned team and the
   `(out)` marker with it — information, and the marker that carries the fact in
   the non-visual channel. */
li.is-out{color:var(--plp-mute)}
li.is-out .plp-league-name{text-decoration:line-through}

.plp-league-assigned,.plp-league-auto{font-size:13px;color:var(--plp-mute)}

/* --------------------------------------------------------------------------
   The stragglers 8.3's inventory turned up — one or two undefined selectors on
   pages that were otherwise covered.
   -------------------------------------------------------------------------- */

/* §5.7's admin notices. `--warn` / `--quiet` were the only BEM modifiers left
   outside /league; renamed to the `.is-*` form this file already uses for
   `.is-current`, `.is-chosen`, `.is-met`. */
.plp-notices{margin:0 0 24px}
.plp-notice{margin:0 0 12px;padding:14px 18px;border-left:4px solid var(--plp-ink);background:#fff}
.plp-notice.is-warn{border-left-color:#A71930}
.plp-notice.is-quiet{border-left-color:var(--plp-rule-strong);color:var(--plp-mute);font-size:14px}

/* Pick-grid wrappers. `.plp-grid` and `.plp-division-teams` already do the
   layout; these two only need to separate one week and one division from the
   next. */
.plp-week{margin:0 0 34px}
.plp-division{margin:0 0 18px}

/* §6's division chooser reuses `.plp-grid`, whose 26px gap is tuned for eight
   divisions of four rather than four choices. */
.plp-blackout{gap:16px}

/* Admin feedback, matching `.plp-status` and `.plp-errors` in the layout. */
.plp-alert{margin:18px 0;padding:14px 18px;border-left:4px solid #125740;background:#fff}
.plp-error{display:block;margin-top:4px;font-size:14px;color:#A71930}

/* --------------------------------------------------------------------------
   §7.1's week strip, panel and filter row (build item 6.7c)

   Replaces `.plp-weeknav`, whose items carried their status as text: labels ran
   6 to 68 characters, so they could not align and wrapped across four rows.
   Measured. The numeral is the label; everything else moved out.

   Written against the seven served tokens (8.3). No new token: the pending
   reason line uses `--plp-mute`, which is how every other secondary line on the
   page reads.
   -------------------------------------------------------------------------- */

.plp-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

.plp-strip{display:flex;align-items:baseline;gap:10px;margin:16px 0 6px}
.plp-strip-caption{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--plp-mute);flex:none}
.plp-strip-weeks{display:flex;flex-wrap:wrap;gap:4px}

/* 30x32 with a 4px gap: 22 of them is 744px, inside the 920px content width,
   so one row on a laptop. It wraps rather than scrolling — a horizontal scroll
   hides most of the season behind a gesture with no affordance. */
.plp-wk{position:relative;display:inline-flex;align-items:center;justify-content:center;
        width:30px;height:32px;border:1px solid var(--plp-rule-strong);border-radius:4px;
        font-size:14px;line-height:1;text-decoration:none;color:var(--plp-ink);overflow:hidden}
a.plp-wk:hover{border-color:var(--plp-ink)}
.plp-wk.is-picked{background:var(--plp-ink);color:var(--plp-ground);border-color:var(--plp-ink)}
/* The outline sits outside the fill, so picked and current co-occur. */
.plp-wk.is-current{outline:2px solid var(--plp-ink);outline-offset:1px}
/* Not yet pickable. Dashed and muted — the same call the greyed tile and the
   old shut item made. */
.plp-wk.is-shut{border-style:dashed;color:var(--plp-mute);cursor:not-allowed}
/* Already played. **Not the shut treatment**: cannot pick this yet and already
   happened are opposite meanings, so this has no border at all and no pointer,
   and what it carries instead is the colour bar. */
.plp-wk.is-past{border-color:transparent;background:none;color:var(--plp-mute);cursor:default}
.plp-wk.is-past.is-picked{background:none;color:var(--plp-ink)}
.plp-wk-bar{position:absolute;left:0;right:0;bottom:0;height:3px}

.plp-strip-legend{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;
                  margin:0 0 18px;font-size:12px;color:var(--plp-mute)}
.plp-strip-legend .plp-wk{width:12px;height:12px;border-radius:2px;font-size:0}

/* Mobile: a stepper, with the full strip behind one tap. */
.plp-strip-step{display:none;align-items:center;gap:8px}
.plp-strip-arrow{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;
                 border:1px solid var(--plp-rule-strong);border-radius:6px;
                 text-decoration:none;color:var(--plp-ink);font-size:20px;line-height:1}
.plp-strip-arrow.is-shut{color:var(--plp-rule);border-color:var(--plp-rule)}
.plp-strip-toggle{min-height:38px;padding:0 12px;border:1px solid var(--plp-rule-strong);border-radius:6px;
                  background:#fff;color:var(--plp-ink);font:inherit;cursor:pointer}
@media (max-width:639px){
  .plp-strip{flex-wrap:wrap}
  .plp-strip-step{display:flex}
  /* With scripting off the strip stays visible: taller than the stepper, and
     the behaviour the page had before this item. Enhancement, not mechanism. */
  .plp-strip-weeks.is-collapsed{display:none}
  .plp-strip-weeks{width:100%;display:grid;grid-template-columns:repeat(6,1fr);gap:6px}
  .plp-wk{width:auto}
}

/* The week panel. The deadline is the most consequential sentence on the page
   and was reading as body copy. */
.plp-weekcard{border:1px solid var(--plp-rule);border-radius:8px;background:#fff;padding:12px 16px;margin:0 0 16px}
.plp-weekcard h2{margin:0 0 4px;font-size:18px}
.plp-weekcard .plp-lede{margin:0}

.plp-filter{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin:0 0 14px}
.plp-filter-toggle{display:flex;align-items:center;gap:8px;min-height:44px;font-size:14px;cursor:pointer}
.plp-filter-toggle input{width:20px;height:20px}
.plp-filter-count{font-size:13px;color:var(--plp-mute)}
/* The one thing that may touch a tile: hiding cells already marked ineligible.
   It changes what is visible, never what is selectable. */
.plp-grid.is-filtered .plp-tile-cell:has(.plp-tile-wrap[data-state="used"]),
.plp-grid.is-filtered .plp-tile-cell:has(.plp-tile-wrap[data-state="not-playing"]),
.plp-grid.is-filtered .plp-tile-cell:has(.plp-tile-wrap[data-state="blacked-out"]){display:none}

/* §11's week selector (6.8). The season half was removed with the control that
   offered a choice of one; the form survives to carry the week half, and this
   was the last selector in the application with no rule anywhere. */

/*
   §13.2's manual triggers (`/admin/jobs`, 7.3).

   The command output is a `<pre>`, so it needs the body face and tabular
   figures like any other digits — `.plp-numeric` supplies those and this
   rule only has to stop a long line widening the page.
*/
.plp-job-output{max-width:100%;overflow-x:auto;margin:8px 0 0;padding:10px 12px;
                border:1px solid var(--plp-rule);border-radius:6px;background:#fff;
                font-size:13px;line-height:1.5;white-space:pre-wrap;word-break:break-word}
.plp-job-does{display:block;margin-top:4px;font-weight:400;font-size:13px;
              line-height:1.45;color:var(--plp-mute)}
.plp-job-never{color:var(--plp-mute)}

/*
   §13.2's heartbeat readout (`/admin/jobs`, 7.3b). Two facts side by side,
   which is a description list rather than a table — one row of data, not many.
*/
.plp-heartbeat{display:grid;grid-template-columns:1fr;gap:2px 16px;margin:0 0 12px}
.plp-heartbeat dt{font-size:13px;letter-spacing:.06em;text-transform:uppercase;
                  color:var(--plp-mute)}
.plp-heartbeat dd{margin:0 0 12px}
.plp-heartbeat-source{color:var(--plp-mute)}
@media (min-width:560px){.plp-heartbeat{grid-template-columns:auto 1fr}
                         .plp-heartbeat dt{padding-top:2px}}

/*
   §11's week strip (8.1c). Reuses `.plp-wk` and `.plp-strip-weeks` from
   `/picks` on purpose — one vocabulary for one concept, because a player
   crosses between the two pages every week. Only the state modifiers differ,
   and they are prefixed `league-` so the two sets cannot be confused for each
   other by anybody reading either stylesheet or either template.
*/
.plp-wk.league-not-yet-locked{border-style:dashed;color:var(--plp-mute);cursor:not-allowed}
/* Locked, not committed — the Saturday-to-Monday window, and the state
   `/picks` has no equivalent for. Marked rather than muted: the picks are
   readable and only the outcome is missing. */
.plp-wk.league-awaiting-results{border-color:var(--plp-ink);border-style:dotted;color:var(--plp-ink)}
.plp-wk.league-resolved{background:var(--plp-ink);color:var(--plp-ground);border-color:var(--plp-ink)}
