/* ============================================================
   CC:A Web Client — Dark Sci-Fi Terminal Theme
   Matched to native Kivy app (theme.py palette + layout)
   ============================================================ */

/* --- Font Import --- */
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');

/* --- Custom Properties (exact from theme.py T[] dict) --- */
:root {
  /* Backgrounds */
  --screen-bg:           rgba(13, 15, 18, 1);      /* T["screen_bg"] 0.05,0.06,0.07 */
  --card-bg:             rgba(33, 36, 41, 1);      /* T["card_bg"] 0.13,0.14,0.16 */

  /* Accent layers (Cool Gray palette) */
  --border-out:          rgba(15, 20, 26, 1);      /* T["border_out"] */
  --border-dark:         rgba(46, 46, 51, 1);      /* T["border_dark"] 0.18,0.18,0.20 */
  --border-mid:          rgba(77, 79, 87, 1);      /* T["border_mid"] 0.30,0.31,0.34 */
  --border-light:        rgba(140, 140, 145, 1);   /* T["border_light"] */

  /* Separator */
  --sep:                 rgba(41, 120, 171, 1);    /* T["sep"] 0.16,0.47,0.67 */

  /* Text */
  --text-hi:             rgba(230, 237, 224, 1);   /* T["text_hi"] 0.90,0.93,0.88 */
  --text-mid:            rgba(158, 163, 153, 1);   /* T["text_mid"] 0.62,0.64,0.60 */
  --text-lo:             rgba(97, 102, 97, 1);     /* T["text_lo"] 0.38,0.40,0.38 */

  /* Faction */
  --consortium:          rgba(217, 51, 51, 1);     /* T["consortium"] 0.85,0.20,0.20 */
  --independence:        rgba(51, 153, 230, 1);    /* T["independence"] 0.20,0.60,0.90 */
  --hive:                rgba(0, 158, 115, 1);     /* T["hive"] 0.00,0.62,0.45 */

  /* Player status */
  --active:              rgba(64, 191, 89, 1);     /* T["active"] 0.25,0.75,0.35 */
  --uploaded:            rgba(217, 51, 51, 1);     /* T["uploaded"] */
  --revoked:             rgba(191, 46, 46, 1);     /* T["revoked"] 0.75,0.18,0.18 */

  /* Button fills (4 consolidated) */
  --btn-blue:            rgba(36, 77, 148, 1);     /* T["btn_blue"] 0.14,0.30,0.58 */
  --btn-green:           rgba(31, 82, 46, 1);      /* T["btn_green"] 0.12,0.32,0.18 */
  --btn-red:             rgba(133, 36, 26, 1);     /* T["btn_red"] 0.52,0.14,0.10 */
  --btn-gray:            rgba(36, 38, 46, 1);      /* T["btn_gray"] 0.14,0.15,0.18 */

  /* Button borders (fill * 1.6, 0.7 alpha — matching style_btn()) */
  --btn-blue-border:     rgba(58, 123, 237, 0.7);
  --btn-green-border:    rgba(50, 131, 74, 0.7);
  --btn-red-border:      rgba(213, 58, 42, 0.7);
  --btn-gray-border:     rgba(58, 61, 74, 0.7);

  /* Voting */
  --vote-select-bg:      rgba(51, 20, 20, 1);     /* T["vote_select_bg"] */
  --vote-select-border:  rgba(217, 51, 51, 1);    /* T["vote_select_border"] */
  --vote-nominee-bg:     rgba(64, 20, 20, 1);     /* T["vote_nominee_bg"] */
  --vote-nominee:        rgba(217, 51, 51, 1);    /* T["vote_nominee"] */
  --vote-yes-bg:         rgba(26, 89, 31, 1);     /* T["vote_yes_bg"] */
  --vote-yes-border:     rgba(51, 204, 77, 1);    /* T["vote_yes_border"] */
  --vote-no-bg:          rgba(89, 26, 26, 1);     /* T["vote_no_bg"] */
  --vote-no-border:      rgba(217, 51, 51, 1);    /* T["vote_no_border"] */
  --vote-passed:         rgba(230, 77, 77, 1);    /* T["vote_passed"] */
  --vote-failed:         rgba(102, 230, 102, 1);  /* T["vote_failed"] */

  /* Species card */
  --species-bg:          rgba(23, 26, 31, 1);     /* T["species_bg"] */
  --species-border:      rgba(140, 140, 145, 1);  /* T["species_border"] */
  --species-text:        rgba(217, 199, 0, 1);    /* T["species_text"] */

  /* Role card */
  --role-card-bg:        rgba(46, 46, 46, 1);     /* T["role_card_bg"] 0.18,0.18,0.18 */
  --role-card-name:      rgba(204, 204, 204, 1);  /* T["role_card_name"] */

  /* Ability / popup text */
  --ability-active:      rgba(77, 191, 255, 1);   /* T["ability_active"] */
  --ability-passive:     rgba(191, 128, 255, 1);  /* T["ability_passive"] */
  --ability-locked:      rgba(115, 115, 115, 1);  /* T["ability_locked"] */
  --lore-text:           rgba(140, 140, 140, 1);  /* T["lore_text"] */
  --rules-text:          rgba(166, 166, 166, 1);  /* T["rules_text"] */
  --intel-text:          rgba(102, 255, 179, 1);  /* T["intel_text"] */
  --secret-text:         rgba(230, 128, 128, 1);  /* T["secret_text"] */
  --status-yellow:       rgba(242, 217, 64, 1);   /* T["status_yellow"] */
  --popup-divider:       rgba(71, 71, 71, 1);     /* T["popup_divider"] */

  /* Broadcast screen effects */
  --broadcast-effect:    rgba(102, 255, 179, 1);  /* T["broadcast_effect"] */
  --broadcast-err-text:  rgba(242, 102, 64, 1);   /* T["broadcast_err_text"] */

  /* Attention / highlight */
  --text-attention:      rgba(255, 230, 102, 1);  /* T["text_attention"] */

  /* Target card */
  --target-disabled-text:     rgba(89, 89, 89, 1);
  --target-disabled-portrait: rgba(31, 31, 31, 1);
  --target-selected-bg:       rgba(64, 26, 26, 1);
  --target-selected-border:   rgba(230, 64, 64, 1);

  /* Lobby dots */
  --player-dot:          rgba(204, 204, 204, 1);
  --player-dot-empty:    rgba(115, 115, 115, 0.5);

  /* Card radius (matching CARD_RADIUS = 7) */
  --card-radius: 7px;

  /* Centre frame empty bg */
  --empty-bg: rgba(28, 31, 36, 1);  /* _EMPTY_BG */
}

/* ============================================================
   Reset & Base
   ============================================================ */

*, *::before, *::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: var(--screen-bg);
  color: var(--text-hi);
  font-family: 'VT323', monospace;
}

#app {
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  font-size: 18px;
  -webkit-font-smoothing: antialiased;
}

/* ============================================================
   Safe Area
   ============================================================ */

