/* MMToC — game-show energy wearing IMC web colors.
   Palette anchored on @imc-trading/mui-theme: darkBlue #03225f, blue #11409c,
   secondaryBlue (action) #0651e2, grayBlue #ddeaff; BUY = success green,
   SELL = error red (house trading convention). System font stacks only. */

:root {
  /* Native controls (checkboxes, file inputs, selects, scrollbars) render light-mode without this,
     so every unchecked checkbox was a white square on a navy card. */
  color-scheme: dark;
  --bg0: #011124;
  --bg1: #012649;
  --bg2: #03225f;
  --ink: #f0f6ff;
  --muted: #9db9e4;
  --gray-blue: #ddeaff;
  --blue: #11409c;
  --action: #0651e2;
  --accent: #8daff2;
  --gold: #fbbf24;
  /* Medals: silver is the coin's own mid-tone, named; bronze the one colour the palette lacked. */
  --silver: #b0aaf0;
  --bronze: #cd7f32;
  --warn: #ffa726;
  --buy: #66bb6a;
  --buy-deep: #2e7d32;
  --sell: #f44336;
  --sell-deep: #d32f2f;
  --card: rgba(221, 234, 255, 0.05);
  --card-border: rgba(221, 234, 255, 0.16);
  --line: rgba(157, 185, 228, 0.4);
  --bgap: clamp(28px, 4vw, 72px);
  --radius: 8px;
  --font-ui: Tahoma, Verdana, sans-serif;
  --font-display: "Oswald", "MarkPro", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
  font-family: var(--font-ui);
  color: var(--ink);
  background: linear-gradient(160deg, var(--bg0), var(--bg1) 55%, var(--bg2));
  background-size: 200% 200%;
  animation: bgshift 30s ease-in-out infinite alternate;
  overflow: hidden;
}

/* Brand moments get the display stack (Oswald/MarkPro where installed). */
.logo, .tagline, .tname, .round-name, .vs, .question, .mkt-count,
.q-value, .a-value, .winner-banner, .splash-title, .champ-name, .champ-label,
.new-panel h2, .recap-panel h2 {
  font-family: var(--font-display);
  letter-spacing: 0.04em;
}

body::before, body::after {
  content: "";
  position: fixed;
  width: 60vmax;
  height: 60vmax;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.28;
  pointer-events: none;
  z-index: 0;
}
body::before {
  background: radial-gradient(circle, #11409c, transparent 60%);
  top: -22vmax;
  left: -16vmax;
  animation: drift1 26s ease-in-out infinite alternate;
}
body::after {
  background: radial-gradient(circle, #0082ba, transparent 60%);
  bottom: -26vmax;
  right: -16vmax;
  animation: drift2 32s ease-in-out infinite alternate;
}

@keyframes bgshift { from { background-position: 0% 0%; } to { background-position: 100% 100%; } }
@keyframes drift1 { to { transform: translate(9vmax, 7vmax) scale(1.15); } }
@keyframes drift2 { to { transform: translate(-8vmax, -6vmax) scale(1.1); } }

#app { position: relative; z-index: 1; height: 100vh; display: flex; flex-direction: column; }
/* No bottom reserve for `#hints`: the strip is a flex item in this column, so it takes its own room
   and gives it back the moment fullscreen hides it. A guessed reserve here is dead space no page can
   use — `.tview` is `height: 100%` of the *content* box, so a `padding-bottom: 3rem` ended the
   bracket (and its horizontal scrollbar) 48px above the viewport whatever the 36px strip measured.
   Scrolling pages get their breathing room from `.stack`'s own bottom padding. */
#view { flex: 1; overflow: auto; min-height: 0; }

button, input, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
input[type="checkbox"] { accent-color: var(--action); width: 1.15rem; height: 1.15rem; cursor: pointer; }

kbd {
  display: inline-block;
  padding: 0.1em 0.5em;
  border-radius: 4px;
  background: rgba(221, 234, 255, 0.1);
  border: 1px solid rgba(221, 234, 255, 0.25);
  border-bottom-width: 2px;
  font-size: 0.82em;
  font-weight: 600;
}

/* ---------------------------------------------------------------- chrome */

/* In flow at the foot of `#app`, not fixed: it sits at the bottom of a 100vh column either way, so
   the geometry is unchanged, but the page above it no longer has to reserve a guessed height — and
   in fullscreen, where the strip is hidden, the room comes back on its own. Still `z-index: 60` over
   the overlay's 50, which is why it stays positioned. */
#hints {
  position: relative;
  flex: none;
  z-index: 60;
  display: flex;
  justify-content: center;
  gap: 1.6rem;
  padding: 0.45rem 1rem;
  font-size: clamp(0.72rem, 1.1vw, 0.95rem);
  color: var(--muted);
  background: rgba(1, 17, 36, 0.8);
  backdrop-filter: blur(8px);
  border-top: 1px solid rgba(221, 234, 255, 0.08);
}
.hint { display: inline-flex; align-items: center; gap: 0.45em; white-space: nowrap; }

#toast {
  position: fixed;
  top: 1.2rem; left: 50%;
  transform: translate(-50%, -180%);
  z-index: 70;
  max-width: min(90vw, 640px);
  padding: 0.75rem 1.4rem;
  border-radius: 6px;
  background: linear-gradient(135deg, var(--sell), var(--sell-deep));
  color: #fff;
  font-weight: 600;
  box-shadow: 0 10px 30px rgba(211, 47, 47, 0.4);
  transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.2);
  pointer-events: none;
}
#toast.show { transform: translate(-50%, 0); }

/* Confirmations should not look like failures. */
#toast.ok {
  background: linear-gradient(135deg, var(--buy), var(--buy-deep));
  box-shadow: 0 10px 30px rgba(46, 125, 50, 0.4);
}

/* Parked off-screen it still casts its shadow down into the top bar, which put a red halo across
   the top of every page. Must stay after #toast.ok — equal specificity. */
#toast:not(.show) { box-shadow: none; }

#confetti { position: fixed; inset: 0; z-index: 80; pointer-events: none; }

.loading, .empty { color: var(--muted); text-align: center; padding: 3rem 1rem; font-size: 1.1rem; }

/* Secondary text, used in five modules. The only .dim rule used to be `.spectator-list .dim`, so
   every other one — "owned by…", the share verbs, "You have read-only access." — rendered at full
   brightness. Scope overrides to their own selector, never by narrowing this base. */
.dim { color: var(--muted); font-size: 0.84rem; }

/* ---------------------------------------------------------------- buttons */

