/* pvpTD — Screen Layouts */

/* Screen base */
.screen {
  position: absolute;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow-x: hidden;
  overflow-y: auto;
  /* Job 025: every screen gets the lobby's atmospheric backdrop instead of a
     flat black void. Heavy dark scrim keeps floating content legible;
     .lobby-screen overrides with a lighter scrim as the hero screen, and
     leaderboard/shop override with their own scene art below. */
  background:
    linear-gradient(180deg, rgba(8, 5, 18, 0.90) 0%, rgba(6, 4, 14, 0.85) 45%, rgba(4, 3, 10, 0.95) 100%),
    url('../assets/maps/map_default_bg.png') center 30% / cover no-repeat,
    var(--color-bg-dark);
  transition: opacity var(--transition-slow);
  pointer-events: auto;
}
.screen.hidden {
  display: none;
}
.screen.fade-out {
  opacity: 0;
  pointer-events: none;
}

.screen-content {
  max-width: 600px;
  width: 90%;
  padding: 24px 24px;
  margin: auto;
  text-align: center;
}

.screen-title {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: bold;
  color: var(--color-secondary);
  margin-bottom: 6px;
  text-shadow: 0 0 20px rgba(240, 192, 64, 0.2);
}

.screen-subtitle {
  font-size: 13px;
  color: var(--color-text-dim);
  margin-bottom: 16px;
}

/* Lobby screen — an atmospheric battlefield behind the menu, not empty dark */
.lobby-screen {
  background:
    radial-gradient(ellipse at 50% 22%, rgba(108, 75, 184, 0.18), transparent 55%),
    linear-gradient(180deg, rgba(10, 7, 22, 0.78) 0%, rgba(8, 5, 18, 0.62) 45%, rgba(6, 4, 14, 0.92) 100%),
    url('../assets/maps/map_default_bg.png') center 30% / cover no-repeat,
    var(--color-bg-dark);
}
/* Vignette to seat the panel in the scene */
.lobby-screen::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 42%, transparent 34%, rgba(4, 3, 10, 0.72) 100%);
}

.lobby-ready-btn {
  margin-top: 14px;
  padding: 10px 40px;
  font-size: 16px;
  font-weight: bold;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-primary);
  color: var(--color-text);
  cursor: pointer;
  transition: all var(--transition-fast);
}
.lobby-ready-btn:hover:not(:disabled) {
  border-color: var(--color-accent);
  box-shadow: 0 0 16px rgba(64, 240, 112, 0.3);
  transform: translateY(-2px);
}
.lobby-ready-btn.is-ready {
  background: var(--color-accent);
  color: #000;
  border-color: var(--color-accent);
}
.lobby-ready-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Results screen */
.results-screen {
  background: radial-gradient(ellipse at center, var(--color-bg-medium), var(--color-bg-dark));
}

.results-winner {
  font-family: var(--font-display);
  font-size: 42px;
  font-weight: bold;
  margin-bottom: 24px;
}
.results-winner.defender { color: var(--color-defender); }
.results-winner.attacker { color: var(--color-attacker); }

.results-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 24px;
  text-align: left;
}
.results-stat {
  display: flex;
  justify-content: space-between;
  padding: 6px 12px;
  background: var(--color-bg-panel);
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.results-stat .label {
  color: var(--color-text-dim);
}
.results-stat .value {
  font-weight: bold;
  color: var(--color-text);
}

/* Job 061: on a 360x690 phone the stat grid pushed Play Again and Back to
   Lobby past the bottom edge — the two actions the screen exists for were
   half-cut and needed a scroll to find. They now stick to the bottom of the
   screen no matter how tall the stats get. */
.results-actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 12px 0 max(12px, env(safe-area-inset-bottom));
  background: linear-gradient(180deg, rgba(20, 14, 34, 0), rgba(20, 14, 34, 0.92) 28%);
}
.results-actions .btn { min-height: 44px; }

/* Settings screen */
.settings-screen .screen-content {
  max-width: 480px;
}

.settings-group {
  margin-bottom: 20px;
  text-align: left;
  /* Generated 9-slice frame (Phase C): parchment-gold, dark center */
  border: 14px solid transparent;
  border-image: url('../assets/ui/panels/panel_ornate.png') 56 fill / 14px stretch;
  padding: 8px 12px 12px;
}
.settings-group-title {
  font-size: 14px;
  font-weight: bold;
  color: var(--color-secondary);
  margin-bottom: 10px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--color-border);
}

.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 0;
}
.settings-row .label {
  font-size: 13px;
  color: var(--color-text);
}

/* Range slider */
.slider-control {
  display: flex;
  align-items: center;
  gap: 8px;
}
.slider-control input[type="range"] {
  width: 120px;
  height: 6px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--color-border);
  border-radius: 3px;
  outline: none;
}
.slider-control input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-secondary);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}
.slider-control .slider-value {
  min-width: 36px;
  text-align: right;
  font-size: 12px;
  color: var(--color-text-dim);
}

/* Select dropdown */
.select-control {
  padding: 4px 8px;
  background: var(--color-bg-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-size: 12px;
  cursor: pointer;
}

/* Toggle switch */
.toggle {
  position: relative;
  width: 40px;
  height: 22px;
  background: var(--color-border);
  border-radius: 11px;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.toggle.on {
  background: var(--color-accent);
}
.toggle .toggle-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--transition-fast);
}
.toggle.on .toggle-knob {
  transform: translateX(18px);
}

/* Profile screen */
.profile-screen .screen-content {
  max-width: 500px;
  text-align: left;
}
.profile-header {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}
.profile-avatar {
  width: 64px;
  height: 64px;
  flex: 0 0 64px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 34%, #3a2f57, var(--color-primary) 78%);
  border: 3px solid var(--color-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-secondary);
  overflow: hidden;
}
.profile-avatar svg {
  width: 40px;
  height: 40px;
  display: block;
}
.profile-info .player-name {
  font-size: 20px;
  font-weight: bold;
  color: var(--color-text);
}
.profile-info .player-rank {
  font-size: 13px;
  color: var(--color-secondary);
}
.profile-stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 20px;
}
.profile-stat-card {
  padding: 12px;
  background: var(--color-bg-panel);
  border-radius: var(--radius-md);
  text-align: center;
}
.profile-stat-card .stat-value {
  font-size: 22px;
  font-weight: bold;
  color: var(--color-secondary);
}
.profile-stat-card .stat-label {
  font-size: 11px;
  color: var(--color-text-dim);
  margin-top: 2px;
}