.safe-area {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* ============================================================
   Screen System
   ============================================================ */

.screen {
  display: none;
  flex-direction: column;
  width: 100%;
  height: 100vh;
  height: 100dvh;  /* dynamic viewport height — excludes mobile browser chrome */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.screen.active {
  display: flex;
}

/* PIN entry screen for hosting a new game. Server-side validated. */
.pin-inner {
  align-items: center;
  text-align: center;
  padding-top: 48px;
}
.pin-title {
  margin: 0 0 18px;
  font-size: 1.4em;
  letter-spacing: 0.05em;
  color: var(--text-hi, #eee);
}
.pin-dots {
  display: flex;
  gap: 14px;
  justify-content: center;
  margin: 6px 0 14px;
}
.pin-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--border-mid, #555);
  background: transparent;
  transition: background 0.1s, border-color 0.1s;
}
.pin-dot.filled {
  background: var(--text-hi, #eee);
  border-color: var(--text-hi, #eee);
}
.pin-error {
  color: var(--consortium, #d93333);
  font-size: 0.95em;
  margin: 4px 0 10px;
  min-height: 1.2em;
}
.pin-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  width: min(100%, 280px);
  margin: 14px auto 0;
}
.pin-key {
  font-size: 1.6em;
  padding: 16px 0;
  border-radius: 10px;
  border: 1px solid var(--border-mid, #555);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-hi, #eee);
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.pin-key:active {
  background: rgba(255, 255, 255, 0.12);
}
.pin-key-blank {
  visibility: hidden;
}

/* Splash / loading screen — Matrix-rain animation over warped background. */
#view-splash {
  position: relative;
  background: #000;
  overflow: hidden;
  align-items: stretch;
  justify-content: stretch;
  padding: 0;
}
.splash-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Warp-to-fill: stretches portrait artwork to cover entire phone screen. */
  object-fit: fill;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  z-index: 1;
  transition: opacity 350ms ease-out;
}
#splash-rain {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
}
.splash-content {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5vh;
  width: 100%;
  height: 100%;
  padding: 8vh 16px;
  pointer-events: none;
  box-sizing: border-box;
}
.splash-welcome,
.splash-icon,
.splash-enter {
  opacity: 0;
  transition: opacity 1000ms ease-out;
}
.splash-welcome.revealed,
.splash-icon.revealed,
.splash-enter.revealed {
  opacity: 1;
}
.splash-welcome {
  color: #fff;
  font-size: clamp(2.5em, 9vw, 5em);
  font-weight: 700;
  letter-spacing: 0.25em;
  text-align: center;
  text-shadow: 0 0 18px rgba(176, 208, 64, 0.45),
               0 2px 12px rgba(0, 0, 0, 0.85);
}
.splash-icon {
  width: clamp(160px, 44vw, 300px);
  height: auto;
  filter: drop-shadow(0 0 16px rgba(176, 208, 64, 0.35));
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}
/* Splash ENTER button reuses the standard .btn .btn-blue look — these rules
   only override size + animation. `button.splash-enter` (specificity 0,1,1)
   is required to beat `.btn-lg` and `.btn` (both 0,1,0) which appear later
   in the stylesheet and would otherwise win the cascade for font-size,
   padding, and transition. */
button.splash-enter {
  font-size: clamp(25px, 5.3vw, 38px);
  padding: 0.5em 1.5em;
  letter-spacing: 0.5em;
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 1000ms ease-out, filter 0.1s;
}
button.splash-enter.revealed {
  pointer-events: auto;
}

/* ============================================================
   Container & View Inner
   ============================================================ */

.container {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: 16px;
}

.view-inner {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: 36px 10px 20px;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

/* ============================================================
   Buttons (matching style_btn: rounded rect + border)
   ============================================================ */

.btn {
  display: inline-block;
  padding: 10px 16px;
  border: 1.2px solid transparent;
  border-radius: var(--card-radius);
  cursor: pointer;
  font-family: 'VT323', monospace;
  font-size: 18px;
  text-transform: uppercase;
  color: var(--text-hi);
  transition: filter 0.1s;
  text-align: center;
}

.btn:active  { filter: brightness(1.15); }
.btn:disabled { opacity: 0.4; cursor: default; filter: none; }

.btn-blue  { background: var(--btn-blue);  border-color: var(--btn-blue-border); }
.btn-green { background: var(--btn-green); border-color: var(--btn-green-border); }
.btn-red   { background: var(--btn-red);   border-color: var(--btn-red-border); }
.btn-gray  { background: var(--btn-gray);  border-color: var(--btn-gray-border); }

.btn-sm { font-size: 15px; padding: 6px 10px; }
.btn-lg { font-size: 21px; padding: 12px 16px; }

/* ============================================================
   Player Cards (matching _PlayerCard anatomy)
   ┌──────────────┬──────┐
   │ Player Name  │[STA] │  Top 30%: name | status icon
   ├──────────────┼──────┤
   │              │[S1]  │  Bottom 70%: species | 3 icon slots
   │  [Species]   │[S2]  │    S1: FR/towel icon
   │              │[S3]  │    S2: info icon
   └──────────────┴──────┘    S3: vote/charge icon
   ============================================================ */

/* ─── Player card (square, 4 fit per row in a 480px container) ───
   Layout:
     ┌────────────────────┐
     │  Name           [x]│   30% — name + status icon
     ├────────────────────┤
     │ [D]            [A] │
     │     SPECIES        │   70% — species image as background,
     │      IMAGE         │        four corner overlays (D/A/C/B)
     │ [C]            [B] │
     └────────────────────┘
*/
.card {
  background: var(--card-bg);
  border-radius: var(--card-radius);
  border: 1px solid rgba(77, 79, 87, 0.6);
  overflow: hidden;
  cursor: pointer;
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
}

/* Top 30%: name + status icon */
.card-top {
  height: 30%;
  display: flex;
  align-items: stretch;
  min-height: 0;
}

.card-name-area {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0 0.15em;
  border-radius: var(--card-radius) 0 0 0;
  min-width: 0;
}

.card-name {
  font-size: 0.85em;
  font-weight: bold;
  color: var(--text-hi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  width: 100%;
}

.card-status-icon {
  width: 30%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.15em;
  border-radius: 0 var(--card-radius) 0 0;
  flex-shrink: 0;
}

.card-status-icon img {
  width: 80%;
  height: 80%;
  object-fit: contain;
}

/* Bottom 70%: species image as background + 4 corner overlays */
.card-body {
  flex: 1;
  position: relative;
  min-height: 0;
}

.card-species {
  position: absolute;
  inset: 6%;
  width: 88%;
  height: 88%;
  object-fit: contain;
  border-radius: 4px;
  pointer-events: none;
}

.card-corner {
  position: absolute;
  /* Height-driven square — base on body height so corners stay 1:1 even though
     the body itself is wider than tall (full width × 70% height). */
  height: 38%;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}
.card-corner img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.card-corner-tl { top: 2%;    left: 2%; }
.card-corner-tr { top: 2%;    right: 2%; }
.card-corner-bl { bottom: 2%; left: 2%; }
.card-corner-br { bottom: 2%; right: 2%; }

/* D corner: phone-colour swatch (matches the home-screen picker) */
.card-phone-swatch {
  display: block;
  width: 80%;
  height: 80%;
  border-radius: 50%;
  border: 1px solid var(--border-mid);
}

/* C corner can hold ability charges (column of small icons) for own card */
.card-charges {
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
}
.card-charges img {
  width: 80%;
  height: auto;
  flex: 0 0 auto;
}
/* With 2 charges both icons need to fit inside the (square) B-corner at
   full size. Pull the second icon up via a negative top margin so it
   overlaps with the first instead of stacking below the corner. */
.card-charges img + img {
  margin-top: -60%;
}
/* When two charges are showing, nudge the top icon further up via
   transform (doesn't affect layout, so the bottom icon stays put).
   Without this the two icons overlap too tightly to read as separate. */
.card-charges img:first-child:not(:only-child) {
  transform: translateY(-30%);
}

/* Portrait tint backgrounds (top row bg matches native portrait_bg) */
.card.active-status .card-name-area,
.card.active-status .card-status-icon {
  background: rgba(46, 46, 51, 1);  /* T["border_dark"] */
}

.card.uploaded .card-name-area,
.card.uploaded .card-status-icon {
  background: rgba(54, 13, 13, 1);  /* uploaded * 0.25 */
}

.card.revoked .card-name-area,
.card.revoked .card-status-icon {
  background: rgba(48, 12, 12, 1);  /* revoked * 0.25 */
}

/* Border colour by status (matching native _PlayerCard) */
.card.active-status { border-color: rgba(77, 79, 87, 0.6); }
.card.uploaded      { border-color: rgba(217, 51, 51, 0.5); }
.card.revoked       { border-color: rgba(191, 46, 46, 0.5); }

/* Selection / vote state (matching set_tint) */
.card.is-you     { background: rgba(51, 153, 230, 0.06); }
.card.selected   { border: 2px solid var(--vote-select-border); background: var(--vote-select-bg); }
.card.vote-yes   { border: 1.5px solid var(--vote-yes-border); background: var(--vote-yes-bg); }
.card.vote-no    { border: 1.5px solid var(--vote-no-border); background: var(--vote-no-bg); }
.card.vote-pass  { border: 1.5px solid rgba(150, 150, 155, 0.7); background: rgba(60, 60, 65, 0.35); }
.card.nominee    { border: 1.8px solid var(--vote-nominee); }
.card.been-nominated { opacity: 0.45; }

/* ============================================================
   Player Grid (4 columns, matching native GridLayout cols=4)
   ============================================================ */

.player-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  flex: 1;
  overflow-y: auto;
  align-content: start;
}

/* ============================================================
   Broadcast Screen
   ============================================================ */

/* Phase banner (matching native FloatLayout, 40dp) */
.bc-banner {
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  flex-shrink: 0;
  height: 40px;
  margin-bottom: 4px;
}

.bc-phase-label {
  font-size: 23px;
  font-weight: bold;
  color: var(--text-hi);
  text-transform: uppercase;
}

.bc-round-label {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-lo);
  font-size: 14px;
}

.bc-log-btn {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

/* Top area: 3-column (own card | centre frame | actions) */
.bc-top-area {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 4px;
  margin-bottom: 4px;
  flex-shrink: 0;
}

.bc-col {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Own card container */
.bc-own-card {
  width: 100%;
  max-width: 120px;
}

/* Centre frame (matching _CenterFrame) */
.centre-frame {
  position: relative;
  background: var(--card-bg);
  border-radius: var(--card-radius);
  border: 1px solid rgba(77, 79, 87, 0.6);
  aspect-ratio: 1 / 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.centre-frame-empty {
  background: var(--empty-bg);
  border-color: rgba(77, 79, 87, 0.3);
}

.centre-frame.nominee { border: 1.8px solid var(--vote-nominee); border-width: 1.8px; }

/* Pending-nomination active half: card whose 2:30 window is currently live
   gets a green border + green tint. Centre = Defender (second half),
   right column = Accuser (first half). */
.centre-frame.pending-active {
  border: 1.8px solid var(--active);
  background: rgba(64, 191, 89, 0.12);
}
.centre-frame.pending-active .centre-frame-top {
  background: rgba(64, 191, 89, 0.18);
}

/* BC centre-frame slot flashes — 1Hz soft pulse on the card background for
   the 8s window the slot is active. Green for broadcaster (good for Indi:
   identity leak); red for everything else (balance @channel, applicant,
   forecast exposure). The slot priority chain in compute_broadcast_timeline
   only fires one slot at a time per player, so flashes don't overlap. */
@keyframes bc-pulse-green {
  0%, 100% { box-shadow: inset 0 0 0 9999px rgba(64, 191, 89, 0); }
  50%      { box-shadow: inset 0 0 0 9999px rgba(64, 191, 89, 0.18); }
}
@keyframes bc-pulse-red {
  0%, 100% { box-shadow: inset 0 0 0 9999px rgba(208, 77, 70, 0); }
  50%      { box-shadow: inset 0 0 0 9999px rgba(208, 77, 70, 0.20); }
}
.centre-frame.bc-flash-green { animation: bc-pulse-green 1s ease-in-out infinite; }
.centre-frame.bc-flash-red   { animation: bc-pulse-red   1s ease-in-out infinite; }

/* Start Vote button overlay: bottom third of the nominee centre frame,
   centred horizontally. Only rendered for the nominee. */
.start-vote-overlay {
  position: absolute;
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  z-index: 2;
}
.start-vote-overlay .btn {
  padding: 6px 14px;
  font-size: 14px;
  white-space: nowrap;
}

/* Centre frame top (30% — name) */
.centre-frame-top {
  height: 30%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  background: var(--empty-bg);
}

.centre-name {
  font-size: 14px;
  font-weight: bold;
  color: var(--text-hi);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
}

/* Centre frame bottom (70% — species left ~55%, status right ~45%) */
.centre-frame-bottom {
  height: 70%;
  display: flex;
  min-height: 0;
}

.centre-species-area {
  width: 55%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
}

.centre-species {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 4px;
}

.centre-status-area {
  width: 45%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px 4px;
}

.centre-status-text {
  font-size: 13px;
  text-align: center;
  word-break: break-word;
}

/* Action buttons column — sized to match the own card on the left so the
   two stacked buttons together occupy the same vertical space, with a
   slim gap between them. */
.bc-action-col {
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
  width: 100%;
  max-width: 120px;
  aspect-ratio: 1 / 1;
}

.bc-grace-text {
  text-align: center;
  color: var(--text-attention);
  font-size: 17px;
  font-weight: bold;
  padding: 8px 4px;
  width: 100%;
}

.bc-action-slot {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.bc-action-slot .btn {
  width: 100%;
  height: 100%;
  font-size: 15px;
  padding: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Vote thumb icons in action buttons */
.bc-action-slot .btn img.vote-thumb {
  width: 24px;
  height: 24px;
  object-fit: contain;
}

/* Vote three-button layout: top slot holds YES + NO side-by-side, bottom
   slot holds PASS spanning the full width. */
.bc-action-slot .bc-vote-row {
  display: flex;
  gap: 4px;
  width: 100%;
  height: 100%;
}
.bc-action-slot .bc-vote-row .btn {
  flex: 1;
  min-width: 0;
}

/* Info row (round + timer + spacer) — matching native 22dp row */
.bc-info-row {
  display: flex;
  align-items: center;
  height: 22px;
  margin: 2px 0;
  flex-shrink: 0;
}

.bc-info-round {
  width: 30%;
  font-size: 14px;
  color: var(--text-lo);
}

.bc-info-timer {
  width: 40%;
  text-align: center;
  font-size: 18px;
  font-weight: bold;
  color: var(--text-attention);
}

/* Inactive-voter grace countdown — replaces the regular vote timer with a
   visibly more urgent display when only inactive players are still pending. */
.vote-grace-timer {
  color: var(--consortium);
  font-size: 22px;
  letter-spacing: 1px;
  text-shadow: 0 0 6px rgba(217, 51, 51, 0.45);
  animation: vote-grace-pulse 0.8s ease-in-out infinite alternate;
}
@keyframes vote-grace-pulse {
  from { opacity: 0.85; }
  to   { opacity: 1; }
}

.bc-info-spacer {
  width: 30%;
}

/* Vote result text */
.bc-vote-result {
  text-align: center;
  color: var(--text-attention);
  font-size: 18px;
  min-height: 0;
}

.bc-vote-result.visible { min-height: 24px; }

/* Proceed row (matching native 56dp) */
.bc-proceed-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 4px;
}

.bc-proceed-row .btn { flex: 7; font-size: 21px; padding: 12px; }
.bc-proceed-count    { flex: 3; text-align: center; color: var(--text-mid); font-size: 16px; }

/* Bottom buttons row (Rules + All Characters) */
.bc-bottom-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}

.bc-bottom-row .btn { flex: 1; font-size: 15px; }

/* Bottom-pinned controls: Proceed + Rules/All Characters always visible on
   short screens. The view-inner is a flex column; we make only the player
   grid scrollable (bc-scroll = flex: 1, overflow-y: auto) and keep this
   row as a non-shrinking sibling, so it sits naturally at the bottom and
   never scrolls out of view. */
.bc-bottom-fixed {
  flex-shrink: 0;
  background: var(--screen-bg);
  padding: 4px 0;
}

/* Scrollable middle (player grid only). flex: 1 absorbs remaining height,
   min-height: 0 prevents flex auto-min sizing from defeating overflow. */
.bc-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* ============================================================
   Blocked Overlay
   ============================================================ */

.blocked-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(20, 20, 26, 0.82);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  text-align: center;
  padding: 24px;
}

.blocked-msg {
  color: var(--vote-passed);
  font-size: 18px;
  line-height: 1.6;
  white-space: pre-line;
}

/* ============================================================
   Uplink Screen
   ============================================================ */

/* Own card row (matching native CARD_S height row) */
.ul-own-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
  flex-shrink: 0;
}

.ul-own-card {
  width: 90px;
  flex-shrink: 0;
}

.ul-own-status {
  font-size: 16px;
  font-weight: bold;
}

.ul-instruction {
  color: var(--text-attention);
  font-size: 18px;
  text-align: center;
  padding: 8px 0;
  flex-shrink: 0;
}

/* Progress bar (matching native download/recovery bar) */
.ul-progress-wrap {
  position: relative;
  margin-bottom: 8px;
  flex-shrink: 0;
}

.ul-progress-bar {
  height: 36px;
  background: var(--card-bg);
  border-radius: 6px;
  overflow: hidden;
}

.ul-progress-fill {
  height: 100%;
  background: var(--independence);
  transition: width 0.5s;
}

.ul-progress-label {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-hi);
  font-weight: bold;
  font-size: 16px;
}

.ul-submitted {
  text-align: center;
  color: var(--text-mid);
  font-size: 16px;
  padding: 16px 0;
}

/* Target grid (matching native 4-column grid) */
.ul-target-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  flex: 1;
  overflow-y: auto;
  align-content: start;
}

/* Action buttons */
.ul-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.ul-actions .btn { flex: 1; font-size: 18px; padding: 12px; }

/* Bottom toolbar */
.ul-bottom-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}

.ul-bottom-row .btn { flex: 1; font-size: 15px; }

/* ============================================================
   Join Screen (matching home.py layout)
   ============================================================ */

.join-inner {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  padding: 40px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.join-inner label {
  color: var(--text-lo);
  font-size: 21px;
  text-transform: uppercase;
  margin-top: 12px;
}

.error-msg {
  color: var(--revoked);
  text-align: center;
  font-size: 16px;
  min-height: 20px;
}

/* Colour picker (matching native 6 swatches in 1 row) */
.colour-picker {
  display: flex;
  flex-direction: row;
  gap: 4px;
  justify-content: center;
}

.colour-swatch {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(82, 82, 82, 1);
  cursor: pointer;
  transition: border-color 0.15s;
}

.colour-swatch.selected,
.colour-swatch.chosen {
  border: 2.5px solid rgba(255, 255, 255, 1);
}

.colour-label {
  text-align: center;
  color: var(--text-mid);
  font-size: 16px;
  margin-top: 4px;
  min-height: 18px;
}

/* ============================================================
   Lobby (matching lobby.py layout)
   ============================================================ */

.lobby-count {
  color: var(--text-mid);
  text-align: center;
  margin: 4px 0 12px;
}

/* OS gesture suppression for all interactive cards.
   Prevents iOS callout menu, Android context menu, text selection,
   image drag and double-tap zoom on tap targets. */
.card,
.ban-card,
.overview-card,
.mini-card,
.bc-own-card,
.role-card-tappable {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
.card img,
.ban-card img,
.overview-card img,
.mini-card img {
  -webkit-user-drag: none;
  user-drag: none;
  /* No pointer-events:none — that breaks event bubbling on some browsers.
     Image drag is suppressed by -webkit-user-drag and contextmenu handler. */
}

/* Larger room code badge (replaces the player count line) */
.lobby-code-badge {
  text-align: center;
  font-size: 26px;
  font-weight: normal;
  color: var(--text-mid);
  margin: 0 0 4px;
}
.lobby-code-value {
  color: var(--text-hi);
  letter-spacing: 2px;
  margin-left: 4px;
}

/* +/- player count box (host only) — between slots and grey box */
.lobby-player-count-box {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 8px 0 12px;
  padding: 6px 10px;
  background: rgba(33, 36, 41, 0.5);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
}
.lobby-player-count-num {
  color: var(--text-hi);
  font-size: 28px;
  font-weight: bold;
  min-width: 30px;
  text-align: center;
}

/* Host grey box (scenario toggles + conditional content) */
.lobby-host-box {
  background: var(--card-bg);
  border: 1px solid var(--border-mid);
  border-radius: 7px;
  padding: 10px;
  margin: 0 0 6px;
}
.lobby-scenario-toggles {
  display: flex;
  gap: 6px;
  margin-bottom: 4px;
}

.lobby-ban-submitted {
  text-align: center;
  color: var(--active);
  font-size: 18px;
  font-weight: bold;
  margin: 12px 0 4px;
}

.slot-area { padding: 4px 0 12px; }

.slot-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 4px;
}

.slot-row-inner {
  display: flex;
  gap: 5px;
  justify-content: center;
}

.slot-row-dots {
  margin-top: 2px;
  cursor: pointer;
}

.slot-sq {
  width: 28px;
  height: 28px;
  border-radius: 4px;
  border: 2px solid var(--sq-color);
  background: transparent;
  flex-shrink: 0;
}

.slot-sq.double {
  border-width: 1.5px;
  position: relative;
}

.slot-sq.double::after {
  content: '';
  position: absolute;
  top: 3px; left: 3px; right: 3px; bottom: 3px;
  border: 1.5px solid var(--sq-color);
  border-radius: 2px;
}

.slot-dot-cell {
  width: 28px;
  height: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.slot-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: none;
  padding: 0;
}

.slot-dot.filled { background: var(--player-dot); }
.slot-dot.ghost  { background: transparent; border: 1.5px solid var(--border-mid); }

.player-info-popup {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.lobby-overlay {
  background: var(--card-bg);
  border-radius: 12px;
  padding: 14px;
  width: 80vw;
  max-width: 360px;
  max-height: 60vh;
  display: flex;
  flex-direction: column;
}

.lobby-overlay-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  color: var(--text-hi);
  font-size: 18px;
  font-weight: bold;
}

.lobby-overlay-list {
  overflow-y: auto;
  flex: 1;
}

.lobby-overlay-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 4px;
  border-bottom: 1px solid var(--border-dark);
}

.lobby-overlay-name {
  color: var(--text-hi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.waiting-msg {
  color: var(--text-lo);
  text-align: center;
  margin-top: 16px;
}

.lobby-bottom {
  margin-top: 12px;
  display: flex;
  gap: 8px;
}

.lobby-bottom .btn { flex: 1; }

/* ============================================================
   Ban Screen (matching lobby.py ban section)
   ============================================================ */

.ban-section {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.ban-section h3 {
  color: var(--text-hi);
  margin: 0 0 4px;
  flex-shrink: 0;
  font-size: 21px;
  font-weight: bold;
}

.ban-scroll {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  margin-bottom: 8px;
}

.ban-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}

.ban-confirm {
  flex-shrink: 0;
  width: 100%;
}

.ban-card {
  background: var(--role-card-bg);
  border: 1px solid rgba(77, 79, 87, 0.3);
  border-radius: var(--card-radius);
  padding: 4px;
  text-align: center;
  cursor: pointer;
  color: var(--text-hi);
  font-family: 'VT323', monospace;
  font-size: 14px;
  aspect-ratio: 1/1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.ban-card.ban-selected { border: 2px solid var(--consortium); }

.ban-img {
  width: 69%;
  max-height: 63%;
  object-fit: contain;
  border-radius: 4px;
  margin-bottom: 4px;
}

.ban-name {
  font-size: 17px;
  font-weight: bold;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  line-height: 1.05;
  text-align: center;
  max-width: 100%;
}

/* Ban section header (faction group) */
.ban-section-header {
  grid-column: 1 / -1;
  font-size: 23px;
  color: var(--text-lo);
  padding: 4px 0 2px;
}

/* ============================================================
   Role Reveal
   ============================================================ */

.role-reveal-btn {
  font-size: 28px;
  padding: 24px 24px;
  flex: 1;
  min-width: 0;
}

.role-spacer {
  flex: 1;
  min-height: 20px;
  max-height: 30vh;
}

.role-reveal-center {
  display: flex;
  justify-content: center;
}

.ready-count {
  color: var(--text-mid);
  text-align: center;
  margin: 12px 0 8px;
}

/* ============================================================
   Role Popup (matching RoleInfoPopup)
   ============================================================ */

.role-popup {
  width: 85vw;
  max-width: 400px;
}

/* Identity row: species (small, left) | role (large, centre) | phone colour (small, right) */
.role-popup-header {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  justify-content: center;
  margin-bottom: 4px;
}

.role-popup-img {
  width: 64px;
  height: 64px;
  border-radius: 10px;
  flex-shrink: 0;
  object-fit: contain;
}

.role-popup-species {
  width: 36px;
  height: 36px;
  border-radius: 4px;
  flex-shrink: 0;
  object-fit: contain;
}

.role-popup-colour {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  flex-shrink: 0;
  align-self: flex-end;
}

.role-popup-name {
  color: var(--text-hi);
  margin: 4px 0 2px;
  text-align: center;
  font-size: 25px;
}

.role-popup-faction {
  font-size: 15px;
  margin: 0 0 6px;
  text-align: center;
}

.role-flavor {
  color: var(--lore-text);
  font-style: italic;
  font-size: 21px;
  text-align: center;
  margin-bottom: 8px;
}

/* Ability items */
.ability-item {
  background: var(--border-dark);
  border-radius: 6px;
  padding: 8px 10px;
  margin-bottom: 6px;
}

.ability-header {
  font-weight: bold;
  font-size: 22px;
  margin-bottom: 2px;
}

.ability-item p {
  margin: 4px 0 0;
  color: var(--text-hi);                /* desc paragraph: white */
  font-size: 22px;
}

.ability-item .ability-rules {
  font-style: italic;
  font-size: 21px;
  color: var(--text-mid);               /* rules paragraph: gray */
}

.ability-active .ability-header  { color: var(--ability-active); }
.ability-passive .ability-header { color: var(--ability-passive); }
.ability-locked .ability-header  { color: var(--ability-locked); }
.ability-locked p                { color: var(--ability-locked); }

/* Inline highlights for descriptions / rules / compact text.
   References to other game terms get colour-coded by category:
   action names → ability-active, effect names → ability-passive,
   Indi roles/factions → independence, Conso roles/factions → consortium. */
.desc-action  { color: var(--ability-active); }
.desc-effect  { color: var(--ability-passive); }
.desc-indi    { color: var(--independence); }
.desc-conso   { color: var(--consortium); }

/* Status section */
.status-section { margin-top: 4px; }

.status-block { margin-bottom: 10px; }
.status-label { color: var(--status-yellow); font-size: 15px; font-weight: bold; margin: 0 0 4px; }
.status-text  { color: var(--intel-text); font-size: 15px; margin: 2px 0; }

.status-card-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: stretch;
}

/* All cards in a status-card-row keep the same fixed size — chosen so 4
   cards exactly fill the row width (allowing for 3 × 6 px gaps). Fewer
   cards leave proportional empty space on the right (3 cards ≈ ¾ row,
   2 cards ≈ ½, 1 card ≈ ¼). PitM and Sylvester have their own layouts
   and aren't affected. */
.status-card-row > .mini-card {
  flex: 0 0 calc(25% - 4.5px);
  width: auto;
  min-width: 0;
  max-width: none;
}

/* Mini cards (used inline in status sections, vote-result popup, etc.)
   Square; contains either a player card variant (with top row) or a role
   card (image + name layout matching .overview-card). */
.mini-card {
  position: relative;
  width: 72px;
  aspect-ratio: 1 / 1;
  background: var(--card-bg);
  border-radius: var(--card-radius);
  border: 1px solid rgba(77, 79, 87, 0.6);
  overflow: hidden;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}

/* Mini player card (used in vote result popup) keeps name/status top row */
.mini-card-top {
  height: 30%;
  display: flex;
  align-items: center;
  padding: 0 2px;
  background: rgba(46, 46, 51, 1);
}

.mini-card-name {
  font-size: 0.75em;
  font-weight: bold;
  color: var(--text-hi);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  line-height: 1.05;
  text-align: left;
  width: 100%;
}

.mini-card-bottom {
  position: relative;
  height: 70%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3px;
}

/* Corner overlay on the mini-card body — same anatomy as .card-corner-tl
   on the big renderCard. Currently only the top-left position is used,
   for the phone-colour swatch. */
.mini-card-corner {
  position: absolute;
  height: 38%;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}
.mini-card-corner-tl { top: 4%; left: 4%; }

.mini-card-phone-swatch {
  display: block;
  width: 80%;
  height: 80%;
  border-radius: 50%;
  border: 1px solid var(--border-mid);
}

.mini-card-species {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 3px;
}


.mini-card-status {
  width: 18%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1px;
}

.mini-card-status img {
  width: 80%;
  height: 80%;
  object-fit: contain;
}

/* Mini card status colours */
.mini-card.active-status { border-color: rgba(77, 79, 87, 0.6); }
.mini-card.active-status .mini-card-top { background: rgba(46, 46, 51, 1); }
.mini-card.uploaded { border-color: rgba(217, 51, 51, 0.5); }
.mini-card.uploaded .mini-card-top { background: rgba(54, 13, 13, 1); }
.mini-card.revoked { border-color: rgba(191, 46, 46, 0.5); }
.mini-card.revoked .mini-card-top { background: rgba(48, 12, 12, 1); }

/* Role + species cards in role-info popups reuse the All-Characters
   .overview-card visual style. Empty variant (e.g. Babel Fish negative)
   has a dim border, no image, neutral label centred. */
.overview-card-empty {
  background: transparent;
  border-color: rgba(77, 79, 87, 0.4);
}
.overview-card-empty .overview-card-name {
  color: var(--text-mid);
}

/* Species clue card: phone-colour swatch in the top-left corner mirroring
   the player card's D-corner placement. */
.species-clue-card {
  position: relative;
}
.species-clue-phone-swatch {
  position: absolute;
  top: 4px;
  left: 4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--border-mid);
  z-index: 1;
}

/* PitM Surveil diagram: 3-column [left] [target] [right] with SVG arrows. */
.pitm-block {
  position: relative;
  padding: 8px 4px;
  width: 100%;
}
.pitm-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}
.pitm-grid {
  position: relative;
  z-index: 1;
  display: grid;
  /* 5-column grid: card | arrow-gap | card | arrow-gap | card.
     Each card column is exactly the same width as a status-card-row card
     (calc(25% - 4.5px) of the popup), so PitM cards visually match every
     other player-card row. The two 1fr columns soak up the remaining
     ~25% as breathing room for the SVG arrows. */
  grid-template-columns:
    calc(25% - 4.5px) 1fr calc(25% - 4.5px) 1fr calc(25% - 4.5px);
  align-items: center;
  gap: 6px;
}
.pitm-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  width: 100%;
}
.pitm-left   { align-items: flex-start; grid-column: 1; }
.pitm-center { align-items: center;     grid-column: 3; }
.pitm-right  { align-items: flex-end;   grid-column: 5; }
.pitm-card-wrap {
  position: relative;
  width: 100%;
}
.pitm-card-wrap .mini-card { width: 100%; }
.pitm-you-tag {
  position: absolute;
  top: -8px;
  left: -4px;
  z-index: 2;
  background: var(--independence, #3399e6);
  color: #fff;
  font-size: 9px;
  font-weight: bold;
  letter-spacing: 0.5px;
  padding: 1px 4px;
  border-radius: 3px;
  pointer-events: none;
}
.pitm-divider {
  border: none;
  border-top: 1px solid var(--border-mid);
  margin: 10px 0;
}

/* James Sylvester Graph Plot — clock-face layout. Player cards positioned
   absolutely on a circle, SVG layer behind draws every recorded edge.
   Most edges are dim/uniform; two "revealed" edges show their true weight
   in independence-blue. */
.sylvester-block {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  margin: 4px auto;
}
.sylvester-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}
.sylvester-card {
  position: absolute;
  z-index: 1;
  container-type: inline-size;
  cursor: pointer;
}
.sylvester-card .mini-card {
  width: 100%;
}
/* Sylvester graph: scale player-name font with card width, with a 10px floor
   so it stays readable on the densest layouts. Single-line + ellipsis when
   the name still doesn't fit; tap the card to surface the full name. */
