
/*
 * Archivo, served from a hashed path so it can be cached for a year. See
 * font-data.ts and the route in router.ts. 34KB, once, shared by every page.
 *
 * A grotesque drawn for print at small sizes, which is the reason it is here:
 * its digits are narrower than the system face's, and this app's widest page is
 * a nine-column table of them.
 *
 * swap rather than block: the text paints immediately in the fallback and
 * re-renders when the face lands. On a table of numbers that flash is real, but
 * it is one paint against a blank table for however long the network takes, and
 * the fallback below is chosen so the layout barely moves when it happens.
 */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/archivo-7150c0ec5ad3.woff2") format("woff2");
}
:root {
  color-scheme: light dark;
  --bg: #fbfaf9; --panel: #fff; --line: #e4e3e0; --line-strong: #c9c7c2;
  --text: #191817; --muted: #6c6a67;
  --accent: #b8371c; --accent-soft: #fdf1ec; --bar: #f7e2da;
  --good: #1c6b46; --down: #191817; --bad: #7f1d12;
  --qb: #9a5a1f; --rb: #2f6b4a; --wr: #3c5480; --te: #7c4070; --pick: #6c6a67;

  /* Corners: 3px for something small enough to be a label, 5px for a control,
     8px for a surface. Nothing here is rounder than 8: docs/design.md. */
  --r-1: 3px; --r-2: 5px; --r-3: 8px;

  /*
   * How wide the content is allowed to get, on any page.
   *
   * Wide, because the content earns it: the rankings are a fourteen-column table
   * and the trade page lays four teams side by side, and squeezing either into a
   * blog measure would be the oversimplification docs/design.md §8 rules out.
   * Bounded, because past this a table row becomes a journey: the eye loses the
   * line it was reading between a player's name and his value, and the top bar
   * ends up with a wordmark and an account menu a metre apart.
   */
  --page: 96rem;

  /* Which of the toggle's two glyphs is the one you can see. It is a display
     value in the palette rather than a class the script adds, because then the
     glyph is right on the first paint in every combination of machine preference
     and stored choice: none of which the server rendering the button can see. */
  --sun: block; --moon: none;
}
/*
 * Dark, from either direction.
 *
 * The media query is the default: with nothing stored, the system decides, which
 * is what this app did before there was a control at all. The :not() is what lets
 * the toggle overrule it: an explicit light choice on a machine set to dark has
 * to win, and a media query at :root would have beaten a plain :root override
 * however the two were ordered.
 *
 * color-scheme is narrowed from "light dark" to the one that is in force, so
 * the things the page does not paint itself: form controls, scrollbars, the
 * dialog's own chrome: follow the choice rather than the machine.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark;
    --bg: #17150f; --panel: #201d16; --line: #332f26; --line-strong: #4a453a;
    --text: #efece4; --muted: #a29d92;
    --accent: #f4643c; --accent-soft: #35211a; --bar: #40241b;
    --good: #57bd8b; --down: #efece4; --bad: #f0806a;
    --qb: #dc9350; --rb: #6cc48f; --wr: #8fa9dd; --te: #cf8fd0; --pick: #a29d92;
    --sun: none; --moon: block;
  }
}
:root[data-theme="dark"] { color-scheme: dark;
    --bg: #17150f; --panel: #201d16; --line: #332f26; --line-strong: #4a453a;
    --text: #efece4; --muted: #a29d92;
    --accent: #f4643c; --accent-soft: #35211a; --bar: #40241b;
    --good: #57bd8b; --down: #efece4; --bad: #f0806a;
    --qb: #dc9350; --rb: #6cc48f; --wr: #8fa9dd; --te: #cf8fd0; --pick: #a29d92;
    --sun: none; --moon: block;
}
:root[data-theme="light"] { color-scheme: light; }
* { box-sizing: border-box; }
/*
 * The top bar is sticky, so anything the app scrolls into view (the roster
 * drilldown, a trade analysis) lands underneath it without this. One line here
 * rather than an offset at each scrollIntoView call site, which would be three
 * places to keep in step with the height of a bar none of them can see.
 */