/* Leaderboard */
.leaderboard-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.leaderboard-table th {
  padding: 8px 12px;
  text-align: left;
  color: var(--color-text-dim);
  border-bottom: 1px solid var(--color-border);
  font-weight: 600;
}
.leaderboard-table td {
  padding: 8px 12px;
  border-bottom: 1px solid rgba(58, 46, 96, 0.4);
}
.leaderboard-table tr:hover td {
  background: rgba(30, 20, 64, 0.5);
}
.leaderboard-rank { font-weight: bold; color: var(--color-secondary); width: 40px; }
.leaderboard-rank.gold { color: #f0c040; }
.leaderboard-rank.silver { color: #c0c0c0; }
.leaderboard-rank.bronze { color: #cd7f32; }

/* Lobby diorama canvas (Phase 1): the menu shows the game.
   (.screen already positions the lobby absolutely — don't override it.) */
.lobby-screen { overflow: hidden; }
.lobby-screen .lobby-diorama {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0.55;
}
/* Job 082 — Dima, near-square unfolded foldable: "I don't see the bottom of the
   main widget... it should be aligned within active window, not just entire
   screen." The .screen base scrolls; .lobby-screen above turns that off for the
   diorama's sake, so the lobby was the ONE screen where content past the window
   bottom was simply unreachable — measured 45px lost at 800x800 and 42px at
   920x767, with no way to scroll to it.
   The scroll therefore lives on the INNER content box, not on .lobby-screen: an
   outer scroller would drag the diorama canvas and the ::after vignette (both
   inset:0 children) up the page with the menu. max-height resolves against the
   flex container, whose height is definite (inset:0 = the active window, which
   on mobile tracks the URL bar — that is the "active window" Dima means).
   When it fits, the flex `margin: auto` still centres it; when it doesn't, the
   flex spec collapses those auto margins to 0 and it top-aligns, so the top is
   never the unreachable end. */
.lobby-screen .screen-content {
  position: relative;
  z-index: 1;
  max-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* The one big PLAY button (doc 08 Phase 1) */
.play-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: 18px auto 6px;
  padding: 16px 64px;
  border-radius: var(--radius-lg);
  border: 2px solid var(--color-secondary);
  background: linear-gradient(180deg, #6c4bb8, #4a2f8a);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 6px 22px rgba(108, 75, 184, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.play-cta:hover {
  transform: translateY(-2px) scale(1.02);
  box-shadow: 0 10px 28px rgba(108, 75, 184, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.play-cta:active { transform: translateY(0) scale(0.99); }
.play-cta .play-cta-label {
  font-family: var(--font-display, inherit);
  font-size: 26px;
  font-weight: 800;
  letter-spacing: 2px;
}
.play-cta .play-cta-sub {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.75);
}

/* Daily quest chips under PLAY (Phase 3) */
.lobby-quests {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 8px;
}
.lobby-quests:empty { display: none; }
.lobby-quest-chip {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 150px;
  padding: 7px 12px 10px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.05);
  color: var(--color-text-muted);
  font-size: 11px;
  cursor: default;
  overflow: hidden;
}
.lobby-quest-chip .lobby-quest-desc { color: var(--color-text); font-size: 11px; }
.lobby-quest-chip .lobby-quest-state { font-weight: 700; font-size: 10px; }
.lobby-quest-chip .lobby-quest-bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: rgba(0, 0, 0, 0.4);
}
.lobby-quest-chip .lobby-quest-bar > span {
  display: block;
  height: 100%;
  background: var(--color-warning, #f0c040);
}
.lobby-quest-chip.claimable {
  border-color: var(--color-warning, #f0c040);
  cursor: pointer;
  animation: quest-glow 1.6s ease-in-out infinite;
}
.lobby-quest-chip.claimable .lobby-quest-state { color: var(--color-warning, #f0c040); }
.lobby-quest-chip.claimed { opacity: 0.5; }
@keyframes quest-glow {
  0%, 100% { box-shadow: 0 0 4px rgba(240, 192, 64, 0.25); }
  50% { box-shadow: 0 0 12px rgba(240, 192, 64, 0.55); }
}

/* Defense of the Day + breach news (Phase 4) */
.lobby-dotd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  max-width: 520px;
  margin: 10px auto 0;
  padding: 10px 14px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-attacker);
  background: rgba(233, 69, 96, 0.07);
  text-align: left;
}
.lobby-dotd .dotd-title { font-size: 13px; color: var(--color-text); }
.lobby-dotd .dotd-meta { font-size: 11px; color: var(--color-text-muted); margin-top: 2px; }
.lobby-news {
  max-width: 520px;
  margin: 8px auto 0;
  padding: 8px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-warning, #f0c040);
  background: rgba(240, 192, 64, 0.08);
  color: var(--color-warning, #f0c040);
  font-size: 12px;
  font-weight: 600;
}

.lobby-section-title {
  margin: 22px 0 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Tutorial overlay */
.tutorial-overlay {
  position: absolute;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: none;
  padding-bottom: 80px;
  /* The overlay itself must never block the game — the tutorial asks the
     player to click the canvas. Only the instruction box is interactive. */
  pointer-events: none;
}
.tutorial-overlay .tutorial-box {
  pointer-events: auto;
}
.tutorial-overlay.hidden { display: none; }

.tutorial-box {
  max-width: 400px;
  padding: 16px 20px;
  background: var(--color-bg-panel);
  border: 2px solid var(--color-secondary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.tutorial-box .tutorial-text {
  font-size: 14px;
  color: var(--color-text);
  margin-bottom: 12px;
  line-height: 1.5;
}
.tutorial-box .tutorial-actions {
  display: flex;
  justify-content: space-between;
}
.tutorial-step-indicator {
  font-size: 11px;
  color: var(--color-text-muted);
}

/* Queue screen */
.queue-screen .screen-content {
  max-width: 400px;
}
.queue-spinner {
  width: 48px;
  height: 48px;
  border: 4px solid var(--color-border);
  border-top-color: var(--color-secondary);
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin: 0 auto 20px;
}
.queue-timer {
  font-size: 24px;
  font-weight: bold;
  color: var(--color-text);
  margin-bottom: 8px;
}
.queue-status {
  font-size: 13px;
  color: var(--color-text-dim);
  margin-bottom: 20px;
}

/* Friends screen */
.friends-list {
  list-style: none;
}
.friend-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(58, 46, 96, 0.4);
}
.friend-item .friend-status {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-border);
}
.friend-item .friend-status.online {
  background: var(--color-accent);
}
.friend-item .friend-name {
  flex: 1;
  font-size: 13px;
}
.friend-item .friend-actions {
  display: flex;
  gap: 4px;
}

/* Shop screen */
.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}
/* Same defect, same fix as .skin-card (job 085): the price row must sit on the
   card's bottom edge or a row of cards prices itself at four different heights.
   Recorded in 085's log as an extension past its stated scope — the shop wasn't
   in Dima's complaint, but it is character-for-character the same bug. */
.shop-item {
  display: flex;
  flex-direction: column;
  padding: 12px;
  background: var(--color-bg-panel);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  text-align: center;
  cursor: pointer;
  transition: all var(--transition-fast);
}
.shop-item:hover {
  border-color: var(--color-secondary);
  transform: translateY(-2px);
  box-shadow: var(--shadow-glow-gold);
}
.shop-item .item-preview {
  width: 64px;
  height: 64px;
  margin: 0 auto 8px;
  background: var(--color-bg-medium);
  border-radius: var(--radius-md);
}
.shop-item .item-name {
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 4px;
}
.shop-item .item-price {
  font-size: 11px;
  color: var(--color-gold);
}
/* Locked preview cards (job 030) — a real shelf: the cosmetic art, its name,
   and how it's earned, with a lock badge. Not a purchase surface. */
.shop-item.locked { cursor: default; }
.shop-item.locked:hover { transform: none; border-color: var(--color-border); }
.shop-item .item-preview {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.shop-item .item-preview img {
  width: 82%;
  height: 82%;
  object-fit: contain;
  filter: grayscale(0.12);
}
.shop-item.locked .item-preview::after {
  content: '\1F512';
  position: absolute;
  top: 3px;
  right: 5px;
  font-size: 11px;
  opacity: 0.7;
}
.shop-item .item-tag {
  font-size: 10.5px;
  font-weight: 600;
  margin-top: 2px;
}
.shop-item.via-pass .item-tag { color: var(--color-secondary); }
.shop-item.via-season .item-tag { color: var(--color-defender); }
.shop-item.via-soon .item-tag { color: var(--color-text-dim); }
.shop-foot {
  margin-top: 16px;
  font-size: 12px;
  color: var(--color-text-dim);
  text-align: center;
}
.shop-foot strong { color: var(--color-secondary); }

/* Battle pass */

/* ── Job 087: the screen has to answer "what is that, what is it for" ──────
   Everything from .bp-content down to .bp-actions is the explanation layer.
   The screen used to be a title, a subtitle, a row of medallions and Close. */
/* Two classes deep on purpose: mobile.css loads AFTER screens.css and its
   tablet band sets `.screen-content { max-width: 500px }` at equal specificity,
   which would win at 641-1024px. The old screen dodged this with an inline
   `style="max-width:700px"`; this is the same intent without the inline. */
.battlepass-screen .bp-content {
  max-width: 720px;
  text-align: left;
}
.bp-content .screen-title { text-align: center; }

/* The one sentence. Larger than body copy on purpose — this is the answer, and
   an explanation set at 13px dim grey is one nobody reads. */
.bp-oneliner {
  font-size: 15px;
  line-height: 1.5;
  color: var(--color-text);
  text-align: center;
  margin: 0 auto 16px;
  max-width: 56ch;
}

.bp-how {
  list-style: none;
  margin: 0 0 16px;
  padding: 12px 14px;
  display: grid;
  gap: 9px;
  background: rgba(14, 10, 26, 0.5);
  border: 1px solid var(--color-border);
  border-radius: 12px;
}
.bp-how li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.bp-how-n {
  flex: 0 0 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-secondary);
  color: #241d29;
  font-size: 12px;
  font-weight: bold;
  line-height: 20px;
  text-align: center;
}
.bp-how-t {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--color-text-dim);
}
.bp-how-t b {
  color: var(--color-text);
  display: block;
}

.bp-progress-block { margin-bottom: 14px; }
.bp-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 5px;
}
.bp-tier-now { font-size: 14px; color: var(--color-text); }
.bp-tier-now b { color: var(--color-secondary); font-size: 17px; }
.bp-of { color: var(--color-text-dim); font-size: 12px; }
.bp-xp-readout {
  font-size: 13px;
  color: var(--color-secondary);
  font-variant-numeric: tabular-nums;
}

/* The empty bar used to READ AS FULL: a 0px fill inside a 652px track painted
   var(--color-border) (#3a2e60) is, to a viewer, one solid purple bar. The
   track is now near-black and inset, so empty looks empty; .is-empty adds a
   dashed leading edge so 0% is unambiguous rather than merely dark. */
.bp-bar {
  height: 12px;
  border-radius: 6px;
  background: rgba(6, 4, 14, 0.85);
  border: 1px solid var(--color-border);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}
.bp-bar-fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--color-secondary), var(--color-accent));
  border-radius: 6px 0 0 6px;
  transition: width 0.3s;
  box-shadow: 0 0 8px rgba(240, 192, 64, 0.5);
}
.bp-bar.is-empty {
  border-style: dashed;
  border-color: rgba(240, 192, 64, 0.35);
}
.bp-next-hint {
  margin-top: 5px;
  font-size: 12px;
  color: var(--color-text-dim);
}

.bp-track-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 5px;
}
.bp-track-label {
  font-size: 12.5px;
  color: var(--color-text);
  letter-spacing: 0.3px;
}
/* macOS overlay scrollbars are invisible at rest, so a 4128px track in a 342px
   window looked like the whole season. This says so in words as well. */
.bp-track-scrollhint {
  font-size: 11.5px;
  color: var(--color-text-dim);
  white-space: nowrap;
}
/* Right-edge fade: a second, wordless "there is more this way". */
.bp-track-wrap { position: relative; }
.bp-track-wrap::after {
  content: '';
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 34px;
  border-radius: 0 12px 12px 0;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(14, 10, 26, 0), rgba(14, 10, 26, 0.92));
}