.sylvester-card .mini-card-name {
  font-size: clamp(10px, 14cqw, 14px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  -webkit-line-clamp: unset;
}
.sylvester-name-tooltip {
  position: absolute;
  bottom: 102%;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.9);
  color: var(--text-hi);
  padding: 3px 7px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: bold;
  white-space: nowrap;
  z-index: 50;
  pointer-events: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* Profiler "Threat level" column — sits in the 4th status-card-row slot
   alongside the 3 neighbour cards. Square slot like a card; inside, the
   label runs vertically on the left as a y-axis caption, with the three
   threat bars stacked on the right (top: red, middle: yellow, bottom:
   green). Bars deliberately leave breathing room around them. */
.prof-threat-col {
  flex: 0 0 calc(25% - 4.5px);
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: stretch;
  padding: 6px 4px;
  box-sizing: border-box;
}
.prof-threat-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 13px;
  font-weight: bold;
  color: var(--text-hi);
  text-align: center;
  flex: 0 0 auto;
  padding: 2px 0;
  align-self: center;
  letter-spacing: 0.5px;
}
.prof-threat-bars {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  margin-left: 6px;
  padding: 4px 4px;
}
.prof-bar-seg {
  flex: 1;
  border-radius: 3px;
  border: 1px solid var(--border-mid);
  min-height: 6px;
}
.prof-bar-seg-0 { background: var(--active);        border-color: var(--active); }
.prof-bar-seg-1 { background: var(--status-yellow); border-color: var(--status-yellow); }
.prof-bar-seg-2 { background: var(--consortium);    border-color: var(--consortium); }