html { scroll-padding-top: 4rem; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 15px/1.5 Archivo, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-text-size-adjust: 100%;
}
/*
 * Numbers are the product, so they are typeset once here rather than remembered
 * at each call site: docs/design.md §7. Tabular figures everywhere a digit can
 * appear means a column of values lines up on the decimal without a monospace
 * font, and a value that changes in place does not shuffle the ones beside it.
 * The slashed zero is the same argument as the tabular figures: it is a number,
 * and it should not have to be read twice.
 */
body { font-variant-numeric: tabular-nums slashed-zero; }
/*
 * ── how wide the page gets ────────────────────────────────────────────────
 *
 * The gutter is a clamp, so a phone gets 12px and a laptop 40px; past --page the
 * content stops growing and centres instead.
 *
 * The **bar is full-bleed and its contents are not**: the border under it has to
 * reach both edges of the window or it stops reading as a bar, while the wordmark
 * and the account menu belong on the same two vertical lines as the content
 * underneath them. So the bar keeps the same gutter plus whatever centring slack
 * main has, computed rather than hand-set: one number, --page, decides both.
 *
 * A percentage rather than a viewport unit on purpose: 100vw includes the
 * scrollbar, which would put the bar's contents a few pixels left of the content
 * below it. (No backticks in this sheet. It is a template literal.)
 */
main { max-width: var(--page); margin-inline: auto; padding: 1.5rem clamp(.75rem, 3vw, 2.5rem) 3.5rem; }
/*
 * The heading scale is tight on purpose: -.02em at this size is the difference
 * between a page title and a marketing headline, and the second one is on the
 * list of things docs/design.md rules out.
 */
h1 { font-size: clamp(1.35rem, 3.5vw, 1.75rem); margin: 0 0 .3rem; letter-spacing: -.024em; font-weight: 650; }
.sub { margin: 0 0 1.5rem; color: var(--muted); font-size: .875rem; max-width: 68ch; }

/*
 * The section switcher, above the heading it names one of.
 *
 * Deliberately not a second row of tabs. The top bar's tabs are the product's
 * five surfaces and they earn the accent underline; repeating that treatment
 * here would give two rows equal weight and leave the reader working out which
 * one they are inside. So this is smaller, quieter, and marks the current page
 * by making it the only ink-coloured word in the row: the same "here" the tabs
 * say, at the volume a sub-section deserves. docs/design.md §11.
 */
/*
 * The site line under every page. Muted, small, and separated by a rule rather
 * than by a slab of empty space. docs/design.md §11 rules out whitespace created
 * to look premium, and this is a signpost, not a section.
 */
.site-foot {
  display: flex; flex-wrap: wrap; gap: .1rem 1rem; align-items: baseline;
  max-width: var(--page); margin: 2.5rem auto 0;
  padding: .9rem clamp(.75rem, 3vw, 2.5rem) 1.5rem;
  border-top: 1px solid var(--line); color: var(--muted); font-size: .75rem;
}
.site-foot a { color: var(--muted); text-decoration: none; }
.site-foot a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 2px; }

.dialog-note { margin-top: .75rem; }
.dialog-note a { color: var(--muted); }

.subnav { display: flex; gap: 1rem; margin: 0 0 .85rem; font-size: .8125rem; }
.subnav a { color: var(--muted); text-decoration: none; font-weight: 550; padding-bottom: .15rem;
            border-bottom: 1px solid transparent; }
.subnav a:hover { color: var(--text); border-bottom-color: var(--line-strong); }
.subnav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--accent); }
a { color: var(--accent); text-underline-offset: 2px; }

/*
 * ── skip link ─────────────────────────────────────────────────────────────
 *
 * The first thing a Tab reaches, and off-screen until it is. Every page puts
 * a wordmark and four links in front of its content, and the rankings put
 * eleven settings fields in front of the table, so without this, reaching the
 * table from the address bar is a dozen presses, on every page, every time.
 *
 * Positioned off the top rather than display:none or visibility:hidden, both of
 * which take an element out of the tab order entirely and would make this a link
 * nobody can ever reach. z-index above the sticky bar, which it would otherwise
 * appear underneath.
 */
