:root[data-theme="red"]{--bg:#f9f3f3;--fg:#261d1c;--muted:#906460;--border:#e4cfcd;--accent:#d32d1e;--accent-fg:#fafafa;--surface:#fdfcfc;--danger:#1f6cd7;--danger-fg:#fafafa;--sheen-color:rgba(211,45,30,0.4)}
:root[data-theme="orange"]{--bg:#f9f6f3;--fg:#26211c;--muted:#836c57;--border:#e4d8cd;--accent:#ac5a13;--accent-fg:#fafafa;--surface:#fdfcfc;--danger:#196cdf;--danger-fg:#fafafa;--sheen-color:rgba(172,90,19,0.4)}
:root[data-theme="yellow"]{--bg:#f9f8f3;--fg:#26241c;--muted:#797151;--border:#e4e0cd;--accent:#876e0b;--accent-fg:#fafafa;--surface:#fdfdfc;--danger:#136be7;--danger-fg:#fafafa;--sheen-color:rgba(135,110,11,0.4)}
:root[data-theme="lime"]{--bg:#f6f9f3;--fg:#21261c;--muted:#65774f;--border:#dae4cd;--accent:#4d7e16;--accent-fg:#fafafa;--surface:#fdfdfc;--danger:#cb4024;--danger-fg:#fafafa;--sheen-color:rgba(77,126,22,0.4)}
:root[data-theme="green"]{--bg:#f3f9f5;--fg:#1c261f;--muted:#517a5f;--border:#cde4d5;--accent:#1b813d;--accent-fg:#fafafa;--surface:#fcfdfc;--danger:#c7442a;--danger-fg:#fafafa;--sheen-color:rgba(27,129,61,0.4)}
:root[data-theme="teal"]{--bg:#f3f9f9;--fg:#1c2625;--muted:#507875;--border:#cde4e2;--accent:#1b7e76;--accent-fg:#fafafa;--surface:#fcfdfd;--danger:#c7442a;--danger-fg:#fafafa;--sheen-color:rgba(27,126,118,0.4)}
:root[data-theme="cyan"]{--bg:#f3f8f9;--fg:#1c2426;--muted:#54767e;--border:#cde0e4;--accent:#1a7a92;--accent-fg:#fafafa;--surface:#fcfdfd;--danger:#ca3f24;--danger-fg:#fafafa;--sheen-color:rgba(26,122,146,0.4)}
:root[data-theme="blue"]{--bg:#f3f6f9;--fg:#1c2026;--muted:#5d718c;--border:#cdd7e4;--accent:#256dd2;--accent-fg:#fafafa;--surface:#fcfcfd;--danger:#c83f23;--danger-fg:#fafafa;--sheen-color:rgba(37,109,210,0.4)}
:root[data-theme="navy"]{--bg:#f3f5f9;--fg:#1c1f26;--muted:#606f91;--border:#cdd4e4;--accent:#4b6cbb;--accent-fg:#fafafa;--surface:#fcfcfd;--danger:#af5442;--danger-fg:#fafafa;--sheen-color:rgba(75,108,187,0.4)}
:root[data-theme="indigo"]{--bg:#f4f3f9;--fg:#1e1c26;--muted:#6f689a;--border:#d0cde4;--accent:#6c5cd6;--accent-fg:#fafafa;--surface:#fcfcfd;--danger:#be472f;--danger-fg:#fafafa;--sheen-color:rgba(108,92,214,0.4)}
:root[data-theme="purple"]{--bg:#f7f3f9;--fg:#221c26;--muted:#806395;--border:#dbcde4;--accent:#9847d1;--accent-fg:#fafafa;--surface:#fdfcfd;--danger:#be4730;--danger-fg:#fafafa;--sheen-color:rgba(152,71,209,0.4)}
:root[data-theme="magenta"]{--bg:#f9f3f8;--fg:#261c24;--muted:#8f5f87;--border:#e4cde0;--accent:#c423a9;--accent-fg:#fafafa;--surface:#fdfcfd;--danger:#256dd1;--danger-fg:#fafafa;--sheen-color:rgba(196,35,169,0.4)}
:root[data-theme="pink"]{--bg:#f9f3f6;--fg:#261c20;--muted:#916175;--border:#e4cdd7;--accent:#c43973;--accent-fg:#fafafa;--surface:#fdfcfc;--danger:#376fbd;--danger-fg:#fafafa;--sheen-color:rgba(196,57,115,0.4)}
:root[data-theme="brown"]{--bg:#f9f5f3;--fg:#26201c;--muted:#866a59;--border:#e4d6cd;--accent:#9f603c;--accent-fg:#fafafa;--surface:#fdfcfc;--danger:#4371b1;--danger-fg:#fafafa;--sheen-color:rgba(159,96,60,0.4)}
:root[data-theme="gray"]{--bg:#f6f6f6;--fg:#212121;--muted:#707070;--border:#d9d9d9;--accent:#707070;--accent-fg:#fafafa;--surface:#fcfcfc;--danger:#d82626;--danger-fg:#fafafa;--sheen-color:rgba(112,112,112,0.4)}
:root[data-theme="black"]{--bg:#f6f6f6;--fg:#212121;--muted:#707070;--border:#d9d9d9;--accent:#313131;--accent-fg:#fafafa;--surface:#fcfcfc;--danger:#631111;--danger-fg:#fafafa;--sheen-color:rgba(49,49,49,0.4)}
:root[data-theme="jungle"]{--bg:#f4f9f3;--fg:#1e261c;--muted:#587a51;--border:#d1e4cd;--accent:#137600;--accent-fg:#ffffff;--surface:#fcfdfc;--danger:#a31800;--danger-fg:#ffffff;--sheen-color:rgba(19,118,0,0.4)}
@media (prefers-color-scheme: dark) {
:root[data-theme="red"]{--bg:#231615;--fg:#efebeb;--muted:#a88582;--border:#563c39;--accent:#e76255;--accent-fg:#0f0f0f;--surface:#322120;--danger:#4d8ce5;--danger-fg:#0f0f0f;--sheen-color:rgba(231,98,85,0.55)}
:root[data-theme="orange"]{--bg:#231c15;--fg:#efedeb;--muted:#a48e7b;--border:#564639;--accent:#d27b2e;--accent-fg:#0f0f0f;--surface:#322820;--danger:#5091eb;--danger-fg:#0f0f0f;--sheen-color:rgba(210,123,46,0.55)}
:root[data-theme="yellow"]{--bg:#232015;--fg:#efeeeb;--muted:#9f9775;--border:#565039;--accent:#ae942a;--accent-fg:#0f0f0f;--surface:#322e20;--danger:#5697f1;--danger-fg:#0f0f0f;--sheen-color:rgba(174,148,42,0.55)}
:root[data-theme="lime"]{--bg:#1d2315;--fg:#edefeb;--muted:#8a9e73;--border:#485639;--accent:#73a539;--accent-fg:#0f0f0f;--surface:#293220;--danger:#e37863;--danger-fg:#0f0f0f;--sheen-color:rgba(115,165,57,0.55)}
:root[data-theme="green"]{--bg:#15231a;--fg:#ebefec;--muted:#76a084;--border:#395643;--accent:#3ca860;--accent-fg:#0f0f0f;--surface:#203226;--danger:#de7965;--danger-fg:#0f0f0f;--sheen-color:rgba(60,168,96,0.55)}
:root[data-theme="teal"]{--bg:#152322;--fg:#ebefef;--muted:#749f9b;--border:#395653;--accent:#3da59d;--accent-fg:#0f0f0f;--surface:#203230;--danger:#df7a65;--danger-fg:#0f0f0f;--sheen-color:rgba(61,165,157,0.55)}
:root[data-theme="cyan"]{--bg:#152023;--fg:#ebeeef;--muted:#7899a2;--border:#395056;--accent:#389fb9;--accent-fg:#0f0f0f;--surface:#202e32;--danger:#e2725b;--danger-fg:#0f0f0f;--sheen-color:rgba(56,159,185,0.55)}
:root[data-theme="blue"]{--bg:#151b23;--fg:#ebedef;--muted:#8090a7;--border:#394556;--accent:#528ee1;--accent-fg:#0f0f0f;--surface:#202732;--danger:#e06a52;--danger-fg:#0f0f0f;--sheen-color:rgba(82,142,225,0.55)}
:root[data-theme="navy"]{--bg:#151923;--fg:#ebecef;--muted:#828da8;--border:#394256;--accent:#718bc9;--accent-fg:#0f0f0f;--surface:#202532;--danger:#c57767;--danger-fg:#0f0f0f;--sheen-color:rgba(113,139,201,0.55)}
:root[data-theme="indigo"]{--bg:#171523;--fg:#ecebef;--muted:#8b86ab;--border:#3d3956;--accent:#897cde;--accent-fg:#0f0f0f;--surface:#222032;--danger:#d56a55;--danger-fg:#0f0f0f;--sheen-color:rgba(137,124,222,0.55)}
:root[data-theme="purple"]{--bg:#1d1523;--fg:#eeebef;--muted:#9a84a9;--border:#4a3956;--accent:#ae6fdb;--accent-fg:#0f0f0f;--surface:#2a2032;--danger:#d56c57;--danger-fg:#0f0f0f;--sheen-color:rgba(174,111,219,0.55)}
:root[data-theme="magenta"]{--bg:#231521;--fg:#efebef;--muted:#a882a2;--border:#563951;--accent:#e152c9;--accent-fg:#0f0f0f;--surface:#32202f;--danger:#518de1;--danger-fg:#0f0f0f;--sheen-color:rgba(225,82,201,0.55)}
:root[data-theme="pink"]{--bg:#23151b;--fg:#efebed;--muted:#a88292;--border:#563945;--accent:#d36995;--accent-fg:#0f0f0f;--surface:#322027;--danger:#5c8dd0;--danger-fg:#0f0f0f;--sheen-color:rgba(211,105,149,0.55)}
:root[data-theme="brown"]{--bg:#231a15;--fg:#efedeb;--muted:#a58c7d;--border:#564439;--accent:#c0805a;--accent-fg:#0f0f0f;--surface:#322620;--danger:#6991c6;--danger-fg:#0f0f0f;--sheen-color:rgba(192,128,90,0.55)}
:root[data-theme="gray"]{--bg:#1c1c1c;--fg:#ededed;--muted:#919191;--border:#474747;--accent:#909090;--accent-fg:#0f0f0f;--surface:#292929;--danger:#e46969;--danger-fg:#0f0f0f;--sheen-color:rgba(144,144,144,0.55)}
:root[data-theme="black"]{--bg:#1c1c1c;--fg:#ededed;--muted:#919191;--border:#474747;--accent:#d8d8d8;--accent-fg:#0f0f0f;--surface:#292929;--danger:#f7cfcf;--danger-fg:#0f0f0f;--sheen-color:rgba(216,216,216,0.55)}
:root[data-theme="jungle"]{--bg:#112800;--fg:#edefeb;--muted:#88a076;--border:#455639;--accent:#78c400;--accent-fg:#112800;--surface:#273220;--danger:#f76a63;--danger-fg:#112800;--sheen-color:rgba(120,196,0,0.55)}
}
/* The shared stylesheet, narrowed to the rules docs/next-actions.md
   item 7 names: body, type, the button, the footer, safe-area insets.
   Not here: custom-property values (theme.go's CSS() generates 4pgno's
   per-theme blocks instead, this file only consumes var(--token) names)
   and every component rule with no caller yet (rosters, courts, print),
   item 8c's job, once a page needs them (owner, 2026-09-06: nothing is
   built ahead of a caller). .primary-btn has display: inline-block and
   text-decoration: none because the tip jar page's use is an <a> styled
   as a button.

   Item 8c added the player view's rules below, once item 8b gave this
   file a caller for them. The property values below (44px touch targets
   throughout, the svh list cap) are expressed over this file's nine
   token names (--surface/--border/--fg/--muted and the rest), which
   theme.go's CSS() generates: a rule written against any other token
   name would resolve to nothing. Courts and print rules still wait for
   Phase 4, per this file's own rule above. */

* {
  box-sizing: border-box;
}

/* The type scale (owner, 2026-10-16: one short scale, not a size per
   rule): every other text size on the phone and print pages is one of
   these. --fs-body is 17px at the browser's 16px default, the body
   rule's own size (that rule and its test keep the literal px), and in
   rem like the rest, so a heading set to it stays above help text set
   to --fs-m at any default size. A card's small label (.tbl h3) is a
   label, not a section heading. Outside the scale, each with its
   reason beside it: the Apple sign-in button's text, matched to
   Google's and Yahoo's fixed brand art; the schedule's name index
   (--fs-index, the size schedule.js's INDEX_* constants were measured
   at in print); the schedule's printed head and foot (9pt in @page,
   set for paper before the scale); the seat view's table number, fitted in px by seat.js;
   the 80px letter in a tonight card; and the display and television
   pages, sized to the screen (vmin, vh and --fs). */
:root {
  --fs-xs: 0.75rem;
  --fs-s: 0.85rem;
  --fs-m: 0.95rem;
  --fs-body: 1.0625rem;
  --fs-l: 1.25rem;
  --fs-xl: 1.6rem;
  --fs-xxl: 2rem;
  --fs-index: 0.9rem;
}


body {
  margin: 0;
  /* 17px, not a default 16px or 1rem, matching
     .claude/rules/webui.md ("body is 17px with
     inputs font: inherit"): a future input on a page sharing this
     stylesheet inherits 17px through font: inherit rather than the
     browser default, which iOS Safari zooms the page for on focus below
     16px. No input exists on tipjar.html today, but the stylesheet is
     shared, and getting this wrong here is exactly the gap a blind
     review of e4b41aa found. */
  font: 400 17px/1.5 -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--fg);
  max-width: 480px;
  margin-inline: auto;
  padding: 1.5rem 1rem 2rem;
  /* The viewport meta tag sets viewport-fit=cover so the page can draw
     under the status bar/home indicator/Dynamic Island; without these,
     content starts drawing under those rather than beside them. env()
     falls back to 0 on devices without an inset, so this is a no-op
     everywhere else. */
  padding-top: calc(1.5rem + env(safe-area-inset-top));
  padding-bottom: calc(2rem + env(safe-area-inset-bottom));
  padding-left: calc(1rem + env(safe-area-inset-left));
  padding-right: calc(1rem + env(safe-area-inset-right));
}

/* On a computer the column widens with the window (owner, 2026-09-26:
   the phone column wasted most of a desktop window), up to 60rem. A
   phone is narrower than either width, so nothing changes there. The
   display and the television page set their own width. On paper the
   page is the column: the printed schedule used a third of the sheet's
   width at the phone column (owner, the same day). */
@media screen and (min-width: 40rem) {
  body {
    max-width: 60rem;
  }
}
@media print {
  /* A few millimeters inside the page's own margins: in Safari's print a
     box ending exactly at the printable edge lost its right border (the
     owner's printout, 2026-09-26: the rounded boxes and the "find your
     name" table cut off at the right). */
  body {
    max-width: none;
    padding: 0 3mm;
  }
}

h1, h2, h3 {
  line-height: 1.25;
  /* A league's name can be 64 characters with no space; a heading breaks
     it rather than run past the page on a phone or on paper (found by
     webtests/tests/print.spec.ts, 2026-09-26). */
  overflow-wrap: anywhere;
}

h1 {
  font-size: var(--fs-l);
  margin: 0 0 0.75rem;
}

h2 {
  font-size: var(--fs-l);
  margin: 1rem 0 0.5rem;
}

/* The page's own title (Tonight, Settings, Schedule, Instruction sheet)
   is larger than the h2 section headings under it, and those larger
   than a field's label (--fs-s, below), each step plainly visible
   (owner, 2026-10-14: League settings, Name, description and icon, and
   Name were too close in size). */
.page-title {
  font-size: var(--fs-xl);
  /* h2's own margins, so an <h1> title (the pages outside a league,
     whose top line is the site bar) sits as an <h2> one does. */
  margin: 1rem 0 0.5rem;
}

h3 {
  font-size: var(--fs-body);
  margin: 1.25rem 0 0.25rem;
}

p {
  margin: 0.25rem 0 0.75rem;
}

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

/* The main action of a page or form, as a link or a button: the same
   look as button.primary below (owner, 2026-09-26: Save and Create a
   league were smaller than Start the next round), the same size as every
   other button. */
.primary-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--accent);
  border-radius: 9px;
  background: var(--accent);
  color: var(--accent-fg);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

/* A page's main button fills the phone column and is its label's width
   on a computer (owner, 2026-10-14). In a row with other links (the
   public pages' closing row) it takes the row's first line on a phone
   and the links wrap under it, so no two controls of different widths
   sit side by side (Apple, Buttons: "placing two buttons of different
   sizes near each other can make the interface look confusing"). A list
   of equal choices (the television claim page's leagues) is a stack of
   them, one size, a coherent set (the same page: "you signal that the
   options form a coherent set of choices"). (Owner, 2026-10-14;
   Material Design 3, Buttons: "Button container width can be
   responsive, which allows it to stretch horizontally", the filled style
   "ideally for only one action on a page"). Below 40rem the column is
   the phone's, which a narrowed window snaps to as well. The same holds
   for a control that is not a main button but stands alone on its line
   ("Invite another organizer" on the settings page), and for a set
   stacked on a phone (the printables' two links), each the column's
   width (owner, 2026-10-14). */
.phone-wide {
  width: 100%;
  box-sizing: border-box;
}
@media screen and (min-width: 40rem) {
  .phone-wide {
    width: auto;
  }
}

/* A text link alone on its line, outside any sentence ("Terms." closing
   the privacy page, "Privacy." the terms page): it keeps the text link's
   look (owner, 2026-10-14) and takes the project's 44 px tap height. WCAG
   2.2 SC 2.5.8's Inline exception reads "The target is in a sentence or
   its size is otherwise constrained by the line-height of non-target
   text" (w3.org Understanding page, updated 11 May 2026, read
   2026-10-14); a one-word line is not a sentence, so the floor is kept
   rather than leaning on the second clause. */
.solo-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;
}

.primary-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Links and buttons (owner, 2026-10-14, replacing item 50's rule that
   every navigation link take this style): a control goes somewhere as a
   link and does something as a button, and looks like what it does (W3C
   ARIA APG, Button: "both the appearance and role of a widget match the
   function it provides"). A link inside a sentence is a text link (the
   footer's tip jar, the contact addresses, "Privacy and terms.", the
   about page's game links), and so are the one-word "Privacy." and
   "Terms." lines that close the privacy and terms pages (owner, the
   same day: Privacy and terms as text links), which take .solo-link
   for the tap height. A link standing on its
   own as a control, in a top row or a list, takes .nav-link, a
   secondary button's shape; a page's main way forward may take
   .primary-btn (GOV.UK's start button is a link). The plan's "Links and buttons" entry has the sources.
   min-height and padding are copied from the button rule below so a
   .nav-link measures the same touch target as a real button; display:
   flex centers the text inside that box the way a real <button>'s own
   UA rendering already does, which min-height and padding alone would
   not guarantee on an inline-block <a>. No fill: background is
   --surface, not --accent, so a page with both a button and a nav-link
   never has two same-weight controls.

   --fg, not --accent: the item's own recipe named --accent for the
   text, but item 49's blind review of 9250ca2 measured
   contrastRatio(Accent, Surface) below WCAG 2.2's 4.5:1 floor in 14 of
   16 dark-mode palettes (as low as 3.81, details.help summary's own
   comment has the same finding) — TestThemes_MeetWCAGContrast never
   catches this pairing because it only asserts accent-on-bg, not
   accent-on-surface. --fg-on-surface is already asserted at 4.5:1 for
   every theme and look by that same test, so this needs no new token
   or new contrast row, the same fix item 49 already applied to the
   help control's own "?" glyph. */
.nav-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--fg);
  text-decoration: none;
}
.nav-link:hover {
  border-color: var(--muted);
}
/* Tonight's display address as its own button: a long address breaks
   rather than run past a phone's width. */
.nav-link.display-open {
  max-width: 100%;
  text-align: left;
  justify-content: flex-start;
  overflow-wrap: anywhere;
  font: inherit;
}

/* A row of a page's own navigation and account controls (owner feedback,
   live review): organizer.html's "Your leagues"/"League settings" and
   settings.html's "Back to {{.Title}}" used to sit apart from "Sign
   out" as separate paragraphs and forms, so the three never lined up.
   .nav-link is already inline-flex and a <form> containing only a
   <button> needs no width of its own, so wrapping both in one flex row
   is enough; no change to either element's own rule. */
.actions-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}
/* Sign out ends the row at its right edge on every page (owner,
   2026-09-26: the same spot everywhere, apart from the links), on the
   row's first line, except the Tonight page's row on a phone, which is
   wider than the screen and wraps (owner, 2026-10-13);
   webtests/tests/page-layout.spec.ts measures both. */
.actions-row > form[action="/auth/signout"] {
  margin-left: auto;
}
/* The Tonight page's row is wider than the phone column, so there
   "League settings" ends the first line at the right edge and Sign out
   takes the second line, under it (owner, 2026-10-14). The column is
   the phone's below 40rem, where a narrowed window snaps to it too. */
.tonight-row > .settings-link {
  margin-left: auto;
}
.tonight-row > form[action="/auth/signout"] {
  flex-basis: 100%;
  text-align: right;
}
@media screen and (min-width: 40rem) {
  .tonight-row > .settings-link {
    margin-left: 0;
  }
  .tonight-row > form[action="/auth/signout"] {
    flex-basis: auto;
  }
}
/* The top row's icons (webui/navicons.go): the back arrow on a link
   that goes back a page, the door on Sign out. Sized to the text and
   stroked in its color, before the words. */
.nav-icon {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  margin-right: 0.4em;
  vertical-align: -0.2em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* "Signed in as ..." sits directly under the top row on every signed-in
   page (owner, 2026-09-26), against the right edge, so it is under Sign
   out, which ends the row (owner, 2026-10-14). */
.signed-in-as {
  margin: 0 0 0.75rem;
  text-align: right;
}
/* "Previous partners" on the player page: a card of its own, set apart
   from the round in progress above it (owner, 2026-09-27), with a label
   on its own line over the names, like the page's other labels. */
.partners p {
  margin: 0.2rem 0;
}

/* The page header: <h1> plus the round help control at its top right
   (docs/next-actions.md item 49, moved here from the foot of the page).
   .page-header is position: relative so details.help summary and, once
   opened, .help-content (below) can each place themselves against the
   header's own box rather than the page's; padding-right on the
   container, not the <h1> alone, so player.html's .topbar (h1 plus
   a status indicator, both flexed inside this same header) also keeps
   its own content clear of the button's corner, not only a bare
   heading. min-height: 40px matches summary's own fixed size (below):
   summary is itself position: absolute, so it does not contribute to
   this element's own flow height, and a short one-line <h1> ("Create a
   league") renders shorter than 40px on its own — without this, the
   button would visibly extend past .page-header's own bottom edge, and
   .help-content's top: 100% (anchored to this box, not to the button)
   would then land above the button's true bottom edge instead of below
   it (caught by TestBrowser_HelpPanelFloatsWithoutReflowingThePage's
   own newleague.html case, which has the shortest <h1> of any page
   here). */
.page-header {
  position: relative;
  min-height: 40px;
  margin: 0 0 0.75rem;
  padding-right: 3rem;
}
.page-header h1 {
  margin: 0;
}

/* Provider sign-in buttons (docs/next-actions.md item 47). One <a> per
   provider, all the same fixed width so no provider looks more
   prominent than another (item 47 step 2's own words) — 320px clears
   Apple's own 140px minimum with room to spare and is also asserted
   directly in internal/webui's layout test. picture/source with
   media="(prefers-color-scheme: dark)" swaps art with no script. */
.signin-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 320px;
  max-width: 100%;
  margin: 0 auto 0.6rem;
  text-decoration: none;
  border-radius: 8px;
  overflow: hidden;
}
.signin-btn picture {
  display: flex;
  width: 100%;
}

/* Google and Yahoo: the picture/img *is* the button (providers.go's
   StyleImage) — the provider's own colors, border and text are already
   baked into the art at the exact padding and colors each guideline
   gives, so nothing here draws a background or border of its own. */
.signin-btn .signin-image {
  display: block;
  width: 100%;
  height: auto;
}

/* Apple: the HIG's custom web-button shape, since Apple's own hosted
   JavaScript button is out under this site's CSP (item 47's own
   citation). Height is 69px, not the HIG's own 44px minimum, because
   this button sits next to Google's and Yahoo's own pre-rendered art
   (.signin-image above), which this project cannot resize non-uniformly
   without distorting brand-mandated artwork: at the shared 320px
   width, Google's SVG (providers/google-light.svg, viewBox 180x40, a
   4.5:1 ratio) renders 320/4.5 = ~71.1px tall, and Yahoo's PNG
   (providers/yahoo-light.png, 414x86px, a ~4.814:1 ratio) renders
   320/4.814 = ~66.5px tall; 69px splits that ~4.6px difference (owner
   found by direct comparison in Safari and Firefox on 4pgno.com,
   2026-09-15: Apple's fixed 44px rendered at roughly half Google's and
   Yahoo's height, the exact "one option looks more prominent" defect
   item 47's own "same width" rule was written to prevent, just measured
   on the wrong axis — .claude/rules/webui.md's "A set of options is one
   group, in every dimension"). HIG's own margins, re-checked at the new
   height rather than assumed to still hold: "at least 1/10 of the
   button's height" before the logo is 6.9px at 69px, comfortably under
   the unchanged 16px left padding; "at least 8% of the button's width"
   after the title is 25.6px at 320px wide, still under the unchanged
   28px right padding. Fixed hex, not theme tokens: the HIG requires the
   background and the logo/title to both be black or white, nothing in
   between, and TestThemes_MeetWCAGContrast's sibling table in
   theme_test.go asserts this exact pair at 4.5:1. */
.signin-btn.signin-custom {
  height: 69px;
  padding: 0 28px 0 16px;
  font: inherit;
  font-weight: 600;
  /* Outside the type scale: sized beside Google's and Yahoo's fixed
     art, the group the comment above keeps one. */
  font-size: 1.15rem;
  white-space: nowrap;
}
/* .signin-btn picture's own width: 100% (above) is meant for Google's
   and Yahoo's own full-button-image case; for Apple's tiny logo it left
   the wrapper as a 100%-wide flex item, which pushed .signin-custom's
   own <span> title flush against the right padding instead of centered
   in the space right of the logo, the way Google's and Yahoo's own art
   centers their baked-in title (docs/next-actions.md item 56,
   docs/outstanding-work.md §26 B). Apple's HIG (developer.apple.com/
   design/human-interface-guidelines/sign-in-with-apple, read
   2026-09-16, the "Creating a custom Sign in with Apple button"
   section) sets only a *minimum* margin between the title and the
   button's right edge ("Maintain a minimum margin between the title
   and the right edge of the button. Ensure the margin measures at
   least 8% of the button's width") and lets the logo's own inset move
   independently ("Inset the logo if necessary ... you can adjust the
   space between the logo and the button's leading edge"); it never
   says where the title sits in between, so centering it is this
   project's own choice, not one the HIG forces either way. `width:
   auto; flex: none` here gives the wrapper back its own intrinsic
   width (the logo's, via .signin-logo's height: 100% below) instead of
   filling the row, and the <span> rule below takes the remaining space
   and centers the title in it. .signin-custom's own former `gap:
   0.9rem` between the two is gone, not merely unneeded: a fixed gap
   sits outside the span's own box, so text-align: center inside that
   box would still center on a space starting after the gap, not on the
   logo's own right edge, landing the title a further half-gap right of
   true center (measured: 7px off at this button's own size). Centering
   already gives the title its own breathing room from the logo, sized
   to whatever space is actually left over, rather than a gap fixed in
   advance. */
.signin-btn.signin-custom picture {
  width: auto;
  flex: none;
  height: 100%;
}
.signin-btn.signin-custom .signin-logo {
  display: block;
  height: 100%;
  width: auto;
  flex: none;
}
.signin-btn.signin-custom span {
  flex: 1;
  text-align: center;
}
.signin-apple {
  background: #000000;
  color: #ffffff;
}
@media (prefers-color-scheme: dark) {
  .signin-apple {
    background: #ffffff;
    color: #000000;
  }
}

footer {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: var(--fs-s);
  color: var(--muted);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
}

.muted {
  color: var(--muted);
  font-size: var(--fs-s);
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.9rem 1rem;
  margin: 0.6rem 0;
}

/* Touch behavior for everything tappable, and the sign-in list is why it
   matters: dozens of name buttons in a column is the primary gesture on
   this page. Without these, a long press on iOS pops the
   text-selection callout instead of behaving like a button, and every
   tap flashes the default gray box. */
button, summary {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* 44px is the smallest reliable touch target, and this page is used
   standing up, one-handed, often in a room with the lights down. A file
   input's own button looks and sizes like every other button
   (::file-selector-button, MDN: Baseline widely available since April
   2021, page modified April 17, 2026, read 2026-09-28). */
button,
input[type="file"]::file-selector-button {
  font: inherit;
  min-height: 44px;
  padding: 0.55rem 1.1rem;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
}
button:hover:not(:disabled),
input[type="file"]:hover:not(:disabled)::file-selector-button {
  border-color: var(--muted);
}
button:disabled {
  opacity: 0.45;
  cursor: default;
}
button.primary {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
  font-weight: 600;
}
button.wide {
  width: 100%;
}
/* Smaller text, never a smaller target: every tappable control is at
   least 44px (.claude/rules/webui.md; owner, 2026-09-26: these were
   36px). */
button.small {
  min-height: 44px;
  padding: 0.3rem 0.7rem;
  font-size: var(--fs-s);
}

/* <select> joins input[type="text"]'s own rule
   here (docs/next-actions.md item 30, the create-league form): base.css's
   own top comment says "nothing is ported ahead of a caller", and no
   page needed it before this one. <textarea> joins for the
   tonight page's pasted list (docs/outstanding-work.md §34), inheriting
   the 17px that keeps iOS from zooming on focus. */
input[type="text"], select, textarea {
  font: inherit;
  min-height: 44px;
  width: 100%;
  padding: 0.5rem 0.75rem;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
}
/* <select> also gets an explicit height, not only the min-height above:
   WebKit on iOS replaces an author's min-height on a select whose height
   is auto with max(15, 20/11 of the font size), 30px at 17px
   (Source/WebCore/rendering/ios/RenderThemeIOS.mm,
   adjustMenuListButtonStyle, WebKit main, read 2026-10-02), and the box
   then takes its natural 40px (the owner's iPhone 16 Pro, 2026-09-29).
   With a height set, the same function sets a 15px min-height and
   leaves the height alone; border-box (the * rule) makes 44 the whole
   box. Owner, 2026-10-02: "Set height: 44px explicitly". Not yet
   measured on a real iPhone. */
select {
  height: 44px;
}
/* Scoped under form, not folded into the rule above: player.html's own
   text input (item 8b) has no <form> ancestor (it is JS-driven, not
   submitted), so this space is only for a real <form> stacking several
   fields vertically, the first page to do that — label/.fieldlabel's
   own margin-bottom (below) is not enough space between one field's
   input and the next field's label on its own. */
form input[type="text"], form select {
  margin-bottom: 0.75rem;
}
/* A field with its button or row of buttons under it (the player's
   "Check in", Tonight's "They are here", the pasted list's "Add them as
   expected", "Delete this league"): the 0.5rem a pop-up's row and a
   share row already keep, where they touched before (owner, 2026-10-14,
   measured 0 px). Only a full-width button: one beside its field in a
   row (Save, Plan) keeps the field's line (the blind review of
   07be3f53: an 8px margin dropped it 4px). */
input[type="text"] + button.wide,
input[type="text"] + .row,
textarea + .row {
  margin-top: 0.5rem;
}
/* The color picker and, once the Konami code unlocks the secret theme
   (item 146; organizer.js), the capsule beside it: a "B", the barrier
   capsule of the game the code comes from. Decoration, not a control. */
.theme-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}
form .theme-row select {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}
.capsule {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.8em;
  height: 1.7em;
  padding: 0 0.8em;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 800;
}
.capsule[hidden] {
  display: none;
}
input:focus-visible, button:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A label names its field; a hint (.muted) explains it. They must not
   look alike, or a hint reads as the label of what follows it (item 132):
   the label is in the text color and semibold, the hint stays muted. */
label, .fieldlabel {
  display: block;
  font-size: var(--fs-s);
  color: var(--fg);
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.row.wrapped {
  flex-wrap: wrap;
}
/* The hidden attribute hides through the browser's own display: none,
   which any author display rule outranks; a row that hides says so
   here (the name dialog's "Not coming" row). */
.row[hidden] {
  display: none;
}
.grow {
  flex: 1 1 auto;
  min-width: 0;
}

.notice {
  border-radius: 9px;
  padding: 0.7rem 0.9rem;
  margin: 0.6rem 0;
  font-size: var(--fs-m);
  border: 1px solid var(--border);
  background: var(--surface);
}
.notice.bad {
  border-color: var(--danger);
  color: var(--danger);
}

/* The tappable roster on the player sign-in: names are the only thing on
   it, at a size a thumb can hit. svh, not vh: vh is defined as lvh (the
   viewport with the URL bar retracted), so on iOS Safari with the bar
   showing — the state somebody is in when they first open this page —
   32vh is more than 32% of what they can actually see. Not dvh, which
   resizes while scrolling this very list. The plain vh line stays as
   the fallback for anything predating svh. */
.namelist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  max-height: 32vh;
  max-height: 32svh;
  overflow-y: auto;
  padding-bottom: 0.15rem;
}
.namelist button {
  min-height: 44px;
  padding: 0.45rem 0.9rem;
  font-size: var(--fs-body);
  font-weight: 600;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--fg);
  cursor: pointer;
}
.namelist button:not(:disabled):hover {
  border-color: var(--accent);
}
.namelist button:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The .qr-block rule styles an <img>: item 17's player QR is a PNG
   served from a per-tenant route (qrcode's own only output format, see
   tipjar.go's comment), not inline SVG. max-width/height:auto scale the
   256px image down inside this page's 480px-max card rather than
   overflowing a narrow phone. */
.qr-block { text-align: center; padding: 0.5rem 0; }
.qr-block img { max-width: 100%; height: auto; border-radius: 4px; }

/* details.help's own box collapses to zero height while closed: the
   HTML spec renders only <summary> when a <details> lacks the open
   attribute, so with that summary taken out of flow below, nothing
   remains to give the element height. Owner feedback, live review,
   2026-09-15: opened, the panel is a floating popover anchored under
   the button, not an inline block that pushes the rest of the page
   down — every page's markup wraps everything after <summary> in one
   .help-content div for exactly this (a bare sequence of sibling <p>/
   <h3>/<ul> elements could not share one background, border and shadow
   otherwise). .help-content's position: absolute resolves against
   .page-header (the nearest positioned ancestor, per the comment on
   that rule below), the same containing block summary's own
   position: absolute already uses, so the popover's top-right corner
   lands at the header's own bottom-right corner regardless of how many
   lines the page's <h1> wraps to. */
details.help {
  margin: 0;
}
details.help summary {
  position: absolute;
  top: 0;
  right: 0;
  /* 40px, docs/next-actions.md item 49's own number and its own
     acceptance-test assertion: a deliberate, explicit exception to
     .claude/rules/webui.md's general 44px touch-target
     floor for the help control. The tonight page's per-section help
     controls ("Phase 5 design" B.2, which chose to reuse this control)
     are the same control and take the same exception. */
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--border);
  /* --fg, not --accent: blind review of 9250ca2 measured
     contrastRatio(Accent, Surface) below WCAG 2.2's 4.5:1 floor in 14 of
     16 dark-mode palettes (as low as 3.81), a pairing
     TestThemes_MeetWCAGContrast never asserts because it only checks
     accent-on-bg, not accent-on-surface. --fg-on-surface is already
     asserted at 4.5:1 for every theme and look by that same test, so
     using it here needs no new token or new contrast row. */
  color: var(--fg);
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  /* Sheen containment: isolation: isolate keeps the ::after gradient sweep
     from compositing with anything behind the button; overflow: hidden
     clips it to the button's own round shape. */
  isolation: isolate;
  overflow: hidden;
}
details.help summary::-webkit-details-marker {
  display: none;
}

/* A periodic highlight sweep over the help control, including two
   measured findings (docs/outstanding-work.md §7): the explicit
   opacity: 0 / transform base below matches the animation's own 0%
   keyframe, since without it the element would show the plain cascade
   default (opacity 1, no transform) as a static bright smear for the
   whole 5s gap before the first animation-delay elapses; and
   --sheen-color (theme.go) tints with the theme's own accent at partial
   alpha over normal compositing rather than a blend mode, proven visible
   against every one of this project's sixteen surfaces
   by TestSheenColor_MeasurablyDiffersFromSurface. The timing (a pulse at
   5s, another at 10s, then every 60s after) is expressed purely through
   keyframe percentage placement against one shared 60s animation,
   applied twice. The pages this animation runs on (every page with a
   help panel) carry no script at all, so it runs continuously, with no
   visibilitychange pause: a deliberate simplification, not an oversight, accepted because a
   help icon's own sheen is decorative rather than something a hidden
   tab wastes meaningful battery keeping animated. */
details.help summary::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 40%;
  height: 100%;
  background: linear-gradient(75deg, transparent, var(--sheen-color), transparent);
  pointer-events: none;
  opacity: 0;
  transform: translateX(-150%);
  animation:
    help-sheen 60s 5s 1 linear,
    help-sheen 60s 10s infinite linear;
}

details.help > summary.sheen-paused::after {
  animation-play-state: paused;
}

@keyframes help-sheen {
  0%, 1% {
    transform: translateX(-150%);
    opacity: 0;
  }
  1.3% {
    opacity: 1;
  }
  2%, 100% {
    transform: translateX(250%);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  details.help summary::after {
    animation: none;
  }
}
/* The popover panel itself. width is capped two ways: min(90vw, ...)
   keeps an 8% margin clear on a 400px phone (per .claude/rules/webui.md
   "Phones") rather than touching both edges, and the fixed rem figure
   keeps it from stretching absurdly wide on a 1280px desktop, where a
   help panel reading edge-to-edge would be harder to read, not easier.
   max-height plus overflow-y: auto keeps a long panel (settings.html's
   is the longest) scrolling within itself rather than running off the
   bottom of a short viewport, since nothing here can rely on scrolling
   the page underneath it the way an inline block could. z-index is
   arbitrary but must clear ordinary content; nothing else on any of
   these pages sets one. */
details.help[open] .help-content {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 20;
  width: min(90vw, 22rem);
  max-height: min(70vh, 28rem);
  overflow-y: auto;
  margin-top: 0.5rem;
  padding: 0.75rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
details.help h3,
.help-popover h3 {
  font-size: var(--fs-body);
  margin: 1.2rem 0 0.3rem;
  color: var(--fg);
}
details.help p, details.help li,
.help-popover p, .help-popover li {
  font-size: var(--fs-m);
  color: var(--muted);
}
details.help ul,
.help-popover ul, .help-popover ol {
  padding-left: 1.1rem;
}
details.help li,
.help-popover li {
  margin: 0 0 0.35rem;
}
details.help li:last-child,
.help-popover li:last-child {
  margin-bottom: 0;
}

/* A help panel opened from a word in a sentence (the about page's "No
   repeat partners": owner, 2026-10-19, the word "avoids"). The word is a
   button carrying popovertarget, the HTML popover attribute's invoker
   (MDN, popover global attribute, modified 2026-07-03: invokers are a
   <button> or <input type="button">; Baseline 2024, since April 2024),
   whose omitted popovertargetaction is "toggle" (MDN, <button>, modified
   2026-04-24), and the panel is popover="auto", which "clicking outside
   the popover" or Esc closes (the same page). No script. The panel keeps
   the UA stylesheet's position: fixed, inset: 0 and margin: auto, which
   center it in the viewport (MDN, Using the Popover API, modified
   2026-07-09), and repeats the look of details.help[open] .help-content
   above, wider and taller than that panel because it is centered on
   the screen rather than hung from a control. .text-link undoes the button
   look so the word reads as the page's other text links (accent, the
   underline a link has by default); a target inside a sentence is WCAG
   2.2 SC 2.5.8's Inline exception, quoted at .solo-link. */
.text-link {
  font: inherit;
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--accent);
  text-decoration: underline;
  -webkit-user-select: text;
  user-select: text;
}
.help-popover {
  width: min(90vw, 32rem);
  max-height: min(85vh, 40rem);
  overflow-y: auto;
  padding: 0.75rem 1rem;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}

/* The contact line at the foot of a help panel (docs/next-actions.md item
   39, decision 2): a mailto is the one link a view is allowed to leave the site by. No new
   theme token: --border and --muted are already asserted at WCAG 2.2
   4.5:1 for every palette in theme_test.go's contrast test, which covers
   this pair without change. */
.contact {
  margin-top: 1rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
  font-size: var(--fs-s);
  color: var(--muted);
}
.contact a {
  color: var(--muted);
}

/* The team symbol sits beside the place's number once the table holds
   four (seat.js draws it inside .table-number or .dp-num, so the number's
   fit counts it), as tall as a digit and standing on the baseline. Its
   outline is currentColor, set here to the text color (the display's
   number is the accent color), and its fills fixed:
   internal/webui/symbols.go, their one home. */
.team-symbol {
  color: var(--fg);
  display: inline-block;
  width: 0.75em;
  height: 0.75em;
  margin-left: 0.12em;
  vertical-align: baseline;
  overflow: visible;
}
/* The player page's round card while a plan is in force (player.js shows
   the buttons and sets data-stepping). The arrows sit on the "Round N"
   line, one at each edge, and the rest of the drawing keeps the card's
   whole width below it. The line is as tall as an arrow and its text is
   centered between two arrow-wide paddings, so the text never runs under
   an arrow. */
.round-browse {
  position: relative;
}
.round-step {
  position: absolute;
  top: 0;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.round-step[hidden] {
  display: none;
}
#round-prev {
  left: 0;
}
#round-next {
  right: 0;
}
.round-step .nav-icon {
  margin-right: 0;
  vertical-align: baseline;
}
.nav-icon.flip {
  transform: scaleX(-1);
}
.round-browse[data-stepping] .round-number {
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: calc(44px + 0.4rem);
}
.table-row {
  position: relative;
  display: flex;
  justify-content: center;
  margin: 0.1rem 0 0.2rem;
}
.table-number {
  flex: none;
  white-space: nowrap;
  /* Outside the type scale: seat.js fits it in px. */
  font-size: var(--number-size, 5.5rem);
  font-weight: 900;
  line-height: 1;
}
/* Text for a screen reader only: in the accessibility tree, not drawn. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.pairing {
  font-size: var(--fs-l);
  font-weight: 600;
  margin: 0.15rem 0;
}
.vs {
  font-size: var(--fs-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 400;
  margin: 0.35rem 0;
}
.you {
  color: var(--accent);
}
.first-line {
  font-size: var(--fs-body);
  font-weight: 600;
  margin: 0.6rem 0 0;
}
.headline {
  font-size: var(--fs-l);
  font-weight: 600;
  margin: 0.3rem 0;
}

/* The organizer's tonight page (docs/next-actions.md item 71): every table at once, compact,
   because the organizer is checking the round is sane, not reading it
   from across a room (the display view is the one that has to carry
   to the back wall). Every rule below reuses one of base-color.go's
   own ten theme variables in a pairing TestThemes_MeetWCAGContrast
   already asserts (theme_test.go), rather than a new hue: a held
   table is a dashed border and muted text, not a colored one, so
   "held" never has to be told apart from "danger" by color alone, and
   no new contrast pair needs its own test. */
.tables {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.6rem;
  margin-top: 0.7rem;
}
.tbl {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.7rem 0.85rem;
}
.tbl h3 {
  margin: 0 0 0.35rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}
.tbl .pair {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.tbl .vs {
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  margin: 0.15rem 0;
}
.tbl .thin {
  font-size: var(--fs-s);
  color: var(--muted);
  font-style: italic;
}
.tbl.held {
  border-style: dashed;
}
.tbl.held h3, .tbl.held .pair {
  color: var(--muted);
}
/* A table that is in play and also held (overview §5): somebody
   pinned to it is not playing at it. It is a game, so it reads as
   one, only the dashed edge and the note say the table is also
   spoken for. */
.tbl.held-too {
  border-style: dashed;
}
.tbl.held-too .thin {
  color: var(--muted);
  font-style: normal;
}

/* The attendance list (docs/next-actions.md item 144): one line per
   person, in four columns: available, name, pinned, and the table they
   are at this round. The two marks are buttons with no box of their
   own, at the 44px tap target (.claude/rules/webui.md); what they show
   is aria-pressed. Their colors are the theme's accent and muted, each
   already asserted against the surface by TestThemes_MeetWCAGContrast,
   so no new pair joins the contrast matrix. */
.roster-card {
  padding: 0 0.75rem;
}
.roster-empty {
  color: var(--muted);
  padding: 0.8rem 0.2rem;
}
.person,
.roster-head {
  display: grid;
  /* The "not here" column beside the pin (owner, 2026-10-17) takes 44
     px from the name, so the place column and the gaps give some back
     (the place holds one or two characters and its heading's word): the
     name is 76 px at 320 px, 101 px before the column (measured
     2026-10-17, Chromium), enough for "Ann K." on one line. */
  grid-template-columns: 44px minmax(0, 1fr) 44px 44px 2.75rem;
  align-items: center;
  column-gap: 0.15rem;
}
/* The table column's heading over the rows: small and muted, centered
   over the numbers it names. */
.roster-head {
  padding-top: 0.4rem;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.roster-head > span:last-child {
  text-align: center;
}
.roster-head[hidden] {
  display: none;
}
.person {
  border-bottom: 1px solid var(--border);
}
.person:last-child {
  border-bottom: none;
}
.person > button {
  min-height: 44px;
  border: none;
  background: none;
  border-radius: 8px;
}
.person > button.check,
.person > button.pin,
.person > button.gone {
  width: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.check .box {
  width: 26px;
  height: 26px;
  border: 2px solid var(--muted);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.check .box svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
  visibility: hidden;
}
.check[aria-pressed="true"] .box {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}
.check[aria-pressed="true"] .box svg {
  visibility: visible;
}
.pin svg {
  width: 26px;
  height: 26px;
  color: var(--muted);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.pin[aria-pressed="true"] svg {
  color: var(--accent);
  fill: currentColor;
}
/* "Not here": an exclamation point in the pin's muted color. */
.gone svg {
  width: 26px;
  height: 26px;
  color: var(--muted);
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
}
.person > button.pname {
  text-align: left;
  padding: 0.35rem 0.25rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.person > button.pname.out {
  color: var(--muted);
  font-weight: 400;
}
/* A name opens its dialog when tapped, so it carries a tap cue after
   it (owner, 2026-10-14; Nielsen Norman Group, "Clickable elements":
   "Users need to know which areas of the page are plain static content,
   and which areas are clickable"), the same kind of mark as the
   repeat-partner tile's. Muted, joined to the name by a no-break space
   (a name that fills its line can still push it to the next, since the
   name breaks anywhere: measured at 320 px by the blind review of
   6ecc1b79), and silent to a screen reader where the browser reads CSS
   alternative text (the second content line; a browser without it
   keeps the first). */
.pname-text::after {
  content: "\00a0\203A";
  content: "\00a0\203A" / "";
  color: var(--muted);
  font-weight: 400;
}
/* The tag wraps as one word, never split across lines. */
.pname .tag {
  display: inline-block;
  white-space: nowrap;
  margin-left: 0.4rem;
  font-size: var(--fs-xs);
  color: var(--muted);
  font-weight: 400;
}
.person .where {
  text-align: center;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The tonight page's counts (item 144): three tiles in a row, number
   over label, the same box as a card. The repeat-partner tile is a
   button that opens the repeat-pairings detail, and says so with the
   disclosure triangle the other folds use. */
.stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  margin: 0.5rem 0 0.6rem;
}
.stats[hidden] {
  display: none;
}
.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem 0.4rem;
  min-width: 0;
}
button.stat {
  font: inherit;
  min-height: 44px;
  color: var(--fg);
}
.stat-n {
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1.2;
}
.stat-label {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.25;
  overflow-wrap: anywhere;
}
/* A label that starts with the league's own word ("tables", "courts")
   starts with a capital. ::first-letter applies only to block
   containers, so an inline one is made inline-block. */
.stat-label::first-letter,
.cap::first-letter {
  text-transform: uppercase;
}
span.cap {
  display: inline-block;
}
button.stat .stat-label::after {
  content: " \25B8";
}
button.stat[aria-expanded="true"] .stat-label::after {
  content: " \25BE";
}
/* Two buttons presented as a pair (Reshuffle and Undo, the two add
   buttons, a dialog's two): one group, so one size
   (.claude/rules/webui.md, "A set of options is one group"), in height
   too: when one label wraps to two lines on a phone, the other button
   stretches to match (owner, 2026-10-14). */
.pair-buttons > button {
  flex: 1 1 0;
  min-width: 0;
}
.pair-buttons,
.share-row {
  align-items: stretch;
}
#next-round #advance {
  margin-top: 0.1rem;
}

/* A section whose heading opens and closes it (item 144): the heading
   holds a button (the WAI-ARIA APG Disclosure pattern, organizer.html's
   top comment), drawn as the heading with the disclosure triangle a
   <details> would show, so the three sections read like the settings
   page's folded group. */
.fold-toggle {
  font: inherit;
  font-weight: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  min-height: 44px;
  text-align: left;
  /* It spans the heading row, so the whole heading is the tap target. */
  display: flex;
  width: 100%;
  align-items: center;
  gap: 0.4rem;
}
.fold-toggle::before {
  content: "\25B8";
  display: inline-block;
  width: 1em;
  flex: none;
}
.fold-toggle[aria-expanded="true"]::before {
  content: "\25BE";
}
.fold > .section-head > h2,
.fold > .fold-head > h2 {
  margin: 0;
  padding-top: 0;
}
/* The setup group's heading row has no help control of its own: its
   sections each have one. */
.fold-head {
  margin-top: 1.25rem;
}

/* The pin and name pop-ups (item 144): a <dialog> shown modal, a card
   in the middle of the screen over a dimmed page. max-height in svh,
   as the sign-in lists are (.claude/rules/webui.md), and
   overscroll-behavior: contain so a scroll that reaches its end does
   not run on into the page. */
dialog.sheet {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem 1.1rem;
  width: min(calc(100vw - 2rem), 24rem);
  max-height: 85vh;
  max-height: 85svh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
dialog.sheet::backdrop {
  background: rgba(0, 0, 0, 0.45);
}
dialog.sheet h2 {
  margin: 0 0 0.75rem;
  overflow-wrap: anywhere;
}
dialog.sheet .row {
  margin-top: 0.5rem;
}
/* While a pop-up is open the page behind it is fixed in place, top set
   by organizer.js to the scroll position it saved: overflow: hidden
   alone does not stop iOS Safari scrolling the page behind
   (.claude/rules/webui.md, "Dialogs lock scroll"). left and right keep
   the body's centered column. */
body.modal-open {
  position: fixed;
  left: 0;
  right: 0;
  overflow: hidden;
}

button.danger {
  background: var(--danger);
  color: var(--danger-fg);
  border-color: var(--danger);
}

/* A warning notice (an unhonored pin, a forced repeat, "no different
   pairing was possible"): distinguished from .notice.bad by a neutral
   border rather than a second colored one, so no new hue joins
   --danger in this file's own contrast matrix. */
.notice.warn {
  border-color: var(--muted);
}

/* The tonight page's paired televisions ("Display screens design
   (2026-10-10, item 147)"): the number and whether it is polling, what
   it shows, and the trash button, each at least 44px tall. */
.displays-head { margin: 1rem 0 0.25rem; }
.display-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.display-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 44px;
  grid-template-areas: "label trash" "screen screen";
  align-items: center;
  column-gap: 0.5rem;
  row-gap: 0.35rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}
.display-row:last-child { border-bottom: none; }
.display-label {
  grid-area: label;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.display-seen { font-size: var(--fs-s); }
.display-row[data-connected="false"] .display-seen { color: var(--danger); }
.display-row > button.trash {
  grid-area: trash;
  width: 44px;
  min-height: 44px;
  padding: 0;
  border: none;
  background: none;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* A segmented control: a few choices side by side, equal widths, the
   one in effect filled (aria-pressed), each at least the project's 44
   px. Its own line under the screen's name, since three choices do not
   fit beside it on a phone. */
.segmented {
  grid-area: screen;
  display: flex;
  border: 1px solid var(--border);
  border-radius: 9px;
  overflow: hidden;
}
.segmented > button {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  margin: 0;
  padding: 0.3rem 0.4rem;
  border: none;
  border-radius: 0;
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: var(--fs-s);
  cursor: pointer;
}
.segmented > button + button {
  border-left: 1px solid var(--border);
}
.segmented > button[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}
.trash svg {
  width: 24px;
  height: 24px;
  color: var(--danger);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The display: the television view (docs/next-actions.md item 72).
   Scoped under body.display so these rules coexist with the phone
   views' own rules in the same shared stylesheet. */
/* body's height: 100% resolves only against a definite html height;
   without it the grid grows with its content, fit() never sees an
   overflow, and the type runs to its 400px ceiling. */
html.display { height: 100%; }
body.display {
  max-width: none;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font: 400 16px/1.3 "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  padding: clamp(0.6rem, 1.4vh, 1.4rem) clamp(0.8rem, 1.6vw, 1.8rem);
  overflow: hidden;
}
body.display header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(0.5rem, 0.9vw, 1.1rem);
  flex: none;
  padding-bottom: clamp(0.4rem, 0.9vh, 0.9rem);
  border-bottom: 2px solid var(--border);
}
body.display h1 {
  font-size: clamp(1.6rem, 4.2vh, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.01em;
}
/* The league's icon stays in front of the heading, then the league's
   name and "Round N" (#round, written by display.js) beside it
   (decisions table, "Tenant icon": on the display header; "Display
   screens design (2026-10-10, item 147)": both on every screen). */
body.display h1.display-title {
  display: flex;
  align-items: center;
  gap: 0.35em;
}
body.display #status,
body.display #revision {
  font-size: clamp(0.8rem, 1.7vh, 1.3rem);
  color: var(--muted);
}
/* One line (owner, 2026-10-16: the room the rounds get): the heading,
   the screen's title, then the updated, revision and screen lines side
   by side, wrapping only on a narrow screen. */
body.display header > div:first-child {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: clamp(0.6rem, 1.2vw, 1.4rem);
  row-gap: 0.15em;
  min-width: 0;
}
.display-qr {
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 0.8vw, 0.9rem);
  flex: none;
}
.display-qr img {
  width: clamp(2.5rem, 6vh, 5rem);
  height: auto;
  display: block;
  border-radius: 3px;
}
.display-qr span {
  font-size: clamp(0.65rem, 1.4vh, 1.05rem);
  color: var(--muted);
  max-width: 11em;
  line-height: 1.25;
}
body.display main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(0.5rem, 0.9vw, 1.1rem) 0;
}
body.display #tables {
  --fs: 2rem;
  display: grid;
  gap: clamp(0.5rem, 0.9vw, 1.1rem);
  height: 100%;
  grid-auto-rows: 1fr;
}
.dtable {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: clamp(6px, 0.7vw, 12px);
  padding: calc(var(--fs) * 0.36) calc(var(--fs) * 0.44);
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.dtable h2 {
  margin: 0 0 calc(var(--fs) * 0.22);
  font-size: calc(var(--fs) * 0.42);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}
.dteam {
  font-size: var(--fs);
  font-weight: 600;
  line-height: 1.25;
  word-break: break-word;
}
.dvs {
  font-size: calc(var(--fs) * 0.38);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: calc(var(--fs) * 0.1) 0;
}
.dwaiting {
  font-size: calc(var(--fs) * 0.4);
  color: var(--muted);
  font-style: italic;
  margin-top: calc(var(--fs) * 0.12);
}
.dtable.held {
  background: repeating-linear-gradient(
    -45deg, var(--bg), var(--bg) 10px, var(--surface) 10px, var(--surface) 20px);
  border-color: var(--danger);
}
.dtable.held h2 { color: var(--danger); }
.dheld-for {
  font-size: calc(var(--fs) * 0.82);
  font-weight: 600;
  color: var(--danger);
  line-height: 1.25;
  word-break: break-word;
}
.dheld-note {
  font-size: calc(var(--fs) * 0.4);
  color: var(--muted);
  margin-top: calc(var(--fs) * 0.12);
}
.dtable.stepped-out { border-color: var(--danger); }
/* A full table whose four names share a first letter (item 145; the
   server's game.sameLetter, internal/initial), on the display and the
   tonight page: each first letter in the accent, and the letter large
   behind the names in the page's background color. Names run over the
   letter, and --bg is the one color every palette's text and accent are
   already held to 4.5:1 against (theme_test.go); the border color left
   the accent at 2.69:1 in teal's dark look (computed 2026-09-29). The
   letter is a pseudo-element so fit(), which measures the card's
   children, never sizes the type around it. The second content
   declaration hides the decoration from assistive technology where the
   alternative-text form parses; where it does not, the first one
   stands. */
.dtable[data-same-letter],
.tbl[data-same-letter] {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.dtable[data-same-letter]::before,
.tbl[data-same-letter]::before {
  content: attr(data-same-letter);
  content: attr(data-same-letter) / "";
  position: absolute;
  right: 0.06em;
  top: 50%;
  transform: translateY(-50%);
  z-index: -1;
  font-size: calc(var(--fs) * 4);
  font-weight: 800;
  line-height: 1;
  color: var(--bg);
  pointer-events: none;
}
/* A tonight card is sized by its text, not fitted, so its letter has a
   size of its own: 80px in a four-name card 117px tall (Chromium, 390
   by 844, measured 2026-09-29, before the type scale set the card's
   label and "vs" to 0.75rem from 0.72rem and 0.7rem). */
.tbl[data-same-letter]::before {
  font-size: 5rem;
}
.dinitial { color: var(--accent); }
.dtable.stepped-out .dheld-note { color: var(--danger); }
body.display footer {
  flex: none;
  padding-top: clamp(0.4rem, 0.9vh, 0.9rem);
  border-top: 2px solid var(--border);
  font-size: clamp(0.8rem, 1.8vh, 1.4rem);
  color: var(--muted);
  line-height: 1.35;
  margin-top: 0;
  border-bottom: none;
}
body.display footer strong { color: var(--fg); font-weight: 600; }
body.display footer.empty { visibility: hidden; }
body.display #message {
  display: none;
  text-align: center;
  white-space: pre-line;
  font-size: clamp(1.2rem, 3.4vh, 2.8rem);
  color: var(--muted);
  line-height: 1.4;
  padding: 0 1em;
}
/* The three screens ("Display screens design (2026-10-10, item 147)"):
   <body data-screen> shows one of #tables, #players and #summary. The
   sit-out footer is the tables screen's; the other two carry who sits
   out themselves. */
body.display #players,
body.display #summary { display: none; }
body.display[data-screen="players"] #tables,
body.display[data-screen="summary"] #tables { display: none; }
body.display[data-screen="players"] #players { display: grid; }
body.display[data-screen="summary"] #summary { display: flex; }
body.display[data-screen="players"] footer#satout,
body.display[data-screen="summary"] footer#satout { display: none; }
/* The players screen's own footer, who is marked not playing (owner,
   2026-10-16); the other screens do not show it. */
body.display footer#notplaying { display: none; }
body.display[data-screen="players"] footer#notplaying { display: block; }
body.display.message-only footer#notplaying { display: none; }
/* What the players screen is, under the round (owner, 2026-10-16). */
body.display #screen-title {
  font-size: clamp(1rem, 3vh, 2.4rem);
  font-weight: 600;
  color: var(--fg);
}
body.display #screen-title:empty { display: none; }
/* Rotate's cross-fade (display.js, fadeOut and fadeIn; owner,
   2026-10-16): half a second each way, a cut where the viewer asks for
   reduced motion. */
@keyframes screen-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes screen-in { from { opacity: 0; } to { opacity: 1; } }
.screen-leaving {
  animation: screen-out 0.5s ease forwards;
  pointer-events: none;
  z-index: 1;
}
.screen-entering { animation: screen-in 0.5s ease; }
@media (prefers-reduced-motion: reduce) {
  /* !important: the copy carries its screen's display inline. */
  .screen-leaving { display: none !important; }
  .screen-entering { animation: none; }
}
body.display #round:empty { display: none; }
body.display #round {
  color: var(--muted);
  font-weight: 600;
}
body.display #screen-no {
  font-size: clamp(0.7rem, 1.4vh, 1.1rem);
  color: var(--muted);
}
/* Players A to Z: the list runs down each column in turn; fitPlayers()
   picks the column count and --fs. */
body.display #players {
  --fs: 2rem;
  height: 100%;
  grid-auto-flow: column;
  column-gap: calc(var(--fs) * 1.2);
  align-content: start;
}
.dplayer {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: calc(var(--fs) * 0.5);
  font-size: var(--fs);
  /* 1.2, not 1.3 (owner, 2026-10-16: more of the height to the type;
     the rule under each row keeps the lines apart). Not 1.15: in Firefox
     its rows came out taller than their line box, so the row's hidden
     overflow cut their letters (displays.spec.ts, "thirty-six players",
     27 of 36 rows, 2026-10-16); 1.2 passes there in all three engines. */
  line-height: 1.2;
  /* fitPlayers() spreads the rows into the height the type leaves.
     Top and bottom, not padding-block, which is newer than the
     televisions' floor (webtests/tv-baseline.mjs). */
  padding-top: var(--spread, 0px);
  padding-bottom: var(--spread, 0px);
  white-space: nowrap;
  overflow: hidden;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}
.dplayer .dp-name { font-weight: 600; }
.dp-where {
  color: var(--accent);
  font-weight: 600;
  font-size: 0.8em;
}
.dp-where.dp-out { color: var(--muted); font-weight: 400; }
/* A row's place, its label alone (owner, 2026-10-16), as large as the
   name, with the team's symbol as on the phones (the rule above
   .team-symbol). */
.dp-where.dp-num {
  font-size: 1em;
  font-weight: 900;
}
/* The round summary: a row of numbers, then the pins. fitBox()
   sets --fs. */
body.display #summary {
  --fs: 2rem;
  height: 100%;
  flex-direction: column;
  justify-content: center;
  gap: calc(var(--fs) * 0.6);
  overflow: hidden;
}
.dstats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--fs) * 0.4);
}
.dstat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: clamp(6px, 0.7vw, 12px);
  padding: calc(var(--fs) * 0.3) calc(var(--fs) * 0.4);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.dstat-n {
  font-size: calc(var(--fs) * 1.6);
  font-weight: 700;
  line-height: 1.1;
}
.dstat-label {
  font-size: calc(var(--fs) * 0.5);
  color: var(--muted);
}
.dsum-line {
  font-size: calc(var(--fs) * 0.7);
  color: var(--muted);
}
.dsum-line strong { color: var(--fg); font-weight: 600; }
.dsum-pins h2 {
  margin: 0 0 calc(var(--fs) * 0.2);
  font-size: calc(var(--fs) * 0.5);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}
.dsum-pin-rows {
  columns: 3;
  column-gap: calc(var(--fs) * 1.2);
}
.dsum-pin {
  display: flex;
  gap: calc(var(--fs) * 0.4);
  align-items: baseline;
  font-size: calc(var(--fs) * 0.7);
  line-height: 1.35;
  break-inside: avoid;
}
.dsum-pin .dp-name { font-weight: 600; }
.dsum-held {
  color: var(--danger);
  font-size: 0.8em;
}
body.display.message-only #tables,
body.display.message-only #players,
body.display.message-only #summary { display: none; }
body.display.message-only #message { display: block; }
body.display.message-only footer { visibility: hidden; }
.display-tip-jar {
  flex: none;
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 0.8vw, 0.9rem);
  padding-top: clamp(0.3rem, 0.6vh, 0.6rem);
  font-size: clamp(0.6rem, 1.3vh, 1rem);
  color: var(--muted);
  line-height: 1.3;
}
.display-tip-jar img {
  width: clamp(2rem, 5vh, 4rem);
  height: auto;
  display: block;
  border-radius: 3px;
}
body.display.message-only .display-tip-jar { visibility: hidden; }

/* The schedule page ("Phase 4 design" decision 10): the sheet and its
   controls. Printing is the browser's own. MDN, read 2026-09-22: the
   print media type's styles "will only be used for printed content";
   @page sets "the page's dimensions, orientation, and margins";
   break-inside: avoid "Avoids any break (page, column, or region) from
   being inserted within the principal box", so a round never splits
   across two sheets. Controls carry .no-print and are hidden there. */
.schedule-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  margin-top: 1rem;
}
.schedule-head img {
  width: 8rem;
  height: auto;
  flex: none;
}
/* The text beside the QR may shrink and its long addresses break, so the
   header fits a phone's width (owner, 2026-09-26: at 390px the QR sat
   past the page's right edge). */
.schedule-head > div {
  min-width: 0;
}
.schedule-head p {
  overflow-wrap: anywhere;
}
.sround {
  margin: 1rem 0;
}
.sround-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.sround-head h3 {
  margin: 0;
}
@media print {
  .no-print {
    display: none !important;
  }
  .sround {
    break-inside: avoid;
  }
}
/* The printed schedule's running header (the league's name and the
   revision) and page numbers, on every page (owner, 2026-09-27). The
   page-margin boxes are CSS Paged Media Level 3's (W3C Working Draft,
   14 September 2023, read 2026-09-27): a margin box "is generated if and
   only if the computed value of its content property is not none", and
   the page counters are "automatically created"; MDN's compatibility
   data (@mdn/browser-compat-data 8.1.3, read 2026-09-27) lists margin
   boxes in Chromium 131 and later only, not Firefox or Safari. Measured
   2026-09-27: Chromium 153's PDF prints both on every page; macOS
   WebKit's print (WKWebView) prints neither, nor repeats a fixed
   element or a table's thead, and counter(page) outside a margin box is
   0 there. So in Safari they come from its own headers and footers, an
   option under Safari in its Print dialog (Apple's Safari User Guide,
   "Print or create a PDF of a webpage", the Safari 16 edition, read
   2026-09-27), which print the page's title; schedule.js keeps the
   revision in the title (the owner's choice, 2026-09-27; the plan's
   entry 123 has the owner's Safari PDFs). Both values are custom
   properties set only on the schedule page, so no other page prints
   them: --print-head by schedule.js, --print-foot by the
   data-print-pages attribute schedule.html's root carries. The boxes sit
   in the 12mm margin and leave the content area as it was: measured
   2026-09-27 in Chromium 153's PDF, the same rows break at the same
   places at the same heights with and without them, so INDEX_TABLE_PX's
   budget holds. */
[data-print-pages] {
  --print-foot: "Page " counter(page) " of " counter(pages);
}
@page {
  margin: 12mm;
  @top-center {
    content: var(--print-head);
    font: 9pt -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
    color: #000;
  }
  @bottom-center {
    content: var(--print-foot);
    font: 9pt -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
    color: #000;
  }
}

/* The tonight page's disclosures ("Paste a list of names", "Remove
   recent additions", the fairness counts; docs/outstanding-work.md
   §34): each summary is a control, so it meets the 44px tap target
   like the buttons (.claude/rules/webui.md), and keeps the browser's
   own display so its disclosure triangle shows it opens (owner,
   2026-09-26), as the setup group's does. display: flex drops that
   marker, so the height comes from padding instead: 17px at
   line-height 1.5 plus 2 x 0.6rem clears 44px. */
#paste-box > summary,
#recent-box > summary {
  min-height: 44px;
  box-sizing: border-box;
  padding: 0.6rem 0;
  cursor: pointer;
}

/* The about page ("Landing and about pages design (2026-09-24)"): each
   screenshot scales to the column, keeps its shape, and is framed so it
   reads as a picture of the site rather than a control on this page. */
.about-feature { margin: 1.5rem 0; }
.about-shot {
  display: block;
  max-width: min(100%, 300px);
  height: auto;
  margin: 0.75rem auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}
/* A television's screen reads better at the column's full width. */
.about-shot.wide { max-width: 100%; }

/* A picture set to page through (owner, 2026-10-12: the printed pages,
   the television's screens), in CSS alone, so the about and game pages
   still run no script (owner, 2026-10-13). One radio per picture, kept
   for the keyboard and screen readers (the arrow keys move through a
   radio group and wrap around) but not drawn; the picture of the checked
   one shows. Each picture's Previous and Next are labels for its
   neighbors' radios, the first and last pointing at each other, at the
   project's 44px; they are aria-hidden, since the radios say the same.
   Up to four pictures a set. */
.shot-set { position: relative; margin: 0.75rem 0; }
/* A printed page is read, so it is shown wider than other pictures. */
.shot-set:not(.wide) .about-shot { max-width: min(100%, 480px); }
.shot-set .set-pick {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}
.shot-set .set-slide { display: none; margin: 0; }
.shot-set .set-pick:nth-of-type(1):checked ~ .set-slide:nth-of-type(1),
.shot-set .set-pick:nth-of-type(2):checked ~ .set-slide:nth-of-type(2),
.shot-set .set-pick:nth-of-type(3):checked ~ .set-slide:nth-of-type(3),
.shot-set .set-pick:nth-of-type(4):checked ~ .set-slide:nth-of-type(4) { display: block; }
.shot-set .set-pick:nth-of-type(1):focus-visible ~ .set-slide:nth-of-type(1) .about-shot,
.shot-set .set-pick:nth-of-type(2):focus-visible ~ .set-slide:nth-of-type(2) .about-shot,
.shot-set .set-pick:nth-of-type(3):focus-visible ~ .set-slide:nth-of-type(3) .about-shot,
.shot-set .set-pick:nth-of-type(4):focus-visible ~ .set-slide:nth-of-type(4) .about-shot {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
.set-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}
.set-nav label { cursor: pointer; }
.set-count { color: var(--muted); }

/* A player's picture inside a drawn phone (owner, 2026-10-13: a generic
   phone, no model's look): a dark rounded case and a status bar
   (phoneframe.go), the picture as its screen. */
.phone-frame {
  max-width: min(100%, 300px);
  margin: 0.75rem auto;
  padding: 0.6rem 0.5rem 0.9rem;
  border-radius: 28px;
  background: #1c1f24;
  /* The case's own dark would vanish on a dark page: a ring in --muted,
     held at 4.5:1 against the page in every theme as text, outlines it
     (about-pictures.spec.ts measures it, light and dark). */
  box-shadow: 0 0 0 2px var(--muted);
}
.phone-frame .about-shot {
  margin: 0;
  max-width: 100%;
  border: none;
  border-radius: 6px;
}
.phone-status {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 0.6rem 0.35rem;
  color: #f2f4f7;
  font-size: var(--fs-xs);
  font-weight: 600;
}
.phone-icons { display: inline-flex; gap: 0.3rem; }
.phone-icons svg { height: 0.7rem; width: auto; fill: currentColor; }
.phone-icons .battery-case { fill: none; stroke: currentColor; }

/* On a computer, a picture sits beside the paragraphs of its section,
   on alternating sides (shot-right, shot-left), and a section ends below
   its picture; the wide pictures and the sets stay full width, and on a
   phone every picture stacks as before (owner, 2026-10-13). */
@media screen and (min-width: 40rem) {
  .about-feature { display: flow-root; }
  .about-shot.shot-right, .phone-frame.shot-right { float: right; margin: 0.25rem 0 0.75rem 1.5rem; }
  .about-shot.shot-left, .phone-frame.shot-left { float: left; margin: 0.25rem 1.5rem 0.75rem 0; }
}

/* The printed schedule (docs/next-actions.md item 100, owner,
   2026-09-24). A planned round is labeled as planned on screen and on
   paper. The "find your name" index is a ruled table with alternate
   shaded columns and a double line after every third name (owner,
   2026-09-27), the shading kept when printing: a browser's
   default, print-color-adjust: economy, may drop background colors to
   save ink (MDN, print-color-adjust, last modified 2026-08-27); "exact"
   keeps them. */
.planned-tag {
  margin-left: 0.5rem;
  font-size: var(--fs-s);
  font-weight: 400;
  color: var(--muted);
}
/* The index is split into tables of at most INDEX_ROUNDS_PER_TABLE
   rounds and INDEX_TABLE_PX of printed height (schedule.js); on a narrow screen one can still scroll
   sideways rather than squeeze. */
.name-index-tables {
  overflow-x: auto;
}
#name-index table {
  width: 100%;
  border-collapse: collapse;
  /* Outside the type scale: schedule.js's INDEX_* constants were
     measured in print at this size. .index-measure reads the same
     property, so what is measured is what prints. */
  font-size: var(--fs-index);
}
#name-index table + table {
  margin-top: 1rem;
}
#name-index th,
#name-index td {
  border: 1px solid var(--border);
  padding: 0.2rem 0.4rem;
  text-align: center;
}
#name-index th:first-child,
#name-index td:first-child {
  text-align: left;
  overflow-wrap: anywhere;
}
/* Every other round's column, header included: a round is child 2 of
   its row, so rounds 2, 4, 6 are the odd children after the name. */
#name-index th:nth-child(2n+3),
#name-index td:nth-child(2n+3) {
  background: var(--surface);
  print-color-adjust: exact;
}
#name-index tbody tr:nth-child(3n):not(:last-child) td {
  border-bottom: 3px double var(--border);
}
#name-index thead th {
  font-weight: 700;
}
/* The index's name type, off screen: schedule.js measures each name
   here, on one line (a span) to choose how many rounds a table takes,
   and wrapped at the name column's width (a div, its width set by the
   script) to know how many lines its row prints on. */
.index-measure {
  position: absolute;
  left: -10000px;
  top: 0;
  visibility: hidden;
  font-size: var(--fs-index);
}
.index-measure > span {
  white-space: nowrap;
}
.index-measure > div {
  overflow-wrap: anywhere;
}
#name-index td.out {
  color: var(--muted);
}
@media print {
  /* Black on white whatever the league's theme or the phone's dark
     mode: html:root[data-theme] outranks the palettes'
     :root[data-theme="..."] rules (theme.go), light and dark alike. The
     index's shading is a light gray on paper. */
  html:root[data-theme] {
    --fg: #000;
    --bg: #fff;
    --surface: #fff;
    --muted: #333;
    --border: #000;
    --accent: #000;
  }
  html:root[data-theme] body {
    background: #fff;
    color: #000;
  }
  #name-index th:nth-child(2n+3),
  #name-index td:nth-child(2n+3) {
    background: #e6e6e6;
  }
  /* On paper a card is one or two lines, a pair and its opponents side
     by side, so a round's games share a row and a page holds many
     rounds (owner, 2026-09-26: four rounds a page before). */
  #schedule .tables {
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: 0.4rem;
    margin-top: 0.35rem;
  }
  #schedule .tbl {
    padding: 0.35rem 0.5rem;
    border-radius: 6px;
  }
  #schedule .tbl h3 {
    margin-bottom: 0.15rem;
  }
  #schedule .tbl .pair,
  #schedule .tbl .vs {
    display: inline;
  }
  #schedule .tbl .vs {
    margin: 0 0.35rem;
  }
  #schedule .tbl .thin {
    display: block;
  }
  #schedule .sround {
    margin: 0.6rem 0;
  }
  #schedule .satout {
    margin: 0.25rem 0 0;
  }
  #name-index {
    break-before: page;
  }
  /* Tighter rows on paper; INDEX_ROW_PX (schedule.js) is this row's
     printed height. */
  #name-index th,
  #name-index td {
    padding: 0.1rem 0.4rem;
  }
  #name-index h2,
  #index-legend {
    break-after: avoid;
  }
  #name-index table {
    break-inside: avoid;
  }
  .name-index-tables {
    overflow: visible;
  }
}