/* Elton "Follow the Rainbow" histogram: y-axis label "Count" + numeric
   ticks, x-axis tick labels = phone-colour names below each bar, plus a
   flag indicator above the bars (solid = single bar, dashed = uncertain
   span). Grid columns are: rotated label | tick labels | y-axis line |
   chart area. The chart area uses 3 stacked rows (flag / bars / x-labels),
   each itself a grid of N columns matching the bar count, so the flag
   indicator and x-labels align exactly with the bars. */
.ftr-grid {
  display: grid;
  grid-template-columns: 14px 22px 4px 1fr;
  grid-template-rows: 6px 90px 18px;
  column-gap: 1px;
  row-gap: 0;
  padding: 4px 6px 0 0;
  margin: 4px auto 0;
  width: 80%;
  max-width: 80%;
}
.ftr-y-axis-label {
  grid-column: 1;
  grid-row: 2;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 13px;
  font-weight: bold;
  color: var(--text-hi);
  text-align: center;
  align-self: center;
  justify-self: end;
}
.ftr-y-ticks {
  grid-column: 2;
  grid-row: 2;
  position: relative;
  font-size: 13px;
  font-weight: bold;
  color: var(--text-hi);
}
.ftr-y-tick {
  position: absolute;
  right: 4px;
  transform: translateY(-50%);
  white-space: nowrap;
  line-height: 1;
}
.ftr-y-axis-line {
  grid-column: 3;
  grid-row: 2;
  background: var(--text-hi);
  width: 4px;
  position: relative;
}
.ftr-y-tick-mark {
  position: absolute;
  right: -4px;
  width: 8px;
  height: 4px;
  background: var(--text-hi);
  transform: translateY(-50%);
}
.ftr-flag-row {
  grid-column: 4;
  grid-row: 1;
  display: grid;
  grid-template-columns: repeat(var(--ftr-n, 1), 1fr);
  column-gap: 3px;
  align-items: center;
  padding-left: 4px;
}
.ftr-flag-line {
  height: 4px;
  border-radius: 2px;
  align-self: center;
}
.ftr-flag-line.solid {
  background-color: currentColor;
}
.ftr-flag-line.dashed {
  background-image: linear-gradient(to right, currentColor 50%, transparent 50%);
  background-size: 12px 100%;
  background-repeat: repeat-x;
}
.ftr-bars {
  grid-column: 4;
  grid-row: 2;
  display: grid;
  grid-template-columns: repeat(var(--ftr-n, 1), 1fr);
  column-gap: 3px;
  align-items: end;
  border-bottom: 4px solid var(--text-hi);
  padding-left: 4px;
}
.ftr-bar {
  border: 1px solid var(--border-mid);
  border-radius: 3px 3px 0 0;
  min-height: 2px;
}
.ftr-x-row {
  grid-column: 4;
  grid-row: 3;
  display: grid;
  grid-template-columns: repeat(var(--ftr-n, 1), 1fr);
  column-gap: 3px;
  padding: 2px 0 0 4px;
}
.ftr-x-tick {
  font-size: 12px;
  font-weight: bold;
  color: var(--text-hi);
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================================================
   Popup / Overlay
   ============================================================ */

.popup-overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.popup {
  background: var(--card-bg);
  border-radius: 12px;
  max-width: 90vw;
  max-height: 85vh;
  overflow-y: auto;
  padding: 16px;
}

.popup h3 {
  color: var(--text-hi);
  margin-top: 0;
}

/* ============================================================
   Vote Result Popup (matching _VoteResultPopup)
   ============================================================ */

.vote-result-popup {
  width: 96%;
  max-width: 480px;
  max-height: 75vh;
  padding: 12px;
}

.vote-result-header {
  text-align: center;
  font-size: 21px;
  font-weight: bold;
  margin-bottom: 8px;
}

.vote-result-fishy {
  text-align: center;
  color: var(--text-attention);
  font-size: 16px;
  margin-bottom: 8px;
}

.vote-result-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.vote-result-col h4 {
  text-align: center;
  font-size: 16px;
  margin: 0 0 4px;
}

.vote-result-col .player-grid {
  grid-template-columns: repeat(2, 1fr);
  gap: 3px;
}

.vote-result-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 4px;
  max-height: 50vh;
  overflow-y: auto;
}

