:root {
  --bg: #f6f7f9;
  --card: #ffffff;
  --ink: #16181d;
  --muted: #6b7280;
  --line: #e3e6ea;
  --accent: #0b5ed7;
  --page: #eef0f4;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f1115; --card: #171a20; --ink: #e8eaed;
    --muted: #9aa1ac; --line: #262b33; --accent: #5b9bff;
    --page: #0b0d11;
  }
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--page, var(--bg)); color: var(--ink);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* A hairline of the accent under the header, and a hint of it behind. Enough to
   say the page belongs to something; not enough to compete with the tables, which
   are what anyone is actually here to read. */
.site-header {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .85rem 1.25rem; flex-wrap: wrap;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 7%, var(--card)) 0%,
      var(--card) 100%);
  border-bottom: 1px solid var(--line);
  box-shadow: inset 0 -2px 0 -1px color-mix(in srgb, var(--accent) 45%, transparent);
}
.brand { font-weight: 700; text-decoration: none; color: inherit; letter-spacing: -.01em; }
.site-header nav { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; }
.site-header nav form { margin: 0; }
.site-header nav a {
  text-decoration: none; padding: .35rem .6rem; border-radius: 7px;
  font-size: .95rem;
}
.site-header nav a:hover { background: var(--bg); }
/* "You are here" comes from aria-current, so the marking and the announcement
   can't drift apart. */
.site-header nav a[aria-current="page"] {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--ink); font-weight: 600;
}
.who { color: var(--muted); font-size: .9rem; margin-left: .5rem; }

/* The menu is a checkbox rather than a script: navigation is the one thing that
   must work even if board.js fails to load. */
.nav-toggle, .nav-button { display: none; }
@media (max-width: 780px) {
  .nav-button {
    display: block; font-size: 1.4rem; cursor: pointer; line-height: 1;
    padding: .2rem .5rem; border: 1px solid var(--line); border-radius: 7px;
  }
  .site-header nav { display: none; width: 100%; flex-direction: column; align-items: stretch; }
  .nav-toggle:checked ~ nav { display: flex; }
  .site-header nav a, .site-header nav form { padding: .5rem .6rem; }
  .site-header nav a { border-bottom: 1px solid var(--line); border-radius: 0; }
}

/* Wide, because the tables are the point of most of these pages -- a matchup
   day lays two lineups and nine stat columns each side of a slot column, and
   anything narrower makes it scroll sideways to be read at all.

   Prose is capped separately below rather than by squeezing the container: a
   paragraph running the full width of a 1400px page is genuinely hard to read,
   but that's a property of the paragraph, not of the page. */
main { max-width: min(100% - 1.5rem, 1600px); margin: 1.5rem auto; padding: 0 1rem; }
main.narrow { max-width: 820px; }

/* Cards lift off the page rather than sitting flush in it. The top highlight is
   what does most of the work on the dark theme -- a plain border there reads as a
   drawn rectangle, a lighter top edge reads as a surface catching light. */
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 1.25rem 1.4rem; margin-bottom: 1.25rem;
  box-shadow: 0 1px 2px rgb(0 0 0 / .04), 0 8px 24px -18px rgb(0 0 0 / .5);
}
@media (prefers-color-scheme: dark) {
  .card {
    box-shadow: inset 0 1px 0 rgb(255 255 255 / .04),
                0 10px 30px -22px rgb(0 0 0 / .9);
  }
}
.card.narrow { max-width: 420px; margin-inline: auto; }
/* Keeps the measure readable on a wide page. Tables, grids and rows are
   unaffected -- only running text is capped. */
.card > p, .card > .muted, .room-head p { max-width: 74ch; }
h1 { font-size: 1.45rem; margin: 0 0 .35rem; letter-spacing: -.015em; font-weight: 700; }
h2 { font-size: 1.1rem; margin: 0 0 .75rem; letter-spacing: -.01em; font-weight: 680; }
/* Column headings read as labels rather than as more table content. */
th { font-weight: 620; }
.muted { color: var(--muted); }

a { color: var(--accent); }

label { display: block; font-size: .9rem; margin-bottom: .3rem; }
/* The full-width treatment is for a form that owns its card -- sign-in, mainly.
   Scoped by :not([class]) so the same input inside a compact inline form (the
   invite row on the setup page) keeps its natural width instead of taking a
   line to itself. */
input[type=email]:not([class]) {
  padding: .6rem .7rem; border: 1px solid var(--line); border-radius: 7px;
  background: var(--bg); color: var(--ink); font-size: 1rem;
}
.card.narrow input[type=email] { width: 100%; margin-bottom: .85rem; }
.invite-form input[type=email] { padding: .35rem .5rem; font-size: .9rem; }
/* Base styling for the plain controls. A bare <select> renders as a white system
   widget, which on the dark theme reads as a hole in the page -- it was doing
   exactly that on the trade form. */
input[type=number], input[type=text]:not([class]), select {
  padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink); font-size: .95rem;
}
button {
  padding: .55rem 1rem; border: 0; border-radius: 7px;
  background: var(--accent); color: #fff; font-size: .95rem; cursor: pointer;
}
button.linkish {
  background: none; color: var(--accent); padding: 0; font-size: 1rem;
}