.btn {
  padding: 0.55rem 1.3rem;
  border-radius: 6px;
  font-weight: 600;
  background: rgba(17, 64, 156, 0.3);
  border: 1px solid rgba(221, 234, 255, 0.22);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.btn:hover { transform: translateY(-1px); background: rgba(17, 64, 156, 0.5); }
.btn:active { transform: translateY(0); }
.btn-primary {
  background: linear-gradient(135deg, var(--action), var(--blue));
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 18px rgba(6, 81, 226, 0.35);
}
.btn-primary:hover { background: linear-gradient(135deg, #2b6ef0, #1a4fb8); box-shadow: 0 8px 26px rgba(6, 81, 226, 0.5); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--ink); background: rgba(221, 234, 255, 0.08); }
.btn-big { font-size: clamp(1rem, 1.8vw, 1.5rem); padding: 0.75rem 2.1rem; }
.btn-small { font-size: 0.85rem; padding: 0.3rem 0.9rem; }
/* A glyph sized to sit in the `.btn` family, not to be confused with the standalone `.icon-btn`
   below — which is a different component with a *destructive* red hover. The two names were one
   transposition apart, which is how `.live-badge`/`.live-chip`/`.live-dot` collided. */
.btn-glyph { padding: 0.35rem 0.6rem; font-size: 1.15rem; line-height: 1; }
.btn-glyph.on { color: var(--gold); }
/* Both forms: `.disabled` is the class the overlay's quote button toggles, `:disabled` the real
   attribute a button that can only ever 400 carries. They have to look the same or the second one
   reads as live. */
.btn.disabled, .btn:disabled { opacity: 0.35; pointer-events: none; }
.btn kbd { margin-left: 0.5em; }

.chip {
  padding: 0.35rem 1.05rem;
  border-radius: 6px;
  border: 1px solid rgba(221, 234, 255, 0.28);
  background: rgba(221, 234, 255, 0.06);
  font-weight: 600;
  transition: all 0.15s ease;
}
.chip:hover { border-color: var(--gold); }
.chip.picked {
  border-color: var(--gold);
  color: var(--gold);
  box-shadow: 0 0 14px rgba(251, 191, 36, 0.35);
}

/* ---------------------------------------------------------------- home */

/* The gate (sign-in and invitation) still centres a hero; the tournaments page does not. */
.home-hero { text-align: center; }

.logo {
  font-size: clamp(4rem, 11vw, 9rem);
  font-weight: 600;
  line-height: 0.95;
  /* IMC legacy brand gradient: teal -> mid blue -> deep blue */
  background: linear-gradient(100deg, #00b5b1, #0082ba 45%, #005195);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 22px rgba(0, 130, 186, 0.35));
}
.logo-accent {
  background: linear-gradient(100deg, #0082ba, #005195 70%, #11409c);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.tagline {
  margin-top: 0.6rem;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: clamp(0.7rem, 1.4vw, 1rem);
}

/* The tournament list is a table because every row answers the same seven questions about a
   bracket, and a column is what lets two of them be compared without reading either. Row-level
   borders rather than a card each: at a dozen brackets, a dozen floating cards is a page with no
   columns in it. */
.ttable { width: 100%; border-collapse: collapse; }
.ttable thead th {
  text-align: left;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  padding: 0 0.7rem 0.5rem;
  white-space: nowrap;
  border-bottom: 1px solid var(--card-border);
}
.ttable td {
  padding: 0.6rem 0.7rem;
  border-top: 1px solid var(--card-border);
  vertical-align: middle;
}
.ttable tbody tr:first-child td { border-top: none; }

.trow { cursor: pointer; transition: background-color 0.15s ease; }
.trow:hover td { background: var(--card); }
/* Keyboard selection, which is how the list is actually driven: a steady accent bar and tint. The
   old row was a card and could pulse its own glow; a collapsed border cannot, and a row of eight
   cells breathing in place is noise rather than emphasis. */
.trow.selected td { background: rgba(6, 81, 226, 0.16); }
.trow.selected td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.trow-actions { white-space: nowrap; width: 1%; }
.trow-actions .icon-btn { padding: 0.25rem; }
/* Only the trashcan is destructive; the global .icon-btn hover is red, which on a row of five
   would make opening a bracket look like losing it. */
.trow-actions .icon-btn:not(.danger):hover { color: #fff; background: rgba(6, 81, 226, 0.55); }

.trow-name {
  font-size: 1.05rem;
  font-weight: 600;
  min-width: 12rem;
}
.trow-name .badge { margin-left: 0.5rem; font-weight: 600; }
.trow-owner { color: var(--muted); white-space: nowrap; width: 1%; }
/* Every column but the name is sized to its content, which is what keeps the three fractions in one
   tight group under their own headings: left to the auto layout they spread across a 1600px page
   and every count is read beneath the *next* column's title, which is how the first cut of this
   table said `0` under MATCHES. The right-aligned heading is what keeps that true when the value is
   the wider of the two — `1024/1024` under `Players` — and needs `.ttable thead th`'s specificity
   to win. */
.tt-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; width: 1%; }
.ttable thead th.tt-num { text-align: right; }
/* The denominator is context, not a second number to read — same trick as the sub-bar's .tstat-sub. */
.tt-of { color: var(--muted); }
.tt-off { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; }
.tt-when { color: var(--muted); font-size: 0.85rem; white-space: nowrap; width: 1%; }
.ttable .tt-status { width: 1%; }

.badge {
  padding: 0.25rem 0.75rem;
  border-radius: 4px;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}
.badge-live { background: rgba(255, 167, 38, 0.14); color: var(--warn); border: 1px solid rgba(255, 167, 38, 0.45); }
/* Watching, not running: the normal state for most people looking at a bracket, so it states a fact
   rather than warning about one. */
.badge-watching {
  background: rgba(221, 234, 255, 0.08);
  color: var(--accent);
  border: 1px solid var(--card-border);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.7rem;
}
.badge-done { background: rgba(17, 64, 156, 0.35); color: var(--accent); border: 1px solid rgba(141, 175, 242, 0.45); }

.icon-btn {
  padding: 0.35rem 0.6rem;
  border-radius: 4px;
  color: var(--muted);
  transition: all 0.15s ease;
}
.icon-btn:hover { color: #fff; background: rgba(211, 47, 47, 0.8); }

/* ---------------------------------------------------------------- new tournament modal */

.modal {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  background: rgba(1, 17, 36, 0.75);
  backdrop-filter: blur(8px);
  animation: fade-in 0.2s ease;
}

.new-panel, .recap-panel {
  width: min(680px, 92vw);
  max-height: 88vh;
  overflow: auto;
  padding: 2rem;
  border-radius: var(--radius);
  background: linear-gradient(170deg, #0a2d66, #03225f 60%, #012649);
  border: 1px solid rgba(221, 234, 255, 0.18);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  animation: pop-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.15);
}
.new-panel h2, .recap-panel h2 { font-size: 1.6rem; font-weight: 600; }

.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field > span { font-weight: 600; color: var(--muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; }
.field input[type="text"], .field textarea {
  padding: 0.6rem 0.9rem;
  border-radius: 4px;
  background: rgba(1, 17, 36, 0.5);
  border: 1px solid rgba(221, 234, 255, 0.2);
  outline: none;
  resize: vertical;
  transition: border-color 0.15s ease;
}
.field input[type="text"]:focus, .field textarea:focus { border-color: var(--accent); }

.field-row { display: flex; gap: 1.2rem; align-items: flex-end; flex-wrap: wrap; }
.field.check { flex-direction: row; align-items: center; gap: 0.6rem; }
.field.check span { text-transform: none; letter-spacing: 0; font-size: 1rem; color: var(--ink); }

/* What an option actually does, on its own line under the label. `.field > span` is the uppercase
   label treatment, so a hint that is a direct child of a plain `.field` (rather than nested inside
   the label's own span, as the checkboxes do it) has to opt back out of all four of its properties. */
.field.check .nt-hint,
.field > .nt-hint {
  display: block;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.45;
  color: var(--muted);
}

/* The same treatment as a field's hint, for the note the duplicate panel opens with: it belongs to
   the whole form rather than to one control, so it sits under the heading and none of the `.field`
   selectors above reach it. */
.nt-note {
  display: block;
  margin: -0.4rem 0 0.2rem;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--muted);
}

/* A hint that shares a `.field-row` with the controls it explains rather than sitting under them —
   the row has the horizontal room, and the create panel does not have the vertical room to spare. */
.field-row > .nt-hint {
  flex: 1 1 14rem;
  align-self: center;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.45;
  color: var(--muted);
}

.field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.btn-inline {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--accent);
  border-bottom: 1px solid transparent;
}
.btn-inline:hover { color: var(--ink); border-bottom-color: currentColor; }

.banks { display: flex; flex-direction: column; gap: 0.4rem; max-height: 200px; overflow: auto; }
.bank {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.5rem 0.9rem;
  border-radius: 4px;
  background: rgba(221, 234, 255, 0.05);
  border: 1px solid rgba(221, 234, 255, 0.12);
  cursor: pointer;
}
.bank:hover { border-color: rgba(221, 234, 255, 0.3); }
.bank-name { flex: 1; font-weight: 600; }
.bank-count { color: var(--muted); font-size: 0.85rem; }

.seg { display: inline-flex; border-radius: 6px; overflow: hidden; border: 1px solid rgba(221, 234, 255, 0.25); }
.seg-btn { padding: 0.5rem 1.3rem; font-weight: 600; background: rgba(221, 234, 255, 0.04); }
.seg-btn + .seg-btn { border-left: 1px solid rgba(221, 234, 255, 0.15); }
.seg-btn.on { background: var(--action); color: #fff; }

.panel-actions { display: flex; justify-content: flex-end; gap: 0.8rem; margin-top: 0.4rem; }

/* The create form is the one panel that outgrows its own 88vh at every ordinary viewport, so Create
   has to stay reachable rather than be the thing that scrolled off. Sticky to the bottom of the
   scrolling panel, with the panel's own gradient behind it so the fields do not read through it.
   Scoped to `.new-panel`: the recap's actions sit under content that always fits. */
.new-panel .panel-actions {
  position: sticky;
  bottom: -2rem; /* cancels the panel's own bottom padding, so it sits flush */
  margin-top: 0;
  padding: 0.9rem 0 0;
  background: linear-gradient(to top, #012649 55%, rgba(1, 38, 73, 0));
}

.bank-warning {
  font-size: 0.92rem;
  font-weight: 600;
  border-radius: 4px;
  padding: 0.55rem 0.9rem;
  border: 1px solid transparent;
}
.bank-warning.ok { color: var(--muted); font-weight: 400; padding-left: 0.2rem; }
.bank-warning.warn {
  color: #ffb74d;
  background: rgba(255, 167, 38, 0.08);
  border-color: rgba(255, 167, 38, 0.4);
}
.bank-warning.bad {
  color: #ef9a9a;
  background: rgba(244, 67, 54, 0.08);
  border-color: rgba(244, 67, 54, 0.4);
}

/* ---------------------------------------------------------------- tournament view */

.tview { height: 100%; display: flex; flex-direction: column; }

.tname { font-weight: 600; }

/* Two counters, one per line, each a fraction the eye can compare down the column rather than a
   sentence to read across the bar. They used to be one line of prose with the bracket's format
   dot-appended, which wrapped at every ordinary width and read as clutter over the game. */
.tmeta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  row-gap: 0.05rem;
  color: var(--muted);
  font-weight: 600;
  font-size: clamp(0.78rem, 1.2vw, 1rem);
  min-width: 0;
}
.tstat { white-space: nowrap; }
.tstat b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.tstat-sub { color: var(--muted); font-weight: 400; }

/* Who is running this bracket. A full-width strip under the sub-bar: unmissable, and it never covers
   the game. Informational blue, not a warning — two hosts in a room is normal, both acting is not. */
.control-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  padding: 0.4rem clamp(0.8rem, 2vw, 1.6rem);
  background: rgba(6, 81, 226, 0.18);
  border-bottom: 1px solid rgba(141, 175, 242, 0.35);
  color: var(--gray-blue);
  font-weight: 600;
  font-size: 0.88rem;
}

/* Mine to run: a footnote, not a strip. Somebody else holding the lease is a conflict worth a band
   across the page; me holding it is the ordinary case and was reported as too loud. So it keeps no
   background, no border and no full-width presence — a faint line tucked under the right-hand end
   of the sub-bar, where the host can find it and nobody else's eye lands on it. */
.control-bar.mine {
  justify-content: flex-end;
  padding: 0.1rem clamp(0.8rem, 2vw, 1.6rem) 0;
  background: none;
  border-bottom: none;
  color: var(--muted);
  font-weight: 400;
  font-size: 0.7rem;
  opacity: 0.55;
}

/* Fullscreen is the projector, and the projector shows the game rather than who is driving it —
   the same reason the top bar and the key legend go. Keyed on the class the app sets from its own
   `fullscreenchange`, not on `:fullscreen`: the in-app F toggle is the only entry point that also
   arms Keyboard Lock, and the rest of this file already hides its chrome the same way. Taking over
   from an abandoned laptop happens on the colleague's own screen, which is not fullscreen. */
body.is-fullscreen .control-bar { display: none; }

.champion-banner {
  align-self: center;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  margin: 0.5rem auto 0;
  padding: 0.6rem 2.4rem;
  border-radius: var(--radius);
  background: linear-gradient(135deg, var(--gold), #f59e0b);
  color: #201500;
  box-shadow: 0 0 44px rgba(251, 191, 36, 0.5);
  animation: bounce-in 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}
.champion-banner .crown { font-size: clamp(1.6rem, 3.2vw, 3rem); }
.champ-name { font-size: clamp(1.6rem, 3.4vw, 3.2rem); font-weight: 600; }
.champ-label { font-size: clamp(1rem, 1.8vw, 1.5rem); font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; }
.champion-banner { cursor: pointer; }
.champ-cta { font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.65; }
.champion-banner:hover .champ-cta { opacity: 1; }

/* ---------------------------------------------------------------- recap */

.recap-panel { width: min(940px, 94vw); }
.recap-tiles { display: flex; flex-wrap: wrap; gap: 0.7rem; }
.recap-tile {
  flex: 1 1 150px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.7rem 0.5rem;
  border-radius: 6px;
  background: rgba(221, 234, 255, 0.06);
  border: 1px solid rgba(221, 234, 255, 0.14);
}
.recap-tile b { font-family: var(--font-display); font-size: clamp(1.5rem, 2.6vw, 2.4rem); color: var(--accent); }
.recap-tile span { color: var(--muted); font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.08em; text-align: center; }

.recap-section { display: flex; flex-direction: column; gap: 0.55rem; }
.recap-section h3 {
  font-family: var(--font-display);
  font-size: 0.9rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
}

.road { display: flex; flex-direction: column; gap: 0.3rem; }
.road-leg {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.4rem 0.9rem;
  border-radius: 4px;
  background: rgba(221, 234, 255, 0.05);
}
.leg-round { flex: 0 0 34%; color: var(--muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; }
.leg-beat { flex: 1; }
.leg-beat b { color: var(--gray-blue); font-weight: 600; }
.leg-bye { color: var(--muted); font-style: italic; }
.leg-score { font-weight: 600; color: var(--gold); font-variant-numeric: tabular-nums; }

.recap-badge {
  align-self: flex-start;
  padding: 0.35rem 1rem;
  border-radius: 6px;
  font-weight: 600;
  font-size: 0.92rem;
  border: 1px solid rgba(221, 234, 255, 0.25);
  color: var(--muted);
}
.recap-badge.perfect { border-color: var(--gold); background: rgba(251, 191, 36, 0.12); color: var(--gold); }

.hl {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.9rem;
  border-radius: 6px;
  background: rgba(221, 234, 255, 0.05);
  border-left: 3px solid var(--accent);
}
.hl-label { color: var(--accent); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; }
.hl-line b { color: var(--gold); }
.hl-q { color: var(--muted); font-size: 0.9rem; font-style: italic; }
.hl-q b { font-style: normal; color: var(--gray-blue); }
.hl-round { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.74rem; font-style: normal; opacity: 0.7; }

/* The audience section only exists once its request lands, and the panel is a gapped column — an
   empty wrapper would otherwise spend a 1.1rem gap on nothing. */
.recap-audience:empty { display: none; }

/* Top three spectators. Same left-accent bar as .hl, medal-coloured; a shorter list is what one or
   two spectators look like, and each row still names its place. */
.podium { list-style: none; display: flex; flex-direction: column; gap: 0.35rem; }
.podium-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.8rem;
  border-radius: 6px;
  background: rgba(221, 234, 255, 0.05);
  border-left: 3px solid var(--card-border);
}

.podium-medal {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 600;
  /* Dark ink on a bright medal — the same panel background the recap sits on. */
  color: #04203f;
}

.podium-who {
  font-weight: bold;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.podium-hits { margin-left: auto; flex: none; color: var(--muted); font-size: 0.8rem; }
.podium-pts {
  flex: none;
  color: var(--gold);
  font-weight: bold;
  font-variant-numeric: tabular-nums;
}

.podium-row.rank-1 { border-left-color: var(--gold); }
.podium-row.rank-1 .podium-medal { background: var(--gold); }
.podium-row.rank-2 { border-left-color: var(--silver); }
.podium-row.rank-2 .podium-medal { background: var(--silver); }
.podium-row.rank-3 { border-left-color: var(--bronze); }
.podium-row.rank-3 .podium-medal { background: var(--bronze); }

/* ---------------------------------------------------------------- bracket */

.bracket {
  flex: 1;
  display: flex;
  gap: var(--bgap);
  padding: 0.6rem clamp(1rem, 3vw, 3rem) 0.8rem;
  overflow: auto;
}
.bracket-compact { --bgap: 42px; }

.round {
  display: flex;
  flex-direction: column;
  flex: 1 0 235px;
  min-width: 235px;
  /* Small brackets would otherwise stretch two cards across the whole screen.
     Auto margins center the columns when there is slack and collapse to 0 when
     the bracket overflows, so wide brackets still scroll from their first round. */
  max-width: 640px;
  /* --bh (set in JS) = column height in px: round-name + the sum of the slot
     heights computed in computeSlotHeights (identical in every column). */
  height: max(100%, var(--bh, 0px));
  transition: height 0.6s ease;
}
.round:first-child { margin-left: auto; }
.round:last-child { margin-right: auto; }
.round-name {
  flex: none; /* equal header height in every column, or slot grids drift apart */
  height: 1.7rem;
  text-align: center;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: clamp(0.7rem, 1.1vw, 0.95rem);
  color: var(--accent);
}
.slots { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.slot {
  /* Height is set inline by JS (computeSlotHeights): a slot-height binary tree
     where a parent spans exactly its two feeders, so byes and finished matches
     shrink to one line while connectors stay attached. Cards center within. */
  flex: none;
  display: flex;
  align-items: center;
  position: relative;
  padding: 0.3rem 0;
  min-height: 0;
  transition: height 0.6s ease;
}

/* Elbows may extend past their slot (--half-sib = sibling height / 2), so no
   ancestor between here and .bracket may clip overflow. */
.slot.out-top::after, .slot.out-bottom::after {
  content: "";
  position: absolute;
  right: calc(var(--bgap) * -0.5);
  width: calc(var(--bgap) * 0.5);
  pointer-events: none;
  transition: height 0.6s ease;
}
.slot.out-top::after {
  /* From this slot's center down to the parent's center: the parent spans
     exactly this slot + its sibling, so the drop is sibling_height / 2. */
  top: 50%;
  height: var(--half-sib, 50%);
  border-top: 3px solid var(--line);
  border-right: 3px solid var(--line);
  border-top-right-radius: 8px;
}
.slot.out-bottom::after {
  bottom: 50%;
  height: var(--half-sib, 50%);
  border-bottom: 3px solid var(--line);
  border-right: 3px solid var(--line);
  border-bottom-right-radius: 8px;
}
.slot.in::before {
  content: "";
  position: absolute;
  left: calc(var(--bgap) * -0.5);
  width: calc(var(--bgap) * 0.5);
  top: calc(50% - 1.5px);
  border-top: 3px solid var(--line);
  pointer-events: none;
}

/* Column headers stay in lockstep bracket-wide, or the slot grids drift apart. */
.bracket-compact .round-name { font-size: 0.72rem; height: 1.4rem; letter-spacing: 0.12em; }

/* Per-round card tiers — see ROW_HEIGHTS in app.js, which mirrors these sizes.
   Compact: crowded early rounds. Roomy: quarter-final onward, where a column
   holds few cards and a default-size one looks lost in its slot. */
.round-compact .slot { padding: 0.15rem 0; }
.round-compact .match { padding: 0.22rem 0.65rem; border-radius: 6px; border-width: 1px; }
.round-compact .pname { font-size: 0.92rem; }
.round-compact .mrow { padding: 0.05em 0; }
.round-compact .pips { font-size: 0.72rem; }

.round-roomy .match { padding: clamp(0.9rem, 2.2vh, 1.8rem) clamp(1rem, 1.6vw, 1.6rem); }
.round-roomy .pname { font-size: clamp(1.2rem, 2vw, 2.2rem); }
.round-roomy .pips { font-size: clamp(0.9rem, 1.4vw, 1.5rem); }

/* One-line cards (~20px card, ~22px slot: LINE_HEIGHT in app.js). The slot is
   flagged per match, since computeBracketLayout decides this match by match. */
.slot.slot-line { padding: 0.05rem 0; }
/* .match.match-line: must outrank the base .match padding/border further down */
.match.match-line {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.12rem 0.6rem;
  border-radius: 4px;
  border-width: 1px;
  line-height: 1.2;
}
.match-line .line-w {
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--gold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-line .line-score { font-size: 0.62rem; font-weight: 600; color: var(--muted); flex: none; }
.match-line .line-l {
  font-size: 0.68rem;
  opacity: 0.55;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.match-line.bye { opacity: 0.35; border-style: dashed; }

/* ---------------------------------------------------------------- match cards */

.match {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: clamp(0.4rem, 1vh, 0.8rem) clamp(0.7rem, 1.1vw, 1.1rem);
  border-radius: var(--radius);
  background: var(--card);
  border: 1.5px solid var(--card-border);
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.match:hover { transform: scale(1.02); }
.match.st-pending { opacity: 0.6; cursor: default; }
.match.st-pending:hover { transform: none; }
.match.st-ready { border-color: rgba(141, 175, 242, 0.45); }
.match.st-in_progress { border-color: rgba(255, 167, 38, 0.55); }
.match.st-done { opacity: 0.85; }
.match.selected {
  border-color: var(--accent);
  animation: pulse-glow 1.6s ease-in-out infinite;
}
/* Byes always render as one-line .match-line cards (see renderLineCard). */
.match.bye { opacity: 0.35; border-style: dashed; cursor: default; }
.match.bye:hover { transform: none; }

.mrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.7rem;
  padding: 0.08em 0;
}
.pname {
  font-size: clamp(1rem, 1.6vw, 1.7rem);
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mrow.winner .pname { color: var(--gold); text-shadow: 0 0 18px rgba(251, 191, 36, 0.4); }
.mrow.loser { opacity: 0.5; }
/* The italic D leans past its own advance width, and `.pname` is a flex item sized to exactly that
   advance — so `overflow: hidden` shaved the last stroke off every TBD in the bracket. The clip
   happens at the padding edge, which is what makes the padding the fix. */
.mrow.tbd .pname { color: var(--muted); font-weight: 400; font-style: italic; padding-right: 0.12em; }

.pips { font-size: clamp(0.75rem, 1.1vw, 1.1rem); letter-spacing: 0.15em; color: var(--accent); white-space: nowrap; }

/* "This match is on" — a bracket card's own corner badge, and nothing else. It used to be called
   .live-dot, which collided with the spectator page's status chip: same specificity, so the chip
   inherited this absolute corner and this colour. Three separate things, three names. */
.match .live-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 12px rgba(255, 167, 38, 0.9);
  animation: live-pulse 1.2s ease-in-out infinite;
}

/* A win by default. On a full card it floats in the corner so the card keeps the height
   computeBracketLayout() gave it; on a one-line card it is the last, unshrinkable item. */
.badge-ff {
  flex: 0 0 auto;
  font-size: 0.6rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--warn);
  white-space: nowrap;
}
.match .badge-ff { position: absolute; top: 4px; right: 8px; }
.match.match-line .badge-ff { position: static; }

.reset-btn {
  position: absolute;
  bottom: 4px;
  right: 6px;
  font-size: 0.95rem;
  color: var(--muted);
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.match:hover .reset-btn, .match.selected .reset-btn { opacity: 1; }
.reset-btn:hover { color: #ef9a9a; }

/* ---------------------------------------------------------------- overlay */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  /* This layer is `inset: 0`, so it runs under `#hints` — which paints over it at z-index 60, and a
     panel that merely fits the viewport still had its Next button behind the key strip at 700px.
     Centring in the space above it is the fix; in fullscreen the strip is hidden and the reserve
     comes back. Keep this in step with `#hints`'s own height. */
  padding-bottom: 2.4rem;
  background: rgba(1, 17, 36, 0.8);
  backdrop-filter: blur(10px);
  animation: fade-in 0.25s ease;

  /* One vertical budget, one scale. Every big number in here used to size itself off `vw` alone
     while the panel's own boxes were sized off `vh`, so the two fought: the trade phase overflowed
     by 96px at 1366x768 and by 157px at 1920x1080 with a three-line question, hiding the
     "it's higher / it's lower" lines under the fold. `--ovw` is that width scale capped by the
     height available — it *equals* 1vw at 16:9 and above, so a 1080p projector is untouched, and
     a shorter viewport shrinks the type instead of pushing the trade cards off the bottom.
     Use `calc(N * var(--ovw))` as the middle term of a clamp wherever `Nvw` used to be. */
  --ovw: min(1vw, 1.78vh);

  /* The question's type scale, named so that exactly one rule multiplies it by the fit factor
     `--qfit` that `fitQuestion()` measures (see `.question`). `13.5vh - 48px` is the share of the
     viewport height left once the header, the quote, the trade cards and the always-present
     `.aud-live` line have taken theirs: it lands on the 6.4rem cap at 1080p, so a projector is
     unchanged, and drops to ~64px at 768. */
  --qsize: clamp(1.5rem, min(calc(5.5 * var(--ovw)), calc(13.5vh - 48px)), 6.4rem);
}

/* The reveal keeps the question on screen as a caption above the answer, so it is sized as one. */
.overlay.phase-reveal { --qsize: clamp(1rem, calc(2.4 * var(--ovw)), 2.6rem); }

body.is-fullscreen .overlay { padding-bottom: 0; }

.ov-panel {
  width: min(1500px, 96vw);
  /* All of the room the overlay has, which is the viewport less the hints strip. The panel's own
     padding is the inset; the old 92vh cap on top of that only ever clipped. */
  max-height: 100%;
  overflow: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(0.5rem, 1.55vh, 1.5rem);
  padding: clamp(0.6rem, 1.8vh, 2rem) 1rem;
}
.ov-panel.enter { animation: pop-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.15); }

/* The overlay's primary action is the one button in the app that must never be the thing that
   pushes a phase past the fold, so its padding yields with the viewport. Capped at the shared
   0.75rem, so nothing changes above ~1090px of viewport height. */
.ov-panel .btn-big { padding: clamp(0.45rem, 1.1vh, 0.75rem) 2.1rem; }

/* ----- toss */

.vs { font-size: clamp(2rem, 5.5vw, 5rem); font-weight: 600; }
.vs-word { color: var(--gold); font-style: italic; font-size: 0.55em; margin: 0 0.3em; }

.coin-stage { perspective: 900px; padding: clamp(0.5rem, 3vh, 2rem) 0; }

.coin {
  width: clamp(130px, 22vh, 230px);
  aspect-ratio: 1;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.2s ease;
}
/* Only a coin someone is known to be able to flip invites a click — a watcher's coin still spins and
   lands, but is not a button. `.flippable` is the *advertisement*, which renderToss withholds until
   write access is confirmed; whether the click is wired is a separate question it answers there. */
.coin.flippable { cursor: pointer; }
.coin.flippable:hover { transform: scale(1.05); }
.coin.spinning { animation: coin-loop 0.4s linear infinite; }
.coin.land-gold { animation: coin-land-gold 1.05s cubic-bezier(0.3, 0.7, 0.25, 1) forwards; }
.coin.land-silver { animation: coin-land-silver 1.05s cubic-bezier(0.3, 0.7, 0.25, 1) forwards; }

.coin .face {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  padding: 12%;
  text-align: center;
  font-size: clamp(0.95rem, 2.8vh, 1.5rem);
  font-weight: 600;
  line-height: 1.1;
  /* Never split inside a word — wrap at spaces only; JS shrinks the font for
     long single words instead (coinNameStyle in app.js). */
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
  backface-visibility: hidden;
  border: 6px solid rgba(255, 255, 255, 0.3);
}
.coin .front {
  background: radial-gradient(circle at 35% 30%, #ffe9a3, #f6c945 45%, #b8860b);
  color: rgba(110, 70, 0, 0.8);
  box-shadow: 0 0 46px rgba(251, 191, 36, 0.5), inset 0 -8px 18px rgba(0, 0, 0, 0.25);
}
.coin .back {
  transform: rotateY(180deg);
  background: radial-gradient(circle at 35% 30%, #e8e6ff, #b0aaf0 45%, #5d4bcf);
  color: rgba(40, 25, 120, 0.8);
  box-shadow: 0 0 46px rgba(167, 139, 250, 0.5), inset 0 -8px 18px rgba(0, 0, 0, 0.25);
}

@keyframes coin-loop {
  from { transform: rotateY(0); }
  to { transform: rotateY(360deg); }
}
/* Both landings do a full theatrical flip; only the final resting face differs
   (1800deg = gold/front face, 1980deg = silver/back face). */
@keyframes coin-land-gold {
  0% { transform: rotateY(0) translateY(0); }
  45% { transform: rotateY(900deg) translateY(-9vh); }
  100% { transform: rotateY(1800deg) translateY(0); }
}
@keyframes coin-land-silver {
  0% { transform: rotateY(0) translateY(0); }
  45% { transform: rotateY(990deg) translateY(-9vh); }
  100% { transform: rotateY(1980deg) translateY(0); }
}

.coin-legend {
  display: flex;
  gap: 1.4rem;
  justify-content: center;
  margin-top: 1rem;
  color: var(--muted);
  font-weight: 600;
  font-size: clamp(0.85rem, 1.4vw, 1.15rem);
}
.legend-item { display: inline-flex; align-items: center; gap: 0.45em; word-break: normal; overflow-wrap: normal; hyphens: none; }
.legend-dot { width: 0.9em; height: 0.9em; border-radius: 50%; flex: none; }
.legend-dot.gold { background: radial-gradient(circle at 35% 30%, #ffe9a3, #f6c945 55%, #b8860b); }
.legend-dot.silver { background: radial-gradient(circle at 35% 30%, #e8e6ff, #b0aaf0 55%, #5d4bcf); }

.toss-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  min-height: 7.5rem;
}
.toss-confirm { visibility: hidden; }
.toss-confirm.show { visibility: visible; }

/* Same quiet register for "the host is doing something" and "you are waiting for the host". */
.toss-hint, .ov-waiting {
  color: var(--muted);
  font-size: clamp(0.95rem, 1.6vw, 1.4rem);
  font-weight: 600;
}
.toss-manual {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  justify-content: center;
  color: var(--muted);
  font-size: clamp(0.85rem, 1.3vw, 1.1rem);
}
.toss-result {
  font-size: clamp(1.3rem, 3vw, 2.6rem);
  font-weight: 400;
  animation: rise 0.45s ease;
}
.toss-result strong { color: var(--gold); font-weight: 600; }

/* Forfeiting is rare and irreversible: present, but never competing with the coin. */
.toss-forfeit { display: flex; gap: 1.4rem; flex-wrap: wrap; justify-content: center; }
.forfeit-link {
  font-size: 0.78rem;
  color: var(--muted);
  opacity: 0.7;
  border-bottom: 1px solid transparent;
}
.forfeit-link:hover { color: #ef9a9a; opacity: 1; border-bottom-color: currentColor; }

/* ----- market header */

.mkt-top {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.6rem);
  flex-wrap: wrap;
  justify-content: center;
}
.mkt-count {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--accent);
  font-size: clamp(0.85rem, 1.5vw, 1.3rem);
}

.mdots { display: inline-flex; gap: 0.5rem; align-items: center; }
.dot {
  width: clamp(10px, 1.2vw, 16px);
  height: clamp(10px, 1.2vw, 16px);
  border-radius: 50%;
  background: rgba(221, 234, 255, 0.14);
}
.dot.cur { background: rgba(221, 234, 255, 0.3); box-shadow: 0 0 10px rgba(221, 234, 255, 0.4); animation: live-pulse 1.4s ease-in-out infinite; }
.dot.won-mm { background: var(--accent); box-shadow: 0 0 10px rgba(141, 175, 242, 0.7); }
.dot.won-tr { background: var(--gray-blue); box-shadow: 0 0 10px rgba(221, 234, 255, 0.7); }

.ov-score { display: inline-flex; gap: 1.4rem; font-size: clamp(0.9rem, 1.6vw, 1.4rem); }
.ovs .pips { margin-left: 0.35em; }

.roles { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; }
.role {
  padding: 0.35rem 1.1rem;
  border-radius: 6px;
  font-weight: 600;
  font-size: clamp(0.9rem, 1.7vw, 1.5rem);
}
.role-mm { background: rgba(17, 64, 156, 0.35); color: var(--accent); border: 1px solid rgba(141, 175, 242, 0.5); }
.role-tr { background: rgba(221, 234, 255, 0.08); color: var(--gray-blue); border: 1px solid rgba(221, 234, 255, 0.35); }

/* The question is the one block in the panel whose height is not bounded — it wraps to as many
   lines as the text needs — so it is the one that has to yield. `--qsize` (on `.overlay`) is the
   viewport's share of that budget; `--qfit` is how much of it this particular string may keep.
   Fit wins over size: no clamp can know how many lines a given sentence wraps to, so `fitQuestion()`
   in app.js measures the panel and shrinks the type until nothing overflows. It only ever shrinks,
   which is what keeps `--qsize` the ceiling a short question on a projector still renders at. */
.question {
  font-size: calc(var(--qfit, 1) * var(--qsize));
  font-weight: 600;
  line-height: 1.08;
  max-width: 96%;
  text-wrap: balance;
}
.phase-reveal .question { opacity: 0.75; }

/* ----- quote entry */

.quote-entry { display: flex; flex-direction: column; align-items: center; gap: 1.4rem; }
#quote-input {
  width: min(70vw, 14ch);
  text-align: center;
  font-size: clamp(2.2rem, 6vw, 5.5rem);
  font-family: var(--font-display);
  letter-spacing: 0.04em;
  font-weight: 600;
  background: transparent;
  border: none;
  border-bottom: 4px solid rgba(141, 175, 242, 0.45);
  outline: none;
  color: var(--gold);
  caret-color: var(--accent);
  transition: border-color 0.15s ease;
}
#quote-input:focus { border-bottom-color: var(--accent); }
#quote-input::placeholder { color: rgba(157, 185, 228, 0.4); font-weight: 400; }
#quote-input.shake { animation: shake 0.35s ease; }

/* ----- trade (house convention: BUY = green, SELL = red) */

.quote-shown { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; }
.q-label { color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.14em; font-size: clamp(0.8rem, 1.4vw, 1.2rem); }
.q-value {
  font-size: clamp(2.2rem, calc(7 * var(--ovw)), 7rem);
  /* The display stack's default leading put 54px of empty line box around a 112px number. */
  line-height: 1.05;
  font-weight: 600;
  color: var(--gold);
  text-shadow: 0 0 40px rgba(251, 191, 36, 0.4);
}

.trade-cards {
  display: flex;
  gap: clamp(1rem, 3vw, 3rem);
  width: 100%;
  justify-content: center;
  flex-wrap: wrap;
}
/* 30vh of card for ~135px of text was 190px of slack per card at 1080p, and it was the single
   biggest claim on the phase's vertical budget. Padding, not an oversized floor, is what keeps the
   card from hugging its label — so the card stays generous where there is room and yields where
   there is not. */
.trade-card {
  flex: 0 1 480px;
  min-height: clamp(104px, 19vh, 260px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: clamp(0.6rem, 1.6vh, 1.6rem) 1rem;
  border-radius: 12px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.trade-card:hover { transform: scale(1.04); }
.trade-card .side { font-size: clamp(2rem, calc(6 * var(--ovw)), 5.2rem); line-height: 1.1; font-weight: 600; font-family: var(--font-display); letter-spacing: 0.06em; }
.trade-card .dir { font-size: clamp(0.95rem, calc(2 * var(--ovw)), 1.7rem); font-weight: 600; opacity: 0.85; }
.trade-card.buy {
  background: linear-gradient(180deg, rgba(102, 187, 106, 0.12), rgba(46, 125, 50, 0.32));
  border: 2px solid rgba(102, 187, 106, 0.6);
  color: #a5d6a7;
}
.trade-card.sell {
  background: linear-gradient(180deg, rgba(244, 67, 54, 0.12), rgba(211, 47, 47, 0.32));
  border: 2px solid rgba(244, 67, 54, 0.6);
  color: #ef9a9a;
}
.trade-card.picked {
  transform: scale(1.06);
  box-shadow: 0 0 0 4px currentColor, 0 0 50px rgba(221, 234, 255, 0.18);
}

/* ----- reveal */

.reveal { display: flex; flex-direction: column; align-items: center; gap: clamp(0.45rem, 1.35vh, 1.3rem); width: 100%; }

.reveal-facts { display: flex; gap: 1.2rem; flex-wrap: wrap; justify-content: center; }
.fact {
  padding: 0.3rem 1.05rem;
  border-radius: 6px;
  background: rgba(221, 234, 255, 0.07);
  border: 1px solid rgba(221, 234, 255, 0.18);
  font-weight: 600;
  font-size: clamp(0.9rem, min(calc(1.7 * var(--ovw)), 2.8vh), 1.5rem);
}
.fact-buy { color: #81c784; border-color: rgba(102, 187, 106, 0.55); }
.fact-sell { color: #e57373; border-color: rgba(244, 67, 54, 0.55); }

/* Exact quote: the quote is the story, the trade cannot change the outcome. */
.fact-exact, .exact-note {
  color: var(--gold);
  border: 1px solid var(--gold);
  background: rgba(251, 191, 36, 0.12);
}
.exact-note {
  padding: 0.4rem 1.3rem;
  border-radius: 6px;
  font-weight: 600;
  font-size: clamp(0.95rem, 1.8vw, 1.6rem);
  text-align: center;
}
.exact .fact-buy, .exact .fact-sell { opacity: 0.45; }

.answer { display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
.a-label { color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.16em; font-size: clamp(0.8rem, 1.4vw, 1.2rem); }
.a-value {
  /* 13.5vh clears the 9rem cap at 1080, so the projector's answer is untouched; below that it is
     what stops the star of the reveal from crowding out everything under it. */
  font-size: clamp(2.6rem, min(calc(8.5 * var(--ovw)), 13.5vh), 9rem);
  font-weight: 600;
  line-height: 1;
  color: var(--accent);
  text-shadow: 0 0 44px rgba(141, 175, 242, 0.45);
  font-variant-numeric: tabular-nums;
}
/* Gold while counting up, and kept gold when the quote turned out to be exact. */
.counting .a-value, .exact .a-value { color: var(--gold); text-shadow: 0 0 44px rgba(251, 191, 36, 0.5); }

.reveal-after { display: flex; flex-direction: column; align-items: center; gap: clamp(0.5rem, 1.3vh, 1.1rem); }
.counting .reveal-after { visibility: hidden; }
.settled .reveal-after { animation: rise 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.2); }

.winner-banner {
  font-size: clamp(1.6rem, min(calc(4 * var(--ovw)), 6.4vh), 3.8rem);
  font-weight: 600;
  /* A display line at the UA's default leading cost 30px of the reveal's budget for nothing. */
  line-height: 1.1;
  color: var(--gold);
  text-shadow: 0 0 30px rgba(251, 191, 36, 0.4);
}
.post-context {
  font-style: italic;
  color: var(--muted);
  font-size: clamp(0.85rem, min(calc(1.6 * var(--ovw)), 2.3vh), 1.4rem);
  line-height: 1.3;
  max-width: 70ch;
}

/* ----- up next ribbon */

.up-next {
  display: inline-flex;
  align-items: baseline;
  gap: 0.6em;
  padding: 0.45rem 1.2rem;
  border-radius: 6px;
  background: rgba(221, 234, 255, 0.06);
  border: 1px solid rgba(221, 234, 255, 0.2);
  white-space: nowrap;
}
.up-next-label {
  font-size: clamp(0.6rem, 0.9vw, 0.85rem);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted);
}
.up-next-names { font-weight: 600; font-size: clamp(0.95rem, 1.7vw, 1.6rem); }
.up-next-vs { color: var(--accent); font-style: italic; font-size: 0.7em; }

.up-next-slot {
  position: absolute;
  top: 1rem;
  right: 1.2rem;
  opacity: 0.85;
  animation: fade-in 0.3s ease;
}

.up-next-big {
  padding: 0.7rem 2rem;
  background: rgba(6, 81, 226, 0.14);
  border: 1px solid rgba(141, 175, 242, 0.5);
  box-shadow: 0 0 26px rgba(6, 81, 226, 0.25);
}
.up-next-big .up-next-label { font-size: clamp(0.8rem, 1.3vw, 1.2rem); color: var(--accent); }
.up-next-big .up-next-names { font-size: clamp(1.4rem, 3.2vw, 2.9rem); }
.up-next-ready {
  color: var(--accent);
  font-weight: 600;
  font-size: clamp(0.9rem, 1.8vw, 1.7rem);
}

/* ----- the grand final's announcement
   Its own overlay phase, sized off `--ovw` like every other phase so a short viewport shrinks the
   type instead of pushing the button under the fold. Deliberately quieter than `.splash`: that one
   celebrates a result, this one sets one up, and the gold gradient is the champion's. */

.final-hype {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.8rem, 2.6vh, 2.2rem);
}
/* The label, deliberately the quietest line of the three: it names the moment, while the line that
   says what they are playing to is the one the room actually needs off the screen. */
.hype-kicker {
  font-family: var(--font-display);
  font-size: clamp(1rem, min(calc(2 * var(--ovw)), 3.4vh), 2rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
}
/* Name / vs / name, one per line, always — a fight poster rather than a sentence.
   Deliberately a column and not an inline row that wraps: player names are unbounded, so at any
   fixed type scale *some* pair overflows the line, and the wrap lands wherever the width happens to
   run out. The first draft put "Brunhilde Andersen vs" on line one and "Cornelia Diaz" on line two,
   which reads as a stray word. A column breaks the same way at every viewport and every name
   length, which is the property worth having on the one screen the whole room is looking at. */
.hype-vs {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.05em;
  font-size: clamp(2rem, min(calc(6 * var(--ovw)), 11vh), 6.5rem);
  font-weight: 600;
  line-height: 1.05;
}
.hype-player { text-wrap: balance; }
.hype-word { color: var(--gold); font-style: italic; font-size: 0.42em; }
.hype-format {
  color: var(--gray-blue);
  font-weight: 600;
  text-wrap: balance;
  max-width: 24em;
  line-height: 1.3;
  font-size: clamp(1.05rem, min(calc(2.6 * var(--ovw)), 4.4vh), 2.6rem);
}

/* ----- splash */

.splash { display: flex; flex-direction: column; align-items: center; gap: 1.2rem; cursor: pointer; }
.splash-emoji { font-size: clamp(4rem, 14vh, 9rem); animation: bounce-in 0.7s cubic-bezier(0.2, 0.9, 0.3, 1.4); }
.splash-title {
  font-size: clamp(2rem, 6vw, 6rem);
  font-weight: 600;
  line-height: 1.05;
  text-wrap: balance;
  background: linear-gradient(100deg, var(--gold), var(--warn));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 26px rgba(251, 191, 36, 0.35));
}
.splash-sub { color: var(--muted); font-weight: 600; font-size: clamp(1rem, 2vw, 1.6rem); }

/* ---------------------------------------------------------------- animations */

@keyframes pulse-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(141, 175, 242, 0.35), 0 0 18px rgba(6, 81, 226, 0.3); }
  50% { box-shadow: 0 0 0 5px rgba(141, 175, 242, 0.12), 0 0 36px rgba(6, 81, 226, 0.5); }
}
@keyframes live-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.35); opacity: 0.7; }
}
@keyframes fade-in { from { opacity: 0; } }
@keyframes pop-in { from { opacity: 0; transform: scale(0.92); } }
@keyframes rise { from { opacity: 0; transform: translateY(26px); } }
@keyframes bounce-in {
  0% { opacity: 0; transform: scale(0.4); }
  70% { transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-12px); }
  50% { transform: translateX(10px); }
  75% { transform: translateX(-6px); }
}

/* ---------------------------------------------------------------- small screens */

@media (max-width: 760px) {
  #hints { gap: 0.8rem; flex-wrap: wrap; }
  .tbar { flex-wrap: wrap; }
  .trade-card { flex-basis: 100%; min-height: 120px; }
  .round { flex-basis: 180px; min-width: 180px; }
}

/* ---------------------------------------------------------------- sign-in & invitations */

.gate {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.6rem;
  padding: 2rem 1rem;
}

.gate-card {
  width: min(400px, 100%);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.6rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.gate-card h2 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.04em;
  margin-bottom: 0.4rem;
}

.gate-card label { font-size: 0.82rem; color: var(--muted); margin-top: 0.5rem; }

.gate-card input,
.new-bank input,
.new-question textarea,
.new-panel select,
.share-form input,
.share-form select,
.join-card input {
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.6);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.55rem 0.7rem;
}

.gate-card input:focus-visible,
.join-card input:focus-visible,
.new-question textarea:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 1px;
}

.gate-card button { margin-top: 1rem; }
.gate-note { font-size: 0.78rem; color: var(--muted); line-height: 1.5; margin-top: 0.6rem; }
.gate-note a { color: var(--accent); }

/* ---------------------------------------------------------------- bank manager */

.banks-page { padding: 1.4rem clamp(1rem, 4vw, 3rem); }

.banks-layout {
  display: grid;
  grid-template-columns: minmax(240px, 340px) 1fr;
  gap: 1.4rem;
  align-items: start;
}

.banks-list { display: flex; flex-direction: column; gap: 0.5rem; }

.brow {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.7rem 0.9rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  cursor: pointer;
}

.brow:hover, .brow.selected { border-color: var(--action); background: rgba(6, 81, 226, 0.14); }
.brow-main { flex: 1; min-width: 0; }
.brow-name { font-weight: bold; }
.brow-meta { font-size: 0.74rem; color: var(--muted); margin-top: 0.15rem; }

.new-bank, .new-question, .bank-detail {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.new-bank h3, .new-question h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
}

.bank-head { display: flex; align-items: baseline; gap: 0.7rem; }
.bank-head h2 { font-family: var(--font-display); font-weight: 500; letter-spacing: 0.03em; }

.qlist { list-style: none; display: flex; flex-direction: column; gap: 0.35rem; }

.qrow {
  display: grid;
  /* Wording, answer, the edit and the delete button share one line; any context wraps
     underneath. Without explicit rows, auto-placement drops the button onto a line of its own. */
  grid-template-columns: 1fr auto auto auto;
  gap: 0.2rem 0.6rem;
  align-items: baseline;
  padding: 0.4rem 0.6rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: rgba(1, 17, 36, 0.35);
}

.qrow > * { grid-row: 1; }
.qanswer { font-weight: bold; color: var(--gold); font-variant-numeric: tabular-nums; }

.qcontext {
  grid-row: 2;
  grid-column: 1 / -1;
  font-size: 0.76rem;
  color: var(--muted);
}

/* Advice, not a rule: a long question is always played, just at a smaller size. Amber rather than
   red for the same reason, and it sits on its own row under the wording it is about. */
.qlong {
  grid-row: 3;
  grid-column: 1 / -1;
  font-size: 0.76rem;
  color: #ffb74d;
}
/* In the editor and the add form the note is a flex item, so it needs its own full-width line. */
.qedit .qlong, .one-question .qlong { flex: 1 1 100%; }

.shares {
  font-size: 0.82rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.7rem 0.8rem;
  background: rgba(1, 17, 36, 0.35);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.shares-state {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.9rem;
  letter-spacing: 0.03em;
  color: var(--accent);
}
.share-list { list-style: none; display: flex; flex-direction: column; gap: 0.3rem; margin: 0.5rem 0; }
.share-form { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.share-form input { flex: 1; min-width: 160px; }

.check { display: flex; align-items: center; gap: 0.45rem; font-size: 0.84rem; color: var(--muted); }

/* ---------------------------------------------------------------- audience, host side */

.audience-badge {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  background: rgba(1, 17, 36, 0.45);
}

.audience-code {
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: 0.16em;
  color: var(--gold);
}

.audience-count { font-size: 0.74rem; color: var(--muted); }

/* ---------------------------------------------------------------- audience, spectator page */

/* The console wraps #view in #app, the column that lets `#view { flex: 1; overflow: auto }` do the
   scrolling; here body is that column. Without it #view is an auto-height block that never scrolls,
   and a market plus a leaderboard is simply clipped by `body { overflow: hidden }` — on a phone the
   footer below the board would be unreachable. */
body.audience { display: flex; flex-direction: column; align-items: stretch; }

.join-card, .play {
  width: min(520px, 100%);
  margin: 0 auto;
  padding: 1.4rem 1.1rem calc(1.4rem + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.join-lead { color: var(--muted); line-height: 1.5; }
.join-card form { display: flex; flex-direction: column; gap: 0.4rem; }
.join-card label { font-size: 0.82rem; color: var(--muted); margin-top: 0.6rem; }
.join-card #code { text-transform: uppercase; letter-spacing: 0.28em; font-size: 1.5rem; text-align: center; }
.join-card button { margin-top: 1.2rem; }

.play-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--card-border);
}

.play-name { font-family: var(--font-display); font-size: 1.2rem; letter-spacing: 0.04em; }
.play-score { font-size: 0.86rem; color: var(--gold); }

.play-market, .play-wait, .play-board {
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.play-question { font-size: 1.15rem; line-height: 1.4; }
.play-quote { margin-top: 0.8rem; font-size: 1.5rem; font-variant-numeric: tabular-nums; }
.play-quote b { color: var(--gray-blue); }

.play-sides { display: flex; gap: 0.8rem; margin-top: 1.2rem; }

/* .side-btn, not .side: the console renders <span class="side"> inside .trade-card, and an unscoped
   .side beat .trade-card.buy's green with `color: var(--ink)` — a white BUY above a green
   "it's higher". Same collision as .live-badge/.live-chip/.live-dot; keep the names apart. */
.side-btn {
  flex: 1;
  font-family: var(--font-display);
  font-size: 1.5rem;
  letter-spacing: 0.08em;
  padding: 1.1rem 0;
  color: var(--ink);
  border: 2px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  /* Big enough to hit with a thumb from the back of the room. */
  min-height: 68px;
}

.side-btn.buy { background: var(--buy-deep); }
.side-btn.sell { background: var(--sell-deep); }
.side-btn.buy.chosen { background: var(--buy); border-color: var(--ink); }
.side-btn.sell.chosen { background: var(--sell); border-color: var(--ink); }
.side-btn:disabled { opacity: 0.55; cursor: default; }

/* The picked side is the one thing on this screen that must be unmistakable, so it keeps full
   opacity while its disabled neighbour fades: the difference a thumb has to read is 1 against 0.55,
   not the 2.17:1 the two greens managed on their own. The tick and the border carry it a third and
   fourth time over, because a phone in a bright room at arm's length is the worst case for colour
   alone and the room is watching the projector, not this. */
.side-btn.chosen:disabled { opacity: 1; border-width: 3px; }
.side-mark { margin-left: 0.4em; }

.play-hint, .play-context { font-size: 0.82rem; color: var(--muted); margin-top: 0.8rem; line-height: 1.5; }
.play-answer { font-size: 1.6rem; margin-top: 0.8rem; }
.play-answer b { color: var(--gold); font-variant-numeric: tabular-nums; }
.play-verdict { margin-top: 0.6rem; font-family: var(--font-display); letter-spacing: 0.05em; }
.play-wait { text-align: center; color: var(--muted); }
.play-wait h2 { font-family: var(--font-display); color: var(--gold); font-size: 1.6rem; }

.play-board h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  letter-spacing: 0.04em;
  margin-bottom: 0.6rem;
}

.play-board ol { list-style: none; display: flex; flex-direction: column; gap: 0.25rem; }

.play-board li {
  display: grid;
  /* Fourth track for the reveal's movement chip, reserved on every row so the totals stay in one
     column — see `.aud-board li`, which is where the chip itself is painted. No animation on this
     side: `join.js` re-renders `#view` every 5s, so a keyframe here would pop for ever. */
  grid-template-columns: 1.6rem 1fr auto minmax(4.4em, auto);
  gap: 0.6rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius);
  font-size: 0.92rem;
}

.play-board li.me { background: rgba(6, 81, 226, 0.28); }
.play-board .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
.play-board .pts { color: var(--gold); font-weight: bold; font-variant-numeric: tabular-nums; }

@media (max-width: 760px) {
  .banks-layout { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------- shared top bar */

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.55rem clamp(0.8rem, 2vw, 1.6rem);
  /* Opaque: content scrolls underneath it. */
  background: #02182f;
  border-bottom: 1px solid var(--card-border);
}

.topbar-left, .topbar-right { display: flex; align-items: center; gap: 0.8rem; }
.topbar-right { position: relative; margin-left: auto; }

.brand {
  font-family: var(--font-display);
  font-size: 1.3rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0;
}

.topnav { display: flex; gap: 0.2rem; }

.navlink {
  font-family: var(--font-ui);
  font-size: 0.86rem;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: var(--radius);
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}

.navlink:hover { color: var(--ink); background: rgba(221, 234, 255, 0.08); }
.navlink.on { color: var(--ink); background: rgba(6, 81, 226, 0.28); }

.account {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.5rem 0.25rem 0.25rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--ink);
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 0.86rem;
}

.account:hover { border-color: var(--card-border); background: rgba(221, 234, 255, 0.06); }

.avatar {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--action);
  color: #fff;
  font-size: 0.72rem;
  font-weight: bold;
  letter-spacing: 0.03em;
}

.account-menu {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  min-width: 230px;
  display: flex;
  flex-direction: column;
  padding: 0.35rem;
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
}

.account-menu-head { padding: 0.5rem 0.6rem 0.6rem; border-bottom: 1px solid var(--card-border); }

/* The account actions read as one block; the outbound spectator link stands on its own below it. */
.account-menu-group {
  display: flex;
  flex-direction: column;
  padding-bottom: 0.35rem;
  margin-bottom: 0.35rem;
  border-bottom: 1px solid var(--card-border);
}

.account-menu-name { font-weight: bold; }
.account-menu-email, .account-menu-role { font-size: 0.76rem; color: var(--muted); }
.account-menu-role { color: var(--gold); }

.account-menu-item {
  text-align: left;
  font-family: var(--font-ui);
  font-size: 0.86rem;
  color: var(--ink);
  text-decoration: none;
  background: none;
  border: 0;
  border-radius: 6px;
  padding: 0.5rem 0.6rem;
  cursor: pointer;
}

.account-menu-item:hover { background: rgba(6, 81, 226, 0.3); }
.account-menu-item.danger { color: #ffb4ae; }
.account-menu-item.danger:hover { background: rgba(211, 47, 47, 0.35); }

/* A footnote, not an action: it is what somebody reads out when reporting a bug. */
.account-menu-version {
  padding: 0.45rem 0.6rem 0.2rem;
  font-size: 0.72rem;
  color: var(--muted);
}

.page-title {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.04em;
  font-size: 1.35rem;
  padding: 1.1rem clamp(0.8rem, 2vw, 1.6rem) 0.9rem;
}

.page { min-height: 100%; }
.banks-page, .banks-layout, .invites-layout { padding-inline: clamp(0.8rem, 2vw, 1.6rem); }
.banks-layout { padding-bottom: 2rem; }

/* ---------------------------------------------------------------- icon buttons */

.icon-btn .icon {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}

.icon-btn.danger { color: var(--muted); }
.icon-btn.danger:hover { color: #ff8a80; background: rgba(211, 47, 47, 0.18); }

/* ---------------------------------------------------------------- people & invitations */

.invites-layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.2rem;
  align-items: start;
  padding-bottom: 2rem;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.1rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.card h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  letter-spacing: 0.04em;
}

.card label { font-size: 0.8rem; color: var(--muted); margin-top: 0.4rem; }
.card input:not([type="checkbox"]) {
  font-family: var(--font-ui);
  font-size: 0.95rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.6);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.5rem 0.65rem;
}
.card button[type="submit"] { margin-top: 0.8rem; }

.itable { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.itable th {
  text-align: left;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: 0 0.7rem 0.4rem 0;
  white-space: nowrap;
}
.itable td { padding: 0.4rem 0.7rem 0.4rem 0; border-top: 1px solid var(--card-border); }
.itable .row-actions { text-align: right; }

.people { list-style: none; display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.86rem; }
.people li { display: flex; align-items: center; gap: 0.5rem; }
.people .who { font-weight: bold; }

.issued {
  margin-top: 0.9rem;
  padding: 0.8rem;
  background: rgba(6, 81, 226, 0.16);
  border: 1px solid var(--action);
  border-radius: var(--radius);
  font-size: 0.84rem;
}

.issued-row { display: flex; gap: 0.4rem; margin: 0.5rem 0; }
.issued-row input {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.76rem;
}

/* ---------------------------------------------------------------- tournament sharing */

.share-badge { position: relative; }

.share-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  width: min(340px, 84vw);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.9rem;
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  text-align: left;
}

.share-panel h3 { font-family: var(--font-display); font-weight: 500; font-size: 0.95rem; }

.audience-label { font-size: 0.72rem; color: var(--muted); }
.audience-label b { color: var(--gray-blue); }

/* ---------------------------------------------------------------- audience result, big screen */

/* How much of the room is in, while the market is open. Under the BUY/SELL cards and deliberately
   quiet: the players are looking at this screen too, so it must not compete with their decision or
   the question. Tabular figures keep it from twitching as the count ticks up. */
.aud-live {
  font-size: clamp(0.72rem, 1.1vw, 0.95rem);
  letter-spacing: 0.04em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The slot is in the DOM for the whole phase; `hidden` is how it says "no audience to count". */
.aud-live[hidden] { display: none; }
.aud-live-bolt { color: var(--gold); }

/* How the room traded, on a projector, at the back of a room — this is one of the best moments in
   the game and it used to be set at web scale (0.74–0.82rem type in a fixed 26px bar) while every
   neighbour in the same panel clamped up to 20–144px. It sizes off `vh` rather than `--ovw`
   deliberately: it is the last block in the reveal's vertical stack, so it should grow into
   whatever height is left over and stand down on a short laptop rather than push the Next button
   under the fold. Green = BUY, red = SELL, as everywhere else. */
.aud-result {
  width: min(760px, 88vw);
  margin: 0.3rem auto 0.1rem;
  display: grid;
  grid-template-areas: "split" "legend" "board";
  gap: clamp(0.3rem, 0.9vh, 0.8rem);
}

.aud-split {
  grid-area: split;
  display: flex;
  height: clamp(24px, 4.4vh, 52px);
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--card-border);
}

.aud-bar {
  display: grid;
  place-items: center;
  font-size: clamp(0.78rem, 2.2vh, 1.6rem);
  font-weight: bold;
  letter-spacing: 0.04em;
  white-space: nowrap;
  transition: width 0.5s ease;
}

.aud-bar.buy { background: var(--buy-deep); }
.aud-bar.sell { background: var(--sell-deep); }
/* Exactly one line, always — this is the only rule here that is load-bearing rather than taste.
   The reveal's vertical stack clears `#hints` by 3.4px at 1920x1080 with a five-line question: a
   wrap here is ~21px of projector type and the Next button goes under the fold. `nowrap` makes
   wrapping impossible; the type is then sized so the longest string it can hold cannot overflow
   either. Worst case is a 1000-spectator room — "184 audience trades · 96 bought · 88 sold", ~44
   characters — against `.aud-result`'s 760px, which fits several times over.

   The legend used to carry the scoring rule as well (`right +150…+50 · wrong −75…−25`), and the `vh`
   term was cut 1.8 → 1.55 to buy the width for it. The rule is gone from the reveal — a room reading
   a scoreboard does not need the price list, and it is on the phone at the moment it matters — but
   the term stays at 1.55, because putting it back costs exactly the vertical slack there is: 1.8vh
   at 1080p is 19.4px against 16.7px, and at `line-height: 1.25` that is +3.4px into a 3.4px budget.

   The `vw` term is what stands the type down on a narrow window instead of overflowing it, and the
   ellipsis is the backstop: the side-by-side breakpoint at its narrowest (a ~900x700 browser window,
   never the projector and never a phone) can still run out of room, and there a trimmed legend is
   strictly better than a nowrap string widening the grid into a horizontal scrollbar. */
.aud-split-legend {
  grid-area: legend;
  font-size: clamp(0.6rem, min(1.55vh, 1.9vw), 1.15rem);
  line-height: 1.25;
  color: var(--muted);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.aud-board {
  grid-area: board;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: clamp(0.1rem, 0.35vh, 0.3rem);
  width: min(26em, 100%);
  margin: 0.15rem auto 0;
}
.aud-board li {
  display: grid;
  /* Four tracks, the last one for the reveal's movement chip — reserved whether or not a given row
     has one. Explicit tracks exist regardless of whether a child occupies them, which is what keeps
     `.pts` in column 3 on every row: without the track, rows carrying a badge would push the total
     left while rows without it left theirs at the right edge, and a projector would show a ragged
     points column. Same reasoning as `.aud-live` keeping its box.

     It comes out of the name's `1fr`, not out of the board's width, so the 1366x768 side-by-side
     layout below is unmoved and the reveal's 3.4px of vertical slack is untouched. */
  grid-template-columns: 1.8em 1fr auto minmax(4.4em, auto);
  gap: 0.6em;
  font-size: clamp(0.82rem, 2.1vh, 1.45rem);
  line-height: 1.3;
  padding: 0.1rem 0.3rem;
}
.aud-board .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
/* Gold whatever the sign. A total below zero is a real outcome now, and it was briefly painted in the
   sell red — but red on the total makes the standing itself read as the news, when the news is what
   the row just *did*. So the two axes stay separated: the total is gold and says where you stand, the
   chip beside it is green or red and says what just happened. The minus sign is what marks a total
   under water, and it carries that on its own for anyone not reading colour at all. */
.aud-board .pts { color: var(--gold); font-weight: bold; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- the movement chip
   `+150 ▲2` beside a total: what this row just scored at the reveal, and how many ranks it just
   took. The reason the reveal is worth watching rather than reading, so it is painted here for the
   projector and reused at phone scale by `.play-board` below.

   Named `.gain*` and nothing shorter. Not `.live-*`, where three classes have already collided
   (`.live-badge` / `.live-chip` / `.live-dot`), and not bare `.up` / `.down` / `.move`, which are
   exactly the names a later feature reaches for. Both boards scope their own paint, so the two
   scales stay independent.

   Colour follows the house convention and matches the `▲ it's higher` / `▼ it's lower` trade cards
   two blocks up the same panel: green up, red down. `--gold` stays with `.pts`, the total; `--warn`
   is left alone entirely, since amber is the bracket's *live* badge. And colour is never the only
   carrier — the triangle, the digits and the `aria-label` each say the direction on their own. */
.aud-board .gain,
.play-board .gain {
  display: inline-flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.4em;
  font-weight: bold;
  font-variant-numeric: tabular-nums;
  /* Under the row it annotates, never level with it: 18.6px against the row's 22.7px at 1080p. */
  font-size: 0.82em;
}

/* Points *can* fall at a reveal now: a wrong pick costs a share of what a right one would have paid.
   So the figure carries its own sign (`signedPoints`) and this class no longer means "a gain" — the
   green is the positive case and `.gain-loss` is the negative one. Two independent axes on one chip:
   the figure is what you scored, the triangle is where you ended up, and a row can perfectly well
   score points and still fall a rank. Painting them from one class is what would lie. */
.aud-board .gain-pts,
.play-board .gain-pts { color: var(--buy); }
.aud-board .gain-pts.gain-loss,
.play-board .gain-pts.gain-loss { color: var(--sell); }
.aud-board .gain-up .gain-move,
.play-board .gain-up .gain-move { color: var(--buy); }
.aud-board .gain-down .gain-move,
.play-board .gain-down .gain-move { color: var(--sell); }
/* `.gain-flat` is a row that scored while everyone above it scored too: the `+N` with no triangle,
   which is the honest thing to draw. It needs no paint of its own — the hook is for the day it does. */

/* The chip arrives after the answer, not with it: 350ms lets the count-up own the first beat, and
   `both` keeps it invisible until then.

   Two things this depends on, both of which are easy to break from JS:
   1. the board is mounted once per `reveal:<match>:<market>` and never patched — see the comment on
      `patchOverlay`. Adding `.gain` (or the board) to the patch path restarts every one of these on
      every connect/disconnect frame;
   2. the delay is fixed and knows nothing about `.counting` / `.settled`. Coupling it to that toggle
      would tie the animation to a class that flips mid-phase. */
@keyframes gain-pop {
  from { opacity: 0; transform: translateY(0.5em) scale(0.85); }
  to { opacity: 1; transform: none; }
}
.aud-board .gain { animation: gain-pop 320ms cubic-bezier(0.2, 0.9, 0.3, 1.4) 350ms both; }

/* Wide but short — a 1366x768 laptop, or a maximised browser on a 1080p screen — cannot stack the
   split, its legend and a five-row leaderboard under the answer and still keep the Next button
   above the fold. Side by side costs nothing and hides nothing, and a projector in fullscreen at
   1080 never reaches this breakpoint, so the stacked, centred reveal is what the room sees. */
@media (min-width: 900px) and (max-height: 950px) {
  .aud-result {
    width: min(1100px, 92vw);
    grid-template-areas: "split board" "legend board";
    grid-template-columns: minmax(0, 1fr) auto;
    /* The board spans both rows and is the taller of the two columns; `1fr` on the second row is
       what makes it absorb the slack, so the legend stays under its own bar instead of drifting
       down to the board's centre. */
    grid-template-rows: auto 1fr;
    align-items: start;
    column-gap: clamp(1rem, 3vw, 2.5rem);
  }
  .aud-board { margin: 0; align-self: center; }
}

/* ---------------------------------------------------------------- spectator additions */

.play-who, .play-tally { display: flex; align-items: baseline; gap: 0.45rem; }
.play-tally { flex-direction: column; align-items: flex-end; gap: 0.1rem; }
/* Name, rank and the live chip on a phone: the chip drops to a second line rather than pushing the
   score off the row. */
.play-who { flex-wrap: wrap; }

.play-rank {
  font-size: 0.74rem;
  color: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: 0.05rem 0.4rem;
}

.play-points {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.bonus { margin-top: 1rem; }
/* Keeps its 40px so the buttons do not jump under the thumb the moment a tap lands — the same
   reason .aud-live keeps its box on the projector. visibility, never display. */
.bonus.is-spent { visibility: hidden; }

.bonus-track {
  height: 6px;
  border-radius: 999px;
  background: rgba(221, 234, 255, 0.14);
  overflow: hidden;
}

.bonus-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--gold), var(--warn));
  /* Drains from whatever width the render set to nothing over the time actually left, so the bar is
     smooth between the 250ms re-renders and lands on empty even if they stop. The duration is set
     inline by renderBonusBar; without it nothing moves, which is the safe failure. */
  animation: bonus-drain linear forwards;
}

@keyframes bonus-drain { to { width: 0; } }

/* One line and the same height it has always been. It names both sides of the scoring now — "Right
   +130 · wrong −65 · faster is bigger, both ways" — and this box growing is what pushed the BUY/SELL
   cards down under the thumb mid-market. `nowrap` makes a wrap impossible; the `vw` term shrinks the
   type on a narrow phone rather than overflowing, so a 320px screen still holds the whole rule. The
   ellipsis is the backstop for a screen narrower than any phone sold. */
.bonus-label {
  display: block;
  margin-top: 0.3rem;
  font-size: clamp(0.62rem, 2.9vw, 0.74rem);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--muted);
}
.bonus-label b { color: var(--gold); font-variant-numeric: tabular-nums; }

.play-verdict.right { color: var(--buy); }
.play-verdict.wrong { color: var(--sell); }

.play-earned {
  margin-left: 0.4rem;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}
/* A wrong pick costs points, so this figure can be a loss — and then it belongs to the verdict's own
   red rather than to the gold a score is painted in. */
.play-earned.lost { color: var(--sell); }

.play-split { margin-top: 0.9rem; }

.split-bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: 0.3rem;
}

.split-buy { background: var(--buy); }
.split-sell { background: var(--sell); }

.join-lead a { color: var(--accent); }
/* The quiet footer both spectator views end on: the way out of this screen, nowhere near a
   BUY/SELL button someone is tapping in a hurry. */
.join-foot, .play-foot {
  margin-top: 1.4rem;
  font-size: 0.78rem;
  color: var(--muted);
  text-align: center;
}
.join-foot a, .play-foot button { color: var(--accent); text-decoration: underline; }

/* ---------------------------------------------------------------- tournament sub-bar */

/* The bracket's own controls sit below the shared bar, so the chrome never changes shape. */
.tbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.5rem clamp(0.8rem, 2vw, 1.6rem);
  border-bottom: 1px solid var(--card-border);
  background: rgba(1, 17, 36, 0.3);
}

/* The name, and under it in small type the format the bracket plays to — a fact the room reads once
   and the host never acts on, so it belongs beneath the name rather than in the counters. */
.tident { display: flex; flex-direction: column; min-width: 0; }
.tname-row { display: flex; align-items: center; gap: 0.15rem; }
.tbar .tname { font-size: 1.15rem; white-space: nowrap; }
.tbar .tformat { font-size: 0.7rem; color: var(--muted); letter-spacing: 0.04em; white-space: nowrap; }
/* Beside the name rather than in `.tbar-actions`, where it would be a pencil among badges with
   nothing saying what it edits. Quiet until hovered: it is not a thing to press during a match. */
.tbar .tname-edit { padding: 0.25rem; opacity: 0.5; }
.tbar .tname-edit:hover { opacity: 1; color: #fff; background: rgba(6, 81, 226, 0.55); }
/* Fullscreen is the room's screen: it shows the game, not the controls for editing it. */
body.is-fullscreen .tbar .tname-edit { display: none; }
.tbar .tmeta { flex: 1 1 14rem; }

.tbar-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  /* Never squeezed: the join code and the controls keep their size and the metrics give way. */
  flex: 0 0 auto;
}

/* ---------------------------------------------------------------- dialogs */

.dlg {
  width: min(440px, 92vw);
  /* The `* { margin: 0 }` reset kills the UA's `margin: auto`, which is what centres a modal
     dialog inside its inset-0 box; put it back rather than positioning dialogs by hand. */
  margin: auto;
  padding: 0;
  color: var(--ink);
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.dlg::backdrop { background: rgba(1, 12, 26, 0.72); }
.dlg form { display: flex; flex-direction: column; gap: 0.6rem; padding: 1.3rem; }

.dlg h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.15rem;
  letter-spacing: 0.03em;
}

.dlg-body { font-size: 0.9rem; line-height: 1.5; color: var(--gray-blue); }
.dlg-fields { display: flex; flex-direction: column; gap: 0.3rem; }
.dlg-fields label { font-size: 0.8rem; color: var(--muted); margin-top: 0.5rem; }

.dlg input {
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.55rem 0.7rem;
}

/* Same paint as a dialog's text field, so a form of choices does not read as a different form. */
.dlg select {
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.55rem 0.7rem;
}

.dlg input:focus-visible,
.dlg select:focus-visible { outline: 2px solid var(--action); outline-offset: 1px; }
.dlg select:disabled { opacity: 0.5; }

/* A tick is a row, not a labelled box above a field: `.dlg input`'s padding would otherwise
   inflate the checkbox itself to the size of a text input. */
.dlg-check { display: flex; align-items: flex-start; gap: 0.5rem; }
.dlg-check input[type="checkbox"] { flex: none; width: 1rem; height: 1rem; padding: 0; }
.dlg-check .dim { display: block; }
.dlg-actions { display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 0.8rem; }
.dlg-copy { display: flex; gap: 0.5rem; }
.dlg-copy input {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
}

.btn-danger { background: var(--sell-deep); border-color: var(--sell); color: #fff; }
.btn-danger:hover { background: var(--sell); }

/* ---------------------------------------------------------------- people page */

.stack {
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
  padding: 0 clamp(0.8rem, 2vw, 1.6rem) 2rem;
  max-width: 860px;
}

.card-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }

.ilist, .plist { list-style: none; display: flex; flex-direction: column; gap: 0.35rem; }

.irow, .prow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  background: rgba(1, 17, 36, 0.4);
  border-radius: var(--radius);
}

.irow-main, .prow-main { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 0.5rem; }
.irow-for, .prow-name { font-weight: bold; }
.prow-seen { font-size: 0.76rem; white-space: nowrap; }
/* The signed-in account gets no trashcan of its own, and its Edit would otherwise sit 45px right of
   everybody else's. 35px is the trashcan measured; a browser check pins the column it keeps. */
.prow-nodelete { width: 35px; }
.avatar.small { width: 24px; height: 24px; font-size: 0.62rem; }

.brow-access { font-size: 0.74rem; color: var(--accent); margin-top: 0.15rem; }

/* ---------------------------------------------------------------- page heads */

.page-head {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 1.1rem clamp(0.8rem, 2vw, 1.6rem) 0.9rem;
}

.page-head .page-title { padding: 0; }
.page-actions { display: flex; align-items: center; gap: 0.6rem; margin-left: auto; }

/* The tournament list is a card in the standard stack, like every other page's content — but the
   only one whose content is a table, so it gets the full width rather than the reading measure the
   rest of the console is set to. Width still belongs to `.stack`, never to the page div. */
.stack-wide { max-width: none; }

/* ---------------------------------------------------------------- question editing */

.qrow.editing { background: rgba(6, 81, 226, 0.16); }

.qedit, .account-edit {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  width: 100%;
}

.qedit input, .account-edit input:not([type="checkbox"]), .irow-new input:not([type="checkbox"]) {
  font-family: var(--font-ui);
  font-size: 0.9rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.4rem 0.55rem;
}

.qe-question { flex: 1 1 18rem; }
.qe-answer { flex: 0 0 7rem; }
.qe-context { flex: 1 1 100%; }
.qedit-actions, .prow-actions { display: flex; gap: 0.4rem; margin-left: auto; }
.btn-inline.primary { color: var(--ink); background: var(--action); padding: 0.3rem 0.7rem; }

.add-questions { display: flex; flex-direction: column; gap: 0.8rem; }

.one-question {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.one-question label { font-size: 0.8rem; color: var(--muted); margin-top: 0.4rem; }
.one-question input {
  font-family: var(--font-ui);
  font-size: 0.95rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.6);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.5rem 0.65rem;
  width: 100%;
}
.one-question button { margin-top: 0.8rem; align-self: flex-start; }
.oq-row { display: flex; gap: 0.7rem; align-items: flex-end; }
.oq-row > div { display: flex; flex-direction: column; gap: 0.3rem; }
.oq-grow { flex: 1; }

.bulk summary { cursor: pointer; color: var(--accent); font-size: 0.86rem; padding: 0.3rem 0; }
.bulk .new-question, .bulk .import-json { margin-top: 0.6rem; }
.import-json label { display: block; font-size: 0.8rem; color: var(--muted); margin-bottom: 0.3rem; }
input[type="file"] { font-size: 0.82rem; color: var(--muted); }

/* ---------------------------------------------------------------- invitations, inline */

.irow { flex-direction: column; align-items: stretch; gap: 0.45rem; }
.irow-head { display: flex; align-items: center; gap: 0.6rem; }
.irow-head .irow-for { flex: 1; min-width: 0; }
.irow-link { display: flex; gap: 0.4rem; }

.invite-link {
  flex: 1;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: var(--gray-blue);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.35rem 0.5rem;
}

.irow-new { background: none; border: 1px dashed var(--card-border); }

.irow-new form { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }

/* The row is a wrapping flex row, so this basis is a width — never let it become a column. */
.irow-new form .email-affix { flex: 1 1 14rem; font-size: 0.9rem; }
.prow.editing { background: rgba(6, 81, 226, 0.16); }
.ae-name { flex: 1 1 12rem; }

/* ---------------------------------------------------------------- sub-bar, icon-sized */

.tbar-actions .icon-btn { padding: 0.35rem; }

.audience-badge {
  gap: 0.4rem;
  padding: 0.2rem 0.5rem;
}

.audience-count {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-variant-numeric: tabular-nums;
  /* `500/500 · 500⚡` is the widest this ever gets; wrapping it would grow the whole bar a line. */
  white-space: nowrap;
}

.audience-count .icon { width: 13px; height: 13px; }
.share-panel .shares { border: 0; background: none; padding: 0; }

/* ---------------------------------------------------------------- sound */

.sound-badge { position: relative; }

/* Which half of the speaker is drawn *is* the state: the button's markup is never rebuilt, only
   classed, so that a frame arriving mid-drag cannot take the volume slider out from under the
   pointer (see syncSoundControls). */
.sound-badge .wave { display: none; }
.sound-badge .icon-btn.on { color: var(--gold); }
.sound-badge .icon-btn.on .wave { display: inline; }
.sound-badge .icon-btn.on .mute { display: none; }

.sound-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  width: min(320px, 84vw);
  padding: 0.9rem;
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  text-align: left;
}

.sound-head { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }

.sound-title {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
}

.sound-switch { min-width: 3.6rem; }
.sound-switch.on { border-color: var(--gold); color: var(--gold); }

/* Four rows — the overall level and the three groups — on one grid, so every row's label, meter and
   read-out line up. Five cells per row, emitted by levelRow(). */
.sound-levels {
  display: grid;
  grid-template-columns: auto auto 1fr auto auto;
  align-items: center;
  gap: 0.35rem 0.45rem;
}

.sound-label { font-size: 0.78rem; color: var(--muted); }

/* Bare glyphs read as decoration next to the meter, so the two steppers are given an edge. */
.sound-levels .icon-btn {
  min-width: 1.75rem;
  padding: 0.1rem 0;
  border: 1px solid var(--card-border);
  border-radius: 5px;
  font-size: 1rem;
  line-height: 1.4;
  text-align: center;
}
.sound-levels .icon-btn:hover { border-color: var(--gold); color: var(--gold); background: none; }

/* A read-out, not a control: the two buttons beside it are what move the level, so that a frame
   arriving cannot interrupt a gesture (see soundPanel). */
.sound-meter {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: rgba(221, 234, 255, 0.14);
}

/* A group's bar runs to 200%, so it carries a notch at the level it was built at — without it, a
   half-filled bar at the default 100% reads as "half volume". */
.sound-meter.wide::after {
  content: "";
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 50%;
  width: 1px;
  background: rgba(221, 234, 255, 0.5);
}

.sound-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--action);
  transition: width 0.12s ease;
}