/* In the vote-result popup, let cards fill the grid cell instead of the
   default 72px so they read at a usable size. aspect-ratio keeps them
   square. */
.vote-result-grid .mini-card {
  width: 100%;
  flex-shrink: 1;
}

/* Vote-weight badge (Blorbek "Last Word" doubles an inactive vote). */
.mini-card-vote-weight {
  position: absolute;
  bottom: 2px;
  right: 4px;
  color: var(--status-yellow);
  font-size: 12px;
  font-weight: bold;
  z-index: 2;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
}

.vote-result-countdown {
  text-align: center;
  color: var(--text-lo);
  font-size: 14px;
  margin-top: 8px;
}

/* ============================================================
   Game Over (matching _GameOverPopup)
   ============================================================ */

.gameover-banner {
  text-align: center;
  margin-bottom: 16px;
}

.gameover-banner h1 {
  font-size: 28px;
  margin: 8px 0;
}

.gameover-winner {
  font-size: 21px;
  text-align: center;
  margin-bottom: 12px;
}

.gameover-section-header {
  font-size: 14px;
  margin: 8px 0 4px;
  padding-bottom: 2px;
  border-bottom: 1px solid var(--border-dark);
}

.game-over-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 8px;
  border-bottom: 1px solid var(--border-dark);
}

.game-over-card .player-name {
  flex: 1;
  font-size: 16px;
}

.role-badge    { font-size: 16px; }
.faction-label { font-size: 14px; }

/* ============================================================
   Text Input (matching style_input terminal scan-line look)
   ============================================================ */

input[type="text"] {
  background: var(--card-bg);
  color: var(--text-hi);
  border: 1px solid var(--border-mid);
  border-radius: 6px;
  padding: 10px;
  font-family: 'VT323', monospace;
  font-size: 21px;
  outline: none;
  width: 100%;
}

input[type="text"]::placeholder { color: var(--text-lo); }
input[type="text"]:focus        { border-color: var(--independence); }

/* ============================================================
   Divider (matching T["sep"] blue rule)
   ============================================================ */

.divider {
  border: none;
  border-top: 1px solid var(--sep);
  margin: 8px 0;
}

/* Popup divider (darker) */
.popup-divider {
  border: none;
  border-top: 1px solid var(--popup-divider);
  margin: 8px 0;
}

/* ============================================================
   Status Bar
   ============================================================ */

.status-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  padding: 6px;
  text-align: center;
  background: var(--btn-red);
  color: var(--text-hi);
  font-family: 'VT323', monospace;
  font-size: 16px;
  z-index: 200;
}

/* ============================================================
   Title & Subtitle (matching home.py title)
   ============================================================ */

.title {
  color: var(--text-hi);
  text-align: center;
  font-size: 34px;
  margin: 16px 0 4px;
  font-weight: bold;
}

.subtitle {
  color: var(--text-mid);
  text-align: center;
  font-size: 16px;
  margin: 0 0 16px;
}

/* ============================================================
   Headers & Labels
   ============================================================ */

.header {
  color: var(--text-mid);
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 8px;
}

.phase-label {
  text-align: center;
  color: var(--text-hi);
  font-size: 23px;
  font-weight: bold;
  padding: 2px 8px;
  margin: 0;
}

/* ============================================================
   Badges & Charge Pips
   ============================================================ */

.badge {
  display: inline-block;
  font-size: 12px;
  padding: 2px 6px;
  border-radius: 8px;
  color: var(--text-hi);
  vertical-align: middle;
}

.badge-green { background: var(--active); }
.badge-red   { background: var(--consortium); }

.charge-pips {
  color: var(--species-text);
  font-size: 12px;
  margin-left: 4px;
}

/* ============================================================
   Scrollbar
   ============================================================ */

::-webkit-scrollbar       { width: 4px; }
::-webkit-scrollbar-track { background: var(--screen-bg); }
::-webkit-scrollbar-thumb { background: var(--border-mid); border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-lo); }

/* ============================================================
   Animations
   ============================================================ */

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.fade-in { animation: fadeIn 0.3s ease-in; }

/* ============================================================
   Utility Classes
   ============================================================ */

/* ============================================================
   Rules Popup
   ============================================================ */

.rules-heading {
  color: var(--text-hi);
  font-size: 18px;
  font-weight: bold;
  margin: 8px 0 4px;
}

.rules-text {
  color: var(--text-mid);
  font-size: 15px;
  margin: 3px 0;
  line-height: 1.4;
}

