/* ---------------------------------------------------------------------------
   RootFor, styled on sitekit, the saarthm.com design system.

   The kit is linked before this file (index.html loads assets/sitekit/
   sitekit.css, which tools/build-sitekit.mjs writes from packages/sitekit:
   the primitives and the saarthm skin). It supplies the type stacks, the
   shape and motion values, the palette (--dark-*, --light-*) and the roles
   every site shares: --bg, --ink, --head, --card, --card-edge, --trim, --tag,
   --rule, --glow. Poppins comes from the kit's fonts/ as well, and the
   monogram from its sprite. The theme boot stays RootFor's own (index.html):
   a share link's theme has to win over the stored one, which the kit's boot
   knows nothing about.

   Where RootFor departs from the parent site, it's for density. That site is a
   spacious page you read; this is a table you scan during a game. So the type
   scale is smaller and the padding tighter, and the app keeps tiers of text
   and surface below the ones the kit names. Every colour here is a kit role,
   a palette entry, or a tint of one; nothing is a second copy of a kit value.

   Colour still carries meaning: --pos = root for, --neg = root against,
   copper = attention, muted = genuinely doesn't matter.
   --------------------------------------------------------------------------- */

:root {
  /* type: the kit's stacks, under the names this file has always used */
  --sans: var(--font);
  --mono: var(--font-mono);

  /* controls: every button, input, select and switch pill stands this tall,
     so a row of mixed controls lines up instead of stair-stepping. The
     compact height is a pill inside a segmented control, or a small button;
     a segment's padding and border bring it back to the full height. */
  --control-h: 40px;
  --control-h-compact: 30px;

  /* shape: --radius-lg is the kit's one radius (16px). The two smaller
     steps are RootFor's own, for its denser controls. --radius-pill,
     --ease and --transition are used straight from the kit. */
  --radius-lg: var(--radius);
  --radius-md: 12px;
  --radius-sm: 8px;
}

/* --- light: Jet & Slate ----------------------------------------------------- */

html[data-theme='light'] {
  /* The kit's roles under RootFor's names: the raised surface, its border,
     body text, the interaction colour (active tabs, focus rings, the
     wordmark). --bg, --trim, --rule and --glow are used by the kit's names. */
  --bg-raised: var(--card);
  --border: var(--card-edge);
  --text: var(--ink);
  --accent: var(--head);

  /* RootFor's own surface tiers below the kit's card. */
  --bg-sunken: #dfe8f3;
  --bg-hover: #e4ecf6;
  --border-strong: #c2d0e1;

  /* Contrast against --bg (#ebf2fa): 6.4 and 4.5 to 1. --text-dim sits a
     shade lighter than the kit's --tag (#3d4f58); a dense table wants a
     softer second tier. --text-faint was #8695a0, which was 2.7:1, below
     the 3:1 floor for even decorative text and the worst offender in either
     theme. */
  --text-dim: #4a5a62;
  --text-faint: #61707a;

  --accent-soft: rgba(83, 98, 141, 0.12);
  --accent-strong: #3f4d73;

  /* --pos / --neg are DATA, never chrome. */
  --pos: #2f7d63;
  --pos-soft: rgba(47, 125, 99, 0.12);
  --neg: #b04a32;
  --neg-soft: rgba(176, 74, 50, 0.11);

  /* Copper is the kit's attention colour. RootFor uses it for "close" and
     for the stakes multiplier. The skin's copper-as-text shade is 5.4:1
     here, so unlike the dark theme it needs no adjustment. */
  --copper: var(--light-copper-ink);
  --copper-soft: rgba(196, 168, 131, 0.22);

  --bar-outline: rgba(31, 54, 61, 0.4);

  --shadow-sm: 0 1px 3px rgba(31, 54, 61, 0.07);
  --shadow-md: 0 3px 14px rgba(31, 54, 61, 0.1);
  --shadow-lift: 0 10px 26px rgba(31, 54, 61, 0.16);
}

/* --- dark: Granite & Copper (default) --------------------------------------- */

html,
html[data-theme='dark'] {
  /* The kit's roles under RootFor's names, as above. --text-dim is the
     kit's secondary text tier exactly; in the light theme it is not. */
  --bg-raised: var(--card);
  --border: var(--card-edge);
  --text: var(--ink);
  --text-dim: var(--tag);
  --accent: var(--head);

  --bg-sunken: rgba(0, 0, 0, 0.16);
  --bg-hover: rgba(235, 242, 250, 0.09);
  --border-strong: rgba(235, 242, 250, 0.28);

  /*
   * The two lower tiers are the same alice blue at reduced opacity, so their
   * contrast against granite is set by the alpha alone.
   *
   * They were 0.58 and 0.40, which is 3.8:1 and 2.7:1, both under the 4.5:1
   * WCAG AA floor for body text, and the second badly so. Column headings,
   * hints and every "proj" figure were rendered in the worse of the two.
   * Raised to 5.5:1 and 4.6:1, which clears AA while keeping a visible step
   * between the tiers and against --text at 7.8:1. The 0.78 tier is now the
   * kit's --tag; the 0.68 one stays RootFor's.
   */
  --text-faint: rgba(235, 242, 250, 0.68);

  --accent-soft: rgba(157, 203, 186, 0.14);
  --accent-strong: #b6dccd;

  /* mint, the skin's dark heading colour, doubles as "root for" */
  --pos: var(--dark-head);
  --pos-soft: rgba(157, 203, 186, 0.14);
  --neg: #e59a83;
  --neg-soft: rgba(229, 154, 131, 0.14);

  /*
   * Lighter than the skin's copper (--dark-copper, #ad8350), and only here.
   *
   * That value is 2.6:1 against granite. It carries the stakes multiplier and
   * the "close" tag, both set in 10px mono, which is the size that needs the
   * most contrast rather than the least. This is the same hue and saturation
   * lifted to 4.5:1, so it still reads as the same copper, and it stays 19
   * degrees clear of --neg.
   *
   * --trim keeps the skin's copper: a 3px rule shared with the parent sites,
   * where contrast is not a consideration and matching them is.
   */
  --copper: #cfb596;
  --copper-soft: rgba(207, 181, 150, 0.16);

  --bar-outline: rgba(235, 242, 250, 0.42);

  --shadow-sm: none;
  --shadow-md: 0 3px 14px rgba(0, 0, 0, 0.4);
  --shadow-lift: 0 10px 26px rgba(0, 0, 0, 0.45);
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

/*
 * Stop iOS resizing the type when you turn the phone sideways. It "helpfully"
 * inflates text in landscape, which breaks every column width computed here.
 */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  background-image: var(--glow);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition:
    background-color var(--transition),
    color var(--transition);
}

::selection {
  background: var(--trim);
  color: var(--bg);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

input,
select {
  font: inherit;
  color: inherit;
}

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

/*
 * Launched from the home screen there is no browser chrome, so without this the
 * page runs under the status bar and the notch. `viewport-fit=cover` in the
 * viewport meta is what makes env() report anything at all; these are the
 * insets it then hands back. On a laptop they are all zero and nothing moves.
 */
.app {
  max-width: 1240px;
  margin: 0 auto;
  padding:
    0
    max(22px, env(safe-area-inset-right))
    calc(80px + env(safe-area-inset-bottom))
    max(22px, env(safe-area-inset-left));
}

/* --- header ---------------------------------------------------------------- */

/*
 * The bar, the tab strip and the score ticker pin together as one block.
 *
 * sitekit's shell draws the bar itself (the brand in the kit's two-line
 * wordmark, the actions, the frosted ground) and this app adds the two rows
 * beneath it. One sticky wrapper rather than three sticky rows, because three
 * rows each need to know the height of the ones above them, and those heights
 * move with the safe-area inset, the breakpoint and whether a record is being
 * shown; a single block pins with nothing measured, and the one thing that
 * has to clear it, the parlay slip, reads --pinned-h, which App writes from a
 * ResizeObserver.
 *
 * Full bleed: the block sits outside .app so its ground and its rule run edge
 * to edge like every other kit bar, and each row centres its own content to
 * the app's measure.
 */
.pinned {
  position: sticky;
  top: 0;
  z-index: 50;
  margin-bottom: 20px;
  /* The block is what meets the notch (viewport-fit=cover in index.html), so
     the inset is its, whichever row happens to be first; painted, so the
     inset is never a strip of page showing through. */
  padding-top: env(safe-area-inset-top, 0px);
  background: var(--bg);
}
.pinned .topbar {
  position: static;
  /* The strip beneath carries the copper rule for the whole block; a second
     one here would be two rules a few pixels apart. */
  border-bottom: 1px solid var(--rule);
}
.pinned .topbar .inner,
.pinned .tabs-wrap {
  max-width: 1240px;
  padding-left: max(22px, env(safe-area-inset-left));
  padding-right: max(22px, env(safe-area-inset-right));
}
.pinned .ticker {
  padding-left: max(22px, env(safe-area-inset-left));
  padding-right: max(22px, env(safe-area-inset-right));
}
/* No ticker on a phone. The player marquee was already gone at this width
   (see .ticker-marquee), which left a one-line summary pinned above a bar
   that already says whose week it is, and the pinned block was a quarter of
   the screen. The record and the projection are on the boards beneath. */
@media (max-width: 700px) {
  .pinned .ticker { display: none; }
}

/* The league summary that used to sit under the wordmark: the one mono line
   of app state, first thing under the pinned block. */
.source-line {
  margin: 0 0 18px;
  font-family: var(--mono);
  font-size: 12.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Set inline in a heading, exactly as the hub page sets the football. Every
   other size the sprite takes (the brand, the footer) is sitekit's. */
.mark-inline {
  display: inline-flex;
  width: 1.1em;
  height: 1.1em;
  vertical-align: -0.15em;
}

/* --- ticker ---------------------------------------------------------------- */

.ticker {
  /* The first row of the pinned block (see .pinned), above the bar; the block
     pins, so this no longer positions itself. Opaque rather than frosted:
     frosting only blurs what is behind it, and with rows scrolling under it
     that is a legible smear, and backdrop-filter silently does nothing in
     several situations, so it was never something to rely on. */
  background: var(--bg);
  /* A hairline between this and the bar. The copper rule is the tab strip's,
     at the bottom, so the three rows read as one bar with one strong edge. */
  border-bottom: 1px solid var(--rule);
  margin: 0;
  padding: 11px 22px;
  display: flex;
  align-items: center;
  gap: 18px;
  transition:
    background-color var(--transition),
    border-color var(--transition);
}

/* The min-height is what fixes the bar's height. It is always rendered, so
   whatever else comes and goes beside it, the bar stays the same size and the
   sticky tabs below never have to move. */
.ticker-summary {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-height: 26px;
  white-space: nowrap;
  padding-right: 18px;
  border-right: 1px solid var(--rule);
  flex-shrink: 0;
}

/* Between where this started and matching the tabs outright: close enough to
   the tabs below to read as one bar, small enough not to compete with them.
   The figures keep a step up on the words so the number still leads. */
.ticker-summary .big {
  font-family: var(--mono);
  font-size: 18px;
  font-weight: 700;
}

.ticker-label {
  font-size: 14px;
  color: var(--text-faint);
}

.ticker-sep {
  color: var(--border-strong);
}

.ticker-empty {
  font-size: 12.5px;
  color: var(--text-faint);
}

/*
 * The players scroll themselves rather than waiting to be dragged.
 *
 * There is always more here than fits, and a strip you have to discover and
 * then drag is one you never read, the fade that used to hint at it was
 * treating the symptom. Moving on its own, the whole list arrives eventually
 * without anyone touching it, which is what a ticker is.
 *
 * Both edges fade so items enter and leave rather than appearing at a hard
 * border.
 */
.ticker-marquee {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 22px,
    #000 calc(100% - 22px),
    transparent
  );
}

/*
 * Slides exactly one copy's width, then repeats. At that instant the next copy
 * occupies the position the first one started in, so the reset is invisible and
 * the strip reads as endless.
 */
.ticker-track {
  display: flex;
  width: max-content;
  animation: ticker-marquee var(--marquee-duration, 40s) linear infinite;
}

@keyframes ticker-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(var(--marquee-shift, -50%));
  }
}