.skip-link {
  position: absolute; left: .5rem; top: -3rem; z-index: 40;
  background: var(--panel); color: var(--text); border: 1px solid var(--accent);
  border-radius: var(--r-2); padding: .5rem .8rem; font-size: .875rem; font-weight: 550;
  text-decoration: none; transition: top .12s ease-out;
}
.skip-link:focus { top: .5rem; }
/* The heading is what the reader wants to see, not an outline around everything
   below it, main only takes focus so that the skip link has somewhere to land. */
main:focus { outline: none; }

/* ── top bar ─────────────────────────────────────────────────────────────── */
.top {
  display: flex; align-items: center; gap: 1rem;
  padding: .55rem calc(max(0px, (100% - var(--page)) / 2) + clamp(.75rem, 3vw, 2.5rem));
  border-bottom: 1px solid var(--line); background: var(--panel);
  position: sticky; top: 0; z-index: 20;
}
.brand {
  font-weight: 700; letter-spacing: -.04em; font-size: 1.15rem; color: var(--text);
  text-decoration: none;
}
/*
 * The tabs are underlined, not filled. Four solid accent pills across the top of
 * every page, one of them lit at all times, spent the brand colour on the one
 * piece of the interface that never changes, and left nothing for the data to
 * say anything with. An underline says "here" just as plainly and gives the
 * colour back. docs/design.md §9.
 */
.tabs { display: flex; gap: .1rem; align-self: stretch; align-items: stretch; }
.tab {
  display: flex; align-items: center; padding: .3rem .55rem; text-decoration: none;
  color: var(--muted); font-size: .875rem; font-weight: 550;
  border-bottom: 2px solid transparent; margin-bottom: -.55rem; padding-bottom: .5rem;
}
.tab:hover { color: var(--text); border-bottom-color: var(--line-strong); }
.tab.on { color: var(--text); font-weight: 650; border-bottom-color: var(--accent); }

.league-menu { margin-left: auto; position: relative; }
.league-menu > summary {
  list-style: none; cursor: pointer; font-size: .8125rem; font-weight: 550;
  padding: .35rem .7rem; border: 1px solid var(--line); border-radius: var(--r-2);
  background: var(--bg); max-width: 15rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.league-menu > summary::-webkit-details-marker { display: none; }
.league-menu > summary::after { content: " ▾"; color: var(--muted); }
.league-menu > summary:hover { border-color: var(--line-strong); }
.menu-body {
  position: absolute; right: 0; top: calc(100% + .35rem); z-index: 30;
  min-width: 17rem; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-3); padding: .5rem; box-shadow: 0 10px 28px rgb(0 0 0 / .10);
}
/* Both would otherwise claim the free space with margin-left:auto and end up
   apart. The league menu keeps it; the account sits against it. Stated after
   .league-menu so equal specificity resolves this way round. */
.account { margin-left: .4rem; }
.account-email { margin: 0 .35rem .4rem; font-size: .8125rem; overflow-wrap: anywhere; }
.account-link {
  margin-left: .4rem; font-size: .8125rem; font-weight: 550; text-decoration: none;
  padding: .35rem .7rem; border: 1px solid var(--line); border-radius: var(--r-2);
  background: var(--bg); color: var(--text); white-space: nowrap;
}
.account-link:hover { border-color: var(--line-strong); }
.account-form { margin: 0; display: contents; }
button.account-link { cursor: pointer; font-family: inherit; }
.menu-label { margin: .15rem .35rem .35rem; font-size: .7rem; text-transform: uppercase;
              letter-spacing: .07em; color: var(--muted); }
/*
 * The operator's own group. The rule goes on the group rather than on the
 * actions row inside it: .menu-actions carries its own top border, which would
 * have drawn between the label and the two things the label is about.
 *
 * The address is set in the label, so it is not uppercased into something that
 * no longer looks like an address: an email in small caps reads as a heading
 * rather than as the account it names.
 */