.bp-track-msg {
  padding: 46px 12px;
  width: 100%;
  text-align: center;
  font-size: 13px;
  color: var(--color-text-dim);
}
.bp-track-msg-err { color: var(--color-warning, #f0c040); }

.bp-claim-status {
  min-height: 18px;
  margin-top: 9px;
  font-size: 13px;
  color: var(--color-accent);
  text-align: center;
}
.bp-claim-status.is-error { color: var(--color-danger, #e05050); }

.bp-note {
  margin: 10px auto 0;
  max-width: 60ch;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--color-text-dim);
  text-align: center;
}
.bp-note b { color: var(--color-text); }

.bp-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 16px;
}

/* Narrow phones: the explanation is the point, so nothing is cut — it is
   tightened. At 390x844 the full screen (title -> both buttons) measured 850px
   before this block, i.e. 6px past the fold, and Dima's own handset loses ~110px
   more to browser chrome (mobile.css:96). Gated on width so the near-square and
   desktop layouts above are untouched. */
@media (max-width: 640px) {
  .bp-oneliner { font-size: 14px; margin-bottom: 12px; }
  .bp-how { padding: 10px 12px; gap: 7px; margin-bottom: 12px; }
  .bp-how-t { font-size: 12px; line-height: 1.4; }
  .bp-progress-block { margin-bottom: 11px; }
  .bp-note { font-size: 11px; margin-top: 8px; }
  .bp-actions { margin-top: 12px; }
  .battlepass-track { padding: 11px 10px; }
  .battlepass-tier { min-height: 152px; }
}

/* Framed, darkened panel so the track reads as a contained rail instead of
   floating on the bright lobby map; a real scrollbar replaces the old hard
   mask-fade-cut the swarm flagged (job 030). */
.battlepass-track {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 14px 12px;
  background: rgba(14, 10, 26, 0.62);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
  scroll-snap-type: x proximity;
}
/* Force a PAINTED, space-reserving scrollbar. The default on macOS is an
   overlay bar that is invisible until you are already scrolling — useless as an
   affordance for a 4473px track in a 340px window. Deliberately NOT using the
   standard `scrollbar-width`/`scrollbar-color`: specifying either makes Chromium
   ignore the ::-webkit- rules below and fall back to an overlay bar that
   reserves 0px (measured: offsetHeight - clientHeight was 2px, the borders). */
.battlepass-track::-webkit-scrollbar { height: 9px; }
.battlepass-track::-webkit-scrollbar-track {
  background: rgba(6, 4, 14, 0.6);
  border-radius: 5px;
}
.battlepass-track::-webkit-scrollbar-thumb {
  background: var(--color-secondary);
  border-radius: 5px;
  border: 2px solid rgba(6, 4, 14, 0.6);
}
/* Trailing spacer so the last tier card clears the panel's right padding */
.battlepass-track::after {
  content: '';
  flex: 0 0 4px;
}
/* Flex column + a fixed height so every card is uniform and the Claim button
   baseline-aligns across the row (a 2-line reward name used to shove one down). */
.battlepass-tier {
  min-width: 74px;
  min-height: 164px;   /* +22 for the reward-kind row added by job 087 */
  padding: 8px 6px;
  background: var(--color-bg-panel);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 0 auto;
  scroll-snap-align: start;
}
/* A tier you have not reached should look it. Every card was identically lit
   before, so the track gave no read on where the player actually is. */
.battlepass-tier.locked { opacity: 0.62; }
.battlepass-tier.claimed {
  border-color: var(--color-accent);
  background: rgba(64, 240, 112, 0.1);
}
/* Says what kind of thing the medallion is — three near-identical parchment
   icons carried the whole distinction before. */
.battlepass-tier .tier-kind {
  margin-top: 4px;
  font-size: 9px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--color-text-dim);
}
/* Claiming used to make the Claim button silently VANISH — the outcome of the
   only action on the screen was a button disappearing. Now the card says so. */
.battlepass-tier .tier-claimed-tag {
  margin-top: auto;
  font-size: 11px;
  color: var(--color-accent);
}
.battlepass-tier .tier-locked-tag {
  margin-top: auto;
  font-size: 11px;
  color: var(--color-text-dim);
  visibility: hidden;   /* a spacer: keeps Claim/Claimed on one baseline */
}
.battlepass-tier.current {
  border-color: var(--color-secondary);
  box-shadow: var(--shadow-glow-gold);
}
.battlepass-tier.milestone {
  border-color: var(--color-warning, #f0c040);
}
.battlepass-tier .tier-reward-name {
  font-size: 10.5px;
  line-height: 1.2;
  height: 2.4em;               /* fixed 2 lines → Claim buttons align across cards */
  color: var(--color-text);    /* was muted/9px — the swarm read the labels as murky */
  max-width: 80px;
  margin: 5px auto 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Claim sits at the card bottom (margin-top:auto) so it lands at the same y on
   every card regardless of a 1- or 2-line name. */
.battlepass-tier .bp-claim-btn {
  margin-top: auto;
  padding: 3px 12px;
  font-size: 11px;
  border-color: var(--color-secondary);
  color: var(--color-secondary);
}
.battlepass-tier .tier-num {
  font-size: 10px;
  color: var(--color-text-dim);
  margin-bottom: 4px;
}
.battlepass-tier .tier-reward {
  width: 46px;
  height: 46px;
  margin: 0 auto;
  /* Light parchment medallion so the (white-bg) reward icons read as an
     intentional icon-in-slot rather than a floating cutout (job 027). */
  background: linear-gradient(180deg, #f4ead8, #e2d1b2);
  border: 1px solid rgba(36, 29, 41, 0.35);
  border-radius: 9px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
}
.battlepass-tier .tier-reward-icon {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* A currency tier (job 114) has no painted medallion — it shows the same coin
   and gem glyphs the shop uses for a balance, sized to fill the same 46px slot
   so the row's rhythm doesn't break where the reward kind changes. */
.battlepass-tier .tier-reward-glyph {
  font-size: 26px;
  line-height: 1;
  display: block;
}

/* Quest tracker */
.quest-tracker {
  position: absolute;
  top: 50px;
  left: 8px;
  z-index: var(--z-hud);
  width: 200px;
  pointer-events: auto;
}
.quest-tracker.hidden { display: none; }
.quest-item {
  padding: 6px 10px;
  margin-bottom: 4px;
  background: rgba(14, 10, 26, 0.8);
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--color-secondary);
}
.quest-item .quest-name {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 2px;
}
.quest-item .quest-progress {
  height: 4px;
  background: var(--color-border);
  border-radius: 2px;
  overflow: hidden;
}
.quest-item .quest-progress-fill {
  height: 100%;
  background: var(--color-secondary);
  border-radius: 2px;
  transition: width 0.3s ease;
}

/* ===== Lobby navigation bar: 4x2 grid of identical tiles ===== */
.lobby-nav {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-top: 14px;
  padding-top: 16px;
  border-top: 1px solid var(--color-border);
}
.lobby-nav-btn .nav-glyph { width: 26px; height: 26px; object-fit: contain; }
.lobby-nav-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 64px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-dim);
  cursor: pointer;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  transition: all var(--transition-fast);
}
.lobby-nav-btn:hover {
  color: var(--color-secondary);
  border-color: var(--color-secondary);
  background: rgba(240, 192, 64, 0.05);
  transform: translateY(-1px);
}
.lobby-nav-btn svg {
  opacity: 0.7;
}
.lobby-nav-btn:hover svg {
  opacity: 1;
}

/* Role card ready indicator */
.role-ready-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-border);
  margin: 6px auto 0;
  transition: all var(--transition-fast);
}
.role-ready-indicator.active {
  background: var(--color-accent);
  box-shadow: 0 0 8px rgba(64, 240, 112, 0.5);
}

/* Role card icon */
.role-card .role-icon {
  margin-bottom: 6px;
}

/* ===== Map Selection Screen ===== */
.map-select-screen {
  /* Themed backdrop like the other menus (job 031) — was a flat navy gradient. */
  background:
    linear-gradient(180deg, rgba(8, 5, 18, 0.88), rgba(10, 6, 20, 0.82)),
    url('../assets/maps/map_default_bg.png') center 30% / cover no-repeat,
    var(--color-bg-dark);
  overflow-y: auto;
}
.map-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
.map-card {
  padding: 0;
  background: var(--color-bg-panel);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  cursor: pointer;
  transition: all var(--transition-fast);
}
.map-card:hover {
  border-color: var(--color-secondary);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}
.map-card.selected {
  border-color: var(--color-accent);
  box-shadow: 0 0 16px rgba(64, 240, 112, 0.3);
}
.map-preview-wrap {
  position: relative;
  width: 100%;
  height: 120px;
  overflow: hidden;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-bg-dark);
}
.map-preview {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}
/* Fallback if a painted thumbnail is missing: a themed colour wash + label */
.map-preview-missing {
  background:
    linear-gradient(160deg, color-mix(in srgb, var(--map-tint) 45%, #16101f), var(--color-bg-dark));
}
.map-info {
  padding: 10px;
}
.map-name {
  font-size: 14px;
  font-weight: bold;
  color: var(--color-text);
  margin-bottom: 2px;
}
.map-difficulty {
  font-size: 10px;
  font-weight: 600;
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  margin-bottom: 4px;
}
.difficulty-normal { background: rgba(64, 240, 112, 0.2); color: #40f070; }
.difficulty-hard { background: rgba(233, 69, 96, 0.2); color: #e94560; }
.difficulty-expert { background: rgba(241, 196, 15, 0.2); color: #f1c40f; }
.map-desc {
  font-size: 11px;
  color: var(--color-text-dim);
  line-height: 1.3;
}

/* ===== Upgrade Tree Panel (card-based) ===== */
.upgrade-tree-panel {
  position: absolute;
  bottom: 100px;
  left: 50%;
  transform: translateX(-50%);
  /* Above the ability rail (40), chat (32) and the emote wheel (31). At
     --z-panel (20) those all painted straight through the sheet's translucent
     background, so the panel looked like a broken double-exposure and their
     buttons stayed clickable underneath it (jobs 057, 065). */
  z-index: 45;
  padding: 0;
  background: rgba(20, 14, 44, 0.96);
  border: 2px solid var(--color-defender);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
  pointer-events: auto;
  overflow: hidden;
  animation: panel-slide-up 0.25s var(--spring-bounce);
}
.upgrade-tree-panel.hidden { display: none; }

.utp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--color-border);
}
.utp-tower-name {
  font-size: 15px;
  font-weight: bold;
}
.utp-tower-tier {
  font-size: 10px;
  color: var(--color-text-dim);
  text-transform: capitalize;
}
.utp-close {
  background: none;
  border: none;
  color: var(--color-text-dim);
  font-size: 20px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 6px;
  transition: all var(--transition-fast);
}
.utp-close:hover {
  color: var(--color-text);
  background: rgba(255, 255, 255, 0.1);
}

.utp-sprite { display: block; object-fit: contain; image-rendering: auto; }
.utp-dot {
  display: block;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1.5px solid rgba(0, 0, 0, 0.4);
}

/* Card-based upgrade layout */
.utp-card-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
}

.utp-current-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 10px;
  border: 2px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--card-radius);
  background: rgba(255, 255, 255, 0.05);
  min-width: 80px;
}
.utp-current-card .utp-card-icon {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.utp-current-card .utp-card-name {
  font-size: 10px;
  font-weight: 700;
  text-align: center;
}
.utp-current-card .utp-card-stats {
  font-size: 9px;
  color: var(--color-text-dim);
  text-align: center;
}

.utp-arrow {
  font-size: 20px;
  color: var(--color-defender);
  animation: arrow-pulse 1.2s ease infinite;
  flex-shrink: 0;
}

.utp-upgrade-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 10px;
  border: var(--card-border-width) solid var(--color-defender);
  border-radius: var(--card-radius);
  background: linear-gradient(180deg, rgba(30, 20, 64, 0.95), rgba(20, 14, 44, 0.98));
  cursor: pointer;
  transition: transform 0.2s var(--spring-bounce), box-shadow 0.2s ease;
  min-width: 90px;
  box-shadow: var(--card-shadow-idle);
  position: relative;
}
.utp-upgrade-card:hover:not(:disabled):not(.cant-afford) {
  transform: translateY(-6px) scale(1.04);
  box-shadow: var(--card-shadow-hover);
  border-color: #fff;
}
.utp-upgrade-card:active:not(:disabled) {
  transform: translateY(-2px) scale(0.97);
}
/* No dim. `:disabled` here only ever means "you can't pay for it yet", and
   062's finding is that dimming reads as "off" or as nothing at all — so the
   card recedes with real colour and keeps its price, ring and chip at full
   strength instead (jobs 080, 089; the treatment is in game.css). */
