/*
 * Shared styling for all four screens.
 *
 * The big screen is projected into a dark room and read from the back, so:
 * type is enormous and sized in viewport units, contrast is high, and nothing
 * important is ever thin, grey or small. The phone screens are the opposite
 * problem — big thumb targets, one decision per screen, no scrolling needed.
 */

/*
 * `hidden` has to actually hide.
 *
 * The browser's own rule is `[hidden] { display: none }`, which any rule of
 * ours setting `display` beats on specificity — so `.minor { display:
 * inline-block }` quietly made every `hidden` button on the console visible.
 * "File the rest away" and "Clear all" sat there on a night with no photos at
 * all, and both would have failed if pressed. One line here rather than
 * remembering it at every call site.
 */
[hidden] { display: none !important; }

:root {
  --ink: #f7f7fb;
  --ink-dim: #a7a7c2;
  --bg: #07070e;
  --bg-2: #10101f;
  --panel: rgba(255, 255, 255, 0.06);
  --panel-line: rgba(255, 255, 255, 0.14);

  /* The two corner washes and the two slowly drifting blobs. Named so a look
     can change the whole mood of the room rather than just the buttons. */
  --glow-1: #241348;
  --glow-2: #07304a;
  --drift-1: rgba(255, 46, 136, 0.16);
  --drift-2: rgba(75, 216, 255, 0.14);

  --hot: #ff2e88;
  --hot-2: #ff8a3d;
  /* The far end of the logo's gradient. Its own variable rather than reusing
     --gold, because gold is the right third colour for a pink-and-orange
     record and quite wrong for a turquoise one. */
  --mark-3: #ffd23f;
  --cool: #4bd8ff;
  --gold: #ffd23f;
  --good: #2fe07a;
  --bad: #ff4d5e;

  --a: #4bd8ff;
  --b: #ffd23f;
  --c: #ff6bd6;
  --d: #7cf76b;
  /* Two more for the pick-them-all round, which shows six. Same spacing round
     the wheel so no two read as the same colour from the back of a room. */
  --e: #ff9d5c;
  --f: #b48cff;

  /* Every native control the browser draws for us — tickboxes, scrollbars,
     the open list of a dropdown — takes its colours from this. Without it the
     OS paints them for a white page and they arrive as white slabs on a dark
     one, which is exactly how the console's tickboxes and dropdowns looked. */
  color-scheme: dark;

  --radius: 18px;
  --shadow: 0 18px 60px rgba(0, 0, 0, 0.55);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  overflow: hidden;
  /* Through variables so a look can repaint the whole room. These were written
     out as hex here, which meant changing --bg for Halloween moved almost
     nothing — the two corner glows were still the ordinary purple and teal. */
  background:
    radial-gradient(120% 90% at 12% 0%, var(--glow-1) 0%, transparent 55%),
    radial-gradient(120% 90% at 88% 100%, var(--glow-2) 0%, transparent 55%),
    var(--bg);
}

/* A slow drift so a static screen never looks like it has frozen. */
body::after {
  content: '';
  position: fixed;
  inset: -30%;
  pointer-events: none;
  background:
    radial-gradient(38% 38% at 30% 30%, var(--drift-1), transparent 70%),
    radial-gradient(38% 38% at 72% 66%, var(--drift-2), transparent 70%);
  animation: drift 26s ease-in-out infinite alternate;
  z-index: 0;
}
@keyframes drift {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(3%, 3%, 0) scale(1.12); }
}

.stage {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* ------------------------------------------------------------------ shared */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.35em 0.9em;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.muted { color: var(--ink-dim); }
.hidden { display: none !important; }

.grad-text {
  background: linear-gradient(100deg, var(--hot) 0%, var(--hot-2) 45%, var(--gold) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* =================================================================== SCREEN
   The projected view. Everything here is sized in vw/vh so it fills whatever
   television or projector it lands on without anyone touching a setting. */

.screen .topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5vw;
  padding: 1.6vh 2.2vw;
  font-size: 1.5vh;
  border-bottom: 1px solid var(--panel-line);
  flex: 0 0 auto;
}
.screen .topbar .title { font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; }
.screen .topbar .right { display: flex; gap: 0.8vw; align-items: center; }

.screen .card {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2.5vh 3vw;
  min-height: 0;
  animation: cardIn 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes cardIn {
  from { opacity: 0; transform: translateY(1.2vh) scale(0.995); }
  to   { opacity: 1; transform: none; }
}

/* ---- lobby */
.lobby-grid {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 3vw;
  align-items: center;
  height: 100%;
}
.lobby h1 {
  font-size: 7.4vh;
  line-height: 0.98;
  margin: 0 0 1.6vh;
  font-weight: 900;
  letter-spacing: -0.02em;
}
.lobby .sub { font-size: 2.5vh; color: var(--ink-dim); margin-bottom: 3.5vh; }
.lobby .join-steps { display: grid; gap: 1.6vh; font-size: 2.6vh; }
.lobby .join-steps li { display: flex; gap: 1.2vw; align-items: baseline; }
.lobby .join-steps .n {
  flex: 0 0 auto;
  width: 1.9em; height: 1.9em;
  display: grid; place-items: center;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--hot), var(--hot-2));
  color: #fff; font-weight: 900; font-size: 0.8em;
}
.lobby ol { list-style: none; padding: 0; margin: 0; }

.qr-panel {
  background: #fff;
  border-radius: 26px;
  padding: 2.2vh;
  box-shadow: var(--shadow);
  display: grid;
  justify-items: center;
  gap: 1.2vh;
}
.qr-panel svg, .qr-panel img { width: 100%; height: auto; display: block; }
.qr-panel .url {
  color: #12121c;
  font-weight: 800;
  font-size: 2.1vh;
  word-break: break-all;
  text-align: center;
}

.player-strip {
  flex: 0 0 auto;
  padding: 1.4vh 2.2vw 2.4vh;
  display: flex;
  flex-wrap: wrap;
  gap: 0.8vh 0.8vw;
  align-content: flex-start;
  max-height: 26vh;
  overflow: hidden;
}
.player-chip {
  padding: 0.6vh 1.1vw;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  font-size: 2.1vh;
  font-weight: 700;
  animation: pop 380ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
@keyframes pop {
  from { opacity: 0; transform: scale(0.7); }
  to   { opacity: 1; transform: scale(1); }
}

/* ---- round intro */
.round-intro { text-align: center; }
.round-intro .kicker { font-size: 2.4vh; letter-spacing: 0.4em; text-transform: uppercase; color: var(--ink-dim); }
.round-intro h1 { font-size: 11vh; margin: 2vh 0; font-weight: 900; letter-spacing: -0.03em; line-height: 0.95; }
.round-intro .blurb { font-size: 3.4vh; color: var(--ink-dim); }
.round-intro .facts { margin-top: 5vh; display: flex; gap: 1.4vw; justify-content: center; font-size: 2vh; }

/* ---- question */
.q-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 2vw;
  margin-bottom: 2vh;
}
.q-counter { font-size: 2vh; }

.q-prompt {
  font-size: 5.6vh;
  line-height: 1.08;
  font-weight: 800;
  letter-spacing: -0.015em;
  margin: 0 0 3vh;
  flex: 0 0 auto;
}
.q-prompt.small { font-size: 4.4vh; }

/* The options stretch to fill whatever height is left, so the screen is used
   right down to the bottom edge and the text is as big as it can be. */
.options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-auto-rows: 1fr;
  gap: 1.8vh 1.8vw;
  flex: 1 1 auto;
  min-height: 0;
}
.option {
  display: flex;
  align-items: center;
  gap: 1.4vw;
  padding: 2.2vh 2vw;
  border-radius: var(--radius);
  background: var(--panel);
  border: 2px solid var(--panel-line);
  font-size: 3.9vh;
  font-weight: 700;
  transition: transform 260ms, background 260ms, border-color 260ms, opacity 260ms;
}
.option .letter {
  flex: 0 0 auto;
  width: 2em; height: 2em;
  display: grid; place-items: center;
  border-radius: 12px;
  font-weight: 900;
  color: #0a0a12;
  font-size: 0.78em;
}
.option:nth-child(1) .letter { background: var(--a); }
.option:nth-child(2) .letter { background: var(--b); }
.option:nth-child(3) .letter { background: var(--c); }
.option:nth-child(4) .letter { background: var(--d); }
.option:nth-child(5) .letter { background: var(--e); }
.option:nth-child(6) .letter { background: var(--f); }

.option.correct {
  background: rgba(47, 224, 122, 0.18);
  border-color: var(--good);
  transform: scale(1.02);
}
.option.dimmed { opacity: 0.32; }

.option .tally {
  margin-left: auto;
  font-size: 0.7em;
  color: var(--ink-dim);
  font-weight: 800;
}

/* ---- timer */
.timer-wrap { flex: 0 0 auto; display: flex; align-items: center; gap: 1.5vw; margin-bottom: 2.4vh; }
.timer-bar {
  flex: 1 1 auto;
  height: 1.8vh;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.timer-bar span {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--good), var(--gold) 55%, var(--hot));
  transform-origin: left center;
}
.timer-num {
  flex: 0 0 auto;
  font-size: 6vh;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  min-width: 2.2em;
  text-align: right;
  letter-spacing: -0.03em;
}
.timer-num.urgent { color: var(--bad); animation: pulse 700ms ease-in-out infinite; }
@keyframes pulse { 50% { transform: scale(1.08); } }

.answered-count { flex: 0 0 auto; font-size: 2.2vh; color: var(--ink-dim); font-weight: 700; }

/* ---- reveal */
.reveal-banner {
  flex: 0 0 auto;
  margin-top: 2.6vh;
  display: flex;
  align-items: center;
  gap: 1.6vw;
  padding: 2vh 2vw;
  border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(255, 46, 136, 0.22), rgba(255, 210, 63, 0.14));
  border: 2px solid rgba(255, 210, 63, 0.4);
  font-size: 3vh;
  animation: cardIn 340ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.reveal-banner .label { font-size: 0.62em; letter-spacing: 0.24em; text-transform: uppercase; color: var(--gold); font-weight: 800; }
.reveal-banner .who { font-weight: 900; font-size: 1.15em; }
.reveal-banner .pts { margin-left: auto; font-weight: 900; color: var(--gold); font-size: 1.5em; }
/* The fastest finger's own face. Sized in vh like everything else on the
   projector so it holds its proportion from a laptop to a 4K screen, and
   square-cropped because a photo from a phone is portrait and a drawn avatar
   is square — one shape, or the banner changes height depending on who won. */
.reveal-banner .fastest-face {
  flex: 0 0 auto;
  width: 11vh;
  height: 11vh;
  border-radius: 14px;
  object-fit: cover;
  background: var(--panel);
  border: 2px solid rgba(255, 210, 63, 0.55);
  animation: cardIn 420ms cubic-bezier(0.2, 0.8, 0.2, 1) 120ms backwards;
}

/* ---- leaderboard */
/* Centred vertically, so four teams look deliberate rather than stranded at
   the top, and ten still fit comfortably. */
.board { display: flex; flex-direction: column; height: 100%; justify-content: center; }
.board h1 { font-size: 6.4vh; margin: 0 0 2.5vh; font-weight: 900; letter-spacing: -0.02em; }
.board-rows { display: grid; gap: 1.1vh; align-content: start; overflow: hidden; }
.board-row {
  display: flex;
  align-items: center;
  gap: 1.6vw;
  padding: 1.5vh 1.8vw;
  border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  font-size: 3.2vh;
  font-weight: 700;
  animation: slideIn 420ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
}
@keyframes slideIn {
  from { opacity: 0; transform: translateX(-2vw); }
  to   { opacity: 1; transform: none; }
}
.board-row .pos {
  flex: 0 0 auto; width: 2em; text-align: center;
  font-weight: 900; color: var(--ink-dim); font-variant-numeric: tabular-nums;
}
.board-row .score { margin-left: auto; font-weight: 900; font-variant-numeric: tabular-nums; }
.board-row.top1 { background: linear-gradient(100deg, rgba(255, 210, 63, 0.26), rgba(255, 138, 61, 0.12)); border-color: var(--gold); }
.board-row.top1 .pos, .board-row.top1 .score { color: var(--gold); }
.board-row.top2 { border-color: rgba(255, 255, 255, 0.4); }
.board-row.top3 { border-color: rgba(255, 138, 61, 0.5); }

/* ---- winner */
.winner { text-align: center; display: grid; place-content: center; gap: 2vh; height: 100%; }
.winner .kicker { font-size: 3vh; letter-spacing: 0.5em; text-transform: uppercase; color: var(--gold); }
.winner h1 { font-size: 13vh; margin: 0; font-weight: 900; letter-spacing: -0.04em; line-height: 0.95; }
.winner .score { font-size: 7vh; font-weight: 900; }
.winner .runners { display: flex; gap: 2vw; justify-content: center; font-size: 2.6vh; margin-top: 3vh; color: var(--ink-dim); }

/* ---- connection warning */
.conn-warn {
  position: fixed;
  bottom: 2vh; left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  padding: 1vh 2vw;
  border-radius: 999px;
  background: var(--bad);
  color: #fff;
  font-weight: 800;
  font-size: 2vh;
  box-shadow: var(--shadow);
}

/* ==================================================================== PHONE
   The player's own phone. One decision per screen, thumb-sized targets, and
   no scrolling during a question — twenty seconds is not long enough to be
   hunting for a button. */

body.phone {
  overflow: auto;
  /* Through the same variables as the projector, or a themed night leaves the
     phones looking like an ordinary one — and the two screens being the same
     night is the whole point. */
  background:
    radial-gradient(110% 60% at 20% 0%, var(--glow-1) 0%, transparent 60%),
    radial-gradient(110% 60% at 80% 100%, var(--glow-2) 0%, transparent 60%),
    var(--bg);
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.phone .wrap {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: 16px;
  gap: 14px;
  max-width: 560px;
  margin: 0 auto;
}

.phone .head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 14px;
}
.phone .head .team { font-weight: 800; font-size: 17px; }
.phone .head .score { font-weight: 900; font-size: 20px; color: var(--gold); font-variant-numeric: tabular-nums; }

.phone .body { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; gap: 16px; }

.phone h1 { font-size: 30px; line-height: 1.05; margin: 0; font-weight: 900; letter-spacing: -0.02em; }
.phone h2 { font-size: 22px; margin: 0; font-weight: 800; }
.phone p { font-size: 17px; line-height: 1.45; margin: 0; color: var(--ink-dim); }

.phone input[type="text"] {
  width: 100%;
  padding: 18px 16px;
  font-size: 20px;
  font-weight: 700;
  border-radius: 14px;
  border: 2px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.08);
  color: var(--ink);
}
.phone input[type="text"]:focus { outline: none; border-color: var(--hot); }

.phone .btn {
  display: block;
  width: 100%;
  padding: 18px 16px;
  font-size: 19px;
  font-weight: 800;
  border-radius: 14px;
  border: none;
  color: #12000c;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.phone .btn:disabled { opacity: 0.5; }
.phone .btn.ghost { background: var(--panel); border: 2px solid var(--panel-line); color: var(--ink); }

/* ---- answer buttons */
/* The four buttons share whatever height is going, so they are enormous thumb
   targets and nobody has to aim in a dark pub. */
/* Two columns, in the same order and the same colours as the projector.
   A player looks up at the big screen, decides "the pink one, bottom left",
   and looks down at their phone — so the phone has to be the same picture, or
   they have to re-read four options against the clock. It was a single column,
   which meant the same four answers in a different arrangement on each screen. */
.answers {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  flex: 1 1 auto;
  grid-auto-rows: 1fr;
  min-height: 0;
}
.answer-btn {
  /* The letter above the text rather than beside it. Beside it costs 42 of the
     ~140 pixels a half-width cell has on the smallest phone, which was enough
     to break "Christmas" across two lines mid-word. The letter stays top left,
     the same letter and the same colour as the projector. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 12px;
  border-radius: 16px;
  border: 2px solid var(--panel-line);
  background: var(--panel);
  color: var(--ink);
  /* Smaller than the single column was, because half the width has to hold
     "I Wish It Could Be Christmas Everyday" without turning it into a column
     of single words. Still the biggest thing on the phone. */
  font-size: 16px;
  font-weight: 800;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 120ms, opacity 200ms, border-color 200ms, background 200ms;
  min-height: 72px;
  overflow: hidden;
}
/* break-word, not anywhere: only split a word that cannot fit a line on its
   own, so "Christmas" wraps whole instead of coming apart. */
.answer-btn .text { width: 100%; overflow-wrap: break-word; line-height: 1.22; }
.answer-btn:active { transform: scale(0.98); }
.answer-btn .letter {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 10px;
  font-weight: 900;
  color: #0a0a12;
  font-size: 16px;
}
.answer-btn:nth-child(1) .letter { background: var(--a); }
.answer-btn:nth-child(2) .letter { background: var(--b); }
.answer-btn:nth-child(3) .letter { background: var(--c); }
.answer-btn:nth-child(4) .letter { background: var(--d); }
.answer-btn:nth-child(5) .letter { background: var(--e); }
.answer-btn:nth-child(6) .letter { background: var(--f); }

.answer-btn.chosen { border-color: var(--ink); background: rgba(255, 255, 255, 0.16); }
.answer-btn.faded { opacity: 0.35; }
.answer-btn.right { border-color: var(--good); background: rgba(47, 224, 122, 0.22); }
.answer-btn.wrong { border-color: var(--bad); background: rgba(255, 77, 94, 0.2); }
.answer-btn[disabled] { cursor: default; }

/* ---- phone timer */
.phone .timer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
}
.phone .timer .bar { flex: 1 1 auto; height: 10px; border-radius: 999px; background: rgba(255,255,255,0.12); overflow: hidden; }
.phone .timer .bar span { display: block; height: 100%; width: 100%; transform-origin: left center; background: linear-gradient(90deg, var(--good), var(--gold) 55%, var(--hot)); }
.phone .timer .num { font-size: 26px; font-weight: 900; font-variant-numeric: tabular-nums; min-width: 1.8em; text-align: right; }
.phone .timer .num.urgent { color: var(--bad); }