.menu-owner { border-top: 1px solid var(--line); margin-top: .45rem; padding-top: .45rem; }
.menu-owner .menu-label { text-transform: none; letter-spacing: .01em; font-size: .75rem;
                          color: var(--text); overflow-wrap: anywhere; }
.menu-owner .menu-actions { border-top: 0; padding-top: 0; }
.menu-note { margin: 0 .35rem .45rem; font-size: .7rem; line-height: 1.35; color: var(--muted); }
.menu-list { list-style: none; margin: 0 0 .4rem; padding: 0; display: grid; gap: .15rem; }
.menu-list button {
  width: 100%; text-align: left; border: 0; background: none; padding: .35rem .5rem;
  border-radius: var(--r-1); font-size: .8125rem;
}
.menu-list button:hover { background: var(--bg); }
.menu-list button[aria-current="true"] { background: var(--text); color: var(--bg); }
.menu-list .meta { display: block; font-size: .7rem; opacity: .7; }
.menu-actions { display: flex; flex-wrap: wrap; gap: .35rem; border-top: 1px solid var(--line); padding-top: .45rem; }
/*
 * Wrap rather than squeeze. Three buttons sharing one row of a 17rem popover left
 * "Add Sleeper league" wrapping inside 81px; given a 6rem basis it takes 124px on
 * a second row instead. A row costs 52px of popover once and reads as a label
 * rather than a stack of single words.
 */
.menu-actions button { flex: 1 1 6rem; font-size: .8125rem; }
/*
 * The account menu's two ways out, stacked and written as links.
 *
 * A row of buttons said "these are things that happen here", and both of them
 * are somewhere to go. Stacked full-width rows are what a menu of destinations
 * looks like everywhere else, which is docs/design.md §12: familiar before novel.
 *
 * One rule for both, and the button reset is the load-bearing half: sign out is
 * a submit rather than an anchor (see accountControl for why) and would otherwise
 * arrive carrying the shell's button border, fill and radius. Matched here so the
 * two rows are indistinguishable, because the difference between them is a
 * property of the request, not of the choice a reader is making.
 */
.menu-items { display: grid; gap: .1rem; border-top: 1px solid var(--line); margin-top: .45rem; padding-top: .35rem; }
.menu-items form { margin: 0; display: contents; }
.menu-item {
  display: block; width: 100%; text-align: left; font: inherit; font-size: .8125rem;
  border: 0; background: none; color: var(--text); text-decoration: none;
  padding: .4rem .5rem; border-radius: var(--r-1);
}
.menu-item:hover { background: var(--bg); text-decoration: none; }

/* ── theme toggle ──────────────────────────────────────────────────────────
 *
 * It sat in the top bar and now sits in the settings section of /profile, beside
 * a label. The rules below are unchanged in everything except the margin that
 * spaced it from the league button, because what shaped it there is what it still
 * wants: a control the size of the words around it, quiet at rest.
 *
 * The em sizing is what makes it line up: 1.5em at .8125rem is 19.5px, which is
 * exactly the line box of the label beside it, so both boxes come out the same
 * height without either one being told a pixel figure.
 *
 * Muted at rest and ink on hover, the same two steps as the tabs. An icon needs
 * 3:1 against its background and muted gives 5.2:1 on paper, 5.6:1 on dark.
 */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .35rem .55rem; font-size: .8125rem; line-height: 1.5;
  background: var(--bg); color: var(--muted);
}
.theme-toggle:hover { color: var(--text); border-color: var(--line-strong); }
.theme-toggle svg { width: 1.2em; height: 1.5em; fill: currentColor; }
/*
 * The glyph is the state, so the state gets no fill. Restated rather than left to
 * inherit, because `button[aria-pressed="true"]` a few rules down paints ink on
 * ink-inverted text: the right answer for a filter chip, and a lit-up white
 * square in the corner of the bar for this. (0,2,0) against that rule's (0,1,1),
 * so this wins wherever the two are stated.
 */