.rules-section {
  color: var(--text-mid);
  font-size: 15px;
  line-height: 1.5;
}

.rules-phase-card {
  background: rgba(20, 22, 26, 1);
  border-radius: 6px;
  padding: 8px 10px;
  margin: 4px 0;
}

.rules-phase-title {
  color: var(--text-hi);
  font-size: 16px;
  font-weight: bold;
  margin: 0 0 2px;
}

.rules-phase-body {
  color: var(--text-mid);
  font-size: 15px;
  margin: 0;
  line-height: 1.4;
}

/* ============================================================
   Character Overview Popup
   ============================================================ */

/* File-folder style tabs: active tab merges with the content panel below;
   inactive tab sits "behind" the content baseline with the container's
   bottom border separating it. The two tabs together span 2/3 of the
   popup width; the PDF download button floats at the right end. */
.char-popup-tabs {
  display: flex;
  align-items: flex-end;
  border-bottom: 1px solid var(--btn-gray-border);
  margin-bottom: 0;
  gap: 4px;
}
.char-popup-tab {
  flex: 0 0 22%;
  padding: 7px 10px 6px;
  background: rgba(20, 22, 28, 0.55);
  border: 1px solid var(--btn-gray-border);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  color: var(--text-mid);
  font-family: 'VT323', monospace;
  font-size: 17px;
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  margin-bottom: -1px;     /* overlap container's bottom border */
  position: relative;
  transition: filter 0.1s, background 0.1s, color 0.1s;
}
.char-popup-tab:hover { filter: brightness(1.1); }
.char-popup-tab.active {
  background: var(--card-bg);
  color: var(--text-hi);
  border-bottom: 1px solid var(--card-bg);  /* visually erase container line under active tab */
  z-index: 1;
  font-weight: bold;
}
.char-popup-pdf-btn {
  margin-left: auto;
  margin-bottom: 4px;
  align-self: center;
  background: var(--btn-gray);
  border: 1.2px solid var(--btn-gray-border);
  border-radius: var(--card-radius);
  padding: 4px 10px;
  cursor: pointer;
  color: var(--text-hi);
  font-family: 'VT323', monospace;
  font-size: 13px;
  letter-spacing: 0.05em;
  transition: filter 0.1s;
}
.char-popup-pdf-btn:hover { filter: brightness(1.1); }
.char-popup-pdf-btn:active { filter: brightness(1.2); }
.char-popup-pdf-btn[hidden] { display: none; }

/* Tiers toggle (Cards tab) — shares .char-popup-pdf-btn styling but adds
   an `.active` pressed state when the tier grouping is enabled. Matches
   the filter-button highlight pattern: brighter text, accent border. */
.char-popup-tiers-btn.active {
  color: var(--text-attention);
  background: var(--card-bg);
  border-color: var(--btn-green-border);
  font-weight: bold;
}

/* Filter toggle bar — first row inside the content panel, above the
   role-section grid. Lets the user flip the popup between "ALL" and a
   curated scenario roster (e.g. "ONBOARDING") without leaving the Cards
   / Text tab. Hidden when the active scenario already locks the filter.
   Buttons match the PDF button's compact styling. */
.char-popup-filter-bar {
  display: flex;
  gap: 4px;
  margin: 0 0 8px;
}
.char-popup-filter-btn {
  background: var(--btn-gray);
  border: 1.2px solid var(--btn-gray-border);
  border-radius: var(--card-radius);
  padding: 4px 10px;
  cursor: pointer;
  color: var(--text-mid);
  font-family: 'VT323', monospace;
  font-size: 13px;
  letter-spacing: 0.05em;
  transition: filter 0.1s, color 0.1s, background 0.1s;
}
.char-popup-filter-btn:hover { filter: brightness(1.1); }
.char-popup-filter-btn.active {
  color: var(--text-hi);
  background: var(--card-bg);
  border-color: var(--btn-green-border);
}

/* Content panel below the tabs: subtle border so the tabs visually attach. */
.char-popup-content {
  border: 1px solid var(--btn-gray-border);
  border-top: none;
  border-radius: 0 0 8px 8px;
  padding: 10px 12px 6px;
  background: var(--card-bg);
  overflow-y: auto;
}

/* Info tab — scenario blurb. Larger font and looser line-height than the
   Text tab; left-aligned paragraph under a centred title. */
.char-popup-info-body {
  padding: 6px 4px 10px;
}
.char-popup-info-title {
  font-family: 'VT323', monospace;
  font-size: 22px;
  text-align: center;
  color: var(--text-hi);
  margin: 6px 0 14px;
}
.char-popup-info-blurb {
  font-size: 17px;
  line-height: 1.55;
  color: var(--text-mid);
  margin: 0 0 8px;
  padding: 0 4px;
}
.char-popup-info-blurb + .char-popup-info-blurb {
  margin-top: 10px;
}
.char-popup-info-for {
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-lo);
  font-style: italic;
  margin: 12px 0 0;
  padding: 0 4px;
}

/* Change log — collapsable section below the Info-tab body. One <details>
   per date, newest on top. Markup comes from buildInfoView →
   _renderChangeLogSection. */
.char-popup-changelog {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--btn-gray-border);
}
.char-popup-changelog-entry {
  margin-bottom: 6px;
}
.char-popup-changelog-entry summary {
  cursor: pointer;
  color: var(--text-hi);
  font-family: 'VT323', monospace;
  font-size: 16px;
  letter-spacing: 0.04em;
  padding: 6px 4px;
  border-radius: 4px;
  list-style: none;          /* hide default disclosure triangle */
}
.char-popup-changelog-entry summary::-webkit-details-marker {
  display: none;
}
.char-popup-changelog-entry summary::before {
  content: '▸ ';
  color: var(--text-mid);
  display: inline-block;
  transition: transform 0.15s ease-out;
}
.char-popup-changelog-entry[open] summary::before {
  content: '▾ ';
}
.char-popup-changelog-entry summary:hover {
  background: rgba(255, 255, 255, 0.04);
}
.char-popup-changelog-entry ul {
  margin: 6px 0 10px;
  padding-left: 22px;
  color: var(--text-mid);
  font-size: 14px;
  line-height: 1.5;
}
.char-popup-changelog-entry li {
  margin-bottom: 8px;
}

/* Text view (Blood-on-the-Clocktower-style compact list) */
.text-view-section {
  margin: 14px 0 4px;
}
.text-view-section-title {
  font-size: 19px;
  font-weight: bold;
  margin: 0 0 4px;
  padding-bottom: 2px;
  border-bottom: 1px solid rgba(120, 120, 120, 0.3);
}
.text-view-role {
  display: flex;
  gap: 8px;
  align-items: center;          /* icon vertically centered across all body lines */
  padding: 6px 0;
  border-bottom: 1px dotted rgba(120, 120, 120, 0.15);
}
.text-view-role:last-child { border-bottom: none; }
.text-view-role-icon {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  object-fit: contain;
}
.text-view-role-body {
  flex: 1;
  min-width: 0;
}
.text-view-role-name {
  font-weight: bold;
  font-size: 16px;
  line-height: 1.15;
  margin-bottom: 2px;
}
.text-view-ability {
  font-size: 13px;
  line-height: 1.25;
  color: var(--text-hi);
  margin: 1px 0;
}
/* Colour-coded ability rows — same scheme as the role-info popup. */
.text-view-ability-action  { color: var(--ability-active); }
.text-view-ability-passive { color: var(--ability-passive); }
.text-view-ability-icon {
  display: inline-block;
  width: 0.9em;
  margin-right: 2px;
  /* inherit colour from the ability row so action/effect rows colour the icon too */
  font-weight: bold;
}
.text-view-wrap {
  border-left: 2px solid rgba(120, 120, 180, 0.4);
  padding: 2px 0 2px 8px;
  margin: 4px 0;
  position: relative;
}
.text-view-wrap-label {
  font-size: 11px;
  font-style: italic;
  color: var(--text-mid);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 2px;
}

.overview-section-header {
  font-size: 23px;
  font-weight: bold;
  margin: 10px 0 4px;
  padding-bottom: 2px;
}

.overview-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}

.overview-card {
  background: var(--role-card-bg);
  border-radius: var(--card-radius);
  border: 1px solid rgba(77, 79, 87, 0.3);
  aspect-ratio: 1/1;
  /* Absolutely-positioned children — image + name are pinned to specific
     percentage offsets from the card edges. Centring math is then
     unambiguous on every browser. The name's vertical *centre* is at
     `top: 82 %`, anchored via `translateY(-50%)`, so 1-line and 2-line
     names share exactly the same y-position. `container-type: inline-size`
     enables `cqw` units so the name font scales with the card width — same
     visual proportions on iPhone, Android, and desktop regardless of grid
     context (All-Characters overview vs role-popup mini-cards). */
  position: relative;
  cursor: pointer;
  overflow: hidden;
  container-type: inline-size;
}

.overview-card:active { filter: brightness(1.2); }

.overview-card-img {
  position: absolute;
  top: 2%;
  left: 1%;
  width: 98%;
  height: 62%;
  object-fit: contain;
  border-radius: 4px;
}

.overview-card-name {
  position: absolute;
  top: 82%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  /* Card-relative sizing: 17% of card width. On iPhone SE (~83px card)
     this is ~14.1px; on a 200px desktop card ~34px — same look everywhere.
     `white-space: pre` honors hard line breaks (\n) injected by the JS
     ROLE_CARD_NAME map and disables soft-wrapping, so names like
     "Broadcaster" can never be split mid-word the way `word-break:
     break-word` did on iOS. line-height 1.0 keeps 2-line names clear of
     both the image above and the card edge below. */
  font-size: 17cqw;
  font-weight: bold;
  color: var(--role-card-name);
  text-align: center;
  white-space: pre;
  overflow: hidden;
  line-height: 1.0;
  padding: 0 2px;
}

.overview-species-card {
  background: var(--species-bg);
  border-color: rgba(140, 140, 145, 0.4);
}

/* Species portraits — pulled in slightly on the sides so the round
   portrait reads as "subject of a portrait", but allowed to fill more of
   the image area than before. */