/* ---- result panel */
.result {
  border-radius: 18px;
  padding: 22px 18px;
  text-align: center;
  border: 2px solid var(--panel-line);
  background: var(--panel);
}
.result.good { border-color: var(--good); background: rgba(47, 224, 122, 0.16); }
.result.bad { border-color: var(--bad); background: rgba(255, 77, 94, 0.14); }
.result .big { font-size: 34px; font-weight: 900; margin-bottom: 6px; }
.result .pts { font-size: 46px; font-weight: 900; color: var(--gold); font-variant-numeric: tabular-nums; }
.result .sub { color: var(--ink-dim); font-size: 16px; margin-top: 6px; }
.result .bonus { display: inline-block; margin-top: 10px; padding: 6px 14px; border-radius: 999px; background: var(--gold); color: #1a1200; font-weight: 900; font-size: 14px; }

.mini-board { display: grid; gap: 8px; }
.mini-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--panel-line);
  font-weight: 700; font-size: 16px;
}
.mini-row.you { border-color: var(--gold); background: rgba(255, 210, 63, 0.16); }
.mini-row .pos { width: 1.6em; color: var(--ink-dim); font-weight: 900; }
.mini-row .score { margin-left: auto; font-weight: 900; font-variant-numeric: tabular-nums; }

.phone .foot { flex: 0 0 auto; text-align: center; font-size: 13px; color: var(--ink-dim); padding-top: 8px; }
.phone .status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--good); margin-right: 6px; }
.phone .status-dot.off { background: var(--bad); }

/* ===================================================================== HOST
   Your own phone or laptop. Used one-handed, in the dark, while talking to a
   room — so: the primary action is always the same big button in the same
   place, and anything destructive is deliberately smaller and out of the way.

   Everything in here is private. The big screen never receives this data at
   all, so there is nothing to accidentally reveal. */

body.host { overflow: auto; background: #0a0a13; }
body.host::after { display: none; }

.host .wrap { max-width: 720px; margin: 0 auto; padding: 12px 12px 128px; display: grid; gap: 12px; }
/* Bingo's call sheet is scanned, not read, so it gets the whole screen. The
   quiz's panels keep the narrower measure — a cue you read at a glance should
   not be a metre wide. */
.host.bingo .wrap { max-width: 1280px; }

.host .bar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px; margin: -12px -12px 0;
  background: rgba(10, 10, 19, 0.94);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--panel-line);
  font-size: 13px;
}
.host .bar .where { font-weight: 800; }
.host .bar .clock { font-weight: 900; font-size: 22px; font-variant-numeric: tabular-nums; }
.host .bar .clock.urgent { color: var(--bad); }

.panel {
  border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  padding: 14px;
}
.panel h3 {
  margin: 0 0 10px;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-dim);
  font-weight: 800;
}
.panel.secret { border-color: rgba(255, 210, 63, 0.5); background: rgba(255, 210, 63, 0.09); }
.panel.secret h3 { color: var(--gold); }

.host .prompt { font-size: 20px; font-weight: 800; line-height: 1.25; margin: 0 0 12px; }

.keylist { display: grid; gap: 7px; }
/* A key row is a button now: tapping it shows who picked that answer. It still
   has to look like a row rather than a control, because it is a list you read
   far more often than you tap. */
.keyrow {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 10px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid transparent;
  color: var(--ink); font-family: inherit;
  font-size: 15px; font-weight: 600;
  cursor: default;
}
.keyrow.has-who { cursor: pointer; }
.keyrow.has-who:hover { background: rgba(255, 255, 255, 0.09); }
.keyrow .caret {
  flex: 0 0 auto; margin-left: 4px;
  color: var(--ink-dim); font-size: 13px;
  transition: transform 140ms;
}
.keyrow .caret.open { transform: rotate(180deg); }
.keyrow .letter {
  width: 26px; height: 26px; flex: 0 0 auto;
  display: grid; place-items: center; border-radius: 7px;
  font-weight: 900; font-size: 13px; color: #0a0a12; background: rgba(255,255,255,0.5);
}
.keyrow.is-correct { background: rgba(47, 224, 122, 0.2); border-color: var(--good); font-weight: 800; }
.keyrow.is-correct .letter { background: var(--good); }
.keyrow .n { margin-left: auto; color: var(--ink-dim); font-weight: 800; font-size: 13px; }

.cue { display: grid; gap: 4px; }
.cue .track { font-size: 26px; font-weight: 900; line-height: 1.1; }
.cue .artist { font-size: 19px; font-weight: 700; color: var(--gold); }
/* Auto-play did not start it. Gold rather than red: the question is up, the
   night is fine, and all this means is "use your thumb". */
.cue-failed {
  margin-top: 8px; padding: 8px 10px; border-radius: 10px;
  background: rgba(255, 210, 63, 0.14);
  border: 1px solid rgba(255, 210, 63, 0.4);
  color: var(--gold); font-size: 13px; font-weight: 700;
}
.cue-failed .tiny { display: block; color: var(--ink-dim); font-weight: 400; margin-top: 2px; }
.cue .from { font-size: 14px; color: var(--ink-dim); }

.actions {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  background: rgba(10, 10, 19, 0.96);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--panel-line);
  display: grid; gap: 8px;
  grid-template-columns: repeat(4, 1fr);
  max-width: 720px; margin: 0 auto;
}
.actions .primary {
  grid-column: 1 / -1;
  padding: 20px; font-size: 21px; font-weight: 900;
  border: none; border-radius: 14px; cursor: pointer;
  color: #12000c; background: linear-gradient(120deg, var(--hot), var(--hot-2));
  -webkit-tap-highlight-color: transparent;
}
.actions .primary:disabled { opacity: 0.45; }
.actions .minor {
  padding: 13px 6px; font-size: 13px; font-weight: 800;
  border-radius: 11px; cursor: pointer;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--panel-line);
  color: var(--ink);
}
.actions .minor.danger { color: var(--bad); border-color: rgba(255, 77, 94, 0.4); }

.plist { display: grid; gap: 6px; }
.prow {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  font-size: 15px;
}
.prow .pos { width: 1.8em; color: var(--ink-dim); font-weight: 900; font-size: 13px; }
.prow .nm { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prow .sc { margin-left: auto; font-weight: 900; font-variant-numeric: tabular-nums; }
.prow .tick { color: var(--good); font-weight: 900; }
.prow .off { color: var(--bad); font-size: 11px; }
/* A note, not an accusation — so it is gold rather than red. Red beside a name
   reads as "this one is cheating" and the app does not know that; it knows the
   phone went dark, which a call coming in also does. */
.prow .wandered-count {
  color: var(--gold);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.keywho.wandered b { color: var(--gold); }
.prow button {
  border: 1px solid var(--panel-line); background: rgba(255,255,255,0.06);
  color: var(--ink); border-radius: 8px; padding: 5px 8px; font-size: 13px; font-weight: 800; cursor: pointer;
}

.host .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.host select, .host input[type="text"], .host input[type="number"] {
  padding: 10px; border-radius: 10px; font-size: 15px;
  background: rgba(255,255,255,0.08); color: var(--ink); border: 1px solid var(--panel-line);
}
.host .tiny { font-size: 12px; color: var(--ink-dim); }
.host a { color: var(--cool); }

.toast {
  position: fixed; left: 50%; bottom: 150px; transform: translateX(-50%);
  background: var(--ink); color: #0a0a12; font-weight: 800;
  padding: 10px 18px; border-radius: 999px; z-index: 60; font-size: 14px;
  box-shadow: var(--shadow);
}

/* ============================================================= ROUND TYPES
   A round is a plugin: it brings its own media block and its own option
   layout, and nothing else on the screen has to know about it. */

/* ---- round 2: whose face is this?
   The image starts hard in on the face and pulls back over the twenty
   seconds, so an early guess is worth more. The caption stays put the whole
   time: these are illustrations, and the screen says so. */
/* A 4:3 window in the middle of the screen rather than a full-width letterbox,
   so a portrait is framed properly instead of being cropped to a strip of
   cheek when it pulls back. */
.zoom-stage {
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 2vh;
  display: flex;
  justify-content: center;
}
.zoom-frame {
  position: relative;
  height: 100%;
  aspect-ratio: 4 / 3;
  max-width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  background: #05050a;
  border: 2px solid var(--panel-line);
  box-shadow: var(--shadow);
}
.zoom-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  will-change: transform;
  /* No CSS transition: the zoom is redrawn every frame from the server's
     clock, so it stays in step with the countdown rather than drifting. */
}
.zoom-caption {
  position: absolute;
  left: 1vw;
  bottom: 1.2vh;
  padding: 0.6vh 1.1vw;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.66);
  border: 1px solid rgba(255, 255, 255, 0.28);
  font-size: 1.7vh;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: #fff;
  backdrop-filter: blur(4px);
}

/* ---- round 3: name that intro
   Nothing on this screen names the track — the host is reading the same
   screen the room is. It is a visual only. */
.intro-visual {
  flex: 1 1 auto;
  min-height: 0;
  margin-bottom: 2vh;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7vw;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  border: 2px solid var(--panel-line);
}
.intro-visual i {
  display: block;
  width: 1vw;
  height: 20%;
  border-radius: 999px;
  background: linear-gradient(180deg, var(--hot), var(--cool));
  animation: eq 1.15s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -70ms);
}
@keyframes eq {
  from { height: 7%; opacity: 0.55; }
  to   { height: 72%; opacity: 1; }
}

/* With a big picture or visual above them, the options go across in one row
   so the media keeps the height it needs. */
.type-image .options,
.type-intro .options {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: auto;
  flex: 0 0 auto;
}
.type-image .option,
.type-intro .option {
  font-size: 2.7vh;
  padding: 1.8vh 1vw;
  gap: 0.8vw;
}
.type-image .q-prompt,
.type-intro .q-prompt {
  font-size: 3.6vh;
  margin-bottom: 1.6vh;
}
.type-image .option .tally,
.type-intro .option .tally { display: none; }

/* =================================================================== EDITOR
   Where you check every question before the gig. Built for a laptop and a cup
   of tea the afternoon before, not for use in a dark room.

   The most important thing on this screen is that the correct answer is
   obvious at a glance, because the whole point of this page is catching the
   one question the AI got wrong. */

body.editor { overflow: auto; background: #0b0b14; }
body.editor::after { display: none; }

.editor .wrap { max-width: 1000px; margin: 0 auto; padding: 0 16px 120px; }

.editor .topbar {
  position: sticky; top: 0; z-index: 20;
  margin: 0 -16px 18px;
  padding: 14px 22px;
  background: rgba(11, 11, 20, 0.95);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--panel-line);
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.editor .topbar h1 { font-size: 19px; margin: 0; font-weight: 900; }
.editor .topbar .spacer { flex: 1 1 auto; }

.editor button, .editor select, .editor input, .editor textarea {
  font-family: inherit;
  font-size: 15px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--panel-line);
  border-radius: 9px;
  padding: 9px 11px;
}
.editor textarea { width: 100%; resize: vertical; line-height: 1.45; }
.editor input:focus, .editor textarea:focus, .editor select:focus { outline: 2px solid var(--hot); outline-offset: 1px; }
.editor button { cursor: pointer; font-weight: 700; }
.editor button:hover { background: rgba(255, 255, 255, 0.14); }
.editor button.go {
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  border: none; color: #12000c; font-weight: 900; padding: 11px 20px;
}
.editor button.go:disabled { opacity: 0.5; cursor: default; }
.editor button.danger { color: var(--bad); border-color: rgba(255, 77, 94, 0.4); }
.editor button.small { padding: 5px 9px; font-size: 13px; }

.round-block { margin-bottom: 26px; }
.round-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(100deg, rgba(255, 46, 136, 0.2), rgba(75, 216, 255, 0.1));
  border: 1px solid var(--panel-line);
  border-bottom: none;
}
.round-head input.title { flex: 1 1 240px; font-weight: 800; font-size: 17px; }
.round-head .type-tag {
  padding: 4px 11px; border-radius: 999px; font-size: 11px; font-weight: 900;
  letter-spacing: 0.12em; text-transform: uppercase; background: rgba(0,0,0,0.35);
}

.qcard {
  border: 1px solid var(--panel-line);
  border-top: none;
  background: rgba(255, 255, 255, 0.03);
  padding: 16px;
}
.qcard:last-child { border-radius: 0 0 14px 14px; }
.qcard.flagged { background: rgba(255, 77, 94, 0.09); }

.qcard .qtop { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.qcard .qnum { font-size: 12px; font-weight: 900; letter-spacing: 0.14em; color: var(--ink-dim); text-transform: uppercase; }
.qcard .qtools { margin-left: auto; display: flex; gap: 6px; }

.opts { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-top: 11px; }
.opt-row { display: flex; align-items: center; gap: 9px; }
.opt-row input[type="text"] { flex: 1 1 auto; }
.opt-row label {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 11px; border-radius: 9px; cursor: pointer;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--panel-line);
  font-weight: 900; font-size: 13px;
  white-space: nowrap;
}
.opt-row.is-correct label { background: rgba(47, 224, 122, 0.24); border-color: var(--good); }
.opt-row.is-correct input[type="text"] { border-color: var(--good); }

.extra-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 9px; margin-top: 11px; }
.extra-fields label { display: grid; gap: 5px; font-size: 12px; color: var(--ink-dim); font-weight: 700; }
.extra-fields.cue { padding: 11px; border-radius: 10px; background: rgba(255, 210, 63, 0.08); border: 1px solid rgba(255, 210, 63, 0.3); }
.extra-fields.cue > .note { grid-column: 1 / -1; font-size: 12px; color: var(--gold); font-weight: 700; }

.problems {
  border-radius: 12px; padding: 14px 16px; margin-bottom: 18px;
  background: rgba(255, 77, 94, 0.14); border: 1px solid var(--bad);
}
.problems ul { margin: 8px 0 0; padding-left: 20px; }
.problems li { margin-bottom: 4px; font-size: 14px; }
.problems.ok { background: rgba(47, 224, 122, 0.14); border-color: var(--good); }

.saved-flash {
  position: fixed; right: 20px; bottom: 20px; z-index: 60;
  padding: 12px 20px; border-radius: 999px; font-weight: 900;
  background: var(--good); color: #04240f; box-shadow: var(--shadow);
}

/* If a picture file is missing, say so plainly rather than showing the
   browser's broken-image icon on a projector. The host can read the question
   out and carry on. */
.zoom-missing { display: none; }
.zoom-frame.no-image .zoom-img { display: none; }
.zoom-frame.no-image .zoom-caption { display: none; }
.zoom-frame.no-image .zoom-missing {
  display: grid;
  place-items: center;
  height: 100%;
  font-size: 3vh;
  font-weight: 800;
  color: var(--ink-dim);
}

/* ================================================================== BINGO */

/* ---- big screen */
.bingo-play { display: flex; flex-direction: column; height: 100%; gap: 2vh; }

.now-playing {
  flex: 0 0 auto;
  text-align: center;
  padding: 3vh 2vw;
  border-radius: var(--radius);
  background: linear-gradient(100deg, rgba(255, 46, 136, 0.2), rgba(75, 216, 255, 0.12));
  border: 2px solid var(--panel-line);
}
.now-playing .np-label {
  font-size: 2vh; letter-spacing: 0.4em; text-transform: uppercase;
  color: var(--gold); font-weight: 800; margin-bottom: 1vh;
}
.now-playing .np-title { font-size: 8vh; font-weight: 900; line-height: 1; letter-spacing: -0.02em; }
.now-playing .np-artist { font-size: 3.4vh; color: var(--ink-dim); font-weight: 700; margin-top: 1vh; }
.pop-in { animation: popIn 420ms cubic-bezier(0.2, 1.2, 0.3, 1); }
@keyframes popIn {
  from { opacity: 0; transform: scale(0.94) translateY(1.5vh); }
  to { opacity: 1; transform: none; }
}

.called-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 1.4vh; }
.called-head { display: flex; gap: 1vw; align-items: center; font-size: 1.9vh; }
.called-head .pill b { font-size: 1.3em; margin-left: 0.3em; }
.called-head .tension.hot { background: var(--hot); border-color: var(--hot); color: #fff; }

.called-list {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: flex; flex-wrap: wrap; gap: 1vh 1vw; align-content: flex-start;
}
.called-chip {
  padding: 1vh 1.3vw; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--panel-line);
  display: flex; flex-direction: column; gap: 0.2vh;
}
.called-chip .ct { font-size: 2.2vh; font-weight: 800; }
.called-chip .ca { font-size: 1.6vh; color: var(--ink-dim); font-weight: 600; }
.called-chip.newest { border-color: var(--gold); background: rgba(255, 210, 63, 0.16); }

/* ---- player card */
.bingo-wrap { display: flex; flex-direction: column; gap: 12px; flex: 1 1 auto; position: relative; }
/* The camera button floats over the bottom right of the phone, and on a card
   it sat ON a square — 58 pixels of button over "Fireflies", which is a square
   nobody can tap and so a full house nobody can get. On a card it drops down
   beside the BINGO button, which is wide enough to lose its right-hand end and
   still be the easiest thing on the screen to hit. */
body.bingo-card .camera-btn { bottom: 12px; right: 12px; width: 52px; height: 52px; font-size: 22px; }
body.bingo-card .bingo-call { margin-right: 72px; }

.bingo-status {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 15px; font-weight: 700; color: var(--ink-dim);
}
.bingo-status .away { font-weight: 900; color: var(--ink); }
.bingo-status .away.hot { color: var(--gold); }
.bingo-status .won { color: var(--good); font-weight: 900; }

