/* The Wing
   Dark, high contrast, large tap targets: played on phones, in a room, in the
   evening. Two accents only, and they mean two different things and never mix:
   --mine marks your own seat and your own private information (your signet
   card, your vote once cast, your play once made). --public marks the room
   code, the rejection counter, the squad, the leader, a revealed vote, a
   sortie's result: the things everyone at the table can see. No decorative
   motion on anything that carries information: a re-render just replaces
   content, nothing slides or fades.

   The dressing is iron and gold: a war college, not a palace. It is confined
   to headings, edges and empty space and never touches a name, a number or a
   vote. A dim room, mixed eyesight and cheap phones set the floor, and
   somebody squinting at a rider's name mid-argument is a worse outcome than
   a plain one. So the ornament is structural and the information is not
   decorated. */

/* Cinzel, an inscriptional Roman face, for titles only. Self-hosted rather
   than linked, so it does not depend on a third party being reachable, and
   subset to latin: one 26KB variable file covering every weight used.
   Licensed under the SIL Open Font License. */
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('fonts/cinzel-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --ink: #07080B;
  --panel: #15171E;
  --panel-line: rgba(214, 187, 122, 0.14);
  --paper: #EDEAE1;
  --paper-dim: rgba(237, 234, 225, 0.55);

  /* Gold is three colours, not one: leaf catches light along an edge and
     goes dark on the turn. Every gold border and rule in this sheet is a
     gradient across all three, which is what separates it from a yellow
     line. --gold on its own is the flat mid tone, for text. */
  --gold: #C9A44C;
  --gold-lit: #F4DFA0;
  --gold-deep: #6E5417;
  --gold-leaf: linear-gradient(145deg, var(--gold-deep), var(--gold-lit) 38%,
                               var(--gold) 55%, var(--gold-deep));

  --mine: #4FC9B4;
  --mine-dim: rgba(79, 201, 180, 0.16);
  --public: #E4B455;
  --public-dim: rgba(228, 180, 85, 0.14);
  --public-fill: rgba(228, 180, 85, 0.32);
  /* Opaque bases for panels whose edge is a gold or red gradient: a
     translucent fill over a border-box gradient lets the gradient through
     across the whole panel, not just its rim. */
  --fill-public: #241C0D;
  --fill-bad: #24100F;
  --fill-seat-marked: linear-gradient(180deg, #7A5A16, #46320B);
  /* The Wingleader, and only the Wingleader. Green because it has to read as
     a different thing from the squad they propose, and one seat often
     carries both: a Wingleader may put themselves on their own squad. */
  --leader: #6ED08A;
  --leader-dim: rgba(110, 208, 138, 0.20);
  --bad: #E5565B;
  --bad-dim: rgba(229, 86, 91, 0.15);

  --display: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
             "Helvetica Neue", Arial, sans-serif;
  /* Titles only. The fallback is a serif rather than the UI sans, so a
     failed font load still reads as a different voice from the body. */
  --title: 'Cinzel', "Iowan Old Style", "Palatino Linotype", Palatino,
           Georgia, "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  --pad: clamp(1.1rem, 5vw, 2rem);
  --gap: clamp(0.75rem, 3vw, 1.25rem);
}

* { box-sizing: border-box; }

/* An author display rule can out-rank the user-agent [hidden] rule at equal
   specificity, which would leave a hidden element on screen. Several rules here
   set display on classes that are also toggled with .hidden, including the
   host-only start control, so settle it once for the whole sheet. */
[hidden] { display: none !important; }

html, body { margin: 0; height: 100%; }

/* Two washes and a grain, all painted rather than loaded: a warm ember low
   and left, a cold one high and right, and a fixed SVG noise tile over the
   top at 3% so the black stops looking like a switched-off screen. Fixed,
   not scrolling, so it reads as the surface the game sits on. */
body {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--display);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 80% at 12% 100%, rgba(201, 164, 76, 0.10), transparent 60%),
    radial-gradient(100% 70% at 88% 0%, rgba(96, 132, 168, 0.09), transparent 62%),
    var(--ink);
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

.wrap {
  width: 100%;
  max-width: 30rem;
  margin: 0 auto;
  padding: calc(var(--pad) + env(safe-area-inset-top)) var(--pad)
           calc(var(--pad) + env(safe-area-inset-bottom));
  flex: 1;
}

.wrap-wide { max-width: 36rem; }

/* ---------- text ---------- */