.sound-pct {
  min-width: 2.8rem;
  text-align: right;
  font-size: 0.78rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The levels still move while the sound is off — they are what the next `M` will play at — so they
   are dimmed rather than disabled. */
.sound-panel.is-off .sound-levels { opacity: 0.5; }
.sound-panel p { font-size: 0.75rem; line-height: 1.5; }

/* ---------------------------------------------------------------- fullscreen */

/* On a projector the console chrome is dead weight; the sub-bar keeps the join code visible. */
body.is-fullscreen .topbar { display: none; }
body.is-fullscreen #hints { display: none; }

/* ---------------------------------------------------------------- spectator page */

.btn-join {
  /* Thumb-sized: people are holding a phone at the back of a room. */
  min-height: 64px;
  font-size: 1.15rem;
  letter-spacing: 0.04em;
}

.join-note { font-size: 0.78rem; color: var(--muted); line-height: 1.45; margin-top: 0.5rem; }
/* Sized on the wrapper, so the local part and the @imc.com suffix scale together. */
.join-card .email-affix { font-size: 1.05rem; }

/* The console's logo is sized for a projector; here it has to leave the code field above the fold
   on a phone, so the same gradient wordmark just runs smaller. */
body.audience .logo { font-size: clamp(2.4rem, 14vw, 4rem); }

/* Losing the connection is not a footnote for someone holding a phone at the back of the room. */
.conn-lost {
  padding: 0.55rem 0.8rem;
  border-radius: var(--radius);
  background: rgba(255, 167, 38, 0.14);
  border: 1px solid rgba(255, 167, 38, 0.45);
  color: var(--warn);
  font-weight: bold;
  font-size: 0.9rem;
  text-align: center;
}

/* The spectator's status chip: a dot and a word, inline beside their name in .play-who. Quiet by
   design — an actual drop raises the .conn-lost banner, so this only has to be legible. */
.live-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  color: var(--muted);
}