.bingo-grid { display: grid; gap: 7px; flex: 1 1 auto; min-height: 0; }
.bingo-cell {
  position: relative;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 2px; padding: 6px 4px;
  border-radius: 12px;
  border: 2px solid var(--panel-line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 120ms, background 180ms, border-color 180ms;
  overflow: hidden;
  min-height: 0;
}
.bingo-cell:active { transform: scale(0.96); }
.bingo-cell .bt {
  font-size: 12.5px; font-weight: 800; line-height: 1.15; text-align: center;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.bingo-cell .ba { font-size: 10px; color: var(--ink-dim); font-weight: 600; text-align: center; }
.bingo-cell .btick {
  position: absolute; inset: 0;
  display: none; place-items: center;
  font-size: 40px; font-weight: 900; color: var(--good);
}
.bingo-cell.marked .bt, .bingo-cell.marked .ba { opacity: 0.28; }
.bingo-cell.marked .btick { display: grid; }
.bingo-cell.confirmed { border-color: var(--good); background: rgba(47, 224, 122, 0.18); }
/* Marked, but you have not played it yet — a gentle "are you sure". */
.bingo-cell.unconfirmed { border-color: var(--gold); background: rgba(255, 210, 63, 0.14); }
.bingo-cell.unconfirmed .btick { color: var(--gold); }

/* A 5x5 is twenty-five squares on a phone, so the type has to come down with
   the cell. At the sizes above, "Bootylicious" came out as "Bootyliciou" and
   "Irreplaceable" as "rreplaceabl" — a square you cannot read is a square you
   cannot mark, and on the night that is somebody missing a full house. Four
   lines rather than three, because the words are what matter here, not the
   tidiness of the grid. */
.bingo-grid.cols-5 { gap: 4px; }
.bingo-grid.cols-5 .bingo-cell { padding: 4px 2px; gap: 1px; border-radius: 9px; border-width: 1.5px; }
.bingo-grid.cols-5 .bingo-cell .bt { font-size: 10.5px; line-height: 1.1; -webkit-line-clamp: 4; }
.bingo-grid.cols-5 .bingo-cell .ba { font-size: 8.5px; }
.bingo-grid.cols-5 .bingo-cell .btick { font-size: 30px; }
/* Three across can afford to be read from further away — whether that is nine
   big squares or a strip of twenty-four. */
.bingo-grid.cols-3 .bingo-cell .bt { font-size: 15px; }
.bingo-grid.cols-3 .bingo-cell .ba { font-size: 12px; }
/* A strip is 3 across and 8 down: taller than the screen at square-ish cells,
   so the rows are short and wide rather than boxes. */
.bingo-grid.cols-3.strip .bingo-cell { min-height: 0; }
.bingo-grid.cols-3.strip .bingo-cell .bt { font-size: 12.5px; -webkit-line-clamp: 2; }
.bingo-grid.cols-3.strip .bingo-cell .ba { font-size: 10px; }
.bingo-grid.cols-4.strip .bingo-cell .bt { font-size: 11.5px; }
.bingo-grid.cols-4.strip .bingo-cell .ba { font-size: 9.5px; }

.bingo-call {
  flex: 0 0 auto;
  background: linear-gradient(120deg, var(--good), var(--cool));
  font-size: 22px; letter-spacing: 0.06em;
}
.bingo-call:disabled { background: var(--panel); color: var(--ink-dim); border: 2px solid var(--panel-line); font-size: 15px; letter-spacing: 0; }

/* What this phone has already won, kept beside what is being played for now. */
.bingo-status .yours {
  flex: 1 0 100%;
  font-size: 12.5px; font-weight: 800; color: var(--good);
}

.bingo-flash {
  position: fixed; left: 16px; right: 16px; bottom: 84px; z-index: 50;
  padding: 16px; border-radius: 14px; text-align: center;
  font-weight: 900; font-size: 17px;
  animation: popIn 300ms cubic-bezier(0.2, 1.2, 0.3, 1);
}
.bingo-flash.good { background: var(--good); color: #04240f; }
.bingo-flash.bad { background: var(--gold); color: #241a00; }

/* ---- the call sheet
   A grid, not a list: forty tracks down a single column is three screenfuls
   with the middle of every row empty, and you are looking for one song with a
   record playing. Boxes wrap to fit whatever screen it is on — seven across a
   laptop, two across a phone — so most of the sheet is in front of you.
   No inner scroller: the page scrolls, so the whole grid can be as tall as it
   needs and you are never scrolling a box inside a box. */
.trackgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 6px;
}
.trackbox {
  display: grid;
  grid-template-columns: 16px 1fr;
  grid-template-areas: 'tick title' '. artist';
  align-content: center;
  gap: 0 6px;
  min-height: 52px;
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid transparent;
  color: var(--ink); text-align: left; cursor: pointer; width: 100%;
}
.trackbox .tick { grid-area: tick; color: var(--good); font-weight: 900; font-size: 14px; }
.trackbox .tt {
  grid-area: title; font-weight: 700; font-size: 14.5px; line-height: 1.2;
  /* Two lines at most, so no box is twice the height of its neighbours and the
     grid stays a grid. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.trackbox .ta {
  grid-area: artist; color: var(--ink-dim); font-size: 12px; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.trackbox.called { background: rgba(47, 224, 122, 0.16); border-color: var(--good); }
.trackbox.called .tt { opacity: 0.65; text-decoration: line-through; }
@media (max-width: 430px) {
  .trackgrid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}
.prow .sc.hot { color: var(--gold); }

/* ================================================================ CONSOLE */

body.console { overflow: auto; background: #0b0b14; }
body.console::after { display: none; }
.console .wrap { max-width: 1000px; margin: 0 auto; padding: 0 16px 60px; }
.console .topbar {
  position: sticky; top: 0; z-index: 20;
  margin: 0 -16px 20px; padding: 14px 22px;
  background: rgba(11, 11, 20, 0.95); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--panel-line);
  display: flex; align-items: center; gap: 14px;
}
.console .topbar h1 { font-size: 19px; margin: 0; font-weight: 900; }
.console .spacer { flex: 1 1 auto; }
.console .tiny { font-size: 12.5px; color: var(--ink-dim); }
/* The console's small buttons. Links and <button>s both, because the running
   panel mixes them — two links out to the other screens and one real action. */
.console a.minor, .console button.minor {
  text-decoration: none; display: inline-block;
  padding: 9px 13px; border-radius: 10px; font-size: 13px; font-weight: 800;
  font-family: inherit; cursor: pointer;
  background: rgba(255, 255, 255, 0.08); border: 1px solid var(--panel-line); color: var(--ink);
}
.console button.minor.danger { color: var(--bad); border-color: rgba(255, 77, 94, 0.4); }
.console button.minor.danger:hover { background: rgba(255, 77, 94, 0.12); }
.console button.minor:disabled { opacity: 0.6; cursor: default; }
.console .panel { border-radius: 14px; background: var(--panel); border: 1px solid var(--panel-line); padding: 16px; margin-bottom: 24px; }
.console .panel h3 { margin: 0 0 12px; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-dim); font-weight: 800; }
.console .panel.running.live { border-color: var(--good); background: rgba(47, 224, 122, 0.1); }
.running-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.running-title { font-size: 20px; font-weight: 900; }
.running-links { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* Where the game has got to. Same size as the line above it but not dimmed,
   because it is the line you came to this panel to read. */
.running-at { margin-top: 3px; font-size: 13px; font-weight: 700; color: var(--good); }
/* The way you actually run the night, so it is a button and not a footnote. */
.running-links .control-link {
  border: none; border-radius: 10px; padding: 10px 16px;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c; font-weight: 900; font-size: 14px;
  text-decoration: none; white-space: nowrap; cursor: pointer;
}

.game-section { margin-bottom: 34px; }
.game-head { display: flex; align-items: flex-end; gap: 14px; margin-bottom: 14px; }
.game-head h2 { margin: 0 0 3px; font-size: 24px; font-weight: 900; letter-spacing: -0.02em; }
.game-head a { margin-left: auto; }

.pack-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }

/* ---- finding a pack when there are eighty of them.

   COMPACT keeps every control — it is about fitting more on screen, not about
   taking anything away. What goes is the blurb and the play count, which are
   the two lines you read once and never again; the launch-time pickers stay,
   because choosing the card shape and the look IS the launch. */
.pack-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pack-search {
  width: 190px; max-width: 42vw;
  padding: 8px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--panel-line);
  color: var(--ink); font: inherit; font-size: 13px;
}
.pack-search:focus { outline: none; border-color: var(--hot); background: rgba(255, 255, 255, 0.1); }
.pack-search::placeholder { color: var(--ink-dim); }

.pack-grid.dense { grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); gap: 8px; }
.pack-grid.dense .pack-card { padding: 10px 12px; gap: 3px; }
.pack-grid.dense .pack-title { font-size: 15px; }
/* The blurb and "played 39 times" — read once, then noise. */
.pack-grid.dense .pack-card > .tiny:not([style]) { display: none; }
.pack-grid.dense .played { display: none; }
.pack-grid.dense .pack-shape { font-size: 11px; }
.pack-grid.dense .pack-shape select { padding: 5px 24px 5px 8px; font-size: 12px; }
.pack-grid.dense .pack-actions { gap: 4px; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); }
.pack-grid.dense .pack-actions button, .pack-grid.dense .pack-actions a { padding: 5px 8px; font-size: 11.5px; }
.pack-grid.dense .pack-card .go { padding: 8px; font-size: 14px; margin-top: 5px; }
.pack-card {
  display: flex; flex-direction: column; gap: 5px;
  padding: 16px; border-radius: 14px;
  background: var(--panel); border: 1px solid var(--panel-line);
}
.pack-card.broken { border-color: var(--bad); }
/*
 * Two lines are RESERVED for a title, whether or not it needs them.
 *
 * "The Madonna Quiz" is one line and "The 2000s Pop Rnb and Chart Quiz" is two,
 * so side by side every row below them — the count, the play line, the Look
 * picker — sat at a different height on each card. The buttons already line up
 * (`.pack-actions` has `margin-top: auto`), which made it worse rather than
 * better: the tops were ragged and the bottoms were not, so the gap landed in
 * the middle of the card where there is nothing to explain it.
 *
 * Not in Compact, where the whole point is to fit more on screen and a reserved
 * empty line is the opposite of that.
 */
.pack-title { font-size: 17px; font-weight: 900; line-height: 1.2; }
.pack-grid:not(.dense) .pack-title { min-height: 2.4em; }
.pack-card .played { margin-bottom: 6px; }
.pack-card .go {
  width: 100%; margin-top: 8px;
  border: none; border-radius: 10px; padding: 12px; cursor: pointer;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c; font-family: inherit; font-weight: 900; font-size: 16px; letter-spacing: 0.01em;
}
.pack-card .go:hover { filter: brightness(1.08); }
.pack-card .go:disabled { opacity: 0.4; cursor: default; filter: none; }

/* ---- Past gigs: one row a night, the photos folded away behind it.
 *
 * The row is a BUTTON rather than a link, because opening a night loads its
 * pictures rather than going anywhere — and a control that looks like a link
 * and does not navigate is one people middle-click and then wonder about. It
 * keeps the old archive row's shape so the page still reads as a list. */
.gig-list { display: grid; gap: 7px; }
.gig-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; width: 100%;
  padding: 12px 14px; border-radius: 10px; text-align: left; cursor: pointer;
  background: var(--panel); border: 1px solid var(--panel-line); color: var(--ink);
  font-family: inherit; font-size: 13px;
}
.gig-head:hover { border-color: var(--hot); }
.gig-head .an { font-weight: 800; font-size: 15px; }
.gig-head .gig-more { margin-left: auto; color: var(--hot); font-weight: 700; }
.gig-body { padding: 10px 14px 4px; display: grid; gap: 8px; }

/* ---- the one-button generator on the console */
.panel.generate { border-color: rgba(255, 46, 136, 0.4); background: rgba(255, 46, 136, 0.07); }
.gen-row { display: flex; gap: 10px; }
.gen-row input {
  flex: 1 1 auto; padding: 13px 14px; font-size: 16px; font-weight: 600;
  border-radius: 10px; border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.08); color: var(--ink);
}
.gen-row input:focus { outline: 2px solid var(--hot); outline-offset: 1px; }
.gen-row .go {
  border: none; border-radius: 10px; padding: 13px 22px; cursor: pointer; white-space: nowrap;
  background: linear-gradient(120deg, var(--hot), var(--hot-2)); color: #12000c; font-weight: 900; font-size: 15px;
}
.gen-row .go:disabled { opacity: 0.45; cursor: default; }

/*
 * The topical quiz sits UNDER the theme box rather than beside it, because it
 * is a different job rather than a different setting: it takes no theme, and a
 * button on the same line as one you have to fill in reads as needing it.
 * Outlined rather than filled so "Write it" stays the loudest thing here — a
 * second gradient button of the same weight is two primaries and no primary.
 */
.gen-topical { display: flex; gap: 14px; align-items: flex-start; margin-top: 12px; flex-wrap: wrap; }
.gen-topical .go {
  border: 2px solid var(--hot); border-radius: 10px; padding: 11px 18px; cursor: pointer;
  background: transparent; color: var(--ink); font-weight: 900; font-size: 14px; white-space: nowrap;
}
.gen-topical .go:disabled { opacity: 0.45; cursor: default; }
.gen-topical .tiny { flex: 1 1 240px; line-height: 1.5; }

.gen-opts { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; margin-top: 12px; font-size: 13px; color: var(--ink-dim); }
.gen-opts label { display: flex; gap: 6px; align-items: center; font-weight: 700; }
.gen-opts input, .gen-opts select {
  padding: 6px 8px; border-radius: 8px; font-size: 13px;
  background: rgba(255, 255, 255, 0.08); color: var(--ink); border: 1px solid var(--panel-line);
}
.gen-opts a { color: var(--cool); }

.gen-status { margin-top: 12px; }
.gen-log {
  max-height: 220px; overflow-y: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px; line-height: 1.7; color: var(--ink-dim);
  padding: 11px 13px; border-radius: 10px; background: rgba(0, 0, 0, 0.35);
}
.gen-good {
  margin-top: 10px; padding: 13px 15px; border-radius: 10px; font-size: 15px;
  background: rgba(47, 224, 122, 0.16); border: 1px solid var(--good);
}
.gen-good a { color: var(--good); font-weight: 800; }
.gen-bad {
  margin-top: 10px; padding: 13px 15px; border-radius: 10px; font-size: 15px;
  background: rgba(255, 77, 94, 0.14); border: 1px solid var(--bad);
}
.tiny.warn { margin-top: 10px; color: var(--gold); }

/* Something the host needs to read mid-gig, in the dark, at a glance. */
.panel.warn {
  background: rgba(255, 210, 63, 0.12);
  border: 1px solid rgba(255, 210, 63, 0.55);
}
.panel.warn h3 { color: var(--gold); }

/* ---- bingo track list in the editor */
.tracklist-edit { display: grid; gap: 6px; }
.track-edit-row { display: flex; align-items: center; gap: 7px; }
.track-edit-row .tnum { width: 2.2em; text-align: right; color: var(--ink-dim); font-size: 12px; font-weight: 800; }
.track-edit-row .ttitle { flex: 2 1 200px; }
.track-edit-row .tartist { flex: 1 1 140px; }
.editor .shortfall { color: var(--bad); }

/* Nothing on the big screen is clickable, and a mouse pointer sitting in the
   middle of a projected image looks amateurish. Hide it. */
body.screen { cursor: none; }

/* The quiz generator, distinguished from the bingo one below it. */
.panel.quiz-generate { border-color: rgba(75, 216, 255, 0.4); background: rgba(75, 216, 255, 0.07); }
.panel.quiz-generate .gen-row .go { background: linear-gradient(120deg, var(--cool), #7cf76b); color: #04222c; }
.gen-opts input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--hot); }
.gen-good ul, .gen-bad ul { font-size: 13px; }

/* ---- console tabs
   One tab per thing you can run. Big enough to hit with a thumb, because the
   console gets used on a phone as often as a laptop. */
.tabbar {
  display: flex;
  gap: 6px;
  margin-bottom: 22px;
  padding-bottom: 2px;
  border-bottom: 1px solid var(--panel-line);
  overflow-x: auto;
}
.tab {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 13px 18px;
  border: none;
  border-bottom: 3px solid transparent;
  background: none;
  color: var(--ink-dim);
  font-family: inherit;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
  white-space: nowrap;
  border-radius: 10px 10px 0 0;
  transition: color 160ms, background 160ms, border-color 160ms;
}
.tab:hover { color: var(--ink); background: rgba(255, 255, 255, 0.04); }
.tab.on {
  color: var(--ink);
  border-bottom-color: var(--hot);
  background: rgba(255, 255, 255, 0.06);
}
.tabcount {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  font-size: 12px;
  font-weight: 900;
}
.tab.on .tabcount { background: var(--hot); color: #fff; }

.tabbody { animation: cardIn 220ms cubic-bezier(0.2, 0.8, 0.2, 1); }

/* The card's buttons, in the order you actually use them: the small ones you
   might press first sit in a row, and LAUNCH is the full width of the card at
   the bottom — the last thing, the biggest thing, and impossible to hit by
   accident on the way to Rename.
   They share one rule rather than four near-identical ones, so they cannot
   drift apart on size or weight the way they had. */
/*
 * A GRID, not a wrapping flex row — so every action is the same size however
 * many there are.
 *
 * These were `flex: 1 1 auto`, which is fine on the three most cards carry and
 * wrong the moment a fourth appears: Playlist pushed Delete onto a row of its
 * own, where being the only item on the line stretched it full width. That made
 * the destructive button the biggest target on the card, sitting directly above
 * Launch — the exact thing "Launch is the last thing on a pack card, and full
 * width" exists to prevent. Columns are worked out from the card, so a fourth
 * button wraps into the next cell at the same width as the rest.
 */
.pack-actions {
  margin-top: auto;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); gap: 6px;
}
/* Four actions are 2x2, not three and a straggler. `auto-fit` packs as many
   per row as will go, which is right for three and wrong for four: the fourth
   drops onto a row of its own and reads as a button that has come loose. Six
   go 3x2 for the same reason. */
.pack-actions[data-count="4"] { grid-template-columns: repeat(2, 1fr); }
.pack-actions[data-count="6"] { grid-template-columns: repeat(3, 1fr); }
.pack-actions > * {
  display: grid; place-items: center;
  min-height: 36px; padding: 0 10px;
  border-radius: 10px;
  font-family: inherit; font-size: 13px; font-weight: 800; line-height: 1;
  text-decoration: none; cursor: pointer;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--panel-line);
  transition: background 140ms, border-color 140ms, color 140ms;
}
.pack-actions > *:hover { background: rgba(255, 255, 255, 0.16); }
.pack-actions > *:disabled { opacity: 0.5; cursor: default; }
.pack-actions > *:disabled:hover { background: rgba(255, 255, 255, 0.08); }

/* Delete is quiet until you go near it, so it is never mistaken for Launch in
   a hurry — and it only turns red under the cursor. */
.pack-del { color: var(--ink-dim); background: rgba(255, 255, 255, 0.04) !important; }
.pack-del:hover { color: var(--bad); border-color: rgba(255, 77, 94, 0.5); background: rgba(255, 77, 94, 0.1) !important; }

