/* ==========================================================================
   Survivor pools: the whole feature's stylesheet.

   One file, deliberately. The rest of this project gives every page its own
   <style> block and they have drifted into six greys and three button shapes.
   Survivor is a self-contained area with an index, three pickers, two
   standings tables, a weekly winners page and a rules page, and all of them
   have to read as one product, so they share one set of tokens and one card
   language.

   Everything here is scoped to markup this app renders. It is loaded only by
   survivor/base.html, so it cannot reach the NFL dashboard or the blog.

   Order:
      1. tokens                     9. team cards (team pool)
      2. base elements             10. player rows (TD pool)
      3. panels and type           11. filters
      4. tabs                      12. the sticky pick tray
      5. badges, pills, chips      13. tables and standings
      6. buttons                   14. rules page
      7. pool hero cards           15. messages and form controls
      8. crests and headshots      16. responsive and motion
                                   17. Dream Team
                                   18. the pick page: hero, week chips, board
                                   19. week strip, compact header, centred pick
   ========================================================================== */

/* -- 1. tokens ------------------------------------------------------------ */

/* These are the main site's values, not approximations of them. Every one is
   lifted from the NFL dashboard's inline <style> block, which is the de facto
   design system for this project — it has no stylesheet of its own to import,
   so the numbers are copied and the source of each is named. If the dashboard
   ever moves, this block is the one place to follow it.

   Survivor previously ran a near-miss of each: #0a0a0b against the dashboard's
   #0d0d0d, #16171a against #161616, #f4f5f7 against #e0e0e0. Individually
   invisible; together they read as a different product, which is the whole
   complaint this block answers. */
:root {
  /* --- main-site core tokens ------------------------------------------- */
  /* dashboard: body { background-color: #0d0d0d; color: #e0e0e0 } */
  --ground:      #0d0d0d;
  --text:        #e0e0e0;

  /* dashboard: .card { background:#161616; border:1px solid #2a2a2a } */
  --panel:       #161616;
  --line:        #2a2a2a;

  /* dashboard: .weeks-grid-wrapper / .sticky-header nav { background:#111;
     border:1px solid #222 } — the recessed surface tabs and trays sit on. */
  --panel-2:     #111111;
  --line-soft:   #222222;

  /* dashboard: .record-row { background:linear-gradient(145deg,#1a1a1a,#222);
     border:1px solid #333 } — flattened to its midpoint for use as a solid
     raised chip, with #333 as the divider rule. */
  --raised:      #1e1e1e;
  --line-hard:   #333333;

  /* dashboard: .week-link { color:#666 }, .projected-label { color:#888 },
     .sb-desc { color:#555 }. #666 is the workhorse muted grey. */
  --text-dim:    #888888;
  --text-faint:  #666666;

  /* The signature gradient, from .rec-season-val — the record numbers. This is
     the one mark that says "this is the same site". */
  --accent:      #00ff88;
  --accent-2:    #00b8ff;
  --brand:       linear-gradient(135deg, #00ff88 0%, #00b8ff 100%);

  /* dashboard: .record-row::before — the 4px rule across the top of the record
     card. Ends on #0062ff rather than #00b8ff, deliberately: it is a hairline,
     and the deeper blue reads at 3px where the lighter one greys out. */
  --brand-rule:  linear-gradient(90deg, #00ff88, #0062ff);

  /* The deep end of that same rule, as a colour of its own. The pool cards'
     artwork needs a third value to give a figure depth without introducing a
     hue the site does not already use; this is #0062ff, from the stop above. */
  --accent-deep: #0062ff;

  /* The third contest's accent, and the one colour on this page that is not
     taken from the dashboard.

     It had to be: green and blue are the brand pair and both were already
     spoken for by a pool card, and a third card needs an edge distinguishable
     from either at a hairline's width, which rules out anything between them
     on the wheel. Amber is the far side from both and reads as gold, which is
     not the worst association for the contest that pays every week.

     Deliberately not an alias of `--warn`, which is a semantic colour (a void
     pick, an urgent deadline). Aliasing them would mean a future tweak to one
     silently restyling the other. */
  --accent-3:      #ffc247;
  --accent-3-deep: #ff9500;

  /* dashboard: .week-link.active-week { background:#00ff88; color:#000;
     box-shadow:0 2px 8px rgba(0,255,136,.2) } — the active-pill treatment,
     reused verbatim for survivor's tabs, chips and primary buttons. */
  --on-accent:   #000;
  --accent-lift: 0 2px 8px rgba(0, 255, 136, .2);

  /* --- semantic, NOT theme --------------------------------------------- */
  /* These carry meaning, so they are deliberately exempt from the unification
     above and must not be folded into the brand palette. Amber is neither
     green nor red; an entrant who reads a void as an elimination panics. */
  --warn:        #ffb020;
  --danger:      #ff6b6b;

  --out:         #ff5a5a;
  --doubtful:    #ff9134;
  --questionable:#ffd53d;

  /* "This number is provisional and still moving."
     
     Semantic, and deliberately not an alias of anything already here — every
     other candidate carries a meaning that would be read instead of this one.
     Green is `--accent`, which means brand and, on a standings row, means
     good. Amber is `--warn` and is also Dream Team's own `--accent-3`, so an
     amber live figure on the Dream Team page would be indistinguishable from
     the page's own furniture. Red is `--danger` and `--out`; a red number
     beside somebody's fantasy points reads as bad news, and a provisional
     score is not bad news.
     
     Cyan is the far side of all three. It is close to `--accent-2` in hue but
     that value only ever appears inside gradients, never as flat text, so the
     two never meet on a page.

     **Do not fold this into another token.** It sits in the semantic block for
     the same reason `--warn` and `--danger` do, and a theme pass that
     "consolidates" it into `--accent` or `--accent-3` would not change a
     colour — it would change what a number on the Dream Team page means. See
     docs/SURVIVOR_OPS.md §11. */
  --live:        #22d3ee;

  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-pill: 999px;

  /* One glow, used for every "this is selected" state in the app. */
  --glow: 0 0 0 2px var(--accent), 0 8px 34px rgba(0, 255, 136, .26);

  /* --- the pool card's own surface ---------------------------------------
     Deliberately *not* a change to `--panel`. Every other panel in this app
     matches the main site's #161616 and must keep matching it; this is the one
     surface that has to separate from the page behind it, because pool cards
     are stacked full-width on a phone and nothing but the gap tells you where
     one ends.

     #161616 on #0d0d0d measures 1.07:1 — nine 8-bit steps. That is invisible
     on a phone in daylight, which is the actual complaint. #1c1c1c is 1.14:1
     and fifteen steps: still unmistakably a dark card, but a card.

     The border is what does most of the work. It is lighter than the surface
     rather than darker, so the card reads as lifted rather than cut out, and
     at 1.50:1 against its own surface it survives a bright screen. */
  --card-surface: #1c1c1c;
  --card-lift:    0 4px 16px rgba(0, 0, 0, .55);

  /* The grey the card border used to be. Kept as the fallback for a card with
     no contest theme on it, which should not exist but would otherwise render
     borderless. */
  --card-border:  #3a3a3a;

  /* How far the accent is held back in a resting border.

     The two contests need different numbers to look the same. #00b8ff is a
     darker colour than #00ff88, so at one shared opacity the TD card's edge
     reads visibly fainter than the team card's: at 40% they measure 2.24:1 and
     3.04:1 against the card surface. The pair below is set so both land on
     3.0:1 instead, which is what "reduced intensity" was actually asking for —
     two borders that look equally held back, not two borders sharing a number.

     Held back to roughly a fifth of the full accent's contrast, so the border
     reads as this pool's colour without two stacked cards becoming two stacked
     neon rectangles. Hover and focus take it to the full accent. */
  --card-edge-alpha: .4;      /* green: 3.04:1 on the card surface */
  --card-edge-alpha-td: .53;  /* blue:  3.01:1, matched to it */
  --card-edge-alpha-dream: .43;  /* amber: 3.05:1, matched to both */

  --tap: 48px;        /* minimum tap target, everywhere */

  /* THE PAGE COLUMN, as two numbers that nothing else may restate.
     `.wrap` is the column the page renders in. The sticky tray is a full-bleed
     band, but the card, the chips and the submit inside it are the SAME
     column: an entrant reading down the page and then down to the thing that
     submits it must not see the bottom block start and end somewhere else.

     These are custom properties rather than literals because that promise used
     to be made by copying `max-width: 880px` into two rules and hoping the
     gutters either side of them stayed equal -- and they did not. `body` took
     its gutter to 20px at the 620px breakpoint and `.tray` kept 14px, so from
     620px up to 920px (where the column finally hits its cap and the mistake
     hides itself again) the tray's contents hung 6px past the column on BOTH
     sides: "Your Survivor Card" clipped at the left edge, the counter past the
     right, the three TD cards wider than the panel above them. Two rules that
     agree about 880 and disagree about the gutter are not the same column.

     Anything that wants to sit on the column reads these. Nothing re-types
     them. `--gutter` is overridden once, in section 16, for both at once. */
  --col-max: 880px;
  --gutter: 14px;
  /* Reserved so the tray never covers the last card in the list. Measured
     rather than guessed: the tray's own height, in Chromium, in every state it
     can be in, at 360, 390 and 880px. The tallest is 228px — three filled
     cards on a phone with a name long enough to defeat the font stepping — so
     this is that plus a little, and the phone override below is gone because
     the phone is where the tray is *tallest*, not shortest.

     168px was right for the old one-line tray and would now hide the last two
     players in the list behind the cards. */
  --tray-h: 236px;
}

/* -- 2. base elements ----------------------------------------------------- */

* { box-sizing: border-box; }

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

body {
  margin: 0;
  padding: 20px var(--gutter) 40px;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  background: var(--ground);
  color: var(--text);
  line-height: 1.5;
  /* No background wash. There were two radial gradients here, a blue and a
     green bloom behind the top of the page, and they were the single loudest
     reason the survivor area did not read as the same site: the NFL dashboard
     is a flat #0d0d0d behind its cards, so any wash at all made the two
     backgrounds visibly different colours where the pages meet in the nav.
     "Matches the main site exactly" means flat. */
}
body.has-tray { padding-bottom: var(--tray-h); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; }

.wrap { max-width: var(--col-max); margin: 0 auto; }

/* The wordmark, matched to the main site's `a.brand`: Arial Black 900 at
   1.5em, white with the second half in solid #00ff88, letter-spacing -1px.
   Survivor set it in the body font, uppercased it, split it after "PICKS"
   and ran a gradient through "BY" — four differences in the one element a
   reader uses to tell what site they are on. The markup was changed to match
   too; the split is part of the wordmark, not styling. */
.masthead { text-align: center; margin-bottom: 20px; }
.masthead .logo {
  display: inline-block;
  font-family: 'Arial Black', 'Segoe UI', sans-serif;
  font-size: 1.5em; font-weight: 900; letter-spacing: -1px;
  color: #fff;
}
.masthead .logo:hover { text-decoration: none; opacity: .85; }
.masthead .logo span { color: var(--accent); }
/* Renamed from `.tag`, which collided with the team-card chip further down
   this file (`.tag`, display:inline-block, with a border and a background).
   `.masthead .tag` won on specificity for the properties it declared, but it
   never declared `display`, so the tagline silently inherited the chip's
   inline-block, padding, background and border. It went unnoticed only because
   the old uppercase wordmark was wide enough to push it onto its own line;
   correcting the wordmark to the main site's narrower mixed case brought them
   up alongside each other. A distinct name is the fix — the two are unrelated
   things that happened to want the same word.

   Styled as the main site's `.rec-season-lbl`: muted grey, letterspaced,
   uppercase, small. */
.masthead-sub {
  display: block;
  color: var(--text-faint); font-size: .74em; letter-spacing: 2px;
  text-transform: uppercase; margin-top: 8px; font-weight: 700;
}

/* `.sitelinks` -- the survivor area's own three-chip row out to the rest of the
   site -- was removed on 2026-08-25 along with every other app's hand-written
   section row. The navigation is one list now
   (`core.context_processors.site_nav`) drawn by one thing, `_bottom_nav.html`,
   the tab bar this area's `base.html` includes -- at every width, desktop
   included. `.sitelinks`' rules are gone with its markup rather than left here
   to rot; the bar carries its own appearance, because it ships on pages from
   six apps that share no stylesheet.

   **The masthead is left exactly as it was**, centred at every width, and that
   is the point of the change rather than an omission from it. The tab bar is
   the navigation now at every width, so there is nothing to lay out beside the
   wordmark and no reason for this masthead to become a left/right row on a
   desktop. What is above the fold here is the wordmark, the area's name, and
   who the viewer is -- which is what it was before `.sitelinks` was added, and
   what every other app's header is too.

   No media query is needed for any of this, which is the useful part: there is
   no breakpoint left in this file's navigation story to keep in step with the
   partial's. */

/* Who the viewer is, on every page of this area.

   It used to sit under `.sitelinks`, sized to match it, so the masthead read as
   one block rather than two rows of chips competing. That row is gone
   (2026-08-25) and this one keeps its size, because the thing it was sized
   against was the masthead, not the row: on a phone it is still the only
   accent-filled control above the fold on a logged-out index, and on a desktop
   it now sits at the right-hand end of the masthead beside the header nav.

   `Sign up free` is filled rather than outlined because it is the one action a
   logged-out visitor to these pages is here to take, and an outlined chip in a
   row of outlined chips is not a call to action. It is small: the pool cards
   below carry the real, per-contest buttons, and this is the fallback for
   somebody reading the standings or the rules, where there is no card. */
.authstrip {
  display: flex; justify-content: center; align-items: center;
  flex-wrap: wrap; gap: 6px;
  margin-top: 8px;
}
.authstrip-cta,
.authstrip-alt {
  padding: 7px 14px; min-height: 36px;
  display: inline-flex; align-items: center;
  border-radius: var(--r-pill);
  font-size: .78em; font-weight: 800;
}
.authstrip-cta {
  background: var(--accent); color: var(--on-accent);
  border: 1px solid var(--accent);
  box-shadow: var(--accent-lift);
}
.authstrip-cta:hover {
  filter: brightness(1.08); color: var(--on-accent); text-decoration: none;
}
.authstrip-alt {
  border: 1px solid var(--line); background: var(--panel);
  color: var(--text-dim); font-weight: 700;
}
.authstrip-alt:hover {
  border-color: var(--accent); color: var(--accent); text-decoration: none;
}
/* Not a control: the signed-in state is a statement, and giving it a border
   would make it look like a third button next to Log out. */
.authstrip-who {
  padding: 7px 4px;
  color: var(--text-dim); font-size: .78em; font-weight: 600;
}
.authstrip-who b { color: var(--text); font-weight: 800; }

/* -- 3. panels and typography --------------------------------------------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 20px;
  margin-bottom: 16px;
}
.panel-tight { padding: 15px 16px; }
.panel h2 {
  margin: 0 0 14px; padding-bottom: 12px;
  font-size: 1.08em; font-weight: 800; letter-spacing: -.2px;
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
.panel h2.bare { border: none; padding-bottom: 0; margin-bottom: 10px; }
.panel h3 { margin: 0 0 8px; font-size: .96em; font-weight: 800; }
.panel > p:last-child { margin-bottom: 0; }

.muted { color: var(--text-dim); }
.faint { color: var(--text-faint); }
.small { font-size: .86em; }

.stats-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
  gap: 8px;
  background: var(--panel-2);
  padding: 13px 11px;
  border-radius: var(--r-md);
  border: 1px solid var(--line-soft);
}
/* Bottom-aligned labels. Not a theme change — a pre-existing bug the flat
   background made obvious: a long prize string ("$1,000.00 CAD", still what
   Dream Team pays) is too long for a ~94px column, so that one value wraps to
   two lines, and with every cell sized to its own content its label sat a
   line lower than the labels either side of it. The
   cells now stretch to the tallest and the labels land on one baseline
   whatever the values do. */
.stat-item {
  text-align: center;
  display: flex; flex-direction: column; justify-content: space-between;
}
.stat-val  { display: block; font-weight: 800; font-size: 1.18em; letter-spacing: -.3px; }
.stat-lbl  {
  font-size: .64em; color: var(--text-faint); text-transform: uppercase;
  letter-spacing: 1.2px; font-weight: 700;
}

/* Same problem one row down. These columns are `minmax(74px, 1fr)`, and a
   37-character prize in a 74px column stacks into a tower that sets the height
   of every tile beside it (the cells stretch to the tallest -- see above).
   Two columns and a smaller value keeps it to about two lines. Only the
   non-cash case claims the extra column; a cash prize is short and stays in
   one, so no existing row re-flows. */
.stat-item.is-form { grid-column: span 2; }
.stat-item.is-form .stat-val {
  font-size: .92em; line-height: 1.2; letter-spacing: 0;
}

/* -- 4. pool switcher and tabs -------------------------------------------- */

/* Which pool, above which tab within it. Two rows rather than one, because
   they answer different questions and merging them into a single row of five
   chips would make neither readable on a phone. */
.poolswitch {
  display: flex; gap: 6px; margin-bottom: 8px;
}
.poolchip {
  flex: 1; min-width: 0; text-align: center;
  padding: 9px 10px; min-height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--panel);
  color: var(--text-dim); font-size: .82em; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.poolchip:hover { color: var(--text); border-color: var(--text-faint);
                  text-decoration: none; }
/* The same solid neon-green active pill as the tabs below it and the main
   site's week tabs. It was a tinted outline — green on 10%-green — which made
   the switcher's "selected" and the tab row's "selected" two different visual
   languages stacked directly on top of each other. */
.poolchip.is-on {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 800;
  box-shadow: var(--accent-lift);
}
.poolchip.is-on:hover { color: var(--on-accent); border-color: var(--accent); }

/* -- tabs ------------------------------------------------------------------ */

/* Picks / Leaderboard / Rules. A row rather than links at the bottom of the
   page, so an entrant can check the standings and come back without using the
   browser's back button. */
.tabs {
  display: flex; gap: 6px; margin-bottom: 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 5px;
  /* The strip scrolls inside itself rather than widening the page.

     `.tab` is `flex: 1`, which looks like it guarantees a fit and does not: a
     flex item's `min-width` is `auto`, so a tab can never shrink below the
     width of its own longest word. "Leaderboard" does not break, so four tabs
     have a floor of about 338px. Three fit a 320px phone; four do not, and
     the 48px that would not fit went into the document -- `scrollWidth` 353
     against a 320px viewport, which is the Dream Team page scrolling sideways
     that was reported on 2026-08-21 and mis-attributed to `.scoring-table`.
     That table is inside `.scroll-x` and has never widened anything; this
     row was the whole of it.

     Contained the same way `.scroll-x` does it, deliberately: the fourth tab
     is reachable by swiping the row, which is the ordinary phone idiom, and
     no tab is hidden, shortened or dropped. Wrapping to a second line was the
     alternative and was not taken -- it changes the height of the navigation
     on the one page that has four tabs, so Dream Team's header would sit a
     row lower than the other two contests' for a reason no entrant can see.

     Note that setting `overflow-x` alone also computes `overflow-y` to `auto`
     -- the spec does not allow one axis to clip while the other stays visible.
     The active pill's `--accent-lift` glow reaches about 10px past it with
     only 5px of padding before that edge, so some of the halo is clipped.
     Screenshotted at 320px before and after and diffed rather than eyeballed:
     the row's box is 292x54 either way, the active pill is 57x42 at the same
     offset either way, and the difference is 2201 pixels at a maximum channel
     delta of 11/255 -- entirely within the soft 20%-opacity halo, nothing of
     the pill itself. Padding the row out to keep those pixels would make
     every tab row on the site taller to protect a shadow nobody can see,
     which is the wrong trade. */
  overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.tab {
  flex: 1; text-align: center;
  padding: 10px 8px; min-height: 42px;
  border-radius: var(--r-pill);
  font-size: .88em; font-weight: 700;
  color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
}
.tab:hover { color: var(--text); text-decoration: none; }
/* The main site's active pill, verbatim: solid #00ff88 on black type with the
   small green lift beneath it (.week-link.active-week). It was a gradient
   here, which is the treatment reserved for the record numbers — using it for
   a tab as well made the gradient ordinary, and left the survivor tab row
   looking like a different control from the week tabs one page away. */
.tab.is-on {
  background: var(--accent); color: var(--on-accent); font-weight: 800;
  box-shadow: var(--accent-lift);
}
.tab.is-on:hover { color: var(--on-accent); }

/* -- 5. badges, pills and chips ------------------------------------------- */

.badge {
  display: inline-block; padding: 4px 10px; border-radius: var(--r-sm);
  font-size: .7em; font-weight: 800; text-transform: uppercase;
  letter-spacing: .8px; white-space: nowrap;
}
.badge-alive { background: rgba(0, 255, 136, .12); color: var(--accent);
               border: 1px solid rgba(0, 255, 136, .55); }
.badge-out   { background: rgba(255, 77, 77, .12); color: var(--danger);
               border: 1px solid rgba(255, 107, 107, .55); }
.badge-grey  { background: var(--raised); color: var(--text-dim);
               border: 1px solid var(--line); }
/* Void keeps its own colour, and is now a **team pool** state only — a
   cancelled game, where the pick counts neither way but the team is still
   spent. An entrant who reads that as an elimination panics; one who reads it
   as a survival miscounts the teams they have left. Amber is neither green nor
   red. The TD pool has no void at all: a pick there scored or it missed. */
.badge-void  { background: rgba(255, 176, 32, .12); color: var(--warn);
               border: 1px solid rgba(255, 176, 32, .55); }
.void-note   { color: var(--warn); font-size: .84em; margin-top: 5px; }

/* "Not eligible" borrowed `.badge-void` until the void rule was removed, which
   put the literal string `badge-void` into the markup of a TD standings page
   the moment an excluded entry appeared on it — on a page whose whole promise
   is that nothing there is ever voided. It reads identically and is a separate
   class so that "no TD surface contains badge-void" is a fact about the
   markup rather than a fact about which fixtures happened to be excluded. */
.badge-inelig { background: rgba(255, 176, 32, .12); color: var(--warn);
                border: 1px solid rgba(255, 176, 32, .55); }

/* Pills are dark chips. They used to be glassy translucent white, because they
   sat on a colour hero card; now that every surface in this app is the house
   panel, the glass had nothing to sit on and read as washed-out grey. */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-size: .71em; font-weight: 800; letter-spacing: .8px;
  text-transform: uppercase; white-space: nowrap;
  background: var(--raised);
  border: 1px solid var(--line-hard);
  color: var(--text-dim);
}
.pill-open   { background: rgba(0, 255, 136, .12); border-color: rgba(0, 255, 136, .55);
               color: var(--accent); }
.pill-urgent { background: rgba(255, 77, 77, .12); border-color: rgba(255, 107, 107, .6);
               color: var(--danger); }
.pill-dot::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; display: inline-block;
}
/* Retained as an explicit alias. The base pill is now dark, so this adds
   nothing — but it is referenced by markup across several templates and
   silently dropping it would change those pills without any edit naming them. */
.pill-dark {
  background: var(--raised); border-color: var(--line-hard); color: var(--text-dim);
}

/* The entry countdown carries the signature gradient as a hairline border: the
   two-layer background trick paints the panel colour inside the border box and
   the gradient in it. A plain `border-color` cannot take a gradient. */
.pill[data-closes-at] {
  color: var(--text);
  border: 1px solid transparent;
  background-image: linear-gradient(var(--panel), var(--panel)), var(--brand);
  background-origin: border-box;
  background-clip: padding-box, border-box;
}
.pill-lock { color: var(--warn); border-color: rgba(255, 176, 32, .45);
             background: rgba(255, 176, 32, .1); }

/* -- 6. buttons ----------------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: var(--tap); padding: 13px 18px;
  /* Same neon-green active treatment as the main site's pills and the tab
     above, so "the primary action" is one look across the whole product. */
  background: var(--accent); color: var(--on-accent);
  border: none; border-radius: var(--r-md);
  font-weight: 800; font-size: 1em; font-family: inherit;
  cursor: pointer; text-align: center;
  box-shadow: var(--accent-lift);
}
.btn:hover { filter: brightness(1.08); text-decoration: none; color: var(--on-accent); }
.btn:disabled {
  background: var(--raised); color: var(--text-faint); cursor: not-allowed;
  filter: none;
}