.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: .75rem; margin: 1rem 0 0; }
.facts div { border-top: 1px solid var(--line); padding-top: .5rem; }
.facts dt { font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.facts dd { margin: .15rem 0 0; font-size: 1.05rem; font-weight: 600; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: .5rem .4rem; border-bottom: 1px solid var(--line); }
th { font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
tr.mine { font-weight: 650; }

.messages { list-style: none; padding: 0; margin: 0 0 1rem; }
.messages li {
  padding: .6rem .85rem; border-radius: 7px; margin-bottom: .5rem;
  background: var(--card); border: 1px solid var(--line);
  /* pre-line, so a message that relays multi-line command output keeps its
     shape. Ordinary one-line messages are unaffected -- it honours newlines
     without preserving the runs of spaces `pre` would. */
  white-space: pre-line;
}
.messages li.error { border-color: #d9534f; }
.messages li.success { border-color: #2e9e5b; }

/* --- prices ---------------------------------------------------------------- */
.filters { display: flex; gap: .5rem; margin: 1rem 0 .5rem; flex-wrap: wrap; }
.filters input[type=search], .filters select {
  padding: .45rem .6rem; border: 1px solid var(--line); border-radius: 7px;
  background: var(--bg); color: var(--ink); font-size: .95rem;
}
.filters input[type=search] { flex: 1; min-width: 180px; }

.small { font-size: .85rem; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.strong { font-weight: 650; }
.negative { color: #d9534f; }
tr.total td { border-top: 2px solid var(--line); font-weight: 650; }

.price-hero {
  display: flex; align-items: baseline; gap: .9rem; flex-wrap: wrap;
  margin: 1rem 0 .5rem; padding: 1rem 0; border-block: 1px solid var(--line);
}
.price-hero .price { font-size: 2.4rem; font-weight: 700; letter-spacing: -.02em; }

.steps { padding-left: 1.2rem; }
.steps li { margin-bottom: .6rem; }

.pager { display: flex; gap: .5rem; align-items: center; justify-content: center;
  margin-top: 1rem; flex-wrap: wrap; }

/* A wide table scrolls inside its own box rather than making the page scroll
   sideways. Applied by wrapping the table, not to every table on the site: the
   blanket rule this replaces turned each table into a block element, so even a
   two-column one refused to wrap and grew its own scrollbar. */
.table-scroll { overflow-x: auto; }
.table-scroll table { min-width: 40rem; }
/* The stat tables size themselves to their content instead, and shed columns
   when it won't fit -- a fixed floor here would defeat that. */
.table-scroll .versus-table, .table-scroll .lineup-table,
.table-scroll .week-table, .table-scroll .standings-table,
.table-scroll .game-log { min-width: 0; }

/* --- the points breakdown --------------------------------------------------- */
/* Category columns carry counts, so the PTS figure is the one place the scoring
   is visible. It opens the working: the categories that earned, what each is
   worth, and the product. Built on the browser's own popover, which means it
   needs no JavaScript and survives the live poller replacing the table. */
.pts-open {
  background: none; border: 0; padding: 0; font: inherit; font-weight: 650;
  color: var(--accent); cursor: pointer;
  text-decoration: underline; text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.pts-open:hover { text-decoration-style: solid; }

.breakdown-pop {
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
  color: var(--ink); padding: 1rem 1.1rem; max-width: min(24rem, calc(100vw - 2rem));
  box-shadow: 0 12px 32px rgb(0 0 0 / .28);
  /* It lives inside a right-aligned number cell, and inherited that. */
  text-align: left; font-weight: 400;
}
.breakdown-pop::backdrop { background: rgb(0 0 0 / .35); }
.breakdown-pop h3 { margin: 0; font-size: 1rem; }
.breakdown-pop p { margin: .1rem 0 .6rem; }
/* It carries the same table the player page uses, but at a fraction of the
   width, so the site-wide floor for a scrolling table has to come off. */
.breakdown-pop .table-scroll { overflow-x: visible; }
.breakdown-pop .scoring-table { min-width: 0; width: 100%; }
.breakdown-pop .scoring-table th, .breakdown-pop .scoring-table td { padding: .3rem .2rem; }
.breakdown-pop .ghost-link { margin-top: .7rem; }

/* --- the players table ----------------------------------------------------- */
.sorter { text-decoration: none; color: inherit; display: inline-flex; gap: .2rem;
  white-space: nowrap; }
.sorter:hover { color: var(--ink); }
.sorter.on { color: var(--ink); }
.sort-mark { font-size: .7em; }
th .sorter { font: inherit; }

.players td { vertical-align: middle; }
.owner-cell { white-space: nowrap; }
.free-tag { color: #2e9e5b; font-weight: 600; font-size: .85rem; }
/* Available, but not on the same terms -- he has to be claimed and settled
   overnight rather than signed on the spot, so he doesn't get the green. */
.pill.waivers-pill { background: color-mix(in srgb, #d9a441 22%, transparent); color: #d9a441; }
/* Shown only where the column carrying the same fact has been dropped. The mirror
   of `wide-only`, and it exists for one thing: a marker that changes which button
   the row offers can't simply vanish with the column it lived in. */
.phone-only { display: none; }

/* Switching a phone to the desktop layout, and back. Hidden on a real desktop --
   it would offer the layout you're already looking at -- and shown on a phone.
   `body.forced-desktop` is what keeps it visible once pressed: in that mode the
   page lays out at 1280px, so the phone media query stops matching and a control
   hidden behind it would vanish along with the way to undo it. */
.view-toggle { display: none; margin: 0; }
body.forced-desktop .view-toggle { display: block; }
.on-waivers { color: #d9a441; }
.sign-now { font-weight: 650; }
.pill.mine-pill { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); }
.claim-form { display: flex; gap: .35rem; margin: 0; align-items: center; }
/* A <select> is as wide as its longest option unless told otherwise, and the
   options here are "drop <full name> ($40)" -- left alone it shoves the Claim
   button off the side of a phone. */
.claim-form select { width: 11rem; min-width: 0; padding: .3rem .4rem; font-size: .85rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--bg); color: var(--ink); }
.act-cell { white-space: nowrap; }
details.card summary { cursor: pointer; }
/* Always shown: position and NHL team have no columns of their own any more, so
   this line is where they live rather than a phone-only echo of them. */
.row-sub { display: block; }

/* On a phone the table sheds its two least-load-bearing columns rather than
   scrolling sideways -- horizontal scroll is what put the price and the claim
   button out of sight, which are the two things anyone came here to use. The
   position and team move under the name, so nothing is actually lost. */
@media (max-width: 700px) {
  .table-scroll table { min-width: 0; }
  .wide-only { display: none; }
  .phone-only { display: inline; }
  .view-toggle { display: block; }
  .act-cell { white-space: normal; max-width: 8rem; }
  .claim-form { flex-direction: column; align-items: stretch; gap: .25rem; }
  .claim-form select { width: 100%; font-size: .75rem; }
}

/* --- auction room ---------------------------------------------------------- */
.room-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; }
/* A week and the arrows that move it, kept together on the left -- so the one
   control that leaves for another page is the only thing on the right. The
   heading loses its bottom margin here or it sits a line above its own arrows. */
.week-nav { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.week-nav h2 { margin: 0; }
button.ghost { background: none; color: var(--accent); border: 1px solid var(--line); }
button:disabled { opacity: .4; cursor: not-allowed; }

.lot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem 1.2rem; margin: 1rem 0 .75rem;
  border: 1px solid var(--line); border-radius: 10px; background: var(--bg);
}
.lot.empty { justify-content: center; color: var(--muted); }
.lot-main { display: flex; flex-direction: column; gap: .3rem; }
.lot-player { font-size: 1.25rem; font-weight: 650; display: flex; gap: .5rem; align-items: baseline; }
.lot-player a { text-decoration: none; color: inherit; }
.lot-bid { display: flex; gap: .5rem; align-items: baseline; }
.lot-bid .amount { font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.clock {
  font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums;
  min-width: 3.4ch; text-align: right;
}
.clock.urgent { color: #d9534f; }

.bidbar { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; margin-bottom: .75rem; }
.bid-input {
  width: 6.5rem; padding: .5rem .6rem; border: 1px solid var(--line);
  border-radius: 7px; background: var(--card); color: var(--ink);
}
.you-lead { color: #2e9e5b; font-weight: 600; }
.cant-bid { color: var(--muted); font-size: .9rem; }

.bidlog { list-style: none; padding: 0; margin: 0 0 1rem; display: flex; gap: .5rem; flex-wrap: wrap; }
.bidlog li { font-size: .82rem; color: var(--muted); background: var(--bg); border: 1px solid var(--line); border-radius: 20px; padding: .15rem .6rem; }

.nominate { margin: 1rem 0; padding: 1rem; border: 1px dashed var(--line); border-radius: 10px; }
.nominate input[type=search] { width: 100%; padding: .55rem .7rem; border: 1px solid var(--line); border-radius: 7px; background: var(--bg); color: var(--ink); }
.search-results { display: flex; flex-direction: column; gap: .2rem; margin-top: .4rem; }
.search-hit { text-align: left; background: var(--bg); color: var(--ink); border: 1px solid var(--line); padding: .4rem .6rem; }
.nominate-go { display: flex; gap: .5rem; align-items: center; margin-top: .7rem; flex-wrap: wrap; }

.teams tr.nominating { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.tag { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--accent); margin-left: .4rem; }
td.maxed { color: #d9534f; font-weight: 600; }
.tag.warn { color: #d9534f; }
td.short { color: #d9534f; }

/* --- the team page --------------------------------------------------------- */
.team-head { display: flex; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap; }
.team-title { min-width: 0; }
/* A grid rather than a flex row, so on a phone the three numbers land in even
   columns instead of two-then-one with a ragged gap. */
.team-stats { display: grid; grid-auto-flow: column; gap: 1.75rem; margin: 0; }
@media (max-width: 560px) {
  .team-stats { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); gap: .75rem; width: 100%; }
  .team-stats dd { font-size: 1.1rem; }
}
.team-stats dt { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.team-stats dd { margin: .1rem 0 0; font-size: 1.35rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.team-stats .stat-sub { font-size: .8rem; font-weight: 400; }

.tabs { display: flex; gap: .3rem; margin-bottom: 1rem; border-bottom: 1px solid var(--line); }
.tab {
  padding: .55rem .95rem; text-decoration: none; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px; font-weight: 600;
  font-size: .95rem;
}
.tab.on { color: var(--ink); border-bottom-color: var(--accent); }
/* How many claims are waiting on tonight's run. On the tab because it is the only
   warning a manager gets that his roster is about to change while he sleeps. */
.tab-count {
  display: inline-block; min-width: 1.25rem; padding: 0 .3rem; margin-left: .25rem;
  border-radius: 10px; background: var(--accent); color: #fff;
  font-size: .72rem; font-weight: 650; text-align: center; line-height: 1.35;
}

/* --- waiver claims ---------------------------------------------------------- */
.rank { width: 3.6rem; }
.claims-note { padding: .8rem 1rem; }
/* Awarded and unsuccessful have to be tellable apart at a glance -- the whole
   point of showing settled claims is answering "did I get him". */
.claim-awarded { color: #2e9e5b; font-weight: 650; }
.claim-failed { color: var(--muted); }
.claim-withdrawn { color: var(--muted); font-style: italic; }

/* --- the lineup board ------------------------------------------------------ */
/* One table per kind of player, each running starting slots then the bench.
   Two tables rather than one because skater and goalie categories share no
   columns -- and because it makes a skater-into-goalie drag impossible rather
   than merely refused. */
.board.busy { opacity: .55; pointer-events: none; }
.board-bar { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
.board-hint { font-size: .85rem; color: var(--muted); margin: .6rem 0 .8rem; }
.board-note { padding: .55rem .8rem; border-radius: 7px; margin: 0 0 .75rem; font-size: .9rem; }
.board-note.bad { border: 1px solid #d9534f; background: color-mix(in srgb, #d9534f 8%, transparent); }
.board-note.good { border: 1px solid #2e9e5b; background: color-mix(in srgb, #2e9e5b 8%, transparent); }

/* Sticky, because what you picked up can scroll out of sight before you reach
   where it goes. A hint at the top of the page would leave with it. */
.pickup {
  position: sticky; top: .5rem; z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .5rem .8rem; margin: .6rem 0; border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 18%, var(--card));
  border: 1px solid var(--accent); font-size: .9rem; font-weight: 600;
}
/* display:flex would otherwise win over the hidden attribute, leaving an empty
   bar sitting there permanently. */
.pickup[hidden] { display: none; }

.lineup-table { margin-bottom: .5rem; }
.lineup-table th { white-space: nowrap; }
/* Same reasoning as the versus table: proportions so the slack is shared out
   rather than all landing in the name column. */
.lineup-table th.num, .lineup-table td.num { width: 4.5%; min-width: 2.2rem; }
.lineup-table .lrow-player { width: 24%; }
.lineup-table .lrow-opp { width: 14%; }
/* A share, not a fixed size -- the same trap as the slot column on a matchup day.
   Left at `3rem` among a row of percentages it became the sink for every point the
   others didn't claim, which on a wide layout with the situational columns shed was
   a third of the table holding one letter. */
.lrow-pos {
  width: 3%; min-width: 2.4rem; font-size: .8rem; font-weight: 700;
  color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.lrow-player { min-width: 11rem; }
.lrow-opp { white-space: nowrap; font-size: .8rem; }
.slot-empty { color: var(--muted); font-style: italic; font-size: .9rem; }

.lrow.is-bench { background: color-mix(in srgb, var(--muted) 8%, transparent); }
.lrow.is-bench .lrow-pos { color: var(--muted); }
.lrow.is-locked { opacity: .7; }
tr.lineup-totals td { border-top: 2px solid var(--line); border-bottom: 0;
  font-size: .85rem; color: var(--muted); }
tr.lineup-totals .strong { color: var(--ink); }

/* A player, in his row. The block is the drag handle; the row is the target. */
.man { display: flex; flex-direction: column; cursor: pointer; user-select: none; min-width: 0; }
.man-name { font-weight: 600; text-decoration: none; color: inherit;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.man-meta { font-size: .74rem; color: var(--muted); }
.man.is-locked { cursor: default; opacity: .8; }
.man.dragging { opacity: .4; }
.man.picked .man-name { color: var(--accent); }

/* The selected player, and where he may go. Both states survive the board being
   redrawn, which is why they're plain classes on plain elements. */
tr.lrow.target { background: color-mix(in srgb, var(--accent) 12%, transparent); }
tr.lrow.target td { border-bottom-color: color-mix(in srgb, var(--accent) 35%, transparent); }
tr.lrow.no-target { opacity: .3; }
tr.lrow.over { background: color-mix(in srgb, var(--accent) 25%, transparent); }

.fallback-row { display: flex; gap: .5rem; align-items: center; margin: .4rem 0; }
.footnote { margin-top: 1rem; padding-top: .75rem; border-top: 1px solid var(--line); }

@media (max-width: 700px) {
  .lrow-opp { display: none; }
  .lrow-player { min-width: 8rem; }
}

/* --- roster and contracts -------------------------------------------------- */
.roster-list { display: flex; flex-direction: column; }
.roster-row {
  display: grid; grid-template-columns: 1fr auto auto; gap: 1rem; align-items: center;
  padding: .6rem .3rem; border-bottom: 1px solid var(--line);
}
.roster-row:last-child { border-bottom: 0; }
.roster-row.on-ir, .roster-row.on-ltir { background: color-mix(in srgb, #d9853b 6%, transparent); }
.roster-who { display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; min-width: 0; }
.roster-money { text-align: right; display: flex; flex-direction: column; }
.roster-money .salary { font-weight: 650; font-variant-numeric: tabular-nums; }
.roster-actions { display: flex; gap: .5rem; align-items: center; }
.roster-actions form { margin: 0; }
@media (max-width: 640px) {
  .roster-row { grid-template-columns: 1fr auto; }
  .roster-actions { grid-column: 1 / -1; justify-content: flex-end; }
}
.pill {
  font-size: .65rem; font-weight: 700; letter-spacing: .06em; padding: .1rem .35rem;
  border-radius: 4px; text-transform: uppercase;
}
.pill.ir { background: color-mix(in srgb, #d9853b 25%, transparent); color: #b26a1f; }
.pill.ltir { background: color-mix(in srgb, #d9534f 22%, transparent); color: #d9534f; }
.injury-slots { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.injury-slots h3 { font-size: .95rem; margin: 0 0 .4rem; }

.playing { color: #2e9e5b; font-size: .85rem; font-weight: 600; }
.gap-warning {
  padding: .55rem .8rem; border-radius: 7px; margin: .75rem 0 0;
  background: color-mix(in srgb, #d9853b 12%, transparent);
  border: 1px solid color-mix(in srgb, #d9853b 40%, transparent);
  font-size: .9rem;
}
/* A quiet, outlined control. It's used on both <a> and <button>, and a bare
   <button> defaults to the filled accent style -- so the background and colour
   have to be reset here or the "ghost" is a solid blue box with invisible text. */
.ghost-link {
  font-size: .9rem; text-decoration: none; padding: .35rem .6rem;
  border: 1px solid var(--line); border-radius: 7px;
  background: none; color: var(--accent); cursor: pointer;
}
.ghost-link:hover:not(:disabled) { border-color: var(--accent); }
.ghost-link:disabled { opacity: .4; cursor: not-allowed; color: var(--muted); }
button.danger, .ghost-link.danger { background: none; color: #d9534f; }
button.danger:disabled, .ghost-link.danger:disabled { color: var(--muted); }

/* --- scoreboard ------------------------------------------------------------ */
.matchup {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1rem;
  padding: .85rem 1rem; margin-top: .6rem; text-decoration: none; color: inherit;
  border: 1px solid var(--line); border-radius: 10px; background: var(--bg);
}
.matchup.mine { border-color: var(--accent); }
.matchup .side { display: flex; align-items: baseline; gap: .7rem; }
.matchup .side:first-of-type { justify-content: flex-start; }
.matchup .side:last-of-type { justify-content: flex-end; }
.matchup .team { font-weight: 600; }
.matchup .score { font-size: 1.35rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.matchup .side.leading .score { color: var(--accent); }
.matchup .versus { font-size: .75rem; color: var(--muted); text-transform: uppercase; }
.matchup.big { padding: 1.4rem 1rem; }
.matchup.big .score { font-size: 2.2rem; }
/* A week's production, one team either side, inside a single card -- so it reads
   like a matchup day rather than like a different application. The gap is the
   divider: two tables sitting apart say "compare the totals" without claiming the
   rows opposite each other are a pair, which over seven shifting lineups they
   aren't. */
.week-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; align-items: start; }
.week-cols .card { margin-bottom: 0; }
/* The rule down the middle does what the day view's slot column does -- says where
   one team stops and the other starts -- without the shading, because shading a
   column implies the rows either side of it are a pair. Dropped along with the
   two-column layout, since a line above a table below it means nothing. */
@media (min-width: 62.01em) {
  .week-cols > .table-scroll + .table-scroll {
    border-left: 1px solid var(--line); padding-left: 1.5rem;
  }
}
/* The goalie section, set off from the skaters above it the way the day view's
   second table is by its own headings. */
.week-goalies { margin-top: 1.1rem; }
.week-table th, .week-table td { padding: .45rem .25rem; }
/* Rostered but yet to start this week. Dimmed the same way a bench row is, so the
   men who have banked something stand out from the men still to come -- which is
   the comparison a mid-week matchup is actually for. */
.week-table tr.is-idle td { color: var(--muted); }
.week-table tr.is-idle a { color: var(--muted); }
.week-table th.num, .week-table td.num { width: 3.4%; }
/* The name column is the one the team heading sits over, so it needs the room a
   heading wants rather than the 12% a day view gives it -- there are half as many
   tables competing for the width here. */
.week-table .versus-name { width: 22%; }
/* Below the point the categories go there is nothing left but the name and the
   points, and the points column was taking the whole surplus: 210px of it at
   390, with the name crushed into 77 and wrapping every player onto two lines.
   A day view never shows this because it has two of each column to share the
   slack between; this table has one of each. The name claims it instead, being
   the column with something to say.

   Scoped to where the categories are actually gone: taking the slack is right
   when the name and the points are alone and wrong the moment a category is
   back, where it squeezed the five core columns into 28px each. */
@media (max-width: 36em) {
  .week-table .versus-name { width: 100%; }
}
/* The global tier thresholds are measured against a matchup day, which carries
   two of every column. This table carries one, and below 62em it is the only
   thing on its row -- so it has roughly twice the width per column that the
   threshold assumes, and obeying it left a 763px name column with a single
   number at the far right and 700px of nothing between them.
   36em, and measured rather than guessed: names stop wrapping at 560px (one wraps
   at 520, two at 500), so the line sits just above that. Re-measure if the name
   cell gains anything. */
@media (min-width: 36.01em) and (max-width: 58em) {
  .week-table .tier-core { display: table-cell; }
}
/* 62em, not the 78em it first looked like: the column tiers thin each side out
   on the way down, so two of them keep fitting well past the point the full ten
   columns stopped. This table never overflows -- its widths are percentages and
   its min-width is 0, so it compresses -- which means the limit is readability,
   not fit, and the limit measured was a player's name breaking over two lines.
   Since the tiers now drop earlier there is no width left where a name wraps at
   all, so this threshold is simply where one column reads better than two. */
@media (max-width: 62em) { .week-cols { grid-template-columns: 1fr; } }

/* --- matchup detail --------------------------------------------------------- */
/* Starts spent and starts left, either side of the score. The two numbers a
   total can't carry: whether you're behind, or just haven't played yet. */
.games-bar {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  margin-top: .9rem; padding-top: .75rem; border-top: 1px solid var(--line);
  font-size: .85rem; flex-wrap: wrap;
}
.games-left { color: var(--muted); }
.games-left.right { text-align: right; }
.games-mid { display: flex; gap: .5rem; align-items: baseline; }
.games-mid strong { font-variant-numeric: tabular-nums; font-size: 1.05rem; }

.day-tabs { display: flex; gap: .25rem; margin-top: .9rem; overflow-x: auto; padding-bottom: .2rem; }
.day-tab {
  padding: .3rem .6rem; border-radius: 7px; text-decoration: none;
  font-size: .85rem; color: var(--muted); white-space: nowrap;
  border: 1px solid transparent;
}
.day-tab:hover { background: var(--bg); }
.day-tab.on { background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--ink); font-weight: 650; }
.day-tab.is-today { border-color: var(--line); }

/* The day view: both lineups reading outward from a shared slot column, so you
   compare centre against centre rather than reading two separate lists.

   Deliberately NOT table-layout:fixed. With nineteen columns that splits the
   width evenly and squeezes the names into three wrapped lines while the stat
   columns sit half empty. Sized instead, and allowed to overflow into the
   surrounding .table-scroll. */
/* No min-width on the table. It forced a scrollbar the moment the window dipped
   below it, which is the opposite of what these tables should do -- see the tier
   rules at the foot of this file.

   Widths are percentages rather than fixed sizes, because a fixed one tells the
   browser the column doesn't want the leftover space, and it hands the lot to
   the name columns instead: nineteen numbers crushed against the edges with a
   gulf of nothing in the middle. Proportions let every column share the slack.
   min-width still guards the floor when there is no slack to share. */
.versus-table th, .versus-table td { vertical-align: middle; padding: .45rem .35rem; }
.versus-table th.num, .versus-table td.num {
  width: 3.6%; min-width: 2rem; font-size: .85rem;
}
/* Shared by the matchup day and the week, which is the point -- the two tables
   are the same table over different spans, and a rule scoped to one of them is
   how they drifted into looking like different applications in the first place.
   The week overrides the width alone, having half as many columns to fit.

   No `.right` variant any more: both halves read name, points, categories, so
   there is only one kind of name cell. The mirrored layout it belonged to is gone
   -- see the note in _versus_table.html. */
.versus-name { width: 12%; min-width: 7.5rem; }
.versus-name a { text-decoration: none; font-weight: 600; white-space: nowrap; }
.versus-game { display: block; font-size: .7rem; color: var(--muted); white-space: nowrap; }
/* A share like everything else. Left as a fixed size it becomes the sink for
   whatever percentage the other columns don't claim, and it would end up the
   widest thing on the page. It holds a bare position now -- "D" for all four
   defence slots rather than D1..D4 -- because the number distinguished one
   identical slot from another, which nobody needs, and this column sits between
   two teams on a matchup day where its width comes out of the players' names. */
.slot-col {
  text-align: center; font-size: .75rem; font-weight: 700; color: var(--muted);
  width: 3%; min-width: 1.9rem; background: var(--bg);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.versus-table tr.is-bench { background: color-mix(in srgb, var(--muted) 8%, transparent); }
.goalie-row { background: color-mix(in srgb, var(--accent) 4%, transparent); }

/* A phone drops the stat spread and keeps names, points and the slot -- the
   parts you can't reconstruct from anywhere else. */
@media (max-width: 760px) {
  /* Let the names wrap. They're nowrap on a desktop so a row reads as one line,
     but on a phone that makes the longest name the floor for the whole table and
     is the last thing forcing a sideways scroll. */
  .versus-name a { white-space: normal; }
  .versus-game { white-space: normal; font-size: .65rem; }
  .versus-name { min-width: 4.5rem; }
  /* And let them take the leftover space, which the columns holding a single
     letter or a single number were otherwise soaking up: at 560 the POS column
     was 195px wide for one character while both names were squeezed to 89 and
     wrapped onto two lines. The same shape as the fixed-width slot column that
     ate a third of the lineup board -- a column that doesn't want the slack
     hands it to whichever one does, and it was never the name column.

     A share each rather than `width: 100%`: two columns both asking for all of it
     is not a tie, and the browser gave the whole surplus to the first -- 334px
     against 82, which for a table whose entire job is setting two teams beside
     each other is worse than the fault it fixed. */
  .versus-table .versus-name { width: 40%; }
  .versus-table .slot-col { width: 1.7rem; min-width: 1.7rem; font-size: .68rem; }
  .versus-table th, .versus-table td { padding: .4rem .15rem; }
}
/* The narrowest phones in common use. Twenty-odd pixels over, and a sideways
   scroll for that would be a poor trade. */
@media (max-width: 30em) {
  .versus-name { min-width: 3.8rem; }
  .versus-table th.num, .versus-table td.num,
  .week-table th.num, .week-table td.num { width: 1.9rem; }
  /* The floor at this width is a long surname at body size — "Svechnikov" is
     ~88px on its own. Nothing else was binding. */
  .versus-name a { font-size: .8rem; }
}

tr.cutline td { border-bottom: 2px solid var(--accent); }

/* --- the game log ---------------------------------------------------------- */
/* Date and opponent take their content width; the categories share what's left,
   the same way the other stat tables work. Sheds columns on the same breakpoints,
   since it carries the same set. */
.game-log th.num, .game-log td.num { width: 4%; }
.log-date { white-space: nowrap; color: var(--muted); font-size: .9rem; }
.log-opponent { white-space: nowrap; }

/* A queued claim that couldn't be granted as things stand. Marked rather than
   removed -- it may well become grantable before its run, and a claim that
   silently vanished would be worse than one that warns. */
.is-blocked { background: color-mix(in srgb, #d9534f 8%, transparent); }
.blocked-note {
  display: block; font-size: .8rem; color: #d9534f; margin-top: .15rem;
}

/* --- the stat-correction notice -------------------------------------------- */
/* Above everything on My Team, because it has already changed his points. Marked
   but not alarming: a correction is normal, being kept in the dark about one is
   not. */
.correction-note {
  border-left: 3px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.correction-note summary { cursor: pointer; font-weight: 600; }
.correction-note summary::marker { color: var(--accent); }
.correction-note table { margin-top: .5rem; }
.gained { color: #2e9e5b; }

/* The players table carries a full stat line now, so it sheds categories the same
   way the matchup tables do. Position and NHL team lost their columns to make room
   and read as a subtitle under the name instead. */
.players th.num, .players td.num { width: 3.2%; }
.season-tabs { margin: .75rem 0 .25rem; }

/* It shows one player per row where a matchup day shows two teams side by side, so
   it needs far less width than the table the shared thresholds were measured
   against -- left on those, it dropped half the categories on a 1280px laptop with
   room for all of them. Measured the same way: all sixteen columns need a 1109px
   window, the eleven left after the extras go need 885. These sit just above.
   Re-measure these two alongside the shared thresholds when a category changes;
   they're a second place the same measurement lives. */
@media (min-width: 72em) { .players .tier-extra { display: table-cell; } }
@media (min-width: 57em) { .players .tier-core  { display: table-cell; } }

/* --- confirming a transaction ---------------------------------------------- */
/* Wide enough for a two-column summary and no wider: this is a page you read once
   and act on, not one you scan. */
.card.narrow-ish { max-width: 40rem; }
.confirm-table { margin: 1rem 0; }
.confirm-table th { text-align: left; width: 9rem; font-size: .8rem; }
.confirm-table td { vertical-align: baseline; }
.confirm-actions { display: flex; gap: .9rem; align-items: center; margin-top: 1rem; }

/* --- the dashboard --------------------------------------------------------- */
/* Main column and a glanceable sidebar. The sidebar is fixed-ish because a list
   of fixtures doesn't get more useful with more width, while the standings and
   the ledger do. Below 60em it drops underneath, where a glance still works. */
.dash { display: grid; grid-template-columns: minmax(0, 1fr) 20rem; gap: 1.25rem;
        align-items: start; }
.dash-main { min-width: 0; }
.dash-side .card { position: sticky; top: 1rem; }
@media (max-width: 60em) {
  .dash { grid-template-columns: 1fr; }
  .dash-side .card { position: static; }
}

/* The ledger: every move in the league, newest first. Reads as a feed rather
   than a table because the interesting part is the sentence, not the columns. */
.ledger { list-style: none; padding: 0; margin: 0; }
.ledger li {
  display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap;
  padding: .5rem .2rem; border-bottom: 1px solid var(--line);
}
.ledger li:last-child { border-bottom: 0; }
.ledger li.mine { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.ledger-when { color: var(--muted); font-size: .8rem; min-width: 3.2rem; }
.ledger-kind {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .04em;
  padding: .1rem .4rem; border-radius: 20px; border: 1px solid var(--line);
  color: var(--muted); white-space: nowrap;
}
/* The two halves of a swap, as two chips rather than two runs of text. Set as
   plain spans they ran together -- "+ Beckett Sennecke − Adam Gaudette" reads as
   one phrase and the minus vanishes into it, which is the one character carrying
   the whole meaning. Each gets a background, a nowrap and real space between. */
.ledger-what { flex: 1; min-width: 12rem; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: .3rem .55rem; }
.ledger .chip {
  white-space: nowrap; border-radius: 6px; padding: .1rem .4rem; font-weight: 600;
  font-size: .95rem;
}
.ledger .chip.in { color: #2e9e5b; background: color-mix(in srgb, #2e9e5b 12%, transparent); }
.ledger .chip.out { color: var(--muted); background: color-mix(in srgb, var(--muted) 14%, transparent); }
/* Inside the arriving player's chip, because it is his price. At the end of the
   line it read as the price of whoever stood nearest -- on a swap, the man being
   dropped. */
.ledger .chip-money { font-weight: 500; opacity: .85; }

/* Tonight's fixtures. Games with one of your players in them are marked, not
   filtered -- which games you have no stake in is part of reading the night. */
.fixtures { list-style: none; padding: 0; margin: .5rem 0; }
.fixtures li {
  display: flex; justify-content: space-between; gap: .5rem;
  padding: .35rem .4rem; border-radius: 6px;
  font-variant-numeric: tabular-nums;
}
.fixtures li.has-mine {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  font-weight: 600;
}
.fixture-teams { white-space: nowrap; }

/* --- standings -------------------------------------------------------------- */
/* The team name takes the leftover width and everything else sits at its content
   size, so the rank stays beside the name instead of drifting half a page from
   it. Sharing the width evenly is what a table does by default, and with seven
   columns on a wide page it reads as seven unrelated lists. */
.standings-table th:first-child, .standings-table td:first-child { width: 4rem; }
.standings-table th:nth-child(2), .standings-table td:nth-child(2) { width: 100%; }
.standings-table th.num, .standings-table td.num { white-space: nowrap; padding-inline: .75rem; }

/* --- what a phone gives up -------------------------------------------------
   Measured inside a 390px viewport, which is the only way to see any of this:
   Chrome reports the full screen as `innerWidth` here however small the window
   is, so resizing it exercises no media query at all. `X_FRAME_OPTIONS` is
   SAMEORIGIN in DEBUG so a page can be rendered in a phone-width iframe.

   The rule is the same one the stat tables follow: shed, don't scroll. A table
   you have to drag sideways hides the half you didn't think to look at. */
@media (max-width: 34em) {
  /* A matchup is three columns on a desktop -- team, score, AT, score, team --
     and there is no honest way to fit two team names and two four-digit scores
     across 390px. It becomes two rows, each name left and its score right, which
     is what every phone scoreboard does and what the grid was approximating. */
  .matchup { grid-template-columns: 1fr; gap: .35rem; }
  /* Both :first-of-type and :last-of-type, because the desktop rules that pack
     each side outward are more specific than a bare `.matchup .side` and quietly
     won -- which left the second row's score sitting against its team name
     instead of lining up under the first. */
  .matchup .side:first-of-type,
  .matchup .side:last-of-type { justify-content: space-between; gap: .6rem; }
  /* The home side is written score-then-name so it reads inward on a desktop.
     Reversed here so both rows read name-then-score down the column. */
  .matchup .side:last-of-type { flex-direction: row-reverse; }
  /* Against the top of the name rather than its first baseline: a name that wraps
     to two lines otherwise drags the score up level with only its first line. */
  .matchup .side { align-items: flex-start; }
  .matchup .score { line-height: 1.2; }
  .matchup .team { min-width: 0; overflow-wrap: anywhere; }
  /* "AT" is a separator between two columns. Stacked, the rows are already a
     pair inside one bordered card, and the word is just a third row of noise. */
  .matchup .versus { display: none; }
  .matchup.big { padding: 1rem .9rem; }
  .matchup.big .score { font-size: 1.7rem; }

  /* Wrap rather than scroll. A hidden day tab is a day nobody opens: the
     overflow gave no hint there were four more, so half the week was invisible. */
  .day-tabs { flex-wrap: wrap; overflow-x: visible; }

  .standings-table th.num, .standings-table td.num { padding-inline: .35rem; }
  .standings-table th:first-child, .standings-table td:first-child { width: 2rem; }

  /* The games strip reads as three columns of one line each, which wraps into
     nonsense at this width. Two lines, each team's starts against its own name. */
  .games-bar { flex-direction: column; align-items: stretch; gap: .2rem; }
  .games-bar .games-left.right { text-align: right; }

  /* The claim control is a drop-to-make-room select beside a button, and together
     they were the widest thing on the players page -- wider than the player's name.
     Stacked and shrunk, they fit; the select still says who you'd drop, which is
     the part you can't get anywhere else on this screen. */
  /* Readable again, because shedding the Owner column paid for it. Squeezing a
     22-option dropdown into 60px was solving the wrong problem. */
  .act-cell { max-width: 7rem; padding-inline: .2rem; }
  .claim-form select { font-size: .75rem; padding: .25rem .3rem; }
  .claim-form button { padding: .3rem .45rem; font-size: .8rem; }

  /* Lineup and matchup tables: the numbers already shed by tier, so what's left
     over is padding on the columns that remain. */
  .lineup-table th, .lineup-table td,
  .versus-table th, .versus-table td { padding-inline: .12rem; }

  /* A career matrix is seasons down and categories across, so it's wide by nature
     and scrolling inside its own box is the honest answer. But a hard 46rem floor
     stopped it shedding anything at all, so it scrolled nearly 300px on a phone
     when the tiers would have taken most of that off. */
  .stats-matrix { min-width: 0; }
  .stats-matrix th.num, .stats-matrix td.num { width: auto; padding-inline: .15rem; }

  /* The diagnostics and setup tables carry raw columns nobody sized for a phone.
     They scroll in their own boxes now rather than dragging the page sideways. */
  .table-scroll table { min-width: 0; }

  /* Four commissioner actions on one row is 200px of buttons. Stacked, each is a
     tap target instead of a sliver, which on a phone it needs to be anyway. */
  .inline-actions { flex-direction: column; align-items: flex-start; gap: .35rem; }
  .invite-form { flex-direction: column; align-items: stretch; gap: .35rem; }
  .invite-form input { min-width: 0; }
}
.live-note, .live-note-inline { color: #2e9e5b; font-size: .85rem; font-weight: 600; }
.live-note { margin: .6rem 0 0; }
.day-total { margin: 0 0 .5rem; font-size: 1.05rem; }

/* --- operations ------------------------------------------------------------ */
.ops-good { color: #2e9e5b; font-weight: 600; }
.ops-bad { color: #d9534f; font-weight: 600; }
.ops-warn, .problem { font-size: .9rem; }
.ops-warn { color: #b26a1f; }
.warn-num { color: #b26a1f; font-weight: 600; }
tr.ops-row-bad td { background: color-mix(in srgb, #d9534f 8%, transparent); }
.problem { padding: .55rem .75rem; margin-top: .5rem; border-radius: 7px;
  border: 1px solid color-mix(in srgb, #d9534f 35%, transparent);
  background: color-mix(in srgb, #d9534f 6%, transparent); }
.problem.soft { border-color: color-mix(in srgb, #d9853b 35%, transparent);
  background: color-mix(in srgb, #d9853b 8%, transparent); }
.invite-form { display: flex; gap: .35rem; flex-wrap: wrap; margin: 0; }
.invite-form input { padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--ink); font-size: .9rem; min-width: 9rem; }
/* Wraps: this row carries four actions now, and on a narrow window an unwrapped
   flex row pushes "remove" off the side of the table. */
.inline-actions { display: flex; gap: .7rem; margin: 0; flex-wrap: wrap; }
.card.narrow input[type=text], .card.narrow input[type=password] {
  width: 100%; padding: .6rem .7rem; margin-bottom: .85rem;
  border: 1px solid var(--line); border-radius: 7px; background: var(--bg);
  color: var(--ink); font-size: 1rem; }

/* --- signing in ------------------------------------------------------------ */
/* The password is the way in that works; the emailed link is folded away behind a
   summary because it only reaches an address the league can actually send to, and
   offering it as an equal choice is what let a manager wait on an email that was
   never coming. */
.signin-alt { margin: .2rem 0 1rem; }
.signin-link { border-top: 1px solid var(--line); padding-top: .8rem; }
.signin-link summary { cursor: pointer; }
.signin-link p { margin: .5rem 0; }
.hurt { color: #d9534f; font-size: .8rem; font-weight: 700; letter-spacing: .03em; }

/* Buttons. `.button` was used in templates before it was ever defined, so it
   rendered as plain text -- easy to miss, since a bare <button> still works. */
.button {
  display: inline-block; padding: .45rem .85rem; border-radius: 7px;
  border: 1px solid var(--accent); background: var(--accent); color: #fff;
  font-size: .9rem; font-weight: 600; text-decoration: none; cursor: pointer;
}
.button:hover { filter: brightness(1.08); }

.plain { list-style: none; padding: 0; margin: .3rem 0; }
.plain li { padding: .3rem 0; border-bottom: 1px solid var(--line); }
.plain li:last-child { border-bottom: 0; }

/* Trades. The two legs sit side by side so a deal reads as an exchange rather
   than a list, and stack on a phone where side-by-side would be unreadable. */
.trade { padding: .75rem 0; border-bottom: 1px solid var(--line); }
.trade:last-child { border-bottom: 0; }
.trade-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; flex-wrap: wrap; }
.trade-legs { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem;
  margin: .5rem 0; }
@media (max-width: 640px) { .trade-legs { grid-template-columns: 1fr; } }
.trade-leg h2 { font-size: 1rem; margin: 0 0 .2rem; }
.retained { color: var(--accent); }

/* Free agency. The status strip is the four numbers that decide whether a claim
   is even possible, so it sits above everything and wraps rather than truncates. */
.fa-status { display: flex; gap: 1.4rem; flex-wrap: wrap; margin: .6rem 0;
  padding: .6rem .75rem; border: 1px solid var(--line); border-radius: 8px; }
.fa-status strong { font-variant-numeric: tabular-nums; }

/* The day's own score, under its slot table. */
tr.day-total td { border-top: 2px solid var(--line); padding-top: .6rem; font-size: .95rem; }
/* Colour on its own can't mark the winning half. The figure in this cell is a
   breakdown button, and every one of those is accent-coloured already, so `.won`
   was setting a colour the thing inside it had anyway and the marker never
   appeared at all. Weight carries it; the colour still does the work on the rare
   plain-text total, the one nobody scored in. */
tr.day-total .won { color: var(--accent); }
tr.day-total .won, tr.day-total .won .pts-open { font-weight: 800; }

/* This week, on the team page. The reason to set a lineup is sitting in a
   matchup; having to open another page put the reason a click from the doing. */
.week-card {
  display: flex; flex-direction: column; gap: .15rem; text-decoration: none;
  color: inherit; padding: .6rem .85rem; border: 1px solid var(--line);
  border-radius: 9px; background: var(--bg); min-width: 13rem;
}
.week-card:hover { border-color: var(--accent); }
.week-card-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.week-card-score { display: flex; gap: .5rem; align-items: baseline; font-variant-numeric: tabular-nums; }
.week-card-score strong { font-size: 1.3rem; }
.week-card-score .winning { color: var(--accent); }
.standing-line { margin: 0 0 .2rem; }


/* --- how the stat tables give way ------------------------------------------- */
/* Twenty-three columns have an intrinsic minimum width, and past a point the
   only honest choices are to drop some or to scroll sideways. Scrolling hides
   the far side of a matchup, which is the single thing that layout exists to
   show, so the columns give way instead — the situational splits first, since
   they're usually zero, then the rest, leaving name, slot and points.

   Set in em rather than px so the thresholds move with the reader's font size:
   someone browsing at 20px needs to shed a column sooner, not later. */
/* Measured against the widest table on the site -- a matchup day, twenty-three
   columns, which carries two of everything. All of them need 1343px of window;
   the core five need 917. The thresholds sit just above each, so a column is
   never dropped while there was still room for it, with a little headroom for
   longer names than the ones measured.
   This has moved with the column count three times: up when points went to two
   decimals, up again when ENG was added, and back down when PPG/PPA merged into
   PPP and ENG into ENP. The day view shows two of every column, so each change is
   paid for twice -- and each time the threshold was left behind, a band of window
   widths overflowed instead of shedding a column, which is the failure this whole
   mechanism exists to prevent. Re-measure whenever a category is added or merged. */
@media (max-width: 85em) {
  .tier-extra { display: none; }
}
@media (max-width: 58em) {
  .tier-core { display: none; }
}

/* --- player detail ---------------------------------------------------------- */
/* The workings, folded away. They have to be available -- every price the app
   quotes must be auditable -- but nobody opens a player page to read them, and
   expanded they pushed his actual production below the fold. */
.workings { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.workings > summary {
  cursor: pointer; font-weight: 650; font-size: .95rem; color: var(--accent);
  list-style-position: outside;
}
.workings[open] > summary { margin-bottom: .8rem; }

.scoring-table { min-width: 26rem; }
/* Seasons down the side, categories across. Wide enough that the category
   columns don't bunch, and it scrolls inside its own box below that. */
.mode-tabs { display: flex; gap: .25rem; }
.stats-matrix { min-width: 46rem; }
.stats-matrix th.num, .stats-matrix td.num { width: 5%; }
.stats-matrix .season-cell { white-space: nowrap; }
.stats-matrix td.is-blank { color: var(--muted); opacity: .45; }
.scoring-table td, .scoring-table th { padding: .45rem .5rem; }
/* A category he never registered is still worth a row -- it shows what the
   scoring counts -- but it shouldn't compete with the ones he did. */
.scoring-table tr.is-blank td { color: var(--muted); }
.scoring-table tr.is-blank td.num { opacity: .55; }