/* An address the reader is to type or open (the about page's
   television address): set apart from the sentence around it. */
.url { color: var(--accent); }

/* The organizer page's display card: two routes to the same screen,
   the display address or a television's own pairing page, split by an
   OR on its own line so they cannot read as one instruction. */
.or-divider {
  text-align: center;
  font-weight: 700;
  letter-spacing: 0.08em;
  margin: 0.75rem 0;
}

/* "Remove recent additions" ("Remove recent additions design
   (2026-09-24)"): each name is a whole-row label, so the row is the
   44px tap target, not only the checkbox. */
.recent-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 44px;
}
.recent-row input {
  width: 1.25rem;
  height: 1.25rem;
}

/* A list of leagues, one full-width button each, stacked with no list
   bullet beside them, each at least the 44px touch target: "Your
   leagues" (landing.html, owner 2026-09-26) and the television claim
   page's (tvclaim.html). On "Your leagues", "Create a new league" sits
   under the list, set off by a line. */
.league-list {
  list-style: none;
  padding: 0;
  margin: 1rem 0;
}
.league-list li + li {
  margin-top: 0.75rem;
}
.league-list .nav-link,
.league-list .primary-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
}
.create-league {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  /* Creating a league, and starting one with no sign-in: one group of
     equal buttons, stacked. One column: the container's width on a phone
     (each is .phone-wide), and on a computer the widest label's, so both
     fit their labels and stay the same width as each other. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.75rem;
}
@media screen and (min-width: 40rem) {
  .create-league {
    grid-template-columns: max-content;
  }
}
/* Signed out, the one start button sits centered like the sign-in
   buttons above it, still in .create-league's ruled-off area (owner,
   2026-10-20). On a phone the column is already the container's width. */
@media screen and (min-width: 40rem) {
  .create-league.start-centered {
    justify-content: center;
  }
}

/* The television page (tv.html): read from across a room, so the body's
   480px phone column is lifted and everything is sized in vmin to fit a
   landscape screen without scrolling: the QR on the left, the words on
   the right, the tip jar line beneath. The QR's PNG is scaled up with
   nearest-neighbor sampling so its modules stay crisp. */
body.tv-page {
  max-width: none;
  min-height: 100vh;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 3vmin 4vmin;
}
.tv-page main {
  flex: 1;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "title title" "code words";
  align-items: center;
  column-gap: 5vmin;
}
.tv-page h1 {
  grid-area: title;
  font-size: 6vmin;
  margin: 0 0 2vmin;
}
.tv-page #tv-waiting,
.tv-page #tv-idle {
  display: contents;
}
.tv-page #tv-waiting[hidden],
.tv-page #tv-idle[hidden] {
  display: none;
}
.tv-qr {
  grid-area: code;
  grid-row: 2 / span 4;
  width: 60vmin;
  height: 60vmin;
  image-rendering: pixelated;
  background: #fff;
}
.tv-page main p {
  grid-column: 2;
  font-size: 3.5vmin;
  margin: 0 0 2vmin;
}
.tv-check-line strong {
  display: block;
  font-size: 12vmin;
  letter-spacing: 0.1em;
  line-height: 1.1;
}
.tv-page #tv-again {
  grid-column: 2;
  justify-self: start;
  font-size: 3.5vmin;
  min-height: 44px;
}
.tv-page footer {
  margin-top: 2vmin;
  font-size: 2vmin;
}