/* Stop it so a value can actually be read, rather than making people chase it. */
.ticker-marquee:hover .ticker-track {
  animation-play-state: paused;
}

/*
 * Persistent looping motion in the corner of the eye is one of the clearest
 * triggers for vestibular disorders, and it's the whole design of this element.
 * Where the system asks for less of it, the strip goes back to being dragged.
 */
@media (prefers-reduced-motion: reduce) {
  .ticker-track {
    animation: none;
  }
  .ticker-marquee {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .ticker-marquee::-webkit-scrollbar {
    display: none;
  }
}

.ticker-item {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-size: 14px;
  flex-shrink: 0;
  /* Spacing has to be a margin rather than the track's `gap`. With `gap` the
     track measures items plus n−1 gaps, so one copy is not exactly 1/n of the
     width and the loop drifts by a gap every cycle. A trailing margin makes
     every copy identical. */
  margin-right: 18px;
}

.ticker-item .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}

.ticker-item .name {
  color: var(--text);
}

.ticker-item .val {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
}

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

/*
 * The strip holds five tabs and about three fit, so it has always scrolled,
 * with nothing to say so. The wrapper exists to hang the chevrons off, since
 * they have to sit still while the strip moves underneath them.
 */
/* --- intro ----------------------------------------------------------------- */

.intro {
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  box-shadow: var(--shadow-sm);
  padding: 15px 18px;
  margin-bottom: 18px;
}

.intro.minimized {
  padding: 9px 18px;
}

.intro-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.intro-head strong {
  font-size: 14px;
  color: var(--text);
}

/* The small text toggle: the intro, a notice, a card's "what has to happen". */
.intro-toggle,
.notice-toggle,
.needs-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 2px 4px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color 0.2s ease;
}

.intro-toggle:hover,
.notice-toggle:hover,
.needs-toggle:hover {
  color: var(--text);
}

.intro-toggle-label {
  white-space: nowrap;
}

.intro-body {
  margin-top: 8px;
}

.intro-lead {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--text-dim);
  max-width: 78ch;
}

/*
 * A definition list, so the tab name and its description are marked up as what
 * they are. It also sidesteps the dash that would otherwise separate them:
 * the grid does that job, and one less piece of punctuation is one less thing
 * to be consistent about.
 */
.intro-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 5px 16px;
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
}

.intro-list dt {
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}

.intro-list dd {
  margin: 0;
  color: var(--text-dim);
  max-width: 72ch;
}

/* Two columns of nowrap labels don't fit beside their text on a phone, so the
   term sits above its description instead. */
@media (max-width: 700px) {
  .intro-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
  .intro-list dt {
    margin-top: 8px;
  }
  .intro-list dt:first-child {
    margin-top: 0;
  }
}

.search-input {
  width: 190px;
}

/*
 * The tab strip: the last row of the pinned block, under the ticker and the
 * bar, so it follows you down the page without needing to know anything's
 * height, and its copper rule closes the block. The horizontal padding
 * is what keeps the chevrons pinned to the strip rather than floating in the
 * margin at every breakpoint; the measure and the safe-area padding come from
 * .pinned .tabs-wrap in the header section.
 */
.tabs-wrap {
  margin: 0 auto;
  padding: 0 22px;
  background: var(--bg);
}

/*
 * An opaque strip above the tabs, covering the join.
 *
 * `top` is a measured value, and a measured value can be a fraction of a pixel
 * out, or briefly stale between a resize and the next frame. Any of that leaves
 * a hairline window onto the page scrolling underneath, and a window with
 * moving text in it is far more noticeable than its size suggests.
 *
 * So the arithmetic decides where the tabs park, and this decides whether
 * anything can show through. The ticker has the higher z-index, so where the
 * two overlap the ticker wins and this is never seen.
 */
.tabs-wrap::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 20px;
  background: var(--bg);
  pointer-events: none;
}

.tabs-rail {
  position: relative;
}

.tabs-cue {
  position: absolute;
  top: 0;
  bottom: 1px; /* clear of the strip's own rule */
  width: 34px;
  display: flex;
  align-items: center;
  color: var(--copper);
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.18s ease;
  /* Indicators only. A tap target here would overlap the tab beneath it and
     take presses meant for that tab, on the narrow screens where this appears
     at all. */
  pointer-events: none;
}

.tabs-cue.on {
  opacity: 1;
}

/* The fade is what makes a half-covered tab read as continuing rather than as
   ending in a chevron. */
.tabs-cue.left {
  left: 0;
  justify-content: flex-start;
  background: linear-gradient(to right, var(--bg) 40%, transparent);
}

.tabs-cue.right {
  right: 0;
  justify-content: flex-end;
  background: linear-gradient(to left, var(--bg) 40%, transparent);
}

.tabs {
  display: flex;
  gap: 4px;
  /* The copper rule the parent sites put under their topbar. It sits here
     rather than under the ticker so it closes off the whole sticky block. */
  border-bottom: 3px solid var(--trim);
  overflow-x: auto;
  scrollbar-width: none;
  /* The strip is scrolled programmatically when a tab is selected; without
     this the browser can fight that with its own focus scrolling. */
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
}

@media (prefers-reduced-motion: reduce) {
  .tabs {
    scroll-behavior: auto;
  }
}
.tabs::-webkit-scrollbar {
  display: none;
}

/*
 * These are the app's primary navigation, so they're sized like it. At 13px
 * they read as secondary text and gave a thin target to aim at; 16px semibold
 * with a deeper hit area fixes both.
 */
.tab {
  padding: 13px 18px;
  color: var(--text-dim);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  border-bottom: 3px solid transparent;
  /* Matches the strip's rule, so the active indicator sits on top of it rather
     than below it. */
  margin-bottom: -3px;
  white-space: nowrap;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  transition:
    color 0.2s ease,
    background-color 0.2s ease;
}
.tab:hover {
  color: var(--text);
  background: var(--bg-hover);
}
/* The indicator is the accent now, not --trim. The rule it overlays is --trim,
   and copper on copper marks nothing. */
.tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 700;
}

.tab .count {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-faint);
  margin-left: 6px;
}
/* --copper rather than --trim: same family, but 4.5:1 instead of 2.6:1, and
   this is a number people actually read. */
.tab.active .count {
  color: var(--copper);
}

/* --- setup ----------------------------------------------------------------- */

.setup {
  max-width: 580px;
  margin: 56px auto;
  text-align: center;
}

.setup h2 {
  font-size: clamp(26px, 5vw, 34px);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 12px;
  color: var(--text);
}

.setup p {
  color: var(--text-dim);
  margin: 0 0 26px;
  line-height: 1.6;
}

.field-row {
  display: flex;
  gap: 8px;
}

input[type='text'],
input[type='number'],
select {
  background-color: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 0 13px;
  /* height as well as min-height: Safari sizes a form control from its own
     font metrics and ignores min-height, which is why the year and week
     boxes stood shorter than the button beside them there while every
     other browser had them level. */
  height: var(--control-h);
  min-height: var(--control-h);
  box-sizing: border-box;
  outline: none;
  min-width: 0;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease;
}