/* The hero call to action. It was a white pill, which only made sense against
   a saturated colour card; on the house panel it read as the loudest thing on
   the page. It is now the same neon-green primary as every other main action,
   just pill-shaped and sized to its text. */
.btn-hero {
  background: var(--accent); color: var(--on-accent);
  /* Full width and squared to the card's corner radius, not a pill. The
     actions are stacked now, and a row of full-width pills reads as a list of
     tags; the same shape as the card's own corners reads as a button. */
  border-radius: var(--r-md);
  width: 100%; padding: 14px 20px;
  box-shadow: var(--accent-lift);
}
.btn-hero:hover { background: var(--accent); color: var(--on-accent); filter: brightness(1.08); }
/* The secondary action: an outlined dark chip, matching the main site's
   inactive nav pills (#1e1e1e on #333, muted grey type). */
.btn-ghost {
  background: var(--raised); color: var(--text-dim);
  border: 1px solid var(--line-hard);
  border-radius: var(--r-md); width: 100%; padding: 13px 20px;
  box-shadow: none;
}
.btn-ghost:hover { background: var(--line-hard); color: var(--text); filter: none; }

/* --- the pool card's call to action, in its four states ------------------
   Which one is drawn is decided by `_pool_card_action()` on the server, from
   the viewer's entry and picks. These rules only have to make the four
   distinguishable at a glance, without reading the label:

     go      there is something to do — the house green, same as every other
             primary action on the site
     done    it is already done, and can still be changed — green, but hollow,
             because a filled green button is a summons and this is a receipt
     view    nothing to do, the record is there to look at — a raised chip
     closed  not a link at all, and drawn so that pressing it is obviously not
             going to achieve anything */
.btn-go { /* the inherited .btn-hero treatment, named so the template can say
             which state it is drawing */ }

.btn-done {
  background: rgba(0, 255, 136, .13);
  color: var(--accent);
  border: 1px solid rgba(0, 255, 136, .5);
  box-shadow: none;
}
.btn-done:hover {
  background: rgba(0, 255, 136, .2); color: var(--accent); filter: none;
}

.btn-view {
  background: var(--raised); color: var(--text);
  border: 1px solid var(--line-hard);
  box-shadow: none;
}
.btn-view:hover { background: var(--line-hard); color: var(--text); filter: none; }

.btn-closed {
  background: var(--panel-2); color: var(--text-faint);
  border: 1px solid var(--line);
  box-shadow: none; cursor: default;
}

/* -- 7. pool hero cards (index) ------------------------------------------- */

/* The pool card is a photograph with a panel under it.

   Its history, briefly, because each step removed something that was carrying
   meaning in a code nobody can read: it began as a full-bleed two-stop colour
   gradient with white type; that became a plain house card with a 4px identity
   hairline; the hairline became a drawing of the contest itself. The drawing
   is still here, and still does the whole job when there is no photograph —
   see `.hero-photo` below and `survivor/heroes.py`.

   The layout is now top to bottom rather than layered: picture, then panel.
   Nothing but the two pills is drawn over the image, and both pills carry
   their own opaque background, so the photograph never has to be dimmed to
   keep type readable. That is why the artwork's scrim was a compromise and
   this one is not: it exists to blend the picture into the panel, not to
   rescue text sitting on top of it. */
.hero {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg);
  /* Wide enough that the page shows through as a gap rather than as a seam.
     16px read as a divider between two halves of one thing. */
  margin-bottom: 24px;
  background: var(--card-surface);
  border: 1px solid var(--card-edge, var(--card-border));
  box-shadow: var(--card-lift);
  color: var(--text);
}

/* The card's starting edge, in the house gradient. This is the hairline the
   card used to wear across its top, brought back for the one job it is good
   at: mid-scroll on a phone, the eye needs a hard "this is a new card" signal,
   and a 3px band of colour at the top edge is unmistakable in a way a border
   alone is not. Led from opposite ends per contest, as before.

   Above the photograph and above the stretched link's overlay, and inert to
   pointer events so it cannot eat a tap. */