/* The settings description box ("Phase 5 design" A.2): a few lines tall,
   grown to fit its text by organizer.js. resize adds a handle where a
   browser supports it (not iOS Safari or Firefox for Android, MDN
   browser-compat-data, css/properties/resize.json, read 2026-09-23);
   vertical only, since sideways would break the form's single column. */
#description {
  resize: vertical;
  overflow: hidden;
}
/* A league's description as a player reads it keeps the line breaks and
   spacing it was typed with (owner, 2026-09-27); the settings box above
   is a textarea, which keeps them already. */
p#description,
#help-description {
  white-space: pre-wrap;
}

/* Per-section help on the tonight page ("Phase 5 design" B, §38): each
   section's heading row holds its own help control, the same 40px round
   summary as the page-level one, right-justified in the row, with the
   popover opening under the row's right edge. No sheen: that is the
   page-level control's attention cue, and six sweeping circles would be
   noise. */
.section-head {
  position: relative;
  min-height: 40px;
  padding-right: 3rem;
  margin-top: 1.25rem;
}
.section-head > h2,
.section-head > h3 {
  margin: 0;
  padding-top: 0.35rem;
}
.section-head + .card,
.section-head + p,
.section-head + .row {
  margin-top: 0.5rem;
}
details.help-section summary::after {
  content: none;
  animation: none;
}