.live-chip i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
  flex: 0 0 auto;
}

.live-chip.on { color: var(--buy); }
.live-chip.on i { background: var(--buy); animation: live-pulse 2s ease-in-out infinite; }
.live-chip.off { color: var(--warn); }
.live-chip.off i { background: var(--warn); }

/* .play-who aligns its items on the baseline, and an inline-flex box takes its baseline from the
   dot; centring the chip against the name is what puts the label on the same line. */
.play-who .live-chip { align-self: center; }

/* ---------------------------------------------------------------- spectator list, host side */

/* A column so the rows scroll and the heading with the two orderings stays put: at 500 spectators
   a popover that scrolls as a whole loses its own controls off the top. */
.spectator-list {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  width: min(320px, 84vw);
  max-height: 60vh;
  overflow: hidden;
  padding: 0.8rem;
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  text-align: left;
}

.spectator-list h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
}

/* The count on the left, the two orderings on the right. */
.spectator-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

/* The segmented control at popover scale. */
.seg-small .seg-btn { padding: 0.15rem 0.5rem; font-size: 0.68rem; }

/* The scrolling part. `min-height: 0` is what lets it shrink inside the capped popover. */
.spectator-list ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-height: 0;
  overflow: auto;
}

/* `flex: none`: rows keep their own height and overflow the popover instead of being squashed. */
.spectator-list li {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
}
/* Faded, but still a row somebody has to read to choose between kick and remove. 0.55 plus a grey
   dot left an absent spectator barely there at all, which is the opposite failure from the one
   before it — the fade is a supporting signal, the word in `.seen` is the one that says it. */