.hero::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--hero-rule, var(--brand-rule));
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  z-index: 3; pointer-events: none;
}
.hero-team::before { --hero-rule: linear-gradient(90deg, #00ff88, #0062ff); }
.hero-td::before   { --hero-rule: linear-gradient(90deg, #0062ff, #00ff88); }

/* The border carries the pool's accent rather than a neutral grey, so a card's
   colour is readable from its edge alone, mid-scroll, before any text is.
   These are the brand accents only: the semantic colours (--warn, --danger,
   --out and the injury values) mean something specific and are never used for
   decoration.

   Written as rgba literals rather than a colour function on the token, because
   `--accent` is a hex and giving a hex an alpha channel needs `color-mix()`,
   which nothing else in this file depends on. The values are `--accent` and
   `--accent-2` exactly. */
.hero-team {
  --card-edge:    rgba(0, 255, 136, var(--card-edge-alpha));
  --card-edge-on: var(--accent);
}
.hero-td {
  --card-edge:    rgba(0, 184, 255, var(--card-edge-alpha-td));
  --card-edge-on: var(--accent-2);
}

/* --- the picture ---------------------------------------------------------
   A fixed shape rather than whatever the file happens to be, so the card is
   the same height before and after the image loads and a photograph swapped
   for a taller one cannot reflow the page. 16:9 on a phone, and much wider on
   a desktop where an 880px card at 16:9 would be a poster.

   `--panel-2` under it is what shows while the image is still arriving, and
   what the SVG fallback is drawn on: one step darker than the card, so the
   picture area reads as recessed rather than as a hole. */
.hero-photo {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--panel-2);
}
@media (min-width: 560px) {
  .hero-photo { aspect-ratio: 3.2 / 1; }
}

.hero-photo-img {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
}

/* The drawing, when there is no photograph. Not a background any more — it is
   the hero in its own right, so it is drawn at nearly full strength rather
   than the .5 it used when type sat on top of it. */
.hero-art-svg {
  display: block; width: 100%; height: 100%;
  opacity: .85;
  color: var(--art-key);
}
.hero-art-svg .art-alt   { color: var(--art-alt); }
/* The third value, for a figure that has to sit behind another one of the same
   colour without becoming a different hue. */
.hero-art-svg .art-deep  { color: var(--art-deep); }
.hero-art-svg .art-field { color: var(--art-field); }
/* The confetti is deliberately the *other* end of the brand gradient from the
   TD card's figures, which is the one place the two hues meet. */
.hero-art-svg .art-confetti { color: var(--accent); }

.hero-team {
  --art-key:   var(--accent);
  --art-alt:   var(--accent-2);
  --art-deep:  var(--accent-deep);
  --art-field: var(--accent);
}
.hero-td {
  --art-key:   var(--accent-2);
  --art-alt:   var(--accent-deep);
  --art-deep:  var(--accent-deep);
  --art-field: var(--accent-deep);
}

/* Two jobs, one element. The bottom stop is the important one: it takes the
   photograph into `--panel` before the picture ends, so there is no seam
   between image and card and no bright band running under the prize figure.
   The top stop is a light vignette that keeps the pills sitting on something
   dark whatever the frame behind them happens to be.

   The rgba values are `--panel` (#161616) with alpha, written out because a
   hex custom property cannot be given an alpha channel without `color-mix()`,
   and this file has no other dependency that new. */
.hero-photo-scrim {
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    linear-gradient(0deg,
      var(--card-surface) 9%,
      rgba(28, 28, 28, .96) 18%,
      rgba(28, 28, 28, .60) 34%,
      rgba(28, 28, 28, .18) 55%,
      rgba(28, 28, 28, 0) 72%),
    linear-gradient(180deg,
      rgba(28, 28, 28, .62) 0%,
      rgba(28, 28, 28, .22) 42%,
      rgba(28, 28, 28, 0) 74%);
}

/* The pills, in the picture's top corners. */
.hero-photo-top {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 10px; padding: 13px 14px;
}

/* --- the panel ----------------------------------------------------------- */
/* Not positioned. See `.hero-cta::after` — the stretched link measures itself
   against `.hero`, and a positioned ancestor here would shrink it to this
   block, leaving the photograph untappable. */
.hero-body { padding: 16px 20px 20px; }

/* --- the whole card as one tap target ------------------------------------
   The stretched-link pattern: the call to action is an ordinary `<a>`, and its
   `::after` is blown up to cover the card, photograph included. One link, one
   accessible name, one place in the tab order — and the entire card is the
   touch surface, which is what a thumb expects of something this size.

   It works because `.hero` is the nearest positioned ancestor and nothing
   between it and the anchor is positioned. `.hero-photo` is positioned, but it
   is a *sibling* of the panel the anchor lives in, not an ancestor of it.

   The trade-off, taken knowingly: text inside the card can no longer be
   selected by dragging. */
.hero-cta::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  border-radius: var(--r-lg);
}
/* Anything else that has to stay clickable inside the card sits above that
   overlay. Without this the Rules and Leaderboard links would be dead. */
.hero-nested { position: relative; z-index: 2; }

.hero.is-tappable:hover { border-color: var(--card-edge-on, var(--text-faint)); }
.hero.is-tappable:hover .hero-photo-img { transform: scale(1.015); }
.hero-photo-img { transition: transform .35s ease; }
/* Keyboard users get the card outlined, not just the button, because the card
   is what the link now covers. Its own colour, at full strength, which is the
   same step hover takes. */
.hero:focus-within { border-color: var(--card-edge-on, var(--accent)); }

/* The prize, built like the main site's record figure: the number carries the
   signature gradient, the word beside it is small, muted and letterspaced.
   This is the survivor page's equivalent of `.rec-season-val` / `.rec-season-lbl`
   and is the one place the gradient appears on this card. */
.hero-prize {
  margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px;
  flex-wrap: wrap;
}
.hero-prize-val {
  /* Sized so "$1,000.00 CAD" stays on one line inside a 390px phone, and so
     it stays *below* `.hero-name`. This is the CASH case only; since
     2026-08-27 the two survivor pools pay a console and take `.is-form`
     below instead. The headroom is kept deliberately rather than tuned down,
     because a pool's prize is an admin field and the next one may be larger
     again.
     At 2.4em it broke after "$1,000.00" and shouted louder than the name of
     the pool, which is the thing a reader is actually choosing between. */
  font-size: 1.5em; white-space: nowrap;
  font-weight: 900; line-height: 1; letter-spacing: -1px;
  background: var(--brand);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  /* Fallback for anything that cannot clip a background to text: the accent
     itself, never the transparent fill, which would render the prize blank. */
  color: var(--accent);
}

/* A prize that is a THING is a phrase, not a figure. "PS5 or Xbox Series X,
   winner's choice" is 37 characters where "$780" is four, and the rule above
   is `white-space: nowrap` at 1.5em precisely so a cash amount never breaks --
   which would push a console straight out of the card on a 390px phone.
   So the non-cash case gets its own size and is allowed to wrap. It is set
   from `prize_form` being non-empty, the same field that decides whether the
   amount is a payment or an approximate retail value, so the two can never
   disagree. Cash pools are untouched and still render exactly as before. */
.hero-prize-val.is-form {
  font-size: 1.02em; white-space: normal;
  letter-spacing: -.2px; line-height: 1.15;
}
.hero-prize-lbl {
  /* `--text-faint` measured 3.0:1 on this surface, and it is a real label
     rather than decoration. One step up clears 4.5:1. */
  color: var(--text-dim); font-weight: 700; letter-spacing: 2px;
  font-size: .72em; text-transform: uppercase;
}

.hero-name {
  margin: 0; font-size: 1.95em; font-weight: 800; line-height: 1.06;
  letter-spacing: -1px; color: var(--text);
}
.hero-tagline {
  margin: 7px 0 15px; font-size: 1em; font-weight: 600;
  color: var(--text-dim);
}

.hero-meta {
  display: flex; gap: 10px 16px; flex-wrap: wrap;
  margin: 0 0 16px; font-size: .82em; color: var(--text-dim);
}
.hero-meta span { display: inline-flex; align-items: center; gap: 6px; }
.hero-meta b { font-weight: 800; color: var(--text); }

/* The meta row's icons. Line art at the size of the text beside them, in
   `currentColor` so they follow whatever the row is set in, and held back a
   little so the numbers stay the thing being read. Every one is decoration:
   no fact on this row is available only as a picture. */
.mi {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .7;
}

/* Nobody has entered yet. Set in the accent, because this line is an
   invitation and the rest of the row is statistics. */
.hero-meta-first { color: var(--accent); font-weight: 700; }
.hero-meta-first .mi { opacity: .9; }

/* Stacked, full width, primary first. A card this size on a phone should have
   one obvious thing to press running the whole width of it, not two pills
   sharing a line and neither wide enough to read. */
.hero-actions { display: flex; flex-direction: column; gap: 10px; }
.hero-actions .btn { width: 100%; }

/* The line under the buttons that says what they are about — "Week 6 · 1 of 3
   in". The button has room for a verb and nothing else; this is where the fact
   lives, and it is the reason "Make Picks" on a TD card cannot be mistaken for
   "you have not started". */
.hero-note {
  font-size: .8em; color: var(--text-dim); font-weight: 600;
  text-align: center;
}

.hero-entry {
  margin-top: 14px; padding-top: 13px;
  border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  font-size: .86em; color: var(--text-dim);
}

/* -- 8. crests, headshots, position and injury ---------------------------- */

/* Crests sit on a light chip on purpose. The colour crest is the recognisable
   one, and several franchises are black and silver or black and gold, which
   vanish against this page's background. */
.crest {
  width: 34px; height: 34px; flex: 0 0 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .93);
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.crest img { width: 78%; height: 78%; object-fit: contain; display: block; }
/* The fallback when a team has no crest: its code, which is never blank. */
.crest-text {
  background: var(--raised); color: var(--text);
  font-size: .6em; font-weight: 800; letter-spacing: -.2px;
}
.crest-lg { width: 44px; height: 44px; flex-basis: 44px; }
.crest-lg.crest-text { font-size: .76em; }
.crest-sm { width: 20px; height: 20px; flex-basis: 20px; }
.crest-sm.crest-text { font-size: .46em; }

.headshot {
  width: 46px; height: 46px; flex: 0 0 46px;
  border-radius: 50%;
  background: var(--raised);
  border: 1px solid var(--line);
  object-fit: cover; object-position: top center;
  display: block;
}
/* The placeholder for a player with no photograph. Initials rather than a grey
   silhouette: a column of identical silhouettes tells an entrant nothing and
   makes two different players look like the same row. */
.headshot-ph {
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: .88em; letter-spacing: .5px;
  color: var(--text-dim);
  background: linear-gradient(150deg, #2b2e34 0%, #202227 100%);
}
.headshot-sm { width: 32px; height: 32px; flex-basis: 32px; font-size: .7em; }

/* Position badge, coloured by role so a long list is scannable. */
.pos {
  display: inline-block; min-width: 30px; text-align: center;
  padding: 2px 6px; border-radius: 5px;
  font-size: .66em; font-weight: 800; letter-spacing: .6px;
  border: 1px solid;
}
.pos-QB { color: #ff9d5c; border-color: rgba(255, 157, 92, .5);  background: rgba(255, 157, 92, .12); }
.pos-RB { color: #5cd6ff; border-color: rgba(92, 214, 255, .5);  background: rgba(92, 214, 255, .12); }
.pos-WR { color: #86ffb4; border-color: rgba(134, 255, 180, .5); background: rgba(134, 255, 180, .12); }
.pos-TE { color: #d3a6ff; border-color: rgba(211, 166, 255, .5); background: rgba(211, 166, 255, .12); }
.pos-FB { color: #ffd98c; border-color: rgba(255, 217, 140, .5); background: rgba(255, 217, 140, .12); }

.jersey { color: var(--text-faint); font-weight: 700; font-size: .82em; }

/* Injury designation. Display only, and load-bearing because of it: a pick on a
   player listed Out still stands, still locks at kickoff, and if he does not
   play it is a plain miss that burns him like any other. The badge is the
   warning; it does not change a single rule. Drawn in the search list and
   repeated on the tray. */
.inj {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  font-size: .6em; font-weight: 900; letter-spacing: .7px;
  text-transform: uppercase; border: 1px solid; vertical-align: middle;
}
.inj-out          { color: var(--out);          border-color: rgba(255, 90, 90, .6);  background: rgba(255, 90, 90, .14); }
.inj-doubtful     { color: var(--doubtful);     border-color: rgba(255, 145, 52, .6); background: rgba(255, 145, 52, .14); }
.inj-questionable { color: var(--questionable); border-color: rgba(255, 213, 61, .55);background: rgba(255, 213, 61, .12); }

/* -- 9. team cards (team pool) -------------------------------------------- */

.gameblock {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  margin-bottom: 10px;
  overflow: hidden;
  background: var(--panel-2);
}
.gameblock-head {
  padding: 8px 13px;
  background: rgba(255, 255, 255, .03);
  border-bottom: 1px solid var(--line-soft);
  font-size: .78em; color: var(--text-dim); font-weight: 600;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.gameblock-sides { display: grid; grid-template-columns: 1fr 1fr; }
.gameblock-sides > * + * { border-left: 1px solid var(--line-soft); }

/* One team. A <label> wrapping a radio, so selection works with scripting off
   and the whole card is the tap target. */
.tcard {
  position: relative; display: flex; align-items: center; gap: 10px;
  padding: 13px 12px; margin: 0; min-height: 76px;
  cursor: pointer; font-weight: 400;
  transition: background .12s ease;
}
.tcard input { position: absolute; opacity: 0; pointer-events: none; }
.tcard:hover { background: rgba(0, 255, 136, .05); }

.tcard-body { min-width: 0; flex: 1; }
.tcard-name {
  font-weight: 800; font-size: .95em; line-height: 1.15;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.tcard-sub {
  margin-top: 3px; font-size: .78em; color: var(--text-dim);
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.tcard-rec { font-weight: 800; color: var(--text-faint); }

/* The round add button on the right, matching the player rows. */
.addbtn {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--raised);
  color: var(--text-dim);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.15em; font-weight: 700; line-height: 1;
}

.tcard.is-selected { background: rgba(0, 255, 136, .09); }
.tcard.is-selected .addbtn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.tcard.is-selected::before {
  content: ""; position: absolute; inset: 0;
  border: 2px solid var(--accent); border-radius: var(--r-sm);
  box-shadow: var(--glow); pointer-events: none;
}

/* Used and unavailable cards stay on the page. An entrant planning a season
   needs to see what they have spent, not have it quietly removed. */
.tcard.is-blocked { opacity: .42; cursor: not-allowed; filter: grayscale(.6); }
.tcard.is-blocked:hover { background: transparent; }
.tcard.is-blocked .addbtn { opacity: .5; }

.tag {
  display: inline-block; padding: 2px 7px; border-radius: 5px;
  font-size: .64em; font-weight: 800; text-transform: uppercase;
  letter-spacing: .6px; white-space: nowrap;
  background: var(--raised); color: var(--text-dim);
  border: 1px solid var(--line);
}
.tag-used { color: var(--warn); border-color: rgba(255, 176, 32, .45);
            background: rgba(255, 176, 32, .1); }

/* -- 10. player rows (TD pool) -------------------------------------------- */

.prow-list { display: flex; flex-direction: column; gap: 8px; }

.prow {
  display: flex; align-items: center; gap: 11px;
  width: 100%; padding: 10px 11px; min-height: 74px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  text-align: left; font-family: inherit; color: var(--text); font-size: 1em;
  cursor: pointer;
  transition: border-color .12s ease, transform .1s ease;
}
.prow:hover { border-color: rgba(0, 255, 136, .45); }
.prow:active { transform: scale(.993); }

.prow-body { min-width: 0; flex: 1; }
.prow-name {
  font-weight: 800; font-size: .95em; line-height: 1.2;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.prow-meta {
  margin-top: 3px; font-size: .8em; color: var(--text-dim);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.prow-team { display: inline-flex; align-items: center; gap: 5px; font-weight: 700; }
.prow-kick { margin-top: 2px; font-size: .76em; color: var(--text-faint); }

.prow.is-picked { border-color: var(--accent); background: rgba(0, 255, 136, .08); }
.prow.is-picked .addbtn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.prow.is-blocked { opacity: .44; cursor: not-allowed; filter: grayscale(.55); }
.prow.is-blocked:hover { border-color: var(--line); }

.prow-empty { text-align: center; padding: 34px 12px; color: var(--text-faint); }

/* -- 11. filters ----------------------------------------------------------- */

.searchbox {
  width: 100%; min-height: var(--tap); padding: 12px 14px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); border-radius: var(--r-md);
  font-size: 16px;   /* 16px stops iOS zooming the page on focus */
  font-family: inherit;
}
.searchbox:focus { outline: none; border-color: var(--accent); }
.searchbox::placeholder { color: var(--text-faint); }

.filter-row {
  display: flex; gap: 7px; overflow-x: auto;
  margin-top: 10px; padding-bottom: 2px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.filter-row::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto; padding: 8px 15px; min-height: 40px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text-dim); font-size: .85em; font-weight: 700;
  cursor: pointer; white-space: nowrap; font-family: inherit;
  display: inline-flex; align-items: center;
}
.chip:hover { color: var(--text); }
.chip.is-on {
  background: rgba(0, 255, 136, .13); border-color: var(--accent);
  color: var(--accent);
}
.filter-row select {
  min-height: 40px; padding: 6px 12px; width: auto; flex: 0 0 auto;
  border-radius: var(--r-pill); font-size: .85em;
}

/* The scoring rule, directly under the filters and always on screen. This
   replaces a per-quarterback note: it is true of every pick in the pool, so it
   is stated once, permanently, rather than appearing when a QB happens to be
   on the page. */
.scoring-note {
  display: flex; gap: 8px; align-items: center;
  margin: 11px 0 0; padding: 9px 12px;
  background: rgba(255, 176, 32, .09);
  border: 1px solid rgba(255, 176, 32, .4);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  color: #ffce78; font-size: .84em; font-weight: 600; line-height: 1.4;
}
.scoring-note b { color: var(--warn); }

.list-note { margin: 10px 0 0; color: var(--text-faint); font-size: .8em; }

/* -- 12. the sticky pick tray --------------------------------------------- */

/* THE TRAY'S MEASURED CONTRACT, IN ONE PLACE
   ------------------------------------------
   This area was edited four times in three days (6194f47, b82f011, e653b2c,
   871498c) and then reported broken on a phone on 2026-08-22. The report was
   RIGHT and this comment previously said it was wrong -- worth recording,
   because the reasoning that got it wrong is seductive. Every stylesheet in
   that range does lay out three cards side by side at 390px, and a test with
   the ordinary three-week fixture proves it. The live page still came out
   broken, because none of these rules were the cause: `.wstrip` (section 19)
   was widening the whole document to 924px, and `.tray` is fixed and sizes to
   the layout viewport, so the tray was being laid out at 924px on a 390px
   phone. Three correct cards dividing the wrong container.

   The lesson is about the fixture, not the cascade. A rule can be measured
   green at five widths and still be wrong on the live site if the thing it is
   measured inside is the wrong size. See the bisect in section 19.

   So the contract is written down here, once, and `TrayGeometryContractTests`
   in survivor/tests.py asserts every line of it in Chromium at 360, 390, 430,
   900 and 1280 with a FULL tray. If you change anything below, that test tells
   you what you changed. It was made to fail three ways before it was trusted.

   AT EVERY WIDTH
     `.tray` is a full-bleed band on purpose -- it is the floor of the page.
     `.tray-inner` is NOT: it is exactly the box `.wrap` is, same cap
     (`--col-max`) and same gutter (`--gutter`), so the block an entrant reads
     starts and ends where the page does. Nothing may re-type either number.
     The three TD cards are `flex: 1 1 0` and divide `.tray-inner` in equal
     shares: the row starts on the column's left edge and ends on its right,
     and no card leaves the screen. Measured: 105px each at 360, 115px at 390,
     129px at 430, 281px at 900, 288px at 1280.

   BELOW 900px -- the phone and the small tablet
     The team and TD submit is 85% of `.tray-inner`, centred, equal gaps either
     side. `display: flex` is load-bearing, not decorative: `.btn` is
     `inline-flex` and auto margins do not centre an inline-level box. The rule
     is at the end of this section, NOT in section 20, which is contractually
     desktop-only.

   AT AND ABOVE 900px -- the desktop
     The team and TD submit is `width: auto` with `min-width: 260px`, pushed
     right so it ends flush with the column. Section 20 owns this. 260px is a
     floor so that "Pick a team" and "Pick at least one player" are one control
     in two contests rather than two different-sized ones.

   DREAM TEAM IS EXCLUDED FROM BOTH, DELIBERATELY
     `.tray-lineup` has its own row (ring + counter + submit) and its own
     rules in section 14: shrink-wrapped above 430px, and wrapped onto its own
     line at 85% centred at 430px and below. Both submit rules here and in
     section 20 carry `:not(.tray-lineup)` for that reason. Do not "unify"
     them without reading section 14 first -- the shapes agree at the phone
     widths already, which is the point that was being made.

   The two submit rules are on `(max-width: 899px)` and `(min-width: 900px)`:
   mutually exclusive, and between them they cover every width exactly once.
   There is no third rule and there must not be one. */

/* "Your Survivor Card". Fixed to the bottom so the slots, the counter and the
   submit button stay visible while the list scrolls: an entrant should never
   have to scroll to find out how many picks they have made. */
.tray {
  /* Hidden until the script puts it in charge. Without scripting the tray has
     nothing to write into and its submit button would sit there permanently
     disabled, so the page falls back to the plain controls in <noscript>
     instead. `has-tray` is set by the same code that starts driving it. */
  display: none;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: rgba(13, 14, 16, .96);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -12px 34px rgba(0, 0, 0, .5);
  /* The band is deliberately full-bleed -- it is the floor of the page and it
     should reach both edges of the screen. Its horizontal padding is the page
     gutter, NOT a number of its own, so that the column inside it starts and
     ends exactly where `.wrap` does at every width. */
  padding: 11px var(--gutter) calc(11px + env(safe-area-inset-bottom, 0px));
}
body.has-tray .tray { display: block; }

/* The column inside the band. Same cap and same centring as `.wrap`, off the
   same custom property, so the two cannot drift apart again. */
.tray-inner { max-width: var(--col-max); margin: 0 auto; }

.tray-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-bottom: 9px;
}
.tray-title {
  font-size: .74em; font-weight: 800; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--text-faint);
}
.tray-count { font-size: .82em; font-weight: 700; color: var(--text-dim); }
.tray-count b { color: var(--accent); font-size: 1.14em; }

.tray-slots { display: flex; gap: 8px; margin-bottom: 10px; position: relative; }

.tslot {
  flex: 1; min-width: 0; min-height: 54px;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px;
  border-radius: var(--r-md);
  border: 1px dashed var(--line);
  background: var(--panel-2);
  color: var(--text-faint);
  font-family: inherit; font-size: .8em; font-weight: 700;
  cursor: default; text-align: left;
}
.tslot-num {
  flex: 0 0 20px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--raised); color: var(--text-faint);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .84em; font-weight: 800;
}
.tslot-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); font-weight: 800;
}
.tslot-sub { color: var(--text-faint); font-weight: 600; font-size: .9em; }
.tslot-sub .inj { margin-left: 5px; }

.tslot.is-filled {
  border-style: solid; border-color: var(--accent);
  background: rgba(0, 255, 136, .09);
  cursor: pointer;
}
.tslot.is-filled .tslot-num { background: var(--accent); color: var(--on-accent); }
.tslot.is-locked {
  border-style: solid; border-color: var(--line);
  background: var(--raised); cursor: not-allowed;
}
/* The single-slot tray on the team pool: one wide box, not three narrow ones. */
.tray-single .tslot { min-height: 58px; }

/* --- the TD tray's confirmation cards ------------------------------------
   Three slots on a 360px phone is about 105px each, and a name in a 105px box
   set on one line is a name you cannot read: the tray was showing "AJ Di...",
   "A.J. ..." and "Cam...", which is useless at the moment it matters most —
   the last surface before submitting, where an entrant checks the three picks
   they are about to commit.

   So each filled slot is a small vertical card instead: face, then the whole
   name, then the position. **No name is ever shortened.** There is no
   `text-overflow` here and no line clamp, deliberately; a name that will not
   fit two lines gets a smaller font (see `fitName` in survivor.js) and, if it
   still will not fit, takes a third line and makes the card taller. Cutting
   the name is the one thing this must never do, because two players whose
   names share a prefix are exactly the case that broke it.

   Scoped to `.tray-cards` so the team pool's one-slot tray keeps its wide
   horizontal box, which has never had this problem. */
.tray-cards { display: flex; gap: 8px; margin-bottom: 10px; align-items: stretch; }

.tray-cards .tslot {
  position: relative;
  flex: 1 1 0; min-width: 0;
  flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 4px;
  padding: 9px 5px 8px;
  min-height: 0;
  text-align: center;
}

/* The slot's number, as a corner badge rather than a chip in the flow: the
   card's own content is what should be read first. */
.tray-cards .tslot-num {
  position: absolute; top: 5px; left: 5px;
  flex: none; width: 17px; height: 17px; font-size: 10px;
}

.tslot-shot {
  width: 42px; height: 42px; border-radius: 50%;
  object-fit: cover; object-position: top center;
  background: var(--raised); border: 1px solid var(--line-hard);
  flex: none;
}
/* The initials stand-in, for a player the feed has no photograph for. Same
   size and shape as the photograph so the row does not jump when one loads. */
.tslot-shot-ph {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-dim); font-size: 13px; font-weight: 800; letter-spacing: .3px;
}

.tray-cards .tslot-name {
  /* Everything that could truncate a name is explicitly off. */
  overflow: visible; text-overflow: clip; white-space: normal;
  overflow-wrap: anywhere; hyphens: auto;
  width: 100%; line-height: 1.16;
  font-size: 12px;              /* the starting point; `fitName` steps it down */
  color: var(--text); font-weight: 800;
}
.tray-cards .tslot-pos {
  display: block; font-size: 10px; font-weight: 700; letter-spacing: .4px;
  color: var(--text-faint); text-transform: uppercase;
}
.tray-cards .tslot-pos .inj { margin-left: 4px; letter-spacing: 0; }

/* The remove control. A generous hit area is safe here precisely because the
   card behind it no longer does anything when tapped: there is no second
   action for a large target to steal. */
.tslot-x {
  position: absolute; top: 3px; right: 3px; z-index: 2;
  width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--raised); border: 1px solid var(--line-hard);
  color: var(--text-dim); font-size: 13px; font-weight: 800; line-height: 1;
  font-family: inherit; cursor: pointer; padding: 0;
}
.tslot-x::before {
  /* The real target: 44px, spilling outside the visible circle. */
  content: ""; position: absolute; top: -11px; right: -11px;
  width: 44px; height: 44px;
}
.tslot-x:hover, .tslot-x:focus-visible {
  background: rgba(255, 90, 90, .16); border-color: var(--danger);
  color: var(--danger);
}

/* The empty slot. Its job is to keep progress obvious, so it says which pick
   it is rather than just "Empty". */
.tray-cards .tslot.is-empty { justify-content: center; gap: 6px; }
.tslot-ph-icon {
  width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line-hard); color: var(--text-faint);
  font-size: 15px; font-weight: 700; line-height: 1;
}
.tslot-ph {
  font-size: 11px; font-weight: 700; color: var(--text-faint);
  line-height: 1.2;
}

/* A locked slot keeps the card shape but says so, and has no remove control:
   its player's game has kicked off and the pick is committed. */
.tray-cards .tslot.is-locked .tslot-pos { color: var(--warn); }

/* The submit inside the tray, on a phone.

   A design decision by the owner on 2026-08-21, not a bug fix: the full-bleed
   submit was correct when the tray was the only thing competing for the
   thumb's reach, but beside the desktop treatment in section 20 it read as a
   band rather than a control. The phone now gets the same idea at phone
   proportions -- inset and centred instead of edge to edge.

   This deliberately retires the byte-identical mobile promise for THIS ONE
   BUTTON. Every other phone rendering in this stylesheet is unchanged, and the
   85% is of `.tray-inner`, so the tray keeps its own padding either side.

   `display: flex` is required and not decorative: `.btn` is `inline-flex`, and
   auto margins do not centre an inline-level box -- without it the button sits
   hard left with the whole 15% as one gap on the right. Measured at 390px:
   308px wide, 27px clear on each side.

   It lives here, in the tray's own section, and NOT in section 20: that
   section is contractually desktop-only and DesktopColumnTests asserts every
   query in it is `min-width` >= 900px. `max-width: 899px` keeps this rule off
   the desktop, and `:not(.tray-lineup)` keeps Dream Team excluded, on the same
   reasoning as section 20. */

@media (max-width: 899px) {
  .tray:not(.tray-lineup) .tray-inner > .btn[data-submit] {
    display: flex;
    width: 85%;
    margin-left: auto;
    margin-right: auto;
  }
}

/* -- 13. tables and standings --------------------------------------------- */

/* `position: relative` is LOAD-BEARING and is the same declaration, for the
   same reason, as the one on `.wstrip` in section 19 -- see the long comment
   there for what it cost to learn.

   `overflow-x: auto` does not clip an absolutely-positioned descendant unless
   this element is also that descendant's containing block. Every standings
   table inside a `.scroll-x` has two: the `<span class="vh">` that names the
   rank column to a screen reader, and, below 560px, `.standings thead`, which
   is hidden the same way. Measured on 2026-08-23 with `.scroll-x` left
   `position: static`, both escaped the scroller on all three standings pages
   at every width -- the sweep that found it is in the session log for that
   date.

   Nothing was visibly wrong, because both escapees are 1px wide and sit at the
   left edge of the table, so neither widened the document. That is luck, not
   design: the same two elements one column further right, or one more hidden
   header added to a table that already scrolls -- and the Dream Team standings
   genuinely do scroll at 560px -- reproduce the tray bug exactly. This closes
   it at the cause and changes nothing visually. */
.scroll-x {
  position: relative;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Visually hidden, still read aloud. The standings' rank column has no visible
   header — it does not need one, but a column of numbers with no name is a
   worse experience read out than it is looked at. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left; padding: 11px 8px;
  border-bottom: 1px solid var(--line-soft);
  font-size: .9em; vertical-align: top;
}
th {
  color: var(--text-faint); font-size: .66em; text-transform: uppercase;
  letter-spacing: 1.2px; font-weight: 800; white-space: nowrap;
}
tbody tr:last-child td { border-bottom: none; }

tr.is-me td { background: rgba(0, 184, 255, .07); }
tr.is-winner td { background: rgba(0, 255, 136, .08); }
tr.is-winner td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.is-excluded td { opacity: .6; }

.rank {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--raised); color: var(--text-dim);
  font-size: .78em; font-weight: 800;
}
.rank-1 { background: var(--brand); color: var(--on-accent); }

.namecell { font-weight: 800; }
.picklist { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }

/* A player or team named inline in a table cell. */
.mini {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px 3px 3px; border-radius: var(--r-pill);
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: .84em; font-weight: 700; white-space: nowrap;
}
.mini-void { border-color: rgba(255, 176, 32, .5); color: var(--warn); }
.mini-hit  { border-color: rgba(0, 255, 136, .5); color: var(--accent); }

/* -- 14. rules page -------------------------------------------------------- */

.toc { display: flex; flex-wrap: wrap; gap: 8px; }
.toc a {
  padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--panel-2);
  font-size: .82em; font-weight: 700; color: var(--text-dim);
}
.toc a:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }

/* -- 15. messages and form controls ---------------------------------------- */

.msg {
  padding: 12px 14px; border-radius: var(--r-md); margin-bottom: 14px;
  font-size: .9em; border: 1px solid;
}
.msg-success { background: rgba(0, 255, 136, .1); color: var(--accent);
               border-color: rgba(0, 255, 136, .5); }
.msg-error   { background: rgba(255, 77, 77, .1); color: var(--danger);
               border-color: rgba(255, 107, 107, .5); }
.msg-info    { background: rgba(255, 255, 255, .05); color: var(--text-dim);
               border-color: var(--line); }
.msg-warning { background: rgba(255, 176, 32, .1); color: var(--warn);
               border-color: rgba(255, 176, 32, .5); }

label {
  display: block; color: var(--text-dim); font-weight: 700;
  margin-bottom: 8px; font-size: .86em;
}
select, input[type=text], input[type=number] {
  width: 100%; min-height: var(--tap); padding: 12px 14px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); border-radius: var(--r-md);
  font-size: 16px; font-family: inherit;
}
select:focus, input:focus { outline: none; border-color: var(--accent); }

.backlink {
  display: block; text-align: center; margin-top: 22px;
  color: var(--text-faint); font-size: .88em;
}

/* The affiliation notice, on every page of this area. Quiet by design: it is
   there to be found and read, not to be the loudest thing on a page about a
   contest. `--text-faint` is the site's smallest print and this is what that
   value is for. */