.eyebrow {
  margin: 0 0 0.4rem;
  text-align: center;
  font-family: var(--title);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.eyebrow-public { color: var(--public); }

/* The wordmark is struck in gold rather than printed in it: the leaf
   gradient is clipped to the glyphs, so the light runs across the letters
   the way it would across a real inscription. */
.wordmark {
  margin: 0.2rem 0 0.4rem;
  text-align: center;
  font-family: var(--title);
  font-size: clamp(2rem, 9.5vw, 2.9rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--gold-lit);
}

/* A hairline rule with a lozenge on it, for under a title. Applied by the
   ::after of whatever it sits beneath, never as an element of its own. */
.wordmark::after {
  content: "";
  display: block;
  width: min(11rem, 60%);
  height: 1px;
  margin: 0.7rem auto 0;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

.wordmark-small {
  margin: 0.1rem 0 1rem;
  text-align: center;
  font-family: var(--title);
  font-size: clamp(1.4rem, 6.5vw, 1.85rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--paper);
}

.hint {
  margin: 0.55rem 0 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--paper-dim);
  text-align: center;
}

/* ---------- the hero ----------
   The banner on the start screen, and the only painted image in the whole
   game. Full bleed past the wrap's padding, because it is faded to nothing
   on all four edges and a margin would only reintroduce the rectangle the
   fade exists to remove. The wordmark is pulled up into the bottom of that
   fade so the two read as one thing. */

.hero {
  margin: calc(-1 * var(--pad)) calc(-1 * var(--pad)) -2.6rem;
}

.hero img {
  display: block;
  width: 100%;
  height: auto;
}

/* The same idea on the screens that carry a painting rather than being one:
   full bleed past the wrap padding, and the content below pulled up into the
   bottom of the fade so the two are not two things stacked. Shorter than the
   hero, because these sit above a screen somebody has to use. */
.screen-art {
  margin: calc(-0.6 * var(--pad)) calc(-1 * var(--pad)) -1.6rem;
}

.screen-art img {
  display: block;
  width: 100%;
  height: auto;
}

.screen-art img[hidden] { display: none; }

/* ---------- sigils and ornaments ----------
   Struck marks, drawn in currentColor so each one takes the colour of
   whatever it sits in. They are always redundant with words next to them:
   nothing on this interface is knowable only by recognising a picture. */

/* Each emblem is a painted file, used as a mask rather than as a picture:
   the shape comes from the file's alpha and the colour from currentColor, so
   one asset serves a gold loyal row and a red sealed one without needing two
   of everything. Where masks are not supported the file is shown directly
   instead, which is why its pixels are flattened to the palette's gold. */
.sigil {
  display: block;
  width: 1.7rem;
  height: 1.7rem;
  flex-shrink: 0;
  background-image: var(--sigil);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}

.sigil-farsight  { --sigil: url('art/sigil-farsight.webp'); }
.sigil-resonance { --sigil: url('art/sigil-resonance.webp'); }
.sigil-rider     { --sigil: url('art/sigil-rider.webp'); }
.sigil-blade     { --sigil: url('art/sigil-blade.webp'); }
.sigil-echo      { --sigil: url('art/sigil-echo.webp'); }
.sigil-unseen    { --sigil: url('art/sigil-unseen.webp'); }
.sigil-sworn     { --sigil: url('art/sigil-sworn.webp'); }

@supports ((-webkit-mask-image: url('#m')) or (mask-image: url('#m'))) {
  .sigil {
    background: currentColor;
    -webkit-mask-image: var(--sigil);
    mask-image: var(--sigil);
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
  }
}

.ornament-row {
  display: flex;
  justify-content: center;
  margin: 0.2rem 0 0.9rem;
  color: var(--gold);
}

.ornament-row-tight { margin: 0.1rem 0 0.5rem; }

/* Painted, and masked the same way an emblem is, so it takes its colour from
   the row it sits in rather than carrying one of its own. Sized by width
   with the aspect ratio declared, so it holds its space before the file
   arrives. */
.ornament {
  display: block;
  width: min(13rem, 62%);
  background: currentColor;
  -webkit-mask-image: var(--ornament);
  mask-image: var(--ornament);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  opacity: 0.9;
}

.ornament-wings {
  --ornament: url('art/ornament-wings.webp');
  aspect-ratio: 600 / 183;
}

.ornament-blades {
  --ornament: url('art/ornament-blades.webp');
  aspect-ratio: 600 / 230;
}

/* Where masks are unsupported the file is shown instead, which is why its
   pixels are flattened to the palette's gold. */
@supports not ((-webkit-mask-image: url('#m')) or (mask-image: url('#m'))) {
  .ornament {
    background: var(--ornament) center/contain no-repeat;
  }
}

/* ---------- the squad being voted on ----------
   The one thing a rider must have read before they vote. Given the weight
   of a headline and the --public colour every piece of public record on
   this interface carries, rather than the dim hint line it used to be. */

.squad-callout {
  position: relative;
  margin: 0.6rem 0 0.5rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid transparent;
  border-radius: 12px;
  background:
    linear-gradient(var(--fill-public), var(--fill-public)) padding-box,
    var(--gold-leaf) border-box;
  text-align: center;
}

.squad-callout-label {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--public);
}