.spectator-list li.off { opacity: 0.72; }
.spectator-list .who { font-weight: bold; white-space: nowrap; }

/* The email gives way first — the dot, the name and the score all have to stay readable. */
.spectator-list .dim {
  margin-left: auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.74rem;
}

.spectator-list .pts { color: var(--gold); font-weight: bold; font-variant-numeric: tabular-nums; }

/* The roster bullet: connected or not, one per row. Self-contained — it used to borrow its round
   corners from the bracket badge back when both were called .live-dot, and the three names stay
   apart (see `.live-badge` / `.live-chip`).

   Filled-and-haloed against hollow-and-grey, not one colour against another: the row now carries
   four action icons, and 7px of hue on its own was not enough to carry "here" versus "gone" past
   them. Off was `--warn` amber, which is the attention colour the bracket uses for a *live* match —
   on this row it read as "something is happening here" rather than "nobody is there". */
.spectator-list .live-dot {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--muted);
}

.spectator-list .live-dot.on {
  background: var(--buy);
  box-shadow: 0 0 0 3px rgba(102, 187, 106, 0.22);
}
.spectator-list .live-dot.off {
  background: transparent;
  border: 1.5px solid var(--muted);
}

/* The audience badge is a popover anchor now. */
.audience-badge { position: relative; }
.audience-count { border: 0; color: var(--muted); font-size: 0.74rem; }
.audience-count:hover { color: var(--ink); }