.overview-species-card .overview-card-img {
  top: 11%;
  left: 8%;
  width: 84%;
  height: 56%;
}

.overview-card.ban-selected {
  background: var(--btn-green);
  border: 2px solid var(--btn-green-border);
}

/* ============================================================
   Utility Classes
   ============================================================ */

.text-hi      { color: var(--text-hi); }
.text-mid     { color: var(--text-mid); }
.text-lo      { color: var(--text-lo); }
.text-species { color: var(--species-text); }
.text-red     { color: var(--consortium); }
.text-green   { color: var(--active); }
.text-gold    { color: var(--text-attention); }

/* Game-log role brackets. `[<Role Name>]` rendered after actor + inline via
   `{role:<tag>}` markers in detail text (see app.js _formatRoleBracket). */
.log-role-indi  { color: var(--independence); font-weight: bold; }
.log-role-conso { color: var(--consortium); font-weight: bold; }

/* DEV strip — host-only, surfaced when `dev_mode_host === true`. Pinned
   to the very top of the viewport, thin, low-key, always available
   regardless of which screen is active. Currently exposes one button
   ("Live Log"); will grow with more dev affordances over time. */
#dev-strip {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  display: none;          /* JS toggles to flex when dev_mode_host */
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  background: rgba(0, 0, 0, 0.85);
  border-bottom: 1px solid var(--border-mid);
  font-family: monospace;
  pointer-events: auto;
}
#dev-strip .dev-tag {
  background: #ffaa00;
  color: black;
  font-weight: bold;
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 3px;
  letter-spacing: 1px;
}
#dev-strip .dev-btn {
  background: #444;
  color: var(--text-hi);
  border: 1px solid var(--border-mid);
  padding: 3px 10px;
  border-radius: 3px;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}
#dev-strip .dev-btn:hover { background: #555; }
.text-center  { text-align: center; }
.flex-row     { display: flex; flex-direction: row; }
.flex-col     { display: flex; flex-direction: column; }
.gap-4        { gap: 4px; }
.gap-8        { gap: 8px; }
.gap-12       { gap: 12px; }
.mt-4         { margin-top: 4px; }
.mt-8         { margin-top: 8px; }
.mt-16        { margin-top: 16px; }
.mb-8         { margin-bottom: 8px; }
.w-full       { width: 100%; }

/* ─────────────────────── Archive viewer ─────────────────────── */
/* Read-only post-game log + stats. Triggered via the magic-combo
 * backdoor on the Join screen. Layout adapts to phone/tablet:
 * a vertical stack on narrow screens, a side-by-side list+detail
 * split when there's room. */

#view-archive {
  align-items: stretch;
  justify-content: flex-start;
  padding: 12px;
  overflow-y: auto;
}

#archive-content {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.archive-title {
  margin: 0 0 4px 0;
  color: var(--text-hi);
  font-size: 1.3em;
  text-align: center;
}

.archive-tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}
.archive-tab {
  flex: 1 1 80px;
  padding: 8px 12px;
  background: rgba(40, 40, 40, 0.6);
  color: var(--text-mid);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  font-size: 1em;
}
.archive-tab.active {
  background: rgba(70, 100, 70, 0.5);
  color: var(--text-hi);
  border-color: var(--active);
}
.archive-exit {
  margin-left: auto;
  flex: 0 0 auto;
  padding: 6px 14px;
}

.archive-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.archive-loading,
.archive-empty,
.archive-detail-empty {
  padding: 24px 12px;
  text-align: center;
  color: var(--text-lo);
}

.archive-error {
  padding: 10px;
  background: rgba(180, 50, 50, 0.2);
  border: 1px solid rgba(229, 57, 53, 0.5);
  color: #f8d4d2;
  border-radius: 4px;
}