/* Spotify green, so the playlist is findable at a glance in a dark room. */
.pack-spotify { color: #0b0b14 !important; background: #1ed760 !important; border-color: transparent !important; }
.pack-spotify:hover { background: #29e26b !important; }

/* Loud on purpose: without backup, work disappears and nothing looks wrong
   until the day it has gone. */
.panel.backup-warn { border-color: var(--gold); background: rgba(255, 210, 63, 0.12); }
.panel.backup-warn h3 { color: var(--gold); }
.panel.backup-warn .tiny { font-size: 13.5px; line-height: 1.6; color: var(--ink); }

/* ---- reading a pack without leaving the console */
.overlay {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(4, 4, 10, 0.78);
  backdrop-filter: blur(6px);
  display: grid; place-items: center;
  padding: 20px;
  animation: cardIn 180ms ease-out;
}
.sheet {
  width: min(860px, 100%);
  max-height: 88vh;
  display: flex; flex-direction: column;
  border-radius: 16px;
  background: #12121e;
  border: 1px solid var(--panel-line);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.sheet-head {
  flex: 0 0 auto;
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.03);
}
.sheet-title { font-size: 19px; font-weight: 900; margin-bottom: 3px; }
.sheet-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.sheet-actions .minor { cursor: pointer; }
.sheet-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 18px 26px; }

.pv-round-head {
  display: flex; align-items: baseline; gap: 10px;
  margin: 18px 0 10px;
  font-size: 16px; font-weight: 900;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--panel-line);
}
.pv-round-head .tiny { margin-left: auto; }
.pv-round:first-child .pv-round-head { margin-top: 0; }

.pv-q { padding: 11px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
.pv-prompt { display: flex; gap: 9px; font-size: 15px; font-weight: 700; line-height: 1.35; margin-bottom: 8px; }
.pv-num {
  flex: 0 0 auto; min-width: 1.7em;
  color: var(--ink-dim); font-weight: 900; font-size: 13px;
}
.pv-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-left: calc(1.7em + 9px); }
.pv-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 9px; border-radius: 7px;
  font-size: 13.5px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid transparent;
  color: var(--ink-dim);
}
.pv-opt.right {
  background: rgba(47, 224, 122, 0.18);
  border-color: var(--good);
  color: var(--ink);
  font-weight: 800;
}
.pv-letter { font-weight: 900; font-size: 11px; opacity: 0.7; }
.pv-note, .pv-cue {
  margin: 7px 0 0 calc(1.7em + 9px);
  font-size: 12.5px; color: var(--ink-dim); line-height: 1.5;
}
.pv-cue { color: var(--gold); }

.pv-tracks { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 4px; }
.pv-track { display: flex; align-items: baseline; gap: 8px; padding: 6px 9px; border-radius: 7px; background: rgba(255,255,255,0.04); font-size: 13.5px; }
.pv-track .pv-tt { font-weight: 700; }
.pv-track .pv-ta { margin-left: auto; color: var(--ink-dim); font-size: 12px; }

/* The title doubles as the way in to reading it. */
button.pack-title {
  background: none; border: none; padding: 0; text-align: left;
  color: var(--ink); font-family: inherit; cursor: pointer;
}
button.pack-title:hover { color: var(--cool); text-decoration: underline; }


@media (max-width: 560px) {
  .pv-opts { grid-template-columns: 1fr; margin-left: 0; }
  .pv-note, .pv-cue { margin-left: 0; }
}

.pv-warn {
  margin-bottom: 16px; padding: 13px 15px; border-radius: 11px;
  background: rgba(255, 210, 63, 0.12); border: 1px solid rgba(255, 210, 63, 0.5);
  font-size: 13.5px; line-height: 1.55;
}
.pv-warn ul { margin: 7px 0 0; padding-left: 0; list-style: none; }

/* Once everything is ticked the panel stops shouting. Leaving it yellow would
   mean the one glance you get before a gig still says "problem". */
.pv-warn.all-clear {
  background: rgba(86, 211, 100, 0.09);
  border-color: rgba(86, 211, 100, 0.4);
}
.pv-warn-head.all-clear { color: var(--good, #56d364); }

/* Each flag is a row you can tick off, so a long list becomes a job you can
   work through rather than a wall of yellow. */
.pv-flag {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 6px 0; border-bottom: 1px solid rgba(255, 210, 63, 0.16);
}
.pv-flag:last-child { border-bottom: 0; }
.pv-flag-text { flex: 1 1 auto; }
.pv-tick {
  flex: 0 0 auto; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 700;
  padding: 4px 11px; border-radius: 999px;
  color: var(--ink); background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.2);
}
.pv-tick:hover:not(:disabled) { background: rgba(255, 255, 255, 0.17); }
.pv-tick:disabled { opacity: 0.5; cursor: default; }

/* Checked ones are kept, not deleted — dismissing one by mistake should be
   undoable, and rewriting a question means wanting its flag back. */
.pv-cleared { margin-top: 9px; }
.pv-cleared-toggle {
  cursor: pointer; font: inherit; font-size: 12px;
  padding: 0; border: 0; background: none; color: var(--dim);
  text-decoration: underline; text-underline-offset: 3px;
}
.pv-cleared-toggle:hover { color: var(--ink); }
.pv-flag.done .pv-flag-text { opacity: 0.55; text-decoration: line-through; }

/* ================================================================== BRAND
   Logo and name, top left, linking back to the console — the way the corner of
   any website behaves. */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--ink);
  border-radius: 10px;
  padding: 4px 8px 4px 4px;
  margin-left: -4px;
  transition: background 160ms;
}
.brand:hover { background: rgba(255, 255, 255, 0.07); }
.brand-mark { flex: 0 0 auto; display: block; }
.brand-name {
  font-weight: 900;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

/* ---- the stacked wordmark: "Mark's" over "Quizporium".

   Whimsy without a font file, because there is no build step and a webfont is
   a request that can fail on pub wifi. So it comes from the ARRANGEMENT: the
   possessive small and tilted, sitting on the product name like a signature,
   and the product name heavy and wide enough to rule it off underneath.

   The tilt is 5 degrees and no more. It is the difference between playful and
   broken, and this is read from the back of a room. */
.brand-name.stacked {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 0.92;
  white-space: nowrap;
}
.brand-who {
  font-size: 0.58em;
  font-weight: 800;
  letter-spacing: 0.03em;
  color: var(--ink-dim);
  transform: rotate(-5deg);
  transform-origin: 0 100%;
  /* Nudged in so the tilt does not put it left of the product name's stem. */
  margin-left: 0.14em;
  margin-bottom: 0.06em;
}
.brand-app {
  font-weight: 900;
  letter-spacing: -0.015em;
  background: linear-gradient(100deg, var(--hot), var(--hot-2) 60%, var(--mark-3));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* On the projector the possessive is the quieter half but still has to carry
   to the back of a room, so it keeps more of the size than it does elsewhere. */
.screen .brand-who { font-size: 0.66em; color: var(--ink); opacity: 0.82; }
.console .brand-name { font-size: 18px; }
.host .brand-name, .editor .brand-name { font-size: 15px; }
/* The owner page drops the logo and the name straight into the slot rather
   than wrapping them in a `.brand` link, so the slot has to be the flex row —
   otherwise the name falls under the mark the moment anything shares the bar
   with it, which is what the hat switch does. */
.console .topbar #brandSlot { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* The control view's bar puts the brand above the "where am I" line. */
.host .bar { flex-wrap: wrap; }
.host .bar #brandSlot { flex: 1 1 100%; margin-bottom: 2px; }

/* Big screen: brand in the corner all night, quiz title beside it. */
.screen .titles { display: flex; align-items: center; gap: 1.1vw; min-width: 0; }
/* The slot holds the logo and the name, side by side rather than stacked. */
.screen .titles #brandSlot { display: flex; align-items: center; gap: 0.7vw; }
.screen .titles .brand-mark { width: 3.4vh; height: 3.4vh; }
.screen .titles .brand-name {
  font-size: 1.9vh;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.85;
}
.screen .titles .title {
  padding-left: 1.1vw;
  border-left: 1px solid var(--panel-line);
  opacity: 0.7;
}

/* Players see it too — they are looking at their phone all night. */
.phone-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  padding-bottom: 4px;
  opacity: 0.85;
}
.phone-brand .brand-name { font-size: 14px; letter-spacing: 0.02em; }

@media (max-width: 430px) {
  .console .brand-name { font-size: 16px; }
}

/* ---- renaming from the read sheet
   Titles look like text until you click them, so the sheet still reads as
   something to read rather than a form to fill in. */
input.sheet-title {
  width: 100%;
  font-size: 19px;
  font-weight: 900;
  font-family: inherit;
  color: var(--ink);
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 3px 7px;
  margin: 0 0 3px -7px;
}
input.sheet-title:hover { background: rgba(255, 255, 255, 0.06); }
input.sheet-title:focus { outline: none; border-color: var(--hot); background: rgba(255, 255, 255, 0.08); }

input.pv-round-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 16px;
  font-weight: 900;
  font-family: inherit;
  color: var(--ink);
  background: none;
  border: 1px solid transparent;
  border-radius: 7px;
  padding: 3px 7px;
  margin-left: -7px;
}
input.pv-round-name:hover { background: rgba(255, 255, 255, 0.06); }
input.pv-round-name:focus { outline: none; border-color: var(--hot); background: rgba(255, 255, 255, 0.08); }

.sheet-actions .go {
  border: none; border-radius: 10px; padding: 9px 16px; cursor: pointer;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c; font-weight: 900; font-size: 13px;
}
.sheet-actions .go:disabled { opacity: 0.6; cursor: default; }

/* One tap from the cue to the track, so there is nothing to search for while
   a room waits. Host view only — the room never sees these. */
.cue-links { display: flex; gap: 8px; margin-top: 11px; }
.cue-open {
  flex: 1 1 auto;
  text-align: center;
  padding: 11px 12px;
  border-radius: 10px;
  text-decoration: none;
  font-size: 14px;
  font-weight: 900;
  background: #1db954;           /* Spotify green — instantly recognisable */
  color: #04240f;
}
.cue-open.ghost { background: rgba(255, 255, 255, 0.1); color: var(--ink); border: 1px solid var(--panel-line); }

.pv-playlist {
  display: inline-block;
  margin: 2px 0 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: #1db954;
}

/* ---- importing a list you already have */
.panel.import { border-color: rgba(29, 185, 84, 0.4); background: rgba(29, 185, 84, 0.06); }
.panel.import .gen-row .go { background: #1db954; color: #04240f; }
/* The paste box is the route now, not a fold-away alternative, so it is always
   open and sized to show a round's worth of tracks without scrolling. */
#impText {
  width: 100%; margin: 10px 0; padding: 11px 13px;
  border-radius: 10px; border: 1px solid var(--panel-line);
  background: rgba(0, 0, 0, 0.3); color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; line-height: 1.6; resize: vertical;
}
#impText:focus { outline: 2px solid #1db954; outline-offset: 1px; }

/* Tickboxes in the brand colour rather than the browser's blue, and big enough
   to hit with a thumb. */
.console input[type="checkbox"],
.editor input[type="checkbox"] { accent-color: var(--hot); width: 16px; height: 16px; cursor: pointer; }

/* ---- dropdowns
   A native select renders as a white slab on a dark page, which is exactly
   what it looked like: the loudest thing on a pack card, louder than Launch.
   So: the page's own dark fill, a gradient rim and a gradient chevron block,
   drawn rather than an emoji or a font glyph so every browser gets the same
   one. `color-scheme: dark` is the line that matters on a Mac and an iPhone —
   without it the OS still paints the open list white. */
.console select,
.editor select {
  appearance: none; -webkit-appearance: none;
  color-scheme: dark;
  padding: 8px 34px 8px 11px;
  border-radius: 10px;
  font-family: inherit; font-size: 13px; font-weight: 800;
  color: var(--ink);
  background-color: rgba(255, 255, 255, 0.07);
  /* the chevron, then a gradient block behind it on the right-hand end */
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2312000c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"),
    linear-gradient(120deg, var(--hot), var(--hot-2));
  background-repeat: no-repeat, no-repeat;
  background-position: right 8px center, right center;
  background-size: 11px 8px, 27px 100%;
  border: 1px solid var(--panel-line);
  /* Wide enough that a short option like "4×4" is not squeezed up against the
     chevron block. */
  min-width: 92px;
  cursor: pointer;
  transition: border-color 140ms, background-color 140ms;
}
.console select:hover,
.editor select:hover { background-color: rgba(255, 255, 255, 0.13); }
.console select:focus-visible,
.editor select:focus-visible { outline: none; border-color: var(--hot); }
.console select:disabled,
.editor select:disabled { opacity: 0.5; cursor: default; }

/* Choosing tonight's card shape, on the pack card, above Launch — because the
   shape is a decision about tonight, not about the pack. */
.pack-shape {
  display: flex; align-items: center; gap: 8px; margin: 8px 0 2px;
  font-size: 12px; color: var(--ink-dim); font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.pack-shape select { flex: 1 1 auto; min-width: 0; }

/* What the last job said, kept where the panel rebuild cannot take it. */
.panel.done-banner {
  display: flex; align-items: flex-start; gap: 14px;
  border-color: rgba(47, 224, 122, 0.5);
  background: rgba(47, 224, 122, 0.09);
}
.panel.done-banner .done-text { flex: 1 1 auto; font-size: 14px; line-height: 1.55; }
.panel.done-banner .done-text a { color: var(--good); font-weight: 700; }
.panel.done-banner .done-close { flex: 0 0 auto; }
/* It worked, but something in it needs reading. A green box you have to read to
   the end of to find the problem is a box you stop reading to the end of. */
.panel.done-banner.warn {
  border-color: rgba(255, 176, 32, 0.55);
  background: rgba(255, 176, 32, 0.09);
}
.panel.done-banner.warn .done-text a { color: var(--gold); }
/* It did not work. Green is the default here, so a job that failed would
   otherwise announce itself in the colour that means "done" — and the reason
   is the only thing on the banner worth reading. */
.panel.done-banner.bad {
  border-color: rgba(255, 77, 94, 0.55);
  background: rgba(255, 77, 94, 0.1);
}
.panel.done-banner.bad .done-text a { color: var(--bad); }

/* Round 2 artwork, opened from a pack card. Free stand-ins and paid portraits
   are different decisions, so they are different buttons. */
.panel.pics { margin-top: 10px; }
.panel.pics .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.panel.pics .redo { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.pics-slot:empty { display: none; }

/* ================================================================== PHOTOS
   Pictures from the room. On the projector they are a filmstrip along the
   bottom, on the phone a sheet with the filters, on the control view a grid
   small enough to scan and tap in the dark. */

/* ---- the projector */
.photo-strip {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  gap: 0.8vw;
  padding: 1.2vh 2.2vw;
  overflow: hidden;
  align-items: flex-end;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), transparent);
}
.photo-strip .photo {
  margin: 0;
  flex: 0 0 auto;
  width: 13vh;
  border-radius: 12px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--panel-line);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  animation: pop 420ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.photo-strip .photo img { display: block; width: 100%; height: 13vh; object-fit: cover; }
.photo-strip .photo figcaption {
  font-size: 1.3vh; font-weight: 700; padding: 0.5vh 0.6vw;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Who picked each answer, under it, on the reveal only. Names the host can
   read straight off the screen while the room is still groaning. */
.keywho {
  display: flex; flex-wrap: wrap; gap: 5px;
  margin: 4px 0 8px 34px;
}
.keywho.shut { display: none; }
.keywho span {
  padding: 3px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 700;
  background: rgba(255, 255, 255, 0.07); color: var(--ink-dim);
  border: 1px solid var(--panel-line);
}
.keywho span.right { color: var(--good); border-color: rgba(47, 224, 122, 0.45); background: rgba(47, 224, 122, 0.12); }
.keywho.none { margin-left: 0; align-items: center; }
.keywho.none b { font-size: 12px; color: var(--gold); margin-right: 2px; }

/* ---- the phone */
.camera-btn {
  position: fixed;
  right: 16px; bottom: 58px;
  width: 58px; height: 58px;
  border-radius: 50%;
  border: 1px solid var(--panel-line);
  background: linear-gradient(140deg, var(--hot), var(--hot-2));
  font-size: 25px; line-height: 1;
  cursor: pointer;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
  z-index: 40;
}
.camera-btn:active { transform: scale(0.94); }

.cam-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(0, 0, 0, 0.72);
  display: flex; align-items: flex-end; justify-content: center;
}
.cam-sheet {
  width: 100%; max-width: 560px;
  max-height: 92vh; overflow-y: auto;
  background: var(--bg-2);
  border: 1px solid var(--panel-line);
  border-radius: 20px 20px 0 0;
  padding: 16px;
}
.cam-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cam-close {
  background: none; border: 0; color: var(--ink-dim);
  font-size: 20px; cursor: pointer; padding: 4px 8px;
}
.cam-warn { color: var(--gold); }
.cam-pick {
  display: block; margin-top: 12px; padding: 18px;
  text-align: center; font-weight: 800; cursor: pointer;
  border: 2px dashed var(--panel-line); border-radius: 14px;
}
.cam-stage { margin-top: 12px; }
/* SQUARE, because the photo is cropped square before it ever gets here — see
   drawFiltered. It used to be `height: auto` over a black background, which on
   a portrait phone photo meant two black bars down the sides of the preview
   and a frame that changed shape with whatever was photographed. */
.cam-canvas {
  width: 100%; aspect-ratio: 1; max-height: 44vh;
  display: block; margin: 0 auto;
  object-fit: contain; border-radius: 14px; background: #000;
}

/* The looks. All of them on screen at once, each showing what it does to
   their own photo — a scrolling row of named grey pills got missed
   completely. */
.cam-looks-head {
  margin: 14px 0 8px;
  font-size: 13px; font-weight: 800; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--gold);
}
/* Directly under the photo, so the top of its own margin is the gap. */
.cam-send {
  width: 100%; margin-top: 12px; padding: 16px; cursor: pointer;
  font: inherit; font-size: 17px; font-weight: 900;
  border: 0; border-radius: 14px; color: #fff;
  background: linear-gradient(140deg, var(--hot), var(--hot-2));
}
.cam-send:disabled { opacity: 0.6; }
.cam-status { margin-top: 10px; color: var(--bad); }