/* A long address in a card (the player and display URLs on the tonight
   page) breaks where it must instead of running past the card's edge
   on a phone. */
.card p strong {
  overflow-wrap: anywhere;
}

/* A popover that opens straight onto a topic heading (the per-section
   ones) has no space above it. */
details.help .help-content > h3:first-child,
.help-popover > h3:first-child {
  margin-top: 0;
}

/* A section whose heading opens and closes it on the settings page:
   "Delete this league", folded until the owner opens it (owner,
   2026-09-26). The summary keeps the list-item marker as the cue, holds
   the heading inline beside it, and meets the 44px tap target
   (.claude/rules/webui.md). The tonight page's sections do the same
   with a heading button (.fold-toggle). */
.fold-group {
  margin-top: 1.25rem;
}
.fold-group > summary {
  cursor: pointer;
  min-height: 44px;
  padding-top: 0.35rem;
}
.fold-group > summary > h2 {
  display: inline;
  margin: 0;
}

/* The repeat-pairings detail ("Phase 5 design" C.5), opened from the
   next round's repeat-partner tile. */
#fairness-summary {
  margin: 0;
  font-weight: 600;
}
.fairness-pairs h3 {
  font-size: var(--fs-body);
  margin: 0.75rem 0 0.25rem;
}
.fairness-pairs ul {
  margin: 0;
  padding-left: 1.1rem;
}
.fairness-players {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.75rem;
}
.fairness-players th,
.fairness-players td {
  text-align: left;
  padding: 0.3rem 0.4rem;
  border-bottom: 1px solid var(--border);
}
.fairness-players th:not(:first-child),
.fairness-players td:not(:first-child) {
  text-align: right;
}