.utp-upgrade-card:disabled {
  cursor: not-allowed;
}
.utp-upgrade-card .utp-upgrade-badge {
  font-size: 8px;
  font-weight: 800;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(78, 204, 163, 0.25);
  color: var(--color-defender);
  letter-spacing: 0.5px;
}
.utp-upgrade-card .utp-card-icon {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.utp-upgrade-card .utp-card-name {
  font-size: 10px;
  font-weight: 700;
  text-align: center;
}
.utp-upgrade-card .utp-card-stats {
  font-size: 9px;
  color: var(--color-text-dim);
  text-align: center;
}
.utp-upgrade-card .utp-card-cost {
  font-size: 11px;
  font-weight: 800;
  color: var(--color-gold);
}

.utp-actions {
  display: flex;
  gap: 8px;
  padding: 8px 14px;
  border-top: 1px solid var(--color-border);
}
.utp-sell-btn {
  flex: 1;
  font-size: 12px !important;
  padding: 7px 12px !important;
  border-color: var(--color-danger) !important;
  color: var(--color-danger) !important;
  border-radius: var(--card-radius);
  transition: all var(--transition-fast);
}
.utp-sell-btn:hover {
  background: rgba(233, 69, 96, 0.15);
}

/* ===== Emote System ===== */
.emote-panel {
  position: absolute;
  bottom: 60px;
  right: 8px;
  /* Above chat: both were --z-hud, so paint order decided and chat (appended
     later in initUI) covered the wheel — Dima: "when I press emote it appears
     under the chat screen" (job 051). */
  z-index: calc(var(--z-hud) + 2);
  pointer-events: auto;
}
.emote-panel.hidden { display: none; }

.emote-trigger {
  padding: 6px 14px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  background: rgba(14, 10, 26, 0.8);
  color: var(--color-text-dim);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--transition-fast);
}
.emote-trigger:hover {
  border-color: var(--color-secondary);
  color: var(--color-secondary);
}

.emote-wheel {
  position: absolute;
  bottom: 38px;
  right: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  padding: 8px;
  background: rgba(14, 10, 26, 0.95);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.emote-wheel.hidden { display: none; }

.emote-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 4px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
  transition: all var(--transition-fast);
  min-width: 48px;
}
.emote-btn:hover {
  border-color: var(--emote-color);
  background: rgba(255, 255, 255, 0.05);
  transform: scale(1.05);
}
.emote-icon {
  font-size: 16px;
  font-weight: bold;
  color: var(--emote-color);
}
.emote-label {
  font-size: 8px;
  color: var(--color-text-dim);
}

/* Emote bubbles */
.emote-display {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: var(--z-toast);
}
.emote-bubble {
  display: inline-block;
  padding: 8px 16px;
  background: rgba(14, 10, 26, 0.9);
  border: 2px solid var(--emote-color);
  border-radius: var(--radius-lg);
  margin: 4px;
  animation: bounce-in 0.3s ease;
}
.emote-bubble.self {
  position: fixed;
  bottom: 100px;
  right: 50px;
}
.emote-bubble.opponent {
  position: fixed;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
}
.emote-bubble-icon {
  font-size: 24px;
  font-weight: bold;
  color: var(--emote-color);
}
.emote-bubble.fade-out {
  animation: toast-out 0.5s ease forwards;
}

/* ===== Chat Panel ===== */
.chat-panel {
  position: absolute;
  bottom: 8px;
  left: 8px;
  z-index: var(--z-hud);
  width: 240px;
  pointer-events: auto;
}
.chat-panel.hidden { display: none; }

.chat-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(14, 10, 26, 0.9);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  cursor: pointer;
}
.chat-title {
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text-dim);
}
.chat-unread {
  padding: 0 6px;
  font-size: 10px;
  font-weight: bold;
  background: var(--color-danger);
  color: #fff;
  border-radius: 10px;
  line-height: 18px;
}
.chat-unread.hidden { display: none; }
/* Reads as a control, not a stray caret — Dima: "I would love to highlight the
   hide button, which doesn't make much sense right now" (job 051). Also a
   44px-tall thumb target. */
.chat-toggle {
  min-width: 38px;
  min-height: 28px;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.10);
  color: var(--color-text);
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
}
.chat-toggle:hover {
  border-color: var(--color-secondary);
  background: rgba(255, 255, 255, 0.18);
}

.chat-body {
  background: rgba(14, 10, 26, 0.9);
  border: 1px solid var(--color-border);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.chat-body.hidden { display: none; }

.chat-messages {
  height: 140px;
  overflow-y: auto;
  padding: 6px 8px;
}
.chat-message {
  font-size: 11px;
  padding: 2px 0;
  color: var(--color-text);
  word-wrap: break-word;
}
.chat-message.self .chat-sender {
  color: var(--color-defender);
}
.chat-message .chat-sender {
  font-weight: 600;
  color: var(--color-attacker);
  margin-right: 4px;
}
.chat-message.system {
  color: var(--color-text-muted);
  font-style: italic;
}

.chat-input-row {
  display: flex;
  gap: 4px;
  padding: 6px;
  border-top: 1px solid var(--color-border);
}
.chat-input {
  flex: 1;
  padding: 4px 8px;
  background: var(--color-bg-dark);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-size: 11px;
  outline: none;
}
.chat-input:focus {
  border-color: var(--color-secondary);
}
.chat-send {
  padding: 4px 8px !important;
  font-size: 10px !important;
}

/* ===== Skin Selection Screen ===== */
.skin-select-screen {
  background: radial-gradient(ellipse at center, var(--color-bg-medium), var(--color-bg-dark));
  overflow-y: auto;
}
.skin-preview-area {
  display: flex;
  justify-content: center;
  margin-bottom: 16px;
}
.skin-preview-area canvas {
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-dark);
}
.skin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}
/* Job 085 — Dima, near-square unfolded: "pricing are not aligned, 'Dead Run'
   which costs zero, its price is horizontally lower than Infernal Tower."
   The grid stretches the CARDS to the row height, but the action button sat in
   normal flow, so its top was decided by however many lines the name and the
   description above it happened to wrap to — measured 13px of spread across the
   four cards of one row. Column + `margin-top:auto` on the button (below) pins
   the price row to the card's bottom edge, so a row shares one baseline no
   matter what is above it. */
.skin-card {
  display: flex;
  flex-direction: column;
  padding: 10px;
  background: var(--color-bg-panel);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  text-align: center;
  cursor: pointer;
  transition: all var(--transition-fast);
}
.skin-card:hover {
  border-color: var(--color-secondary);
  transform: translateY(-2px);
}
.skin-card.selected {
  border-color: var(--color-accent);
  box-shadow: 0 0 12px rgba(64, 240, 112, 0.3);
}
.skin-card-preview {
  display: flex;
  justify-content: center;
  margin-bottom: 6px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.skin-card-preview canvas {
  display: block;
}
.skin-card-name {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text);
}
.skin-card-rarity {
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
}
.skin-card-desc {
  font-size: 9px;
  color: var(--color-text-dim);
  margin-top: 2px;
}

/* ===== Enhanced Results Screen ===== */
.results-banner {
  margin-bottom: 24px;
}
.results-banner.defender .results-winner {
  text-shadow: 0 0 30px rgba(78, 204, 163, 0.4);
}
.results-banner.attacker .results-winner {
  text-shadow: 0 0 30px rgba(233, 69, 96, 0.4);
}
.results-subtitle {
  font-size: 14px;
  color: var(--color-text-dim);
  margin-top: -16px;
  margin-bottom: 4px;
}
.results-explain {
  font-size: 13px;
  color: var(--color-text);
  margin-bottom: 8px;
  opacity: 0.85;
}