/* ---- the control view */
.panel.photos.off { opacity: 0.75; }
.panel.photos .photo-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.panel.photos .photo-head h3 { margin: 0; }
.photo-grid {
  display: grid; gap: 8px; margin-top: 10px;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
}
.host-photo {
  position: relative; padding: 0; cursor: pointer;
  border: 1px solid var(--panel-line); border-radius: 10px;
  overflow: hidden; background: none;
}
.host-photo img { display: block; width: 100%; height: 84px; object-fit: cover; }
.host-photo .who {
  display: block; font-size: 11px; font-weight: 700; color: var(--ink);
  padding: 4px 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Says what a tap does before it does it. */
.host-photo .bin-badge {
  position: absolute; top: 5px; right: 5px;
  display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 8px;
  color: #fff; background: rgba(214, 42, 62, 0.92);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
.host-photo:active .bin-badge { transform: scale(0.9); }

/* ========================================================== PICK-THEM-ALL
   The fourth round type: several answers are right and you lock in all of
   them. Six options rather than four, so the grid gets a third row and the
   type comes down a little to keep it on one screen. */

.question.type-multi .options { grid-auto-rows: 1fr; }
.question.type-multi .option { font-size: 3.2vh; padding: 1.6vh 1.8vw; }

/* The instruction the room needs. Not the answer — which ones never reaches
   this screen at all. */
.pick-banner {
  flex: 0 0 auto;
  align-self: flex-start;
  margin-bottom: 1.6vh;
  padding: 0.6vh 1.4vw;
  border-radius: 999px;
  font-size: 2.4vh;
  font-weight: 800;
  background: rgba(75, 216, 255, 0.16);
  border: 1px solid var(--cool);
  color: var(--ink);
}
.pick-banner b { font-size: 1.2em; color: var(--cool); }

/* ---- the phone */
.answers.multi .answer-btn { position: relative; }
.answer-btn .tick {
  position: absolute;
  right: 12px; top: 50%;
  transform: translateY(-50%) scale(0.4);
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--good); color: #06210f;
  font-weight: 900; font-size: 15px;
  opacity: 0; transition: opacity 140ms, transform 140ms;
}
.answer-btn.picked { border-color: var(--good); background: rgba(47, 224, 122, 0.14); }
.answer-btn.picked .tick { opacity: 1; transform: translateY(-50%) scale(1); }

.lock-btn {
  flex: 0 0 auto;
  width: 100%;
  padding: 18px;
  cursor: pointer;
  font: inherit; font-size: 19px; font-weight: 900;
  color: #fff; border: 0; border-radius: 16px;
  background: linear-gradient(140deg, var(--hot), var(--hot-2));
}
.lock-btn:disabled {
  background: rgba(255, 255, 255, 0.09);
  color: var(--ink-dim);
  cursor: default;
}

/* Part marks get their own face. "Not this time" over a score of 180 reads
   as a bug. */
.result.part {
  background: rgba(255, 210, 63, 0.16);
  border: 1px solid var(--gold);
}
.result.part .big { color: var(--gold); }

/* ==================================================================== RULES
   The first slide of every quiz. The right half is the join code, as big as
   the projector allows — this slide is up while the room is still filling.
   Everything the room has to read lives in the left half. */

.rules { display: flex; flex-direction: column; height: 100%; justify-content: center; }
.rules h1 { font-size: 8vh; margin: 0 0 3vh; font-weight: 900; letter-spacing: -0.02em; }

.rules-split { display: grid; grid-template-columns: 1fr 1fr; gap: 3vw; align-items: center; }
.rules-left { min-width: 0; }

.rules-join { display: grid; justify-items: center; gap: 1.6vh; min-width: 0; }
.rules-join .qr-panel { width: 100%; max-width: min(44vw, 66vh); }
.rules-join .qr-panel .url { font-size: 2.4vh; }
.rules-join-head {
  font-size: 2.4vh; font-weight: 800; color: var(--gold); text-align: center;
}

/* The points box. The only thing on the slide besides the code, so it gets to
   be big: the number in orange, what it is for in white underneath it. */
.rules-score {
  padding: 3.4vh 2.4vw;
  border-radius: var(--radius);
  background: var(--panel);
  border: 1px solid var(--panel-line);
}
.rules-score-row { margin-bottom: 3.2vh; }
.rules-score-row:last-of-type { margin-bottom: 0; }
.rules-score-row .big {
  display: block;
  font-size: 7.4vh; font-weight: 900; line-height: 1;
  color: var(--hot-2);
}
.rules-score-row .big i {
  font-style: normal; font-size: 0.44em; font-weight: 900;
  letter-spacing: 0.06em; margin-left: 0.24em;
}
.rules-score-row .what {
  display: block; margin-top: 0.8vh;
  font-size: 2.6vh; font-weight: 600; color: var(--ink); line-height: 1.3;
}
.rules-note {
  margin-top: 3vh; padding-top: 2vh;
  border-top: 1px solid var(--panel-line);
  font-size: 2.2vh; color: var(--gold); font-weight: 700;
}

/* The mid-quiz scoreboard reuses the round board, with a line saying where in
   the quiz it was taken. */
.board-sub {
  font-size: 2.4vh; color: var(--ink-dim);
  margin: -1vh 0 2.4vh;
}

/* The scoreboard toggle shows its own state — you should be able to tell what
   is on the projector without turning round to look at it. */
.minor.on {
  background: linear-gradient(140deg, var(--hot), var(--hot-2));
  border-color: transparent;
  color: #fff;
}

/* ================================================================== ADVERTS
   A venue's slide, between rounds. This one earns money, so it gets the whole
   screen rather than a corner. */

.advert { display: flex; flex-direction: column; height: 100%; justify-content: center; }
.ad-venue {
  align-self: flex-start;
  font-size: 2vh; letter-spacing: 0.3em; text-transform: uppercase;
  color: var(--ink-dim); margin-bottom: 2vh;
}
.ad-body { display: flex; align-items: center; gap: 4vw; }
.ad-words { flex: 1 1 auto; min-width: 0; }
.ad-words h1 {
  font-size: 9vh; line-height: 0.98; margin: 0; font-weight: 900;
  letter-spacing: -0.02em;
  background: linear-gradient(100deg, var(--hot), var(--hot-2) 55%, var(--gold));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.ad-words p { font-size: 3.4vh; color: var(--ink); margin: 2.4vh 0 0; line-height: 1.3; }

.ad-image { flex: 0 0 32%; }
.ad-image img {
  width: 100%; height: auto; max-height: 56vh; object-fit: contain;
  border-radius: var(--radius);
}

/* Big enough to scan from a table at the back with a phone held badly. */
.ad-qr {
  flex: 0 0 auto;
  background: #fff; border-radius: 20px; padding: 1.8vh;
  display: grid; justify-items: center; gap: 1vh;
  box-shadow: var(--shadow);
}
.ad-qr img { width: 26vh; height: 26vh; display: block; }
.ad-qr-label {
  color: #12121c; font-weight: 800; font-size: 1.9vh;
  max-width: 26vh; text-align: center;
}

/* ---- the console editor */
.ad-venue-in {
  display: block; width: 100%; margin-top: 6px;
  background: none; border: 0; color: var(--ink-dim);
  font: inherit; font-size: 13px; padding: 0;
}
.ad-slide {
  padding: 14px; margin-bottom: 12px;
  border-radius: 12px; background: var(--panel);
  border: 1px solid var(--panel-line);
}
.ad-slide-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.ad-slide label { display: block; margin: 10px 0 4px; color: var(--ink-dim); }
.ad-slide input {
  width: 100%; padding: 10px 11px; border-radius: 9px;
  background: rgba(0, 0, 0, 0.3); color: var(--ink);
  border: 1px solid var(--panel-line); font: inherit; font-size: 14px;
}
.ad-slide .ad-h { font-weight: 800; }

/* ---- the picker on the control view */
.ad-set {
  margin: 14px 0 6px; font-size: 12px; font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-dim);
}
.ad-pick {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; margin-bottom: 8px; padding: 14px;
  border-radius: 12px; cursor: pointer; text-align: left;
  background: rgba(255, 255, 255, 0.07); color: var(--ink);
  border: 1px solid var(--panel-line); font: inherit;
}
.ad-pick-head { font-weight: 700; font-size: 15px; }
.ad-pick-tag {
  flex: 0 0 auto; font-size: 11px; font-weight: 800; letter-spacing: 0.1em;
  padding: 3px 8px; border-radius: 999px;
  background: rgba(75, 216, 255, 0.2); color: var(--cool);
}

/* ============================================================ PHOTOS TAB
   A night's photos in the console: bin the duds, share the rest. */

.night { margin-bottom: 26px; }
.night-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 10px; }
.night-head h3 { margin: 0; font-size: 18px; }
.night-head .minor { margin-left: auto; }

.night-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.cphoto {
  margin: 0; position: relative; overflow: hidden;
  border-radius: 12px; background: var(--panel);
  border: 1px solid var(--panel-line);
}
.cphoto img { display: block; width: 100%; height: 150px; object-fit: cover; }
.cphoto figcaption {
  font-size: 12px; font-weight: 700; padding: 7px 9px 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A green edge means it is in the private repo and safe from a restart. */
.cphoto.filed { border-color: rgba(47, 224, 122, 0.5); }
.cphoto:not(.filed)::after {
  content: 'not filed';
  position: absolute; top: 8px; right: 8px;
  font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 999px;
  background: rgba(255, 210, 63, 0.9); color: #221a00;
}
.cphoto-acts {
  position: absolute; left: 0; right: 0; bottom: 34px;
  display: flex; gap: 6px; justify-content: center;
  opacity: 0; transition: opacity 140ms;
}
.cphoto:hover .cphoto-acts, .cphoto:focus-within .cphoto-acts { opacity: 1; }
.cphoto-acts button {
  cursor: pointer; font: inherit; font-size: 12px; font-weight: 700;
  padding: 6px 12px; border-radius: 999px;
  color: var(--ink); background: rgba(10, 10, 18, 0.86);
  border: 1px solid var(--panel-line);
}
.cphoto-acts button { display: inline-flex; align-items: center; gap: 5px; }
.cphoto-acts .bin { color: var(--bad); }

/* Touch screens have no hover, and this tab is used on a phone. */
@media (hover: none) {
  .cphoto-acts { opacity: 1; position: static; padding: 0 8px 8px; }
}

/* ============================================================ JOIN CORNER
   A small code on every screen where somebody could still walk in. Top right,
   under the topbar, so it never collides with the photo strip along the
   bottom. Deliberately absent during a question — the room should be looking
   at the answers, not at this. */

.join-corner {
  position: absolute;
  top: 8.5vh; right: 2.2vw;
  display: flex; align-items: center; gap: 1vw;
  padding: 1.1vh 1.2vw;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.94);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  animation: pop 400ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.join-corner img { width: 11vh; height: 11vh; display: block; }
.join-corner-words { display: grid; gap: 0.3vh; color: #12121c; }
.join-corner-words b { font-size: 2vh; font-weight: 900; }
.join-corner-words span { font-size: 1.6vh; font-weight: 700; opacity: 0.72; }

/* Real errors in the read-through: these stop the quiz being played, so they
   must not look like the advisory flags sitting under them. */
.pv-warn.pv-broken {
  border-color: rgba(255, 77, 94, 0.55);
  background: rgba(255, 77, 94, 0.08);
}
.pv-warn.pv-broken .pv-warn-head { color: var(--bad); }

/* Doing something about "answers land A x15 B x10 — lopsided". Sits on the end
   of that same line, so the complaint and the fix are one sentence. */
.pv-even {
  display: inline-block;
  margin-left: 8px;
  padding: 3px 10px;
  border: 1px solid var(--panel-line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.pv-even:hover { background: rgba(255, 255, 255, 0.14); }
.pv-even.urge {
  border-color: var(--gold);
  background: rgba(255, 176, 32, 0.16);
  color: var(--gold);
}
/* Offered but refused, with the reason in the title — a button that vanishes
   mid-game just looks broken. */
.pv-even.off { opacity: 0.5; cursor: default; }
.pv-even.off:hover { background: rgba(255, 255, 255, 0.06); }

/* ============================================================ first letter
   The alphabet round. No options: the question is asked, the phone shows a
   keyboard, and only the first letter of the answer has to be right. */

/* The projector's twenty-six. Deliberately NOT the two-column option grid —
   at 3.9vh a row each they would run off the bottom of the screen. A tile is
   just the letter, big enough to read from the back, with the count under it
   as the answers come in. */
.options.alphabet {
  grid-template-columns: repeat(9, 1fr);
  grid-auto-rows: 1fr;
  gap: 1.4vh 1vw;
}
.options.alphabet .option {
  flex-direction: column;
  justify-content: center;
  gap: 0.3vh;
  padding: 1vh 0.4vw;
  font-size: 6vh;
  font-weight: 900;
  /* So the grid can give height back when the answer appears above it on the
     reveal, rather than the two of them ending up on top of each other. */
  min-height: 0;
  overflow: hidden;
}
/* The count under each letter as the answers come in. Its space is held even
   when it is empty, so the letters do not jump the moment somebody answers. */
.options.alphabet .option .tally { margin-left: 0; font-size: 0.34em; min-height: 1.1em; }

/* The answer said out loud on the reveal. A lit-up letter is not an answer —
   the room has to be told it was Fleetwood Mac, in words, or the reveal says
   nothing. Used on the projector and on the host's own answer key. */
.answer-said {
  display: flex;
  align-items: center;
  gap: 0.8em;
  margin: 1.4vh 0;
}
.answer-said .answer-letter {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 2em; height: 2em;
  border-radius: 14px;
  background: var(--good);
  color: #06110a;
  font-weight: 900;
}
.answer-said .answer-words { font-weight: 900; }
.screen .answer-said { font-size: 4.6vh; }
.host .answer-said { font-size: 20px; margin: 4px 0 10px; }
.host .answer-said .answer-letter { border-radius: 10px; }

/* The phone's keyboard. Five across, not nine like the projector: nine keys on
   a 320px phone is 28 pixels each, which nobody hits in a dark pub against a
   clock. The ORDER is the same, and that is the part that matters — a player
   looking for F already knows which letter they want. */
.keyboard {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;
  justify-content: center;
  min-height: 0;
}
/* The six rows share whatever height is left rather than each claiming a size
   of its own. A fixed key height fits one phone and pushes Z off the bottom of
   the next one, and a row you have to scroll to is a row nobody presses in
   time — which on a 320px iPhone SE is exactly what happened. */
.keyboard-row { display: flex; gap: 6px; flex: 1 1 0; min-height: 0; }
.answer-btn.key {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  height: 100%;
  padding: 0;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: clamp(20px, 7vw, 34px);
  font-weight: 900;
  border-radius: 12px;
}
/* Z sits on its own on the last row. Left where the A above it is rather than
   stretched across five columns — a key five times the width of every other
   one reads as a different kind of button. */
.keyboard-row:last-child .answer-btn.key { flex: 0 0 calc((100% - 24px) / 5); }
/* A count per round type in the generator. Unticking greys the number rather
   than hiding it, so what you typed is still there when you tick it back on. */
.gen-rounds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 8px;
  margin: 10px 0 4px;
}
.gen-round {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 12px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.04);
  cursor: pointer;
  transition: opacity 160ms, border-color 160ms;
}
.gen-round.off { opacity: 0.5; }
.gen-round-name { flex: 1 1 auto; font-weight: 700; font-size: 13px; }
.gen-round input[type="number"] { width: 52px; text-align: center; font-weight: 800; }

/* ========================================================= picture reveals
   Four ways for round 2 to give itself away. The zoom is the original and
   still the default; these are the same job done differently so ten questions
   are not ten of the same effect.

   All four run on the same curve in screen.js — that is a scoring decision,
   not a styling one. See REVEAL_MODES in src/quizzes.js. */

/* ---- pixelate: drawn tiny into a canvas and blown up by the browser, which
   is the one way to do this that needs no library and no ctx.filter. */
.pix-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Least-known last, so a browser that understands `pixelated` uses it and one
     that does not still falls back to something sharp. Order matters here: get
     it the wrong way round and the projector smooths the blocks into mush,
     which is the whole effect gone. */
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}
/* The real picture waits underneath and takes over at the very end, so what
   the room finally sees is the photograph rather than a good impression. */
.reveal-pixelate .zoom-img { visibility: hidden; }
.reveal-pixelate.pix-done .zoom-img { visibility: visible; }
.reveal-pixelate.pix-done .pix-canvas { display: none; }

/* ---- blur: one CSS filter, supported everywhere including the old iOS that
   ctx.filter is not. */
.reveal-blur .zoom-img { will-change: filter, transform; }

/* ---- tiles: a blind of panels that come away a few at a time. */
.tile-grid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols, 6), 1fr);
  grid-template-rows: repeat(var(--rows, 4), 1fr);
  gap: 3px;
}
.tile-grid i {
  /* Opaque, not the usual translucent panel fill — a tile that lets the
     picture through is a tile that is not covering anything. */
  background: #14131f;
  border: 1px solid var(--panel-line);
  border-radius: 6px;
  transition: opacity 420ms ease, transform 420ms ease;
}
.tile-grid i.gone { opacity: 0; transform: scale(0.82); }

/* A picture that did not load says so, whichever effect it was going to use. */
.zoom-frame.no-image .pix-canvas,
.zoom-frame.no-image .tile-grid { display: none; }

/* The primary action on a section heading. `.go` was only ever styled inside a
   pack card and the editor, so a button using it anywhere else came out looking
   like the secondary ones next to it — which on this panel is the wrong way
   round: New invoice is the thing you came here to press. */
.game-head .row { margin-left: auto; }
.game-head .go, .sheet-actions .go, .inv-actions .go {
  border: none;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c;
  font-weight: 900;
}
.game-head .go:hover, .sheet-actions .go:hover, .inv-actions .go:hover { filter: brightness(1.08); }
.game-head .go:disabled, .sheet-actions .go:disabled { opacity: 0.5; cursor: default; filter: none; }

/* ============================================================== invoicing
   Read at half eleven on a phone, in a car park, by somebody who wants to go
   home. Everything is one column and the amount is the biggest thing on it. */

.inv-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.04);
  margin-bottom: 8px;
}
.inv-main { flex: 1 1 220px; min-width: 0; }
.inv-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.inv-who { font-weight: 700; }
.inv-status {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.09);
  color: var(--ink-dim);
}
.inv-row.status-paid .inv-status { background: rgba(47, 224, 122, 0.2); color: var(--good); }
.inv-row.status-cancelled { opacity: 0.55; }
.inv-row.status-cancelled .inv-amount { text-decoration: line-through; }
.inv-amount { font-weight: 900; font-size: 19px; margin-left: auto; }
.inv-actions { display: flex; gap: 6px; align-items: center; }
.inv-actions a.minor { text-decoration: none; display: inline-flex; align-items: center; }