/*
 * The select draws its own chevron.
 *
 * A native pop-up button is a different shape and size in every browser -
 * two stacked chevrons in Safari, a triangle in Chrome - and it brings its
 * own bezel, which is the other half of why the boxes would not match. The
 * arrow is two gradient wedges rather than an image so it takes currentColor
 * and follows the theme without a second asset.
 */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 17px) calc(50% - 1px),
    calc(100% - 13px) calc(50% - 1px);
  background-size: 5px 5px;
  background-repeat: no-repeat;
}

input[type='text']:focus,
input[type='number']:focus,
select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

input::placeholder {
  color: var(--text-faint);
}

/* --- buttons: sitekit pills ------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-height: var(--control-h);
  background: transparent;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 0 18px;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
  transition:
    transform 0.08s ease,
    box-shadow 0.15s ease,
    background-color var(--transition),
    color var(--transition),
    border-color var(--transition);
}
.btn:active {
  transform: scale(0.97);
}
.btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.btn.primary {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
  box-shadow: 0 3px 14px var(--shadow-sm);
}
.btn.primary:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn.small {
  min-height: var(--control-h-compact);
  padding: 0 13px;
  font-size: 12px;
}

/* --- controls -------------------------------------------------------------- */

.controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

/*
 * A run of buttons that belong together.
 *
 * The point is that the grouping is in the markup rather than in a spacer
 * element, so it still means something once the line wraps. Two groups with
 * space-between look identical to the old flex:1 spacer on a wide screen, and
 * unlike the spacer they degrade into two stacked rows instead of one row with
 * a hole punched through it.
 */
.btn-group {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.setup-actions {
  justify-content: space-between;
}

@media (max-width: 760px) {
  /*
   * Below this the two groups can't sit side by side, so put each on its own
   * line deliberately rather than leaving it to whether the text happens to
   * fit: that is the difference between a stable layout and one that
   * rearranges itself when a label changes length.
   */
  .setup-actions .btn-group {
    flex: 1 1 100%;
  }

  /*
   * Buttons are pills sized to their own text, so a wrapped row leaves every
   * right edge in a different place. Growing them from a common basis makes
   * each row divide evenly, and a lone button on the last line fills it
   * rather than trailing off.
   */
  .setup-actions .btn {
    flex: 1 1 140px;
  }
}

@media (max-width: 430px) {
  /*
   * One per row below this, because two genuinely do not fit.
   *
   * The widest label, "Clear cached data", is about 163px including its
   * padding and border. Half of a 390px screen less the page and card padding
   * is 160px. Buttons are `white-space: nowrap`, so the label cannot reflow to
   * absorb the shortfall -- it overflows the pill instead. 430px is the first
   * width where half a row (180px) clears the widest label with room to spare.
   */
  .setup-actions .btn {
    flex: 1 1 100%;
  }
}

.control-group {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-dim);
}

/* The parent site's "// section" voice, rule and all. */
.control-label,
.section-label {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--transition);
}

.section-label {
  display: flex;
  align-items: center;
  gap: 10px;
}
.section-label::before {
  content: '// ';
}
.section-label::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--rule);
}

/*
 * The segmented control: the view switches on the boards (All / QB / RB, by
 * importance or by name, and so on). Sized to this app's controls, which are
 * all one height so a wrapped row still lines up.
 *
 * The theme pill is NOT one of these. It is sitekit's, styled by the shell
 * layer, so it is the same pill as the one on Spreads and the hub rather than
 * something that merely resembles it. Hence :not(.theme-seg) throughout: these
 * rules would otherwise outrank the kit's, being later in the cascade at the
 * same specificity.
 */
.seg:not(.theme-seg) {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  min-height: var(--control-h);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 4px;
  transition:
    background-color var(--transition),
    border-color var(--transition);
}

/* The sliding highlight. Width and offset are set from JS because the labels
   differ in width; everything else, including the easing, is sitekit's. */
.seg:not(.theme-seg) .seg-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: 0;
  border-radius: var(--radius-pill);
  background: var(--text);
  box-shadow: 0 1px 4px var(--shadow-sm);
  pointer-events: none;
  z-index: 0;
  transition:
    transform 0.28s var(--ease),
    width 0.28s var(--ease),
    background-color var(--transition);
}

.seg:not(.theme-seg) button {
  position: relative;
  z-index: 1;
  min-height: var(--control-h-compact);
  padding: 0 13px;
  font-size: 12px;
  font-weight: 600;
  border-radius: var(--radius-pill);
  color: var(--text-faint);
  transition:
    color 0.2s ease,
    background-color 0.2s ease;
}
.seg:not(.theme-seg) button:hover {
  color: var(--text);
}
.seg:not(.theme-seg) button.active {
  background: var(--text);
  color: var(--bg);
  box-shadow: 0 1px 4px var(--shadow-sm);
}

/*
 * The one thing the theme pill does not take from the kit. The kit's thumb
 * sits at left: 3px and its theme.js translates by offsetLeft minus that
 * padding; this app's ThemeToggle translates by the raw offsetLeft, which
 * already includes the padding. Both land in the same place, but only if the
 * thumb starts at zero here. Change one and the other has to move with it.
 */
.theme-seg .seg-thumb {
  left: 0;
}

/* --- exposure board -------------------------------------------------------- */

.board {
  border: 1px solid var(--border);
  border-top: 3px solid var(--trim);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-raised);
  box-shadow: var(--shadow-md);
}

/* The header and the rows MUST share this grid, or the labels drift away from
   the columns they describe. Keep the two selectors together. */
.board-head,
.board-row {
  display: grid;
  grid-template-columns: 26px minmax(170px, 2.1fr) 66px 96px 52px 52px 60px 78px 116px;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  /* Centred by default; the player name opts out below. Variable-length names
     are much harder to scan centred than the short numeric columns. */
  text-align: center;
}

.board-head {
  height: 38px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Header cells are buttons, which shrink to their text. Without this they sit
   at one edge of the column and nothing lines up with the values below. */
.col-head {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  transition: color 0.2s ease;
}
.col-head.left {
  justify-content: flex-start;
}
.col-head:hover {
  color: var(--text);
}
.col-head.sorted {
  color: var(--trim);
}

.col-head .arrow {
  font-size: 7px;
  line-height: 1;
}

.board-row {
  height: 44px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.board-row:last-child {
  border-bottom: none;
}
.board-row:hover,
.board-row.expanded {
  background: var(--bg-hover) !important;
}

.chev {
  color: var(--text-faint);
  font-size: 10px;
  transition: transform 0.2s var(--ease);
}
.chev.open {
  transform: rotate(90deg);
}

.player-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  text-align: left;
}

.player-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* The name is the thing that gives way, never the chip or the status tag. */
  flex: 1;
  min-width: 0;
}

.player-meta {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-faint);
  white-space: nowrap;
  flex-shrink: 0;
}

.pos-chip {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 3px 7px;
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  flex-shrink: 0;
}

.num {
  font-family: var(--mono);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.num.muted {
  color: var(--text-faint);
}

.num.strong {
  font-weight: 700;
  color: var(--text);
}

.net {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.read {
  font-size: 11.5px;
  color: var(--text-dim);
}

/* Diverging bar: yours grows right of centre, against grows left.
   The whole bar and each filled segment are outlined so the split stays
   readable against any row tint. */
.stack {
  position: relative;
  height: 14px;
  background: var(--bg-sunken);
  border: 1px solid var(--bar-outline);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.stack .centre {
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--bar-outline);
  z-index: 3;
}

.stack .mine,
.stack .theirs {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
  transition: width 0.3s var(--ease);
  /* An inset ring rather than a border, so outlining a segment doesn't change
     the width it represents. */
  box-shadow: inset 0 0 0 1px var(--bar-outline);
}

.stack .mine {
  left: 50%;
  background: var(--pos);
  border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
}

.stack .theirs {
  right: 50%;
  background: var(--neg);
  border-radius: var(--radius-pill) 0 0 var(--radius-pill);
}

/* A zero-width segment must not show as a sliver of outline. */
.stack .mine[style*='width: 0%'],
.stack .theirs[style*='width: 0%'] {
  box-shadow: none;
}

.status-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: 1px;
}
/* On the field now: green, and blinking on a two-second cycle so a live
   row can be found at a glance. Held steady for anyone who has asked their
   device for less motion. */
.status-dot.live {
  background: var(--pos);
  box-shadow: 0 0 0 2px var(--pos-soft);
}
.status-dot.blink {
  animation: blink 2s step-end infinite;
}
@keyframes blink {
  50% {
    opacity: 0.15;
  }
}
@media (prefers-reduced-motion: reduce) {
  .status-dot.blink {
    animation: none;
  }
}

/* The smaller second figure in a points cell: the projection once actual
   points lead. */
.num .sub {
  color: var(--text-faint);
  font-weight: 500;
  font-size: 0.85em;
  margin-left: 2px;
}
/* Side by side the slash says "and the projection"; stacked, the second
   line says it on its own, so the phone rule below drops it. */
.num .sub::before {
  content: "/ ";
}
.status-dot.done {
  background: var(--border-strong);
}
.status-dot.upcoming {
  background: var(--pos);
}

/* --- expanded detail ------------------------------------------------------- */

.detail {
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
  padding: 15px 16px 17px 50px;
}

.detail-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
}

@media (max-width: 720px) {
  .detail-cols {
    grid-template-columns: minmax(0, 1fr);
  }
}