/* Star rating */
.results-star-row {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 16px;
}
.results-star {
  font-size: 32px;
  display: inline-block;
  animation: star-pop 0.4s ease both;
}
.results-star.empty {
  opacity: 0.2;
  filter: grayscale(1);
}
@keyframes star-pop {
  0% { transform: scale(0); opacity: 0; }
  60% { transform: scale(1.3); }
  100% { transform: scale(1); opacity: 1; }
}
.results-star-note {
  font-size: 12px;
  color: var(--color-text-muted);
  margin: -8px 0 16px;
}

/* End-of-match progression strip (Phase 3) */
.results-progression {
  margin: 12px auto 4px;
  padding: 12px 16px;
  max-width: 460px;
  border-radius: var(--radius-lg);
  border: 1px solid rgba(240, 192, 64, 0.35);
  background: rgba(240, 192, 64, 0.06);
}
.results-progression.hidden { display: none; }
.prog-xp-row { display: flex; align-items: center; gap: 10px; }
.prog-level {
  font-weight: 800;
  color: var(--color-warning, #f0c040);
  font-size: 14px;
  white-space: nowrap;
}
.prog-xp-bar {
  flex: 1;
  height: 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.45);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.prog-xp-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #f0c040, #ffe38a);
  transition: width 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}
.prog-xp-gain { font-size: 12px; color: var(--color-text-muted); white-space: nowrap; }
.prog-levelup, .prog-streak {
  margin-top: 8px;
  font-weight: 700;
  font-size: 14px;
  color: var(--color-warning, #f0c040);
  animation: star-pop 0.45s ease both;
}
/* Live-now spectate list (Tier 4) */
.lobby-live {
  /* Job 090 — this read `min(460px, 92vw)`: a VIEWPORT-relative width on a box
     whose parent is a fixed-cap panel. That is the exact defect job 048 already
     fixed one level up on .lobby-menu (see its comment): the panel's content box
     is at most 438px (480 cap - 40 padding - 2 border), so 460px always
     overflowed, and 92vw overflowed harder the narrower the window got. Flexbox
     treats `margin: auto` as 0 when free space is negative, so the box did not
     centre and hang off both sides — it left-aligned and hung 22px (desktop) to
     59px (390px wide) off the RIGHT, straight into .lobby-menu's `overflow:
     hidden`, which sliced the Watch button in half.
     100% resolves against the flex container's content box, so it now fits by
     construction at every width. The 460px cap is kept only so the strip cannot
     outgrow its own design if the panel is ever widened. */
  width: min(460px, 100%);
  margin: 10px auto 0;
  padding: 10px 14px;
  border-radius: var(--radius-md, 8px);
  border: 1px solid rgba(126, 200, 227, 0.35);
  background: rgba(20, 16, 31, 0.72);
  text-align: left;
}
.lobby-live.hidden { display: none; }
.lobby-live-title { font-weight: 800; font-size: 13px; color: #7ec8e3; margin-bottom: 6px; }
/* The row has one incompressible item (.lobby-live-meta is nowrap) and one that
   must stay whole (the Watch button), so the player names are the only thing
   that may absorb a narrow panel — they ellipsize. If even that is not enough,
   the button wraps to its own line rather than being pushed out of the panel:
   at 390px the strip is 300px wide and a long username would otherwise shove
   Watch past the clip again. */
.lobby-live-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 4px 0;
  font-size: 13px;
}
.lobby-live-players {
  flex: 1 1 140px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lobby-live-meta { flex: 0 1 auto; color: var(--color-text-muted); font-size: 11px; white-space: nowrap; }
/* 3px of vertical padding gave a 25px-tall tap target. 32px is the minimum a
   thumb can be asked to hit; `margin-left: auto` keeps it at the right edge on
   the line it lands on, wrapped or not. */
.lobby-live-row .btn {
  flex: 0 0 auto;
  margin-left: auto;
  padding: 6px 14px;
  min-height: 32px;
  font-size: 12px;
}

/* Spectator banner (Tier 4) */
.spectator-bar {
  position: fixed;
  top: 52px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 7px 16px;
  border-radius: 999px;
  border: 1px solid #7ec8e3;
  background: rgba(20, 16, 31, 0.92);
  color: #7ec8e3;
  font-size: 13px;
  z-index: 60;
}
.spectator-bar.hidden { display: none; }
.spectator-bar .btn { padding: 3px 12px; font-size: 12px; }

/* Replay control bar (Tier 4) */
.replay-bar {
  position: fixed;
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(680px, 94vw);
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--color-gold, #f0c040);
  background: rgba(20, 16, 31, 0.94);
  z-index: 70;
}
.replay-bar.hidden { display: none; }
/* Job 086: a fixed square, laid out by flex, so the icon inside is centred by
   geometry rather than by a font's idea of where a glyph sits in its em box. */
.replay-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background: none;
  border: 1px solid rgba(240, 192, 64, 0.4);
  color: var(--color-gold, #f0c040);
  border-radius: 999px;
  width: 40px;
  height: 40px;
  min-width: 40px;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.replay-btn:hover { background: rgba(240, 192, 64, 0.12); }
.replay-btn .replay-ico { display: block; }

/* Play: a triangle clipped out of a solid block, so its bounding box is the
   flex-centred box and the ink is centred with it. */
.replay-toggle .replay-ico {
  width: 13px;
  height: 14px;
  background: currentColor;
  clip-path: polygon(6% 0%, 100% 50%, 6% 100%);
}
/* Pause: two bars of equal weight either side of the same centre. */
.replay-toggle.is-playing .replay-ico {
  box-sizing: border-box;
  width: 12px;
  height: 14px;
  background: none;
  clip-path: none;
  border-left: 4px solid currentColor;
  border-right: 4px solid currentColor;
}
/* Close: two rules crossing at the exact centre of the square. */
.replay-close .replay-ico {
  position: relative;
  width: 14px;
  height: 14px;
}
.replay-close .replay-ico::before,
.replay-close .replay-ico::after {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 2px;
  margin-top: -1px;
  border-radius: 1px;
  background: currentColor;
}
.replay-close .replay-ico::before { transform: rotate(45deg); }
.replay-close .replay-ico::after { transform: rotate(-45deg); }

/* Job 086 — measured at 390x844 the track was **0px wide**: it is the only
   flexible item in the row, and .replay-title's `white-space: nowrap` made the
   record's full name (173px) incompressible, so the track absorbed the entire
   deficit and the close button still overflowed the bar by 8px. The scrubber
   now has a floor it can never be squeezed below, and the title yields first. */
.replay-track {
  flex: 1 1 120px;
  min-width: 96px;
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  cursor: pointer;
  overflow: hidden;
}
.replay-track-fill { height: 100%; width: 0; background: var(--color-gold, #f0c040); }
.replay-time {
  flex: none;
  font-size: 12px;
  color: var(--color-text-muted);
  min-width: 72px;
  text-align: center;
  white-space: nowrap;   /* "0:22 / 0:33" wrapped to two lines at 390px */
}
.replay-title {
  flex: 0 1 auto;
  min-width: 0;          /* nowrap alone makes this item refuse to shrink */
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12px;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.replay-watch-btn { padding: 3px 12px; font-size: 12px; }

/* Generated inline icons in the progression strip (Phase C) */
.prog-icon {
  width: 22px;
  height: 22px;
  object-fit: contain;
  vertical-align: -5px;
  margin-right: 2px;
}

/* Streak chest reveal (Phase 4): a beat bigger than the lines around it */
.prog-chest {
  margin-top: 8px;
  padding: 8px 14px;
  border-radius: var(--radius-md, 8px);
  font-weight: 800;
  font-size: 15px;
  animation: star-pop 0.6s cubic-bezier(0.2, 1.6, 0.4, 1) both;
  border: 1px solid;
}
.prog-chest.chest-wood { color: #c98d5a; border-color: #c98d5a; background: rgba(201, 141, 90, 0.12); }
.prog-chest.chest-silver { color: #c9d4e0; border-color: #c9d4e0; background: rgba(201, 212, 224, 0.10); }
.prog-chest.chest-gold { color: #f0c040; border-color: #f0c040; background: rgba(240, 192, 64, 0.14); }
.prog-quests { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; }
.prog-quest {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--color-text-muted);
}
.prog-quest.done { color: var(--color-defender); }
.prog-quest-count { font-variant-numeric: tabular-nums; }

/* Claim-your-name after a win (Phase 1) */
.results-name-claim {
  margin: 14px auto 4px;
  padding: 12px 18px;
  max-width: 380px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-secondary);
  background: rgba(108, 75, 184, 0.12);
}
.results-name-claim.hidden { display: none; }
.results-name-title { font-size: 14px; margin-bottom: 8px; color: var(--color-text); }
.results-name-row { display: flex; gap: 8px; justify-content: center; }
.results-name-row input {
  flex: 1;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 255, 255, 0.15);
  background: rgba(0, 0, 0, 0.35);
  color: var(--color-text);
  font-size: 14px;
}

.results-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-bottom: 16px;
}
.results-stat-card {
  padding: 10px 6px;
  background: var(--color-bg-panel);
  border-radius: var(--radius-md);
  text-align: center;
}
.results-stat-icon {
  font-size: 18px;
  margin-bottom: 2px;
}
.results-stat-value {
  font-size: 18px;
  font-weight: bold;
  color: var(--color-secondary);
}
.results-stat-label {
  font-size: 9px;
  color: var(--color-text-dim);
  margin-top: 2px;
}

.results-highlights {
  display: flex;
  gap: 12px;
  justify-content: center;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.results-highlight {
  font-size: 14px;
  font-weight: bold;
  padding: 4px 12px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: var(--radius-md);
  animation: bounce-in 0.5s ease;
}

/* ===== Achievement grid ===== */
.achievement-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin-bottom: 16px;
}
.achievement-card {
  padding: 8px 4px;
  background: var(--color-bg-panel);
  border-radius: var(--radius-md);
  text-align: center;
  border: 1px solid var(--color-border);
}
.achievement-card.earned {
  border-color: var(--color-secondary);
}
.achievement-card.locked {
  opacity: 0.4;
}
.achievement-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  margin: 0 auto 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
}
.achievement-name {
  font-size: 9px;
  color: var(--color-text-dim);
  line-height: 1.2;
}

/* ===== Match history ===== */
.match-history {
  max-height: 200px;
  overflow-y: auto;
}
.match-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-bottom: 1px solid rgba(58, 46, 96, 0.3);
  font-size: 12px;
}
.match-result {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 11px;
}
.match-row.win .match-result {
  background: rgba(64, 240, 112, 0.2);
  color: #40f070;
}
.match-row.loss .match-result {
  background: rgba(233, 69, 96, 0.2);
  color: #e94560;
}
.match-role {
  font-weight: 600;
  color: var(--color-text);
  min-width: 60px;
}
.match-map {
  color: var(--color-text-dim);
  flex: 1;
}
.match-duration {
  color: var(--color-text-muted);
  min-width: 40px;
  text-align: right;
}
.match-date {
  color: var(--color-text-muted);
  min-width: 50px;
  text-align: right;
}

/* --- vs-AI practice ladder (M3.3) --- */
.vs-ai-section {
  margin-top: 18px;
  text-align: center;
}
.vs-ai-title {
  font-size: 13px;
  color: var(--color-text-dim);
  margin-bottom: 8px;
}
.vs-ai-tiers {
  display: flex;
  gap: 10px;
  justify-content: center;
}
.vs-ai-btn {
  min-width: 74px;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-light);
  background: var(--color-bg-panel);
  color: var(--color-text);
  font-family: var(--font-display);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  transition: transform var(--transition-fast), border-color var(--transition-fast);
}
.vs-ai-btn span {
  font-family: var(--font-main);
  font-size: 11px;
  color: var(--color-text-dim);
}
.vs-ai-btn:hover:not(.locked) { transform: translateY(-2px); border-color: var(--color-secondary); }
.vs-ai-btn:active:not(.locked) { transform: scale(0.95); }
.vs-ai-btn.locked { opacity: 0.35; cursor: not-allowed; }
.vs-ai-btn.beaten { border-color: var(--color-defender); }
.vs-ai-btn.beaten span::after { content: ' ✓'; color: var(--color-defender); }

/* --- Side select: the first-run screen — pick Defender or Attacker --- */
/* Job 120. This sized itself from the VIEWPORT (`width: min(560px, 94vw)`) while
   living inside `.screen-content`, which is capped, padded and `overflow-x:
   hidden` — the same defect 048 fixed one level up and 090 fixed for a sibling.
   Between 641 and 1024 CSS px the cap is 500px, so 560 - (500 - 48) = 108px of
   the block hung past the clip edge and ate the Attacker card. Size from the
   CONTAINER: 100% of whatever room there is, never more than the design width. */
.side-select {
  width: 100%;
  max-width: 560px;
  margin: 26px auto 0;
  text-align: center;
}
.side-select.hidden { display: none; }
.side-select-title {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 1px;
  margin-bottom: 16px;
  /* The block sits directly on the animated lobby diorama, not on a panel. */
  text-shadow: 0 2px 6px rgba(4, 3, 10, 0.9);
}
/* Fit-driven, not breakpoint-driven. `1fr 1fr` + a `max-width: 640px` media query
   meant the stacking decision depended on a number an in-app webview can report
   differently from the device's real width — and Dima's Movistar webview landed
   on the wrong side of it. auto-fit drops to one column whenever two won't fit,
   at any width, in any browser. */
.side-pick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}
.side-card {
  padding: 26px 18px 22px;
  border: 2px solid rgba(255, 255, 255, 0.09);
  border-radius: var(--radius-xl);
  background: linear-gradient(180deg, rgba(34, 24, 60, 0.96), rgba(19, 13, 38, 0.97));
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  backdrop-filter: blur(6px);
  cursor: pointer;
  text-align: center;
  transition: all var(--transition-fast);
}
.side-card:hover { transform: translateY(-3px); }
.side-card.defender:hover { border-color: var(--color-defender); box-shadow: var(--shadow-glow-defender); }
.side-card.attacker:hover { border-color: var(--color-attacker); box-shadow: var(--shadow-glow-attacker); }
/* Keyboard focus has to be visible or the card is reachable but invisible to
   the person reaching it (job 040). Same lift as hover, plus a ring. */
.side-card:focus-visible,
.role-card:focus-visible {
  outline: 3px solid var(--color-gold, #f0c040);
  outline-offset: 3px;
  transform: translateY(-3px);
}
.side-card[aria-disabled="true"],
.role-card[aria-disabled="true"] { cursor: not-allowed; }
.side-card .side-art {
  width: 76px;
  height: 76px;
  object-fit: contain;
  margin-bottom: 10px;
}
.side-card .role-name { font-size: 20px; font-weight: 800; margin-bottom: 6px; }
.side-card.defender .role-name { color: var(--color-defender); }
.side-card.attacker .role-name { color: var(--color-attacker); }
.side-card .role-desc { font-size: 12.5px; color: var(--color-text-dim); line-height: 1.5; }
.side-card .side-goal {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--color-text);
}
.side-card.defender .side-goal { color: var(--color-defender); }
.side-card.attacker .side-goal { color: var(--color-attacker); }
/* The side you're currently on, when re-opening the chooser via Switch side */
.side-card.current { position: relative; }
.side-card.current.defender { border-color: var(--color-defender); }
.side-card.current.attacker { border-color: var(--color-attacker); }
.side-card.current::after {
  content: 'Current';
  position: absolute;
  top: 8px; right: 8px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 1px 7px;
}
.side-select-lead {
  font-size: 13.5px;
  color: var(--color-text-dim);
  line-height: 1.5;
  max-width: 460px;
  margin: 0 auto 14px;
  text-shadow: 0 2px 6px rgba(4, 3, 10, 0.9);
}
/* Job 120: this line lands on the diorama's lit path band, and at
   --color-text-muted (#6b6088) over it Dima couldn't read it at all — he
   described it as sitting under a translucent band. It is not covered (the
   diorama is z-index 0 under the content's z-index 1); it was simply too dim
   for what happens to be painted behind it. Contrast, not z-order. */
.side-select-hint {
  /* block + fit-content, not inline-block: inline-block put the pill on the
     same line as the "See how to play" button, which is also inline-block. */
  display: block;
  width: fit-content;
  margin: 14px auto 0;
  padding: 4px 14px;
  border-radius: 999px;
  background: rgba(8, 5, 18, 0.62);
  font-size: 12px;
  color: var(--color-text-dim);
  text-shadow: 0 1px 4px rgba(4, 3, 10, 0.95);
}
.side-howto-link {
  margin-top: 8px;
  background: transparent;
  border: none;
  color: var(--color-secondary);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
}
.side-howto-link:hover { color: var(--color-gold); }
/* Job 120 — the way back out of the chooser. Left-aligned in an otherwise
   centred block, above the lead, in the flow (not absolute) so it can never
   overlap the logo above it or the lead below it at any width. */
.side-select-top {
  display: flex;
  justify-content: flex-start;
  margin-bottom: 10px;
}
.side-select-top.hidden { display: none; }
.side-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 8px 16px;
  background: rgba(19, 13, 38, 0.85);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-dim);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  cursor: pointer;
}
.side-back:hover { color: var(--color-text); border-color: var(--color-border-light); }
.side-back:focus-visible {
  outline: 3px solid var(--color-gold, #f0c040);
  outline-offset: 3px;
}

/* --- Side banner: who you are + the way out --- */
.lobby-side-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.side-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.side-badge img { width: 24px; height: 24px; object-fit: contain; }
.side-defender .side-badge { color: var(--color-defender); }
.side-attacker .side-badge { color: var(--color-attacker); }
.side-switch {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-dim);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  padding: 3px 10px;
}
.side-switch:hover { color: var(--color-text); border-color: var(--color-text-dim); }
.side-banner-actions { display: flex; align-items: center; gap: 4px; }
.side-help {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  color: var(--color-text-dim);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  padding: 3px 10px;
}
.side-help:hover { color: var(--color-gold); border-color: var(--color-secondary); }