/* The forms. A two-column grid that collapses on a phone, because these get
   filled in on a laptop and read on a phone. */
.inv-form { display: grid; gap: 16px; }
.inv-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px 14px;
  padding: 14px;
  border-radius: 14px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.03);
}
.inv-group h4 {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.inv-field { display: grid; gap: 4px; font-size: 13px; font-weight: 700; }
.inv-field.wide, .inv-group .wide, .inv-group.wide { grid-column: 1 / -1; }
.inv-field span { color: var(--ink-dim); font-size: 12px; }
.inv-field input[type="checkbox"] { justify-self: start; width: 20px; height: 20px; }

.inv-lines { display: grid; gap: 6px; grid-column: 1 / -1; }
.inv-line { display: flex; gap: 8px; }
.inv-line [data-desc] { flex: 1 1 auto; min-width: 0; }
/* Wide enough for "1,250.50" and no wider — it is a number, not a sentence. */
.inv-line [data-amount] { flex: 0 0 110px; text-align: right; font-weight: 800; }
.inv-total { grid-column: 1 / -1; font-size: 15px; text-align: right; }

.inv-customers { display: grid; gap: 6px; }
.inv-cust {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--panel-line);
}
.inv-cust > div { flex: 1 1 auto; min-width: 0; }

/* ==================================================================== looks
   Dressing a night up: a palette and a handful of drawn shapes. The quiz
   underneath is identical — nothing about how a round plays changes.

   The palettes live here rather than in looks.js so there is one place to
   change a colour, and there is a test that every look in that file has a
   block here. A look with no palette would silently be the ordinary one
   wearing skulls.

   THE OPTION COLOURS (--a to --f) MUST CHANGE ON BOTH SCREENS OR NEITHER.
   A player looks up, decides "the pink one, bottom left", and looks back down
   — so if the projector goes orange and the phone stays pink, that mapping is
   broken and the theme has cost somebody points. Both pages carry data-look
   for exactly this reason. Keep the six well spaced round the wheel: they have
   to be told apart from the back of a room, which matters more than the
   palette being tasteful. */

/* ---- My account.

   Everything about the person rather than about a pack or a night. Laid out as
   plain rows of label-over-value, because most of it is a STATEMENT — what you
   are on, what the room sees — and only two things on the page are controls.
   Making the statements look like inputs is how somebody comes away thinking
   they turned invoicing off when they cancelled a subscription. */