.disclaimer {
  max-width: 62ch; margin: 16px auto 0;
  text-align: center; line-height: 1.5;
  color: var(--text-faint); font-size: .78em;
}

/* Keyboard focus. The team cards hide their radio input so the whole card can
   be the tap target, which would otherwise take the focus ring with it, so the
   card wears the ring on the input's behalf. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tcard:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
.tab:focus-visible, .chip:focus-visible { outline-offset: 1px; }

/* -- 16. responsive and motion --------------------------------------------- */

@media (min-width: 620px) {
  /* The gutter widens ONCE, here, and the page and the tray both follow it.
     This single line is the fix: it used to be `body { padding: 26px 20px }`,
     which moved the column in by 20px and left the tray on 14px, and that 6px
     was the whole misalignment. Declared on `:root` beside where the token is
     defined, so the override sits with the default rather than in whichever
     element happened to need it first. */
  :root { --gutter: 20px; }
  body { padding: 26px var(--gutter) 48px; }
  /* No reduction here any more: see `--tray-h`. The tray is 225-228px on a
     phone and 211px on a desktop, so the phone needs *more* reserved space,
     not less. Subtracting 20px here is what would put the last player behind
     the submit button. */
  .panel { padding: 24px; }
  .hero { padding: 26px; }
  .hero-name { font-size: 2.3em; }
  .prow-list { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
  .tab { font-size: .92em; }
}

/* -- the standings on a phone --------------------------------------------- */

/* Below 560px the standings stop being a table and become one card per entrant.
   `.scroll-x` is still there as a safety net, but on a phone it should never
   have anything to scroll.

   Why not just let it scroll sideways, which is what it did until now: this is
   the page entrants screenshot and post. A horizontally scrolled table
   screenshots as whatever slice happened to be on screen, so the picture that
   gets shared is missing the columns it was taken to show — and the entrant has
   no way to tell, because the cut edge looks like the edge of the table. A row
   that fits is the only version of this that survives being shared.

   Nothing is dropped. Every column survives as a labelled line, which is why
   the cells carry `data-label`; hiding "Players used" on a phone would take the
   pool's whole point with it. */
@media (max-width: 559px) {
  /* The card on a phone. The picture is 16:9 here (see `.hero-photo`), which
     is most of what a 360px screen shows before the panel starts, so the panel
     gets a little tighter to compensate and the name comes down half a step.
     Nothing about legibility changes with width any more: the only type over
     the photograph is inside the two pills, and both carry their own opaque
     background at every size. */
  .hero-body { padding: 14px 16px 18px; }
  .hero-name { font-size: 1.72em; }
  .hero-photo-top { padding: 11px 12px; }

  .standings, .standings tbody, .standings tr, .standings td { display: block; width: 100%; }

  /* The header row is the one thing that goes: its labels reappear per cell
     below. Hidden from sight but not from a screen reader, which still gets a
     coherent table. */
  .standings thead { position: absolute; width: 1px; height: 1px; overflow: hidden;
                     clip: rect(0 0 0 0); clip-path: inset(50%); }

  .standings tr {
    position: relative;
    padding: 14px 14px 14px 52px;
    border-bottom: 1px solid var(--line-soft);
  }
  .standings tr:last-child { border-bottom: none; }

  /* The row highlights moved from the cells to the row, because with
     `display:block` the cells no longer tile it. */
  .standings td { border: none; padding: 0; background: none !important; }
  .standings tr.is-me { background: rgba(0, 184, 255, .07); }
  .standings tr.is-winner { background: rgba(0, 255, 136, .08); box-shadow: inset 3px 0 0 var(--accent); }
  .standings tr.is-excluded { opacity: .6; }
  .standings tr.is-winner td:first-child { box-shadow: none; }

  /* Rank sits in the gutter the row's left padding reserves for it, so the
     name starts at a constant x down the whole list. */
  .standings .rankcell { position: absolute; top: 14px; left: 12px; width: auto; }

  .standings .namecell {
    font-weight: 800; font-size: 1.02em; letter-spacing: -.2px;
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  }
  .standings .statuscell { margin-top: 7px; display: flex; gap: 6px; flex-wrap: wrap; }

  /* Weeks and TDs read as a small stat strip rather than two stacked lines.
     `width: auto` is load-bearing: the block rule above sets every cell to
     100%, which makes an inline-flex element take a whole line to itself and
     puts these back on separate rows. */
  .standings .numcell {
    display: inline-flex; align-items: baseline; gap: 5px; width: auto;
    margin: 9px 14px 0 0; font-variant-numeric: tabular-nums;
  }
  .standings .numcell::before {
    content: attr(data-label);
    color: var(--text-faint); font-size: .64em; font-weight: 800;
    text-transform: uppercase; letter-spacing: 1.2px;
  }

  .standings .pickscell { margin-top: 11px; }
  .standings .pickscell::before {
    content: attr(data-label);
    display: block; margin-bottom: 6px;
    color: var(--text-faint); font-size: .64em; font-weight: 800;
    text-transform: uppercase; letter-spacing: 1.2px;
  }
  /* The picks wrap onto as many lines as they need instead of forcing the
     table wider, which is what was pushing the columns off-screen. */
  .standings .picklist { display: flex; flex-wrap: wrap; gap: 6px; }
  /* `.mini` is nowrap everywhere else, which is right in a wide table cell and
     wrong in a ~256px card: "Amon-Ra St. Brown" would simply run out of the
     pill. It wraps inside the pill here instead. Truncating with an ellipsis
     was the alternative and is worse — a half-printed name on a leaderboard
     somebody is about to post is exactly the wrong thing to shorten. */
  .standings .mini { max-width: 100%; white-space: normal; }

  /* -- the contest table on the rules page --------------------------------
     The same stacked card, for the same reason, on the four-column summary
     of the contests (`rules.html`). That table cannot be made to fit: `th`
     is `white-space: nowrap` site-wide, which is right in a wide table and
     is why "Entries close" alone claims 123px, and the four columns together
     will not go below a 361px min-content. On a 360px phone that pushed the
     DOCUMENT to 396px and the entire rules page scrolled sideways. Shortening
     the date from "Thu 10 Sep 2026, 00:20" to "Sep 10" was worth doing and
     was not enough on its own -- the header, not the value, is what sets that
     column's floor.

     Side-scrolling was the other option and is worse here than it is on the
     standings: this page is the contract. A column that has to be dragged
     into view is a term somebody can truthfully say they never saw.

     The header row is hidden with `height: 0` rather than the
     `position: absolute` the standings' `thead` uses, deliberately. An
     absolutely-positioned box inside a scroller whose containing block it is
     not is the whole of the 2026-08-22 tray bug (see `.wstrip`, section 19,
     and `.scroll-x` above): it escapes the scroller and widens the document.
     This table is not in a scroller today. Hiding it without leaving the flow
     means it cannot become one if it ever is. It stays in the accessibility
     tree either way. */
  .contests,
  .contests thead,
  .contests tbody,
  .contests tr,
  .contests td { display: block; width: 100%; }

  /* `display: block` on the header row is not decoration: left as a
     `table-header-group` it keeps laying its cells out as a table, and
     "Entries close" at `white-space: nowrap` held that row at 327px inside a
     290px column -- 2px past the edge of a 360px screen, which is a document
     that still scrolls sideways however invisible the row is. As a block it
     takes the column's width, and `overflow: hidden` clips what will not fit
     instead of letting it widen the page. `height: 0` is what takes it out of
     sight without taking it out of the accessibility tree. */
  .contests thead { height: 0; overflow: hidden; }

  .contests tr {
    padding: 14px 0;
    border-bottom: 1px solid var(--line-soft);
  }
  .contests tr:last-child { border-bottom: none; }
  .contests td { border: none; padding: 0; }

  /* The contest's name leads the card; the other three read as a small
     labelled strip under it, exactly as `.standings .numcell` does. `width:
     auto` is load-bearing for the same reason it is there: the block rule
     above sets every cell to 100%, which would put each of the three on a
     line of its own. */
  .contests td[data-label="Contest"] {
    font-weight: 800; font-size: 1.02em; letter-spacing: -.2px;
  }
  .contests td:not([data-label="Contest"]) {
    display: inline-block; width: auto;
    margin: 9px 16px 0 0; vertical-align: top;
  }
  /* The prize is the one value that runs to two lines when a pool pays weekly
     as well, so it takes a row of its own rather than deciding, per pool and
     per screen, whether "Starts" fits beside it. Three cards that wrap
     differently from each other read as a mistake even when each one is
     correct. */
  .contests td[data-label="Prize"] { display: block; width: 100%; }
  .contests td:not([data-label="Contest"])::before {
    content: attr(data-label);
    display: block; margin-bottom: 2px;
    color: var(--text-faint); font-size: .64em; font-weight: 800;
    text-transform: uppercase; letter-spacing: 1.2px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}


/* -- 17. Dream Team --------------------------------------------------------
   The third contest. Everything here is additive: no rule above is changed,
   so both survivor pools render exactly as they did.

   It introduces no tokens of its own. `--accent-3`, `--accent-3-deep` and
   `--card-edge-alpha-dream` are declared in section 1 with the rest, and the
   reasoning for choosing amber is there too.
   -------------------------------------------------------------------------- */

/* `--accent-3` and `--accent-3-deep` are declared with the other accents in
   section 1, not here: this file keeps one token block and a second `:root`
   at the bottom is where tokens go to be forgotten. */

/* The hairline across the top of the card, and the edge colour the panel
   picks up. Matches the shape of the `.hero-team` / `.hero-td` rules above so
   the three cards differ in hue and in nothing else. */
.hero-dream::before { --hero-rule: linear-gradient(90deg, #ffc247, #ff9500); }

/* Third in the set above, and the alpha is measured rather than picked. Amber
   is a light colour, so a value that looks reasonable next to the other two in
   source reads far too faint on screen: .18 measures 1.50:1 against the card
   surface where green and blue sit at ~3.05:1, which would have made the Dream
   Team card look like the disabled one of the three. .43 puts it on 3.05:1 and
   the three edges finally look equally held back. */
.hero-dream {
  --card-edge:    rgba(255, 194, 71, var(--card-edge-alpha-dream));
  --card-edge-on: var(--accent-3);
}

/* The SVG fallback scene's tints, read through `currentColor` by
   `_pool_art.html`. Same four names the other two themes define. */
.hero-dream {
  --art-key:   var(--accent-3);
  --art-alt:   var(--accent-3-deep);
  --art-deep:  #c96f00;
  --art-field: var(--accent-3);
}

/* -- the lineup: eight rows, each on its own clock ------------------------- */

/* This replaced a strip of seven small cards along the bottom of the screen.
   The cards were the lineup, squeezed into the space left over by a list of
   nine hundred players; these rows are the page, and the list is a sheet that
   opens over them.

   A row is deliberately the same shape as a `.prow` in the picker — photograph,
   name, meta line, something on the right — so that tapping a player in the
   sheet and watching him appear in the lineup is visibly the same object
   moving rather than two unrelated renderings of one person. */

.lineup-rows { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }

.lrow {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--tap);
  padding: 9px 11px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--raised);
  cursor: pointer;
  transition: border-color .12s ease, transform .08s ease;
}
.lrow:hover { border-color: rgba(0, 255, 136, .45); }
.lrow:active { transform: scale(.995); }

/* An empty slot is drawn as an invitation rather than as a hole: a dashed
   edge, a plus, and the words "Tap to draft a player". Seven empty rows on a
   Tuesday should read as a lineup waiting to be built, not as seven errors. */
.lrow.is-empty {
  border-style: dashed;
  background: var(--panel-2);
}
.lrow.is-empty .lrow-empty-label {
  display: block; font-weight: 700; color: var(--text-dim); font-size: .95em;
}
/* The position the slot wants, on the same line as the invitation rather than
   on a second line under it. One line reads as one sentence — "tap to draft a
   player, a running back" — where two read as a label and a caption. */
.lrow-sep { color: var(--text-faint); font-weight: 400; }
.lrow-empty-pos { color: var(--text-faint); font-weight: 600; }

/* The slot badge: a fixed-width column so the eight codes line up down the
   left edge and the eye can find "TE" without reading the others. It is wide
   enough for FLEX, which is the longest of them. */
.lrow-slot {
  flex: 0 0 46px; min-width: 46px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
}
.lrow-code {
  display: inline-block; padding: 2px 6px;
  border-radius: var(--r-pill);
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: 10px; line-height: 1.2; font-weight: 900; letter-spacing: .8px;
  text-transform: uppercase; color: var(--text-dim);
}
.lrow.is-filled .lrow-code {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
/* What the slot takes, under its code. On the filled rows this is the only
   remaining reminder of why a swap will not offer a tight end for RB2. */
.lrow-takes {
  font-size: 9px; font-weight: 700; letter-spacing: .2px;
  color: var(--text-faint); white-space: nowrap;
}

.lrow-shot {
  width: 40px; height: 40px; flex: 0 0 40px;
  border-radius: 50%; object-fit: cover;
  background: var(--panel-2); border: 1px solid var(--line);
}
.lrow-shot-ph {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; color: var(--text-faint);
}

/* The plus sits where the photograph does on a filled row, so a row does not
   jump sideways the moment it is filled. */
.lrow-plus {
  width: 40px; height: 40px; flex: 0 0 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px dashed var(--line); color: var(--text-faint);
  font-size: 20px; font-weight: 300; line-height: 1;
}

.lrow-body { min-width: 0; flex: 1; }
.lrow-name {
  display: block; font-weight: 800; font-size: .97em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lrow-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 2px; font-size: .78em; color: var(--text-dim);
}

/* The row's own countdown. This is the piece of the redesign that carries the
   lock rule: there is no single deadline on this page, so every filled row
   says when *it* closes and they routinely disagree by three days. */
.lrow-lock {
  display: block; margin-top: 3px;
  font-size: .74em; font-weight: 700; letter-spacing: .2px;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.lrow-lock.is-soon { color: var(--warn); }
.lrow-lock.is-locked { color: var(--text-faint); }

/* A locked row. Greyed and inert, but still legible — an entrant looking back
   at Sunday evening wants to read who was in the slot, not be told it is gone.
   `is-passed` is the same state reached by the countdown ticking through zero
   on a page that was already open. */
.lrow.is-locked, .lrow.is-passed {
  cursor: default; opacity: .62; filter: grayscale(.4);
  border-style: solid;
}
.lrow.is-locked:hover, .lrow.is-passed:hover { border-color: var(--line); }

/* The active row: the one the sheet is currently picking for. Marked twice
   over — a ring and a lift — rather than by colour alone, which would be
   invisible to anybody who cannot separate amber from the panel. */
.lrow.is-active {
  border-color: var(--accent-3);
  box-shadow: 0 0 0 2px rgba(255, 194, 71, .45), 0 6px 18px rgba(0, 0, 0, .35);
  transform: translateY(-1px);
}

/* The swap control. A real button rather than a click target on the row,
   because "change this player" and "open this slot" are the same action here
   and the icon is what makes that discoverable on a row that already has a
   player in it. */
.lrow-swap {
  margin-left: auto; flex: 0 0 auto;
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text-dim); font-size: 15px; line-height: 1;
  cursor: pointer;
}
.lrow-swap:hover, .lrow-swap:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
.lrow-swap-off { cursor: default; opacity: .6; }
.lrow-swap-off:hover { border-color: var(--line); color: var(--text-dim); }

/* A settled slot shows what it scored instead of a swap control. Tabular so a
   column of eight of them lines up on the decimal point, and always two
   places — including the minus a turnover can put in front. */
.lrow-pts {
  margin-left: auto; padding-left: 10px;
  font-size: 1.02em; font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent-3); white-space: nowrap;
}

/* A locked slot whose player has no stat line yet: the five minutes between a
   slot freezing and its kickoff, and any player whose game is live but who has
   not done anything countable.

   It prints a dash rather than 0.00 on purpose. "Nothing has happened yet" and
   "he is on nothing" are different sentences and this contest is careful about
   the difference everywhere else — see `live.lineup_total` — so the row does
   not claim a score it has not been given. Dimmed and at the same width as a
   real number, so the column still lines up and the dash reads as "waiting"
   rather than as a result. The poller fills it in place the moment a stat
   lands; there is no reflow, because the cell is already here. */
.lrow-pts.is-pending {
  color: var(--text-dim);
  font-weight: 600;
}

/* The number alone is not a sentence. A screen reader on a locked row would
   otherwise announce "12.40" with nothing to say what 12.40 is, and "—" with
   nothing at all. Visually hidden rather than `display:none`, which would take
   it out of the accessibility tree along with the pixels. */
.lrow-pts-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* On a narrow phone the "RB or WR" line under the code is the first thing that
   can go: the sheet says what the slot takes as soon as it opens. */
@media (max-width: 400px) {
  .lrow { gap: 8px; padding: 8px 9px; }
  .lrow-slot { flex-basis: 38px; min-width: 38px; }
  .lrow-takes { display: none; }
  .lrow-shot, .lrow-plus { width: 34px; height: 34px; flex-basis: 34px; }
}

/* -- the picker sheet ------------------------------------------------------ */

/* A bottom sheet over the lineup: fixed to the viewport, sliding up from the
   bottom edge, with the page scrimmed behind it and the lineup left exactly
   where it was.

   It used to be a panel in the document flow, and the reason that had to
   change is a phone. A panel in the flow is laid out *between* the row that
   was tapped and the rows below it, so opening the picker on the seventh slot
   pushed that row off the top of the screen and left the entrant reading a
   list of nine hundred players with no visible reminder of which slot it was
   filling. A sheet cannot do that: the lineup does not move, and closing the
   sheet reveals the row already filled in.

   Three structural rules hold it together, and each is load-bearing:

   1. the sheet is `position: fixed` against the *viewport*, so no ancestor of
      it may have a `filter`, `backdrop-filter`, `transform` or `perspective` —
      any of those makes that ancestor the containing block and the sheet would
      be positioned against a `<form>` instead of the screen. That is why the
      scrim is a sibling and not a parent, even though a parent would be the
      obvious way to write it;
   2. the sheet is a flex column with exactly one growing child, so the head
      and the search field stay put and only the list scrolls;
   3. the slide is driven by `--sheet-drag` rather than by writing `transform`
      directly, because a drag has to be able to move the sheet without
      destroying the `translateX` that centres it on a wide screen. */

.sheet-scrim {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(0, 0, 0, .64);
  opacity: 0;
  transition: opacity .24s ease;
}
.sheet-scrim.is-open { opacity: 1; }
.sheet-scrim[hidden] { display: none; }

.picker-sheet {
  position: fixed; z-index: 71;
  left: 0; right: 0;
  /* Set by the script from `visualViewport` when a software keyboard is up,
     so the sheet sits on top of the keyboard rather than behind it. */
  bottom: var(--sheet-bottom, 0px);
  display: flex; flex-direction: column;
  max-height: var(--sheet-max, 86vh);
  padding: 0 12px calc(10px + env(safe-area-inset-bottom, 0px));
  border: 1px solid var(--accent-3); border-bottom: 0;
  border-radius: 18px 18px 0 0;
  background: var(--panel);
  box-shadow: 0 -14px 46px rgba(0, 0, 0, .62);
  /* 101% rather than 100%: a 1px border and a fractional device pixel can
     leave a hairline of the sheet showing along the bottom edge at 100%. */
  transform: translateY(var(--sheet-drag, 101%));
  transition: transform .26s cubic-bezier(.22, .68, .3, 1);
}
.picker-sheet.is-open { --sheet-drag: 0px; }
.picker-sheet[hidden] { display: none; }
/* During a drag the position is written per pointer move, and a transition
   would make the sheet lag the finger. */
.picker-sheet.is-dragging { transition: none; }

/* The drag handle. `aria-hidden` in the markup and decorative here: dragging
   is a second way to do what the X button does, never the only way. */
.sheet-grip {
  flex: 0 0 auto;
  display: flex; justify-content: center;
  padding: 9px 0 6px;
  cursor: grab; touch-action: none;
}
.sheet-grip span {
  width: 42px; height: 4px; border-radius: 2px; background: var(--line);
}
.sheet-grip:active { cursor: grabbing; }

.sheet-head {
  flex: 0 0 auto;
  display: flex; align-items: flex-start; gap: 10px;
  padding-bottom: 10px;
}
.sheet-title {
  flex: 1; min-width: 0;
  font-size: 1.02em; font-weight: 800; line-height: 1.25;
}
.sheet-title b { color: var(--accent-3); font-weight: 900; }
.sheet-sub {
  display: block; margin-top: 3px;
  font-size: .72em; font-weight: 600; color: var(--text-faint);
}
.sheet-x {
  flex: 0 0 auto;
  width: 34px; height: 34px; padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text-dim); font-size: .86em; line-height: 1;
  font-family: inherit; cursor: pointer;
}
.sheet-x:hover, .sheet-x:focus-visible {
  border-color: var(--accent); color: var(--accent);
}

/* Search and the filter chips, pinned above the list. The way out of nine
   hundred players is never scrolled off the top of the sheet. */
.sheet-top { flex: 0 0 auto; }
.sheet-top .filter-row { margin-top: 8px; }

/* The only scrolling region inside the sheet. `overscroll-behavior: contain`
   is what stops a flick past the end of the list from scrolling the page
   underneath, which on a phone reads as the sheet having come unstuck. */
.sheet-scroll {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 8px 0 2px;
}

/* The page behind does not scroll while the sheet is up. Set on <body> by the
   script and removed on close. */
body.sheet-lock { overflow: hidden; }

/* On anything wider than a phone the sheet stops being full-bleed and becomes
   a centred panel still anchored to the bottom edge. The `translateX` is why
   the drag writes a custom property instead of the transform itself. */
@media (min-width: 620px) {
  .picker-sheet {
    left: 50%; right: auto;
    width: min(560px, calc(100% - 24px));
    transform: translateX(-50%) translateY(var(--sheet-drag, 101%));
  }
}

/* "Locks in 2h 14m" on a player's row in the sheet — the same clock, from the
   same `data-lock` attribute and the same ticker, that his lineup row will
   carry the moment he is drafted into it. Seeing it before the tap is the
   point: the lock is the one fact that decides whether a pick is worth making.
   */
.prow-lock {
  display: block; margin-top: 3px;
  font-size: .75em; font-weight: 700; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.prow-lock.is-soon { color: var(--warn); }

/* -- the progress ring and submit ------------------------------------------ */

.tray-lineup-inner {
  display: flex; align-items: center; gap: 12px;
}

/* "N of 8 spots filled" as a ring rather than a bar, because the number in the
   middle is the thing being read and the ring is the thing being glanced at.
   Drawn with a conic gradient off one custom property, so the script sets a
   single number and the browser does the rest. */
.ring {
  flex: 0 0 auto;
  width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: conic-gradient(
    var(--accent) calc(var(--ring-pct, 0) * 1%),
    var(--line) 0
  );
}
.ring::before {
  content: ""; position: absolute;
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(13, 14, 16, .96);
}
.ring { position: relative; }
.ring-num {
  position: relative;
  font-size: .96em; font-weight: 900; color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.tray-progress {
  flex: 1; min-width: 0;
  font-size: .84em; color: var(--text-dim); line-height: 1.35;
}
.tray-progress b { color: var(--text); font-weight: 800; }
.tray-progress .small { display: block; }

/* `width: auto` is load-bearing and looks redundant. `.btn` is `width: 100%`
   for the full-width buttons it is normally used as, and `flex: 0 0 auto`
   keeps that 100% as the flex basis — so the submit button was laid out at the
   full width of the tray *next to* the ring and the counter, and overflowed
   the viewport by its own left offset. At 390px that put 48 pixels of it off
   the right edge. */
.tray-lineup .btn { flex: 0 0 auto; width: auto; margin: 0; }

/* Below ~430px the three items stop fitting on one line in any useful way.
   The row is ring (44px, fixed) + counter (`flex: 1`, and `min-width: 0`, so
   it crushes rather than overflows) + submit (~167px, measured). At a 430px
   viewport the gutter leaves 402px, so the counter gets
   402 - 44 - 24(gaps) - 167 = 167px; at 390px it gets 127px, and "8 of 8
   spots filled" wraps to three lines beside a button that is not using its
   room. Nothing scrolls sideways -- this is a crush, not an overflow, which
   is why it read as a layout that merely looked bad.

   So the submit takes its own row, at the same 85% centred it gets on the
   team and TD trays below 900px (section 12). `flex: 0 0 85%` and NOT
   `flex: 1 0 100%; width: 85%`: a definite flex-basis overrides `width` when
   the flex base size is computed, so that spelling would silently draw the
   full-bleed band that section 12 exists to have stopped drawing. The wrap
   is unconditional rather than a hope -- the ring alone is ~11% of the row,
   so 11% + counter + 85% exceeds 100% at every width in this range.

   This retires the `.small` cliff that used to live at 400px. Hiding "Empty
   slots score zero" was buying horizontal room for a row that no longer has
   to fit, and the note is worth reading on the phone widths that were losing
   it. `gap: 9px` is kept from that rule and now sets the row gap too, which
   is the same 9-10px the other two trays put between their slots and their
   submit.

   Filed in section 14, the Dream Team tray's own section, for the reason
   section 12 gives at length: section 20 is contractually desktop-only and
   DesktopColumnTests fails any max-width query put there. */
@media (max-width: 430px) {
  .tray-lineup-inner { flex-wrap: wrap; gap: 9px; }
  .tray-progress { flex: 1 1 auto; }
  .tray-lineup .btn { flex: 0 0 85%; margin: 0 auto; }
}

/* -- the scoring table on the lineup page ---------------------------------- */

/* The banner over the picker used to end "Nothing else scores." That sentence
   was true of six columns and false of eleven, so it is a table now, printed
   from the same constants the settlement pass multiplies by. */
.scoring-panel { margin-top: 14px; }
.scoring-table { font-size: .86em; }
.scoring-table td { padding: 5px 8px; }
.scoring-worth { font-weight: 700; color: var(--accent-3); white-space: nowrap; }

/* -- points on a row ------------------------------------------------------- */

/* The score a locked slot produced, on the right of a picker row where the add
   button sits on a pickable one. Tabular figures so a column of them lines up
   on the decimal point. The Dream Team lineup rows have their own,
   `.lrow-pts`, drawn to the same rule. */
.prow-pts {
  margin-left: auto; padding-left: 10px;
  font-size: 1.02em; font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent-3); white-space: nowrap;
}

/* -- the locked-slot header ------------------------------------------------ */

/* Was three inline styles on the TD page, and named when the Dream Team page
   repeated it seven times. Only the TD page uses it now: a Dream Team slot no
   longer has a header of its own, because the whole row is the slot. */
.slot-head {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-bottom: 10px;
}
.slot-head h3 { font-size: 1em; }

/* -- a week of the entrant's own lineups ----------------------------------- */

.lineup-week { padding: 16px 0; border-bottom: 1px solid var(--line-soft); }
.lineup-week:last-child { border-bottom: none; padding-bottom: 0; }
.lineup-week:first-child { padding-top: 4px; }

.lineup-week-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin-bottom: 10px;
}
.lineup-week-head h3 { margin: 0; font-size: 1.02em; }

.lineup-week-pts {
  font-size: .9em; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.lineup-week-pts b { font-size: 1.24em; color: var(--accent-3); font-weight: 900; }

/* -- the weekly winners page ----------------------------------------------- */

.winner-row {
  padding: 14px 0; border-bottom: 1px solid var(--line-soft);
}
.winner-row:last-child { border-bottom: none; }
.winner-row.is-me { background: rgba(0, 184, 255, .07); }

.winner-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 6px;
}

.winner-week {
  font-size: .64em; font-weight: 900; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-faint);
  padding: 4px 9px; border-radius: var(--r-pill);
  background: var(--panel-2); border: 1px solid var(--line);
}