.detail h4 {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 14px 0 8px;
}
.detail h4:first-child {
  margin-top: 0;
}

.detail-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.detail-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12.5px;
  padding: 6px 11px;
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
  border-left: 3px solid var(--border-strong);
}
.detail-item.mine {
  border-left-color: var(--pos);
}
.detail-item.theirs {
  border-left-color: var(--neg);
}
.detail-item.bench {
  border-left-color: var(--border-strong);
  opacity: 0.65;
}

/* `min-width: 0` is what makes the ellipsis above actually work.
 *
 * A flex child defaults to `min-width: auto`, which is its *content's*
 * min-content width, and `white-space: nowrap` makes that the full length of
 * the string. So `flex: 1` can grow this but never shrink it: the text refuses
 * to truncate and the row gets wider than its container instead. `overflow:
 * hidden` doesn't help, because min-content is computed before anything is
 * clipped.
 *
 * On a phone that pushed the whole board wider than the screen. A long league
 * name, "Forever Rebuilding Dynasty League · Diverse Stripes", was enough to
 * do it on its own. See the ellipsis rule in tools/check-mobile.cjs. */
.detail-item .league {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.detail-item .slot {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-faint);
}

.empty-note {
  color: var(--text-faint);
  font-size: 12.5px;
  font-style: italic;
  margin: 0 0 10px;
}

/* --- matchup cards --------------------------------------------------------- */

.matchup-grid {
  display: grid;
  /* min() so the 330px floor can't outgrow the screen on a narrow phone: below
     that width the card simply takes the full column instead of overflowing. */
  grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
  gap: 14px;
}

/* sitekit card: 3px trim on top, 16px radius, lift on hover. */
.matchup {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition:
    transform 0.12s ease,
    box-shadow 0.2s ease,
    opacity 0.2s ease;
}
.matchup:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
}

/*
 * One colour on every card.
 *
 * The band used to carry three states: mint if projected to win, salmon if
 * projected to lose, copper if too close to call. It stopped working. Copper
 * and salmon sit 19° apart in hue, and once copper was lightened for
 * readability their lightness gap fell to 6%, so "close" and "losing" read as
 * the same warm smudge. A grid of cards then looks like an arbitrary mix rather
 * than three deliberate categories, which is worse than saying nothing.
 *
 * The percentage in the corner already says how a matchup is going, precisely,
 * and .winbar below shows it as a bar. The band is now just the card's edge.
 */
.matchup .band {
  height: 3px;
  background: var(--accent);
}

.matchup-body {
  padding: 14px 16px 16px;
}

.matchup-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
}

.matchup-title .name {
  font-weight: 700;
  font-size: 13.5px;
  color: var(--accent);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.matchup-title .prob {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  margin-left: auto;
  flex-shrink: 0;
}

.score-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 10px;
  margin-bottom: 11px;
}

.score-side {
  min-width: 0;
}
.score-side.right {
  text-align: right;
}

.score-side .team {
  font-size: 11.5px;
  color: var(--text-dim);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.score-side .pts {
  font-family: var(--mono);
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

.score-side .proj {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
}

.score-line .vs {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.needed {
  background: var(--bg-sunken);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  font-size: 12.5px;
  color: var(--text-dim);
  border: 1px solid transparent;
}

/* --- what has to happen: the combined and by-matchup views ------------------ */

/*
 * The combined table shares the board's grid discipline: head and rows on the
 * same five columns, two of which drop out on a phone.
 */
.need-head,
.need-row {
  display: grid;
  grid-template-columns: 26px minmax(170px, 2.4fr) 66px 74px 150px;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  text-align: center;
}

.need-head {
  height: 38px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.need-head .left {
  text-align: left;
}

.need-row {
  min-height: 44px;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.need-row:hover {
  background: var(--bg-hover);
}
.need-row.expanded {
  background: var(--bg-hover);
}

/* A player who is on your side in one league and against you in another. */
.need-row.conflicted {
  border-left: 3px solid var(--copper);
}

.need-cell {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 7px;
  flex-wrap: wrap;
}

.need-both {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--copper);
}

.need-threshold {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.need-threshold.bar {
  color: var(--pos);
}
.need-threshold.cap {
  color: var(--neg);
}

/* --- by matchup ------------------------------------------------------------- */

.needs-groups {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.need-group {
  border: 1px solid var(--border);
  border-top: 3px solid var(--trim);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.need-group.is-tanked {
  border-top-color: var(--neg);
}

.need-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px 0;
}
.need-group-head .name {
  font-weight: 700;
  font-size: 13.5px;
  color: var(--accent);
}

.need-sides {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0;
  padding: 12px 16px 16px;
}

.need-side + .need-side {
  border-left: 1px solid var(--border);
  padding-left: 16px;
}
.need-side {
  min-width: 0;
  padding-right: 16px;
}

.need-side h4 {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 700;
}

.need-side-score {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 400;
  color: var(--text-faint);
}

/* --- what has to happen ----------------------------------------------------- */

.needs {
  margin-top: 8px;
}


.needs-body {
  margin-top: 7px;
  padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}

.needs-lead {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
}

.needs-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-size: 12px;
}
.needs-row + .needs-row {
  border-top: 1px solid var(--rule);
}

.needs-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.needs-proj {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-faint);
}
/* A player on the field now: the dot, his points so far in full ink, then
   his projection for the time left in the quieter tone. */
.needs-live {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.needs-live strong {
  color: var(--text);
  font-weight: 700;
}

/* Two numbers where a normal league has one: the bar to appear, and the bar to
   win. They sit together so the gap between them is readable at a glance. */
.bb-thresholds {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
  flex: none;
}

/* Splits the lineup from everyone else. The two groups answer different
   questions, and running them together as one list was the whole problem. */
.bb-section {
  margin: 12px 0 2px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.bb-section:first-of-type {
  margin-top: 4px;
}

/* Dimmed, because these are not scoring for you as things stand. */
.bb-outside {
  opacity: 0.72;
}


.bb-blocked {
  font-size: 10.5px;
  color: var(--text-faint);
  font-style: italic;
  white-space: nowrap;
}

.bb-in {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pos);
}

/* In the lineup on projection, not yet in it on results. Quieter than the
   green "counting" so the settled and the expected read differently. */
.bb-soon {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}

.needs-target {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--accent);
  min-width: 42px;
  text-align: right;
}

.needed strong {
  font-family: var(--mono);
  color: var(--text);
  font-weight: 700;
}

.needed.good {
  background: var(--pos-soft);
  border-color: color-mix(in srgb, var(--pos) 28%, transparent);
}
.needed.bad {
  background: var(--neg-soft);
  border-color: color-mix(in srgb, var(--neg) 28%, transparent);
}

.winbar {
  height: 6px;
  background: var(--bg-sunken);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-top: 11px;
}
.winbar div {
  height: 100%;
  border-radius: var(--radius-pill);
  transition: width 0.4s var(--ease);
}

/* A matchup you've called as already decided. Still visible and still
   reversible, just visibly out of play. */
/* The whole card dims, band included, so it needs no colour of its own. The
   grey override here was only ever there to beat the three state rules. */
.matchup.is-settled {
  opacity: 0.55;
}
.matchup.is-settled:hover {
  opacity: 1;
}

/* A labelled switch, drawn as a pill the height of the controls beside it.
   The word stays put and the switch carries the state. */
.settle-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-sizing: border-box;
  min-height: var(--control-h);
  padding: 0 12px 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--bg-raised);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pos);
  transition:
    background-color var(--transition),
    border-color var(--transition);
}
.settle-toggle .switch {
  width: 30px;
  height: 17px;
}
.settle-toggle .switch span {
  width: 13px;
  height: 13px;
}
.settle-toggle .switch.on span {
  transform: translateX(13px);
}
/* At the foot of a matchup card the two switches sit in a row. */
.matchup .settle-toggle {
  margin-top: 12px;
}
.matchup .settle-toggle + .settle-toggle {
  margin-left: 8px;
}
.settle-toggle.off {
  color: var(--text-faint);
}
.settle-toggle:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

/* --- distribution chart ---------------------------------------------------- */

.dist-panel {
  margin-top: 22px;
  padding: 18px 20px 20px;
  border: 1px solid var(--border);
  border-top: 3px solid var(--trim);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  box-shadow: var(--shadow-md);
  max-width: 560px;
}

.dist {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 150px;
  margin: 16px 0 8px;
}

.dist-bar {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 5px;
  height: 100%;
}

.dist-bar .fill {
  width: 100%;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--accent-soft);
  min-height: 2px;
  transition: height 0.35s var(--ease);
}
.dist-bar.likeliest .fill {
  background: var(--accent);
}

.dist-bar .pct {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-dim);
}

.dist-axis {
  display: flex;
  gap: 5px;
}
.dist-axis div {
  flex: 1;
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-faint);
}

/* --- game view ------------------------------------------------------------- */

.game-group {
  border: 1px solid var(--border);
  border-top: 3px solid var(--trim);
  border-radius: var(--radius-lg);
  margin-bottom: 14px;
  overflow: hidden;
  background: var(--bg-raised);
  box-shadow: var(--shadow-md);
}

.game-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
}

.game-head .matchup-name {
  font-weight: 700;
  font-size: 13.5px;
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
}

.game-head .matchup-name .at {
  font-family: var(--mono);
  color: var(--text-faint);
  font-weight: 500;
  font-size: 11px;
  margin: 0 2px;
}