/* Share and copy ("Phase 5 design" D.2): the buttons in a row are one
   group of equal size (.claude/rules/webui.md, "A set of options is one
   group"). */
.share-row {
  margin-top: 0.5rem;
}
.share-row > button {
  flex: 1 1 0;
  min-height: 44px;
}

/* A league's picture icon beside its name ("Phase 5 design" E.6): the
   height of the text it sits in, its own shape kept. */
img.icon {
  height: 1.15em;
  width: auto;
  vertical-align: -0.2em;
  border-radius: 4px;
}
#icon-current img.icon {
  height: 64px;
}
#icon-picture input[type="file"] {
  margin-bottom: 0.75rem;
  font: inherit;
  /* The larger button plus the file name ran 46px past a 320px phone
     (WebKit, measured 2026-09-28); the name is cut, the button kept. */
  max-width: 100%;
}
input[type="file"]::file-selector-button {
  margin-right: 0.75rem;
}
/* Where players look for their league (owner, 2026-09-26: the picture
   read as too small there), it is drawn larger than the text: beside
   the name on the player page, and above it, centered, on the
   instruction sheet. */
.league-title img.icon {
  height: 2em;
  vertical-align: middle;
  margin-right: 0.15em;
}
.handout .handout-name img.icon {
  display: block;
  height: 30mm;
  margin: 0 auto 0.3rem;
}