/* --- Side theming: the menu wears your colors --- */
.lobby-menu.side-defender { border-color: rgba(78, 204, 163, 0.35); }
.lobby-menu.side-attacker { border-color: rgba(233, 69, 96, 0.35); }
.side-defender .play-cta {
  border-color: var(--color-defender);
  background: linear-gradient(180deg, #2f9e77, #1c6b50);
  box-shadow: 0 6px 22px rgba(78, 204, 163, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.side-defender .play-cta:hover { box-shadow: 0 10px 28px rgba(78, 204, 163, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.25); }
.side-attacker .play-cta {
  border-color: var(--color-attacker);
  background: linear-gradient(180deg, #c74560, #8c2c40);
  box-shadow: 0 6px 22px rgba(233, 69, 96, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.side-attacker .play-cta:hover { box-shadow: 0 10px 28px rgba(233, 69, 96, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.25); }

/* --- Lobby menu: one centered column, PLAY on top, uniform rows below --- */
.lobby-menu {
  position: relative;
  /* Sized against the PARENT, not the viewport. `94vw` was wider than
     .screen-content's content box (90% of the viewport minus padding — 292px on
     a 360px phone), and `margin: 0 auto` cannot centre a box wider than its
     container: it overflows to the right. That is what put the whole lobby
     off-centre and clipped on Dima's phone (job 048). */
  width: min(480px, 100%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background:
    linear-gradient(180deg, rgba(30, 21, 54, 0.94) 0%, rgba(17, 12, 34, 0.96) 100%);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: var(--radius-xl);
  padding: 18px 20px 20px;
  backdrop-filter: blur(10px);
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
/* A thin side-colored accent bar crowning the panel */
.lobby-menu::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--color-secondary), transparent);
  opacity: 0.7;
}
.lobby-menu.side-defender::before { background: linear-gradient(90deg, transparent, var(--color-defender), transparent); }
.lobby-menu.side-attacker::before { background: linear-gradient(90deg, transparent, var(--color-attacker), transparent); }

/* Header logo — framed as a deliberate crest badge (the art has a solid
   backing, so a border + rounding reads as intentional, not a pasted box) */
.lobby-logo-wrap { text-align: center; margin-bottom: 10px; }
.lobby-logo {
  max-width: min(260px, 62%);
  height: auto;
  border-radius: 16px;
  border: 2px solid rgba(240, 192, 64, 0.5);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.6), 0 0 22px rgba(240, 192, 64, 0.14);
  pointer-events: none;
  user-select: none;
}

/* PLAY sheen — a slow shine sweep so the hero button feels alive */
.lobby-menu .play-cta { position: relative; overflow: hidden; }
.lobby-menu .play-cta::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -60%;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.22), transparent);
  transform: skewX(-18deg);
  animation: play-sheen 5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes play-sheen {
  0% { left: -60%; }
  28% { left: 150%; }
  100% { left: 150%; }
}

/* Nav tiles — subtle depth + a warm hover, so they read as crafted buttons */
.lobby-nav-btn {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(0, 0, 0, 0.12));
  border-color: rgba(255, 255, 255, 0.08);
}
.lobby-nav-btn:hover {
  color: var(--color-secondary);
  border-color: var(--color-secondary);
  background: linear-gradient(180deg, rgba(240, 192, 64, 0.12), rgba(240, 192, 64, 0.03));
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
}
.lobby-nav-btn .nav-glyph { transition: transform var(--transition-fast); }
.lobby-nav-btn:hover .nav-glyph { transform: scale(1.12); }

/* Fold-out rows — a touch of inner light + firmer hover */
.lobby-menu-row {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(0, 0, 0, 0.14));
}
.lobby-menu-row:hover {
  border-color: var(--color-secondary);
  background: linear-gradient(180deg, rgba(240, 192, 64, 0.08), rgba(0, 0, 0, 0.14));
}
.lobby-menu-row .menu-ico { filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4)); }
.lobby-menu .play-cta {
  width: 100%;
  margin: 14px 0 4px;
  padding: 16px 20px;
}
.lobby-menu-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  background: var(--color-bg-panel);
  color: var(--color-text);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}
