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

:root {
  --bg: #0b1220;
  --card: #151f33;
  --card-2: #1c2942;
  --border: #26344f;
  --text: #e8eef7;
  --muted: #8b98ab;
  --accent: #a3e635;
  --accent-dark: #65a30d;
  --accent-text: #14200a;
  --danger: #f87171;
  --win: #4ade80;
  --loss: #f87171;
  --radius: 14px;
  /* Text scale. The Larger text switch (Profile tab, help sheet) raises it
     on one device. Sizes that are text, or hold text, are written as
     calc(Npx * var(--fs)) so they grow with it; a plain px size would not. */
  --fs: 1;
}

html, body { height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: calc(16px * var(--fs));
  line-height: 1.45;
}
body.large-text { --fs: 1.2; }

/* ---------- icons ----------
   icon() in app.js writes each icon as an empty span with its width and
   height for the size it is drawn at (larger text included), painted in
   currentColor through a mask of its drawing at that size's line weight. The
   mask rules (.im-*) are added to the page by app.js as each is first drawn,
   so nothing here resizes an icon or sets its weight. */
.i { display: inline-block; vertical-align: -3px; flex: none; background-color: currentColor; }
/* Windows High Contrast replaces every background colour, which would blank
   an icon painted as one. Icons keep theirs, painted in the system colour of
   the text beside them: ButtonText in a button, LinkText in a link (they can
   differ from CanvasText), and the history caret, a mask on a pseudo-element,
   likewise. */
@media (forced-colors: active) {
  .i { forced-color-adjust: none; background-color: CanvasText; }
  button .i { background-color: ButtonText; }
  a .i { background-color: LinkText; }
  details.hist summary::before { forced-color-adjust: none; background: CanvasText; }
}
#toast .i { margin-right: 6px; }
.confetti-msg .i { margin-right: 8px; vertical-align: -4px; }
/* A warning reads as a warning in any text colour: lime hint bar, grey note, red conflict line. */
.i-warning { color: #fbbf24; }

/* The bottom padding keeps the last card clear of the fixed tab bar, which is
   taller when the text is. */
#app { max-width: 480px; margin: 0 auto; padding: 16px 16px calc(90px * var(--fs)); min-height: 100%; }

/* ---------- generic ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 12px;
}
.muted { color: var(--muted); font-size: calc(14px * var(--fs)); }
.small { font-size: calc(13px * var(--fs)); }
h1 { font-size: calc(26px * var(--fs)); margin-bottom: 4px; }
h2 { font-size: calc(18px * var(--fs)); margin-bottom: 8px; }

button {
  font: inherit;
  border: none;
  border-radius: 12px;
  padding: 13px 18px;
  font-weight: 700;
  cursor: pointer;
  background: var(--card-2);
  color: var(--text);
  transition: filter .15s, transform .05s;
}
button:active { transform: scale(.98); }
button.primary { background: var(--accent); color: var(--accent-text); }
button.danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); }
button.ghost { background: transparent; color: var(--muted); border: 1px solid var(--border); }
button.block { display: block; width: 100%; }
button.sm { padding: 7px 12px; font-size: calc(13px * var(--fs)); border-radius: 9px; font-weight: 600; }
button:disabled { opacity: .45; cursor: default; }

select,
input[type="text"], input[type="number"], input[type="password"] {
  font: inherit;
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--card-2);
  color: var(--text);
  outline: none;
}
input:focus, select:focus { border-color: var(--accent-dark); }
select { appearance: none; -webkit-appearance: none; }

.row { display: flex; gap: 10px; align-items: center; }
.row .grow { flex: 1; }
.stack > * + * { margin-top: 10px; }

/* ---------- header ---------- */
/* The chips wrap under each other rather than push the page sideways: a
   moderator's RANKED + MOD + code row is wider than a 360px screen at the
   larger text size (and a few pixels over even without it). A flex item will
   not shrink below its content unless min-width is 0, so the row needs it to
   wrap at all. The logo keeps its width; each chip stays on one line. */
.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 14px;
}
.topbar > .row { flex-wrap: wrap; justify-content: flex-end; gap: 6px 8px; min-width: 0; }
.brand { font-weight: 800; font-size: calc(20px * var(--fs)); letter-spacing: .3px; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.chip {
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 99px; padding: 4px 12px; font-size: calc(13px * var(--fs)); font-weight: 700;
  letter-spacing: 1px; white-space: nowrap;
}
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--danger); margin-right: 6px; vertical-align: 1px; }
.dot.on { background: var(--win); }

/* ---------- home ---------- */
.hero { text-align: center; padding: 46px 0 30px; }
.hero .logo { display: flex; justify-content: center; margin-bottom: 14px; }
.hero p { color: var(--muted); margin-top: 6px; }

/* ---------- tabs ---------- */
.tabs {
  position: fixed; bottom: 0; left: 0; right: 0;
  background: rgba(11, 18, 32, .96);
  border-top: 1px solid var(--border);
  backdrop-filter: blur(8px);
  display: flex; justify-content: center; gap: 4px;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  z-index: 10;
}
/* min-width 0 and the ellipsis: five labels at the larger text size must
   share a 360px screen, and a flex item otherwise refuses to shrink below its
   label and pushes the bar off the side. */