.theme-toggle[aria-pressed="true"] { background: var(--bg); color: var(--muted); border-color: var(--line); }
.theme-toggle[aria-pressed="true"]:hover { background: var(--bg); color: var(--text); border-color: var(--line-strong); }
.theme-toggle .sun { display: var(--sun); }
.theme-toggle .moon { display: var(--moon); }

/* ── controls ──────────────────────────────────────────────────────────────
 *
 * Selected is ink, not accent. Every filter, format and position control on the
 * rankings page can be on at once, and in accent that was eleven blue fills in a
 * row above the table: the loudest thing on a page whose subject is the table.
 * Ink says "on" with more contrast than the accent did (13:1 against paper), and
 * it inverts for free in dark mode because the two tokens swap.
 */
button {
  font: inherit; color: inherit; cursor: pointer; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-2); padding: .35rem .7rem;
}
button:hover { border-color: var(--line-strong); background: var(--bg); }
button[aria-pressed="true"] { background: var(--text); color: var(--bg); border-color: var(--text); }
button[aria-pressed="true"]:hover { background: var(--text); border-color: var(--text); }
.group { display: flex; gap: .15rem; background: var(--panel); border: 1px solid var(--line);
         border-radius: var(--r-2); padding: .2rem; }
.group button { background: none; border: 0; border-radius: var(--r-1); padding: .3rem .7rem; }
.group button:hover { background: var(--bg); }
/*
 * Restated after .group button, not merged into the rule above. Both selectors
 * carry the same specificity, so the later one wins: without this the grouped
 * variant stripped the fill but kept the inverted text colour, leaving the
 * selected button unreadable against the panel. It shipped that way once.
 */
.group button[aria-pressed="true"] { background: var(--text); color: var(--bg); }
.group button[aria-pressed="true"]:hover { background: var(--text); }
/*
 * A filter chip is on until you turn it off, so "on" is the resting state and
 * gets no emphasis at all: five ink-filled pills above the table said "look
 * here" about a row that is only ever saying "nothing is filtered". Off is what
 * is worth marking, and it is marked by receding: muted text on the page colour,
 * so the chips you have left on are the ones that stand up.
 */
.chip { font-weight: 600; font-size: .8125rem; }
/*
 * Each selector is stated twice, once inside .group, because the position chips
 * sit in one: .group button[aria-pressed="true"] is (0,2,1) and a bare
 * .chip[aria-pressed="true"] is (0,2,0), so the group's ink fill won and the
 * chips stayed black however this rule was written. Chrome said so: the same
 * class of specificity bug the group rule above already carries a note about,
 * and the reason browser-check exists.
 */
.chip[aria-pressed="true"], .group .chip[aria-pressed="true"] {
  background: transparent; color: var(--text); border-color: var(--line-strong); font-weight: 650;
}
.chip[aria-pressed="false"], .group .chip[aria-pressed="false"] {
  background: var(--bg); color: var(--muted); border-color: var(--line); font-weight: 550;
}
.chip:hover { border-color: var(--line-strong); }
input[type="search"], input[type="text"], input[type="number"], select {
  font: inherit; color: inherit; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-2); padding: .35rem .55rem;
}
input[type="search"] { padding: .45rem .7rem; min-width: 12rem; }
input[type="number"] { width: 3.6rem; text-align: right; font-variant-numeric: tabular-nums; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.field { display: inline-flex; align-items: center; gap: .35rem; font-size: .8125rem; color: var(--muted); }
.field.wide input, .field.wide select { min-width: 13rem; }
.field > span { white-space: nowrap; }
.hint { margin: .5rem 0 0; color: var(--muted); font-size: .75rem; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden;
                   clip-path: inset(50%); white-space: nowrap; }

dialog { border: 1px solid var(--line); border-radius: var(--r-3); background: var(--panel);
         color: var(--text); padding: 0; max-width: min(34rem, 92vw); }
dialog::backdrop { background: rgb(0 0 0 / .45); }
.dialog-body { padding: 1rem 1.1rem 1.1rem; display: grid; gap: .6rem; }
.dialog-body h2 { margin: 0; font-size: 1.05rem; }
.dialog-actions { display: flex; gap: .5rem; }
.league-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem;
               max-height: 15rem; overflow-y: auto; }