/* The code is what the room squints at from the back — and it is useless without the address, so
   that is on screen too rather than hidden in a tooltip nobody on a projector can hover. */
.audience-code { font-size: 1.7rem; letter-spacing: 0.2em; }
body.is-fullscreen .audience-code { font-size: 2.1rem; }
.audience-where { font-size: 0.9rem; color: var(--gray-blue); letter-spacing: 0.02em; }
body.is-fullscreen .audience-where { font-size: 1.1rem; }

/* ---------------------------------------------------------------- misc console polish */

.bulk h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  margin-bottom: 0.4rem;
}

.bank-rename { display: flex; gap: 0.4rem; align-items: center; flex: 1; }
.bank-rename .br-name {
  flex: 1;
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.4rem 0.55rem;
}

.account-edit { align-items: center; }
.account-edit .ae-name { flex: 0 1 12rem; }
.ae-email { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.share-form .share-email {
  flex: 1;
  min-width: 0;
  font-family: var(--font-ui);
  font-size: 0.86rem;
  color: var(--ink);
  background: rgba(1, 17, 36, 0.7);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 0.35rem 0.5rem;
}

/* ---------------------------------------------------------------- email fields

   Every address in the app is typed as a local part with the domain fixed beside it (emailField in
   util.js). The frame moves from the input to the wrapper so the pair reads as one control, while
   the input keeps the padding its own page gives it — that is what keeps the field the same height
   as the ones above and below it at all three scales in play. Styled by name: there is deliberately
   no `.card form` rule, and a flex-direction added there would turn every nested basis into a
   height. This block comes last so it outranks the per-page input rules it has to undo. */

.email-affix {
  display: flex;
  align-items: center;
  min-width: 0;
  background: rgba(1, 17, 36, 0.6);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
}

.email-affix:focus-within { outline: 2px solid var(--action); outline-offset: 1px; }

.email-affix input.email-local {
  flex: 1 1 auto;
  min-width: 0;
  padding-right: 0.15em;
  font: inherit;
  background: none;
  border: 0;
  border-radius: 0;
}

/* The wrapper carries the focus ring for the whole control. */
.email-affix input.email-local:focus-visible { outline: none; }

/* Not editable, and never the part that shrinks or wraps. */
.email-domain {
  flex: 0 0 auto;
  padding-right: 0.7em;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- question pool + choice dialog
   Appended as one block for the question-management and undo work; nothing above was touched. */

/* Same popover geometry as .share-panel and .spectator-list — a fourth badge in the same row must
   not open in a fourth shape. The scroll cap is on the duplicate list, since that is the part that
   grows with the bracket. */
.questions-badge { position: relative; }

.questions-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(360px, 86vw);
  max-height: 70vh;
  overflow: auto;
  padding: 0.9rem;
  background: #04203f;
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
  text-align: left;
}

.questions-panel h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
}