/*
 * Fixture and kickoff as one unit.
 *
 * Side by side while there is room. They used to be two separate children of
 * .game-head, which meant the kickoff was a flex item competing with the stats
 * for width -- and a flex item's floor is its longest word, so on a phone
 * "Sun, Sep 13 at 3:25 PM CDT" collapsed into a six-line column one word wide
 * and dragged the whole header down with it.
 */
.game-when {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Overrides the flex default of `auto`, which would refuse to shrink below
     the fixture plus the whole kickoff string and push the stats off instead. */
  min-width: 0;
}

/* A live game says so where the kickoff used to be, with the same
   blinking dot as the board; a finished one says Final and folds. */
.game-live,
.game-final {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.game-live {
  color: var(--pos);
  font-weight: 700;
}
.game-live .status-dot {
  margin-right: 1px;
}
.game-head.collapsible {
  cursor: pointer;
}
.game-head.collapsible:hover {
  background: var(--bg-hover);
}
.game-group.folded .game-head {
  border-bottom: none;
}
.game-fold {
  font-size: 8px;
  color: var(--text-faint);
  transition: transform 0.2s ease;
}
.game-fold.open {
  transform: rotate(90deg);
}

.game-head .kick {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}

@media (max-width: 700px) {
  /*
   * Three left-aligned rows: fixture, kickoff, then the two numbers.
   *
   * Stacking the kickoff under the fixture is most of the fix, but not all of
   * it. The numbers need about 200px ("No preference" is a wide string at
   * 15px), and the fixture with its two logos needs about 122px. A 360px
   * screen has 300px of usable width, so those two cannot share a row either
   * -- keeping them side by side just moves the squeeze from the kickoff onto
   * the fixture.
   *
   * Given a full row each, every one of them fits on a single line, and the
   * header comes out shorter than the squeezed version despite using more
   * rows.
   */
  .game-head {
    flex-wrap: wrap;
  }

  .game-when {
    flex: 1 1 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
  }

  /* Undo the desktop `margin-left: auto`, which would strand the numbers on
     the right of their own row, away from everything they belong to. */
  .game-head .game-stats {
    margin-left: 0;
  }

  /* Flush left with the rows above, rather than each number right-aligned
     inside its own invisible column. */
  .game-head .game-stat {
    align-items: flex-start;
    text-align: left;
  }
}

/* The readings for a game, sitting together on the right. */
.game-stats {
  margin-left: auto;
  display: flex;
  gap: 18px;
  text-align: right;
  /* Three of these now, and "NE blowout" is a wide value. Wrapping is the
     difference between a second line and a header that overflows the card. */
  flex-wrap: wrap;
}

.game-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.25;
}

.game-stat .k {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.game-stat .v {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* "Root for MIN" is words, not a figure, so it gets the body face. */
.game-stat.wide {
  min-width: 92px;
}

.game-stat.wide .v {
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: -0.01em;
}

/* The two sides of the game, side by side. */
.game-sides {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.game-sides .game-side + .game-side {
  border-left: 1px solid var(--border);
}

@media (max-width: 640px) {
  .game-sides {
    grid-template-columns: minmax(0, 1fr);
  }
  .game-sides .game-side + .game-side {
    border-left: none;
    border-top: 1px solid var(--border);
  }
}

.game-side {
  padding: 12px 15px 14px;
  min-width: 0;
}

.game-side-head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding-bottom: 8px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.03em;
}

.game-side-head .team-net {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.game-player {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-size: 12.5px;
  min-width: 0;
}

.game-player .nm {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Both numbers, always: a player who is 2 for you and 2 against nets to zero
   but is not irrelevant, and hiding that behind a single net figure loses it. */
.game-player .split {
  display: flex;
  gap: 7px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.game-player .split .for {
  color: var(--pos);
}
.game-player .split .against {
  color: var(--neg);
}
.game-player .split .zero {
  color: var(--text-faint);
  font-weight: 500;
  opacity: 0.55;
}

/* Logos come from a CDN and can fail; TeamLogo removes itself when they do, so
   nothing here needs a fallback box. */
.team-logo {
  flex-shrink: 0;
  object-fit: contain;
  vertical-align: middle;
}

/* Most NFL logos are dark-on-transparent, which disappears on the granite
   background. A soft light disc behind them keeps them readable. */
html[data-theme='dark'] .team-logo {
  background: rgba(235, 242, 250, 0.92);
  border-radius: 50%;
  padding: 1px;
}

/* --- settings / leagues ---------------------------------------------------- */

.league-table {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-raised);
  box-shadow: var(--shadow-md);
}

/*
 * League and team share the flexible width rather than the league taking all
 * of it.
 *
 * The team column was 90px, which was plenty back when it rendered a `0-0`
 * record. It now renders the actual team name, and 90px of "Forever Rebuilding
 * Dynasty League"'s neighbour is not a name, it's a fragment. Meanwhile the
 * league column held a whole 1fr and sat half empty.
 *
 * Teams is a two-digit number under a five-letter heading, so it doesn't need
 * 90px either; that goes back into the two columns holding real text.
 */
.league-row {
  display: grid;
  /* The first column holds a 34px switch and, in the head, the word over it. */
  grid-template-columns: 64px minmax(0, 1.5fr) 62px 112px minmax(0, 1fr);
  grid-template-areas: 'toggle name teams stakes team';
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
}

.league-toggle {
  grid-area: toggle;
}
.league-name {
  grid-area: name;
  display: flex;
  align-items: baseline;
  min-width: 0;
}

.league-name-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.league-teams {
  grid-area: teams;
  color: var(--text-faint);
  font-size: 12.5px;
}
.league-stakes {
  grid-area: stakes;
  display: flex;
  align-items: center;
  gap: 8px;
}
.league-team {
  grid-area: team;
  display: flex;
  align-items: baseline;
  gap: 6px;
  color: var(--text-dim);
  font-size: 12.5px;
  min-width: 0;
}

/* Sits inside the league name, which ellipsises, so it must not be squeezed. */
.league-format {
  margin-left: 7px;
  flex: none;
  font-size: 9px;
  padding: 1px 5px;
}

/* The part that gives way. The record is short and fixed, so it stays whole. */
.league-team-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.league-record {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-faint);
  flex: none;
  white-space: nowrap;
}

/* The column headings carry the labelling on desktop. */
.league-stakes-label {
  display: none;
}
.league-row:last-child {
  border-bottom: none;
}
.league-row.head {
  background: var(--bg-sunken);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.league-row.hidden-league {
  opacity: 0.45;
}

.weight-input {
  width: 68px;
  padding: 5px 9px;
  text-align: right;
  font-family: var(--mono);
}

.switch {
  width: 34px;
  height: 19px;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
  position: relative;
  transition: background-color 0.2s var(--ease);
  flex-shrink: 0;
}
.switch.on {
  background: var(--pos);
}
.switch span {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s var(--ease);
}
.switch.on span {
  transform: translateX(15px);
}

.section {
  margin-bottom: 32px;
}

.section h3 {
  font-size: 15px;
  font-weight: 700;
  margin: 0 0 4px;
  color: var(--accent);
}

/* The quiet paragraph under a heading or over a list, everywhere. */
.hint {
  color: var(--text-faint);
  font-size: 12.5px;
  margin: 0 0 12px;
  max-width: 620px;
  line-height: 1.6;
}

.section code {
  font-family: var(--mono);
  font-size: 11.5px;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

/* A removable pill, e.g. a saved Sleeper account. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 13px;
  border-radius: var(--radius-pill);
  font-family: var(--mono);
  font-size: 11.5px;
  background: var(--accent-soft);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  color: var(--text);
}


/* --- misc ------------------------------------------------------------------ */

.notice {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 15px 18px;
  margin-bottom: 16px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.6;
  background: var(--bg-raised);
  box-shadow: var(--shadow-sm);
}

/* Folded away, it's one line: still visibly a warning, no longer a paragraph. */
.notice.minimized {
  padding: 9px 18px;
}

/* Title and body as one paragraph, with the close control out of the flow so
   it can't push the text into an awkward wrap at some widths and not others. */
.notice.inline {
  position: relative;
  padding-right: 46px;
}

/* The action button sits inside this run of text, so the line box has to give
   it room rather than letting it overlap the line above. */
.notice-inline {
  display: block;
  line-height: 2;
}

.notice-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1;
  color: var(--text-faint);
  transition:
    color 0.2s ease,
    background-color 0.2s ease;
}

.notice-close:hover {
  color: var(--text);
  background: var(--bg-hover);
}

.notice-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}


/* The chevron alone is ambiguous on a warning: it could mean "expand for
   more" or "this is a list". The word removes the doubt where there's room. */
.notice-toggle-label {
  display: inline;
}

.notice-body {
  margin-top: 6px;
}

.notice-list {
  margin: 0;
  padding-left: 18px;
}

.notice-actions {
  margin-top: 10px;
}

/* Wraps rather than scrolls: the useful part of a component stack is usually
   the first few lines, and a horizontal scrollbar hides them on a phone. */
.error-detail {
  margin: 10px 0 0;
  padding: 10px 12px;
  max-height: 240px;
  overflow: auto;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-word;
}

.notice.warn {
  border-color: color-mix(in srgb, var(--neg) 40%, transparent);
  background: var(--neg-soft);
}

.notice.settled-note {
  border-color: color-mix(in srgb, var(--copper) 45%, transparent);
  background: var(--copper-soft);
}

/* A newer build is live. Copper: worth noticing, nothing is wrong. */
.notice.update-note {
  border-color: color-mix(in srgb, var(--copper) 55%, transparent);
  border-left: 3px solid var(--copper);
  background: var(--copper-soft);
}

/* What the account API said when you came back from Google (1.6.0). */
.notice.account-note {
  margin-bottom: 14px;
}
.notice.ok {
  border-color: color-mix(in srgb, var(--pos) 45%, transparent);
  background: var(--pos-soft);
}

/* Sign in with Google: Google's own button colours (near-black on the dark
   theme, white on the light) so it reads as the button people know; the
   shape and type are this app's .btn. */
.btn.google {
  gap: 10px;
  background: #131314;
  border-color: #8e918f;
  color: #e3e3e3;
}
.btn.google:hover {
  background: #1f1f20;
  border-color: #8e918f;
  color: #e3e3e3;
}
html[data-theme='light'] .btn.google {
  background: #fff;
  border-color: #747775;
  color: #1f1f1f;
}
html[data-theme='light'] .btn.google:hover {
  background: #f3f3f3;
}
.gsi-logo {
  flex: none;
  width: 18px;
  height: 18px;
}

/* Tanking inverts the colours in a league, so it says so plainly. */
.notice.tank-note {
  border-color: color-mix(in srgb, var(--neg) 45%, transparent);
  border-left: 3px solid var(--neg);
}

/* The per-matchup tank switch, sitting under the counting/ignored one. */
.tank-toggle.on {
  color: var(--neg);
}
.tank-toggle.on .switch {
  background: var(--neg);
}
.matchup.is-tanked {
  border-color: color-mix(in srgb, var(--neg) 40%, var(--border));
}

/* --- hover explainer -------------------------------------------------------- */

/*
 * A number that states a result should be able to explain itself. Focusable as
 * well as hoverable, so it isn't mouse-only.
 */
.explain {
  position: relative;
  border-bottom: 1px dotted var(--text-faint);
  cursor: help;
  outline: none;
}

.explain-pop {
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  z-index: 60;
  width: min(430px, 78vw);
  padding: 13px 15px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--bg-raised);
  box-shadow: var(--shadow-lift);
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.55;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition:
    opacity 0.18s var(--ease),
    transform 0.18s var(--ease),
    visibility 0.18s;
}

.explain:hover .explain-pop,
.explain:focus .explain-pop,
.explain:focus-within .explain-pop {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.explain-pop strong {
  display: block;
  margin: 11px 0 3px;
  color: var(--accent);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.explain-pop strong:first-child {
  margin-top: 0;
}

.notice strong {
  color: var(--text);
}

/* An abbreviated injury flag: "(Q)", not "Questionable". Full word in the
   tooltip. Never shrinks: it's two characters and the name absorbs the loss. */
.status-tag {
  flex: none;
  color: var(--neg);
  background: var(--neg-soft);
  border-color: color-mix(in srgb, var(--neg) 40%, transparent);
}

.tag {
  font-family: var(--mono);
  font-size: 10px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--copper-soft);
  border: 1px solid color-mix(in srgb, var(--copper) 40%, transparent);
  color: var(--copper);
}

/* The breakpoint list under a player's detail. */
.lev-panel {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--border);
}

.lev-at {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--accent);
  min-width: 42px;
}

/* The one week a league can end on. Same pill, turned all the way up - a
   solid copper fill instead of a wash, because this tag marks the single
   card the whole season was for. */
.tag-champ {
  background: var(--copper);
  border-color: var(--copper);
  color: var(--bg);
  font-weight: 700;
}

/* A fixed-size stage for content that swaps with a spinner: the widest
   occupant defines the box, so the swap can never move its neighbours. */
.swap-box {
  position: relative;
  display: inline-block;
}

.swap-spin {
  position: absolute;
  inset: 0;
  margin: auto;
}

/* Refresh ticks are digits changing in place; tabular figures keep every
   tick the same width so the footer line cannot breathe in and out. */
.footer-status {
  font-variant-numeric: tabular-nums;
}

.spinner {
  display: inline-block;
  width: 13px;
  height: 13px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: -2px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.loading-state {
  text-align: center;
  padding: 80px 20px;
  color: var(--text-dim);
}

/* --- footer: sitekit's, through the shell layer ----------------------------
   The footer element itself (the space above it, the copper rule, centred)
   is the kit's rule, and so is the mark inside it. What follows is only what
   this app adds. */

.footer .footer-status {
  margin-top: 14px;
  font-size: 12px;
  color: var(--text-faint);
}

/* The build stamp, quiet enough to ignore until you need it. */
.build-tag {
  font-family: var(--mono);
  font-size: 10.5px;
  opacity: 0.8;
}

/* The parent site's copyright line, to the pixel: 12px, dimmed, not mono. */
/* The page heading, present for screen readers and hidden from everyone
   else: the bar shows the wordmark instead. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  border: 0;
}
/* The monogram in the footer is the way back to the hub. */
.foot-mark-link {
  display: inline-flex;
  color: inherit;
  border-radius: 50%;
}
.footer .fine {
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-dim);
  opacity: 0.85;
  transition: color var(--transition);
}

.coffee {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  background: var(--copper);
  border: 1px solid var(--copper);
  color: var(--bg);
  font-size: 12.5px;
  font-weight: 600;
  box-shadow: 0 3px 14px var(--shadow-sm);
  transition:
    transform 0.08s ease,
    box-shadow 0.15s ease;
}
.coffee:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-lift);
  text-decoration: none;
  color: var(--bg);
}
.coffee:active {
  transform: scale(0.97);
}

