:root {
  --bg: #f4f5f7;
  --card: #ffffff;
  --ink: #16181d;
  --muted: #5f6672;
  --line: #dcdfe5;
  --accent: #9a2a2a;      /* singlet red */
  --accent-ink: #ffffff;
  --good: #1d7a4c;
  --warn: #a86a00;
  --bad: #b3261e;
  --radius: 10px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 17px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
a { color: var(--accent); }

/* The bars that stay put.

   All of them in one sticky box: the nav, and whichever of the sandbox, meet
   mode, view-as and site-node bars are showing. Each says something you need
   while you are acting -- which copy of the data this is, whose eyes you are
   looking through -- and a warning that scrolls off the top is a warning that
   stops being read.

   Capped, because on a phone a sandbox bar with a role switcher in it can wrap to
   three lines, and a header that eats half the screen is worse than one that
   scrolls. Past the cap it scrolls inside itself instead of pushing the page
   down. */
.stickytop {
  position: sticky; top: 0; z-index: 50;
  max-height: 45vh; overflow-y: auto;
  box-shadow: 0 1px 6px rgba(0, 0, 0, .18);
}
@media print { .stickytop { position: static; box-shadow: none; } }

header.topbar {
  background: var(--ink); color: #fff; padding: 10px 16px;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
header.topbar .brand { font-weight: 700; font-size: 19px; color: #fff; text-decoration: none; letter-spacing: .3px; }
header.topbar nav { display: flex; gap: 14px; flex-wrap: wrap; margin-left: auto; align-items: center; }
header.topbar nav a { color: #e7e9ee; text-decoration: none; font-size: 15px; }
header.topbar nav a:hover { color: #fff; text-decoration: underline; }
header.topbar .who { color: #a9b0bd; font-size: 13px; }

/* On a phone the links and the bands fold away behind one button, so the page
   is the page. Everything is still in the document -- the meet band's End
   meet and Check-in, the site-node note -- it is a press away rather than a
   third of the screen. Wider than that, nothing changes. */
.navtoggle { display: none; }
@media (max-width: 720px) {
  .navtoggle {
    display: inline-block; margin-left: auto; border: 1px solid rgba(255, 255, 255, .45);
    background: transparent; color: #fff; border-radius: 8px; font-size: 20px;
    line-height: 1; padding: 6px 11px; cursor: pointer;
  }
  header.topbar nav { display: none; width: 100%; flex-direction: column; align-items: flex-start; gap: 12px; padding: 6px 0 4px; }
  header.topbar nav a { font-size: 17px; }
  .stickytop .meetbar, .stickytop .sitebar, .stickytop .bandtoggle { display: none; }
  .stickytop.nav-open header.topbar nav { display: flex; }
  .stickytop.nav-open .meetbar { display: flex; }
  .stickytop.nav-open .sitebar { display: block; }
}

/* The page uses the whole width of whatever it is on. It was a 1000px
   reading column, which on a monitor left two thirds of the screen empty and
   every table squeezed into the middle; a head table, and a league admin at a
   desk, want the room. Phones keep their narrow gutter below. */
main { margin: 0; padding: 18px 24px 60px; }
@media (max-width: 720px) {
  main { padding: 14px 14px 48px; }
  /* On a phone the meets list wraps rather than scrolling sideways. */
  #meets-card td { white-space: normal !important; }
}
.board, .panel { max-width: none; }
h1 { font-size: 26px; margin: 8px 0 4px; }
h2 { font-size: 19px; margin: 26px 0 10px; }
.sub { color: var(--muted); margin: 0 0 18px; }

.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px; margin-bottom: 16px;
}
.card h2:first-child, .card h3:first-child { margin-top: 0; }

.grid { display: grid; gap: 14px; }
@media (min-width: 720px) { .grid.two { grid-template-columns: 1fr 1fr; } }

label { display: block; font-size: 14px; color: var(--muted); margin: 12px 0 4px; font-weight: 600; }
/* The :where() matters. Written as a bare :not() chain, each :not() adds its
   argument's specificity, so this selector scored (0,4,1) -- higher than an
   ordinary override like `.checkrow input[type=number]` at (0,2,1), which then
   silently lost and the field stayed full width. :where() matches identically
   but contributes nothing, so a single class is enough to override it. */
input:where(:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=submit])),
select, textarea {
  width: 100%; padding: 11px 12px; font-size: 17px; color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
}
input[type=file] {
  width: 100%; padding: 10px; font-size: 15px; background: var(--bg);
  border: 1px dashed var(--line); border-radius: 8px; color: var(--ink);
}
input:focus, select:focus, textarea:focus { outline: 3px solid #9a2a2a33; border-color: var(--accent); }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > * { flex: 1 1 160px; }

.btn {
  display: inline-block; padding: 12px 18px; font-size: 17px; font-weight: 600;
  border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink);
  cursor: pointer; text-decoration: none; text-align: center;
}
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.big { width: 100%; padding: 18px; font-size: 20px; }
.btn.small { padding: 7px 12px; font-size: 14px; }
.btn:hover { filter: brightness(1.07); }
.btn.danger { color: var(--bad); }
/* The host club's own meet, in a meet list. Not a different action -- every row
   opens the same page -- just a mark saying "this one is yours to set up", so
   the club that has to pair a laptop can find its row without reading hosts.
   Light blue because red is the accent and green and amber already mean
   matched and checking-in in the status pill beside it. */
.btn.host { background: #dce9f7; border-color: #a9c9e8; color: #1d4f91; }
/* A meet that is over, in a meet list. Solid rather than tinted like .host:
   that one marks a row among rows you might open, this one goes somewhere
   else entirely, and a button that changes the destination should not look
   like a button that changes the emphasis. Needs no dark variant -- --bad is
   not redefined there, and white on it reads in both. */
.btn.results { background: var(--bad); border-color: var(--bad); color: #fff; }
/* A button that wants pressing now -- the same solid red, by a name that says
   why: something is waiting on it. */
.btn.alert { background: var(--bad); border-color: var(--bad); color: #fff; }

/* A team's buttons on a provisional row, tinted in that team's own colour,
   the same on every row: the meet's teams take these in order. Light tints,
   so four of them on a row do not shout. */
.btn.team0, .teamvote.yes.team0 { background: #dce9f7; border-color: #a9c9e8; color: #1d4f91; font-weight: 700; }   /* blue */
.btn.team1, .teamvote.yes.team1 { background: #ecdff5; border-color: #cdb2e3; color: #6a3390; font-weight: 700; }   /* purple */
.btn.team2, .teamvote.yes.team2 { background: #d5f1ee; border-color: #9ed6d0; color: #0f6b66; font-weight: 700; }   /* turquoise */
.btn.team3, .teamvote.yes.team3 { background: #dff3e6; border-color: #a8d9bb; color: #1d6b3f; font-weight: 700; }   /* green */
.btn.team4, .teamvote.yes.team4 { background: #fbeed3; border-color: #e6cf98; color: #8a5a00; font-weight: 700; }   /* amber */
.btn.team5, .teamvote.yes.team5 { background: #f9dde7; border-color: #e6aac0; color: #8f2d55; font-weight: 700; }   /* pink */
html[data-theme="dark"] .btn.team0, html[data-theme="dark"] .teamvote.yes.team0 { background: #182a3d; border-color: #2f4f73; color: #a8ccff; }
html[data-theme="dark"] .btn.team1, html[data-theme="dark"] .teamvote.yes.team1 { background: #2a1d3a; border-color: #4d3670; color: #dcb8ff; }
html[data-theme="dark"] .btn.team2, html[data-theme="dark"] .teamvote.yes.team2 { background: #14302e; border-color: #2a5a56; color: #8fe0d8; }
html[data-theme="dark"] .btn.team3, html[data-theme="dark"] .teamvote.yes.team3 { background: #163124; border-color: #2c5a3e; color: #9fdcb5; }
html[data-theme="dark"] .btn.team4, html[data-theme="dark"] .teamvote.yes.team4 { background: #3a2c14; border-color: #6a5326; color: #ffd98a; }
html[data-theme="dark"] .btn.team5, html[data-theme="dark"] .teamvote.yes.team5 { background: #3a1d2a; border-color: #6a3650; color: #ffb3cc; }

/* A field that is meant to be changed, beside ones that are not: the meet's
   start time on the laptop, among a name, date and place that are read-only
   there. Light blue, the same tint as a host team's button. */
input.editable, html[data-theme="light"] input.editable { background: #dce9f7; border-color: #a9c9e8; }
html[data-theme="dark"] input.editable { background: #182a3d; border-color: #2f4f73; }

/* "Please wait": the page covered while a slow form submits (public/wait.js). */
.waitover { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center;
            background: rgba(0, 0, 0, .55); }
.waitbox { background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 12px;
           padding: 26px 36px; font-size: 20px; font-weight: 700; display: flex; align-items: center; gap: 16px;
           box-shadow: 0 8px 30px rgba(0, 0, 0, .35); }
.waitspin { width: 26px; height: 26px; border-radius: 50%; border: 4px solid var(--line); border-top-color: var(--accent);
            animation: waitspin 0.9s linear infinite; }
@keyframes waitspin { to { transform: rotate(360deg); } }

table { width: 100%; border-collapse: collapse; font-size: 15px; }
th, td { text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line); }
th { font-size: 13px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
tr:last-child td { border-bottom: none; }
.tablewrap { overflow-x: auto; }

/* Sortable column headings. The arrow is always in the layout -- a caret that
   only appears on the sorted column shifts every other heading sideways the
   first time you click one. */
th.sortcol { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortcol:hover { color: var(--ink); }
th.sortcol:focus-visible { outline: 3px solid #9a2a2a33; outline-offset: -3px; }
th.sortcol::after { content: '\2195'; opacity: .28; margin-left: 5px; font-size: 12px; }
th.sortcol[aria-sort=ascending]::after { content: '\2191'; opacity: 1; color: var(--accent); }
th.sortcol[aria-sort=descending]::after { content: '\2193'; opacity: 1; color: var(--accent); }

.pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 12px;
  font-weight: 700; background: #eceef2; color: var(--muted); }
.pill.good { background: #e3f3ea; color: var(--good); }
.pill.warn { background: #fbf0dd; color: var(--warn); }
.pill.bad  { background: #fbe4e2; color: var(--bad); }
.pill.blue { background: #dde8f6; color: #1d4f91; }
/* A wrestler's bout count: none is red, one orange, two yellow, three or
   more green -- the same four steps on every page that shows a count. */
.pill.n0 { background: #fbe4e2; color: var(--bad); }
.pill.n1 { background: #fde6d3; color: #b04a0c; }
.pill.n2 { background: #faf3c4; color: #7d6a00; }
.pill.n3 { background: #e3f3ea; color: var(--good); }

.flash { padding: 12px 14px; border-radius: 8px; margin-bottom: 14px; font-weight: 600; }
.flash.err { background: #fbe4e2; color: var(--bad); }
.flash.ok  { background: #e3f3ea; color: var(--good); }
.flash.warn { background: #fdf3dc; color: #7a5200; font-weight: 400; }

.stats { display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 14px; }
.stat { flex: 1 1 120px; background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px; }
.stat .n { font-size: 26px; font-weight: 700; }
.stat .l { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
/* Small counts, for a box that has to stay short (the dashboard's season). */
.stats.compact { gap: 6px; margin: 0; }
.stats.compact .stat { flex: 1 1 0; min-width: 64px; padding: 5px 8px; }
.stats.compact .stat .n { font-size: 18px; line-height: 1.2; }
.stats.compact .stat .l { font-size: 10px; letter-spacing: .3px; }

/* check-in list: thumb-sized targets */
/* Check-in: one wrestler, one row.
   A weigh-in table gets read down the names, so the name column is the only
   thing that flexes and everything else holds a fixed column. The scale weight
   is four characters -- it does not need, and should not get, the width of the
   screen. */
.checkrow { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; column-gap: 12px; padding: 9px 8px;
  border-bottom: 1px solid var(--line); }
.checkrow:last-child { border-bottom: none; }
/* After the check-in close, the meet admin's unticked rows go red: the box
   still works, and the colour says it should not need to. */
.checkrow.closed { box-shadow: inset 4px 0 0 var(--bad); }
.checkrow.closed input[type=checkbox] { accent-color: var(--bad); }
.checkrow input[type=checkbox] { width: 28px; height: 28px; accent-color: var(--accent); margin: 0; }
.checkrow .nm { font-weight: 600; min-width: 0; }
/* Inline, so the row stays one line; it wraps under the name only when the
   screen genuinely cannot fit it. */
.checkrow .meta { font-weight: 400; font-size: 13px; color: var(--muted); margin-left: 8px; }
.checkrow .wt { width: 84px; text-align: right; padding: 8px 10px; font-size: 16px; margin: 0; }
.checkrow.on { background: #f3f8f4; }
/* "saved" beside the name for a moment after a row writes itself, or the
   refusal, which stays. Small and quiet on success; the row turning green is
   the real signal, this only says the server agrees. */
.checkrow .saved { display: none; font-weight: 400; font-size: 12px; color: var(--good);
  margin-left: 8px; }
.checkrow .saved.show { display: inline; }
.checkrow .saved.bad { color: var(--bad); font-weight: 600; }
/* A sheet nobody on this machine can tick. Only the controls dim -- the names,
   the weights and the pills are the whole reason to read it from the league
   server, and muting those would make the page look broken rather than
   read-only. */
.checkrow.off input[type=checkbox], .checkrow.off .wt { opacity: .38; cursor: not-allowed; }

@media (max-width: 560px) {
  .checkrow .meta { display: block; margin-left: 0; }
  .checkrow .wt { width: 72px; }
}

.bout { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; margin-bottom: 8px;
  background: #fff; }
.bout .hd { display: flex; gap: 8px; align-items: baseline; }
.bout .no { font-weight: 700; color: var(--muted); min-width: 34px; }
.bout .vs { font-weight: 700; }
.bout .dt { font-size: 13px; color: var(--muted); margin-top: 3px; }
.matcol h3 { margin: 0 0 8px; padding-bottom: 6px; border-bottom: 3px solid var(--accent); }

/* QR codes: the SVGs the encoder emits carry only a viewBox, so without an
   explicit size they collapse to nothing in a flex or grid cell. The white
   plate matters too — a black-on-transparent code is invisible in dark mode,
   and unscannable on a dark-themed screen. */
.qrbox { width: 100%; max-width: 240px; margin: 0 auto; }
.qrbox svg {
  display: block; width: 100%; height: auto;
  background: #fff; padding: 8px; border-radius: 10px;
}

footer.foot { color: var(--muted); font-size: 13px; text-align: center; padding: 20px; }

@media print {
  header.topbar, .noprint, footer.foot { display: none !important; }
  body { background: #fff; font-size: 12px; }
  main { max-width: none; padding: 0; }
  .card, .bout { break-inside: avoid; border-color: #999; }
  .matcol { break-after: page; }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a; --card: #1c1f25; --ink: #eceef2; --muted: #9aa2b1;
    --line: #2c313a; --accent: #d24b4b; --accent-ink: #14161a;
  }
  input, select, textarea, .btn { background: #23272e; color: var(--ink); }
  .btn.primary { color: #fff; }
  .btn.host { background: #1c3350; border-color: #33587f; color: #cfe2f7; }
  .pill { background: #2a2f38; }
  .checkrow.on { background: #1d2a22; }
}

/* ===================================================================
   The mat scoreboard.
   Sized for a phone held in one hand at the scorer's table, with the
   two things that get pressed under pressure -- the clock and the
   points -- given the most room and the biggest targets.
   =================================================================== */
.board { margin: 0; }

.toprow {
  display: grid; grid-template-columns: auto 1fr auto; gap: 10px;
  align-items: start; margin-bottom: 14px;
}
/* End Match sits far left and Edit far right, so neither is anywhere near
   the buttons being pressed all match. */
/* Specificity matters here: .btn.danger is two classes, so a single .endbtn
   would lose to it and paint red text on a red button. */
.btn.endbtn { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn.endbtn:hover { filter: brightness(1.1); }
.editbtn { white-space: nowrap; }

.clockbox { text-align: center; }
.periodline { display: flex; gap: 8px; align-items: center; justify-content: center; }
.periodno { font-size: 15px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;
  color: var(--muted); min-width: 108px; }
.time {
  font-size: 60px; font-weight: 800; line-height: 1.05; margin: 2px 0 8px;
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.time.expired { color: var(--bad); }
.startstop { min-width: 150px; }
/* Blue while the clock is running: green already means a wrestler and a
   finished mat, and a running clock is neither. */
.board.running .time { color: #1d4f91; }

.mat2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.side {
  border: 3px solid var(--line); border-radius: var(--radius);
  padding: 14px 12px; text-align: center; background: var(--card);
  /* A column so the score and the buttons stay level across the two sides
     even when one wrestler's name wraps onto a second line. */
  display: flex; flex-direction: column;
}
.side .pts { margin-top: auto; }
.side.green { border-color: #1d7a4c; }
.side.red { border-color: #b3261e; }
.colour { font-size: 13px; font-weight: 800; letter-spacing: 2px; }
.side.green .colour { color: #1d7a4c; }
.side.red .colour { color: #b3261e; }
.wname { font-size: 22px; font-weight: 800; line-height: 1.2; margin: 4px 0 2px; }
.wteam { font-size: 13px; color: var(--muted); }
.pts {
  font-size: 76px; font-weight: 800; line-height: 1; margin: 8px 0 12px;
  font-variant-numeric: tabular-nums;
}
.ptbtns { display: flex; gap: 10px; }
.ptbtns .btn { flex: 1 1 0; font-size: 30px; padding: 16px 0; font-weight: 800; }

/* Each wrestler's + carries their own corner colour, so a glance down at the
   phone matches the ankle bands rather than needing to be read.
   Two classes deep on purpose: .btn.primary is itself two classes, and a
   single-class rule here would silently lose to it. */
.side.green .plus { background: #1d7a4c; border-color: #1d7a4c; color: #fff; }
.side.red   .plus { background: #b3261e; border-color: #b3261e; color: #fff; }
/* Minus stays quiet: taking a point off should never be the loud button. */
.ptbtns .minus { background: var(--card); color: var(--ink); }

.panel { margin: 18px 0 0; border-color: var(--accent); }

@media (max-width: 620px) {
  .toprow { grid-template-columns: 1fr 1fr; grid-template-areas: "end edit" "clock clock"; }
  .toprow > form, .toprow > #endBtn { grid-area: end; }
  .editbtn { grid-area: edit; }
  .clockbox { grid-area: clock; }
  .time { font-size: 52px; }
  .pts { font-size: 56px; }
  .wname { font-size: 18px; }
}

/* ===================================================================
   Pairing quality.
   One visual language everywhere a pairing is shown: a dial for a
   single pair, a bar in a list. Colour carries the meaning at a
   glance, the number carries it exactly, and the words carry it for
   anyone who cannot rely on the colour.
   =================================================================== */
.q { display: inline-flex; align-items: center; gap: 8px; }
.q .qtext { display: inline-flex; align-items: center; gap: 8px; }
/* Stacked: the bar over the score and its word, the width of the bar. */
.q.stack { flex-direction: column; align-items: flex-start; gap: 4px; }
.q.stack .qnum { min-width: 0; text-align: left; }

/* A provisional pairing: three blocks side by side, two lines each -- the
   slider over its score, then each wrestler's name over their bout count,
   with "vs" between. */
.pv { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.pv .pw { display: flex; flex-direction: column; gap: 4px; }
.pv .pw .nm { font-weight: 700; }
.pv .pvs { font-weight: 700; color: var(--muted); align-self: center; }
.qbar {
  position: relative; width: 120px; height: 10px; border-radius: 999px;
  background: linear-gradient(90deg, #b3261e 0%, #c46a1a 35%, #b39a1a 55%, #4b9a4b 75%, #1d7a4c 100%);
  overflow: visible;
}
.qbar .pin {
  position: absolute; top: -4px; width: 4px; height: 18px; border-radius: 2px;
  background: var(--ink); box-shadow: 0 0 0 2px var(--card);
}
.qnum { font-weight: 800; font-variant-numeric: tabular-nums; min-width: 30px; text-align: right; }
.qlabel { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; }
.q-excellent .qnum, .q-excellent .qlabel { color: #1d7a4c; }
.q-good      .qnum, .q-good .qlabel      { color: #4b8a3a; }
.q-fair      .qnum, .q-fair .qlabel      { color: #a86a00; }
.q-marginal  .qnum, .q-marginal .qlabel  { color: #c4551a; }
.q-poor      .qnum, .q-poor .qlabel      { color: #b3261e; }

/* The dial, for a single pair being decided on. */
.dial { width: 108px; height: 62px; flex: none; }
.dial text { font-family: inherit; }

.approvalrow { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.teamvote { display: flex; align-items: center; gap: 6px; font-size: 14px;
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; }
.teamvote.yes { border-color: var(--good); color: var(--good); font-weight: 700; }
.teamvote.no { border-color: var(--bad); color: var(--bad); font-weight: 700; }

/* Role chips. A person can hold several, so these have to read at a glance in a
   table cell rather than as a sentence. */
.rolechip { display: inline-block; font-size: 12px; font-weight: 600; padding: 2px 8px;
  border-radius: 999px; margin-right: 4px; white-space: nowrap;
  background: #eef2f6; color: #33506b; border: 1px solid #d5dee7; }
.rolechip.ta { background: #f1ecfa; color: #533d86; border-color: #ded1f3; }
.rolechip.site { background: #fbe9e9; color: #8f2b2b; border-color: #f0cfcf; }
/* Head coach and helper are the two ends of the ladder, so they are the two that
   have to be distinguishable at a glance. Helper shared team admin's purple until
   a roster showed both at once and they read as the same job. */
.rolechip.head { background: #e7f1ea; color: #22593a; border-color: #cbe1d3; }
.rolechip.helper { background: #fdf1e3; color: #8a5320; border-color: #f2ddc2; }
@media (prefers-color-scheme: dark) {
  .rolechip { background: #263141; color: #b8cbe0; border-color: #33445c; }
  .rolechip.ta { background: #2e2740; color: #cbb9ee; border-color: #443a5e; }
  .rolechip.site { background: #3a2626; color: #eab5b5; border-color: #543636; }
  .rolechip.head { background: #23342a; color: #a9d6bd; border-color: #33503f; }
  .rolechip.helper { background: #3a2f22; color: #e4c295; border-color: #574430; }
}

/* The team's coaches and admins, above their roster. A contact card rather than
   a table: there are usually two or three of them, and what you want is a name,
   a role and something to tap. */
.people { display: grid; gap: 10px; grid-template-columns: 1fr; margin-top: 10px; }
@media (min-width: 620px) { .people { grid-template-columns: 1fr 1fr; } }
.person { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
.person .pn { font-weight: 700; }
.person .pc { font-size: 14px; margin-top: 4px; line-height: 1.5; }

@media print {
  /* Sorting is a screen affordance: the arrows and the hint mean nothing on
     paper, and the arrow glyph prints as a stray character next to a heading. */
  th.sortcol::after { content: none; }
  /* Role chips need to survive a printer that drops background colours, so on
     paper they are a bordered label rather than a coloured pill. */
  .rolechip { background: none !important; color: #000 !important; border: 1px solid #999; }
  .person { border-color: #999; }
  .printonly { display: block !important; }
}
.printonly { display: none; }

/* Dashboard tiles: the top-bar links as targets you can hit with a thumb while
   holding a clipboard. Auto-fill rather than a fixed column count, so the same
   markup is one column on a phone and four on the laptop at the head table. */
.tiles { display: grid; gap: 12px; margin: 0 0 18px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.tile { display: block; padding: 14px 16px; text-decoration: none; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); }
.tile:hover { border-color: var(--accent); }
.tile .tl { display: block; font-weight: 700; font-size: 17px; color: var(--accent); }
.tile .th { display: block; font-size: 13px; color: var(--muted); margin-top: 3px; }

/* The home page as a desk. On a phone the tiles are the page and the boxes
   marked deskonly stay out of the way; on a monitor the tiles go and the page
   is a grid of boxes -- the season, the teams, the meets ahead, what is
   waiting, what came home. In meet mode (#home.plain) the cards stack as they
   always did. */
#home, .hb-main { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); align-items: start; }
#home { margin: 0 0 18px; }
#home > .card, .hb-main > .card { margin: 0; }
#home.plain, #home.plain > .hb-main { display: block; }
#home.plain .card { margin: 0 0 14px; }
/* The league admin's results, down the right third: sticky, and scrolling on
   its own so a long season does not push the desk down the page. */
.hb-side { position: sticky; top: 64px; max-height: calc(100vh - 84px); overflow-y: auto; }
.hb-side > .card { margin: 0 0 16px; }
.hb-side > .card:last-child { margin-bottom: 0; }
/* The split desk's left area: the season and the housekeeping box stacked in
   the first column, the meets ahead in the second, then the wide boxes below. */
/* The split desk, placed by hand: the season and the requests share the top
   row and its height; under the season, the teams; under
   the requests, the meets. The requests and the teams scroll inside themselves. */
@media (min-width: 900px) {
  #home.split > .hb-main > #hb-season { grid-column: 1; grid-row: 1; align-self: stretch; }
  #home.split > .hb-main > #hb-req { grid-column: 2; grid-row: 1; align-self: stretch; }
  #home.split > .hb-main > #hb-left { grid-column: 1; grid-row: 2; }
  #home.split > .hb-main > #hb-upcoming { grid-column: 2; grid-row: 2; }
}
#hb-req { display: flex; flex-direction: column; }
#hb-left { display: flex; flex-direction: column; gap: 16px; }
#hb-left > .card { margin: 0; }
.hb-req-body { overflow-y: auto; min-height: 0; max-height: 230px; }
#hb-teams .hb-rows { max-height: 420px; overflow-y: auto; }
.hb-rows > div { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.hb-rows > div:last-child { border-bottom: 0; }
.hb-side-head { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin-bottom: 10px; }
/* The season and the two views, on one line: the dropdown gives way, the buttons do not. */
.hb-resbar { margin: 0; flex-wrap: nowrap; align-items: center; width: 100%; }
.hb-resbar select { flex: 1 1 auto; min-width: 0; width: auto; padding: 6px 8px; font-size: 14px; }
.hb-resbar .btn { flex: 0 0 auto; white-space: nowrap; }
/* A meet as a block, in the admin desk's middle column: name, when and where, buttons. */
.hb-meet { padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 15px; }
.hb-meet:last-of-type { border-bottom: 0; }
.hb-big { font-size: 24px; font-weight: 700; line-height: 1.2; }
.hb-season-top { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; justify-content: space-between; }
.hb-season-top > .stats { flex: 1 1 220px; }
.hb-btns { margin: 12px 0 0; display: flex; flex-wrap: wrap; gap: 6px; }
.hb-list { margin: 0; padding-left: 18px; }
.hb-list li { margin: 2px 0; }
.hb-table { width: 100%; border-collapse: collapse; }
.hb-table th, .hb-table td { padding: 5px 6px; text-align: left; border-bottom: 1px solid var(--line); font-size: 14px; vertical-align: middle; }
.hb-table th { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
.hb-table .attchoose { display: flex; gap: 6px; flex-wrap: wrap; }
.hb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 0 20px; }
.hb-grid > div { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
/* The pages of a dashboard, on a monitor: label and hint, in columns. */
.hb-links { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 2px 14px; }
.hb-link { display: flex; flex-direction: column; padding: 6px 8px; border-radius: 6px; text-decoration: none; color: var(--ink); }
.hb-link:hover { background: var(--line); }
.hb-link strong { color: var(--accent); }
.hb-link span { font-size: 12px; color: var(--muted); }
.hb-link.urgent span { color: var(--warn); font-weight: 600; }
/* The way back to the desk, at the top of every other page. */
.backdash { margin: 0 0 12px; }
@media (min-width: 1100px) { main > .backdash { column-span: all; } }
/* The dashboard being read, in the top bar. */
header.topbar nav a.here { text-decoration: underline; text-underline-offset: 5px; }
@media (max-width: 899px) { #home .deskonly { display: none; } }
@media (min-width: 900px) {
  .tiles.phoneonly { display: none; }
  #home:not(.plain) > .hb-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #home.split { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
  #home > .wide, .hb-main > .wide { grid-column: 1 / -1; }
}
@media (min-width: 1300px) {
  #home:not(.plain):not(.split) > .hb-main { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The league badge in the top bar. Capped small on purpose: it sits beside the
   name, and a tall logo would push the whole header down on every page. */
header.topbar .brand { display: inline-flex; align-items: center; gap: 9px; }
.brandlogo { height: 26px; width: auto; max-width: 90px; border-radius: 4px;
  background: #fff; object-fit: contain; }

/* League contacts. */
.board { display: grid; gap: 10px; grid-template-columns: 1fr; }
@media (min-width: 620px) { .board { grid-template-columns: 1fr 1fr; } }

/* A tile with something waiting behind it. The handoff's warning about approval
   queues is that nobody reads them; a count on the way past is cheap insurance. */
.tile.urgent { border-color: var(--warn); }
.tile.urgent .th { color: var(--warn); font-weight: 600; }

/* ---------------------------------------------------------- family check-in

   The one control a parent uses on meet morning, in a car park, one-handed.
   Full width on a phone and inline on a desk, because the same button is the
   whole page on one and a cell in a list on the other. */
.cib { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0 2px; }
.cib > .btn, .cib .cidone .btn { flex: 1 1 260px; text-align: center; }
.cistat { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex: 1 1 200px; }
.cistat .sub { font-size: 13px; }
.cidone { flex: 1 1 100%; margin: 0; }
.cidone .btn { width: 100%; }

/* Disabled is a state, not a broken link: it still reads as a button so the
   parent can see that there IS one, and why it is not lit. A hidden button is
   indistinguishable from being on the wrong page. */
.btn.cioff {
  opacity: .55; cursor: default; pointer-events: none;
  border-style: dashed; font-weight: 600;
}

/* One child per row on the home page's meet-day card. */
.cirow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 10px 0; border-top: 1px solid var(--line); }
.cirow:first-of-type { border-top: 0; }
.cirow .cin { flex: 1 1 180px; }
.cirow .cin .sub { display: block; font-size: 13px; }
.cirow .cib { margin: 0; flex: 2 1 320px; }

/* Two answers, side by side, both big enough to hit without looking. They wrap
   to a column on a narrow phone rather than shrinking the text. */
.twobtn { display: flex; gap: 10px; flex-wrap: wrap; }
.twobtn .btn { flex: 1 1 220px; padding: 16px 12px; font-size: 17px; font-weight: 700; }

/* ------------------------------------------------- the family's mat board

   A parent's view of meet day: their children first, then only the mats those
   children are on. Two of these can be on one page when a household has
   children at two venues, so nothing here is positioned by id. */
.fboard + .fboard { border-top: 1px solid var(--line); padding-top: 18px; }
.kidline { padding: 10px 0; border-top: 1px solid var(--line); }
.kidline:first-child { border-top: 0; padding-top: 2px; }
.kidline .kn { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kidline .kidstatus { margin-top: 5px; font-size: 15px; }
.kidline .kidstatus > div { padding: 2px 0; }

/* All children, or one. Wraps rather than scrolls: a family of four on a narrow
   phone gets two rows of buttons, not a hidden fourth child. */
.pickchild { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }

/* A card with something in it that needs a person. Same idea as .tile.urgent:
   the count has to be visible on the way past, or the queue never gets read. */
.urgentcard { border-color: var(--warn); }
.urgentcard > h2 { color: var(--warn); }


/* The impersonation bar. Loud on purpose: the failure mode this guards against
   is an administrator forgetting they are somebody else and then doing something
   as them. It is never dismissible and it carries its own way out. */
.viewbar {
  background: var(--bad, #b3261e); color: #fff; padding: 8px 16px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  font-size: 14px; font-weight: 600;
}
.viewbar .btn { background: #fff; border-color: #fff; color: var(--bad, #b3261e); }

/* A testing sandbox. Purple, because it must not be mistaken for either of the
   other two bars at a glance: red means you are somebody else but the data is
   real, blue means the data is real and this is pretending to be a laptop. This
   one means the data is not real at all, which is the opposite reassurance and
   deserves its own colour rather than a shade of an existing one. */
.sandbar {
  background: #4b2a83; color: #fff; padding: 8px 16px;
  display: flex; flex-direction: column; gap: 8px;
  font-size: 14px; font-weight: 600;
}
/* Two rows with fixed corners: what-this-is left and whose-eyes right on the
   first, which-day left and where-you-are / Reset / Exit right on the second.
   The corners hold still as notices come and go, so a thumb that learned where
   "Look as" lives finds it there next time. */
.sandbar .sandrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.sandbar .btn { background: #fff; border-color: #fff; color: #4b2a83; }
/* A group of controls. The role switcher and the date each get one; the
   right-hand cluster on the second row is one too, so Reset and Exit stay
   together and away from the "look as a parent" a thumb is reaching for. */
.sandbar .sandroles { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.sandbar .sandright { margin-left: auto; gap: 10px; }
/* What to do where you are standing, said beside the choice. */
.sandbar .sandhint { color: #d9cdf0; font-weight: 400; font-size: 13px; }
/* The role you are already in: still a target, so the row keeps its order, but
   plainly not a thing to press again. */
.sandbar .btn.on { background: transparent; border-color: #b39ddb; color: #ede7f6;
  cursor: default; }
.sandbar .btn.on:hover { filter: none; }
/* The people dropdown. Narrow on purpose -- it sits in a bar with six other
   controls, and the names inside it are longer than the space any of them can
   afford. It grows to fit the widest option when opened, which is the browser's
   job and not worth fighting. */
.sandbar .sandpick {
  width: auto; max-width: 190px; padding: 6px 8px; font-size: 13px;
  border-radius: 6px; border: 1px solid #b39ddb;
  background: #fff; color: #4b2a83;
}
.sandbar .sandpick:focus { outline: 2px solid #fff; outline-offset: 1px; }

/* ------------------------------------------------ the parent's results plot

   Twenty dots do not justify a charting library, so the SVG is scored by hand
   in the template. This is only the frame around it: a scroller, because a
   season of matches is wider than a phone, and a key, because five dot colors
   without one is a puzzle rather than a chart. */
.plotscroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line); border-radius: 6px; background: #fff; }
.plotscroll svg { display: block; }
.plotkey { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px;
  font-size: 13px; color: var(--muted); }
.plotkey i.k { display: inline-block; width: 13px; height: 13px; border-radius: 50%;
  vertical-align: -2px; margin-right: 5px; }

/* A win and a loss have to be legible without relying on color alone, so the
   letter carries the weight and the color merely agrees with it. */
td.won, .won { color: var(--good); }
td.lost, .lost { color: var(--bad); }

/* Attending: two answers, each big enough for a thumb. */
.att { min-width: 104px; text-align: center; font-weight: 700; }
.att.yes { background: var(--good); border-color: var(--good); color: #fff; }
.att.no { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn.checkin { background: var(--good); border-color: var(--good); color: #fff; font-weight: 700; }
.btn.checkin.warn { background: #d99400; border-color: #d99400; color: #1c1f25; }
.attchoose { display: flex; gap: 8px; flex-wrap: wrap; }
.attchoose .btn { padding: 12px 16px; }

/* Once a family has answered, the row stops being a control and becomes a
   sentence: what they said, in the colour of the answer, with a way back to the
   two buttons. Text rather than a filled button -- a button invites a press, and
   this one is already pressed.

   <details> and not a script: script-src 'self' rules out inline handlers, so a
   toggle would need its own file and a listener to do what the browser already
   does. Open, the details lays out in a row so the buttons appear beside Cancel
   rather than under it. */
.attsaid { font-weight: 700; font-size: 17px; white-space: nowrap; }
.attsaid.yes { color: var(--good); }
.attsaid.no { color: var(--bad); }
.attedit > summary { display: flex; align-items: center; gap: 10px;
  list-style: none; cursor: pointer; }
.attedit > summary::-webkit-details-marker { display: none; }
.attedit[open] { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.attshut, .attedit[open] .attsaid, .attedit[open] .attopen { display: none; }
.attedit[open] .attshut { display: inline-block; }
/* On meet day the answer is shown but not editable -- the question has moved on
   to whether they are in the building. Muted, so it reads as a note rather than
   as the live control it is before the day. */
.attsaid.said { font-size: 15px; opacity: .8; }

/* A meet, collapsed or open. The marker is the whole summary row so a thumb
   cannot miss it. */
.meetsum { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 4px 10px; }
.meetsum::-webkit-details-marker { display: none; }
.meetsum .mt { font: 700 11px/1 "Segoe UI", sans-serif; letter-spacing: 1px;
  text-transform: uppercase; color: var(--accent); flex: 0 0 100%; margin-bottom: 4px; }
.meetsum strong { font-size: 17px; }
.meetsum::after { content: "▾"; margin-left: auto; color: var(--muted); }
details[open] > .meetsum::after { content: "▴"; }

/* One wrestler per block on the Meets tab. A table of three button columns does
   not survive a phone held one-handed in a gym, which is the only place this
   page is ever read. */
.attlist { margin-top: 12px; }
.attrow { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
  padding: 12px 0; border-top: 1px solid var(--line); }
.attrow:first-child { border-top: 0; }
.attname { flex: 1 1 160px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.attbtns { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.attbtns .btn { padding: 12px 16px; }

/* Something on a roster worth a coach's second look. Small, quiet and next to
   the name rather than in a column of its own: most wrestlers have none, and a
   column that is empty sixty times is a column nobody reads. The title carries
   the reason, so the chip itself only has to be noticeable. */
.flagchip {
  display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 7px;
  border-radius: 999px; vertical-align: 1px; white-space: nowrap;
  background: #fdf1dc; color: #8a5600; border: 1px solid #e8cd9a; cursor: help;
}
.flagchip.bad { background: #fbe6e4; color: #a1241d; border-color: #eebab5; }
@media (prefers-color-scheme: dark) {
  .flagchip { background: #3a2f1c; color: #e6c68a; border-color: #574529; }
  .flagchip.bad { background: #3a2626; color: #eab5b5; border-color: #543636; }
}
@media print { .flagchip { background: none !important; border: 1px solid #999; color: #000 !important; } }

/* A guardian's own child on a board built for a whole venue. Underlined as well
   as coloured, because a parent looking for one name in a hundred should not
   have to be able to tell this red from the surrounding ink. */
/* Blue, not amber: amber is what a warning pill looks like, and "one of ours"
   is not a warning. */
.mykid {
  background: #e3eefc; color: #1d4f91; font-weight: 700;
  border-radius: 4px; padding: 0 4px; box-decoration-break: clone;
  box-shadow: inset 0 -2px 0 #5b8fd6;
}
@media (prefers-color-scheme: dark) {
  .mykid { background: #1c2a3a; color: #a8c8f0; box-shadow: inset 0 -2px 0 #2a4a8a; }
}
@media print { .mykid { background: none; box-shadow: none; text-decoration: underline; } }

/* What a checked-in wrestler still has to come, at the top of their block on
   meet day. A parent's first question is "which mat, and when", and it should
   be answerable without scrolling past three buttons to reach it. */
.boutlist { flex: 1 1 100%; margin: 8px 0 0; }
.boutrow {
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  padding: 7px 0; border-top: 1px solid var(--line);
}
.boutrow:first-child { border-top: 0; padding-top: 2px; }
.boutwhere { font-weight: 700; font-size: 16px; }

/* ===================================================================
   The meet interface.

   A venue laptop and the league server are two different machines doing
   two different jobs, and on a Saturday morning somebody has both open.
   Telling them apart has to be instant and pre-verbal -- before you read
   the heading, before you look at the address bar. So the whole accent
   moves from the league's singlet red to blue.

   One token does it, because everything that is "the accent colour"
   already reads --accent: the top bar, the buttons, the pills, the
   headings, the plots. Adding a second palette anywhere else would be
   the way this drifts apart.
   =================================================================== */
body.meet {
  --accent: #1d4f91;          /* mat blue */
  --accent-ink: #ffffff;
}
@media (prefers-color-scheme: dark) {
  body.meet { --accent: #6ea8e8; --accent-ink: #14161a; }
}

/* Central pretending to be a laptop. Loud, undismissable and the same shape
   as the impersonation bar, for the same reason: somebody who forgets which
   one they are looking at will do something on the wrong machine. */
.meetbar {
  background: #1d4f91; color: #fff; padding: 8px 16px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  font-size: 14px; font-weight: 600;
}
.meetbar .btn { background: #fff; border-color: #fff; color: #1d4f91; }

/* -------------------------------------------------- Phone View (demo preview)
   A real preview of the mobile layout: the page is loaded in a phone-width
   iframe, so the app's media queries fire as they would on a phone. Toggled
   from the demonstration / testing bar; see public/phoneview.js. The sticky bar
   stays put (it carries the toggle) while the real page content is hidden and
   the phone frame takes its place. */
html.phone-view main,
html.phone-view > body > .foot { display: none !important; }

.phone-stage {
  display: flex; justify-content: center; align-items: flex-start;
  padding: 20px 12px 44px; background: #e7ebf1;
}
.phone-device {
  background: #0d0f12; border-radius: 40px; padding: 12px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .35); max-width: 100%;
}
.phone-screen {
  display: block; width: 390px; max-width: calc(100vw - 48px);
  height: min(844px, calc(100vh - 200px)); border: 0; border-radius: 28px;
  background: #fff;
}
/* On an actual small screen the framed preview is pointless and would overflow:
   show the page normally instead of a blank stage. */
@media (max-width: 480px) {
  html.phone-view main { display: block !important; }
  .phone-stage { display: none; }
}

/* ---------------------------------------------- collapsible status bands
   The purple demo/testing bar and the blue meet band can be folded away by the
   handle at the top of the sticky box (public/bandtoggle.js), which matters most
   in Phone View where they would otherwise take a third of a narrow screen. The
   red "viewing as" bar is never collapsed -- it is a safety notice. */
.bandtoggle {
  display: block; width: 100%; border: 0; cursor: pointer;
  background: rgba(0, 0, 0, .28); color: #fff;
  font-size: 12px; font-weight: 600; letter-spacing: .02em;
  padding: 3px 10px; text-align: right; line-height: 1.4;
}
.bandtoggle:hover { background: rgba(0, 0, 0, .42); }
.stickytop.bands-collapsed .sandbar,
.stickytop.bands-collapsed .meetbar { display: none !important; }

/* --------------------------------------------- hand-bout picker rows
   A stacked, wrapping list instead of a wide table, so the Make-a-Bout-by-Hand
   picker never scrolls left/right on a phone. Name + stats take the width; the
   Find-opponents button sits on the right and drops below on a narrow screen. */
.pickrow {
  display: flex; gap: 10px 14px; align-items: center; flex-wrap: wrap;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.pickrow:last-child { border-bottom: 0; }
.pickrow .pickwho { flex: 1 1 220px; min-width: 0; }
.pickrow .pickwho .sub { display: inline; margin-left: 6px; }
.pickrow .btn { flex: 0 0 auto; }

/* ------------------------------------------------ wide screens: columns, not stretch
   Taking the width cap off left every card stretched across a monitor: a
   form field two feet wide, a paragraph a hundred and fifty characters long,
   and still a page that scrolled. What a wide screen buys is room for more
   than one thing side by side. So on a wide screen the cards flow into two
   columns (three on a very wide one), each column a comfortable reading
   width; headings, notices and the stat strip span the page; fields and
   prose keep a width a hand can reach and an eye can follow. The pages
   built as one wrapper -- the meet-day page, the laptop set-up page -- lay
   their own cards out in a grid instead. Phones are untouched. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]), select, textarea { max-width: 640px; }
main > p.sub, .card > p, .card > .sub { max-width: 90ch; }
@media (min-width: 1100px) {
  main { column-count: 2; column-gap: 24px; }
  main > h1, main > h2, main > p, main > .flash, main > .stats, main > .tiles, main > .row,
  main > form, main > nav, main > table, main > .tablewrap, main > #board, main > #mine,
  main > .noprint, main > #day, main > #setup, main > #home, main > .spanall { column-span: all; }
  main > .card, main > details, main > div, main > section { break-inside: avoid; page-break-inside: avoid; }
  /* A grid for the pages whose cards live inside one wrapper. The meet-day
     page is its three steps across -- router, mat displays, Start -- in that
     order left to right; the laptop set-up page is two across with its first
     and last cards full width. */
  #day { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; align-items: start; }
  #setup { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
  #day > .card, #setup > .card { margin: 0; }
  #setup > .card:last-child, #setup > .card:first-child, #setup > #ready { grid-column: 1 / -1; }
  #setup > #ready[hidden] { display: none; }
}
@media (min-width: 1700px) {
  main { column-count: 3; }
  #setup { grid-template-columns: 1fr 1fr 1fr; }
}

/* Bouts side by side, as many to a row as fit, where a long single column
   would mean scrolling: the no-opponent list and the sign-off queue on the
   pairings page. A wide row holds a full pairing (slider, facts, votes,
   buttons); a plain one a name and a button. */
.boutgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 8px; }
.boutgrid.wide { grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)); }
.boutgrid > .bout { margin-bottom: 0; }
@media (max-width: 640px) { .boutgrid, .boutgrid.wide { grid-template-columns: 1fr; } }

/* The pairings page: the list wrapper (which the summary button hides) runs the
   width of the page, and the mat cards inside it sit in the same columns the
   page itself uses -- one card to a mat, side by side. */
@media (min-width: 1100px) {
  main > #howscored, main > #tb-list, main > #tb-summary, main > .fullrow, main > #meets-card, main > #invite-card { column-span: all; }
  /* The check-in list is one long column of rows; it gets the page too. */
  main > .card:has(.checkrow) { column-span: all; }
  /* Any card with a table in it gets the width of the page: a roster, a list of
     coaches, results, a season's meets -- squeezed into one column they clip
     or scroll sideways, and nothing on them wants to be narrow. */
  main > .card:has(.tablewrap), main > .card:has(> table), main > details:has(.tablewrap) { column-span: all; }
  /* Their prose runs the width of the box too: these cards are the width of the page. */
  #howscored > p, #tb-list > .card > p, .fullrow > .card > p { max-width: none; }
  #tb-list .matcards { column-count: 2; column-gap: 24px; }
  #tb-list .matcards > .card { break-inside: avoid; page-break-inside: avoid; }
}
@media (min-width: 1700px) { #tb-list .matcards { column-count: 3; } }

/* The check-in sheet is a list read down the names, and a list two feet wide
   is harder to tick than one a hand can span. It keeps a sheet's width and
   sits beside its own instructions card rather than under it. */
@media (min-width: 1100px) {
  main > form#checkin-sheet { column-span: none; break-inside: avoid; page-break-inside: avoid; max-width: 820px; }
}

/* A paragraph that is really a row of buttons -- the club chooser on the
   withdraw and check-in pages -- is not prose, and the reading-width cap on
   prose was wrapping it onto two lines with half the screen empty. */
main > p:has(> .btn), .card > p:has(> .btn) { max-width: none; }

/* The control centre lays itself out; the page-level columns must not. */
@media (min-width: 1100px) { main > #ctl { column-span: all; } }

/* The meet page's laptop card is the one thing on it a host club must do, so
   it runs the width of the page rather than sitting in a column. */
@media (min-width: 1100px) {
  main > #laptop { column-span: all; }
  #laptop > p, #laptop > .sub { max-width: none; }
}

/* ================================================================ the look
   Chosen on Meet Settings and carried on <html data-theme>. "auto" leaves the
   device's own preference (the prefers-color-scheme rules above) in charge.
   "dark" is the mat displays' palette -- near-black, flat panels with a thin
   line round them, the displays' red, amber, green and blue -- so the laptop
   and the televisions read as one thing. "light" pins the plain page even on
   a device set to dark. */
html[data-theme="light"] {
  --bg: #f4f5f7; --card: #ffffff; --ink: #16181d; --muted: #5f6672; --line: #dcdfe5;
  --accent: #9a2a2a; --accent-ink: #ffffff; --good: #1d7a4c; --warn: #a86a00; --bad: #b3261e;
  color-scheme: light;
}
html[data-theme="light"] input, html[data-theme="light"] select, html[data-theme="light"] textarea,
html[data-theme="light"] .btn { background: #fff; color: var(--ink); }
html[data-theme="light"] .btn.primary { color: #fff; }
html[data-theme="light"] .pill { background: #eef0f4; }
html[data-theme="light"] body.meet { --accent: #9a2a2a; --accent-ink: #fff; }

/* Dark: the mat displays' palette, so the laptop and the televisions read
   as one thing -- near-black, flat panels with a thin line round them, the
   display's red for the accent, its amber, green and blue for the pills. */
html[data-theme="dark"] {
  --bg: #0e1013; --card: #14181d; --ink: #f4f6fa; --muted: #98a1b0; --line: #262b33;
  --accent: #d24b4b; --accent-ink: #ffffff; --good: #3ea06a; --warn: #ffc266; --bad: #ff6b6b;
  --gold: #ffc266; --radius: 12px;
  color-scheme: dark;
}
html[data-theme="dark"] body { background: var(--bg); color: var(--ink); }
html[data-theme="dark"] body.meet { --accent: #d24b4b; --accent-ink: #ffffff; }
html[data-theme="dark"] h1 { letter-spacing: -.02em; }
html[data-theme="dark"] .card, html[data-theme="dark"] .bout, html[data-theme="dark"] .tile, html[data-theme="dark"] .stat {
  background: var(--card); border-color: var(--line); box-shadow: none;
}
html[data-theme="dark"] .card .card, html[data-theme="dark"] .card .bout { background: #0e1013; }
html[data-theme="dark"] .btn { background: #1c2128; color: var(--ink); border-color: #303640; }
html[data-theme="dark"] .btn:hover { border-color: #6d7684; filter: none; }
html[data-theme="dark"] .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
html[data-theme="dark"] .btn.danger { color: var(--bad); border-color: #5a3030; }
html[data-theme="dark"] .btn.host { background: #182a3d; border-color: #2f4f73; color: #cfe2f7; }
html[data-theme="dark"] input, html[data-theme="dark"] select, html[data-theme="dark"] textarea {
  background: #0e1013; color: var(--ink); border-color: #303640;
}
html[data-theme="dark"] .pill { background: #1c2128; color: var(--muted); }
html[data-theme="dark"] .pill.good { background: #163124; color: var(--good); }
html[data-theme="dark"] .pill.warn { background: #3a2c14; color: var(--warn); }
html[data-theme="dark"] .pill.bad { background: #3d2022; color: var(--bad); }
html[data-theme="dark"] .pill.blue { background: #182a3d; color: #7fb8ff; }
html[data-theme="dark"] .pill.n0 { background: #3d2022; color: var(--bad); }
html[data-theme="dark"] .pill.n1 { background: #3a2a18; color: #ffa060; }
html[data-theme="dark"] .pill.n2 { background: #3a3618; color: #ffe066; }
html[data-theme="dark"] .pill.n3 { background: #163124; color: var(--good); }
html[data-theme="dark"] .flash { background: #14181d; color: var(--ink); border-left: 4px solid var(--warn); }
html[data-theme="dark"] .flash.ok { border-left-color: var(--good); color: var(--good); }
html[data-theme="dark"] .flash.err { border-left-color: var(--bad); color: var(--bad); }
html[data-theme="dark"] .flash[style] { background: #14181d !important; color: var(--ink) !important; }
html[data-theme="dark"] header.topbar { background: #0e1013; border-bottom: 3px solid var(--accent); }
html[data-theme="dark"] th { color: var(--muted); font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; }
html[data-theme="dark"] .tile .tl { color: var(--warn); }
html[data-theme="dark"] .checkrow.on { background: #163124; }
html[data-theme="dark"] .mykid { background: #182a3d; color: #7fb8ff; box-shadow: inset 0 -2px 0 #2f4f73; }
html[data-theme="dark"] a { color: #7fb8ff; }
html[data-theme="dark"] .tick { background: #1c2128; color: var(--ink); }
html[data-theme="dark"] .tick.on { background: var(--good); color: #0e1013; }
html[data-theme="dark"] .qrbox svg { background: #fff; }
