/* One stylesheet for all three screens. Dark by default: the room will be dim,
   and a white projector fills an auditorium with glare. */

:root {
  --bg: #0b0f14;
  --panel: #141b24;
  --panel-2: #1c2530;
  --line: #2a3644;
  --ink: #eef4fb;
  --muted: #94a6ba;
  --accent: #3b82f6;
  --green: #16a34a;
  --red: #dc2626;
  --radius: 14px;
  --pad: clamp(16px, 4vw, 32px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

body { overflow-x: hidden; }

button {
  font: inherit;
  color: inherit;
  touch-action: manipulation;
  cursor: pointer;
}

.hidden { display: none !important; }

/* The three seconds before the first question. Opaque and on top of
   everything, because its whole job is to be the only thing in the room
   worth looking at for a moment. */
/* While it is up nothing behind it should be reachable or visible — including
   the scrollbar of whatever screen it covers. */
body:has(> .countdown:not(.hidden)) { overflow: hidden; }
.countdown {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: var(--bg);
}
.countdown span {
  font-size: clamp(140px, 42vmin, 460px);
  font-weight: 800;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  animation: countdown-tick 1s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
/* Punch in, hold, then leave. The hold is the point: a digit that starts
   fading immediately is faint for most of its second, which on a projector
   reads as a screen that is already going wrong. */
@keyframes countdown-tick {
  from { transform: scale(0.55); opacity: 0; }
  18%  { transform: scale(1); opacity: 1; }
  72%  { transform: scale(1); opacity: 1; }
  to   { transform: scale(1.14); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .countdown span { animation: none; opacity: 1; }
}

/* --- connection banner ---------------------------------------------------- */

.conn {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  padding: 10px var(--pad);
  background: #7c2d12;
  color: #ffedd5;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  transform: translateY(-100%);
  transition: transform 0.2s ease;
}
.conn.show { transform: translateY(0); }

/* --- phone ---------------------------------------------------------------- */

.phone {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  padding: var(--pad);
  gap: 18px;
}

.phone__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  color: var(--muted);
  font-size: 14px;
  letter-spacing: 0.02em;
}

.phone__step { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }

.phone__q {
  margin: 0;
  font-size: clamp(22px, 6.5vw, 30px);
  line-height: 1.3;
  font-weight: 700;
}

/* The answers so far, one chip per question. */
.trail {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 5px;
}
.trail__i {
  display: grid;
  justify-items: center;
  padding: 4px 0 5px;
  border-radius: 9px;
  background: var(--panel);
  border: 2px solid var(--line);
  line-height: 1.1;
}
.trail__i span { font-size: 11px; color: var(--muted); }
.trail__i b { font-size: 16px; color: var(--muted); }
.trail__i.is-done { background: #14304f; border-color: #1e4a7a; }
.trail__i.is-done b { color: #fff; }
.trail__i.is-now { border-color: var(--accent); }

.opts { display: flex; flex-direction: column; gap: 12px; }

.opt {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  min-height: 72px;
  padding: 16px 18px;
  text-align: left;
  background: var(--panel);
  border: 2px solid var(--line);
  border-radius: var(--radius);
  font-size: 17px;
  line-height: 1.35;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.06s ease;
}
.opt:active { transform: scale(0.99); }
.opt__key {
  flex: 0 0 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: var(--panel-2);
  font-weight: 700;
  color: var(--muted);
}
.opt[aria-pressed="true"] {
  background: #14304f;
  border-color: var(--accent);
}
.opt[aria-pressed="true"] .opt__key { background: var(--accent); color: #fff; }
.opt[disabled] { opacity: 0.55; cursor: default; }

/* The term carries the answer; the sentence underneath is what makes picking
   it comfortable. Four long sentences in a row are a wall — the term has to be
   readable at a glance, and the sentence read only if the term is not enough. */
.opt__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.opt__term { font-size: 18px; font-weight: 700; line-height: 1.25; }
.opt__note { font-size: 14px; line-height: 1.35; color: var(--muted); }
.opt[aria-pressed="true"] .opt__note { color: #cbd5e1; }

.ack {
  margin-top: auto;
  padding: 14px 16px;
  border-radius: var(--radius);
  background: #0f2a1a;
  border: 1px solid #1c5133;
  color: #86efac;
  font-weight: 600;
  text-align: center;
}

.wait {
  flex: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  text-align: center;
  padding: 24px 0;
}
.wait h1 { margin: 0; font-size: clamp(24px, 7vw, 34px); line-height: 1.25; }
.wait p { margin: 0; color: var(--muted); font-size: 17px; max-width: 30ch; line-height: 1.5; }

.pulse {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 4px solid var(--line);
  border-top-color: var(--accent);
  animation: spin 1.1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .pulse { animation-duration: 3s; } }

/* Connected, or stopped. Either way there is nothing left to wait for, so the
   ring stops turning and states the outcome instead. */
.pulse--ok,
.pulse--stop {
  animation: none;
  display: grid;
  place-items: center;
  border-color: transparent;
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
}
.pulse--ok { background: var(--green); color: #04140a; }
.pulse--stop { background: #7f1d1d; color: #fee2e2; }
@media (prefers-reduced-motion: no-preference) {
  .pulse--ok, .pulse--stop { animation: mark-in 0.32s cubic-bezier(0.2, 0.8, 0.3, 1); }
}
@keyframes mark-in { from { transform: scale(0.5); opacity: 0; } to { transform: none; opacity: 1; } }

/* the reveal: the whole phone becomes one colour, readable across a room */
.result {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
  padding: clamp(20px, 7vw, 48px);
  text-align: center;
  color: #fff;
  background: var(--band, #0b0f14);
  animation: flip 0.45s ease both;
}
@keyframes flip { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }

/* The reveal runs in two acts.
   Act one: for --flash-secs the phone is nothing but a pulsing block of colour
   with no text at all, so two hundred directors hold their phones up and read
   the room rather than their own screen.
   Act two: the colour settles and the personal result fades in.

   The phones receive the reveal within ~10 ms of each other, so they pulse in
   step. The rate is deliberately 2.5 Hz: flashing above 3 Hz can trigger
   photosensitive seizures (WCAG 2.3.1), which is not a risk worth taking in a
   room of two hundred people. It pulses toward a deeper shade rather than
   toward white so that when the text does arrive it stays readable. */
.result {
  --flash-cycle: 0.4s;   /* 2.5 Hz — do not shorten below 0.34s (3 Hz) */
  --flash-count: 12;     /* 12 x 0.4s = 4.8s of colour before any text */
  --flash-start: 0.35s;
  --text-at: 5.15s;      /* --flash-start + --flash-count x --flash-cycle */
}

@keyframes bandFlash {
  0%, 100% { background-color: var(--band); }
  50% { background-color: var(--band-alt); }
}
@keyframes textIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

.result--flash {
  animation:
    flip 0.45s ease both,
    bandFlash var(--flash-cycle) ease-in-out var(--flash-start) var(--flash-count);
}
/* `both` holds the from-state through the delay, so the text is genuinely
   absent — not merely transparent-but-laid-out — for the whole colour phase. */
.result--flash .result__body {
  animation: textIn 0.8s ease var(--text-at) both;
}

/* Reduced motion: no pulsing. The wait stays.
   Dropping the wait as well handed these phones their score five seconds
   before the rest of the room got theirs, which is the one thing the reveal
   exists to prevent: two hundred people look at their own colour in the same
   second, or the moment does not happen at all. This is not a rare path
   either, since Android turns the setting on by itself in battery saver.
   So the colour is solid from the first frame, and the result still arrives
   on the beat, fading with no movement in it. A phone that missed the reveal
   and loads straight into its result has no --flash class and still shows
   everything at once, which is right: five seconds of blank colour on a page
   that just opened reads as broken. */
@media (prefers-reduced-motion: reduce) {
  .result,
  .result--flash {
    animation: none;
  }
  .result--flash .result__body {
    animation: textFade 0.4s ease var(--text-at) both;
  }
}
@keyframes textFade {
  from { opacity: 0; }
  to { opacity: 1; }
}
.result__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.result__label { font-size: clamp(44px, 14vw, 84px); font-weight: 800; line-height: 1; margin: 0; }
.result__score { font-size: clamp(20px, 6vw, 30px); font-weight: 700; opacity: 0.92; margin: 0; font-variant-numeric: tabular-nums; }
/* The letter, after the number. Big enough to be the thing people say to
   each other afterwards, which is the point of grading it at all. */
.result__grade {
  margin: 0;
  font-size: clamp(30px, 9vw, 52px);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.01em;
}

.result__advice { font-size: clamp(15px, 4.4vw, 19px); line-height: 1.5; opacity: 0.94; margin: 0 auto; max-width: 34ch; }

/* The verdict fills the first screen on its own — that is the phone held up in
   the air — and the personal advice starts below the fold. So the card
   scrolls, and the hero is sized to exactly one screen of it. */
.result { justify-content: flex-start; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.result__body { gap: 28px; }
.result__hero {
  min-height: calc(100dvh - 2 * clamp(20px, 7vw, 48px));
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 16px;
}
.result__more { font-size: 15px; font-weight: 600; opacity: 0.85; }

.review { display: grid; gap: 14px; text-align: left; padding-bottom: 12px; }
.review__h { margin: 0; font-size: 22px; line-height: 1.25; }
.review__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

/* Dark glass on the band colour rather than panels of their own colour: a
   yellow "improve" card on a red phone is illegible, and the band is the
   colour this screen is about. The level lives in the badge. */
.rv {
  background: rgba(0, 0, 0, 0.28);
  border-radius: var(--radius);
  padding: 14px 16px;
  display: grid;
  gap: 6px;
}
.rv__badge {
  justify-self: start;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.02em;
  padding: 3px 10px;
  border-radius: 999px;
  background: #fff;
  color: #0b0f14;
}
.rv--low .rv__badge, .rv--none .rv__badge { background: #fee2e2; color: #7f1d1d; }
.rv--mid .rv__badge { background: #fef3c7; color: #78350f; }
.rv--best .rv__badge { background: #dcfce7; color: #14532d; }
.rv__topic { margin: 2px 0 0; font-size: 18px; line-height: 1.3; }
.rv__mine { margin: 0; font-size: 14px; opacity: 0.8; }
.rv__tip { margin: 0; font-size: 16px; line-height: 1.45; }
.rv__more summary { cursor: pointer; font-weight: 700; font-size: 15px; padding: 4px 0; text-decoration: underline; text-underline-offset: 3px; }
.rv__more ol { margin: 6px 0 0; padding-left: 1.3em; display: grid; gap: 6px; font-size: 15px; line-height: 1.45; }

.mail { display: grid; gap: 12px; margin-top: 6px; }
.mail__open, .mail__send {
  min-height: 56px;
  border: 0;
  border-radius: var(--radius);
  background: #fff;
  color: #0b0f14;
  font-size: 17px;
  font-weight: 800;
}
.mail__form { display: grid; gap: 10px; }
.mail__form label { font-weight: 700; font-size: 15px; }
.mail__form input {
  min-height: 52px;
  padding: 0 14px;
  border-radius: 12px;
  border: 2px solid rgba(255, 255, 255, 0.6);
  background: rgba(0, 0, 0, 0.3);
  color: #fff;
  font: inherit;
  font-size: 17px; /* 16px or more, or iOS zooms the page on focus */
}
.mail__form input::placeholder { color: rgba(255, 255, 255, 0.55); }
.mail__copy {
  min-height: 44px;
  border: 2px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--radius);
  background: transparent;
  font-size: 15px;
  font-weight: 700;
}
.mail__err { margin: 0; font-weight: 700; background: rgba(0, 0, 0, 0.35); padding: 8px 12px; border-radius: 10px; }
.mail__note { margin: 0; font-size: 13px; line-height: 1.5; opacity: 0.85; }
.result__raise {
  margin-top: 8px;
  font-size: clamp(15px, 4.4vw, 19px);
  font-weight: 700;
  padding: 12px 16px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.22);
  align-self: center;
}

/* --- stage (projector) ---------------------------------------------------- */

.stage {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  padding: clamp(20px, 3vw, 48px);
  gap: clamp(16px, 2.5vh, 32px);
}
.stage__head {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.6vw, 28px);
  color: var(--muted);
  font-size: clamp(16px, 1.6vw, 24px);
}
.stage__title { color: var(--ink); font-weight: 700; }
.stage__head > #step { margin-left: auto; white-space: nowrap; }
/* The mark is blue on transparent, which is nearly invisible on this ground.
   A white plate keeps the brand colours exactly as issued rather than
   recolouring someone else's logo to suit the theme. */
.stage__brand {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  background: #fff;
  border-radius: 8px;
  padding: 5px 9px;
}
.stage__brand img { display: block; height: clamp(20px, 2.4vh, 34px); width: auto; }
.stage__body { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: clamp(16px, 3vh, 40px); }

.join { display: grid; grid-template-columns: auto 1fr; gap: clamp(24px, 4vw, 64px); align-items: center; }
.join__qr {
  background: #fff;
  padding: clamp(12px, 1.5vw, 24px);
  border-radius: 20px;
  width: clamp(260px, 34vw, 520px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}
.join__qr img { width: 100%; height: 100%; display: block; }
/* The platform's name, and the joke it is built on: the room knows Kahoot,
   and the point of the next fifteen minutes is the two letters in front of
   it. Struck rather than replaced, because the lobby holds for minutes while
   people arrive and a name that swapped itself out early would leave most of
   them looking at a punchline with no setup. */
.brand {
  margin: 0 0 0.15em;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.3em;
  font-size: clamp(34px, 5vw, 84px);
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.brand__was {
  position: relative;
  color: var(--muted);
  font-weight: 400;
}
/* Drawn, not typeset: line-through cannot be animated, a pseudo-element can,
   and the pen stroke is the beat the whole thing turns on. */
.brand__was::after {
  content: "";
  position: absolute;
  left: -0.04em;
  right: -0.04em;
  top: 0.55em;
  height: 0.07em;
  border-radius: 999px;
  background: var(--red);
  /* Drawn by default so reduced motion still gets a struck-out name; the
     animation is what starts it at nothing. */
  transform: scaleX(1);
  transform-origin: left center;
}
.brand__is { color: var(--accent); }

/* A 15 second cycle that spends nine of them holding the finished line, so a
   director who looks up at any point in a two minute lobby sees a name rather
   than a transition. */
@media (prefers-reduced-motion: no-preference) {
  .brand__was { animation: brand-was 15s ease-in-out infinite; }
  .brand__was::after { animation: brand-strike 15s ease-in-out infinite; }
  .brand__is { animation: brand-is 15s ease-in-out infinite; }
}

@keyframes brand-was {
  0%, 2% { opacity: 0; }
  6%, 92% { opacity: 1; }
  98%, 100% { opacity: 0; }
}
@keyframes brand-strike {
  0%, 14% { transform: scaleX(0); }
  22%, 100% { transform: scaleX(1); }
}
@keyframes brand-is {
  0%, 24% { opacity: 0; transform: translateX(-0.25em); }
  34%, 92% { opacity: 1; transform: none; }
  98%, 100% { opacity: 0; transform: translateX(-0.25em); }
}

/* Demoted to a caption: the name is the headline now, and this is the one
   instruction under it. */
.join__lead {
  margin: 0 0 0.5em;
  font-size: clamp(18px, 1.9vw, 32px);
  line-height: 1.25;
  color: var(--ink);
}
.join__url {
  display: inline-block;
  font-size: clamp(22px, 2.8vw, 44px);
  font-weight: 700;
  color: #7dd3fc;
  background: var(--panel);
  border: 2px solid var(--line);
  border-radius: 14px;
  padding: 0.35em 0.6em;
  word-break: break-all;
}
/* Two things people get wrong on the way in. A third-party QR app opens the
   link in its own in-app browser, where the socket and localStorage behave
   differently — and in this room, of all rooms, "use the camera" is the lesson
   as much as the instruction. */
.join__tips {
  margin: 0.75em 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5em;
  font-size: clamp(14px, 1.35vw, 22px);
  line-height: 1.3;
  color: var(--muted);
}
/* The tick sits in its own column so a tip that wraps lines up under itself
   rather than under the tick. */
.join__tips li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.55em;
  align-items: baseline;
}
.join__tips li::before { content: "✓"; grid-column: 1; color: var(--accent); font-weight: 700; }
.join__tips b { grid-column: 2; font-weight: 400; }

.join__count { margin-top: 0.8em; font-size: clamp(20px, 2.4vw, 38px); color: var(--muted); }
.join__count b { color: var(--ink); font-size: 1.5em; font-variant-numeric: tabular-nums; }

.qtext { margin: 0; font-size: clamp(26px, 3.5vw, 56px); line-height: 1.18; font-weight: 800; }
/* Room to breathe between the question and the answers to it. In em, so it
   tracks the question size, and only when the guide is closed — that mode is
   already fighting for vertical space. */
#question:not(.showing-guide) .qtext { margin-bottom: 0.5em; }
.qopts { display: grid; gap: clamp(8px, 1.2vh, 16px); margin: 0; padding: 0; list-style: none; }
.qopts li {
  display: flex;
  gap: 0.7em;
  align-items: baseline;
  font-size: clamp(16px, 1.7vw, 28px);
  line-height: 1.3;
  color: var(--muted);
  background: var(--panel);
  border-radius: 12px;
  padding: 0.5em 0.7em;
}
.qopts b { color: var(--ink); }
.qopt__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.qopt__term { color: var(--ink); font-weight: 700; line-height: 1.2; }
.qopt__note { font-size: 0.72em; line-height: 1.3; color: var(--muted); }

/* The options own the width until the guide is asked for, then they share it.
   Shrinking the options rather than overlaying the guide keeps both readable:
   people are still choosing while the presenter is explaining where to look. */
.qmain { display: grid; grid-template-columns: 1fr; gap: clamp(16px, 2.5vw, 40px); min-height: 0; }
#question.showing-guide { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: clamp(10px, 1.8vh, 26px); }
#question.showing-guide .qmain { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); flex: 1; min-height: 0; }
/* A guide with a picture takes the whole screen, options and all.
   Every participant already has the options in their hand — the projector is
   duplicating them — and a walkthrough of six phone screens squeezed into a
   third of a wall is unreadable, which defeats the only reason to show it.
   The options come back the moment the guide closes. */
#question.showing-guide.guide-img { gap: clamp(6px, 1vh, 14px); }
#question.showing-guide.guide-img .qmain { grid-template-columns: minmax(0, 1fr); }
/* No frame once the panel holds nothing but the picture. A composed
   walkthrough is about 3:1 where the panel is about 2.6:1, so fitted to the
   width it can never fill the height, and a bordered box with a third of it
   empty reads from the back of the room as something that failed to load.
   Without the frame the leftover is simply the wall, and the picture keeps a
   stretched parent to size itself against. */
#question.showing-guide.guide-img .guide {
  background: none;
  border-color: transparent;
  padding: 0;
}
#question.showing-guide.guide-img .guide__body { justify-content: center; }
/* Beside, not above: on a 16:9 screen the panel is short and wide, so the step
   text costs the picture its scarcest dimension if it sits on top of it. */
#question.showing-guide.guide-img .guide__body { flex-direction: row; align-items: stretch; }
#question.showing-guide.guide-img .guide__img { flex: 1 1 0; min-width: 0; width: auto; height: 100%; max-width: none; }
/* The options, the counter and the progress bar all go: the options are in
   every participant's hand already, and the counter is on the admin panel the
   presenter is holding. Height is what a dense walkthrough is starved of on a
   16:9 screen, so everything that is duplicated elsewhere gives up its rows. */
#question.showing-guide.guide-img .qopts,
#question.showing-guide.guide-img .counter,
#question.showing-guide.guide-img .bar { display: none; }
/* One line of the question, to say which one this is — the full text is on
   the phones and in the header count. */
#question.showing-guide.guide-img .qtext {
  font-size: clamp(15px, 1.6vw, 26px);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* The counter and the question are already known to the room by the time the
   guide goes up; the picture is the thing that has to be legible. */
#question.showing-guide .counter { font-size: clamp(15px, 1.5vw, 26px); }
#question.showing-guide.guide-wide .qtext { font-size: clamp(20px, 2.4vw, 38px); }
/* Auto rows stretch to fill a grid by default, which turns four options into
   four tall slabs the moment the guide takes half the width. */
#question.showing-guide .qopts { align-content: start; min-height: 0; overflow-y: auto; }
#question.showing-guide .guide { min-height: 0; }
#question.showing-guide .qtext { font-size: clamp(24px, 3.2vw, 48px); }
#question.showing-guide .qopts li { font-size: clamp(15px, 1.5vw, 25px); padding: 0.4em 0.6em; }

.guide {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 1.2vh, 14px);
  min-width: 0;
  padding: clamp(14px, 1.6vw, 24px);
  border-radius: 18px;
  background: var(--panel);
  border: 2px solid var(--line);
}
.guide h2 { margin: 0; font-size: clamp(16px, 1.5vw, 26px); color: var(--accent); }
.guide__title { display: flex; align-items: center; gap: 0.7em; flex-wrap: wrap; }

/* Platform colours, so the badge naming the screen on show is the colour of
   the phone it belongs to. */
.guide__badge { --os: var(--accent); }
[data-os="iphone"] { --os: #e2e8f0; }
[data-os="android"] { --os: #3ddc84; }
[data-os="windows"] { --os: #38bdf8; }

.guide__badge {
  font-size: 0.92em;
  font-weight: 800;
  letter-spacing: 0.02em;
  border-radius: 999px;
  padding: 0.18em 0.85em;
  background: var(--os);
  color: #0b0f14;
}

.guide__body { display: flex; gap: clamp(10px, 1.2vw, 20px); flex: 1; min-height: 0; align-items: stretch; }
/* Height-first: a phone screenshot is portrait, so let it take the panel's
   full height and take whatever width that implies. Nothing shares the panel
   with it any more, so the width it implies is whatever it needs. */
.guide__img {
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: top center;
  border-radius: 12px;
  align-self: stretch;
}
/* Landscape: a composed walkthrough of several phones wants the panel's width,
   so it stacks and letterboxes into whatever height is left. */
.guide__body--wide { flex-direction: column; gap: 8px; }
/* max-height keeps a squarer walkthrough inside the panel it is drawn in:
   without it the picture keeps its own ratio and spills past the border. It
   letterboxes at the sides instead, which costs a little width and no edges. */
.guide__body--wide .guide__img {
  width: 100%;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
  flex: 1;
}

/* White plate: a QR needs the quiet zone and the contrast the spec assumes,
   and this page's ground is the wrong way round for it. */
.guide__qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  padding: 8px 8px 6px;
  border-radius: 12px;
  background: #fff;
}
/* Sized to be scanned from a seat, not just from the front row. It shares the
   panel with the screenshot now, so the panel's height is what caps it. */
.guide__qr img { display: block; width: clamp(140px, 24vh, 300px); height: auto; }
.guide__qr span {
  font-size: clamp(10px, 0.85vw, 14px);
  font-weight: 600;
  color: #0b0f14;
  font-family: "Cascadia Mono", Consolas, monospace;
}

.counter {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  font-size: clamp(22px, 2.4vw, 40px);
  color: var(--muted);
}
.counter b { color: var(--ink); font-size: 1.6em; font-variant-numeric: tabular-nums; }
.bar { height: 14px; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--accent); transition: width 0.3s ease; }

/* The dot grid: one dot per director. Both the column count and the dot size
   are computed in JS to fit the box exactly (see layoutDots), because the count
   swings from a five-person rehearsal to two hundred directors and neither
   auto-fit columns nor a fixed size looks right across that range. */
#results { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: clamp(16px, 3vh, 40px); }
#results.showing-breakdown .dots { display: none; }

.dots {
  --dot: 24px;
  display: grid;
  grid-template-columns: repeat(var(--cols, 20), var(--dot));
  gap: var(--dot-gap, 6px);
  align-content: center;
  justify-content: center;
  flex: 1;
  min-height: 0;
  width: 100%;
}
.dots i {
  width: var(--dot);
  height: var(--dot);
  border-radius: 50%;
  opacity: 0;
  animation: pop 0.35s ease forwards;
}
@keyframes pop { from { opacity: 0; transform: scale(0.3); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: 1; } }

.tally { display: flex; gap: clamp(16px, 2vw, 40px); flex-wrap: wrap; }
.tally__item {
  flex: 1 1 240px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(12px, 1.4vw, 26px);
  padding: clamp(14px, 1.6vw, 28px);
  border-radius: 18px;
  background: var(--panel);
  border-left: 10px solid var(--line);
}
.tally__pct { font-size: clamp(40px, 6vw, 96px); font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.tally__side { min-width: 0; }
.tally__label { font-size: clamp(16px, 1.8vw, 30px); font-weight: 700; line-height: 1.1; }
.tally__count {
  margin-top: 0.35em;
  font-size: clamp(13px, 1.25vw, 21px);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The per-question matrix. One row per question, columns ranked safest-first,
   so the presenter reads the whole workshop off one screen without scrolling —
   a stack of cards did not fit once the workshop grew past five questions. The
   rows share the grid, so the columns line up across questions. */
.breakdown { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; overflow: hidden; }

/* During the discussion the tally is context, not the headline — the room has
   already seen it. Shrinking it is what buys the matrix enough height to show
   every question at once on a 720p projector. */
/* .stage is min-height:100dvh so the lobby can grow around a large QR. That
   is wrong for the matrix: the page would scroll and the presenter, who is not
   standing at the projector, would never know the bottom rows were cut off.
   Pin it to the viewport here and let the matrix scroll inside itself instead. */
body.breakdown-open .stage,
body.guide-open .stage { height: 100dvh; padding: clamp(14px, 2vw, 32px); gap: clamp(8px, 1.2vh, 16px); }
body.breakdown-open .stage__body,
body.guide-open .stage__body { min-height: 0; gap: clamp(8px, 1.2vh, 18px); justify-content: stretch; }
body.breakdown-open .btn--breakdown { min-height: 40px; padding: 8px 18px; }

#results.showing-breakdown { gap: clamp(8px, 1.4vh, 18px); }
#results.showing-breakdown .tally__item { flex-basis: 160px; padding: 8px 16px; border-left-width: 6px; }
#results.showing-breakdown .tally__pct { font-size: clamp(22px, 2.6vw, 40px); }
#results.showing-breakdown .tally__label { font-size: clamp(12px, 1.1vw, 18px); }
#results.showing-breakdown .tally__count { font-size: clamp(11px, 0.95vw, 15px); margin-top: 0.2em; }

.matrix {
  display: grid;
  grid-template-columns: minmax(0, 2.6fr) repeat(var(--cols, 4), minmax(0, 1fr)) auto;
  gap: clamp(4px, 0.6vh, 8px);
  align-content: start;
  flex: 1;
  min-height: 0;
  /* Last resort only: the matrix scrolls inside itself so the projected page
     never does — a cut-off bottom row is invisible to whoever is presenting. */
  overflow-y: auto;
}
.matrix__head {
  color: var(--muted);
  font-size: clamp(11px, 0.95vw, 16px);
  padding: 0 4px 2px;
  align-self: end;
}
.matrix__head--col { text-align: center; }

.matrix__q {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 6px 12px;
  border-radius: 10px;
  background: var(--panel);
  font-size: clamp(12px, 1vw, 18px);
  line-height: 1.25;
}
.matrix__q > b {
  width: 1.8em;
  height: 1.8em;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--panel-2);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Two lines is enough to recognise a question you just read aloud. */
.matrix__q > span {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Each cell is two-sided: how many chose it on the front, what it actually was
   on the back. The matrix can only show one at a time — the counts are what the
   room reads first, the keyword is what the presenter turns to when explaining
   the number — so it turns over rather than trying to fit both. */
.matrix__cell {
  position: relative;
  min-height: 2.9em;
  perspective: 900px;
  cursor: pointer;
}
.matrix__card {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.3, 1);
  transition-delay: var(--flip-delay, 0ms);
}
.breakdown.flipped .matrix__card,
.matrix__cell.flipped .matrix__card { transform: rotateY(180deg); }
/* A single cell reads as a toggle against whichever way the sweep left it, so
   one card can be turned back for a number while the rest show keywords. */
.breakdown.flipped .matrix__cell.flipped .matrix__card { transform: none; }
/* A cell turned on its own is deliberate; it should not wait for the sweep. */
.matrix__cell.flipped .matrix__card { transition-delay: 0ms; }

.matrix__face {
  position: absolute;
  inset: 0;
  display: grid;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 10px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.matrix__face--num { grid-template-columns: auto minmax(0, 1fr) auto; }
.matrix__face--key { grid-template-columns: auto minmax(0, 1fr); transform: rotateY(180deg); }

.matrix__face > u { text-decoration: none; opacity: 0.6; font-size: clamp(10px, 0.85vw, 15px); }
.matrix__face > b { text-align: center; font-size: clamp(16px, 1.7vw, 30px); font-variant-numeric: tabular-nums; }
.matrix__face > i { font-style: normal; opacity: 0.75; font-size: clamp(10px, 0.85vw, 15px); }
.matrix__face--key > span {
  font-size: clamp(11px, 1vw, 17px);
  line-height: 1.2;
  font-weight: 600;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.matrix__cell--empty { cursor: default; }

/* Turning a hundred cards is decoration, not information — the keyword still
   arrives, it just arrives immediately. */
@media (prefers-reduced-motion: reduce) {
  .matrix__card { transition: none; transition-delay: 0ms; }
}

.matrix__score {
  display: grid;
  place-items: center;
  padding: 0 10px;
  color: var(--green);
  font-weight: 800;
  font-size: clamp(14px, 1.3vw, 24px);
  font-variant-numeric: tabular-nums;
}

.stage__actions { display: flex; align-items: center; justify-content: flex-end; gap: 14px; }
.btn--breakdown { min-height: 48px; padding: 10px 20px; font-size: clamp(14px, 1.2vw, 19px); }

.hint { color: var(--muted); font-size: clamp(13px, 1.1vw, 18px); text-align: right; }

/* A closed tab takes a director out of the room for the rest of the session,
   and the lobby QR is long gone by question three. It lives in the header's
   right-hand corner, and large: on the real wall the bottom edge is hidden
   behind the stage furniture, and phones kept losing their tab while their
   owners were off in Settings reading the guide. The header row grows to fit
   it, which the guide image absorbs — the stage is pinned to the viewport
   while a guide is up, so nothing is pushed off screen. */
.rejoin {
  display: flex;
  align-items: center;
  gap: 0.8em;
  color: var(--muted);
  font-size: clamp(13px, 1.25vw, 22px);
  line-height: 1.25;
  text-align: right;
  flex: none;
}
.rejoin__qr {
  background: #fff;
  border-radius: 10px;
  padding: 6px;
  width: clamp(96px, 9vw, 170px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  flex: none;
}
.rejoin__qr img { width: 100%; height: 100%; display: block; }
.rejoin b { display: block; color: var(--ink); }
.rejoin__t span { display: block; opacity: 0.75; }

/* --- admin ---------------------------------------------------------------- */

.admin { max-width: 780px; margin: 0 auto; padding: var(--pad); display: grid; gap: 20px; }
.admin h1 { margin: 0; font-size: 22px; }
.admin__meta { display: flex; gap: 20px; flex-wrap: wrap; color: var(--muted); font-size: 15px; }
.admin__meta b { color: var(--ink); font-size: 20px; font-variant-numeric: tabular-nums; }

.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; }
.card h2 { margin: 0 0 12px; font-size: 15px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }

.controls { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.btn {
  min-height: 64px;
  padding: 14px 16px;
  border-radius: var(--radius);
  border: 2px solid var(--line);
  background: var(--panel-2);
  font-size: 17px;
  font-weight: 700;
}
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
.btn--go { background: #14304f; border-color: var(--accent); }
.btn--reveal { background: #14532d; border-color: var(--green); grid-column: 1 / -1; min-height: 84px; font-size: 21px; }
.btn--danger { background: #3f1414; border-color: #7f1d1d; }
.btn--armed { background: #7f1d1d; border-color: #ef4444; animation: nudge 0.9s ease-in-out infinite; }
@keyframes nudge { 50% { transform: translateY(-2px); } }

.dist { display: grid; gap: 10px; }
.dist__row { display: grid; gap: 6px; }
.dist__top { display: flex; justify-content: space-between; gap: 12px; font-size: 15px; color: var(--muted); }
.dist__top b { color: var(--ink); font-variant-numeric: tabular-nums; }
.dist__meter { height: 10px; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
.dist__meter > span { display: block; height: 100%; background: var(--accent); transition: width 0.25s ease; }

.link { color: #7dd3fc; font-size: 15px; }
.note { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 0; }

/* The matrix was fitted to a 720p wall row by row; the header QR would cost it
   its last question. By the discussion everyone who is coming back has. */
body.breakdown-open .rejoin { display: none; }

/* Admin rename form. 16px+ inputs so a phone does not zoom in on focus. */
.rename { display: grid; gap: 8px; }
.rename label { font-size: 14px; color: var(--muted); font-weight: 600; }
.rename input {
  min-height: 48px;
  padding: 0 12px;
  border-radius: 10px;
  border: 2px solid var(--line);
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  font-size: 17px;
}
.rename input:focus { outline: none; border-color: var(--accent); }
.rename__row { display: flex; align-items: center; gap: 14px; margin-top: 6px; }
.rename__row .btn { min-height: 52px; padding: 10px 24px; }