@media (max-width: 700px) {
  /* Seven visible columns once .hide-sm cells drop out: header and rows must
     both land on seven, or the labels wrap onto their own line. */
  /* Five columns on a phone: Yours and Vs drop out, and the width they were
     using goes to the name. Net and Importance between them already carry the
     same information: which way he swings you, and how much rides on him. */
  .board-head,
  .board-row {
    grid-template-columns: 20px minmax(140px, 2fr) 52px 50px 58px;
    gap: 7px;
    padding: 0 9px;
  }
  .need-head,
  .need-row {
    grid-template-columns: 20px minmax(130px, 2fr) 52px 120px;
    gap: 7px;
    padding: 0 9px;
  }
  .need-sides {
    grid-template-columns: minmax(0, 1fr);
  }

  /*
   * League stakes stops being a table on a phone.
   *
   * Five columns need 414px of width; a 360px handset offers 340. The name
   * column was the one set to 1fr, so it absorbed the whole shortfall and
   * collapsed to nothing, leaving a row of numbers with no way to tell which
   * league they belonged to, which is worse than overflowing.
   *
   * Stacked, the name gets the full width and the toggle and the stakes box
   * stay where a thumb expects them.
   */
  .league-row {
    grid-template-columns: 34px minmax(0, 1fr) auto;
    grid-template-areas:
      'toggle name  stakes'
      'toggle teams stakes'
      'toggle team  stakes';
    row-gap: 2px;
    column-gap: 12px;
    padding: 12px 14px;
  }
  /* Column headings mean nothing once the row is stacked. */
  .league-row.head {
    display: none;
  }
  .league-name {
    font-weight: 600;
  }
  .league-teams::after {
    content: ' teams';
  }
  .league-team:not(:empty)::before {
    content: 'you: ';
  }
  .league-stakes {
    flex-direction: column;
    gap: 3px;
    align-items: center;
  }
  .league-stakes-label {
    display: block;
    font-family: var(--mono);
    font-size: 9px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
  }
  .weight-input {
    width: 62px;
  }
  .need-side {
    padding-right: 0;
  }
  .need-side + .need-side {
    border-left: none;
    border-top: 1px solid var(--border);
    padding-left: 0;
    padding-top: 12px;
    margin-top: 12px;
  }
  .hide-sm {
    display: none;
  }
  .detail {
    padding-left: 16px;
  }
  .app {
    padding:
      0
      max(14px, env(safe-area-inset-right))
      calc(60px + env(safe-area-inset-bottom))
      max(14px, env(safe-area-inset-left));
  }

  /*
   * 16px is the threshold below which iOS Safari zooms the page when a field
   * takes focus, and it never zooms back out. Every input goes to 16px here;
   * the desktop sizes above are untouched.
   */
  input,
  select,
  textarea {
    font-size: 16px;
  }

  /*
   * One height for every control, so a wrapped header row lines up instead of
   * stair-stepping. 44px is the platform thumb-target guideline.
   *
   * The selects were left to whatever their text and padding came to, which was
   * close to 44 but not equal, and that is exactly the sort of near-miss that
   * reads as sloppy rather than as a bug.
   */
  :root {
    --control-h: 44px;
    --control-h-compact: 36px;
  }
  .tab {
    min-height: var(--control-h);
  }
  /* A segment's padding and border sit on top of the pills inside it, so the
     padding gives a little here for the frame to land on 44 as well. Not the
     theme pill: that one is sitekit's at every width. */
  .seg:not(.theme-seg) {
    padding: 3px;
  }
  .seg:not(.theme-seg) .seg-thumb {
    top: 3px;
    bottom: 3px;
  }
  /*
   * "Search player or team" didn't fit in 190px, so it was cut mid-word.
   *
   * The field goes full width, which is the actual fix: 190px is a desktop
   * measurement sitting in a row that wraps here anyway. The placeholder also
   * drops to 13.5px, and only the placeholder: the input itself has to stay at
   * 16px or iOS zooms in on focus and never zooms back out. iOS reads the
   * field's font-size for that decision and ignores ::placeholder, so the two
   * rules don't fight.
   */
  .search-input {
    width: 100%;
    flex: 1 1 100%;
  }
  .search-input::placeholder {
    font-size: 13.5px;
  }
  /* The pinned rows keep to .app's gutter at this width, so the bar, the
     strip and the ticker line up with the boards beneath them. They used to
     bleed with negative margins from inside .app; the block is full bleed
     now, so they pad instead. The safe-area top is the bar's (see .pinned
     .topbar), not the ticker's. */
  .pinned .topbar .inner,
  .pinned .tabs-wrap,
  .pinned .ticker {
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }
  .ticker {
    padding-top: 10px;
    padding-bottom: 10px;
  }
  /*
   * No player strip on a phone.
   *
   * It was never usable at this width: three or four names visible at a time,
   * moving, above the board those same players are listed in with more detail
   * and in a stable order. The summary is the part that earns its space on a
   * small screen, so that is the part that stays.
   */
  .ticker-marquee,
  .ticker-empty {
    display: none;
  }
  .ticker-summary {
    padding-right: 0;
    border-right: none;
  }
}