/* The repeats, worst case a long list, so it scrolls inside the popover rather than pushing the
   two buttons off the bottom of it. */
.qdupes {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  max-height: 30vh;
  overflow: auto;
  font-size: 0.78rem;
}

.qdupes li { display: flex; gap: 0.5rem; justify-content: space-between; }
.qdupes .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qdupes .pts { flex: 0 0 auto; color: var(--warn); font-variant-numeric: tabular-nums; }

/* The badge says something is wrong before the panel is opened. */
.icon-btn.warn { color: var(--warn); }

/* A dialog offering three answers rather than yes/no: stacked, each with its consequence under it,
   because the host is choosing between outcomes and a row of verbs reads as a toolbar. */
.dlg-choices { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.4rem; }

.dlg-choice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  text-align: left;
  padding: 0.6rem 0.8rem;
}

.dlg-choice .choice-label { font-weight: 600; }
.dlg-choice .choice-hint { font-size: 0.76rem; font-weight: 400; opacity: 0.8; line-height: 1.35; }

/* ---------------------------------------------------------------- host controls on the roster

   The spectator list gained four verbs per row (rename / kick / block / remove) plus the last-seen
   the payload has always carried and nothing ever drew. That is what the widening is for: at the
   original 320px the email and the score were already fighting for the same slack.

   `.dim` (the address) keeps `margin-left: auto`, so it is still the column that gives way first —
   the name, the age of the last ping, the score and the buttons all have to stay readable while
   somebody is deciding whether a row is a dropped phone or a squatter. */