.winner-name {
  font-weight: 800; font-size: 1.04em; letter-spacing: -.2px;
  display: inline-flex; align-items: center; gap: 7px; flex-wrap: wrap;
}

.winner-pts {
  margin-left: auto; font-variant-numeric: tabular-nums;
  font-size: .86em; color: var(--text-dim);
}
.winner-pts b { font-size: 1.3em; color: var(--accent-3); font-weight: 900; }

/* The recorded arithmetic. Small and dense on purpose: it is a paragraph of
   working that most readers skim and the occasional reader checks line by
   line, and it must be present for the second kind of reader without crowding
   out the result for the first. */
.winner-reason {
  margin: 0 0 6px; font-size: .82em; line-height: 1.55;
  color: var(--text-dim);
}

@media (max-width: 559px) {
  /* The total goes under the name rather than fighting it for the right edge
     on a narrow screen. */
  .winner-pts { margin-left: 0; width: 100%; }
}

/* --- the live layer ------------------------------------------------------
   Provisional numbers, and the one visual job they have: never being mistaken
   for official ones.

   A live figure differs from a settled one in three ways at once — colour, a
   flag, and (on the leaderboard) a dot beside the total — because any single
   one of them fails somewhere. Colour alone is invisible to a colour-blind
   entrant and lost in a screenshot; a flag alone is easy to skim past on a row
   of eight; a dot alone says nothing about what it means. Three cues, one
   meaning, and the meaning is spelled out in words at least once per page.

   Everything here disappears on its own the moment official grading runs,
   because the template stops emitting `is-live` — there is no state to clear
   and no second stylesheet to keep in step. */