.tabs button {
  flex: 1; max-width: 120px; background: transparent; color: var(--muted);
  font-size: calc(12px * var(--fs)); font-weight: 600; padding: 8px 4px; border-radius: 10px;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* At the larger text size "Rankings" (about 60px) is wider than the 57.6px a
   fifth of a 360px bar leaves inside the usual spacing, and ended in an
   ellipsis. The label keeps its size, since that is what the switch is for,
   and the space around the buttons gives way: 66.8px each. */
body.large-text .tabs { gap: 2px; padding-left: 4px; padding-right: 4px; }
body.large-text .tabs button { padding-left: 1px; padding-right: 1px; }
.tabs button .ico { display: block; font-size: calc(20px * var(--fs)); margin-bottom: 2px; }
.tabs button.on { color: var(--accent); background: var(--card); }

/* ---------- court card (player) ---------- */
/* Clipped: a name drawn wider than the card (a run of very wide ligatures
   has nowhere to wrap) must not lie over the page beside it. */
.court-card { text-align: center; padding: 24px 16px; overflow: hidden; }
.court-num { font-size: calc(15px * var(--fs)); color: var(--muted); text-transform: uppercase; letter-spacing: 2px; }
.court-big { font-size: calc(72px * var(--fs)); font-weight: 800; color: var(--accent); line-height: 1.1; }
/* A named court ("Championship") can't use the single-digit size. */
.court-big.named { font-size: calc(34px * var(--fs)); line-height: 1.2; overflow-wrap: anywhere; }
.vs-row { margin: 14px 0 4px; }
/* Long names wrap rather than widen the card. */
.team-line { font-size: calc(17px * var(--fs)); font-weight: 700; overflow-wrap: anywhere; }
.vs { color: var(--muted); font-size: calc(13px * var(--fs)); margin: 6px 0; text-transform: uppercase; letter-spacing: 2px; }
.you { color: var(--accent); }

.score-form { display: flex; gap: 10px; align-items: flex-end; margin-top: 16px; }
/* min-width 0 lets the two boxes give way to the Submit button on a narrow
   phone instead of pushing it past the card's edge. */
.score-form .fld { flex: 1; min-width: 0; }
.score-form label { display: block; font-size: calc(12px * var(--fs)); color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 1px; }
.score-form input { text-align: center; font-size: calc(24px * var(--fs)); font-weight: 800; }

.result-badge { display: inline-block; padding: 4px 14px; border-radius: 99px; font-weight: 800; margin-top: 12px; }
.result-badge.win { background: rgba(74, 222, 128, .15); color: var(--win); }
.result-badge.loss { background: rgba(248, 113, 113, .12); color: var(--loss); }
.result-badge.wait { background: var(--card-2); color: var(--muted); }

/* ---------- lists ---------- */
.plist { list-style: none; }
.plist li {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 0; border-bottom: 1px solid var(--border);
}
.plist li:last-child { border-bottom: none; }
.pname { flex: 1; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pname .sub { display: block; font-size: calc(12px * var(--fs)); color: var(--muted); font-weight: 400; }
.inactive .pname { color: var(--muted); text-decoration: line-through; }
/* In the game log the partner and opponents ARE the content, so long names
   wrap onto a second line instead of being cut off. */
.plist li.gamelog { align-items: flex-start; }
.plist li.gamelog .pname,
.plist li.gamelog .sub { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.plist li.gamelog > b { white-space: nowrap; }
/* Tonight's recap: the names and the score ARE the row, so they wrap rather
   than being cut off. Two long first names at 360px took the score off the end
   of "Biggest win" (428px of text in a 294px line), and with larger text the
   champion's and workhorse's records went too. */
.plist li.recap-row { align-items: flex-start; }
.plist li.recap-row .pname,
.plist li.recap-row .sub { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
/* The same for a single-line name that has to carry more than a name: the
   session name and its date above the Name button, which larger text cut. */
.pname.wrap { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
/* The moderator's club roster: a name, a sub-line ending in the DUPR status,
   and two buttons. On a 360px phone all three on one line left the sub-line
   115px, cut off before the DUPR status, and the row being edited left the
   name 0px beside the field. The buttons wrap together, right-aligned, below
   a name that would get less than 160px (on a wider screen it all still fits
   on one line), the sub-line wraps, and while editing the name takes a line
   of its own. */
.plist li.roster-row { flex-wrap: wrap; row-gap: 8px; }
.plist li.roster-row .pname { flex: 1 1 160px; }
.plist li.roster-row .sub { white-space: normal; overflow-wrap: anywhere; }
.plist li.roster-row .roster-acts { display: flex; gap: 6px; margin-left: auto; flex-shrink: 0; }
/* The field needs about 200px to show its whole "blank = clear" hint; short
   of that it takes the line and Save and Cancel drop below it, on the right. */
.plist li.roster-row.editing > input { flex: 1 1 200px; min-width: 0; }
.plist li.roster-row.editing .pname { flex-basis: 100%; }
/* The moderator's Players tab, the rows beside that card: a name, a sub-line
   carrying who is online, the sit-out count and the pairing, and three icon
   buttons. On a 360px phone a 13-character first name left that 150px of room
   for 168px of text, and with larger text 141px for 196px, which took the
   whole "sat out 0×" off the row — the one number the tab is there for. Both
   lines wrap here, as they do on the club roster card above them: an ellipsis
   on the name would hide which of two similar names a row is for, and this
   tab's rows are where the moderator pauses, pairs and renames people. */
.plist li.prow { align-items: flex-start; }
.plist li.prow .pname,
.plist li.prow .sub { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }

/* ---------- standings ---------- */
/* A name with nowhere to wrap — one long run, or very wide ligatures — used to
   widen the page instead: the phone scrolled sideways on the standings, the
   night card and the roster. Breaking between characters is the last resort
   for a "word" that does not fit; ordinary names are unaffected. A table sizes
   itself to its content, so its cells need it as well as the bold names. */
#app b, #app .pname, #app .mrow .mnames, #app .pchip, #app .ktile-name, .wchip { overflow-wrap: anywhere; }
table.standings { width: 100%; border-collapse: collapse; table-layout: auto; }
.standings td, .standings th { overflow-wrap: anywhere; min-width: 0; }
.standings th { text-align: left; font-size: calc(12px * var(--fs)); color: var(--muted); text-transform: uppercase; letter-spacing: 1px; padding: 6px 4px; }
.standings td { padding: 10px 4px; border-top: 1px solid var(--border); font-weight: 600; }
.standings td.rank { color: var(--muted); width: 34px; }
.standings tr.me td { color: var(--accent); }
.standings td.num { text-align: right; font-variant-numeric: tabular-nums; }
.diff-pos { color: var(--win); }
.diff-neg { color: var(--loss); }

/* ---------- moderator match cards ---------- */
.match-card { padding: 14px; }
.match-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.match-court { font-weight: 800; color: var(--accent); }
.match-status { font-size: calc(12px * var(--fs)); text-transform: uppercase; letter-spacing: 1px; }
.match-status.final { color: var(--win); }
.match-status.pending { color: var(--muted); }
.match-status.conflict { color: var(--danger); }
.match-status.void { color: var(--danger); }
.mscore { display: flex; gap: 8px; margin-top: 10px; align-items: center; }
.mrow { display: flex; align-items: center; gap: 10px; padding: 5px 0; }
.mrow .mnames { flex: 1; font-weight: 600; min-width: 0; }
.mrow input { width: 64px; text-align: center; font-weight: 800; padding: 8px; flex: none; }

.hintbar {
  display: block; width: 100%; text-align: left;
  background: rgba(163, 230, 53, .08);
  border: 1px solid var(--accent-dark);
  color: var(--accent);
  font-weight: 600; font-size: calc(14px * var(--fs));
  padding: 11px 14px; border-radius: 12px; margin-bottom: 12px;
}
.hintbar.go {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 700; text-align: center;
}

.stepper { display: flex; align-items: center; gap: 14px; }
.stepper button { width: 44px; height: 44px; font-size: 22px; padding: 0; border-radius: 12px; }
.stepper .val { font-size: 22px; font-weight: 800; min-width: 30px; text-align: center; }

.qr-wrap { text-align: center; }
.qr-wrap img { width: 78%; max-width: 300px; border-radius: 12px; background: #fff; padding: 8px; }
.code-big { font-size: 40px; font-weight: 800; letter-spacing: 8px; color: var(--accent); margin: 8px 0 2px; }

/* ---------- announcement banner ---------- */
.card.announce {
  border-color: var(--accent-dark);
  background: rgba(163, 230, 53, .08);
  font-size: calc(15px * var(--fs));
}

/* ---------- history ---------- */
details.hist { border-top: 1px solid var(--border); padding: 10px 0; }
details.hist summary { cursor: pointer; font-size: calc(15px * var(--fs)); list-style: none; }
details.hist summary::-webkit-details-marker { display: none; }
/* The disclosure caret is the icon set's chevron-down, drawn as a mask so it
   takes the muted colour: pointing right when closed, down when open. */
details.hist summary::before {
  content: ""; display: inline-block; width: calc(12px * var(--fs)); height: calc(12px * var(--fs));
  margin-right: 6px; vertical-align: -1px; background: var(--muted); transform: rotate(-90deg);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
}
details.hist[open] summary::before { transform: none; }
details.hist table { margin-top: 8px; }

/* ---------- confirm modal ---------- */
.modal-back {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(4, 8, 16, .7);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.modal { max-width: 340px; width: 100%; margin: 0; }

/* ---------- toast ---------- */
#toast {
  position: fixed; left: 50%; bottom: calc(84px * var(--fs)); transform: translateX(-50%);
  background: #2a1a1e; color: #ffd7d7; border: 1px solid var(--danger);
  padding: 10px 18px; border-radius: 12px; font-size: calc(14px * var(--fs)); font-weight: 600;
  z-index: 50; max-width: 90vw; text-align: center;
}
#toast.ok { background: #16281a; color: #c9f7d4; border-color: var(--win); }

.sitout-banner { text-align: center; padding: 28px 16px; }
.sitout-banner .big { font-size: 40px; }

/* ---------- big-screen spectator mode ---------- */
/* A wall display should use the whole TV, not a phone-width column. */
body.watch #app { max-width: 1800px; padding-bottom: 24px; }
.watch-head { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 14px; flex-wrap: wrap; }
.watch-head .brand { font-size: 26px; }
.wtitle { font-size: 22px; font-weight: 800; flex: 1; text-align: center; min-width: 200px; }
.wjoin { text-align: center; }
.wjoin img { width: 110px; background: #fff; border-radius: 10px; padding: 5px; display: block; }
.wjoin-code { font-weight: 800; letter-spacing: 4px; color: var(--accent); font-size: 18px; margin-top: 4px; }

.wsection { margin: 18px 2px 10px; font-size: 20px; }
.wgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
/* Nine or more courts: tighter cards so a big venue still fits one screen. */
.wgrid.many { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 10px; }
.wgrid.many .wcourt { padding: 14px 10px; }
.wgrid.many .wcourt-num { font-size: 15px; }
.wgrid.many .wteam { font-size: 19px; margin-top: 4px; column-gap: 5px; }
.wgrid.many .wteam .av { width: 22px; height: 22px; font-size: 10px; }
.wgrid.many .wvs { font-size: 13px; }
.wgrid.many .wscore { font-size: 22px; }
.wgrid.many .wstatus { font-size: 11px; }
/* A grid item is as wide as its widest word unless told otherwise: one name
   of very wide glyphs spread a court across its neighbour and off the TV.
   Clipped as well, for a single glyph wider than the card. */
.wcourt { text-align: center; padding: 26px 16px; margin-bottom: 0; min-width: 0; overflow: hidden; }
.wteam > * { min-width: 0; overflow-wrap: anywhere; }
.wcourt-num { color: var(--accent); font-weight: 800; font-size: 21px; letter-spacing: 2px; text-transform: uppercase; }
.wteam {
  font-size: 27px; font-weight: 800; line-height: 1.3; margin-top: 8px;
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; column-gap: 7px;
}
.wteam .amp { color: var(--muted); font-weight: 400; font-size: 20px; }
.wvs { color: var(--muted); font-size: 17px; margin: 4px 0; text-transform: uppercase; letter-spacing: 2px; }
.wscore { color: var(--accent); font-size: 30px; letter-spacing: 1px; }
.wstatus { margin-top: 10px; font-size: 13px; text-transform: uppercase; letter-spacing: 2px; color: var(--muted); }
.wstatus.final { color: var(--win); }
.wstatus.conflict { color: var(--danger); }

.wsit { margin: 12px 2px; font-size: 16px; color: var(--muted); }
.wchip { display: inline-block; background: var(--card-2); border: 1px solid var(--border); color: var(--text); padding: 5px 14px; border-radius: 99px; margin: 3px 4px 3px 0; font-weight: 700; font-size: 15px; }

.wnext-card { display: flex; align-items: center; gap: 12px; padding: 14px 16px; margin-bottom: 0; }
.wcourt-mini { color: var(--accent); font-weight: 800; white-space: nowrap; }
.wnext-teams {
  font-size: 18px; font-weight: 700;
  display: flex; align-items: center; flex-wrap: wrap; column-gap: 5px;
}
.wnext-teams .av { width: 24px; height: 24px; font-size: 10px; margin-right: 0; }
.wnext-teams .amp { color: var(--muted); font-weight: 400; }
.wvs-mini { color: var(--muted); font-size: 13px; text-transform: uppercase; }

.wbig { font-size: 22px; text-align: center; padding: 18px; }
.wboard td { font-size: 18px; padding: 8px 6px; }
.wtv-link { display: block; margin-top: 10px; color: var(--muted); font-size: 13px; text-align: center; text-decoration: none; }
.wtv-link:hover { color: var(--accent); }

/* ---------- editable next-round chips ---------- */
.pchip {
  display: inline-block; background: var(--card-2); border: 1px solid var(--border);
  color: var(--text); padding: 5px 12px; border-radius: 99px;
  margin: 2px 3px 2px 0; font-weight: 600; font-size: calc(13px * var(--fs));
}
.pchip.sel { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.pchip.next { border-color: var(--accent); color: var(--accent); }
.wchip.next { border-color: var(--accent); color: var(--accent); font-weight: 800; }
.pchip.sel .lvl { filter: brightness(0.4); }

/* ---------- tap-to-score chips ---------- */
.nprow { display: flex; align-items: center; flex-wrap: wrap; gap: 3px; margin-top: 8px; }
/* The chips grow with their numbers, and the row wraps, so sixteen of them
   still fit a 360px card at the larger size. */
.nplab { font-size: calc(11px * var(--fs)); color: var(--muted); font-weight: 700; width: calc(34px * var(--fs)); text-transform: uppercase; }
.numchip {
  min-width: calc(30px * var(--fs)); height: calc(30px * var(--fs)); padding: 0 4px; border-radius: 8px;
  background: var(--card-2); border: 1px solid var(--border); color: var(--text);
  font-weight: 700; font-size: calc(13px * var(--fs));
}
.numchip.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }

/* ---------- game clock ---------- */
.mclock { font-size: calc(13px * var(--fs)); font-weight: 800; color: var(--accent); margin-left: 6px; white-space: nowrap; }
.mclock.over { color: var(--danger); animation: pulse 1.2s infinite; }
/* Time on court with no game clock: quiet until a game has gone on long enough to look at. */
.mclock.elapsed { color: var(--muted); font-weight: 700; }
.mclock.elapsed.long { color: #fbbf24; }
.mscore.usereport { flex-wrap: wrap; }
.court-card .mclock { display: block; margin: 2px 0 4px; font-size: calc(16px * var(--fs)); }
@keyframes pulse { 50% { opacity: 0.45; } }

/* ---------- moderator game clock card ---------- */
.tclocks { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tclock {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--card-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 8px 14px; min-width: 76px; color: var(--text);
}
.tcourt { font-size: 11px; font-weight: 800; color: var(--muted); text-transform: uppercase; }
.tface { font-size: 24px; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; }
.tclock.over .tface { color: var(--danger); animation: pulse 1.2s infinite; }
.tclock.idle .tface { color: var(--muted); font-size: 16px; padding: 4px 0; }
.tclock.go .tface { color: var(--warn, #fbbf24); animation: pulse 0.6s infinite; }
.mclock.go { color: #fbbf24; font-weight: 800; animation: pulse 0.6s infinite; }

/* ---------- skill level badges (ranked open play) ---------- */
.lvl {
  display: inline-block; min-width: 26px; text-align: center; padding: 1px 6px;
  border-radius: 8px; font-size: calc(11.5px * var(--fs)); font-weight: 800; vertical-align: 1px;
}
.lvl.l1 { background: #64748b33; color: #94a3b8; }
.lvl.l2 { background: #22c55e2e; color: #4ade80; }
.lvl.l3 { background: #38bdf82e; color: #38bdf8; }
.lvl.l4 { background: #a78bfa2e; color: #a78bfa; }
.lvl.l5 { background: #f59e0b2e; color: #fbbf24; }
.lvl.ur { background: #64748b26; color: #8b98ab; border: 1px dashed #64748b66; }

/* ---------- colored initial avatars ---------- */
.av {
  display: inline-flex; width: 22px; height: 22px; border-radius: 50%;
  align-items: center; justify-content: center; font-size: 10px; font-weight: 800;
  color: #fff; margin-right: 6px; vertical-align: middle; margin-top: -3px; flex: none;
}
.wteam .av { width: 30px; height: 30px; font-size: 13px; margin-right: 0; }
.wboard .av { width: 24px; height: 24px; font-size: 11px; vertical-align: middle; margin-top: -4px; }
.pchip .av { width: 18px; height: 18px; font-size: 9px; margin-right: 4px; vertical-align: middle; margin-top: -2px; }

/* ---------- kiosk: shared venue touchscreen ---------- */
body.kiosk #app { max-width: 900px; }
.chip.mkiosk { background: #38bdf822; color: #38bdf8; border: 1px solid #38bdf855; font-weight: 800; }
.kbtn {
  display: block; width: 100%; padding: 20px; margin-top: 12px; border-radius: 16px;
  background: var(--card-2); border: 2px solid var(--border); color: var(--text);
  font-size: 20px; font-weight: 800;
}
.kbtn.primary { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.kbtn.ghost { background: transparent; font-size: 17px; }
.kinput {
  display: block; width: 100%; margin-top: 10px; padding: 16px; border-radius: 14px;
  font-size: 19px; background: var(--card-2); border: 2px solid var(--border); color: var(--text);
}
.kcourt {
  display: block; width: 100%; text-align: left; padding: 18px; margin-top: 12px;
  border-radius: 16px; background: var(--card-2); border: 2px solid var(--border); color: var(--text);
}
.kcourt-num { font-size: 15px; font-weight: 800; color: var(--accent); letter-spacing: 1px; }
.kcourt-teams { font-size: 21px; font-weight: 700; margin: 6px 0; }
.kcourt-cta { font-size: 14px; color: var(--muted); font-weight: 700; }
.kteam { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* The two names of a team, beside the score box: they wrap between characters
   rather than push the score off the screen. */
.kteam-name { font-size: 21px; font-weight: 800; min-width: 0; overflow-wrap: anywhere; }
.kscore { font-size: 34px; font-weight: 800; color: var(--accent); min-width: 56px; text-align: right; }
.knums { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.knum {
  width: 54px; height: 54px; border-radius: 12px; font-size: 19px; font-weight: 800;
  background: var(--card); border: 2px solid var(--border); color: var(--text);
}
.knum.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.krow-static { font-size: 18px; margin: 6px 0; }
/* ---------- first-run setup ---------- */
.setup-q { font-weight: 700; margin: 16px 0 8px; }
.setup-row { display: flex; flex-wrap: wrap; gap: 8px; }
.setup-row.col { flex-direction: column; }
.setup-opt {
  flex: 1 1 auto; min-width: 64px; padding: 14px 12px; border-radius: 12px;
  background: var(--card-2); border: 2px solid var(--border); color: var(--text);
  font-size: 16px; font-weight: 700; text-align: left;
}
.setup-row:not(.col) .setup-opt { text-align: center; }
.setup-opt.on { border-color: var(--accent); background: var(--accent); color: var(--accent-text); }
.setup-opt .setup-sub { display: block; font-size: 12px; font-weight: 600; opacity: 0.8; margin-top: 3px; }

/* ---------- per-player overflow row ---------- */
.plist li.rowmenu { background: var(--card-2); border-radius: 10px; gap: 6px; flex-wrap: wrap; }

/* ---------- player help sheet ---------- */
.helpsheet {
  max-height: 82vh; overflow-y: auto; overflow-x: hidden;
  text-align: left; width: 100%; max-width: 520px;
}
.help-body { margin-top: 4px; }
.help-item { padding: 12px 0; border-bottom: 1px solid var(--border); }
.help-item:last-child { border-bottom: none; }
/* Everything wraps — no sideways scrolling to read an answer. */
.help-q, .help-a {
  white-space: normal; overflow: visible; text-overflow: clip;
  overflow-wrap: anywhere; word-break: break-word; max-width: 100%;
}
.help-q { font-size: calc(16px * var(--fs)); font-weight: 700; margin: 0 0 4px; }
.help-a { font-size: calc(14px * var(--fs)); line-height: 1.55; color: var(--muted); margin: 0; }
.ladder { display: block; margin: 10px 0; }
.ladder-row {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; margin-bottom: 5px;
  background: var(--card-2); border-radius: 10px; font-size: calc(14px * var(--fs)); color: var(--text);
}
.ladder-row b { margin-left: auto; color: var(--muted); font-size: calc(13px * var(--fs)); white-space: nowrap; }

.kcount { font-size: 15px; font-weight: 700; color: var(--muted); margin: 2px 2px 10px; }
.kwin { font-size: 26px; font-weight: 800; margin-top: 10px; }
.kwin-score { font-size: 46px; font-weight: 800; color: var(--accent); margin: 4px 0 2px; }
.kundo {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 12px; border: 2px solid #4ade8055; font-size: 17px; font-weight: 700;
}
/* The saved line names the winners: a name with nowhere to wrap (a run of very
   wide ligatures) pushed the whole kiosk screen sideways. */
.kundo-line { min-width: 0; overflow-wrap: anywhere; }
.kfind-status { font-size: 21px; font-weight: 700; margin: 10px 0 6px; }
.kattract { text-align: center; font-size: 18px; font-weight: 800; color: var(--accent); margin-top: 16px; animation: pulse 2s infinite; }
.wcount { font-size: 17px; font-weight: 700; color: var(--muted); margin-top: 4px; }

/* name tiles + A-Z filter + PIN keypad */
.ktiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
.ktile {
  display: flex; align-items: center; gap: 10px; padding: 16px 14px; border-radius: 14px;
  background: var(--card-2); border: 2px solid var(--border); color: var(--text);
  font-size: 19px; font-weight: 700; text-align: left;
}
.ktile .av { width: 30px; height: 30px; font-size: 13px; }
.ktile-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kletters { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.kletter {
  min-width: 44px; height: 44px; border-radius: 10px; font-size: 16px; font-weight: 800;
  background: var(--card-2); border: 2px solid var(--border); color: var(--text);
}
.kletter.on { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
.kdots { display: flex; justify-content: center; gap: 14px; margin: 18px 0; }
.kdot { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--border); background: transparent; }
.kdot.on { background: var(--accent); border-color: var(--accent); }
.kkeys { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 330px; margin: 0 auto; }
.kkey {
  height: 68px; border-radius: 14px; font-size: 26px; font-weight: 800;
  background: var(--card-2); border: 2px solid var(--border); color: var(--text);
}
.kkey.ghost { font-size: 17px; color: var(--muted); }
body.kiosk .kteam.kb { margin-top: 14px; }
body.kiosk .kbtn.kcheck { margin-top: 16px; }

.kqr { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-top: 12px; }
.kqr img { width: 150px; height: 150px; border-radius: 12px; background: #fff; padding: 8px; flex: none; }
.kqr-code { font-size: 32px; font-weight: 800; letter-spacing: 6px; color: var(--accent); margin: 8px 0 4px; }

/* ---------- icon color swatches ---------- */
.swatch {
  display: inline-flex; width: 30px; height: 30px; border-radius: 50%;
  align-items: center; justify-content: center; margin: 2px 5px 2px 0;
  border: 2px solid transparent; color: #fff; font-size: 11px; font-weight: 800;
}
.swatch.sel { border-color: var(--text); box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent); }

/* ---------- ranked achievement badges ---------- */
.badge {
  display: inline-block; background: var(--card-2); border: 1px solid var(--border);
  color: var(--text); padding: 3px 10px; border-radius: 99px;
  font-size: calc(12px * var(--fs)); font-weight: 700; margin: 2px 4px 2px 0;
}

/* ---------- level-up confetti ---------- */
.confetti { position: fixed; inset: 0; pointer-events: none; z-index: 999; overflow: hidden; }
.confetti i {
  position: absolute; top: -24px; width: 10px; height: 16px; border-radius: 2px;
  animation: confall linear forwards;
}
@keyframes confall { to { transform: translateY(110vh) rotate(760deg); } }
.confetti-msg {
  position: absolute; top: 34%; left: 50%; transform: translateX(-50%);
  background: var(--card); border: 2px solid var(--accent); color: var(--text);
  padding: 16px 26px; border-radius: 16px; font-size: 20px; font-weight: 800;
  white-space: nowrap; animation: pulse 1s 2;
}

/* ---------- spectator leaderboard climb flash ---------- */
.wboard tr.rose td { animation: rosebg 3.5s ease-out; }
@keyframes rosebg { 0%, 40% { background: #4ade8028; } 100% { background: transparent; } }

/* ---------- spectator: fresh court call (new group seated) ---------- */
.wcourt.wfresh { border: 2px solid #a3e635; animation: freshpulse 1.1s infinite; }
.wcalled { color: #a3e635; font-weight: 800; font-size: 13px; letter-spacing: 2px; margin-bottom: 4px; }
@keyframes freshpulse {
  50% { box-shadow: 0 0 26px #a3e63555; border-color: #d9f99d; }
}

/* ---------- spectator: open court indicator ---------- */
.wcourt.wopen { border: 2px dashed #4ade8066; }
.wopen-label { color: #4ade80; font-weight: 800; letter-spacing: 4px; animation: pulse 1.6s infinite; }
.wstatus.openfree { color: #4ade80; }

/* ---------- open play vs rounds game tags ---------- */
.modetag {
  display: inline-block; padding: 1px 8px; border-radius: 8px;
  font-size: calc(11px * var(--fs)); font-weight: 800; vertical-align: 1px; white-space: nowrap;
}
.modetag.open { background: #38bdf82e; color: #38bdf8; }
.modetag.rd { background: #a78bfa2e; color: #a78bfa; }
.modetag.ranked { background: #f59e0b2e; color: #fbbf24; }

/* ---------- topbar mode chips ---------- */
.chip.mranked { background: #f59e0b22; color: #fbbf24; border: 1px solid #f59e0b55; font-weight: 800; }
.chip.mopen { background: #38bdf822; color: #38bdf8; border: 1px solid #38bdf855; font-weight: 800; }

/* ---------- big, obvious take-a-break button ---------- */
.breakbtn {
  background: var(--card-2); border: 2px solid #fbbf2466; color: var(--text);
  font-size: calc(17px * var(--fs)); font-weight: 800; padding: 16px 18px; border-radius: 14px;
}
.breakbtn .sub2 { display: block; font-size: calc(12px * var(--fs)); font-weight: 600; color: var(--muted); margin-top: 3px; }

/* =========================================================================
   Kiosk on a tablet, portrait and landscape
   =========================================================================
   Every rule below is under body.kiosk (a kiosk screen) or body.kiosk-out (a
   kiosk screen just signed out), so phones and the big screen never see them.
   Pure CSS: rotating reflows the page and never redraws it, so a half-typed
   PIN, a chosen letter or a picked score all stay put.

   The layouts are chosen by shape, not by the orientation media feature. An
   on-screen keyboard that shrinks a portrait tablet to 768x700 makes it
   "landscape" to that feature, and the page would jump to the side-by-side
   layout under the player's fingers. By aspect ratio the same screen is still
   tall: tall is up to 1.3:1 and at least 600px wide, wide is from 1.3:1 and at
   least 900px wide. A narrower kiosk keeps the single column it always had. */

/* Any kiosk. Names wrap to a second line rather than lose their ending. */
body.kiosk .ktile-name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: break-word; min-width: 0; }
/* The foot of a kiosk screen holds its buttons and, when typing, the
   keyboard: messages drop in from the top instead. */
body.kiosk #toast, body.kiosk-out #toast { top: 16px; bottom: auto; }
/* Finger-sized on a shared screen (the wrap-up's filters, Undo). Close kiosk
   mode stays small on purpose, so nobody taps it by accident. */
body.kiosk button.sm:not([data-action="leave"]) { min-height: 48px; padding-left: 16px; padding-right: 16px; }
body.kiosk .kundo { flex-wrap: wrap; }
/* Undo is how a mis-entered score is taken back: outlined in the saved green
   so a player finds it, not a grey ghost. */
body.kiosk .kundo button.sm { white-space: nowrap; margin-left: auto; color: var(--win); border: 2px solid var(--win); font-size: 16px; font-weight: 800; }
/* A field scrolled into view (focus, a keyboard opening) brings the button
   under it along. */
body.kiosk .kinput { scroll-margin-bottom: 110px; }
/* Attract: the nudge stays pinned to the foot however long the board is, on
   a solid bar with an edge, so a board longer than the screen runs under a
   footer rather than fading out half-drawn. The board's end clears the bar. */
body.kiosk .kattract {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; margin: 0; animation: none;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  background: var(--bg); border-top: 1px solid var(--border);
}
body.kiosk .kattract-text { display: inline-block; animation: pulse 2s infinite; }
body.kiosk .kidle { padding-bottom: 72px; }
/* The big screen's "Live view · exit" line means nothing on a kiosk. */
body.kiosk .kidle > p.muted.small { display: none; }
/* Signed out: the setup is open and within reach, in a tablet-width column. */
body.kiosk-out #app { max-width: 640px; }
body.kiosk-out details.hist summary { padding: 13px 0; }

/* ---- any tablet: tall or wide ---- */
@media (min-width: 600px) and (max-aspect-ratio: 1299/1000), (min-width: 900px) and (min-aspect-ratio: 13/10) {
  body.kiosk #app { max-width: 1400px; padding-bottom: 28px; }

  /* Courts two abreast, each team on its own line. */
  body.kiosk .kcourt-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  body.kiosk .kcourt-grid > :only-child { grid-column: 1 / -1; }
  body.kiosk .kcourt-grid .kcourt { margin: 0; }
  body.kiosk .kcourt-grid .kct { display: block; }
  body.kiosk .kcourt-grid .kvs { display: block; font-size: 13px; font-weight: 800; letter-spacing: 2px; text-transform: uppercase; margin: 1px 0; }

  /* The step's way out stays on screen at the foot of a long list. */
  body.kiosk .kfoot {
    position: sticky; bottom: 0; z-index: 5;
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;
    margin-top: -2px; padding: 14px 0 calc(12px + env(safe-area-inset-bottom));
    background: linear-gradient(to bottom, rgba(11, 18, 32, 0), var(--bg) 14px);
  }
  body.kiosk .kfoot .kbtn { margin: 0; }
  body.kiosk .kfoot [data-action="kiosk-home"] { order: -1; }

  /* Name tiles grow with the screen's height; letters are a full grid of
     finger-sized keys. A tile's narrowest width grows with its name's type
     (18px type at 180px, 23px at 220px), so a long first name (Maximilianna,
     Christabelle) fits whole on a big screen too rather than breaking
     mid-word. */
  body.kiosk .ktiles {
    grid-template-columns: repeat(auto-fill, minmax(clamp(180px, calc(20vh - 36px), 220px), 1fr));
    grid-template-columns: repeat(auto-fill, minmax(clamp(180px, calc(20dvh - 36px), 220px), 1fr));
  }
  body.kiosk .ktile {
    min-height: clamp(64px, 5vh, 84px); min-height: clamp(64px, 5dvh, 84px);
    font-size: clamp(18px, 1.7vh, 23px); font-size: clamp(18px, 1.7dvh, 23px);
  }
  /* Slimmer padding for the same reason. A name longer than any tile breaks
     at the tile's edge (hyphenated only where the tablet has a dictionary). */
  body.kiosk .ktile { padding: 14px 10px; gap: 8px; }
  body.kiosk .ktile-name { hyphens: auto; }
  body.kiosk .kletters { display: grid; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); gap: 6px; }
  body.kiosk .kletter { min-width: 0; height: 50px; font-size: 18px; }

  /* Attract: the logo, the title and the QR stay on one row. */
  body.kiosk .kidle .watch-head { flex-wrap: nowrap; }
  body.kiosk .kidle .watch-head > div:nth-child(2) { flex: 1; min-width: 0; }

  /* Instructions are read at arm's length: the hints, the head count, the
     court's call to action (a step brighter too) and the line's chips are a
     size up from a phone-width kiosk. */
  body.kiosk .muted.small { font-size: 16px; }
  body.kiosk .kcount { font-size: 17px; }
  body.kiosk .kcourt-cta { font-size: 16px; color: #aab6c6; }
  body.kiosk .khome .pchip { font-size: 15px; }
}

/* ---- tall: portrait tablets and portrait screens ---- */
@media (min-width: 600px) and (max-aspect-ratio: 1299/1000) {
  /* Home: courts, then the two buttons side by side, then the QR, then the
     line. The groups' boxes step aside (display: contents) so their cards
     can be ordered as one column. */
  body.kiosk .khome { display: flex; flex-direction: column; gap: 12px; }
  body.kiosk .khome-main, body.kiosk .khome-side { display: contents; }
  body.kiosk .khome .card, body.kiosk .khome .kbtn { margin: 0; }
  body.kiosk .kcourts { order: 1; }
  body.kiosk .kactions { order: 2; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  body.kiosk .kqr { order: 3; }
  body.kiosk .knext { order: 4; }
  body.kiosk .kqr img {
    width: clamp(150px, 16vh, 220px); height: clamp(150px, 16vh, 220px);
    width: clamp(150px, 16dvh, 220px); height: clamp(150px, 16dvh, 220px);
  }

  /* PIN keypad sized to the hand: keys grow with the screen's height. */
  body.kiosk .kkeys { gap: 12px; max-width: clamp(330px, 42vh, 760px); max-width: clamp(330px, 42dvh, 760px); }
  body.kiosk .kkey {
    height: clamp(68px, 9vh, 160px); height: clamp(68px, 9dvh, 160px);
    font-size: clamp(26px, 3vh, 48px); font-size: clamp(26px, 3dvh, 48px);
  }
  /* The word keys (clear, name) stay labels, smaller than the digits; the
     backspace glyph grows with its key instead (icon() writes its size
     inline, hence !important). */
  body.kiosk .kkey.ghost { font-size: clamp(17px, 1.8vh, 22px); font-size: clamp(17px, 1.8dvh, 22px); }
  body.kiosk .kkey.ghost .i {
    width: clamp(26px, 3vh, 40px) !important; height: clamp(26px, 3vh, 40px) !important;
    width: clamp(26px, 3dvh, 40px) !important; height: clamp(26px, 3dvh, 40px) !important;
  }
  body.kiosk .kdots { gap: 18px; margin: 22px 0; }
  body.kiosk .kdot { width: 22px; height: 22px; }

  /* Score numbers: an even grid, taller on a taller screen. */
  body.kiosk .knums { display: grid; grid-template-columns: repeat(auto-fill, minmax(54px, 1fr)); gap: 8px; }
  body.kiosk .knum { width: auto; height: clamp(54px, 5.6vh, 76px); height: clamp(54px, 5.6dvh, 76px); font-size: 20px; }

  /* Attract: courts two abreast on any tall screen (three for a big venue),
     so four courts make a square rather than three and one. */
  body.kiosk .kidle .wgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  body.kiosk .kidle .wgrid.many { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  body.kiosk .kidle .wcourt { padding: 18px 12px; }
  body.kiosk .kidle .wteam { font-size: 24px; }
  body.kiosk .kidle .wtitle { min-width: 0; }
}

/* ---- tall and high (a 12.9" tablet or a portrait screen): bigger type, and
   a short step in the middle of the screen rather than stranded at its top.
   After the tall block, so where both size the same thing this one wins. ---- */
@media (min-width: 600px) and (max-aspect-ratio: 1299/1000) and (min-height: 1300px) {
  body.kiosk .kbtn { padding: 26px 20px; font-size: 23px; }
  body.kiosk .kcourt { padding: 24px; }
  body.kiosk .kcourt-num { font-size: 18px; }
  body.kiosk .kcourt-teams { font-size: 26px; }
  body.kiosk .kcourt-cta { font-size: 18px; }
  body.kiosk .card h2 { font-size: 23px; }
  body.kiosk .kinput { padding: 22px; font-size: 22px; }
  body.kiosk .kqr-code { font-size: 40px; }
  body.kiosk .khome .pchip { font-size: 17px; padding: 8px 14px; }
  body.kiosk .muted.small { font-size: 18px; }
  body.kiosk .kcount { font-size: 19px; }

  /* A short step (a PIN, the score, "Is this right?", Find me's
     answer) sits centred in the space under the header, its way out right
     under it. A browser without :has() leaves it at the top. */
  body.kiosk #app:has(> .kpincard), body.kiosk #app:has(> .kscorecard), body.kiosk #app:has(> .kstep) {
    display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh;
  }
  body.kiosk #app > .kpincard, body.kiosk #app > .kscorecard, body.kiosk #app > .kstep { margin-top: auto; }
  body.kiosk #app:has(> .kpincard) > :last-child, body.kiosk #app:has(> .kscorecard) > :last-child, body.kiosk #app:has(> .kstep) > :last-child { margin-bottom: auto; }

  /* Score: eight to a row, so the keys grow instead of the rows lengthening. */
  body.kiosk .knums { grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 10px; }
  body.kiosk .knum { height: clamp(76px, 6vh, 116px); height: clamp(76px, 6dvh, 116px); font-size: 26px; }

  /* Find me's answer and the score check, in type to read from a step back. */
  body.kiosk .kstep > h2 { font-size: 30px; }
  body.kiosk .kfind-status { font-size: 34px; margin: 18px 0 12px; }
  body.kiosk .kwin { font-size: 34px; }
  body.kiosk .kwin-score { font-size: 64px; }
}

/* ---- wide: landscape tablets ---- */
@media (min-width: 900px) and (min-aspect-ratio: 13/10) {
  /* The header and the head count share one row. */
  body.kiosk .khead { display: flex; align-items: center; gap: 8px 18px; margin-bottom: 12px; }
  body.kiosk .khead > .topbar { display: contents; }
  body.kiosk .khead .brand { order: 0; }
  body.kiosk .khead .kcount { order: 1; flex: 1; margin: 0; text-align: center; }
  body.kiosk .khead .topbar > .row { order: 2; }

  /* Home: courts and the line on the left; Undo, the two buttons and the QR
     on the right, held in view while the courts scroll. */
  body.kiosk .khome { display: grid; grid-template-columns: minmax(0, 3fr) minmax(320px, 2fr); gap: 16px; align-items: start; }
  body.kiosk .khome-side { position: sticky; top: 12px; }
  body.kiosk .khome-side > .kundo:first-child { margin-top: 0; }
  body.kiosk .khome-side > .kactions:first-child > .kbtn:first-child { margin-top: 0; }

  /* Name list: the heading and its hint on one line. */
  body.kiosk .kjoin > h2 { display: inline-block; margin-right: 14px; }
  body.kiosk .kjoin > h2 + p { display: inline-block; }

  /* PIN: what to do on the left, centred as one block; the keypad on the right. */
  body.kiosk .kpincard { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: 1fr auto auto auto 1fr; column-gap: 36px; align-items: center; }
  body.kiosk .kpincard > h2 { grid-column: 1; grid-row: 2; font-size: 24px; }
  body.kiosk .kpincard > p { grid-column: 1; grid-row: 3; font-size: 15px; }
  body.kiosk .kpincard > .kdots { grid-column: 1; grid-row: 4; }
  body.kiosk .kpincard > .kkeys {
    grid-column: 2; grid-row: 1 / -1; margin: 0; gap: 10px; max-width: none;
    width: clamp(300px, 46vh, 440px); width: clamp(300px, 46dvh, 440px);
  }
  body.kiosk .kpincard .kkey { height: clamp(64px, 11vh, 100px); height: clamp(64px, 11dvh, 100px); }

  /* Score: the two teams side by side, six keys to a row so the keys grow
     rather than the rows lengthening, as tall as the screen allows. A slip
     here (12 for 11) goes into the standings. */
  body.kiosk .kscorecard { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 28px; align-items: start; }
  body.kiosk .kscorecard > h2, body.kiosk .kscorecard > .kcheck { grid-column: 1 / -1; }
  body.kiosk .kscorecard > .ka { grid-column: 1; }
  body.kiosk .kscorecard > .kb { grid-column: 2; }
  body.kiosk .kscorecard > .kteam { grid-row: 2; margin-top: 0; }
  body.kiosk .kscorecard > .knums { grid-row: 3; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; }
  body.kiosk .kscorecard .knum { width: auto; height: clamp(52px, 9vh, 84px); height: clamp(52px, 9dvh, 84px); font-size: 21px; }

  /* Registration: the four fields two by two, so the form clears a keyboard. */
  body.kiosk .kfields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; }

  /* Wrap-up: the recap beside the standings. */
  body.kiosk .kwrap { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; align-items: start; }

  /* Attract: the courts and the line beside the leaderboard. */
  body.kiosk .kidle { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(280px, 1fr); column-gap: 18px; align-items: start; }
  body.kiosk .kidle > * { grid-column: 1 / -1; }
  body.kiosk .kidle > .wmain { grid-column: 1; }
  body.kiosk .kidle > .wside { grid-column: 2; }
  body.kiosk .kidle .wgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  body.kiosk .kidle .wgrid.many { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  body.kiosk .kidle .wcourt { padding: 14px 12px; }
  body.kiosk .kidle .wteam { font-size: 23px; }

  /* Signed out: the join card right under the logo (the moderator card's
     extra height goes to the second row, not between them), the moderator
     card beside. A message drops in over the logo's column, not over the
     moderator card it is pointing to. */
  body.kiosk-out #app { max-width: 1100px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: auto 1fr; column-gap: 16px; align-items: start; }
  body.kiosk-out .hero { padding: 20px 0 16px; }
  body.kiosk-out #app > .card:nth-child(3) { grid-column: 2; grid-row: 1 / span 2; }
  body.kiosk-out #toast { left: calc(50% - min(100vw, 1100px) / 4 + 4px); max-width: calc((min(100vw, 1100px) - 48px) / 2); }
}

/* ---- wide and short (a 962x601 tablet): a little less padding ---- */
@media (min-width: 900px) and (min-aspect-ratio: 13/10) and (max-height: 700px) {
  body.kiosk #app { padding-top: 10px; }
  body.kiosk .kbtn { padding: 14px 18px; font-size: 19px; }
  /* Home: the line moves to the right column under a slimmer QR card, so a
     player's place in line is on screen without a scroll. The column gives
     up being sticky, which a screen this short barely scrolls for. */
  body.kiosk .khome { grid-template-rows: repeat(4, auto) 1fr; row-gap: 12px; }
  body.kiosk .khome-main, body.kiosk .khome-side { display: contents; }
  body.kiosk .khome .card { margin: 0; }
  body.kiosk .khome .kactions > .kbtn:first-child { margin-top: 0; }
  body.kiosk .kcourts { grid-column: 1; grid-row: 1 / -1; }
  body.kiosk .kundo, body.kiosk .kactions, body.kiosk .kqr, body.kiosk .knext { grid-column: 2; }
  body.kiosk .kundo { order: 1; }
  body.kiosk .kactions { order: 2; }
  body.kiosk .kqr { order: 3; }
  body.kiosk .knext { order: 4; }
  body.kiosk .kqr h2 + p { display: none; }
  body.kiosk .kqr img { width: 120px; height: 120px; }
  body.kiosk .kidle .watch-head { margin-bottom: 6px; }
  /* Attract: only the courts and the leaderboard, so no half-drawn row of
     chips (sitting out, the line) sits at the bar. Home shows the line. */
  body.kiosk .kidle .wmain > .wgrid ~ * { display: none; }
  body.kiosk .kidle .wjoin img { width: 84px; }
  body.kiosk .kidle .wsection { margin: 6px 2px 6px; font-size: 17px; }
  body.kiosk .kidle .wcourt { padding: 8px 10px; }
  body.kiosk .kidle .wcourt-num { font-size: 16px; }
  body.kiosk .kidle .wteam { font-size: 19px; margin-top: 4px; }
  body.kiosk .kidle .wteam .av { width: 24px; height: 24px; font-size: 10px; }
  body.kiosk .kidle .wvs { margin: 0; font-size: 13px; }
}

/* ---- an on-screen keyboard is up (or a very short screen) ---- */
/* A pinned foot would sit over the form's own button: let it scroll. */
@media (max-height: 520px) {
  body.kiosk .kfoot { position: static; }
}