.league-list button { width: 100%; text-align: left; }
.league-list .meta { color: var(--muted); font-size: .75rem; }

/* ── panels and tables ─────────────────────────────────────────────────────
 *
 * A panel is a surface, and a surface that does not hold its content off its own
 * border is not one. The padding and the gap below are stated here rather than
 * on each page because the page that forgot them (/connect, whose five panels
 * shared no rule of their own) rendered its text flush against the rule and its
 * sections touching each other, which read as one broken box rather than five.
 * A page that wants its own padding still states it: page styles are emitted
 * after these, so .side and .verdict win where they disagree.
 */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-3);
         padding: 1rem 1.1rem; }
/*
 * Scoped to main's direct children, and that scoping is the whole rule.
 *
 * Written bare, the selector matches any two adjacent panels: including the two
 * trade sides, which are siblings inside a grid. A grid item does not want a
 * margin: it has a cell and a gap already, so the second side was pushed 16px
 * down inside its own cell and the two halves of a trade sat out of line with
 * each other. That shipped in the shell refresh and was in every screenshot of
 * the page afterwards, unnoticed, because a 16px offset looks like a choice.
 *
 * The controls clauses are the same rule's other half, and dropping them had a
 * symptom of their own: on /connect the assistant picker sits *between* two
 * panels, and the controls row carries only a bottom margin, so the picker was
 * flush against the panel above it and 16px clear of the one below. Measured at
 * 0 and 16. One over-simplification, two pages, two symptoms.
 *
 * Scoping to main's own children says what was always meant: things stacked down
 * the page, in normal flow, need air. Things a page has arranged itself do not.
 */
main > .panel + .panel,
main > .panel + .controls,
main > .controls + .panel { margin-top: 1rem; }
/* Stated once here rather than per page: three pages lay out a row of controls
   above their content, two of them had identical copies of this rule, and the
   third, /connect, had none, so its assistant picker sat in the text flow. */
.controls { display: flex; flex-wrap: wrap; gap: .5rem .75rem; align-items: center; margin-bottom: 1rem; }
/*
 * The note at the end of a controls row: how many assets are showing, how many
 * simulations the odds came from. Stated here for the same reason .controls is:
 * three pages use the class, two of them carried identical copies of this rule,
 * and the third, /league: carried none, so its line rendered as 15px body text
 * flush against the controls instead of as the caption it is.
 */
.count { color: var(--muted); font-size: .8125rem; margin-left: auto; }
/*
 * The wide tables scroll inside here rather than widening the page, which is
 * what keeps a 13-column table usable on a phone at all. overscroll-behavior-x
 * is the mobile half of that: without it, swiping past the end of the table
 * hands the gesture to the page, and on iOS that gesture is back-navigation.
 */
.table-wrap { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-3);
              overflow-x: auto; overscroll-behavior-x: contain; }
/*
 * The row height is set by the tallest cell, and the tallest cell is set by the
 * line box of its text, so the leading matters more here than the padding does.
 * 1.5 is right for a paragraph and loose for a row of figures that are one line
 * each by construction. At 1.3 a rankings row came down from 46px to 38px, which
 * is four more rows on a laptop screen and the same information.
 */
table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; line-height: 1.3; }
th, td { text-align: left; padding: .4rem .6rem; white-space: nowrap; width: 1%; }
th.spacer { width: 100%; padding: 0; cursor: default; }
th.spacer:hover { background: none; }
/*
 * The header is a label, not a heading: small, spaced, muted, and separated from
 * the body by the one rule in the table that is darker than the row lines. That
 * hierarchy is what lets the row rules stay as faint as they are: the eye reads
 * the block of numbers, and finds the divisions only when it looks for them.
 */