.live-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.5;
  /* Dark ink on the cyan chip: 9.6:1, which survives a phone in daylight. */
  color: #032a35;
  background: var(--live, #22d3ee);
  vertical-align: middle;
}

/* The pulse marks a number that is still moving. Motion is a real cue and a
   real accessibility hazard, so it is opt-in to the browser's own setting
   rather than to ours: a reader who has asked for less motion keeps every
   other cue and loses only this one. */
@media (prefers-reduced-motion: no-preference) {
  .live-tag { animation: live-pulse 2.4s ease-in-out infinite; }
}
@keyframes live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* A slot's provisional score. Same size and position as a settled one, so the
   row does not reflow when grading lands — only the colour and the flag go. */
.lrow-pts.is-live {
  color: var(--live, #22d3ee);
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  line-height: 1.1;
}

.stat-val.is-live { color: var(--live, #22d3ee); }

.live-banner {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid rgba(34, 211, 238, 0.35);
  background: rgba(34, 211, 238, 0.08);
  font-size: 0.85rem;
  line-height: 1.5;
}

.standings .numcell b.is-live { color: var(--live, #22d3ee); }

.live-dot {
  color: var(--live, #22d3ee);
  font-size: 0.6rem;
  vertical-align: middle;
  margin-left: 2px;
}

/* A TD pool card whose player has already scored. The card is locked, so this
   is the only thing on it that can still change. */
.tslot.is-live {
  border-color: rgba(34, 211, 238, 0.55);
  box-shadow: inset 0 0 0 1px rgba(34, 211, 238, 0.25);
}

.live-note {
  margin-top: 10px;
  font-size: 0.8rem;
  line-height: 1.5;
}

/* -- 18. the pick page: hero, run strip, week chips, board ----------------

   Written mobile-first, because that is where this page failed. At 390px the
   old layout drew thirty-two full-width team cards for sixteen games, put the
   date on every one of them, and kept the entrant's own live pick below all of
   it. Everything below is a reply to one of those three facts.

   Widths here assume the narrowest phone this site sees: 390px minus 14px of
   body padding either side, minus a 16px panel, leaves about 330px for a game
   row. Two team halves and an "@" have to fit in that, so the halves are two
   short lines rather than one long one. */

/* --- the current pick ----------------------------------------------------
   The first thing on the page and, on a Thursday evening, the only thing an
   entrant came for. It carries its own state in three ways at once, the same
   rule the live layer follows: a word, a colour and (when live) a dot. */

.pickhero {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 14px 15px 12px;
  margin-bottom: 12px;
  overflow: hidden;
}

/* The state rule across the top. One element, one variable, seven states, so a
   new state is a colour rather than a new block of CSS. */
.pickhero::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: var(--hero-state, var(--text-faint));
}

.pickhero.is-open    { --hero-state: var(--accent); }
.pickhero.is-locked  { --hero-state: var(--warn); }
.pickhero.is-live    { --hero-state: var(--live, #22d3ee); }
.pickhero.is-pending { --hero-state: var(--text-faint); }
.pickhero.is-won     { --hero-state: var(--accent); }
.pickhero.is-lost    { --hero-state: var(--danger); }
.pickhero.is-void    { --hero-state: var(--warn); }
.pickhero.is-empty   { --hero-state: var(--line-hard); }

.pickhero-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px;
}
.pickhero-week {
  font-size: .68em; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint);
}
.pickhero-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  font-size: .68em; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--hero-state, var(--text-dim));
  border: 1px solid currentColor;
  /* A tint of the state colour behind the text. `color-mix` degrades to no
     background at all on a browser that lacks it, which is a card that still
     reads correctly rather than one that breaks. */
  background: color-mix(in srgb, var(--hero-state, transparent) 12%, transparent);
}

.pickhero-body {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap;
}
.pickhero-team { display: flex; align-items: center; gap: 10px; min-width: 0; }
.pickhero-name { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.pickhero-name b {
  font-size: 1.5em; font-weight: 900; letter-spacing: -.5px;
}
.pickhero-nick {
  font-size: .78em; color: var(--text-dim); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pickhero-vs {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-dim); font-size: .84em; font-weight: 700;
}
.pickhero-prep { color: var(--text-faint); font-weight: 600; }
.pickhero-opp { font-weight: 800; color: var(--text); }

/* Present but empty until there is a settled score, so landing one does not
   reflow the card under a thumb. */
.pickhero-score {
  margin-left: auto;
  font-size: 1.3em; font-weight: 800; letter-spacing: -.5px;
  color: var(--text-dim); white-space: nowrap;
}
.pickhero-score b { color: var(--text); font-weight: 900; }
.pickhero-dash { color: var(--text-faint); margin: 0 2px; }
.pickhero.is-won  .pickhero-score b { color: var(--accent); }
.pickhero.is-lost .pickhero-score b { color: var(--danger); }

.pickhero-foot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  margin-top: 10px; padding-top: 9px;
  border-top: 1px solid var(--line-soft);
  font-size: .8em;
}
.pickhero-kick { color: var(--text); font-weight: 700; }
.pickhero-note { color: var(--text-dim); }

/* --- the run so far ------------------------------------------------------
   A strip above the board rather than a panel below it. The chips answer the
   common question (which weeks are in, with whom, how they ended) in one line;
   the table is the same rows spelled out, for when they do not. */

.runstrip {
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  padding: 11px 13px;
  margin-bottom: 16px;
}
.runstrip-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 9px;
}
.runstrip-title {
  font-size: .68em; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-faint);
}
.runstrip-count { font-size: .74em; color: var(--text-faint); font-weight: 700; }

/* Scrolls sideways rather than wrapping to four rows in week fifteen. */
.runstrip-chips {
  display: flex; gap: 7px; overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; padding-bottom: 2px;
}
.runstrip-chips::-webkit-scrollbar { display: none; }

.rchip {
  display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
  padding: 5px 9px 5px 7px; border-radius: 999px;
  border: 1px solid var(--line-hard); background: var(--raised);
  color: var(--text); font-size: .76em; font-weight: 700;
  text-decoration: none;
}
.rchip:hover { text-decoration: none; border-color: var(--text-faint); }
.rchip-wk { color: var(--text-faint); font-weight: 800; font-size: .9em; }
.rchip-abbr { font-weight: 800; letter-spacing: -.2px; }
/* The result, in the same colours the badges use elsewhere on the page. */
.rchip.is-survived   { border-color: rgba(0, 255, 136, .5); }
.rchip.is-survived .rchip-abbr { color: var(--accent); }
.rchip.is-eliminated { border-color: rgba(255, 77, 77, .5); }
.rchip.is-eliminated .rchip-abbr { color: var(--danger); }
.rchip.is-void       { border-color: rgba(255, 176, 32, .5); }
.rchip.is-void .rchip-abbr { color: var(--warn); }

.runstrip-more { margin-top: 10px; }
.runstrip-more > summary {
  cursor: pointer; list-style: none;
  font-size: .78em; font-weight: 700; color: var(--text-dim);
  padding: 4px 0;
}
.runstrip-more > summary::-webkit-details-marker { display: none; }
.runstrip-more > summary::before {
  content: "\25B8"; display: inline-block; margin-right: 6px;
  color: var(--text-faint); transition: transform .15s ease;
}
.runstrip-more[open] > summary::before { transform: rotate(90deg); }
.runstrip-more > summary:hover { color: var(--text); }

/* --- which week ----------------------------------------------------------
   Chips, because the page used to change week under the entrant with no way
   back to the one they were watching. The dot says what the week wants without
   costing a second line, and every dot has a `.vh` word beside it so it is not
   the only carrier of the fact. */

.weeknav {
  display: flex; gap: 8px; overflow-x: auto; margin-bottom: 14px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  padding-bottom: 2px;
}
.weeknav::-webkit-scrollbar { display: none; }

.wchip {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 8px 13px; border-radius: 999px;
  border: 1px solid var(--line-hard); background: var(--panel-2);
  color: var(--text-dim); font-size: .82em; font-weight: 700;
  white-space: nowrap; text-decoration: none;
  /* A chip is a tap target on a phone before it is a label. */
  min-height: 38px;
}
.wchip:hover { color: var(--text); border-color: var(--text-faint); text-decoration: none; }
.wchip.is-on {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 800;
}
.wchip.is-on:hover { color: var(--on-accent); border-color: var(--accent); }
.wchip.is-missed { opacity: .55; }

.wchip-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.wchip-needs { background: var(--accent); }
.wchip-in    { background: var(--text-faint); }
.wchip-shut  { background: transparent; border: 1px solid var(--text-faint); }
.wchip.is-on .wchip-needs,
.wchip.is-on .wchip-in { background: var(--on-accent); }
.wchip.is-on .wchip-shut { border-color: var(--on-accent); }

/* --- the board -----------------------------------------------------------
   One row per game. The old layout drew one card per team, which is thirty-two
   cards for a week that has sixteen games in it, and put the full date on each
   one -- so two games kicking off at the same time produced consecutive cards
   with identical headers that read, correctly, as the same game twice. The
   date now lives on the day heading and the time lives on the row. */

.board { padding: 0; overflow: hidden; }

.gday-head {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 14px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line-soft);
  border-top: 1px solid var(--line-soft);
}
.gday:first-child .gday-head { border-top: none; }
.gday-name {
  font-size: .72em; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text);
}
.gday-date { font-size: .72em; color: var(--text-faint); font-weight: 700; }

.gm { padding: 8px 10px; border-bottom: 1px solid var(--line-soft); }
.gm:last-child { border-bottom: none; }
.gm.is-off { opacity: .5; }

.gm-time {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-size: .7em; font-weight: 700; color: var(--text-faint);
  letter-spacing: .02em; margin-bottom: 4px;
}

.gm-sides { display: grid; grid-template-columns: 1fr auto 1fr; align-items: stretch; }
.gm-at {
  align-self: center; padding: 0 6px;
  font-size: .72em; font-weight: 700; color: var(--text-faint);
}

/* Each half is the tap target, not a button inside it. */
.gside {
  position: relative;
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 8px; border-radius: 10px;
  border: 1px solid transparent;
  cursor: pointer;
  /* Comfortably over the 44px the platform guidelines ask for, with the
     padding, which matters more here than anywhere else on the site: this is
     the control that spends a team for the season. */
  min-height: 46px;
}
.gside input { position: absolute; opacity: 0; pointer-events: none; }
.gside .crest { width: 28px; height: 28px; flex: 0 0 28px; }
.gside:hover { background: rgba(0, 255, 136, .05); }