.archive-split {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
@media (min-width: 720px) {
  .archive-split {
    flex-direction: row;
    align-items: stretch;
  }
  .archive-list  { flex: 0 0 38%; max-height: 70vh; overflow-y: auto; }
  .archive-detail { flex: 1 1 auto; max-height: 70vh; overflow-y: auto; }
}

.archive-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.archive-row {
  padding: 8px 10px;
  background: rgba(30, 30, 30, 0.6);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}
.archive-row:hover { background: rgba(50, 50, 50, 0.7); }
.archive-row.open  {
  background: rgba(60, 80, 60, 0.5);
  border-color: var(--active);
}

.archive-row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.archive-row-id {
  font-family: monospace;
  color: var(--text-hi);
  font-size: 1.05em;
}
.archive-row-winner {
  font-size: 0.8em;
  padding: 2px 6px;
  border-radius: 3px;
  text-transform: uppercase;
}
.archive-winner-independence { background: rgba(76, 175, 80, 0.3); color: #b6e7b8; }
.archive-winner-consortium   { background: rgba(229, 57, 53, 0.3); color: #f6c0bf; }
.archive-winner-hive         { background: rgba(255, 179, 0, 0.3); color: #ffe5a3; }
.archive-winner-none         { background: rgba(120, 120, 120, 0.3); color: #ccc; }

.archive-row-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 0.85em;
  color: var(--text-mid);
  margin-top: 4px;
}

.archive-detail {
  background: rgba(20, 20, 20, 0.5);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 4px;
  padding: 12px;
}
.archive-detail-head h3 {
  margin: 0 0 4px 0;
  font-size: 1.15em;
  color: var(--text-hi);
}
.archive-detail-meta {
  font-size: 0.85em;
  color: var(--text-mid);
}

.archive-section-h {
  margin: 14px 0 6px 0;
  color: var(--text-hi);
  font-size: 1em;
  border-bottom: 1px solid rgba(255,255,255,0.1);
  padding-bottom: 3px;
}

.archive-players {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.archive-player-row {
  display: grid;
  grid-template-columns: 90px 1fr 80px 80px 70px;
  gap: 6px;
  font-size: 0.85em;
  padding: 4px 6px;
  background: rgba(40, 40, 40, 0.4);
  border-radius: 3px;
}
.archive-player-alias  { color: var(--text-hi); font-family: monospace; }
.archive-player-role   { color: var(--text-mid); }
.archive-faction-independence { color: #8ddc90; }
.archive-faction-consortium   { color: #f08c89; }
.archive-faction-hive         { color: #ffd680; }
.archive-faction-none         { color: var(--text-lo); }
.archive-player-species { color: var(--species-text); }
.archive-status-alive    { color: #8ddc90; }
.archive-status-uploaded { color: var(--text-attention); }
.archive-status-revoked  { color: #f08c89; }
.archive-status-down     { color: var(--text-lo); }

.archive-log {
  max-height: 360px;
  overflow-y: auto;
  font-family: monospace;
  font-size: 0.82em;
  background: rgba(0,0,0,0.3);
  padding: 8px;
  border-radius: 3px;
}
.archive-log-line {
  padding: 2px 0;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  color: var(--text-mid);
}
.archive-log-line:last-child { border-bottom: none; }
.archive-log-phase { color: var(--text-lo); }
.archive-log-line strong { color: var(--text-hi); }

.archive-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
}
.archive-stats-card {
  background: rgba(30, 30, 30, 0.6);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 4px;
  padding: 10px 12px;
}
.archive-stats-num {
  font-size: 2em;
  color: var(--text-hi);
  text-align: center;
  font-weight: bold;
}
.archive-stats-label {
  text-align: center;
  color: var(--text-mid);
  font-size: 0.85em;
}
.archive-stats-row {
  display: flex;
  justify-content: space-between;
  padding: 3px 0;
  font-size: 0.9em;
  color: var(--text-mid);
}
.archive-stats-row span:first-child { color: var(--text-mid); }
.archive-stats-row span:last-child { color: var(--text-hi); font-family: monospace; }
.archive-stats-row small { color: var(--text-lo); }

.archive-chart-wrap {
  position: relative;
  width: 100%;
  height: 260px;
  background: rgba(20, 20, 20, 0.4);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 4px;
  padding: 8px;
}
/* Taller wrap for charts that need vertical room for many labels — used
   by the top-20 role-appearances horizontal bar chart so all 20 y-labels
   fit without Chart.js auto-skipping every other one. */
.archive-chart-wrap-tall {
  height: 440px;
}
.archive-chart-wrap canvas {
  max-height: 100%;
  width: 100% !important;
}

/* ── BC lock swiper ────────────────────────────────────────────────
   In-frame swiper appears in the centre frame during Broadcast when no
   other slot content (pn / vote / balance / broadcast / applicant /
   selected player) is rendered. Lock overlay is body-level fullscreen.
   See BcLockSystem in app.js for show/hide gating. */
.bc-swiper {
  position: relative;
  width: 50%;
  height: 80%;
  touch-action: none;
}
.bc-swiper .bc-track {
  position: absolute; inset: 0;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: inset 0 0 0 0.18rem rgba(255, 255, 255, 0.18),
              0 0.4rem 0.9rem rgba(0,0,0,0.45);
  overflow: hidden;
}
.bc-swiper .bc-handle {
  position: absolute;
  left: 0.35rem; right: 0.35rem;
  top: 0.35rem;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ffffff 0%, #d8dde3 50%, #9ea4ad 100%);
  box-shadow: 0 0.3rem 0.7rem rgba(0,0,0,0.55),
              inset 0 0.1rem 0.2rem rgba(255,255,255,0.6);
  transform: translateY(var(--bc-off, 0px));
  touch-action: none;
  cursor: grab;
  display: grid; place-items: center;
  overflow: hidden;
}
.bc-swiper.bc-unlock .bc-handle { top: auto; bottom: 0.35rem; }
.bc-swiper .bc-handle img {
  width: 60%; height: auto;
  transform: translateY(-10%);
  pointer-events: none;
  filter: drop-shadow(0 0.1rem 0.2rem rgba(0,0,0,0.35));
}
.bc-swiper.bc-flash .bc-handle {
  background: radial-gradient(circle at 50% 50%, #ffffff 0%, #f4f6f9 60%, #c8cdd3 100%);
  box-shadow: 0 0 0 0.18rem rgba(255,255,255,0.55),
              0 0 1.2rem rgba(255,255,255,0.65);
}
.bc-swiper.bc-dragging .bc-handle { cursor: grabbing; transition: none; }
.bc-swiper:not(.bc-dragging) .bc-handle {
  transition: transform 0.25s cubic-bezier(.25,.9,.3,1);
}

/* Swiper wrap: centres the .bc-swiper inside whatever parent it lives in.
   Two flavours:
   • bc-fixed   — body-level fixed; sized via JS to overlay the centre frame
                  during BC. Lives outside #broadcast-content so innerHTML
                  re-renders never touch it (drag state survives state pushes).
   • inside .bc-unlock-wrap — fills the unlock area on the lock overlay. */
.bc-swiper-wrap {
  display: grid; place-items: center;
}
.bc-swiper-wrap.bc-fixed {
  position: fixed;
  z-index: 100;
}
.bc-swiper-wrap.bc-fixed.bc-hidden {
  display: none;
}
.bc-unlock-wrap > .bc-swiper-wrap {
  position: absolute; inset: 0;
}

/* Full-screen lock overlay (matrix rain + unlock swiper) */
.bc-lock-overlay {
  position: fixed; inset: 0; z-index: 9999;
  background: #000;
  display: none; place-items: center;
  touch-action: none;
}
.bc-lock-overlay.active { display: grid; }
.bc-lock-overlay canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* Position + size set inline in JS at lock time to mirror the lock pill's
   screen rect, so the unlock pill appears at the exact same place + size. */
.bc-lock-overlay .bc-unlock-wrap {
  position: absolute; z-index: 1;
  display: grid; place-items: center;
  pointer-events: auto;
}
.bc-lock-overlay .bc-flash-veil {
  position: absolute; inset: 0;
  background: rgba(217, 51, 51, 0);
  pointer-events: none;
  transition: background 0.12s;
  z-index: 2;
}
.bc-lock-overlay.bc-vote-flash .bc-flash-veil {
  background: rgba(217, 51, 51, 0.55);
}


/* ====================================================================
   Uplink Browse-gate minigames. Players whose Uplink mode is `browse`
   (inactive, hacked, role with no Uplink action, etc.) must finish a
   short puzzle before the Browse button unlocks. Per-game speed
   records honoured on the game-over screen. Lifted from the standalone
   prototype at web/minigame.html — keep these in sync if you tweak.
   ==================================================================== */
:root {
  --mg-tile-bg:          #181c22;
  --mg-tile-border:      #2e333b;
  --mg-tile-active:      rgba(64, 191, 89, 0.95);
  --mg-tile-hit:         rgba(64, 191, 89, 0.35);
  --mg-tile-miss:        rgba(217, 51, 51, 0.55);
  --mg-progress-fill:    rgba(64, 191, 89, 0.85);
  --mg-match-glow:       rgba(64, 191, 89, 0.55);
}

.mg-host {
  width: 66.7%;
  aspect-ratio: 1 / 1;
  max-width: 66.7%;
  max-height: 40vh;
  margin: 8px auto;
  display: flex; align-items: center; justify-content: center;
  padding: 0.4rem;
  box-sizing: border-box;
}
.mg-instruction {
  text-align: center;
  color: var(--text-hi);
  font-size: 16px;
  margin: 6px 0 4px;
  min-height: 20px;
}
.mg-progress-wrap {
  height: 4px;
  background: rgba(255,255,255,0.06);
  border-top: 1px solid #1d2127;
  overflow: hidden;
  margin: 4px 0 6px;
}
.mg-progress-fill {
  height: 100%; width: 0%;
  background: var(--mg-progress-fill);
  transition: width 0.18s ease-out;
}

/* Decode — 4x4 tap-sequence grid */
.decode-grid {
  aspect-ratio: 1 / 1;
  height: 100%;
  max-width: 100%; max-height: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 0.35rem;
}
.decode-tile {
  background: var(--mg-tile-bg);
  border: 1px solid var(--mg-tile-border);
  border-radius: 0.45rem;
  display: grid; place-items: center;
  color: var(--text-dim);
  font-family: 'Consolas', 'Menlo', monospace;
  font-size: 1.05rem;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.08s;
}
.decode-tile.active {
  background: var(--mg-tile-active);
  border-color: #7be09a;
  color: #04130a;
  transform: scale(1.04);
}
.decode-tile.hit  { background: var(--mg-tile-hit);  border-color: var(--btn-green-border); }
.decode-tile.miss { background: var(--mg-tile-miss); border-color: #b73a3a; }
.decode-tile.locked { pointer-events: none; opacity: 0.65; }

/* Memory — 3x3 flip-match grid */
.memory-grid {
  aspect-ratio: 1 / 1;
  height: 100%;
  max-width: 100%; max-height: 100%;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 0.45rem;
}
.mg-card {
  position: relative;
  width: 100%; height: 100%;
  aspect-ratio: 1 / 1;
  perspective: 700px;
  cursor: pointer;
}
.mg-card-inner {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.45s cubic-bezier(.4, .8, .3, 1);
}
.mg-card.flipped .mg-card-inner { transform: rotateY(180deg); }
.mg-card-face, .mg-card-back {
  position: absolute; inset: 0;
  border: 1px solid var(--mg-tile-border);
  border-radius: 0.5rem;
  background: var(--mg-tile-bg);
  overflow: hidden;
  display: grid; place-items: center;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.mg-card-back {
  background:
    radial-gradient(circle at 30% 25%, rgba(64,191,89,0.06) 0%, rgba(0,0,0,0) 55%),
    linear-gradient(135deg, #181c22 0%, #0e1115 100%);
  color: rgba(64, 191, 89, 0.28);
  font-family: 'Consolas', 'Menlo', monospace;
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0.12em;
}
.mg-card-face { transform: rotateY(180deg); background: #0c0f12; }
.mg-card-face img {
  width: 70%; height: 70%; object-fit: contain; display: block;
}
.mg-card-face .mg-name,
.imp-card .mg-name {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: linear-gradient(0deg, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0) 100%);
  color: var(--text-hi);
  text-align: center; line-height: 1.1;
}
.mg-card-face .mg-name {
  font-size: 0.7rem; letter-spacing: 0.05em;
  padding: 0.45rem 0.2rem 0.2rem;
}
.imp-card .mg-name {
  font-size: 0.65rem; letter-spacing: 0.04em;
  padding: 0.35rem 0.15rem 0.15rem;
}
.mg-card.matched .mg-card-face {
  border-color: var(--btn-green-border);
  box-shadow: 0 0 0 1px var(--mg-match-glow), 0 0 0.5rem var(--mg-match-glow);
}
.mg-card.miss .mg-card-face {
  border-color: #b73a3a;
  box-shadow: 0 0 0 1px rgba(217, 51, 51, 0.55);
}
.mg-card.locked { pointer-events: none; }

/* Impostor — 3x3 face-up role grid (8 unique + 1 duplicate = 9 cards) */
.impostor-grid {
  aspect-ratio: 1 / 1;
  height: 100%;
  max-width: 100%; max-height: 100%;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 0.3rem;
}
.imp-card {
  position: relative;
  width: 100%; height: 100%;
  aspect-ratio: 1 / 1;
  background: #0c0f12;
  border: 1px solid var(--mg-tile-border);
  border-radius: 0.45rem;
  overflow: hidden;
  cursor: pointer;
  display: grid; place-items: center;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.imp-card img {
  width: 70%; height: 70%; object-fit: contain; display: block;
}
.imp-card.miss {
  border-color: #b73a3a;
  box-shadow: 0 0 0 1px rgba(217, 51, 51, 0.55);
}
.imp-card.hit {
  border-color: var(--btn-green-border);
  box-shadow: 0 0 0 1px var(--mg-match-glow), 0 0 0.5rem var(--mg-match-glow);
}
.imp-card.locked { pointer-events: none; }

/* Trace — 4x4 hidden-path grid, two rounds */
.trace-grid {
  aspect-ratio: 1 / 1;
  height: 100%;
  max-width: 100%; max-height: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  gap: 0.3rem;
}
.trace-cell {
  aspect-ratio: 1 / 1;
  background: var(--mg-tile-bg);
  border: 1px solid var(--mg-tile-border);
  border-radius: 0.45rem;
  cursor: pointer;
  position: relative;
  transition: background 0.12s, border-color 0.12s;
}
.trace-cell::after {
  content: '';
  position: absolute; top: 50%; left: 50%;
  width: 0.35rem; height: 0.35rem;
  margin: -0.175rem 0 0 -0.175rem;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
}
.trace-cell.source    { background: rgba(64, 191, 89, 0.40); border-color: var(--btn-green-border); }
.trace-cell.source::after    { background: var(--btn-green-border); }
.trace-cell.dest      { background: rgba(217, 51, 51, 0.25); border-color: #b73a3a; }
.trace-cell.dest::after      { background: #d54d46; }
.trace-cell.confirmed { background: rgba(64, 191, 89, 0.32); border-color: var(--btn-green-border); }
.trace-cell.confirmed::after { background: var(--btn-green-border); }
.trace-cell.miss      { background: var(--mg-tile-miss); border-color: #b73a3a; }
.trace-cell.locked    { pointer-events: none; }

/* Fastest-minigames panel on the game-over screen. */
.gameover-minigame-records {
  margin-top: 16px;
  padding: 10px 12px;
  background: var(--card-bg);
  border: 1px solid var(--btn-gray-border);
  border-radius: 8px;
  color: var(--text-mid);
  font-size: 15px;
  line-height: 1.5;
}
.gameover-minigame-records h4 {
  margin: 0 0 6px;
  color: var(--text-hi);
  font-size: 16px;
  letter-spacing: 0.04em;
}
.gameover-minigame-records .mg-record-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
}
.gameover-minigame-records .mg-record-row .mg-name-cell {
  color: var(--text-hi);
}
.gameover-minigame-records .mg-record-row .mg-time-cell {
  color: var(--ability-passive);
  font-family: 'VT323', monospace;
  font-size: 14px;
}