/* The instruction sheet (handout.html): one printed page, centered, the
   player QR large enough to scan from a table's length away, the tip
   jar's smaller. Sized in mm so the paper version is what is designed;
   print colors come from the black-on-white print rules above. */
.handout {
  max-width: 180mm;
  margin: 0 auto;
  text-align: center;
}
.handout .handout-name {
  font-size: var(--fs-xxl);
  margin: 0.5rem 0 0.3rem;
}
.handout-description {
  /* Its line breaks print too (owner, 2026-09-27); tenant's
     MaxDescriptionLines keeps the sheet to one page. */
  white-space: pre-wrap;
  font-size: var(--fs-body);
  margin: 0 0 0.6rem;
}
.handout-qr img {
  width: 70mm;
  height: 70mm;
}
.handout-qr .url {
  margin: 0.2rem 0 0.8rem;
  word-break: break-all;
}
.handout h2 {
  margin: 0.4rem 0;
}
.handout-steps {
  max-width: 150mm;
  margin: 0 auto;
  text-align: left;
  font-size: var(--fs-body);
  line-height: 1.45;
}
.handout-steps li + li {
  margin-top: 0.4rem;
}
/* The tip jar's QR and line sit together under one dividing line
   (owner, 2026-09-26: the QR entirely below it), so the line is the
   block's and not the footer's own. */