/*
 * Phones proper.
 *
 * The seven-column layout above needs 420px of content to reach its minimum,
 * but a 390px handset only offers 362px once .app padding is off, so the board
 * overflowed. These widths bring the minimum to 330px, which clears a 375px
 * iPhone SE with room to spare. tools/check-mobile.cjs does this arithmetic on
 * every run so the columns can't drift back over budget.
 *
 * overflow-x on .board is the backstop: on a screen narrower than anything
 * targeted here the board scrolls sideways instead of being clipped.
 */
@media (max-width: 430px) {
  .board-head,
  .board-row {
    /* Net and Importance hold "+3" and "3". They were sized for the headings
       rather than the numbers; 8px off each is 16px straight to the name. */
    grid-template-columns: 16px minmax(120px, 2fr) 42px 36px 38px;
    gap: 5px;
    padding: 0 7px;
  }
  /* A backstop, not a layout. If this ever actually scrolls, something inside
     is wider than the screen and the fix belongs there, scrolling just hides
     it, and hides the left edge of every row along with it. */
  .board {
    overflow-x: auto;
  }
  /* Everything in a player cell except the name is fixed width, so each pixel
     trimmed here is a pixel the name gets back. Roughly 13 of them. */
  .player-cell .pos-chip {
    padding: 3px 5px;
    letter-spacing: 0.04em;
  }
  .player-cell .status-tag {
    padding: 2px 5px;
  }
  .app {
    padding:
      0
      max(10px, env(safe-area-inset-right))
      calc(60px + env(safe-area-inset-bottom))
      max(10px, env(safe-area-inset-left));
  }
  .pinned .topbar .inner,
  .pinned .tabs-wrap,
  .pinned .ticker {
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-right));
  }
  .num {
    font-size: 11.5px;
  }
  .net {
    font-size: 12px;
  }
  .col-head {
    font-size: 9px;
    letter-spacing: 0.04em;
  }
  .player-cell {
    gap: 4px;
  }
  /* Seven position pills in one row is the tightest control here; trimming the
     padding keeps the group on a single line rather than clipping its ends.
     Not the theme pill, which has two words and is sitekit's at every width;
     this was flattening it to 24px against everyone else's 32. */
  .seg:not(.theme-seg) button {
    padding: 0 9px;
  }
  /* The needs column holds up to two thresholds and wraps rather than clip. */
  .need-head,
  .need-row {
    grid-template-columns: 16px minmax(96px, 2fr) 42px 106px;
    gap: 5px;
    padding: 0 7px;
  }
  .league-row {
    padding: 11px 12px;
    column-gap: 10px;
  }
}

/* --- parlay ---------------------------------------------------------------- */

/*
 * Two columns: the lists on the left, the slip on the right and pinned, so
 * ticking a leg fifteen groups down still shows what it did. Below 900px the
 * slip comes first and the lists follow, because on a phone the thing you
 * are building is more useful at the top than a list you have to scroll back
 * up from.
 */
.parlay-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 18px;
  align-items: start;
}

@media (max-width: 900px) {
  .parlay-layout {
    grid-template-columns: minmax(0, 1fr);
  }
  .parlay-layout .slip {
    order: -1;
    position: static;
  }
}

.parlay-note {
  color: var(--text-faint);
  font-size: 12.5px;
  line-height: 1.6;
  max-width: 720px;
  margin: -6px 0 16px;
}

.parlay-count {
  /* Two digits plus the browser's spinner. 54px clipped the second digit. */
  width: 72px;
  box-sizing: border-box;
  padding: 4px 6px;
  text-align: center;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.parlay-lists {
  min-width: 0;
}

.parlay-list {
  margin-bottom: 26px;
}

.parlay-list h3 {
  font-size: 15px;
  font-weight: 700;
  margin: 0 0 4px;
  color: var(--accent);
}

/* One player, or one game, and the legs on offer for it. */
.leg-group {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  margin-bottom: 10px;
  overflow: hidden;
}

.leg-group-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 9px 12px;
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.leg-group-head .at,
.game-card-toggle .at {
  font-family: var(--mono);
  color: var(--text-faint);
  font-size: 11px;
  margin: 0 4px;
}

.leg-who {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.leg-kick {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--text-faint);
}

/* The reason a game is on the list, under its header. */
.leg-why {
  margin: 0;
  padding: 6px 12px 0;
  color: var(--text-faint);
  font-size: 12.5px;
  line-height: 1.5;
}

.leg-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  font-size: 13px;
  cursor: pointer;
  border-top: 1px solid var(--border);
  transition: background-color 0.15s ease;
}
.leg-group .leg-row:first-of-type {
  border-top: none;
}
.leg-row:hover {
  background: var(--bg-hover);
}
.leg-row.on {
  background: var(--accent-soft);
}
.leg-row input {
  accent-color: var(--accent);
  margin: 0;
}

.leg-text {
  flex: 1;
  min-width: 0;
}