.spectator-list { width: min(420px, 92vw); }

/* Whether the room can still hear them, and since when. Fixed-ish width so the column of scores
   after it does not jitter as rows tick over from "left just now" to "left 2m ago", and wide enough
   for the longest of those — it is the one part of the row that has to be readable at a glance. */
.spectator-list .seen {
  flex: 0 0 auto;
  min-width: 4.9rem;
  text-align: right;
  white-space: nowrap;
  color: var(--muted);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
}

/* The words carry the row now, so they are the one thing the fade below must not swallow. */
.spectator-list .seen.gone { color: var(--gray-blue); }

/* Row-scale icon buttons: the popover cannot afford the 0.35rem/0.6rem padding of a toolbar. */
.spectator-actions { flex: 0 0 auto; display: flex; gap: 0.1rem; margin-left: 0.15rem; }
.spectator-actions .icon-btn { padding: 0.2rem; }
.spectator-actions .icon-btn .icon { width: 14px; height: 14px; }

/* Blocked addresses live under the roster rather than in it: the host needs to see who they shut
   out in order to let them back in, and mixing them into the count would misreport the room. */
.spectator-list .banned-list li { opacity: 0.6; }
.spectator-list .banned-list .who { text-decoration: line-through; }

/* ---------------------------------------------------------------- the audience line's budget

   `⚡ 37 / 120 traded` sits under the BUY/SELL cards for the whole trade phase, but it used to
   `display: none` until the first spectator connected — so the panel *grew* mid-market, the moment
   somebody joined, and at 1366x768 with a four-line question that growth was what pushed it 23px
   past the fold. A layout that changes because a stranger opened their phone is wrong twice over:
   it hides the trade cards, and it does it in front of a room.

   So the slot always occupies its line and only its content is hidden, and the question — the one
   block in the panel whose height is unbounded — gives that line back out of its own budget. The
   `vh` coefficient is raised alongside the constant so a 1080p projector is not charged for it:
   the question is still ~98px there, against the 6.4rem cap it was already hitting.

   That raised clamp now lives in `--qsize` on `.overlay`, next to `--ovw`, because the fit factor
   has to multiply exactly one declaration — a second `.question` rule down here would have been a
   ceiling the JS could not see. */

.aud-live[hidden] { display: block; visibility: hidden; }

/* The "nobody has joined yet" placeholder is an `li.dim`, so it was picking up the `margin-left:
   auto` meant for the address column and sitting hard right — which reads as a stray label rather
   than an empty list, now that a "Blocked" section can follow it. */
.spectator-list ul > li.dim { margin-left: 0; }

/* ---------------------------------------------------------------- the join screen

   The room's way in, at the size of the room: a QR code, the same link in type, the code for
   whoever would rather type it, and everyone who is already in.

   Sized off `vh` throughout, like the overlay and for the same reason — the one thing that must
   never happen is the QR growing until the names it is meant to sit above are pushed off the
   bottom. Everything here scales together out of one vertical budget. */

.join-modal {
  /* `#hints` paints over the top of this (z-index 60), exactly as it does over the overlay; in
     fullscreen the strip is gone and the reserve comes back as room. */
  padding-bottom: 2.4rem;
}
body.is-fullscreen .join-modal { padding-bottom: 0; }

.join-screen {
  width: min(1500px, 96vw);
  max-height: 100%;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: clamp(0.8rem, 3vh, 2.2rem);
  padding: clamp(0.8rem, 2.5vh, 2rem) clamp(0.8rem, 2vw, 2rem);
  animation: pop-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.15);
}

.join-invite {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1.2rem, 4vw, 4rem);
  flex-wrap: wrap;
}

/* White, and with the quiet zone the encoder already draws inside it: a QR on a dark background is
   legal and half the phones in the room will refuse to read it. */
.join-qr {
  flex: 0 0 auto;
  width: clamp(170px, 42vh, 460px);
  padding: clamp(6px, 1vh, 14px);
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
}
.join-qr svg { display: block; width: 100%; height: auto; }

.join-words {
  display: flex;
  flex-direction: column;
  gap: clamp(0.15rem, 0.9vh, 0.6rem);
  min-width: 0;
}
.join-words h2 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  font-size: clamp(0.9rem, 2.4vh, 1.7rem);
}
/* The link is the line a camera resolves and a person types, so it is the one that gets the gold.
   `break-all` because a deployment can be reached on a hostname nobody chose to be short. */
.join-link {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 4.4vh, 3.1rem);
  color: var(--gold);
  word-break: break-all;
  line-height: 1.1;
}
.join-or { color: var(--muted); font-size: clamp(0.75rem, 1.9vh, 1.2rem); }
.join-or b { color: var(--ink); font-weight: 600; }
.join-code {
  font-family: var(--font-display);
  font-size: clamp(2rem, 8.5vh, 6rem);
  letter-spacing: 0.14em;
  line-height: 1.05;
}

.join-people {
  display: flex;
  flex-direction: column;
  gap: clamp(0.3rem, 1.2vh, 0.9rem);
  border-top: 1px solid var(--card-border);
  padding-top: clamp(0.5rem, 1.8vh, 1.4rem);
}
.join-people h3 {
  font-family: var(--font-display);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-size: clamp(0.85rem, 2.2vh, 1.4rem);
}
.join-empty { color: var(--muted); font-size: clamp(0.85rem, 2vh, 1.2rem); }

/* Multi-column, filling downwards and then across, so the alphabetical run stays readable as the
   room arrives. Names are deliberately all this list carries — no score, no dot, no address: the
   question somebody is asking from across a room is "am I in?", and every other column is one more
   thing to read past. */
.join-names {
  list-style: none;
  column-width: clamp(8rem, 11vw, 13rem);
  column-gap: clamp(0.8rem, 2.5vw, 2.5rem);
  font-size: clamp(0.85rem, 2.4vh, 1.55rem);
}
.join-names li {
  padding: 0.1em 0;
  break-inside: avoid;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