thead th {
  position: sticky; top: 0; z-index: 1; background: var(--panel);
  border-bottom: 1px solid var(--line-strong); font-size: .6875rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); font-weight: 600; user-select: none;
}
tbody tr { border-top: 1px solid var(--line); }
tbody tr:first-child { border-top: 0; }
/* A row under the pointer, faintly. Anything stronger competes with the row a
   page paints to mean something: a cut, the pick in question, the team whose
   roster is open. Which row is *yours* is said in words instead; see .you. */
tbody tr:hover { background: color-mix(in srgb, var(--text) 3%, transparent); }
.num, th.num { text-align: right; }
.pos { font-weight: 700; font-size: .6875rem; letter-spacing: .05em; }
.p-QB { color: var(--qb); } .p-RB { color: var(--rb); } .p-WR { color: var(--wr); }
.p-TE { color: var(--te); } .p-PICK { color: var(--pick); }
.dash { color: var(--muted); opacity: .5; }
/*
 * The Manager column exists only where there is a league to have a manager in.
 *
 * Without one the cell is empty on every row, by construction: the name comes
 * off the saved league's rosters and there is nothing else it could say. On the
 * rankings that was a column of blanks until somebody connected a league; on a
 * segment page, which is lean and ships no client script, it could never fill at
 * all, so ten public pages carried a heading over 500 empty cells.
 *
 * Hidden rather than left out of the markup, the way the source columns are.
 * One row builder still emits every cell, so what the server renders and what
 * the browser redraws stay identical, and turning the column on is a class
 * rather than a re-render. The class goes on the table, which is what makes this
 * rule work for a page that has no client script to add it.
 */
table:not(.with-manager) td.manager,
table:not(.with-manager) th.manager-head { display: none; }
/*
 * "You", in a row that is yours: your team in the standings, your player in the
 * rankings, your side of a trade.
 *
 * It replaces a tinted row, and the replacement is the point. A tint has to be
 * faint enough to read text through, which puts it in the same register as the
 * hover fill and the open-row fill, so three unrelated meanings competed for the
 * same one signal, and on the rankings it painted thirty rows of a five-hundred
 * row table, which is a wash rather than a mark. A word says which row is yours
 * once, in the cell that names the thing, and leaves the row's background free
 * to mean what it means everywhere else: the pointer is here.
 *
 * Accent, because "this one is yours" is the only identity claim in the product
 * and identity is one of the jobs docs/design.md §9 gives colour. It is a word
 * rather than a glyph for the same reason the theme toggle is a glyph rather
 * than a word: this one reads.
 */
.you {
  flex: none; color: var(--accent); font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
}
.empty { padding: 2rem; text-align: center; color: var(--muted); margin: 0; }
footer { margin-top: 1rem; color: var(--muted); font-size: .8125rem; max-width: 80ch; }
.needs-league { padding: 2.5rem 1.5rem; text-align: center; color: var(--muted); }
.needs-league button { margin-top: .75rem; }

/* ── narrow screens ───────────────────────────────────────────────────────────
 *
 * Most of the layout above already reflows: the grids are auto-fit, the wide
 * tables scroll inside .table-wrap, and main's padding is a clamp. Two things
 * did not, and both were measured on a 390px viewport rather than guessed at:
 *
 *   1. The top bar. Brand, three tabs and the league button in one non-wrapping
 *      row came to 569px with a real league name in it, so every page scrolled
 *      sideways and the league button, the control that switches leagues, sat
 *      off the right edge with no way to reach it. The tabs take their own row
 *      here and the league name ellipsizes.
 *   2. Anything anchored to the right of a control that was itself off-screen:
 *      the league popover ended at 569px too, for the same reason.
 *
 * One breakpoint on purpose. 700px is where the one-row top bar stops fitting
 * its widest state, and a second number would be a second thing to keep true.
 */