.squad-callout-names {
  margin: 0.3rem 0 0;
  font-family: var(--title);
  font-size: clamp(1.1rem, 5.2vw, 1.42rem);
  font-weight: 700;
  line-height: 1.3;
  color: var(--paper);
}

.label {
  display: block;
  margin-bottom: 0.55rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.label-public { color: var(--public); }

.field { margin-bottom: 1.6rem; }

/* ---------- inputs ---------- */

input[type="text"], input[type="number"] {
  width: 100%;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  border: 2px solid var(--panel-line);
  background: var(--panel);
  color: var(--paper);
  font-family: var(--display);
  font-size: 1.05rem;
}

input:focus { outline: none; border-color: var(--public); }

.join-row { display: flex; gap: 0.6rem; }
.join-row input { flex: 1; }
.join-row .action { width: auto; margin-top: 0; padding-left: 1.5rem; padding-right: 1.5rem; }

#join-code, #lobby-code, .room-code {
  font-family: var(--mono);
  text-transform: uppercase;
}

#join-code {
  letter-spacing: 0.3em;
  text-align: center;
  font-size: 1.3rem;
}

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

.action {
  display: block;
  width: 100%;
  min-height: 3.1rem;
  margin-top: 0.5rem;
  padding: 0.9rem 1rem;
  border: none;
  border-radius: 10px;
  background: var(--gold-leaf);
  color: #16110A;
  font-family: var(--title);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), 0 3px 10px rgba(0, 0, 0, 0.45);
  cursor: pointer;
}

.action:disabled { opacity: 0.4; cursor: not-allowed; }

.action-secondary {
  background: transparent;
  border: 1px solid var(--gold);
  color: var(--gold-lit);
  box-shadow: none;
}

.action-quiet {
  margin-top: 1.4rem;
  background: transparent;
  color: var(--paper-dim);
  font-weight: 500;
  /* Clears the primary button's lit edge and drop shadow, or an escape
     hatch reads as a card. */
  box-shadow: none;
}

/* ---------- status and error ---------- */

.status-pill {
  display: inline-block;
  margin-bottom: 1.1rem;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  background: var(--bad-dim);
  color: var(--bad);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.error {
  margin-top: 1.1rem;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: var(--bad-dim);
  color: var(--bad);
  font-size: 0.9rem;
  line-height: 1.4;
}

/* ---------- the toast ----------
   The one surface every server refusal lands on, in every phase: fixed above
   every .screen section (which are siblings, not ancestors, of this), so
   switching screens can never hide a message that arrived a moment ago. */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(1.1rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: calc(100% - 2 * var(--pad));
  max-width: 26rem;
  padding: 0.85rem 0.9rem 0.85rem 1.1rem;
  border-radius: 12px;
  border: 2px solid var(--bad);
  background: var(--panel);
  color: var(--bad);
  font-size: 0.88rem;
  line-height: 1.4;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}

.toast-text { flex: 1; }

.toast-dismiss {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--bad);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

/* ---------- the room code ---------- */

/* Stays monospaced. It is dictated across a room and transcribed by seven
   people, so unambiguous letterforms beat a handsome face; the gold is
   carried by the leaf gradient instead. */
.room-code {
  margin: 0.1rem 0 0.2rem;
  text-align: center;
  font-size: clamp(2.8rem, 16vw, 4.2rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--gold-lit);
}

/* Struck rather than printed: the leaf gradient clipped to the glyphs, so
   the light runs across the letters the way it would across an inscription.
   Guarded, because the unsupported fallback for background-clip: text is
   transparent text, and an invisible room code would stop the game before
   it started. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .wordmark, .room-code {
    background: var(--gold-leaf);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* ---------- chips (settings with a few fixed choices) ---------- */

.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.chip {
  flex: 1;
  min-width: 3.4rem;
  min-height: 2.75rem;
  padding: 0.6rem 0.4rem;
  border: 2px solid var(--panel-line);
  border-radius: 8px;
  background: transparent;
  color: var(--paper);
  font-family: var(--mono);
  font-size: 1rem;
  cursor: pointer;
}

.chip[aria-pressed="true"] {
  background: var(--public);
  border-color: var(--public);
  color: var(--ink);
  font-weight: 700;
}

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--panel-line);
}

.setting-row:last-child { border-bottom: none; }

.setting-label { font-size: 0.92rem; }

/* ---------- the formation ring ----------
   Seat i sits at angle (360 / n) * i - 90 degrees, so seat 1 is at the top.
   Each seat is placed by rotating around the ring's centre and translating
   outward; its label carries the opposite rotation so names stay upright. */