.lobby-menu-row:hover { border-color: var(--color-secondary); transform: translateY(-1px); }
.lobby-menu-row.open { border-color: var(--color-secondary); }
.lobby-menu-row .menu-ico {
  width: 34px;
  height: 34px;
  object-fit: contain;
  flex: 0 0 34px;
}
.lobby-menu-row .menu-txt {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.lobby-menu-row .menu-label {
  font-size: 15px;
  font-weight: 700;
}
.lobby-menu-row .menu-sub {
  font-size: 11.5px;
  color: var(--color-text-dim);
}
.lobby-menu-row .toggle-arrow {
  color: var(--color-text-dim);
  transition: transform var(--transition-fast);
}
.lobby-menu-row.open .toggle-arrow { transform: rotate(180deg); }

/* Expanded panel tucked under its menu row */
.lobby-expand {
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  background: rgba(0, 0, 0, 0.25);
  padding: 4px 14px 14px;
  margin-top: -4px;
}
.lobby-expand.hidden { display: none; }
.lobby-menu .vs-ai-section { margin-top: -4px; }
.lobby-menu .vs-ai-tiers { margin-top: 10px; flex-wrap: wrap; }
.lobby-menu .lobby-section-title { margin: 16px 0 4px; }
.lobby-menu .invite-row { flex-wrap: wrap; }

/* --- How to play: first-time visual explainer (overlay modal) --- */
.howto-backdrop {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 4, 14, 0.72);
  backdrop-filter: blur(3px);
  /* #ui-root is pointer-events:none so the game canvas gets input; every
     interactive UI layer must opt back in or its clicks fall through. */
  pointer-events: auto;
}
.howto-backdrop.hidden { display: none; }
.howto-modal {
  position: relative;
  width: min(560px, 96vw);
  max-height: 92vh;
  overflow-y: auto;
  background: var(--color-bg-medium);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-xl);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  padding: 24px 22px;
  text-align: center;
  animation: howto-pop var(--transition-normal);
}
@keyframes howto-pop {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
.howto-close {
  position: absolute;
  top: 10px;
  right: 12px;
  background: transparent;
  border: none;
  color: var(--color-text-dim);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.howto-close:hover { color: var(--color-text); }
.howto-title {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 800;
  color: var(--color-secondary);
}
.howto-tagline { margin-top: 4px; font-size: 13.5px; color: var(--color-text-dim); }

/* The clarifying mini-board: path + tower + marching monsters + gate */
.howto-board {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 18px 0 8px;
  padding: 14px 12px;
  border-radius: var(--radius-lg);
  background:
    linear-gradient(rgba(14, 10, 26, 0.55), rgba(14, 10, 26, 0.55)),
    url('../assets/maps/map_default_bg.png');
  background-size: cover;
  background-position: center;
  border: 1px solid var(--color-border);
}
.howto-tower {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}
.howto-tower img { width: 40px; height: 40px; object-fit: contain; }
.howto-tower span { font-size: 9px; font-weight: 700; color: var(--color-defender); text-transform: uppercase; letter-spacing: 0.5px; }
.howto-lane {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: 4px;
  height: 44px;
  border-top: 3px dashed rgba(240, 192, 64, 0.5);
  border-bottom: 3px dashed rgba(240, 192, 64, 0.5);
  background: rgba(200, 170, 110, 0.14);
}
.howto-lane .howto-mob { width: 30px; height: 30px; object-fit: contain; animation: howto-march 2.4s linear infinite; }
.howto-lane .howto-mob:nth-child(2) { animation-delay: 0.4s; }
.howto-lane .howto-mob:nth-child(3) { animation-delay: 0.8s; }
@keyframes howto-march { 0% { transform: translateX(6px); } 50% { transform: translateX(-6px); } 100% { transform: translateX(6px); } }
.howto-gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}
.howto-gate img { width: 34px; height: 34px; object-fit: contain; }
.howto-gate span { font-size: 9px; font-weight: 700; color: var(--color-lives); text-transform: uppercase; letter-spacing: 0.5px; }
.howto-caption { font-size: 12px; color: var(--color-text-dim); line-height: 1.5; margin-bottom: 16px; }

.howto-steps {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  text-align: left;
}
.howto-step {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg-panel);
}
.howto-step.def { border-color: rgba(78, 204, 163, 0.4); }
.howto-step.atk { border-color: rgba(233, 69, 96, 0.4); }
.howto-step-ico { width: 30px; height: 30px; object-fit: contain; flex: 0 0 30px; margin-top: 1px; }
.howto-step-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.howto-step-txt b { font-size: 13px; }
.howto-step.def b { color: var(--color-defender); }
.howto-step.atk b { color: var(--color-attacker); }
.howto-step-txt span { font-size: 11.5px; color: var(--color-text-dim); line-height: 1.45; }

.howto-tip {
  margin: 14px 0 4px;
  padding: 9px 12px;
  border-radius: var(--radius-md);
  background: rgba(240, 192, 64, 0.1);
  border: 1px solid rgba(240, 192, 64, 0.3);
  font-size: 12px;
  color: var(--color-secondary);
}
/* Primary "proceed" CTA — gold, the one brand accent for confirm actions
   (job 031). Was a lone purple gradient; the only role-coloured button is the
   big lobby PLAY, and red/pink stays reserved for attacker/danger. */
.howto-cta {
  margin-top: 14px;
  width: 100%;
  padding: 13px;
  border: none;
  border-radius: var(--radius-md);
  background: linear-gradient(180deg, #f4cd57, #e0a92e);
  color: #2a1a08;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 1px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(224, 169, 46, 0.42);
  transition: transform var(--transition-fast);
}
.howto-cta:hover { transform: translateY(-1px); }
.howto-cta:active { transform: translateY(0); }
@media (max-width: 560px) {
  .howto-steps { grid-template-columns: 1fr; }
  .howto-board { flex-direction: column; }
  .howto-lane { width: 100%; }
}

/* --- Challenge link (M3.5) --- */
.invite-row {
  margin-top: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}
.invite-code {
  font-size: 13px;
  color: var(--color-secondary);
  letter-spacing: 0.5px;
}
.invite-code.hidden { display: none; }

.results-share-code {
  margin-top: 10px;
  font-size: 13px;
  color: var(--color-secondary);
  word-break: break-all;
}
.results-share-code.hidden { display: none; }

/* --- Quest tracker live states (M6.2) --- */
.quest-tracker-title {
  font-size: 12px;
  color: var(--color-text-dim);
  margin-bottom: 6px;
  letter-spacing: 0.4px;
}
.quest-item.claimable {
  cursor: pointer;
  border-left: 2px solid var(--color-secondary);
}
.quest-item.claimable:hover { background: var(--color-bg-panel-hover); }
.bp-claim-btn {
  margin-top: 4px;
  padding: 2px 10px;
  font-size: 11px;
}

/* ── Job 027: meta empty-state scenes (throne / armory) ──────────────────
   Leaderboard and Shop were content adrift in a black void. They now sit on
   their own painted scenes with a legible dark content panel over the art. */
.leaderboard-screen {
  background:
    linear-gradient(180deg, rgba(8, 5, 18, 0.80) 0%, rgba(6, 4, 14, 0.66) 42%, rgba(5, 3, 12, 0.92) 100%),
    url('../assets/ui/meta/empty_throne.jpg') center 22% / cover no-repeat,
    var(--color-bg-dark);
}
.shop-screen {
  background:
    linear-gradient(180deg, rgba(8, 5, 18, 0.84) 0%, rgba(10, 6, 20, 0.72) 42%, rgba(5, 3, 12, 0.94) 100%),
    url('../assets/ui/meta/empty_armory.jpg') center center / cover no-repeat,
    var(--color-bg-dark);
}
.leaderboard-screen .screen-content,
.shop-screen .screen-content,
.profile-screen .screen-content {
  margin-top: 9vh;
  background: rgba(14, 10, 26, 0.82);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  padding: 26px 26px 24px;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(2px);
}
/* Profile is tall (header + stats + two sections) — frame it as one card but
   pull it up and let it scroll so it never runs off the viewport (job 029). */
.profile-screen .screen-content {
  margin-top: 4vh;
  max-height: 88vh;
  overflow-y: auto;
}


/* --- Cosmetics: wallet, packs, buy/equip actions (jobs 069, 070) --------- */
.cos-wallet {
  display: flex;
  gap: 14px;
  justify-content: center;
  margin: 4px 0 12px;
}
.cos-bal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--color-border);
  font-weight: 800;
  color: var(--color-secondary, #f0c040);
}
.cos-icon { font-size: 15px; }
.cos-note {
  font-size: 12px;
  color: var(--color-text-dim);
  text-align: center;
  margin-bottom: 12px;
}
.cos-empty { color: var(--color-text-dim); text-align: center; padding: 20px; }