.acct-grid {
  display: flex; flex-wrap: wrap; gap: 22px 40px;
  margin-bottom: 12px;
}
.acct-val { font-size: 17px; font-weight: 800; margin-top: 3px; }
.acct-val.good { color: var(--good); }
.acct-val.bad { color: var(--bad); }
.acct-val.brand-preview {
  background: linear-gradient(100deg, var(--hot), var(--hot-2) 70%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.acct-note { margin-top: 4px; }
.acct-note.bad { color: var(--bad); }
.acct-actions { margin-top: 14px; }
.acct-missing { margin: 6px 0 0; padding-left: 20px; font-size: 12.5px; color: var(--ink-dim); }
.acct-missing li { margin-bottom: 4px; }

/* The only real switches on the page, so they are the only things that look
   like switches. A whole row is the target — a 14px tickbox is not something
   to aim at on a phone in a car park. */
.acct-toggles { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.acct-toggle {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 11px 14px;
  border-radius: 12px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.04);
  cursor: pointer;
}
.acct-toggle:hover { background: rgba(255, 255, 255, 0.07); }
.acct-toggle span { min-width: 0; }
/* The words take the room, the switch sits on the right at a fixed size. It
   was a tick box, which reads as a form you fill in; this is the SAME control
   as the hat switch in the top right, because one shape for "is this on"
   across the app is recognised rather than read. */
.acct-toggle { align-items: center; justify-content: space-between; cursor: default; }
.acct-toggle-what { flex: 1 1 auto; }
.feat-switch { flex: 0 0 auto; }
.feat-switch .hat-half { padding: 6px 13px; font-size: 11.5px; }
/* A tier above yours gets the tab bar's "+" rather than a switch reading Off.
   You did not turn it off — you do not have it, and telling those two apart is
   the whole point of the three states. */
.feat-plus {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 999px;
  border: 1px dashed var(--panel-line);
  color: var(--ink-dim);
  font-weight: 900; font-size: 15px; line-height: 1;
}

.acct-links { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }

/* ---- the tier ladder.

   Bronze, Silver, Gold, and they stack. Each section is edged in its own metal
   so the three are told apart at a glance rather than by reading three
   headings — and a tier ABOVE yours is dimmed as a whole rather than hidden,
   because something you can see and cannot use is a thing you might buy.

   The metals are written out rather than taken from --hot: they are the same
   three on everybody's account, whatever colour scheme is on, because a tier is
   a fact about what you pay and not part of anybody's branding. */
.tier-panel { border-left: 3px solid var(--tier); }
.tier-panel.tier-bronze { --tier: #cd7f32; }
.tier-panel.tier-silver { --tier: #c0c7d0; }
.tier-panel.tier-gold   { --tier: #ffd23f; }
.tier-panel h3 { display: flex; align-items: center; gap: 9px; color: var(--ink); }
.tier-dot {
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--tier);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tier) 22%, transparent);
  flex: 0 0 auto;
}
.tier-yours, .tier-price {
  margin-left: auto;
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 800;
  padding: 3px 9px; border-radius: 999px;
}
.tier-yours { background: rgba(47, 224, 122, 0.18); color: var(--good); }
.tier-price { background: rgba(255, 255, 255, 0.08); color: var(--ink-dim); letter-spacing: 0.04em; text-transform: none; }

/* A rung you have not reached. Legible, obviously not yours, nothing to press. */
.tier-panel.locked { opacity: 0.72; }
.tier-panel.locked .acct-toggle { cursor: default; background: none; border-style: dashed; }
.tier-panel.locked .acct-toggle:hover { background: none; }
.tier-panel.locked .feat-plus { opacity: 0.75; }

/* The owner's tier picker on the subscriber list. */
.tier-pick { display: inline-flex; gap: 6px; }
.tier-pick .tierbtn.on { border-color: var(--hot); color: var(--ink); background: rgba(255, 255, 255, 0.12); }

/* ---- the colour picker on the console.

   The swatches are the colours themselves, not their names: "Orchid" means
   nothing until you have seen it, and the point is choosing one you like the
   look of. Same reasoning as the photo filters being shown rather than listed.

   Each blob is written out here rather than taking var(--hot), because they all
   have to be on screen AT ONCE and a variable would make every one of them the
   colour you already have. Keep these in step with the [data-scheme] blocks
   below — there is a test that the ids match, though not the hexes. */
/* Breathing room under the explanation. The swatches sat straight on top of
   it, so the words read as a label for the first swatch rather than for the
   row. */
.scheme-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.scheme-swatch {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 14px 8px 9px;
  border-radius: 999px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.05);
  color: var(--ink-dim);
  font: inherit; font-size: 13px; font-weight: 800;
  cursor: pointer;
}
.scheme-swatch:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); color: var(--ink); }
.scheme-swatch.live { border-color: var(--hot); color: var(--ink); background: rgba(255, 255, 255, 0.1); }
.scheme-swatch:disabled { opacity: 0.45; cursor: default; }
.scheme-blob {
  width: 26px; height: 26px; border-radius: 50%;
  flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.scheme-blob[data-swatch="sunset"] { background: linear-gradient(120deg, #ff2e88, #ff8a3d); }
.scheme-blob[data-swatch="orchid"] { background: linear-gradient(120deg, #ff4fa3, #a855f7); }
.scheme-blob[data-swatch="lagoon"] { background: linear-gradient(120deg, #2ad3c5, #3d8bff); }
.scheme-blob[data-swatch="ember"]  { background: linear-gradient(120deg, #ff3d3d, #ffa62b); }
.scheme-blob[data-swatch="citrus"] { background: linear-gradient(120deg, #9ae62b, #ffd23f); }
.scheme-blob[data-swatch="ultra"]  { background: linear-gradient(120deg, #8b5cf6, #ff2ed6); }

/* ============================================================== SCHEMES
   A quizmaster's own two colours — see public/assets/schemes.js.

   A SCHEME IS A BRAND; A LOOK IS A NIGHT. A scheme sets --hot and --hot-2 and
   the washes behind everything, and deliberately does NOT touch --a to --f.
   Those are the option colours, they are how a player matches "the pink one,
   bottom left" between the projector and their phone, and they belong to the
   looks below and to nothing else.

   THESE BLOCKS MUST STAY ABOVE THE LOOKS. Both are a single attribute selector,
   so they have the same specificity and source order is what decides. A themed
   night has to beat somebody's brand colours — Halloween is orange and black on
   everybody's account. There is a test that this file keeps them in this order.

   "sunset" is the original and needs no block: it is what :root already says,
   so an account with no scheme set, and every page before the scheme arrives,
   looks exactly as it always did. */

[data-scheme="orchid"] {
  --mark-3: #ffc2f0;
  --hot: #ff4fa3;
  --hot-2: #a855f7;
  --glow-1: #2d1245;
  --glow-2: #3b0f3a;
  --drift-1: rgba(255, 79, 163, 0.16);
  --drift-2: rgba(168, 85, 247, 0.15);
}

[data-scheme="lagoon"] {
  --mark-3: #8df2e4;
  --hot: #2ad3c5;
  --hot-2: #3d8bff;
  --glow-1: #0b2f45;
  --glow-2: #10324a;
  --drift-1: rgba(42, 211, 197, 0.16);
  --drift-2: rgba(61, 139, 255, 0.15);
}

[data-scheme="ember"] {
  --mark-3: #ffe08a;
  --hot: #ff3d3d;
  --hot-2: #ffa62b;
  --glow-1: #3a1108;
  --glow-2: #40210a;
  --drift-1: rgba(255, 61, 61, 0.16);
  --drift-2: rgba(255, 166, 43, 0.15);
}

[data-scheme="citrus"] {
  --mark-3: #fff6a8;
  --hot: #9ae62b;
  --hot-2: #ffd23f;
  --glow-1: #17330d;
  --glow-2: #3a3208;
  --drift-1: rgba(154, 230, 43, 0.15);
  --drift-2: rgba(255, 210, 63, 0.14);
}

[data-scheme="ultra"] {
  --mark-3: #ff9df0;
  --hot: #8b5cf6;
  --hot-2: #ff2ed6;
  --glow-1: #1f1150;
  --glow-2: #3d0b47;
  --drift-1: rgba(139, 92, 246, 0.17);
  --drift-2: rgba(255, 46, 214, 0.15);
}

/* New Year — gold on midnight, the one look that is about a CLOCK rather than
   a season. Its range starts on the 27th because Christmas now ends on Boxing
   Day; two looks claiming the same evening would make the picker's answer
   depend on list order. */
[data-look="newyear"] {
  --glow-1: #0d1a3a;
  --glow-2: #2e2410;
  --drift-1: rgba(255, 209, 102, 0.20);
  --drift-2: rgba(120, 170, 255, 0.16);
  --panel: #121a2e;
  --hot: #ffd166;
  --hot-2: #ffa63a;
  --cool: #78aaff;
  --gold: #ffe08a;
  --bg: #060a18;
  --bg-2: #101a33;
  --a: #ffd166;   /* champagne */
  --b: #78aaff;   /* midnight */
  --c: #ff6f91;   /* rocket */
  --d: #6fe3c4;   /* sparkler */
  --e: #c9a0ff;   /* confetti */
  --f: #ff9f45;   /* ember */
}

/* Easter — the only PASTEL look, which is the point: it is the one time of
   year a pub quiz is in the afternoon with families in. Kept dark behind, like
   every other look, because the projector still has to read from the back. */
[data-look="easter"] {
  --glow-1: #1b2e1a;
  --glow-2: #2e2a12;
  --drift-1: rgba(167, 226, 148, 0.20);
  --drift-2: rgba(255, 214, 235, 0.18);
  --panel: #16211a;
  --hot: #ffb3d1;
  --hot-2: #ffd98a;
  --cool: #a7e294;
  --gold: #ffe9a3;
  --bg: #070f0a;
  --bg-2: #101c14;
  --a: #ffb3d1;   /* blossom */
  --b: #a7e294;   /* spring */
  --c: #9ad4ff;   /* duck egg */
  --d: #ffe07a;   /* daffodil */
  --e: #d3b0ff;   /* crocus */
  --f: #ff9a6c;   /* carrot */
}

/* St Patrick's — green and gold, and the most PUB of the looks. */
[data-look="stpatricks"] {
  --glow-1: #0b2a16;
  --glow-2: #2c2708;
  --drift-1: rgba(74, 214, 122, 0.20);
  --drift-2: rgba(255, 209, 102, 0.16);
  --panel: #10261a;
  --hot: #4ad67a;
  --hot-2: #ffd166;
  --cool: #7ae0ff;
  --gold: #ffdf7a;
  --bg: #04120a;
  --bg-2: #0c2114;
  --a: #4ad67a;   /* shamrock */
  --b: #ffd166;   /* gold */
  --c: #ff8fa3;   /* rosy */
  --d: #7ae0ff;   /* sky */
  --e: #c8a0ff;   /* heather */
  --f: #ff9f45;   /* stout head */
}

/* Bonfire Night — the darkest of them, because that is the whole look: a black
   November sky with things going off in it. */
[data-look="bonfire"] {
  --glow-1: #24102e;
  --glow-2: #3a1608;
  --drift-1: rgba(255, 138, 61, 0.20);
  --drift-2: rgba(120, 170, 255, 0.14);
  --panel: #171224;
  --hot: #ff8a3d;
  --hot-2: #ffd166;
  --cool: #78aaff;
  --gold: #ffcf5c;
  --bg: #05060f;
  --bg-2: #120e1e;
  --a: #ff8a3d;   /* ember */
  --b: #78aaff;   /* smoke-blue */
  --c: #6fe36f;   /* green rocket */
  --d: #ffd166;   /* sparkler */
  --e: #ff5470;   /* red burst */
  --f: #c9a0ff;   /* violet burst */
}

/* Eurovision — the loudest palette in the app, and rightly so: it is one night
   a year and the whole point is that it looks like a light rig. */
[data-look="eurovision"] {
  --glow-1: #2b0f3d;
  --glow-2: #0d2a4a;
  --drift-1: rgba(255, 90, 200, 0.20);
  --drift-2: rgba(90, 200, 255, 0.18);
  --panel: #1a1030;
  --hot: #ff5ac8;
  --hot-2: #ffc84a;
  --cool: #5ac8ff;
  --gold: #ffd76b;
  --bg: #0a0518;
  --bg-2: #180c2e;
  --a: #ff5ac8;   /* stage pink */
  --b: #5ac8ff;   /* stage blue */
  --c: #6fe36f;   /* laser green */
  --d: #ffc84a;   /* spotlight */
  --e: #c07bff;   /* violet wash */
  --f: #ff7a5a;   /* warm gel */
}

/* International — a tournament night. Deliberately NEUTRAL rather than any
   country's colours: it goes up for a World Cup, a Euros and an Olympics, and
   a palette that looked like one nation's flag would be wrong at two of them.
   The flags come from the props, where the room chooses. */
[data-look="international"] {
  --glow-1: #0e2438;
  --glow-2: #16321c;
  --drift-1: rgba(90, 170, 255, 0.18);
  --drift-2: rgba(110, 227, 140, 0.16);
  --panel: #121e2a;
  --hot: #4aa8ff;
  --hot-2: #6fe38c;
  --cool: #9ad4ff;
  --gold: #ffd76b;
  --bg: #060d16;
  --bg-2: #0f1c28;
  --a: #4aa8ff;   /* pitch blue */
  --b: #6fe38c;   /* grass */
  --c: #ff6f91;   /* rosette */
  --d: #ffd76b;   /* medal */
  --e: #c9a0ff;   /* floodlight */
  --f: #ff9f45;   /* terrace orange */
}

[data-look="halloween"] {
  --glow-1: #2a1038;
  --glow-2: #3d1a05;
  --drift-1: rgba(255, 117, 24, 0.18);
  --drift-2: rgba(157, 123, 255, 0.16);
  /* Opaque, unlike the usual translucent panel, so the shapes drifting behind
     the screen do not show through the answers. A ghost half-visible through
     option C is a muddier option C, and legibility beats atmosphere. */
  --panel: #1b1226;
  --hot: #ff7518;
  --hot-2: #ffb03a;
  --cool: #9d7bff;
  --gold: #ffc93c;
  --bg: #0b0611;
  --bg-2: #1a0d22;
  --a: #ff8a3d;   /* pumpkin */
  --b: #9d7bff;   /* witch purple */
  --c: #6fe36f;   /* slime */
  --d: #ffd23f;   /* candle */
  --e: #ff5470;   /* blood */
  --f: #63d6ff;   /* ghost */
}

[data-look="valentines"] {
  --glow-1: #3d0d29;
  --glow-2: #2a0616;
  --drift-1: rgba(255, 45, 111, 0.2);
  --drift-2: rgba(255, 158, 205, 0.14);
  --panel: #26101c;
  --hot: #ff2d6f;
  --hot-2: #ff86b0;
  --cool: #ff9ecd;
  --gold: #ffd6e4;
  --bg: #12040c;
  --bg-2: #26081a;
  --a: #ff6b9d;
  --b: #ffd23f;
  --c: #b48cff;
  --d: #ff9d5c;
  --e: #63d6ff;
  --f: #7cf76b;
}

[data-look="christmas"] {
  --glow-1: #0d2e1c;
  --glow-2: #35060f;
  --drift-1: rgba(232, 18, 60, 0.16);
  --drift-2: rgba(92, 225, 160, 0.16);
  --panel: #0f1f18;
  --hot: #e8123c;
  --hot-2: #ff5f5f;
  --cool: #5ce1a0;
  --gold: #ffd23f;
  --bg: #04100c;
  --bg-2: #0a2018;
  --a: #ff4d5e;   /* holly berry */
  --b: #ffd23f;   /* tinsel */
  --c: #5ce1a0;   /* tree */
  --d: #9fd8ff;   /* frost */
  --e: #ff9d5c;
  --f: #d9b3ff;
}

[data-look="summer"] {
  --glow-1: #0d3350;
  --glow-2: #4a2a05;
  --drift-1: rgba(255, 140, 26, 0.18);
  --drift-2: rgba(23, 195, 247, 0.16);
  --panel: #0d2130;
  --hot: #ff8c1a;
  --hot-2: #ffd23f;
  --cool: #17c3f7;
  --gold: #ffe066;
  --bg: #04121c;
  --bg-2: #0a2436;
  --a: #ffd23f;   /* sun */
  --b: #17c3f7;   /* sea */
  --c: #ff6f61;   /* coral */
  --d: #7cf76b;   /* palm */
  --e: #ff9ecd;
  --f: #b48cff;
}

/* ---- the shapes themselves
   Down the two side margins and nowhere near the middle. A question with a
   clock running wants the whole centre of the projector, and a ghost drifting
   over an option is the one way a decoration could actually lose somebody
   points. */
/* Starts below the topbar. The brand and the countdown live up there on a
   transparent strip, and a pumpkin behind the logo reads as a fault rather
   than as decoration. */
.look-layer {
  position: absolute;
  inset: 64px 0 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}
.motif {
  position: absolute;
  display: block;
  color: var(--hot-2);
  /* Faint enough to be atmosphere rather than something to look at, but not so
     faint that a projector in a bright function room shows nothing at all. */
  opacity: 0.22;
  transform: scale(var(--scale, 1));
  animation: motif-drift var(--drift, 22s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);
}
.motif.right { transform: scale(var(--scale, 1)) scaleX(-1); }
.motif:nth-child(3n) { color: var(--cool); }
.motif:nth-child(3n + 2) { color: var(--hot); }

@keyframes motif-drift {
  from { transform: translateY(-14px) rotate(-7deg) scale(var(--scale, 1)); }
  to   { transform: translateY(14px) rotate(7deg) scale(var(--scale, 1)); }
}
/* Mirrored ones need their own frames, or the scaleX is thrown away mid-drift. */
.motif.right { animation-name: motif-drift-right; }
@keyframes motif-drift-right {
  from { transform: translateY(-14px) rotate(7deg) scale(var(--scale, 1)) scaleX(-1); }
  to   { transform: translateY(14px) rotate(-7deg) scale(var(--scale, 1)) scaleX(-1); }
}

/* Everything the room reads sits above them, whatever else changes. */
.stage > .card, .stage > .topbar { position: relative; z-index: 1; }

/* And the big text separates itself from whatever drifts behind it. The panels
   are opaque so an answer is safe, but the question and the countdown sit on
   bare background — a snowflake behind the word "UK" is legible in a dark room
   and less so in a function room with the lights half up. One shadow costs
   nothing and takes the question out of the argument entirely. */
[data-look]:not([data-look="default"]) .q-prompt,
[data-look]:not([data-look="default"]) .timer-num,
[data-look]:not([data-look="default"]) .brand-name,
[data-look]:not([data-look="default"]) .round-intro h1,
[data-look]:not([data-look="default"]) .answer-said {
  text-shadow: 0 2px 12px var(--bg), 0 0 3px var(--bg);
}

/* Somebody who has asked their device to stop moving things gets a still
   picture rather than no picture — the look is the point, the drifting is not. */
@media (prefers-reduced-motion: reduce) {
  .motif { animation: none; }
}

/* On a phone they go behind everything at a lower strength: the screen is
   small, the buttons matter, and there is no margin to speak of.
   `.wrap` has to be lifted or the shapes float over the answer buttons — the
   projector gets this from `.stage > .card` and the phone has no equivalent. */
.phone .look-layer { position: fixed; z-index: 0; }
.phone .wrap { position: relative; z-index: 1; }
.phone .motif { opacity: 0.1; }

/* A tab for something not on this plan. Shown rather than hidden: a thing you
   can see and cannot use is a thing you might buy, and a thing that is not
   there is a thing nobody knew existed. */
.tab.locked { opacity: 0.55; }
.tab.locked:hover { opacity: 0.8; }
.tabcount.lock { background: rgba(255, 210, 63, 0.22); color: var(--gold); }

/* Signing in. One form, in the middle, on a page that does nothing else. */
.sign-in { max-width: 380px; margin: 12vh auto; display: grid; gap: 16px; }
.sign-in-brand { display: flex; align-items: center; gap: 10px; justify-content: center; }
.sign-in h1 { margin: 0; text-align: center; font-size: 30px; }
.sign-in form { display: grid; gap: 12px; }
.sign-in label { display: grid; gap: 5px; font-size: 13px; font-weight: 700; color: var(--ink-dim); }
.sign-in input {
  padding: 13px;
  border-radius: 12px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink);
  font: inherit;
  font-size: 16px; /* iOS zooms the page in on anything smaller */
}
.sign-in input:focus { outline: none; border-color: var(--hot); }
.sign-in button.go {
  padding: 13px;
  border: none;
  border-radius: 12px;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c;
  font: inherit;
  font-weight: 900;
  font-size: 16px;
  cursor: pointer;
}
.sign-in button.go:disabled { opacity: 0.6; cursor: default; }
.sign-in-problem:not(:empty) {
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(255, 77, 94, 0.14);
  border: 1px solid rgba(255, 77, 94, 0.4);
  color: var(--ink);
  font-size: 14px;
}
.sign-in .tiny { text-align: center; }
.subs { display: grid; gap: 8px; }
.inv-actions .addon.on { border-color: var(--good); color: var(--good); }

/* A photo somebody has just sent, big, in the middle, for a few seconds.
   The strip along the bottom is the record of the night; this is the moment.
   At 13vh in the strip a face is the size of a stamp on a projector, which is
   the opposite of the point of letting the room send them at all. */
.photo-big {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  z-index: 3;
  background: radial-gradient(60% 60% at 50% 50%, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.35));
  animation: photo-big-fade 420ms ease;
}
/* A POLAROID: white all round, deep at the bottom, and tilted a few degrees.
   The frame is what makes a phone snap read as a keepsake from the night
   rather than as a picture the projector happened to put up — and the tilt
   makes a run of them look like prints dropped on a table.

   The angle comes from JS as `--tilt`, and every transform here has to carry
   it: leave it out of a keyframe and the photo snaps square halfway through. */
.photo-big figure {
  margin: 0;
  padding: 1.6vh 1.6vh 0;
  background: #f7f5ef;
  border-radius: 4px;
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.75), 0 2px 0 rgba(255, 255, 255, 0.6) inset;
  transform: rotate(var(--tilt, 0deg));
  animation: photo-big-in 520ms cubic-bezier(0.2, 1.1, 0.4, 1);
}
/* Tall rather than wide: a phone photo is portrait, and filling the height is
   what makes it read from the back of a room. A touch shorter than it was, to
   pay for the frame without the whole thing growing. */
.photo-big img {
  display: block;
  max-height: 60vh;
  max-width: 74vw;
  object-fit: contain;
  background: #241f1a;
}
/* The wide bottom lip is the half that says "polaroid". The name sits in it,
   dark on white, because a caption in the app's own light-on-dark would read
   as a label stuck over the print rather than written on it. */
.photo-big figcaption {
  padding: 1.1vh 0 1.9vh;
  font-family: 'Segoe Print', 'Bradley Hand', 'Comic Sans MS', cursive;
  font-size: 3.2vh;
  font-weight: 700;
  text-align: center;
  color: #241f1a;
}
.photo-big.going { animation: photo-big-fade 900ms ease reverse forwards; }
.photo-big.going figure { animation: photo-big-out 900ms ease forwards; }

@keyframes photo-big-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes photo-big-in {
  from { opacity: 0; transform: rotate(var(--tilt, 0deg)) scale(0.86); }
  to   { opacity: 1; transform: rotate(var(--tilt, 0deg)) scale(1); }
}
@keyframes photo-big-out {
  from { opacity: 1; transform: rotate(var(--tilt, 0deg)) scale(1); }
  to   { opacity: 0; transform: rotate(var(--tilt, 0deg)) scale(1.05); }
}

/* The strip underneath, a size up. It was small enough that a face in it was
   unidentifiable, which made it decoration rather than a record. */
.photo-strip .photo { width: 18vh; }
.photo-strip .photo img { height: 18vh; }
.photo-strip .photo figcaption { font-size: 1.6vh; }

/* Props to stick on a photo. The row people actually wanted — the colour looks
   are still there, folded away underneath. */
/* As many across as fit, rather than a fixed count — there are two dozen props
   now and a hard number is wrong on either a 320px phone or a wide one. All of
   them stay on screen and the panel scrolls: a row that scrolls SIDEWAYS is how
   the whole feature went unnoticed the first time. */
.cam-props {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(66px, 1fr));
  gap: 6px;
  margin-bottom: 6px;
}
/* A mid-grey tile, not the page's own dark panel.
   Half these props are nearly black — the shades, the moustache, the disguise —
   and on a dark tile they read as an empty square you would never tap. A pale
   slab each is three dozen white blocks shouting on a dark phone, so it is a
   mid tone that both the black shapes and the yellow crown stand out against.

   A GRADIENT rather than one flat fill, and that is not decoration. The props
   are flat colour, so a tile of a single tone can be matched exactly by one of
   them and that prop disappears — the viking helmet was drawn in the tile's
   own grey and read as two horns floating in nothing. Against a tone that
   changes down the tile, no flat colour can hide across the whole of it. */
.cam-prop {
  padding: 6px 3px 4px;
  border-radius: 12px;
  border: 1px solid var(--panel-line);
  background: linear-gradient(160deg, #b6bccb 0%, #8b91a6 55%, #6f7689 100%);
  cursor: pointer;
  display: grid;
  gap: 2px;
  justify-items: center;
  -webkit-tap-highlight-color: transparent;
}
/* NAMED as well as drawn, unlike the colour looks — those were replaced by a
   thumbnail of your own photo because a name told you nothing. A prop is the
   other way round: at this size a monobrow and a moustache are the same dark
   smear, and a beard reads as a shield. */
.cam-prop-art { display: block; width: 100%; aspect-ratio: 1; }
.cam-prop-name {
  font-size: 9px;
  font-weight: 800;
  line-height: 1.1;
  color: #1b1e28;
  text-align: center;
}
/* `pan-y`, NOT `none`.
   A tile is dragged off, but scrolling the sheet to REACH the tray is the same
   movement of the same thumb. `none` claimed the gesture outright and the tray
   became unreachable — with three dozen tiles, most of that area is tiles, so
   there was nothing left to scroll with. The browser keeps vertical scrolling;
   the tile takes over only after a short hold. See HOLD_MS in play.js. */
.cam-prop { touch-action: pan-y; }
.cam-prop:active { transform: scale(0.94); }
.cam-prop-art svg { width: 100%; height: 100%; display: block; }

.cam-frame { position: relative; }
/* CENTRED, with Undo on the right and no say in where the words sit.
   It was `space-between`, so the heading sat left while Undo was showing and
   jumped to the middle the moment the last prop came off — a heading that
   moves when you press something else reads as a fault.

   THREE COLUMNS RATHER THAN AN ABSOLUTE BUTTON. Absolute was the first go and
   it collided: once the row was inside the guide card it lost 20px of padding
   either side, and at 320px the centred heading ran under Undo with nothing to
   stop it. Two matching `1fr` columns keep the middle one dead centre, and
   when there is genuinely not enough room the grid gives the button its width
   and moves the heading rather than printing one on top of the other.

   Measured: dead centre at 390px in both states, and at 320px the heading
   sits 7px left of centre while Undo is up, clearing it by 8px. Seven pixels
   once, when the first prop goes on, is the cheaper of the two — the other
   way of buying it back is a smaller button, and Undo is already a small
   target on a phone. */
.cam-looks-head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}
.cam-looks-head > span { grid-column: 2; }
.cam-looks-head .cam-undo { grid-column: 3; justify-self: end; }
/* Tonight's look gets the top row and says which night it is, in the gold the
   rest of the panel's headings use. Only ever on screen when the game carries
   a look — an ordinary quiz sees the tray it always saw. */
.cam-season-head .cam-season-name { color: var(--gold); }
.cam-props-season { margin-bottom: 10px; }
.cam-undo {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--panel-line);
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
/* Four gestures as a 2x2 BLOCK of CHIPS rather than a sentence.
   Written as one line of prose it broke wherever the phone happened to run out
   of room — "pinch to size" on one line and "and turn" on the next, with a
   short ragged line under it. Wrapped as bare chunks it was even but looked
   like four pieces of text floating over the tray with nothing holding them.

   `1fr` columns rather than `auto`: with `auto` each column sizes to its own
   longest chunk, so "Tap to add" got a chip half the width of the one beside
   it and the block read as four different objects. Equal columns and equal
   rows (`grid-auto-rows: 1fr`) make four identical cards, which is the whole
   point of giving them a background.

   AND IT NEEDS ITS OWN FONT SIZE. `.tiny` has a size on the console and on the
   control view and has never had one on the phone, so the hint was coming out
   at body size — as big as the question text, three lines of it, shouting
   above a tray of props. */
.cam-hint {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
  gap: 6px;
  margin: 0;
  font-size: 12px;
  line-height: 1.3;
}
.cam-hint span {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 7px 6px;
  border-radius: 10px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink-dim);
  font-weight: 700;
  text-align: center;
}
/* THE HEADING AND THE GESTURES ARE ONE CARD, tinted in the same gold the
   heading is written in — a heading floating above a block of chips is a
   fifth loose object rather than a label on them.

   NOT the red-into-orange of the button above it. That gradient is the app's
   one "press this" — Launch on the console, Send it up here — and the same
   reasoning that keeps it off the shop's Buy button keeps it off a panel of
   instructions. Two of them stacked, the lower one doing nothing when pressed,
   costs the real button its meaning.

   The tint is two pseudo-elements rather than a colour written out, because
   `--gold` is redefined by every seasonal look — eleven times in this file —
   so a hardcoded gold would be the one thing on a Halloween phone still
   wearing the ordinary night's palette. `z-index: -1` behind the content,
   with the card making its own stacking context so it cannot escape. */
.cam-guide {
  position: relative;
  z-index: 0;
  border-radius: 14px;
  padding: 10px;
  margin: 14px 0 10px;
}
.cam-guide::before,
.cam-guide::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}
.cam-guide::before { z-index: -2; background: var(--gold); opacity: 0.08; }
.cam-guide::after { z-index: -1; border: 1px solid var(--gold); opacity: 0.3; }
.cam-guide .cam-looks-head { margin: 0 0 9px; }
/* touch-action none so dragging a prop does not scroll the sheet under it —
   without this the picture slides away the moment a finger moves. */
.cam-frame .cam-canvas { touch-action: none; }

/* "Something wrong with this one?" on the control view.
   Deliberately quiet and full width at the bottom of the answer key, nowhere
   near the primary button — it is never needed in a hurry, and a mis-tap
   beside Reveal is the thing the rest of that screen exists to prevent. */
.report-q {
  margin-top: 12px;
  width: 100%;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px dashed var(--panel-line);
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.report-q:hover:not(:disabled) { color: var(--ink); border-color: var(--gold); }
.report-q:disabled { opacity: 0.7; cursor: default; color: var(--gold); border-style: solid; }

/* Reported questions on the owner page. The question itself is the thing you
   read, so it gets the size; everything else is context underneath it. */
.rep-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 0;
  border-bottom: 1px solid var(--panel-line);
}
.rep-row:last-child { border-bottom: 0; }
.rep-row.done { opacity: 0.55; }
.rep-main { flex: 1 1 auto; min-width: 0; }
.rep-q { font-weight: 700; margin-bottom: 4px; }
.rep-acts { flex: 0 0 auto; display: flex; gap: 8px; align-items: center; }
.rep-acts a.minor { text-decoration: none; display: inline-block; }

/* "You are wearing the quizmaster hat." Gold rather than red — it is a mode,
   not a fault — and in the flow of the page rather than floating over it, so
   it cannot cover a control on a phone held in the dark. */
.acting-bar {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: 8px 14px;
  background: rgba(255, 210, 63, 0.14);
  border-bottom: 1px solid rgba(255, 210, 63, 0.45);
  color: var(--ink);
  font-size: 13px;
}
.acting-bar button {
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 210, 63, 0.6);
  background: none;
  color: var(--gold);
  font: inherit;
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
}

/* ---- the hat switch: Owner | Quizmaster, top right, one tap either way.
   It is the sign as well as the switch, so the LIVE half is a solid block of
   colour and the other is barely there — pink for the owner, gold for the
   quizmaster, the same gold the acting bar has always used. Answered at a
   glance, not read. The dim half still passes contrast, because it is a button
   you are meant to find. */
.hat-switch {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--panel-line);
  flex: 0 0 auto;
}
.hat-switch .hat-half {
  padding: 7px 14px;
  border: none;
  border-radius: 999px;
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  cursor: pointer;
  white-space: nowrap;
  transition: background 140ms ease, color 140ms ease;
}
.hat-switch .hat-half:hover:not(.live) { background: rgba(255, 255, 255, 0.08); color: var(--ink); }
/* Whichever half is live wears the app's own red-into-orange, whatever it is.
   The tier rungs beside it carry the metals — bronze, silver, gold — because
   there the colour IS the meaning. Two colour languages in one control would
   be one too many. Which HAT is on is still unmissable: the body takes a gold
   hairline under the topbar whenever the quizmaster hat is worn. */
.hat-switch .hat-half.live {
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c;
}
.hat-switch.working { opacity: 0.55; }
.hat-switch .hat-half:disabled { cursor: default; }

/* Which RUNG the hat is being worn as. Inside the switch, because it only means
   anything while the hat is on. Initials rather than words — B S G with "All"
   in front — because this is a corner of a topbar, not a settings page, and the
   metal on the live one says which is which faster than the label would. */
.tier-preview {
  display: inline-flex; align-items: center; gap: 2px;
  margin-left: 4px; padding-left: 6px;
  border-left: 1px solid var(--panel-line);
}
.tier-preview .tier-half {
  min-width: 24px;
  padding: 5px 6px;
  border: none; border-radius: 999px;
  background: none; color: var(--ink-dim);
  font: inherit; font-size: 11px; font-weight: 800;
  cursor: pointer;
}
.tier-preview .tier-half:hover:not(.live) { background: rgba(255, 255, 255, 0.1); color: var(--ink); }
.tier-preview .tier-half.live { background: rgba(255, 255, 255, 0.16); color: var(--ink); }
.tier-preview .tier-half.t-bronze.live { background: #cd7f32; color: #1a0e02; }
.tier-preview .tier-half.t-silver.live { background: #c0c7d0; color: #10131a; }
.tier-preview .tier-half.t-gold.live   { background: #ffd23f; color: #1a1200; }
.tier-preview .tier-half:disabled { opacity: 0.5; cursor: default; }

/* The whole page picks up a gold hairline while the quizmaster hat is on, so
   even a screenshot of the middle of the console says which hat it was taken
   in. Cheaper than a banner and it covers nothing. */
body.wearing-hat .topbar { border-bottom-color: rgba(255, 210, 63, 0.5); }

/* On a phone the topbar is already tight, so the switch loses its padding
   before it loses its words — "Owner" and "Quizmaster" are the whole point. */
@media (max-width: 560px) {
  .hat-switch .hat-half { padding: 6px 10px; font-size: 11px; }
}

/* The record of what the owner did while inside somebody's account. Small and
   plain: it is a log to scan, not a feature to admire — and it has to stay
   readable when it is forty lines long. */
.support-log { margin-top: 8px; max-height: 260px; overflow-y: auto; }
.support-row {
  display: flex; gap: 12px; align-items: baseline;
  padding: 6px 0; border-top: 1px solid var(--panel-line);
  font-size: 12.5px;
}
.support-row .tiny { flex: 0 0 auto; min-width: 132px; }

/* "Still need help?" — shown while there is still time to answer, not at the
   moment the grant dies. Gold rather than red: nothing is wrong, something is
   about to lapse, and those are different feelings. */
.support-still {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 10px; padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 210, 63, 0.5);
  background: rgba(255, 210, 63, 0.1);
  font-size: 13.5px;
}

/* The suggestion box. Plain and quick: the point is that sending one costs
   almost nothing, so it must not look like a form to fill in. */
.sugg-kinds { flex-wrap: wrap; gap: 6px; }
.sugg-kind.on {
  background: linear-gradient(120deg, var(--hot), var(--hot-2)) !important;
  color: #12000c; border-color: transparent;
}
.sugg-text {
  width: 100%; margin-top: 10px; padding: 11px 13px;
  border-radius: 12px; border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.05); color: var(--ink);
  font: inherit; font-size: 14px; resize: vertical;
}
.sugg-text:focus { outline: none; border-color: var(--hot); }
/* Every `.go` in this stylesheet is scoped to the panel it sits in — there is
   no generic rule — so this button needs its own or it draws as the browser's
   default grey, which reads as disabled next to the lit kind chips. */
.sugg-send {
  border: none; border-radius: 10px; padding: 11px 20px; cursor: pointer;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c; font-family: inherit; font-weight: 900; font-size: 14.5px;
}
.sugg-send:hover { filter: brightness(1.08); }

/* ---- the inbox.
   A message list rather than a table: each one is somebody's sentence, and the
   thing you do with it is read it and answer it. Cleared ones fade rather than
   disappear, so the list still shows the shape of what has been dealt with. */
.sugg-item {
  padding: 14px 16px; margin-bottom: 10px;
  border-radius: 14px;
  border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.04);
}
.sugg-item.done { opacity: 0.62; }
.sugg-head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.sugg-said { margin-top: 7px; font-size: 15px; line-height: 1.5; }
.sugg-kind-tag {
  font-size: 10.5px; font-weight: 900; letter-spacing: 0.05em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.1); color: var(--ink-dim);
}
/* Broken is the one worth spotting from across the page — it is the only kind
   that might be costing somebody a gig right now. */
.sugg-kind-tag.broken { background: rgba(255, 77, 94, 0.18); color: var(--bad); }
.sugg-kind-tag.idea { background: rgba(47, 224, 122, 0.16); color: var(--good); }
/* A pack request wears Gold, because that is the rung it comes from and
 * because it is the one entry in this list that costs money to answer. */
.sugg-kind-tag.pack { background: rgba(255, 200, 90, 0.18); color: #ffc85a; }
.pile.packs.on { border-color: #ffc85a; color: #ffc85a; }
.sugg-reply {
  margin-top: 10px; padding: 9px 13px;
  border-left: 3px solid var(--hot);
  background: rgba(255, 255, 255, 0.04); border-radius: 0 10px 10px 0;
  font-size: 14px; line-height: 1.5;
}
.sugg-acts { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.sugg-compose { margin-top: 11px; }
.sugg-draft {
  width: 100%; padding: 11px 13px;
  border-radius: 12px; border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.05); color: var(--ink);
  font: inherit; font-size: 14px; resize: vertical;
}
.sugg-draft:focus { outline: none; border-color: var(--hot); }
.sugg-compose .go {
  border: none; border-radius: 10px; padding: 10px 18px; cursor: pointer;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c; font-family: inherit; font-weight: 900; font-size: 14px;
}
.pile.on {
  background: linear-gradient(120deg, var(--hot), var(--hot-2)) !important;
  color: #12000c; border-color: transparent;
}

/* What a quizmaster has sent, and what came back. */
.sugg-mine-row {
  padding: 10px 0; border-top: 1px solid var(--panel-line);
  font-size: 14px; line-height: 1.5;
}

/* Whether they have opened a reply. Deliberately "opened" rather than "read":
   it means their console drew it, which is all any read receipt has ever
   meant, and overstating it would be the one thing on this page that lies. */
.seen.yes { color: var(--good); }
.seen.no { color: var(--ink-dim); }

/* What the drafting model is told. Folded away, because it is a thing you edit
   once a month rather than something to read every time you open the inbox. */
.house { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--panel-line); }
.house summary { cursor: pointer; color: var(--ink-dim); }
.house summary:hover { color: var(--ink); }
.house-note { margin: 8px 0; max-width: 720px; line-height: 1.55; }
.house-text {
  width: 100%; padding: 11px 13px;
  border-radius: 12px; border: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.05); color: var(--ink);
  font: inherit; font-size: 13.5px; resize: vertical;
}
.house-text:focus { outline: none; border-color: var(--hot); }


/*
 * A pack somebody wrote themselves.
 *
 * A quiet gold hairline and a small word, not a badge shouting for attention:
 * the grid is mostly the catalogue and this says "and this one is mine" at a
 * glance without turning the shelf into two shelves. Same gold as the hat's
 * hairline, because it means the same thing there — whose this is.
 */
.pack-card.own { position: relative; border-color: rgba(255, 200, 90, 0.45); }
/* The title is a button and runs full width, so the badge must not eat a tap
   meant for it — hence pointer-events: none below. */
.pack-card.own .pack-title { padding-right: 56px; }
.pack-yours {
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 0.62rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #ffc85a;
  border: 1px solid rgba(255, 200, 90, 0.45);
  border-radius: 999px;
  padding: 1px 7px;
  pointer-events: none;
}

/*
 * "Open the editor" on the own-packs panel.
 *
 * Every `.go` in this stylesheet is scoped to the panel it sits in — there is
 * no generic rule — so an anchor with that class drew as a plain underlined
 * link, which reads as a footnote rather than as the way in. Its own class,
 * sized to sit under a paragraph rather than fill the panel.
 */
.own-open {
  /* inline-block, not a bare inline: with padding on an inline element the
     button overlaps the paragraph above and below it rather than taking a line
     of its own. */
  display: inline-block;
  align-self: flex-start;
  margin: 12px 0 4px;
  border-radius: 10px;
  padding: 11px 20px;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  color: #12000c;
  font-weight: 900;
  font-size: 15px;
  text-decoration: none;
}
.own-open:hover { filter: brightness(1.08); }

/* The not-backed-up line under it is a separate thought, not a caption. */
.panel .own-open + .warn { display: block; margin-top: 8px; }


/* ---------------------------------------------------- the owner's overview
 *
 * Four sections that did not exist: what is running tonight, the money, the
 * catalogue as a product, and one subscriber opened up. All of them are rows
 * of "a thing on the left, a number on the right", so they share one shape
 * rather than growing four near-identical ones that drift.
 */
.own-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.own-fig {
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 12px;
  padding: 14px;
}
.own-fig b { display: block; font-size: 26px; font-weight: 900; line-height: 1.1; }
.own-fig span { display: block; margin-top: 4px; font-size: 12px; opacity: 0.72; }
/* Spending more than is arriving is the one figure that should catch the eye. */
.own-fig.bad { border-color: var(--bad); }
.own-fig.bad b { color: var(--bad); }

/*
 * The month against its ceiling.
 *
 * Three states rather than a colour that slides: "fine", "getting close" and
 * "over" are the only three things anybody does anything about, and a bar that
 * shaded gradually would need reading rather than glancing at.
 */
.budget-bar {
  margin-top: 12px;
  height: 12px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  overflow: hidden;
}
.budget-bar span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--hot, #ff2e88), var(--hot-2, #ff8a3d));
}
.budget-bar.close span { background: var(--gold); }
.budget-bar.over span { background: var(--bad); }

.budget-set { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.budget-set label { display: flex; align-items: center; gap: 8px; }
.budget-input {
  width: 108px;
  padding: 8px 10px;
  font: inherit;
  color: inherit;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 8px;
}

/* One job's share of the bill, under its name. Thin on purpose — it is a sense
 * of proportion beside a number, not a chart. */
.job-bar {
  margin-top: 6px;
  height: 5px;
  max-width: 380px;
  border-radius: 999px;
  background: var(--panel-line);
  overflow: hidden;
}
.job-bar span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--hot, #ff2e88), var(--hot-2, #ff8a3d));
}
.own-row-num .tiny { display: block; opacity: 0.7; font-weight: 600; }

.own-rows { display: grid; gap: 6px; }
.own-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 10px;
}
.own-row-main { min-width: 0; }
.own-row-num { flex: 0 0 auto; text-align: right; white-space: nowrap; }
/* A room with a game actually in it — the reason this tab exists. */
.own-row.live { border-color: rgba(255, 200, 90, 0.5); }
.own-live { color: #ffc85a; font-weight: 900; font-size: 13px; }
.own-code {
  margin-left: 8px; padding: 1px 7px; border-radius: 6px;
  background: rgba(255,255,255,0.08); font-weight: 900; letter-spacing: 0.08em; font-size: 12px;
}
.own-flag {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px; font-size: 11px;
  background: rgba(255, 200, 90, 0.18); color: #ffc85a;
}
.own-flag.bad { background: rgba(255, 90, 90, 0.18); color: var(--bad); }

/* One subscriber, opened up under their own row. */
.panel.person { border-color: var(--hot); margin: -2px 0 10px; }
.inv-main.open-person { cursor: pointer; }
.inv-main.open-person:hover b { text-decoration: underline; }


/* --------------------------------------------------------------- the shop
 *
 * A pack on the shelf rather than in the library. Deliberately quieter than a
 * card you can launch: it is something to consider, not something to do — and
 * the packs you can actually run tonight have to stay the loud half of the
 * page for somebody who opened it ten minutes before a gig.
 */
.pack-card.locked {
  border-style: dashed;
  border-color: var(--panel-line);
  opacity: 0.92;
}
.pack-card.locked .pack-title { opacity: 0.9; }
.shop-price { font-size: 20px; font-weight: 900; opacity: 1; margin-top: 4px; }
.pack-card.locked .go.buy {
  width: 100%; margin-top: 10px;
  border: none; border-radius: 10px; padding: 11px; cursor: pointer;
  /* Not the app's own red-into-orange: that gradient is Launch, and one shape
     and one colour for "start the night" is worth more than a loud shop. */
  background: rgba(255,255,255,0.1); color: var(--ink);
  font-family: inherit; font-weight: 900; font-size: 15px;
}
.pack-card.locked .go.buy:hover { background: rgba(255,255,255,0.16); }
.game-section .shop-head { margin-top: 26px; }


/* A pack written to be current, and one that no longer is.
 *
 * Not hidden when it expires — a pack that vanished reads as lost work, and
 * last month's news round is a fine thing to run on purpose. It just stops
 * being the first thing you see, and it says which week it was for.
 */
/*
 * What a rung includes, as a STATEMENT rather than a switch.
 *
 * Content is not something you turn off — it is what you pay for — so it gets
 * the word "included" where a capability gets an On | Off. Drawn quietly and
 * without a border of its own, so it reads as the heading of the section
 * rather than as the first item in a list of controls.
 */
.acct-toggle.content { background: rgba(255, 255, 255, 0.03); }
.feat-said {
  font-size: 11px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-dim); white-space: nowrap; padding: 6px 10px;
}

/*
 * "Nothing here for the night you have booked?" — under the shop, because that
 * is where the want arrives. Quieter than a pack card on purpose: it is the
 * last thing on the tab and must not compete with Launch.
 */
.panel.ask-pack { margin-top: 18px; }
.panel.ask-pack h3 { margin-bottom: 4px; }
.panel.ask-pack textarea {
  width: 100%; margin-top: 10px; padding: 10px; border-radius: 10px;
  background: var(--panel-2, rgba(255,255,255,0.04)); color: var(--ink);
  border: 1px solid var(--panel-line); font: inherit; font-size: 14px; resize: vertical;
}
.panel.ask-pack.locked { border-style: dashed; opacity: 0.85; }
/* Every .go rule in this stylesheet is panel-scoped, so a new panel gets a
 * plain grey button unless it says otherwise. Same trap the own-packs link hit. */
.panel.ask-pack .go {
  border: none; border-radius: 10px; padding: 11px 20px; cursor: pointer; white-space: nowrap;
  background: linear-gradient(120deg, var(--hot), var(--hot-2)); color: #12000c; font-weight: 900; font-size: 14px;
}
.panel.ask-pack .go:disabled { opacity: 0.45; cursor: default; }

/*
 * "18 phones waiting to join." Gold rather than red: nothing has gone wrong,
 * the host is being asked a question. Above the player list, where they are
 * already looking while a room fills up.
 */
.panel.joinq { border-color: #ffc85a; }
.panel.joinq h3 { color: #ffc85a; }
.panel.joinq .go {
  margin-top: 10px; width: 100%; border: none; border-radius: 10px; padding: 13px;
  background: linear-gradient(120deg, var(--hot), var(--hot-2)); color: #12000c;
  font-weight: 900; font-size: 16px; cursor: pointer;
}
.plist + .tidy, button.tidy { margin-top: 10px; width: 100%; }

.pack-card .fresh { color: #ffc85a; }
.pack-card .fresh.gone { color: var(--muted); }
.pack-card.stale { opacity: 0.72; }
.pack-card.stale:hover { opacity: 1; }

/* Your four letters, at a size you can read out over a microphone. */
.room-code {
  font-size: 40px; font-weight: 900; letter-spacing: 0.14em;
  line-height: 1.1; margin: 6px 0 8px;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* Which tier a locked thing is on, in that tier's own colour. A bare gold plus
   on everything says "there is more" and nothing else; these say which rung,
   before the tooltip is read. */
.tabcount.lock.lock-silver { background: rgba(203, 213, 225, 0.2); color: #cbd5e1; }
.tabcount.lock.lock-gold   { background: rgba(255, 210, 63, 0.22); color: var(--gold); }
.tabcount.lock.lock-bronze { background: rgba(205, 127, 50, 0.22); color: #e0a066; }

/* Sign out belongs in the furniture, not four panels down a settings page. */
.topbar-out { margin-left: 10px; }

/* ---- The launch bar: the fast way to start a night.
 *
 * Deliberately the loudest thing on the page when nothing is running, and gone
 * entirely when something is — the top of the console answers one question at
 * a time, and mid-game that question is "what is on the projector".
 */
.launchbar { border-color: rgba(255, 46, 136, 0.35); }
.lb-find { display: flex; gap: 8px; align-items: center; margin-top: 10px; flex-wrap: wrap; }
.lb-game { flex: 0 0 auto; }
.lb-search { position: relative; flex: 1 1 260px; min-width: 200px; }
.lb-text {
  width: 100%; padding: 13px 14px; font: inherit; font-size: 17px;
  color: inherit; background: var(--panel); border: 1px solid var(--panel-line);
  border-radius: 10px;
}
.lb-text:focus { outline: none; border-color: var(--hot); }

/* The matches, over the page rather than pushing it about — a list that shoved
   everything down on every keystroke would move the button you are aiming for. */
.lb-hits {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 40;
  display: grid; gap: 2px; padding: 6px;
  background: var(--bg-2, #14141f); border: 1px solid var(--panel-line);
  border-radius: 10px; box-shadow: var(--shadow);
}
.lb-hit {
  text-align: left; padding: 11px 12px; font: inherit; font-weight: 700;
  color: inherit; background: none; border: none; border-radius: 8px; cursor: pointer;
}
.lb-hit:hover, .lb-hit:focus { background: rgba(255, 255, 255, 0.09); outline: none; }

.lb-set { display: flex; gap: 10px; flex-wrap: wrap; margin: 12px 0 10px; }
/* Big, because it is pressed in a dark room by somebody who is late.
 * The gradient is written out here rather than inherited: the app's `.go`
 * fill is scoped to `.pack-card .go`, so a Launch button anywhere else came
 * out grey — which on the loudest control on the page reads as disabled. */
.lb-go {
  width: 100%; padding: 18px; font-size: 19px; font-weight: 900;
  border: none; border-radius: 12px; cursor: pointer;
  font-family: inherit; letter-spacing: 0.01em; color: #12000c;
  background: linear-gradient(120deg, var(--hot), var(--hot-2));
}
.lb-go:hover { filter: brightness(1.08); }
.lb-go:disabled { opacity: 0.4; cursor: default; filter: none; }

/* The two no-typing picks. Side by side where there is room and stacked on a
   phone, because the choice is meant to be read in one glance rather than
   scrolled. They sit BELOW the search box: somebody who knows the pack name
   types it, and somebody who does not is the person these are for. */
.lb-quick { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
.lb-quick:not(:empty) { margin-top: 12px; }
.lb-quick-go { display: grid; gap: 3px; padding: 15px 16px; font-size: 17px; }
.lb-quick-title { font-weight: 900; }
/* Why this one is being offered, in the button's own dark ink rather than the
   page's grey — on a gradient fill a `.tiny` grey is close to invisible. */
.lb-quick-why { font-size: 12px; font-weight: 700; opacity: 0.72; }

/* Who is already in the shared portrait library. Names rather than pictures:
   the job is spotting "michael-jackson" next to "mj", which is a reading job. */
.pic-lib { margin-top: 10px; }
.pic-lib summary { cursor: pointer; opacity: 0.8; }
.lib-names { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.lib-name {
  font-size: 12px; font-weight: 700; padding: 4px 9px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--panel-line);
}
/* A stand-in is not a drawing anybody paid for, so it reads quieter. */
.lib-name.stand-in { opacity: 0.5; font-weight: 600; }