/* The price, or the honest absence of one. */
.leg-num {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  white-space: nowrap;
}
.leg-num.priced {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* --- the slip --- */

.slip {
  position: sticky;
  top: var(--pinned-h, 200px);
  border: 1px solid var(--border);
  border-top: 3px solid var(--trim);
  border-radius: var(--radius-lg);
  background: var(--bg-raised);
  box-shadow: var(--shadow-md);
  padding: 14px 16px 16px;
}

.slip-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.slip-head .section-label {
  flex: 1;
}

.slip-mode {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  white-space: nowrap;
}

.slip-empty {
  color: var(--text-faint);
  font-size: 12.5px;
  margin: 4px 0 12px;
  line-height: 1.5;
}

.slip-legs {
  display: flex;
  flex-direction: column;
}

.slip-leg {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}

.slip-leg-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.slip-leg-main .leg-why {
  padding: 0;
  font-size: 11px;
}

/* flip, DK, remove: small, quiet, and the same shape as each other. */
.slip-tool {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  color: var(--text-dim);
  background: transparent;
  text-decoration: none;
  line-height: 1.4;
  white-space: nowrap;
}
.slip-tool:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.slip-remove {
  font-size: 13px;
  padding: 0 7px;
}

.slip-odds {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 0 4px;
}
.slip-odds strong {
  font-family: var(--mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.slip-odds-note {
  color: var(--text-faint);
  font-size: 12px;
  line-height: 1.5;
}

.slip-warn {
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--copper-soft);
  border: 1px solid color-mix(in srgb, var(--copper) 40%, transparent);
  color: var(--text);
  font-size: 12px;
  line-height: 1.5;
}

.slip-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}
.slip-actions .btn {
  flex: 1 1 auto;
  padding: 8px 12px;
  /* One of these is a link, not a button. A button centres its label on
     its own; an anchor starts it at the left, so the row is told outright. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.slip-note {
  color: var(--text-faint);
  font-size: 12.5px;
  line-height: 1.5;
  margin: 12px 0 0;
}


/*
 * Game legs are a different kind of bet from a player leg, so they carry a
 * small copper tag and a copper edge wherever they appear: the Game lines
 * list, its rows, and the slip.
 */
.leg-kind {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 1px 6px;
  margin-right: 6px;
  border-radius: var(--radius-pill);
  background: var(--copper-soft);
  border: 1px solid color-mix(in srgb, var(--copper) 40%, transparent);
  color: var(--copper);
  vertical-align: 1px;
}
.parlay-list h3 .leg-kind {
  vertical-align: 2px;
}
.leg-group.game {
  border-left: 3px solid var(--copper);
}
.leg-row.game.on {
  background: var(--copper-soft);
}
.slip-leg.game {
  border-left: 3px solid var(--copper);
  padding-left: 8px;
}

/*
 * The way back to the slip on a phone: pinned to the bottom once the slip
 * has scrolled off the top. Hidden on a desktop, where the slip is sticky
 * and there is nothing to come back from.
 */
.slip {
  scroll-margin-top: var(--pinned-h, 200px);
}

.slip-return {
  position: fixed;
  left: 50%;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 30;
  padding: 11px 20px;
  border-radius: var(--radius-pill);
  background: var(--text);
  color: var(--bg);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  box-shadow: var(--shadow-lift);
  border: 1px solid var(--border-strong);
}
.slip-return:active {
  transform: translateX(-50%) scale(0.97);
}

@media (min-width: 901px) {
  .slip-return {
    display: none;
  }
}

/*
 * The fire and the thumbs down beside a name. Sized to the text it follows
 * and kept from wrapping away from it; the title carries the explanation.
 */
.flag {
  display: inline-block;
  margin-left: 4px;
  font-size: 0.95em;
  line-height: 1;
  vertical-align: -1px;
  white-space: nowrap;
}
.ticker .flag {
  margin-left: 3px;
  font-size: 11px;
}

/*
 * The exposure line on a builder row: "net -2 (+1 / -3)". One face, one
 * size, baseline-aligned, so the label, the net and the two numbers it came
 * from read as a single phrase. The net keeps its colour; the split keeps
 * its green and red, dimmed to a hint when a side is zero.
 */
.leg-exposure {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  line-height: 1.3;
}
.leg-exposure .k {
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.leg-exposure-split {
  color: var(--text-faint);
  font-weight: 500;
}
.leg-exposure .for {
  color: var(--pos);
  font-weight: 700;
}
.leg-exposure .against {
  color: var(--neg);
  font-weight: 700;
}
.leg-exposure .zero {
  color: var(--text-faint);
  font-weight: 500;
}

/* The per-row flip in the lists: same pill as the slip's tools, kept quiet
   until the row is hovered so forty rows do not read as forty buttons. */
.leg-flip {
  opacity: 0.55;
}
.leg-row:hover .leg-flip,
.leg-flip:focus-visible {
  opacity: 1;
}
@media (hover: none) {
  .leg-flip {
    opacity: 1;
  }
}

/* What just happened on the QuickPickBot button, said plainly under it. */
.slip-handoff {
  margin: 10px 0 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}
.slip-handoff.ok {
  background: var(--pos-soft);
  border: 1px solid color-mix(in srgb, var(--pos) 40%, transparent);
}
.slip-handoff.warn {
  background: var(--copper-soft);
  border: 1px solid color-mix(in srgb, var(--copper) 40%, transparent);
}
.slip-handoff a {
  color: var(--accent);
  font-weight: 600;
}

/*
 * A game's read: the one-word shape as a small pill, the sentence after it,
 * and the two tiers of legs under it. Each team in the header carries its
 * net, so the sentence and the numbers sit together.
 */
.leg-side-net {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.leg-read {
  margin: 0;
  padding: 8px 12px 2px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim);
}

.leg-shape {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  color: var(--text);
  vertical-align: 1px;
}
.shape-rout {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
}
.shape-shootout {
  background: var(--pos-soft);
  border-color: var(--pos);
  color: var(--pos);
}
.shape-slog {
  background: var(--neg-soft);
  border-color: var(--neg);
  color: var(--neg);
}
.shape-only {
  background: var(--copper-soft);
  border-color: color-mix(in srgb, var(--copper) 40%, transparent);
  color: var(--copper);
}

.leg-tier {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 8px 12px 2px;
}
.leg-tier + .leg-row {
  border-top: none;
}

/* The counts line sits directly above a player's legs; give the first leg
   room so the sentence does not read as the row's label. */
.leg-why + .leg-row {
  margin-top: 6px;
}

/*
 * The By game view: one collapsed card per game. The header is a button so
 * the whole row opens it; the chevron says which way it is.
 */
.game-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
  margin-bottom: 10px;
  overflow: hidden;
}
.game-card.open {
  border-color: var(--border-strong);
}

.game-card-head {
  display: flex;
  align-items: center;
  background: var(--bg-sunken);
}
.game-card.open .game-card-head {
  border-bottom: 1px solid var(--border);
}
.game-card-head .game-build {
  margin-right: 10px;
  flex-shrink: 0;
}

.game-card-toggle {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  text-align: left;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--text);
  background: transparent;
  transition: background-color 0.15s ease;
}
.game-card-toggle:hover {
  background: var(--bg-hover);
}

.game-card-chev {
  font-size: 9px;
  color: var(--text-faint);
  transition: transform 0.2s ease;
}
.game-card-chev.open {
  transform: rotate(90deg);
}

.game-card-count {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

.game-card-body {
  padding: 10px 10px 4px;
}
.game-card-body .leg-group {
  margin-bottom: 8px;
}

/* The one-tap same-game parlay, in a game header. */
.game-build {
  white-space: nowrap;
}
.game-build:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.slip-head .slip-tool:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/*
 * The builder's controls as fields: label over control, wrapping as a row.
 * A label that sits above its control cannot be separated from it by a
 * line break, which is what made the old single row read as a jumble on a
 * phone. The refresh field pins to the right edge where there is room.
 */
.parlay-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 18px;
  padding: 12px 14px;
  margin-bottom: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
/* A control keeps its own width under a wider label rather than stretching
   to it, which left a pill twice the size of its one button. */
.field .seg,
.field .parlay-count {
  align-self: flex-start;
}
/* The number boxes stand as tall as the pills beside them, so the small
   labels above a row of fields sit on one line. */
.field .parlay-count {
  height: var(--control-h);
}

.field-label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.field .btn.small {
  align-self: flex-start;
}

/* Two fields that travel together: Lines and its refresh. As one flex item
   they wrap as a unit, so a crowded row moves both to the next line, flush
   left, rather than stranding the refresh on a line of its own. */
.field-pair {
  display: flex;
  align-items: flex-end;
  gap: 10px 18px;
  flex-wrap: wrap;
}

@media (max-width: 700px) {
  .parlay-controls {
    gap: 10px 14px;
    padding: 10px 12px;
  }
}

/*
 * On a phone the points column is 42px wide, so "6.5 / 21.0" cannot sit
 * on one line. Rather than let it wrap where it happens to, the projection
 * becomes a deliberate second line under the points, smaller and quieter,
 * and the row keeps its height.
 */
@media (max-width: 430px) {
  .board-row .num .sub,
  .need-row .num .sub {
    display: block;
    margin-left: 0;
    font-size: 9.5px;
    line-height: 1.1;
  }
  .board-row .num .sub::before,
  .need-row .num .sub::before {
    content: none;
  }
  /* The two figures centre on each other; the dot stays outside, to the
     left, so it does not pull the pair off centre. */
  .board-row .num-stack,
  .need-row .num-stack {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    vertical-align: middle;
  }
}

/* The bench switch in the board's control row, sized to sit level with the
   filter pills beside it. */

/* A lone action drawn inside a segmented pill: the pill's size and type,
   the button's colour, since it is something to press rather than a
   choice that is on or off. */
.seg button.seg-action {
  color: var(--text);
}
.seg button.seg-action:disabled {
  opacity: 0.6;
  cursor: wait;
}

/* A leg from the live book, and a live game's header in the builder. The
   tag is the game tag in green; the header badge reuses the By-game one. */
.leg-kind.leg-live {
  background: var(--pos-soft);
  border-color: var(--pos);
  color: var(--pos);
}
.leg-kick-live {
  margin-left: auto;
}

/* ---------------------------------------------------------------------------
 * Small rules that used to be inline styles. Kept here so the stylesheet
 * checks can see them, and so the same one-property look is spelled once.
 * ------------------------------------------------------------------------- */

.mono {
  font-family: var(--mono);
}
.dim {
  color: var(--text-dim);
}
.faint {
  color: var(--text-faint);
}
.ink {
  color: var(--text);
}
/* Pushes whatever follows it to the far end of a flex row. */
.spacer {
  flex: 1;
}
/* A quiet line that sits beside a control rather than under a heading. */
.hint.inline {
  display: inline;
  margin: 0;
  max-width: none;
}
/* A hint that follows the control it explains. */
.hint.after {
  margin: 12px 0 0;
}
.hint strong {
  color: var(--text);
}
/* Anything ellipsised in a row. */
.ellipsis {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hidden-input {
  display: none;
}
/* The svg sprite: out of flow, out of sight. */
.sprite {
  position: absolute;
}

/* Forms: the input takes the room, the button keeps its width. */
.field-row > input {
  flex: 1;
}
.field-row.narrow {
  max-width: 420px;
}
.chip button {
  color: var(--text-faint);
  padding: 0;
  line-height: 1;
}
/* Space after a group rather than before the next one: a leading margin
   indents whichever group wraps onto a phone's second line. */
.controls .control-group {
  margin-right: 12px;
}
.controls.advanced {
  margin-top: 12px;
}
.proxy-input {
  width: 240px;
}

/* Notices. */
.notice p {
  margin: 6px 0 10px;
  font-size: 12.5px;
}
.notice .btn.small {
  margin-left: 10px;
}
.section .notice {
  margin-bottom: 12px;
}
.pick-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.pick-row .record {
  margin-left: 6px;
  color: var(--text-faint);
}
.pick-cancel {
  margin-top: 10px;
}

/* The ESPN league table has three columns to the Sleeper table's five. */
.espn-table {
  margin-bottom: 12px;
}
.espn-table .league-row {
  grid-template-columns: minmax(0, 1fr) 120px 90px;
}
.espn-table .tag {
  margin-left: 8px;
}
.league-id {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-faint);
}

/* Empty states. */
.board-empty {
  padding: 28px;
  text-align: center;
  color: var(--text-faint);
}

/* Setup. */
.setup .notice {
  margin-top: 18px;
  text-align: left;
}
.setup-alt {
  margin-top: 24px;
  font-size: 13px;
  color: var(--text-dim);
}
.setup-alt .link-btn {
  color: var(--pos);
  text-decoration: underline;
}
.setup-foot {
  margin-top: 22px;
  font-size: 12.5px;
  color: var(--text-faint);
}
/* A miss on the first screen: beside the field, closable, and the field
   stays where it was for another go. */
.setup .notice.setup-error {
  margin-top: 14px;
}
/* Returning people: the sign-in under the field, not beside it, so the
   field stays the one thing a newcomer sees first. */
.setup-google {
  margin-top: 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--text-dim);
}

/* The simulation panel's heading and summary line. */
.dist-panel .section-label {
  margin: 0 0 2px;
}
.dist-panel .dist-summary {
  font-size: 12.5px;
  color: var(--text-dim);
  margin: 0 0 4px;
}