/* The box has to hold the whole ring, not just the circle its seats sit on.
   A seat is centred on that circle, so it sticks out past it by half its own
   width in every direction; the box was sized to the circle alone, so the
   bottom seats hung out below it and whatever came next was drawn over them.
   Both dimensions are now derived from the same two numbers the seats
   themselves use, so they cannot drift apart again. */
.ring {
  --ring-radius: min(32vw, 158px);
  --seat-size: clamp(3.7rem, 15.5vw, 4.8rem);
  /* The filigree rim rides outside the seat so the rider's name keeps the
     whole face to itself. That makes the seat's true footprint this, not
     --seat-size, and it is what both the box below and the spacing at ten
     riders have to be measured against: adjacent seat centres sit
     2 * radius * sin(180/n) apart, which at ten riders is comfortably wider
     than this. */
  --seat-frame: calc(var(--seat-size) * 1.16);
  position: relative;
  width: calc(2 * var(--ring-radius) + var(--seat-frame) + 0.7rem);
  height: calc(2 * var(--ring-radius) + var(--seat-frame) + 0.7rem);
  max-width: 100%;
  margin: 1.4rem auto 0.4rem;
}

.ring-compact { margin-top: 0.6rem; margin-bottom: 0.2rem; }

.seat-btn {
  /* --seat-size and --ring-radius are inherited from .ring, which sizes its
     own box from them. */
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--seat-size);
  height: var(--seat-size);
  margin: calc(var(--seat-size) / -2) 0 0 calc(var(--seat-size) / -2);
  transform: rotate(var(--angle)) translate(0, calc(-1 * var(--ring-radius)));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.12rem;
  border-radius: 999px;
  /* Just a dark disc. The struck rim below is the only ring a seat gets: the
     button used to carry its own gold border as well, and between that, the
     face edge, an inset highlight and the rim's own three circles a seat was
     five concentric rings deep and nothing could be read off it. */
  border: none;
  background: linear-gradient(180deg, #1B1E26, #0F1117);
  box-shadow: 0 2px 7px rgba(0, 0, 0, 0.55);
  color: var(--paper);
  font-family: var(--display);
  cursor: default;
}

/* The struck rim. Masked rather than drawn, so it takes --seat-rim and turns
   with the seat's state: gold ordinarily, green for the Wingleader, and the
   colour of a vote once one has been revealed. */
.seat-btn::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--seat-frame);
  height: var(--seat-frame);
  margin: calc(var(--seat-frame) / -2) 0 0 calc(var(--seat-frame) / -2);
  background: var(--seat-rim, var(--gold));
  /* Versioned because public/_headers gives everything under /art/ a
     day-long cache. Without the query, a phone that already holds the old
     frame keeps showing it for 24 hours no matter what this stylesheet
     says. Bump it whenever the art changes; the sigils and ornaments below
     are deliberately left alone so they are not refetched for nothing. */
  -webkit-mask: url('art/seat-frame.webp?v=3') center / contain no-repeat;
  mask: url('art/seat-frame.webp?v=3') center / contain no-repeat;
  pointer-events: none;
}

.seat-btn.seat-empty {
  border: 2px dashed var(--panel-line);
  color: var(--paper-dim);
  background: transparent;
  box-shadow: none;
  cursor: pointer;
}

/* An empty seat is an invitation, not a rider: no rim to strike yet. */
.seat-btn.seat-empty::after { display: none; }

/* Rim colour, weakest claim first: whichever of these applies last wins, so
   on the tally a revealed vote beats everything, and while a squad is being
   voted on the Wingleader still reads green over the squad's amber. */
.seat-btn.seat-marked { --seat-rim: var(--public); }
.seat-btn.seat-mine { --seat-rim: var(--mine); cursor: pointer; }
.seat-btn.seat-turn { --seat-rim: var(--leader); }
.seat-btn.seat-voted-fly { --seat-rim: var(--leader); }
.seat-btn.seat-voted-standdown { --seat-rim: var(--bad); }
.seat-btn.seat-disconnected::after { opacity: 0.5; }

.seat-btn.seat-disconnected { opacity: 0.45; }

.seat-label {
  display: block;
  max-width: 100%;
  overflow-wrap: anywhere;
  text-align: center;
  /* Sized so an eight-letter name sits on one line inside the medallion:
     "Rhiannon" broke to "Rhiann / on" at the old size, which is exactly the
     rider whose name people need to read fastest. */
  font-size: 0.7rem;
  line-height: 1.12;
  letter-spacing: -0.01em;
  transform: rotate(calc(-1 * var(--angle)));
}

/* A seat on the squad currently being voted on. Filled, so it reads at a
   glance as "these ones are going". */
/* On the squad. This has to survive the rim being claimed by the Wingleader's
   green or by a revealed vote, so the face carries it too and carries it
   brightly: at arm's length across a table, a filled seat and an empty one is
   the difference people actually see. */