.handout-tip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.handout-tip img {
  width: 28mm;
  height: 28mm;
}
.handout-tip footer {
  margin: 0;
  padding: 0;
  border-top: none;
  text-align: left;
}
/* On paper the sheet must fit one Letter or A4 page even at the
   longest name (64) and description (200) a league can have; measured
   by webtests/tests/handout.spec.ts's page count. */
@media print {
  .handout .handout-name {
    font-size: var(--fs-xl);
    margin-top: 0;
  }
  /* 24mm: at the longest name (64) and description (200), printed at
     the page's full width, the sheet stayed on one Letter and one A4
     page with the picture at every size tried up to 32mm (measured
     2026-09-26, Chromium's print pipeline, before the 3mm print inset;
     webtests/tests/print.spec.ts's one-page checks pass with the inset
     and a wide, tall or tiny picture). 24mm leaves room. */
  .handout .handout-name img.icon {
    height: 24mm;
  }
  .handout-description {
    font-size: var(--fs-m);
  }
  .handout-qr img {
    width: 60mm;
    height: 60mm;
  }
  .handout-steps {
    font-size: var(--fs-m);
    line-height: 1.35;
  }
  .handout-tip {
    margin-top: 0.8rem;
    /* Its line and its QR stay on one page. */
    break-inside: avoid;
  }
  .handout-tip img {
    width: 24mm;
    height: 24mm;
  }
}