.skin-tabs { display: flex; gap: 8px; justify-content: center; margin-bottom: 14px; flex-wrap: wrap; }
.skin-tabs .skin-tab { min-height: 40px; }

/* margin-top:auto, not 8px: the flex column above pushes the price/action row
   (and anything after it, e.g. a pack's "test purchase" caption) to the bottom
   of the stretched card, which is what puts every price in a row on one line
   (job 085). The 8px minimum gap moves onto the description instead. */
.skin-card .btn { width: 100%; margin-top: auto; min-height: 40px; font-size: 12px; }
.skin-card .skin-card-desc { margin-bottom: 8px; }
/* Owned-but-not-worn reads as an invitation; equipped reads as done. */
.skin-card.owned { border-color: rgba(78, 204, 163, 0.45); }
.cos-equipped { opacity: 0.75; border-color: var(--color-defender); color: var(--color-defender); }
/* Locked and unaffordable are different states and must not look the same:
   one is "come back when you've done X", the other is "keep saving". */
.cos-locked { opacity: 0.8; border-color: var(--color-border-light); color: var(--color-text-dim); font-size: 11px !important; }
.cos-cant { opacity: 0.7; border-color: var(--color-cant-afford, #ff9f43); color: var(--color-cant-afford, #ff9f43); }

/* Job 101 — Dima, foldable: "In Mob skins there is a lock not aligned. And text
   'send 200 monsters' — it's out of the button box."

   One cause, two symptoms. `.btn` is globally `white-space: nowrap` with
   `justify-content: center` and no overflow handling, so "\1F512 Send 200
   monsters" (116px of content) inside Crimson Tide's 112px button overflowed
   BOTH ways: the lock hung 6px past the left border — a glyph floating outside
   the box — and the last letters ran 7px past the right. Measured identically at
   882x864 and 1440x900.

   Three rules, and none of them may undo job 085 (a row of cards must keep one
   action-button baseline):

   1. the condition leaves the button for a line of its own that can wrap;
   2. that line takes the flex column's free space (`margin-top: auto`) so the
      BUTTON still ends flush with the card's bottom edge, exactly as 085 left
      it — the auto margin simply moved up one element;
   3. card action buttons stop being `nowrap`. That is the standing guard: a
      future label too long for its card now wraps INSIDE the button instead of
      spilling outside it. It also stops a nowrap button dictating the grid
      track's min-content — at 390px the Crimson Tide column measured 184px
      against its neighbour's 148px, a visibly lopsided pair of cards. */
.skin-card-unlock {
  margin-top: auto;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--color-secondary, #f0c040);
  opacity: 0.85;
  overflow-wrap: anywhere;
}
.skin-card .skin-card-unlock + .btn { margin-top: 6px; }
.skin-card .btn,
.shop-item .btn { white-space: normal; overflow-wrap: anywhere; }
/* The lock is its own flex item, so `.btn`'s gap and align-items centre it
   against the label rather than sitting it on the text baseline. */
.cos-lock-ico { font-size: 12px; line-height: 1; }

/* Job 085 — Dima on the Get-gems tab: "it aligns to the left, should be aligned
   to the center." .skin-grid is `repeat(auto-fill, minmax(110px, 1fr))`, which
   at 882px AND at 1440px lays down FOUR tracks for THREE packs: 145px of empty
   track on the right, 0 on the left. auto-fit was the obvious swap and is wrong
   — it would stretch the skins tab's lone 5th card across the whole 572px row.
   The packs are a short, fixed list, so they get a centred wrap of their own and
   the skins keep their grid. */
.skin-grid.pack-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
}
.skin-grid.pack-grid .pack-card { flex: 0 1 170px; max-width: 210px; }
.pack-card { text-align: center; }
.pack-amount { font-size: 18px; font-weight: 800; color: var(--color-secondary, #f0c040); margin: 6px 0; }
.pack-amount em { font-size: 12px; font-style: normal; color: var(--color-defender); }
.pack-test {
  margin-top: 6px;
  font-size: 10px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--color-text-dim);
}


/* Shop filter chips (job 070). The old chips toggled an `active` class that no
   rule styled — Dima: "chips not indicating my selection". They were literally
   invisible state. */
.shop-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin-bottom: 14px;
}
.shop-chip {
  padding: 8px 14px;
  min-height: 40px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: rgba(255, 255, 255, 0.04);
  color: var(--color-text-dim);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.shop-chip:hover { border-color: var(--color-border-light); color: var(--color-text); }
.shop-chip.active {
  background: rgba(240, 192, 64, 0.16);
  border-color: var(--color-secondary, #f0c040);
  color: var(--color-secondary, #f0c040);
  box-shadow: 0 0 0 1px rgba(240, 192, 64, 0.25);
}
.shop-item .item-rarity {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-top: 2px;
}
.shop-item .btn { width: 100%; margin-top: auto; min-height: 40px; font-size: 12px; }
.shop-item .item-tag { margin-bottom: 8px; }
.shop-foot { font-size: 12px; color: var(--color-text-dim); text-align: center; margin-top: 10px; }


/* --- Achievement wall + worn badge (job 071) ----------------------------- */
.profile-avatar-wrap { position: relative; display: inline-block; }
.profile-badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  min-width: 26px;
  height: 26px;
  padding: 0 5px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.4px;
  color: #1a1428;
  background: var(--color-secondary, #f0c040);
  border: 2px solid var(--color-bg-dark, #14101f);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
.profile-badge.hidden { display: none; }

.settings-group-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ach-wall-btn { padding: 5px 12px; min-height: 34px; font-size: 12px; }

.ach-wall-overlay {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(6, 4, 14, 0.72);
  pointer-events: auto;
}
.ach-wall-overlay.hidden { display: none; }
.ach-wall-panel {
  width: min(620px, 96vw);
  max-height: 86vh;
  overflow-y: auto;
  padding: 16px;
  border-radius: 16px;
  background: var(--color-bg-panel, #1c1533);
  border: 1px solid var(--color-border);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
}
.ach-wall-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 16px;
  font-weight: 800;
  color: var(--color-secondary, #f0c040);
}
.ach-wall-hint { font-size: 12px; color: var(--color-text-dim); margin: 4px 0 12px; }
.ach-wall-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.ach-wall-card {
  padding: 10px;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: rgba(255, 255, 255, 0.03);
  text-align: center;
}
.ach-wall-card.earned { border-color: var(--color-secondary, #f0c040); cursor: pointer; }
.ach-wall-card.worn { box-shadow: 0 0 0 2px rgba(240, 192, 64, 0.4); }
.ach-wall-card.locked { opacity: 0.75; }
.ach-wall-glyph {
  width: 38px; height: 38px;
  margin: 0 auto 6px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 900; font-size: 13px;
  color: #1a1428;
  background: var(--color-border);
}
.ach-wall-card.earned .ach-wall-glyph { background: var(--color-secondary, #f0c040); }
.ach-wall-name { font-size: 13px; font-weight: 800; }
.ach-wall-desc { font-size: 11px; color: var(--color-text-dim); margin-top: 2px; min-height: 28px; }
.ach-wall-bar {
  height: 4px; border-radius: 2px; margin-top: 6px;
  background: rgba(255, 255, 255, 0.1); overflow: hidden;
}
.ach-wall-bar i { display: block; height: 100%; background: var(--color-defender, #4ecca3); }
.ach-wall-state { font-size: 10px; color: var(--color-text-dim); margin-top: 4px; font-weight: 700; }
.ach-wall-card.worn .ach-wall-state { color: var(--color-secondary, #f0c040); }


/* --- Friends: add-by-id, requests, actions (job 073) --------------------- */
.friend-add-row { display: flex; gap: 8px; margin-bottom: 8px; }
.friend-add-row input { flex: 1 1 auto; padding: 10px; }
.friend-add-row .btn { min-height: 42px; padding: 0 16px; }
.friend-me {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11px; color: var(--color-text-dim); margin-bottom: 8px;
}
.friend-me code {
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.06);
  word-break: break-all;
}
.friend-copy { padding: 3px 10px; min-height: 30px; font-size: 11px; }
.friend-msg { min-height: 16px; font-size: 12px; margin-bottom: 6px; }
.friend-msg.ok { color: var(--color-defender, #4ecca3); }
.friend-msg.bad { color: var(--color-attacker, #e94560); }
.friend-mini { padding: 3px 10px; min-height: 32px; font-size: 11px; }
.friend-pending { font-size: 11px; color: var(--color-text-dim); }