.seat-btn.seat-marked {
  background: var(--fill-seat-marked);
  color: #FFF6E2;
}

/* The Wingleader is public: everyone at the table can see the ring.
   Outlined in green, and declared after .seat-marked on purpose, so that a
   Wingleader who has put themselves on their own squad comes out green
   ringed and filled yellow: leader and passenger at once, which is exactly
   what they are and exactly the thing worth noticing. */
.seat-btn.seat-turn {
  border-color: var(--leader);
  box-shadow: 0 0 0 3px var(--leader-dim);
}

/* How a rider voted, pinned to their seat, on the tally screen only. Never
   drawn while a vote is open: public/screens.js has no path that could,
   because the room does not send a live vote to anybody. */
.seat-vote {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  margin: -0.675rem 0 0 -0.675rem;
  border-radius: 999px;
  border: 1px solid var(--ink);
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
  /* Pinned to the same visual corner of every seat, which takes both parts
     of this transform and in this order. The seat button carries the ring's
     rotation, so rotating back by the same angle first puts this badge's own
     axes back in line with the screen; only then does the translate move it
     up and to the right for everybody. Positioning it with top/right instead
     would offset it along the seat's rotated axes, and the badges would
     scatter around the ring. */
  transform: rotate(calc(-1 * var(--angle))) translate(1.35rem, -1.35rem);
}