/* Who organizes and the open invitations on the settings page: one row
   per person or link, no bullet, the buttons spaced after the text
   (owner, 2026-09-26: Revoke touched the text before it). */
.member-list {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0;
}
.member-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}
.member-list li > span {
  flex: 1 1 12rem;
}
.member-list li > p {
  flex-basis: 100%;
  margin: 0;
}

/* A form is no wider than what it holds on a computer (owner,
   2026-09-26: the settings and create-league fields stretched the whole
   column). */
.narrow-form {
  max-width: 36rem;
}

/* The settings form: eight groups (docs/implementation-plan.md,
   "Settings page layout and help design"), one column at every width,
   filling the page's column as the tonight page does, in the order the
   tab key takes. Each field is the column's width (inputs are
   width: 100%), and every help popover anchors at its row's right edge. */
.settings-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: 1.25rem;
}
/* "Changes saved" (settings.html): a moment's note at the top of the
   screen after a field saves, in the text's colors on the surface, which
   the contrast test asserts for every theme. */
.saved-note {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 0.75rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  margin: 0;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface);
  color: var(--fg);
  white-space: nowrap;
}
.settings-group .section-head {
  margin-top: 0;
  margin-bottom: 0.5rem;
}
/* A hint sits directly under its label or heading, above its control
   (item 132), so it is close to what it explains and away from the
   control. */
label + .muted,
.section-head + .muted {
  margin: 0 0 0.4rem;
}
.section-head > h2 > label {
  display: inline;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  margin: 0;
}

/* The site-wide notice (docs/next-actions.md item 108): one line at the
   top of every page, set apart like a card; on the display, a line above
   the games that fit() leaves room for, since the body is a column.
   Never on paper: a notice about the site says nothing on a printed
   schedule. */
.site-banner {
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
  margin: 0 0 1rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
body.display .site-banner {
  flex: none;
  margin-bottom: 0.6rem;
  text-align: center;
  font-size: clamp(1rem, 2.4vh, 2rem);
}
@media print {
  .site-banner {
    display: none;
  }
}

/* The front page's links to the game pages (docs/next-actions.md item
   111): one set, so one size (.claude/rules/webui.md, "A set of options
   is one group"): one column on a phone, two by two from 600px, never
   three and one; every row as tall as the tallest, so a label that wraps
   to two lines does not make its row taller than the others (owner,
   2026-10-15; webtests/tests/page-layout.spec.ts measures it). */
#game-links {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: 1fr;
  /* .actions-row centers its items, which would leave a one-line link
     shorter than its row. */
  align-items: stretch;
}
@media (min-width: 600px) {
  #game-links {
    grid-template-columns: repeat(2, 1fr);
  }
}
#game-links .nav-link {
  text-align: center;
  justify-content: center;
}

/* The front page (owner, 2026-10-12: "less boring, but still
   conservative"): a larger title beside the mark (two pairs, the line
   between them), the opening sentence as a lede, and the four points as
   cards, two to a row on a computer. The words and their order are the
   page's own; only their presentation is here. Accent text on the
   surface is one of the pairs every palette asserts at 4.5:1
   (theme.go). */
body.landing .page-header h1 {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
/* The front page's title size, which the site bar's pages give their
   own title too (below). */
body.landing .page-header h1,
body.site-page h1 {
  font-size: var(--fs-xl);
}
.landing-mark {
  flex: none;
  width: 2.4rem;
  height: 2.4rem;
}
.landing-mark .pair-a, .site-mark .pair-a { fill: var(--accent); }
.landing-mark .pair-b, .site-mark .pair-b { fill: var(--muted); }
.landing-mark .divider, .site-mark .divider { stroke: var(--border); }
body.landing .lede {
  font-size: var(--fs-l);
}
#pitch {
  list-style: none;
  padding: 0;
  margin: 1rem 0 1.25rem;
  display: grid;
  gap: 0.75rem;
}
#pitch li {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: 10px;
  padding: 0.9rem 1rem;
  margin: 0;
}
#pitch li strong {
  display: block;
  color: var(--accent);
  font-size: var(--fs-body);
  margin-bottom: 0.2rem;
}
@media screen and (min-width: 40rem) {
  body.landing .page-header h1,
  body.site-page h1 { font-size: var(--fs-xxl); }
  #pitch { grid-template-columns: 1fr 1fr; }
}

/* The site bar (mark.go's SiteBar) on the pages outside a league that
   are not a player's (owner, 2026-10-12): the mark and the site's name,
   linking to the front page, above the page's own title, which is set
   at the front page title's size (that rule, above). The link is the project's 44px touch
   target. The television page sizes it, like the rest of that page, to
   be read across a room, and keeps its own title size. */
.site-bar {
  margin: 0 0 0.5rem;
}
.site-bar a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  color: var(--fg);
  font-weight: 600;
  text-decoration: none;
}
.site-bar a:hover {
  text-decoration: underline;
}
.site-mark {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
}
.tv-page .site-bar a {
  font-size: 3vmin;
}
.tv-page .site-mark {
  width: 5vmin;
  height: 5vmin;
}