.gside-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gside-abbr {
  display: flex; align-items: baseline; gap: 5px;
  font-size: .98em; font-weight: 800; letter-spacing: -.2px;
}
.gside-rec { font-size: .68em; font-weight: 800; color: var(--text-faint); }

.gside-tick {
  margin-left: auto; width: 20px; text-align: center;
  color: var(--accent); font-weight: 900;
}

.gside.is-selected {
  background: rgba(0, 255, 136, .1);
  border-color: rgba(0, 255, 136, .55);
}
.gside.is-blocked {
  opacity: .42; cursor: not-allowed; filter: grayscale(.6);
}
.gside.is-blocked:hover { background: transparent; }
.gside:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }

/* A closed week's board. Not a control, so it does not offer a hover, a tick
   or a pointer -- the difference between "you may not pick this" and "there is
   nothing here to pick" is worth drawing. */
.gside.is-static { cursor: default; }
.gside.is-static:hover { background: transparent; }
.gside.is-mine { border-color: rgba(0, 184, 255, .55); background: rgba(0, 184, 255, .08); }
.gside.is-won .gside-abbr { color: var(--accent); }
.gside-score {
  margin-left: auto; font-size: 1.02em; font-weight: 800;
  color: var(--text-dim); letter-spacing: -.3px;
}
.gside.is-won .gside-score { color: var(--accent); }

.tag-final { color: var(--text-faint); }
.tag-mine {
  color: var(--accent-2); border-color: rgba(0, 184, 255, .5);
  background: rgba(0, 184, 255, .12);
}

/* --- the final-week tiebreak --------------------------------------------
   One line and the input it needs; the explanation is a disclosure. It used to
   be a five-line paragraph above the field, which on a phone pushed the field
   itself off screen in the one week it is required.

   The input stays outside the <details>: it is `required`, and a required
   control inside a closed disclosure cannot be focused, so the browser would
   block the submission while pointing at something invisible. */

.tiebreak { border-color: rgba(0, 255, 136, .5); }
.tb-line { display: flex; align-items: center; gap: 12px; }
.tb-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tb-lbl {
  font-size: .68em; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--accent);
}
.tb-game { font-size: .82em; color: var(--text-dim); font-weight: 600; }
.tb-line input {
  margin-left: auto; width: 84px; flex: 0 0 84px; text-align: center;
  font-size: 1.1em; font-weight: 800;
}
.tb-more { margin-top: 10px; }
.tb-more > summary {
  cursor: pointer; list-style: none;
  font-size: .78em; font-weight: 700; color: var(--text-dim); padding: 4px 0;
}
.tb-more > summary::-webkit-details-marker { display: none; }
.tb-more > summary::before {
  content: "\25B8"; display: inline-block; margin-right: 6px;
  color: var(--text-faint); transition: transform .15s ease;
}
.tb-more[open] > summary::before { transform: rotate(90deg); }
.tb-more p { margin: 6px 0 0; }

/* --- wider than a phone --------------------------------------------------
   The board is a list on a phone and stays a list here: the fix for a long
   week was one row per game, not two columns of them. What the extra width
   buys is breathing room inside a row. */

@media (min-width: 620px) {
  .pickhero { padding: 18px 20px 15px; }
  .pickhero-name b { font-size: 1.7em; }
  .gm { padding: 10px 14px; }
  .gside { padding: 9px 10px; gap: 10px; }
  .gside .crest { width: 32px; height: 32px; flex-basis: 32px; }
  .gside-abbr { font-size: 1.04em; }
  .gday-head { padding: 10px 16px; }
}

/* Under 360px the crest is the first thing that can go without losing a fact:
   the code beside it says the same thing in less room. */
@media (max-width: 340px) {
  .gside .crest { width: 22px; height: 22px; flex-basis: 22px; }
  .gside { gap: 6px; padding: 8px 5px; }
  .gm-at { padding: 0 2px; }
}

/* The note that says an earlier week is still being graded.
   Quiet on purpose: it is a caveat, not an alarm. A warning-coloured banner
   here would read as "something is wrong", and nothing is -- the entrant is
   picking a week they are entitled to pick, and may simply already be out of
   it. Amber text on the panel, one line, no box. */
.pendingnote {
  margin: 8px 0 0;
  font-size: .8em;
  line-height: 1.45;
  color: var(--warn);
}

/* The pool-level sibling of `.pendingnote`: an earlier week is still being
   played and this many entries are waiting on it. Same quiet register and the
   same reasoning -- nothing is wrong, the week simply is not over -- but it
   sits under the week strip rather than beside the pick, because it is a
   statement about the weeks in that strip. Dim rather than amber: the amber
   one is addressed to you and may cost you the week, this one is context. */
.weekpending {
  margin: 8px 0 0;
  font-size: .8em;
  line-height: 1.45;
  color: var(--text-dim);
}

/* -- 19. the week strip, the compact header and the centred pick ----------

   The Yahoo Survival Football structure, in this site's tokens. Their layout
   idea is the good one: a single scrollable row of week cells at the top that
   is simultaneously the navigation, the history and the record. Three things
   this page used to draw separately, one under the other, in three shapes. */

/* --- the header, one line -------------------------------------------------
   The four-box stats grid wrapped onto two rows at 390px and pushed the pick
   below the fold for three numbers that are glanced at. */

.statline {
  margin: 0;
  font-size: .84em;
  color: var(--text-dim);
  line-height: 1.6;
}
.statline b { color: var(--text); font-weight: 800; }
.statline-dot { color: var(--text-faint); margin: 0 6px; }

/* --- the week strip -------------------------------------------------------
   One cell per week. The number on top, the crest of that week's pick below
   it, and the result in the ring around the crest.

   Scrolls sideways rather than wrapping: eighteen cells will not fit on a
   phone at any legible size, and wrapping to three rows would put the current
   week in an unpredictable place. The script scrolls the current cell into
   view; with scripting off the strip starts at week 1 and still works. */

.wstrip {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 2px 2px 6px;
  margin-bottom: 14px;
  /* Anchors the scroll so a cell never comes to rest half off the edge. */
  scroll-snap-type: x proximity;

  /* LOAD-BEARING, AND THE WHOLE OF THE 2026-08-22 TRAY BUG.
     ------------------------------------------------------
     `overflow-x: auto` above does NOT clip an absolutely-positioned
     descendant unless this element is also that descendant's containing
     block. Every `.wcell` carries a `<span class="vh">` (the screen-reader
     label naming the week and its result), and `.vh` is `position: absolute`.
     With `.wstrip` left `position: static`, those spans resolved their
     containing block to the initial one, escaped the scroller entirely, and
     came to rest at their static position inside the scrolled content --
     about x=923 for an eighteen-week season. That widened the DOCUMENT to
     924px.

     On a phone the layout viewport grows to the document, and `.tray` is
     `position: fixed; left: 0; right: 0`, so the tray sized itself to 924px
     rather than to the screen. That is what was reported as "the tray is
     broken": three cards dividing 880px at 288px each, so card one filled
     the 390px screen, card two began at x=318 and card three was never on
     it, and the submit was 748px and clipped. Not one rule in section 12,
     14 or 20 was wrong -- they were all being asked to lay out inside a
     container that was 2.4x the width of the phone.

     `position: relative` makes this the containing block, so the labels are
     clipped by the scroller like everything else in it. It changes nothing
     visually: the spans were never visible.

     Introduced in daccb6d, which added both this rule and the `.vh` cells in
     the same commit. Asserted by `TrayGeometryContractTests`, which builds a
     full eighteen-week strip precisely because a three-week fixture fits on a
     phone and so never reproduced this. */
  position: relative;
}
.wstrip::-webkit-scrollbar { display: none; }

.wcell {
  flex: 0 0 auto;
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 46px;
  padding: 7px 4px 8px;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  background: var(--panel-2);
  color: var(--text-dim);
  text-decoration: none;
  /* A tap target before it is a label. */
  min-height: 62px;
}
a.wcell:hover { text-decoration: none; border-color: var(--text-faint); }

.wcell-num {
  font-size: .68em;
  font-weight: 800;
  letter-spacing: .04em;
}

/* The ring. One element, one variable, six states — a new state is a colour
   rather than another block of CSS. */
.wcell-mark {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 2px solid var(--cell-ring, transparent);
  box-sizing: content-box;
}
.wcell-mark .crest { width: 24px; height: 24px; flex-basis: 24px; }

.wcell.is-won     { --cell-ring: var(--accent); }
.wcell.is-lost    { --cell-ring: var(--danger); }
.wcell.is-void    { --cell-ring: var(--warn); }
.wcell.is-pending { --cell-ring: var(--text-faint); }
.wcell.is-missed  { --cell-ring: transparent; opacity: .55; }
.wcell.is-future  { --cell-ring: transparent; opacity: .4; cursor: default; }
.wcell.is-open    { --cell-ring: rgba(0, 255, 136, .45); }

.wcell-dash, .wcell-lock {
  font-size: .8em;
  color: var(--text-faint);
  line-height: 1;
}
.wcell-lock { font-size: .68em; }

/* The week being looked at. Filled rather than outlined, because on a strip of
   eighteen an outline is not findable at a glance. */
.wcell.is-on {
  background: var(--raised);
  border-color: var(--accent);
  color: var(--text);
  opacity: 1;
}
.wcell.is-on .wcell-num { color: var(--accent); }

/* --- the current pick, centred -------------------------------------------
   The crest is the largest thing on the page on purpose. It is the one fact an
   entrant opens the pool to look at, and it used to be a 34px chip in a line
   of text. */

.pickhero-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 6px 0 2px;
}

.pickdisc {
  width: 96px; height: 96px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 3px solid var(--hero-state, var(--line-hard));
  background: var(--panel-2);
}
.pickdisc .crest { width: 74px; height: 74px; flex-basis: 74px; }
.crest-xl { width: 74px; height: 74px; flex-basis: 74px; }
.crest-xl.crest-text { font-size: 1.1em; }

/* No pick yet. Dashed, so the shape reads as "something goes here" rather than
   as an empty thing that is finished. */
.pickdisc.is-blank {
  border-style: dashed;
  border-color: var(--line-hard);
  background: transparent;
}

.pickhero-name {
  text-align: center;
  display: flex; flex-direction: column; gap: 2px;
  line-height: 1.2;
}
.pickhero-name b { font-size: 1.15em; font-weight: 900; letter-spacing: -.3px; }
.pickhero-vs { font-size: .8em; color: var(--text-dim); font-weight: 700; }

.pickhero-score {
  margin: 0;
  font-size: 1.5em; font-weight: 800; letter-spacing: -.5px;
  color: var(--text-dim); white-space: nowrap;
  min-height: 1px;
}

.pickhero-foot { justify-content: center; text-align: center; }

/* The clear control. Only ever rendered over an unsubmitted selection --
   see the note in pool.html: withdrawing a *saved* pick is a rules question,
   not a layout one. */
.pickclear {
  position: absolute; top: 8px; right: 8px;
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line-hard);
  background: var(--panel-2);
  color: var(--text-dim);
  font-size: .9em; font-weight: 800; line-height: 1;
  cursor: pointer;
}
/* `display: inline-flex` above beats the user agent's `[hidden] {display:none}`,
   so the attribute has to be honoured explicitly or the control is visible with
   nothing to clear. */
.pickclear[hidden] { display: none; }
.pickclear:hover, .pickclear:focus-visible {
  color: var(--text); border-color: var(--text-faint);
}

/* --- pick popularity, on a locked week's board only ---------------------- */
.gside-pop {
  font-size: .7em;
  font-weight: 700;
  color: var(--text-faint);
  letter-spacing: .01em;
}

@media (min-width: 620px) {
  .wcell { width: 54px; min-height: 70px; }
  .wcell-mark { width: 32px; height: 32px; }
  .wcell-mark .crest { width: 28px; height: 28px; flex-basis: 28px; }
  .pickdisc { width: 112px; height: 112px; }
  .pickdisc .crest, .crest-xl { width: 86px; height: 86px; flex-basis: 86px; }
  .pickhero-name b { font-size: 1.3em; }
}

/* The TD strip's three dots: one per slot, filled when that pick scored.
   Sized so three of them plus their gaps sit inside the same 28px ring the
   team strip puts a crest in. */
.wdots { display: inline-flex; gap: 3px; align-items: center; }
.wdot {
  width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--text-faint);
}
.wdot.is-hit     { background: var(--accent); border-color: var(--accent); }
.wdot.is-miss    { background: transparent; border-color: var(--danger); }
.wdot.is-waiting { background: var(--text-faint); border-color: var(--text-faint); }
.wdot.is-empty   { border-color: var(--line-hard); opacity: .6; }

/* -- 20. the desktop column ------------------------------------------------

   The centred column itself is not new: `.wrap` and `.tray-inner` have both
   been `max-width: 880px; margin: 0 auto` since this stylesheet was written,
   and measurement confirms it -- at a 1920px viewport both boxes sit at
   x=520, w=880, and the document does not overflow sideways on any page in
   this tree. The tray's *band* is deliberately full-bleed; its contents are
   not.

   What was actually wrong on a wide screen was the primary action. `.btn` is
   `width: 100%` because on a phone the tray is the width of the thumb's
   reach, and a full-width button there is correct. At 880px that same rule
   drew an 880px-wide "Submit 1 Pick": a coloured bar across the page rather
   than a control, and the loudest thing on a screen whose actual content is a
   single crest. The Dream Team tray already solved this for itself
   (`.tray-lineup .btn`, section 14); this gives the team and TD trays the
   same treatment instead of leaving one contest's tray shaped unlike the
   other two.

   The breakpoint is 900px and everything in this section is inside it, on
   purpose: the phone layout is measured, it is what entrants will actually
   use, and it comes out of THIS section byte-identical -- which is still true and
   is asserted by DesktopColumnTests. (The phone's own treatment of this button
   was changed deliberately on 2026-08-21, but by a rule in section 12, not by
   anything here.) 900 is above every
   breakpoint already in this file (560, 620) so no existing rule changes
   meaning, and above every phone and small-tablet width, so the only
   renderings affected are the desktop ones this section is for. */

@media (min-width: 900px) {
  /* `display: flex` rather than the inherited `inline-flex`, so that
     `margin-left: auto` has a block box to push. `min-width` keeps the button
     a deliberate size rather than shrink-wrapping to its label, which would
     make "Pick a team" and "Pick at least one player" two visibly different
     controls doing the same job in two contests. */
  .tray:not(.tray-lineup) .tray-inner > .btn[data-submit] {
    display: flex;
    width: auto;
    min-width: 260px;
    margin-left: auto;
  }
}