.seat-vote-fly { background: var(--leader); color: #06210F; }
.seat-vote-standdown { background: var(--bad); color: #2A0A0B; }

.seat-btn.seat-voted-fly { border-color: var(--leader); }
.seat-btn.seat-voted-standdown { border-color: var(--bad); }

/* ---------- the signet card ----------
   Every rider's own signet, visible at all times a game is running and
   they hold a seat: the single most looked-at thing on the screen. Fixed
   near the top, above whichever .screen is showing, with its own --mine
   border so it never reads as public information. */

/* The card floats above every screen, so each screen must reserve exactly its
   height or the card sits on top of the content. The height changes when the
   card is collapsed and varies by signet, so screens.js measures it and writes
   --signet-h; this is the fallback for the moment before the first measure. */
.screen:not(#start) { padding-top: calc(var(--signet-h, 0px) + 0.6rem); }

/* Sized down deliberately. Every pixel of this card is a pixel the screen
   under it does not get, for the whole game, on a phone. So: one tight
   strip carrying the signet, the side, and what the signet sees, and
   nothing else. The full description of each signet is in the help
   overlay, which is where a rider who wants to re-read it goes. */
.signet-card {
  position: fixed;
  top: calc(0.5rem + env(safe-area-inset-top));
  left: calc(0.6rem + env(safe-area-inset-left));
  right: calc(4.1rem + env(safe-area-inset-right));
  z-index: 800;
  max-width: 22rem;
  padding: 0.5rem 0.75rem 0.6rem;
  border-radius: 10px;
  /* Sealed orders, in the literal sense: a dark plate with a gold rim, the
     same dressing as every other panel, rather than the plain teal box it
     was. The teal is kept for the signet's own name and for what it sees,
     which is the part that is private to this rider; the frame stays gold
     so a glance across the table gives away nothing about which side the
     card belongs to. */
  border: 1px solid transparent;
  background:
    linear-gradient(180deg, #14161C, #0B0C11) padding-box,
    var(--gold-leaf) border-box;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(244, 223, 160, 0.10);
}

.signet-card-head {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  width: 100%;
  min-height: 1.9rem;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* Collapsed by a tap: everything past the head is emptied by screens.js
   (not just hidden here), so nothing survives in the DOM for a curious
   neighbour to inspect. The role text itself is swapped for a neutral
   placeholder rather than merely restyled, which is why there is no
   ".signet-card-collapsed .signet-card-role" rule here: the real label is
   gone, not just dimmed. */
/* Shrunk to the width of its own label once it is shut. It carries one short
   line in that state and no reason to hold the width of a card that is not
   showing anything. */
.signet-card-collapsed {
  right: auto;
  max-width: min(13rem, calc(100% - 5rem));
  padding: 0.35rem 0.6rem 0.4rem;
}

.signet-card-collapsed .signet-card-head { min-height: 1.5rem; }

.signet-card-collapsed .signet-card-role {
  font-size: 0.82rem;
  color: var(--paper-dim);
  font-style: italic;
}

/* Sits at the right-hand end of the head row rather than on a line of its
   own: the affordance has to be visible (nobody taps a card that does not
   look tappable) but it does not deserve a line of the screen. */
.signet-card-hint {
  align-self: baseline;
  margin: 0 0 0 auto;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper-dim);
  white-space: nowrap;
}

/* The emblem sits with the name, and goes when the name goes: it identifies
   the signet just as plainly as the word does. */
.signet-card-sigil {
  display: flex;
  color: var(--mine);
}

.signet-card-sigil:empty { display: none; }

.signet-card-role {
  font-family: var(--title);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--mine);
}

.signet-side {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.signet-side-sealed { color: var(--bad); }

.signet-card-blurb {
  margin: 0.15rem 0 0;
  font-size: 0.76rem;
  line-height: 1.35;
  color: var(--paper-dim);
}

.signet-card-sees:not(:empty) { margin-top: 0.3rem; }

.signet-sees, .signet-sees-none {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.35;
  color: var(--mine);
}

.signet-sees-none { color: var(--paper-dim); font-style: italic; }

/* Below the room code, room-code-scale headroom is left clear of the fixed
   signet card and help control by the wrap's own top padding; on the
   lobby, where no game exists yet, the card is simply hidden. */

/* ---------- the rejection counter ----------
   "It must be impossible to miss": a fixed pill, not folded into a
   sentence, in --public because it is public record. Turns toward --bad as
   the fifth refusal, which ends the game outright, gets close. */

.rejection-pill {
  display: inline-block;
  margin: 0 auto 0.9rem;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  background: var(--public-dim);
  color: var(--public);
  font-family: var(--mono);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
}

.rejection-pill.rejection-hot {
  background: var(--bad-dim);
  color: var(--bad);
}

/* ---------- turn / status lines ---------- */

.turn-indicator {
  margin: 0 0 0.6rem;
  text-align: center;
  font-size: 0.95rem;
}

.turn-indicator.turn-public { color: var(--public); }

.private-line {
  margin: 0.8rem 0;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: var(--mine-dim);
  color: var(--mine);
  font-size: 0.9rem;
  line-height: 1.5;
  text-align: center;
}

/* ---------- the squad / naming picker ----------
   Shared by nominating (the Wingleader picks a squad) and naming (the
   Blade picks one rider). A tap toggles or selects; a seat that cannot be
   picked (the squad is already full, or an option this file must not
   offer) is disabled rather than removed, so the count of what remains
   never silently reflows under a thumb mid-tap. */

.squad-picker { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.6rem; }

.squad-pick {
  min-width: 4.4rem;
  min-height: 2.9rem;
  padding: 0.6rem 0.9rem;
  border: 2px solid var(--panel-line);
  border-radius: 10px;
  background: transparent;
  color: var(--paper);
  font-family: var(--display);
  font-size: 0.92rem;
  cursor: pointer;
}

.squad-pick:disabled { opacity: 0.35; cursor: not-allowed; }

.squad-pick.squad-pick-on {
  background: var(--mine-dim);
  border-color: var(--mine);
  color: var(--mine);
  font-weight: 700;
}

/* ---------- skipping a dropped phone ----------
   Offered to everybody, not just the viewer: anybody may skip a
   disconnected Wingleader, voter or squad member. --bad-tinted, because a
   dropped phone is the one thing here that genuinely is trouble. */

.skip-list { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.9rem; }

.skip-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  border: 2px solid var(--bad);
  background: var(--bad-dim);
  color: var(--bad);
  font-size: 0.85rem;
}

.skip-btn { width: auto; min-height: 2.75rem; margin: 0; padding: 0.4rem 0.9rem; font-size: 0.85rem; }

/* ---------- the record: a resolved vote, a sortie's result ----------
   Public record, always --public, never who played what: the vote
   breakdown names names because the vote itself is public in this game; a
   sortie result never carries anything beyond a falter count. */

.record { margin-bottom: 1rem; }

.record-entry {
  padding: 0.85rem 1rem;
  border-radius: 12px;
  border: 2px solid var(--panel-line);
  background: var(--panel);
}

.record-head {
  margin: 0 0 0.55rem;
  font-size: 0.88rem;
  color: var(--paper);
}

/* The two sides of a resolved vote, side by side. Two columns even on the
   narrowest phone: the whole value of this block is the comparison, and
   stacking them would turn it back into the single unreadable list it used
   to be. */
.vote-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.vote-column {
  padding: 0.5rem 0.6rem;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
}

.vote-column-fly { box-shadow: inset 3px 0 0 var(--public); }
.vote-column-standdown { box-shadow: inset 3px 0 0 var(--bad); }

.vote-column-head {
  margin: 0 0 0.35rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.vote-column-fly .vote-column-head { color: var(--public); }
.vote-column-standdown .vote-column-head { color: var(--bad); }

.vote-name {
  margin: 0 0 0.15rem;
  font-size: 0.84rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
  color: var(--paper);
}

.vote-name-none { color: var(--paper-dim); font-style: italic; }

/* The fold-away record of every earlier vote. Quiet, because it is a
   reference somebody reaches for mid-argument, not a thing to read on the
   way past. */
.record-toggle {
  display: block;
  width: 100%;
  margin: 0 0 1rem;
  padding: 0.6rem 0.8rem;
  border-radius: 10px;
  border: 1px dashed var(--panel-line);
  background: transparent;
  color: var(--paper-dim);
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}

.record-history .record-entry { margin-bottom: 0.6rem; }

.record-verdict { margin: 0; font-weight: 700; font-size: 0.9rem; }
.record-approved { color: var(--public); }
.record-rejected { color: var(--bad); }

.quip-line {
  margin: 0.5rem 0 0;
  font-style: italic;
  font-size: 0.86rem;
  color: var(--paper-dim);
  text-align: center;
}

/* ---------- the sortie track ----------
   Five pips and a tally, on every screen a game runs behind. Public in
   every phase. Deliberately small and quiet: it is a reference, not an
   announcement, and it is on screen the whole game. */

.sortie-track {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0 0 0.7rem;
}

.track-pip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 6px;
  border: 1px solid var(--panel-line);
  background: var(--panel);
  color: var(--paper-dim);
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1;
}

.track-pip-held {
  border-color: var(--mine);
  background: var(--mine-dim);
  color: var(--mine);
}

.track-pip-broken {
  border-color: var(--bad);
  background: var(--bad-dim);
  color: var(--bad);
}

/* The sortie in the air, or the one about to be: outlined, not filled, so
   it never reads as a result it has not produced yet. */
.track-pip-now {
  border-color: var(--public);
  color: var(--public);
}

/* The one that takes two falters to break. A corner mark rather than a
   different colour, which would collide with held and broken. */
.track-pip-double { position: relative; }

.track-pip-double::after {
  content: "2";
  position: absolute;
  top: -0.35rem;
  right: -0.3rem;
  padding: 0 0.2rem;
  border-radius: 999px;
  background: var(--public);
  color: var(--ink);
  font-size: 0.55rem;
  font-weight: 700;
  line-height: 1.3;
}

.track-tally {
  margin-left: 0.3rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: var(--paper-dim);
}

/* ---------- the tally ----------
   The vote, held on screen instead of flashing past. Sized under the
   debrief's banner: a vote is a beat, not the climax. */

.tally-banner {
  margin: 0.9rem 0 0.3rem;
  text-align: center;
  font-family: var(--title);
  font-size: clamp(1.4rem, 6.4vw, 1.9rem);
  font-weight: 700;
  letter-spacing: 0.03em;
}

.tally-approved { color: var(--leader); }
.tally-refused { color: var(--bad); }

.tally-detail {
  margin: 0 0 0.2rem;
  text-align: center;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

/* ---------- the debrief ----------
   The beat between a sortie and whatever follows it. The quip is the point
   of this screen, so it is set large enough to be read aloud across a
   room, not tucked under the result the way it used to be. */

.debrief-banner {
  margin: 1.2rem 0 0.6rem;
  text-align: center;
  font-family: var(--title);
  font-size: clamp(1.7rem, 8vw, 2.4rem);
  font-weight: 700;
  letter-spacing: 0.03em;
}

.debrief-held { color: var(--mine); }
.debrief-broken { color: var(--bad); }

.debrief-detail {
  margin: 0;
  text-align: center;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.debrief-quip {
  margin: 1.3rem auto 1.6rem;
  max-width: 26rem;
  padding: 1.1rem 0.5rem;
  text-align: center;
  font-size: clamp(1.05rem, 4.6vw, 1.3rem);
  font-style: italic;
  line-height: 1.45;
  color: var(--paper);
}

.debrief-quip::before,
.debrief-quip::after {
  content: "";
  display: block;
  width: min(12rem, 70%);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

.debrief-quip::before { margin: 0 auto 1.1rem; }
.debrief-quip::after { margin: 1.1rem auto 0; }

.debrief-standing {
  margin: 0 0 1.4rem;
  text-align: center;
  font-size: 0.9rem;
  color: var(--paper-dim);
}

/* ---------- the ending ---------- */

.ending-banner {
  margin: 0.4rem 0 0.3rem;
  padding: 1.1rem 1rem;
  border-radius: 12px;
  border: 1px solid transparent;
  text-align: center;
  font-family: var(--title);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  background:
    linear-gradient(var(--fill-public), var(--fill-public)) padding-box,
    var(--gold-leaf) border-box;
  color: var(--public);
}

.ending-banner.ending-sealed {
  background:
    linear-gradient(var(--fill-bad), var(--fill-bad)) padding-box,
    linear-gradient(145deg, #4A1618, #E5565B 40%, #8C2A2E) border-box;
  color: var(--bad);
}

/* The reveal. This is the payoff of the whole evening, so each rider gets a
   plate of their own rather than a line in a list: emblem, name at full
   weight, signet, and the side they turned out to be on. Everything is open
   at this point, so colouring a row by side costs no secret and makes the
   table readable from across the room. */

.reveal-body { margin: 0.6rem 0 0; }

.reveal-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "sigil name  signet"
    "sigil side  signet";
  align-items: center;
  column-gap: 0.7rem;
  margin-bottom: 0.5rem;
  padding: 0.6rem 0.8rem;
  border-radius: 10px;
  border: 1px solid transparent;
  background:
    linear-gradient(180deg, #14161C, #0C0E13) padding-box,
    var(--gold-leaf) border-box;
}

.reveal-row:last-child { margin-bottom: 0; }

.reveal-sigil {
  grid-area: sigil;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  border: 1px solid var(--panel-line);
  background: rgba(0, 0, 0, 0.35);
}

.reveal-sigil .sigil { width: 2.1rem; height: 2.1rem; }

.reveal-row .reveal-seat {
  grid-area: name;
  align-self: end;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  overflow-wrap: anywhere;
  color: var(--paper);
}

.reveal-side {
  grid-area: side;
  align-self: start;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.reveal-row .reveal-value {
  grid-area: signet;
  text-align: right;
  font-family: var(--title);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.15;
}

/* A loyal rider reads gold, a sealed one red, on the emblem, the signet and
   the side alike, so a row is one colour and takes no reading to place. */
.reveal-loyal .reveal-value,
.reveal-loyal .reveal-side,
.reveal-loyal .reveal-sigil { color: var(--gold-lit); }

.reveal-sealed .reveal-value,
.reveal-sealed .reveal-side,
.reveal-sealed .reveal-sigil { color: var(--bad); }

.reveal-sealed {
  background:
    linear-gradient(180deg, #1A1013, #0D0809) padding-box,
    linear-gradient(145deg, #3E1416, #E5565B 42%, #6E1F22) border-box;
}

.reveal-sealed .reveal-sigil { border-color: rgba(229, 86, 91, 0.35); }

/* ---------- help ---------- */

.help-fab {
  position: fixed;
  top: calc(0.9rem + env(safe-area-inset-top));
  right: calc(0.9rem + env(safe-area-inset-right));
  z-index: 900;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 999px;
  border: 2px solid var(--panel-line);
  background: var(--panel);
  color: var(--paper);
  font-family: var(--display);
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.help-overlay {
  position: fixed;
  inset: 0;
  /* Above the toast (z-index 1000): a server refusal that lands while help is
     open must still be readable, not buried under the panel a player opened
     to make sense of the game. */
  z-index: 1050;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.help-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
}

.help-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 34rem;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  border: 2px solid var(--panel-line);
  border-bottom: none;
  border-top-left-radius: 16px;
  border-top-right-radius: 16px;
  box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.5);
}

.help-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 1rem 0.7rem 1rem 1.2rem;
  border-bottom: 1px solid var(--panel-line);
}

.help-panel-title {
  margin: 0;
  font-family: var(--title);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.help-close {
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--paper);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}

.help-panel-body {
  overflow-y: auto;
  padding: 0.3rem 1.2rem 1.4rem;
}

.help-layer-title {
  margin: 1.3rem 0 0.7rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.help-layer-1 .help-layer-title { margin-top: 0.9rem; }

.help-heading {
  margin: 1.1rem 0 0.4rem;
  font-family: var(--title);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--gold-lit);
}

.help-heading:first-of-type { margin-top: 0; }

.help-layer-1 p,
.help-details-body p {
  margin: 0 0 0.7rem;
  font-size: 0.92rem;
  line-height: 1.5;
}

.help-list { margin: 0 0 0.7rem; padding-left: 1.3rem; }
.help-list li { margin-bottom: 0.6rem; font-size: 0.92rem; line-height: 1.5; }

.help-details {
  border: 2px solid var(--panel-line);
  border-radius: 10px;
  margin-bottom: 0.6rem;
  overflow: hidden;
}

.help-details.help-details-mine { border-color: var(--mine); }

.help-details summary {
  padding: 0.85rem 1rem;
  font-size: 0.92rem;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

.help-details summary::-webkit-details-marker { display: none; }
.help-details summary::marker { content: ''; }
.help-details summary::before { content: '+ '; color: var(--paper-dim); }
.help-details[open] summary::before { content: '\2212  '; }

.help-details-body { padding: 0 1rem 1rem; }

.help-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 0.9rem;
  font-size: 0.85rem;
}

.help-table th, .help-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--panel-line);
}

.help-table th {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper-dim);
}

.help-yours-badge {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--mine);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.help-print-link {
  display: block;
  margin-top: 0.6rem;
  padding: 0.9rem;
  border: 2px solid var(--public);
  border-radius: 10px;
  color: var(--public);
  text-align: center;
  font-weight: 700;
  text-decoration: none;
}