@media (max-width: 700px) {
  .top { flex-wrap: wrap; gap: .5rem; padding: .5rem .75rem; }
  .brand { font-size: 1.05rem; }
  /* Two rows of bar to clear now instead of one. */
  html { scroll-padding-top: 7.5rem; }
  /*
   * flex-wrap above is what stops the bar overflowing; these decide what shape
   * it takes once it is allowed to. Wrapping on its own puts the breaks wherever
   * they happen to fall, and where that is depends on how long the league is
   * called: a 40-character name took a row of its own and pushed the bar to
   * three rows. Stated instead: tabs on their own row, three equal targets
   * across it, league name beside the brand.
   *
   * The 60% cap is what holds that second part, and it is the only thing that
   * needs to. Flex wraps an item onto a new line based on the width it *wants*,
   * so the usual min-width: 0 does not prevent the wrap, and it is not needed
   * for the ellipsis either, because a flex item's automatic minimum size is
   * itself clamped by max-width. Capped at 60% of the bar the button can never be
   * the reason the line breaks, and a name too long for that ellipsizes.
   */
  .tabs { order: 3; flex: 1 0 100%; gap: .25rem; }
  /*
   * Four tabs across one row, not three, and five for the one account that has
   * the Admin tab. The horizontal padding is what decides whether they fit: at
   * the desktop .7rem a side they overflow a 320px phone by a few pixels, which
   * is invisible on a 390px one and is exactly the case the narrow mobile check
   * exists to catch. min-width: 0 lets a long label shrink rather than setting
   * the row's floor, which is what leaves room for the fifth.
   */
  /* The desktop tab hangs its underline on the bar's bottom edge with a negative
     margin. On a second row that would pull the underline through the bar's own
     border, so here the tab sits in its row and underlines itself. */
  .tab { flex: 1; justify-content: center; text-align: center; padding-inline: .3rem;
         min-width: 0; margin-bottom: 0; padding-bottom: .3rem; }
  .league-menu { max-width: 60%; }
  .league-menu > summary { max-width: 100%; }
  /* min-width cannot be capped by max-width, min-width wins, so the popover's
     17rem floor has to be lifted before the cap means anything. */
  .menu-body { min-width: 0; width: 17rem; max-width: calc(100vw - 1.5rem); }

  /* 16px is the threshold below which iOS Safari zooms in on a focused field,
     and it does not zoom back out afterwards. Stated at the same specificity as
     the "font: inherit" rule above, and after it, so it wins. */
  input[type="search"], input[type="text"], input[type="number"], select, textarea { font-size: 1rem; }
  input[type="search"] { min-width: 0; }
  input[type="number"] { width: 4rem; }

  /* 29px off the 1005px rankings table; the column widths in rankings-page.ts
     take another 62px, leaving 917px of table to swipe through. */
  th, td { padding: .4rem .5rem; }

  dialog { max-width: calc(100vw - 1.5rem); max-height: 85vh; max-height: 85dvh; }
  .dialog-body { padding: .9rem 1rem 1rem; }
  /* The label sitting beside its input is what makes this row too wide to fit. */
  .field.wide { flex-direction: column; align-items: stretch; }
  .field.wide input, .field.wide select { min-width: 0; width: 100%; }
  .dialog-actions button { flex: 1; }
}

/* ── touch ────────────────────────────────────────────────────────────────────
 *
 * Keyed on the pointer, not the width: a tablet is wide and still a thumb, and a
 * narrow desktop window is narrow and still a mouse. The controls above are
 * 28-33px tall, under both platform minimums (44px iOS, 48dp Android).
 */
@media (pointer: coarse) {
  .tab, summary, button, select, input { min-height: 2.75rem; }
  /* The rule above makes it 44px tall; a 30px-wide button is still not a 44px
     target, and this is the only control in the app with no words to widen it. */
  .theme-toggle { min-width: 2.75rem; }
  /* A hit area around the wordmark without moving the wordmark: it is a link
     home, and at 25px tall it was the smallest target in the shell. */
  .brand { display: inline-flex; align-items: center; min-height: 2.75rem; }
  /* The sort control on a table is its header cell, so it needs the same. */
  thead th { padding-top: .7rem; padding-bottom: .7rem; }
  /*
   * Links inside prose are deliberately not in that list. They are 17px tall
   * because they are words in a sentence, and padding them to 44px would either
   * break the line spacing around them or make them overlap each other.
   */
}
