/* --- CSS Design Tokens (Harmonious Curated HSL Palette) --- */
:root {
  --bg-deep: hsl(222, 24%, 7%);
  --panel-bg: hsla(222, 24%, 12%, 0.65);
  --border-light: hsla(222, 20%, 22%, 0.35);
  --border-active: hsla(43, 85%, 55%, 0.55);
  
  --primary-gold: hsl(43, 85%, 55%);
  --primary-gold-glow: hsla(43, 85%, 55%, 0.25);

  /* P2: Changed from neon green to thematic blood-crimson. Used by the HP
     bar and its glow; misleading for anything outside the health context. */
  --hp-green: hsl(355, 72%, 48%);
  --hp-green-glow: hsla(355, 72%, 48%, 0.35);
  --threat-red: hsl(355, 85%, 55%);

  /* v1.8.2 — semantic palette so "friendly", "danger", and "success" don't
     accidentally render in the wrong colour (e.g. a friendly NPC in crimson).
     Prefer these over reaching for --hp-green / --threat-red. */
  --color-friendly-gold: hsl(43, 85%, 60%);
  --color-friendly-gold-soft: hsla(43, 85%, 55%, 0.18);
  --color-success-green: hsl(142, 55%, 48%);
  --color-danger-crimson: hsl(355, 72%, 48%);
  --color-quest-active: hsl(43, 85%, 60%);
  --color-quest-completed: hsl(142, 45%, 55%);
  --color-quest-failed: hsl(355, 70%, 60%);
  --color-quest-hidden: hsl(220, 14%, 58%);
  
  --text-primary: hsl(220, 20%, 94%);
  --text-muted: hsl(220, 10%, 65%);
  
  /* P1: Fantasy serif for all section/panel headers */
  --font-fantasy: 'Cinzel', serif;
  
  --transition-smooth: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  --shadow-premium: 0 12px 40px -10px rgba(0, 0, 0, 0.6);
}

/* --- Base Framework Resets --- */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: var(--bg-deep);
  color: var(--text-primary);
  font-family: 'Inter', sans-serif;
  overflow: hidden;
  /* v3.0.0-alpha.2.1 — 100dvh ("dynamic viewport height") fixes the
     iOS Safari / Chrome-mobile bug where 100vh includes the URL bar
     and the bottom of the page (where the input lives) gets clipped. */
  height: 100dvh;
  position: relative;
}

/* P1: title-font now uses Cinzel (fantasy serif) for section headers to elevate the thematic register */
.title-font {
  font-family: var(--font-fantasy);
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* --- Ambient Backdrops Glows --- */
.ambient-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(120px);
  z-index: -1;
  opacity: 0.15;
}
.glow-1 {
  width: 400px;
  height: 400px;
  background: var(--primary-gold);
  top: -100px;
  left: 10%;
}
.glow-2 {
  width: 500px;
  height: 500px;
  background: var(--threat-red);
  bottom: -150px;
  right: 15%;
}

/* --- Premium Glassmorphism Panels --- */
.glass-panel {
  background: var(--panel-bg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  /* P1: Softer, subtler border — panels share the same surface, no harsh seam */
  border: 1px solid hsla(222, 20%, 25%, 0.28);
  border-radius: 16px;
  box-shadow: var(--shadow-premium);
  transition: var(--transition-smooth);
}

.glass-panel:hover {
  border-color: hsla(222, 20%, 30%, 0.38);
}

/* --- Main Layout Grid --- */
.game-container {
  display: grid;
  /* v1.8.7 — Hero HUD (left) bumped 280→320 so the character sheet, paperdoll
     and backpack labels don't crowd the column.
     v3.0.0-alpha.2.1 — minmax(0, 1fr) for the center column lets it shrink
     below its intrinsic content width when the viewport is narrow, instead
     of pushing the whole layout horizontally past the body's overflow-hidden
     (which was clipping the input box on smaller windows). */
  grid-template-columns: 320px minmax(0, 1fr) 400px;
  /* P2: All columns must be the same height — align-items stretch is default for grid */
  align-items: stretch;
  gap: 16px;
  padding: 16px;
  /* v3.0.0-alpha.2.1 — 100dvh keeps the layout pinned to the visible viewport
     even when mobile browser chrome (URL bar, toolbar) shows or hides. */
  height: 100dvh;
  max-width: 1800px;
  margin: 0 auto;
  /* min-width: 0 on the grid container ensures the same shrinkability
     applies if it's ever nested inside a flex parent. */
  min-width: 0;
}

/* v3.0.0-alpha.2.1 — Every direct child of the 3-col grid gets min-width:0
   so its flex contents (chat header, character card, board) can actually
   shrink to the column width instead of forcing horizontal overflow. */
.game-container > .sidebar,
.game-container > .chat-terminal,
.game-container > .tactical-board {
  min-width: 0;
  min-height: 0;
}

/* --- Sidebar HUD & widgets --- */
.sidebar {
  display: flex;
  flex-direction: column;
  padding: 16px;
  overflow-y: auto;
  gap: 16px;
}

.sidebar-header {
  border-bottom: 1px solid var(--border-light);
  padding-bottom: 8px;
  margin-bottom: 8px;
}

.sidebar-header h2 {
  font-size: 1.25rem;
  color: var(--primary-gold);
}

.character-card {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-light);
  border-radius: 12px;
  padding: 12px;
}

.character-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}

.char-avatar {
  font-size: 2rem;
  background: rgba(255, 255, 255, 0.05);
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}

.character-meta h3 {
  font-size: 1.1rem;
}

.badge {
  display: inline-block;
  background: rgba(255, 255, 255, 0.07);
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
}

.badge.friendly {
  background: var(--color-friendly-gold-soft);
  color: var(--color-friendly-gold);
  border: 1px solid hsla(43, 85%, 55%, 0.45);
}
.badge.allied {
  background: hsla(142, 55%, 48%, 0.16);
  color: var(--color-success-green);
  border: 1px solid hsla(142, 55%, 48%, 0.45);
}
.badge.hostile,
.badge.unfriendly {
  background: hsla(355, 72%, 48%, 0.16);
  color: var(--color-danger-crimson);
  border: 1px solid hsla(355, 72%, 48%, 0.45);
}

.badge.active-combat {
  /* NOTE: the rgba(355, ...) channel values below are pre-existing legacy CSS
     and are out-of-range (RGB max is 255). Browsers clamp the red channel to
     255 (or invalidate the declaration entirely), so the actual painted
     background is NOT the intended translucent-red the author meant. The
     original `color: var(--threat-red)` on whatever-the-clamped-rgba-renders
     measured 3.58:1 in axe-core and failed WCAG AA. The Story 5.3 color
     shift below clears 4.5:1 against the effective backdrop. Fixing the
     rgba() channels themselves (e.g., to hsla(355, 85%, 55%, 0.15)) is
     deferred — see deferred-work.md §"Deferred from: code review of
     story-5-3-...". */
  background: rgba(355, 85, 55, 0.15);
  color: hsl(355, 90%, 75%);
  border: 1px solid rgba(355, 85, 55, 0.3);
}

/* HP Progress Bars */
.stat-group {
  margin-bottom: 12px;
}

.stat-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.8rem;
  margin-bottom: 4px;
}

.progress-bar-bg {
  background: rgba(255, 255, 255, 0.05);
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  border-radius: 4px;
  transition: width 0.4s ease;
}

/* P2: Crimson HP bar — matches the medieval dark fantasy aesthetic far better than neon green */
.progress-hp {
  background: linear-gradient(90deg, hsl(355, 65%, 35%), hsl(355, 72%, 52%));
  box-shadow: 0 0 8px var(--hp-green-glow);
}

.stats-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 12px;
}

.mini-stat {
  background: rgba(0, 0, 0, 0.2);
  padding: 6px;
  border-radius: 8px;
  text-align: center;
  font-size: 0.8rem;
}

.mini-stat .label {
  display: block;
  font-size: 0.65rem;
  color: var(--text-muted);
  margin-bottom: 2px;
}

.sub-section {
  border-top: 1px solid var(--border-light);
  padding-top: 12px;
  margin-top: 12px;
}

.sub-section h4 {
  font-size: 0.85rem;
  color: var(--primary-gold);
  margin-bottom: 6px;
}

.inventory-items, .quest-items {
  list-style: none;
  font-size: 0.8rem;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.quest-items li {
  padding: 4px 0;
  color: var(--text-muted);
}

/* --- Paperdoll equipment slots --- */
.paperdoll {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 4px;
}
.paperdoll .slot {
  background: rgba(0, 0, 0, 0.25);
  border: 1px dashed var(--border-light);
  border-radius: 8px;
  padding: 8px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 0.65rem;
  color: var(--text-muted);
  text-align: center;
  cursor: default;
  min-height: 56px;
  transition: all 0.15s ease;
}
.paperdoll .slot .slot-icon {
  font-size: 1.4rem;
  filter: grayscale(0.6) opacity(0.4);
  transition: all 0.18s ease;
}
.paperdoll .slot.filled {
  background: rgba(212, 175, 55, 0.10);
  border: 1px solid rgba(212, 175, 55, 0.55);
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.18);
  color: var(--text-primary);
  cursor: pointer;
}
.paperdoll .slot.filled .slot-icon {
  filter: none;
}
.paperdoll .slot.filled:hover {
  background: rgba(212, 175, 55, 0.16);
  transform: translateY(-1px);
}
.paperdoll .slot .slot-label {
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  font-size: 0.6rem;
}
.paperdoll .slot.filled .slot-label {
  color: var(--primary-gold);
  opacity: 0.85;
}
/* v1.8.2 — item name lives in a second line so the slot title ("MAIN",
   "BODY", …) is never overwritten. Long names ellipsise instead of
   pushing the slot wider. */
.paperdoll .slot .slot-item {
  font-size: 0.62rem;
  line-height: 1.1;
  color: var(--text-primary);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 2px;
}

.inv-item {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border-light);
  border-radius: 6px;
  color: var(--text-primary);
  transition: all 0.15s ease;
}
.inv-item:hover {
  background: rgba(212, 175, 55, 0.06);
  border-color: rgba(212, 175, 55, 0.4);
}
.inv-item.equipped {
  border-color: rgba(212, 175, 55, 0.55);
  background: rgba(212, 175, 55, 0.10);
  box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.35) inset;
}
.inv-item.equipped::before {
  content: '★';
  position: absolute;
  margin-left: -10px;
  margin-top: -10px;
  color: var(--primary-gold);
  font-size: 0.7rem;
  text-shadow: 0 0 4px rgba(212, 175, 55, 0.7);
}
.inv-icon { font-size: 1.1rem; line-height: 1; }
.inv-name { flex: 1; font-size: 0.82rem; }
.inv-qty {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-size: 0.72rem;
}

/* v1.8.2 — quest log items rendered as `[📜 Title] [status pill]`.
   Replaces the old "(STATUS)" parenthetical strings with badge pills
   that share the same colour grammar as the rest of the app. */
.quest-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 2px;
}
.quest-title {
  font-size: 0.82rem;
  text-transform: capitalize;
  letter-spacing: 0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.quest-row.active .quest-title {
  color: var(--primary-gold);
  font-weight: 600;
}
.quest-pill {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.quest-pill.active {
  color: var(--color-quest-active);
  background: hsla(43, 85%, 60%, 0.15);
  border-color: hsla(43, 85%, 60%, 0.45);
}
.quest-pill.completed {
  color: var(--color-quest-completed);
  background: hsla(142, 45%, 55%, 0.15);
  border-color: hsla(142, 45%, 55%, 0.45);
}
.quest-pill.failed {
  color: var(--color-quest-failed);
  background: hsla(355, 70%, 60%, 0.15);
  border-color: hsla(355, 70%, 60%, 0.45);
}
.quest-pill.hidden {
  color: var(--color-quest-hidden);
  background: hsla(220, 14%, 58%, 0.15);
  border-color: hsla(220, 14%, 58%, 0.45);
}

/* Legacy hook — older snapshots may still render with the old class name. */
.quest-active {
  color: var(--primary-gold) !important;
  font-weight: 500;
}

.relations-container {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.8rem;
}

.relation-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* --- Center Section: Narrative Chat Terminal --- */
.chat-terminal {
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative;
}

/* Floating "▼ N new" pill — visible when the user has scrolled up reading
   history and new GM/player messages have arrived they haven't seen. */
.new-messages-pill {
  position: absolute;
  bottom: 92px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--primary-gold);
  color: var(--bg-deep);
  border: none;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(212, 175, 55, 0.45);
  animation: pillSlideUp 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.new-messages-pill:hover { transform: translateX(-50%) translateY(-2px); }
@keyframes pillSlideUp {
  from { opacity: 0; transform: translateX(-50%) translateY(10px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.terminal-header {
  display: flex;
  align-items: center;
  padding: 16px;
  border-bottom: 1px solid var(--border-light);
  gap: 12px;
}

.status-indicator {
  width: 8px;
  height: 8px;
  background: var(--primary-gold);
  border-radius: 50%;
  box-shadow: 0 0 8px var(--primary-gold);
}

.terminal-header h1 {
  font-size: 1.35rem;
  flex: 1;
}

.campaign-date {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.message-feed {
  flex: 1 1 0;
  /* min-height: 0 is required for overflow to trigger on a flex child;
     without it the column flex parent grows to fit content and the scrollbar
     never appears. */
  min-height: 0;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.message {
  max-width: 85%;
  border-radius: 12px;
  padding: 12px 16px;
  line-height: 1.5;
  font-size: 0.95rem;
  animation: messageSlideIn 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}

.system-msg {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-light);
  align-self: flex-start;
  color: var(--text-muted);
}

/* --- Collapsible system messages panel --- */
.system-messages-panel {
  position: sticky;
  top: -16px; /* Stick perfectly at the top edge of the scroll container */
  margin: -16px -16px 16px -16px; /* Break out of .message-feed padding */
  width: auto;
  z-index: 5;
  background: rgba(15, 16, 22, 0.95);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--border-light);
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.system-messages-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  color: var(--text-muted);
  padding: 8px 12px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.8rem;
  text-align: left;
  transition: color 0.15s ease;
}
.system-messages-toggle:hover { color: var(--primary-gold); }
.system-messages-toggle .chevron {
  display: inline-block;
  font-size: 0.65rem;
  transition: transform 0.18s ease;
  width: 10px;
}
.system-messages-panel:not(.collapsed) .system-messages-toggle .chevron {
  transform: rotate(90deg);
}
.system-messages-toggle .badge {
  margin-left: auto;
  background: rgba(212, 175, 55, 0.15);
  border: 1px solid rgba(212, 175, 55, 0.4);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.7rem;
  color: var(--primary-gold);
  min-width: 22px;
  text-align: center;
}
.system-messages-toggle .badge.is-empty {
  opacity: 0.4;
}
.system-messages-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 2px 14px 10px;
  max-height: 220px;
  overflow-y: auto;
  border-top: 1px solid var(--border-light);
}
.system-messages-panel.collapsed .system-messages-body {
  display: none;
}
.system-msg-line {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.4;
  font-family: 'Inter', monospace;
}
.system-msg-line .system-msg-time {
  opacity: 0.5;
  font-variant-numeric: tabular-nums;
  margin-right: 6px;
}

/* --- Header controls (room badge, invite, voice, date) --- */
.room-badge {
  font-size: 0.75rem;
  background: rgba(212, 175, 55, 0.15);
  border: 1px solid rgba(212, 175, 55, 0.4);
  padding: 2px 8px;
  border-radius: 4px;
  color: var(--primary-gold);
  margin-left: 8px;
  font-weight: normal;
  font-family: 'Inter', sans-serif;
}
.scene-chip {
  font-size: 0.78rem;
  background: rgba(145, 200, 145, 0.10);
  border: 1px solid rgba(145, 200, 145, 0.35);
  padding: 2px 8px;
  border-radius: 4px;
  color: hsl(140, 50%, 75%);
  margin-left: 6px;
  font-weight: normal;
  font-family: 'Inter', sans-serif;
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-block;
  vertical-align: middle;
}
.scene-chip.hidden { display: none; }
.header-icon-btn {
  background: transparent;
  border: 1px solid var(--border-light);
  color: var(--text-muted);
  border-radius: 6px;
  padding: 2px 8px;
  cursor: pointer;
  font-size: 0.9rem;
  margin-left: 6px;
  transition: all 0.15s ease;
}
.header-icon-btn:hover {
  border-color: var(--primary-gold);
  color: var(--primary-gold);
  background: rgba(212, 175, 55, 0.08);
}
.header-controls {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-left: auto;
}
.voice-toggle-label {
  font-size: 0.8rem;
  color: #c8c8cb;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 4px;
  user-select: none;
}
.voice-toggle-label input[type="checkbox"] {
  cursor: pointer;
  accent-color: #d4af37;
}
.voice-picker {
  background: rgba(0,0,0,0.4);
  border: 1px solid var(--border-light);
  color: var(--text-primary);
  border-radius: 6px;
  padding: 4px 6px;
  font-size: 0.75rem;
  font-family: inherit;
  max-width: 180px;
  cursor: pointer;
}
.voice-picker:focus {
  outline: none;
  border-color: var(--primary-gold);
}
.voice-picker:hover { border-color: rgba(212, 175, 55, 0.4); }

/* --- Invite modal --- */
.invite-url-row {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}
.invite-url-row input {
  flex: 1;
  background: rgba(0,0,0,0.4);
  border: 1px solid var(--border-light);
  color: var(--text-primary);
  padding: 8px 12px;
  border-radius: 6px;
  font-family: 'Inter', monospace;
  font-size: 0.85rem;
}
.invite-url-row input:focus {
  outline: none;
  border-color: var(--primary-gold);
}

/* --- Character creation portrait preview --- */
.char-preview-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px;
  background: rgba(212, 175, 55, 0.05);
  border: 1px solid rgba(212, 175, 55, 0.3);
  border-radius: 10px;
  margin-bottom: 12px;
}
.char-portrait-preview {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 2px solid var(--primary-gold);
  background: rgba(0,0,0,0.4);
  flex-shrink: 0;
  box-shadow: 0 0 14px rgba(212, 175, 55, 0.35);
}
.char-preview-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
}
.char-preview-name {
  font-family: 'Outfit', sans-serif;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--primary-gold);
}
.char-preview-class {
  font-size: 0.85rem;
  color: var(--text-muted);
}
.btn-reroll {
  align-self: flex-start;
  margin-top: 4px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border-light);
  color: var(--text-muted);
  font-size: 0.72rem;
  padding: 3px 8px;
  border-radius: 5px;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s ease;
}
.btn-reroll:hover { border-color: var(--primary-gold); color: var(--primary-gold); }
.btn-reroll:disabled {
  opacity: 0.5;
  cursor: wait;
}
.char-preview-actions {
  display: flex;
  gap: 6px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.char-backstory-input {
  background: rgba(0,0,0,0.4);
  border: 1px solid var(--border-light);
  color: var(--text-primary);
  padding: 8px 10px;
  border-radius: 6px;
  font-family: 'Inter', sans-serif;
  font-size: 0.85rem;
  width: 100%;
  resize: vertical;
  line-height: 1.4;
}
.char-backstory-input:focus {
  outline: none;
  border-color: var(--primary-gold);
}

/* --- Settings modal sections + sliders --- */
.settings-section {
  border-top: 1px solid var(--border-light);
  padding-top: 14px;
  margin-top: 14px;
}
.settings-section:first-of-type {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}
.settings-section h3 {
  font-size: 0.95rem;
  color: var(--primary-gold);
  margin-bottom: 10px;
}
.slider-value {
  float: right;
  font-variant-numeric: tabular-nums;
  color: var(--primary-gold);
  font-size: 0.8rem;
  font-weight: 500;
}
input[type="range"] {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: rgba(255,255,255,0.08);
  border-radius: 2px;
  outline: none;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--primary-gold);
  cursor: pointer;
  border: 2px solid var(--bg-deep);
  box-shadow: 0 0 8px rgba(212, 175, 55, 0.5);
  transition: transform 0.12s ease;
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.15); }
input[type="range"]::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--primary-gold);
  cursor: pointer;
  border: 2px solid var(--bg-deep);
  box-shadow: 0 0 8px rgba(212, 175, 55, 0.5);
}

/* --- Danger button + zone (Wipe & Restart Campaign) ---
   v1.8.2: heavier crimson framing + ⚠️ label so the destructive-action area
   reads as a hazard zone (was previously a 0.25-alpha dashed line that the
   eye glided past). Button itself shifted from "♻️ refresh" iconography
   (which colour-codes as safe/green) to "🗑️ wipe" iconography. */
.danger-zone {
  margin-top: 12px;
  padding: 10px 12px 12px;
  border: 1px dashed hsla(355, 72%, 48%, 0.55);
  border-radius: 8px;
  background: hsla(355, 72%, 48%, 0.04);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* v3.3.0 — Scene image strip. */
.scene-image-strip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin: 12px 16px 0;
  padding: 8px;
  border: 1px solid hsla(220, 30%, 30%, 0.4);
  border-radius: 10px;
  background: hsla(222, 30%, 8%, 0.6);
  flex-shrink: 0;
}
/* v3.59.0 — Left-rail variant. Lives inside the party HUD column, under
   the backpack. Sits flush with the column instead of using the
   central-column margins. */
.scene-image-strip.scene-image-strip-rail {
  margin: 8px 0 0;
  padding: 6px;
  width: 100%;
}
.scene-image-strip.hidden { display: none; }
.scene-image-strip img {
  width: 100%;
  max-width: 480px;
  height: auto;
  border-radius: 8px;
  display: block;
  /* Soft fade-in when first arriving from the server. */
  animation: sceneImageFadeIn 0.45s ease;
}
.scene-image-strip-rail img {
  max-width: 100%;
}
@keyframes sceneImageFadeIn {
  from { opacity: 0; transform: scale(0.985); }
  to   { opacity: 1; transform: scale(1); }
}
/* Story 5.5 — reduced-motion fallback for the scene-art fade-in (AC #4).
   The keyframe stays defined (for non-reduce browsers); the animation
   simply runs instantly under reduced-motion so the image still appears
   but never travels through the ken-burns-style scale. */
@media (prefers-reduced-motion: reduce) {
  .scene-image-strip img {
    animation: none !important;
  }
}
.scene-image-label {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* v3.16.0 — Achievement toast + in-feed unlock callout. */
.floating-achievement-toast {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  left: 50%;
  z-index: 200;
  background: linear-gradient(135deg, hsla(43, 70%, 35%, 0.95), hsla(43, 55%, 20%, 0.95));
  border: 1px solid hsla(43, 85%, 60%, 0.8);
  color: #fff;
  padding: 12px 16px;
  border-radius: 10px;
  box-shadow: 0 8px 22px hsla(43, 85%, 50%, 0.45);
  display: flex;
  gap: 12px;
  align-items: center;
  font-size: 0.85rem;
  opacity: 0;
  transform: translateX(-50%) translateY(-20px);
  transition: opacity 0.3s ease, transform 0.3s ease;
  width: max-content;
  max-width: min(360px, calc(100vw - 32px));
}
.floating-achievement-toast.visible { opacity: 1; transform: translateX(-50%) translateY(0); }
.achievement-toast-icon { font-size: 1.6rem; }
.achievement-toast-body strong { display: block; color: hsl(43, 85%, 80%); }
.achievement-toast-body em { display: block; font-style: italic; color: #ddd; font-size: 0.78rem; margin-top: 2px; }
.message.achievement-msg {
  background: hsla(43, 60%, 30%, 0.18);
  border: 1px solid hsla(43, 70%, 55%, 0.4);
  color: hsl(43, 30%, 92%);
}

/* v3.15.0 — Compact per-character dice stats widget. */
.dice-stats {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 4px 8px;
  background: rgba(212, 175, 55, 0.04);
  border: 1px solid rgba(212, 175, 55, 0.15);
  border-radius: 6px;
  font-size: 0.72rem;
  color: var(--text-muted);
}
.dice-stats.hidden { display: none; }
.dice-stats-text strong { color: var(--text-primary); }

/* v3.14.0 — Next-level milestones preview + curated feature picker. */
.level-milestones {
  margin: 8px 0 0;
  padding: 6px 10px;
  background: rgba(212, 175, 55, 0.06);
  border: 1px solid rgba(212, 175, 55, 0.18);
  border-radius: 6px;
}
.level-milestones.hidden { display: none; }
.level-milestones-label {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.level-milestones-list {
  list-style: none;
  margin: 0; padding: 0;
  font-size: 0.78rem;
}
.level-milestones-list li {
  padding: 1px 0;
  color: var(--text-primary);
}
.levelup-curated {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.levelup-curated .curated-feature-btn {
  background: rgba(212, 175, 55, 0.1);
  border: 1px solid rgba(212, 175, 55, 0.35);
  color: var(--text-primary);
  padding: 4px 10px;
  border-radius: 14px;
  font-size: 0.78rem;
  cursor: pointer;
  transition: all 0.15s ease;
}
.levelup-curated .curated-feature-btn:hover {
  background: rgba(212, 175, 55, 0.2);
  border-color: var(--primary-gold);
}

/* v3.13.0 — Trophy room launcher + modal. */
.trophy-room-launcher {
  margin-top: 12px;
  padding: 10px 12px 12px;
  border: 1px dashed hsla(43, 70%, 55%, 0.5);
  border-radius: 8px;
  background: hsla(43, 60%, 45%, 0.05);
  display: flex;
  justify-content: stretch;
}
.trophy-room-launcher { flex-wrap: wrap; gap: 8px; }
.trophy-room-launcher button { flex: 1 1 calc(50% - 4px); min-width: 120px; }
.recap-card { max-width: 680px; }
.trophy-card { max-width: 600px; }
.trophy-list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.trophy-list li {
  padding: 6px 10px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 0.85rem;
}
.trophy-list li.empty {
  color: var(--text-muted);
  font-style: italic;
  border-style: dashed;
  background: transparent;
}
.trophy-list li.rarity-tier { border-left: var(--rarity-border-treatment) var(--rarity-color); }
.trophy-list .trophy-meta { font-size: 0.7rem; color: var(--text-muted); margin-left: 8px; }

/* v3.9.0 — Loot policy controls. Creator-only section is hidden when
   the local browser doesn't hold a creator token. */
.creator-only-badge {
  font-size: 0.65rem;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin-left: 6px;
}
#loot-policy-section.hidden { display: none; }

/* v3.7.0 — OOC chat lane. The toggle button next to the input flips
   the input row into out-of-character mode; OOC bubbles render in the
   chat feed but with a muted purple tint so they're never confused
   with GM narration or player intents. */
.btn-ooc-toggle {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid hsla(263, 30%, 50%, 0.45);
  color: hsl(263, 50%, 80%);
  padding: 8px 10px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: all 0.18s ease;
}
.btn-ooc-toggle[aria-pressed="true"] {
  background: hsla(263, 60%, 50%, 0.22);
  border-color: hsla(263, 60%, 65%, 0.7);
  color: hsl(263, 70%, 90%);
}
.input-area.ooc-mode input { border-color: hsla(263, 60%, 65%, 0.55); color: hsl(263, 30%, 95%); }
.input-area.ooc-mode .btn-primary {
  background: linear-gradient(135deg, hsl(263, 55%, 55%), hsl(263, 70%, 40%));
  color: #fff;
}

.message.ooc-msg {
  background: hsla(263, 50%, 40%, 0.12);
  border: 1px dashed hsla(263, 50%, 55%, 0.4);
  align-self: flex-start;
  color: hsl(263, 25%, 85%);
  font-style: italic;
  font-size: 0.85rem;
  max-width: 70%;
}
.message.ooc-msg .ooc-label {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: hsla(263, 60%, 50%, 0.22);
  color: hsl(263, 70%, 90%);
  font-size: 0.65rem;
  font-style: normal;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
  vertical-align: middle;
}
.message.ooc-msg .ooc-sender { font-weight: 600; font-style: normal; color: hsl(263, 60%, 90%); margin-right: 4px; }

/* v3.6.0 — Encounter HUD. Compact panel above the tactical canvas that
   surfaces the encounter archetype, round number, win/loss conditions,
   and objective state. Visible only during active combat. */
.encounter-hud {
  margin: 8px 16px 0;
  padding: 8px 10px;
  border: 1px solid hsla(43, 60%, 50%, 0.4);
  border-radius: 8px;
  background: hsla(43, 50%, 35%, 0.07);
  font-size: 0.78rem;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-shrink: 0;
}
.encounter-hud.hidden { display: none; }
.encounter-hud-row { display: flex; gap: 12px; align-items: flex-start; }
.encounter-hud-header { justify-content: space-between; align-items: baseline; }
.encounter-hud-archetype {
  font-family: var(--font-fantasy);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: capitalize;
  color: var(--primary-gold);
}
.encounter-hud-round {
  font-size: 0.72rem;
  color: var(--text-muted);
  padding: 1px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
}
.encounter-hud-rules { display: flex; gap: 12px; }
.encounter-hud-col { flex: 1; min-width: 0; }
.encounter-hud-col-label {
  display: block;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 2px;
}
.encounter-hud-list,
.encounter-hud-objectives {
  list-style: none;
  margin: 0; padding: 0;
}
.encounter-hud-list li,
.encounter-hud-objectives li {
  font-size: 0.74rem;
  line-height: 1.35;
  padding: 1px 0;
  color: var(--text-primary);
}
.encounter-hud-objectives li.reached  { text-decoration: line-through; color: var(--color-success-green); }
.encounter-hud-objectives li.foe-took { color: var(--color-danger-crimson); }
.encounter-hud-objectives-wrap.hidden { display: none; }

/* v3.5.0 — Death-save pip strip. Tucked next to the HP bar in the roster
   row of a downed PC. ◯ pips = successes (gold), ✗ pips = failures (red). */
.death-save-pips {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  margin-left: 8px;
  font-size: 0.72rem;
  vertical-align: middle;
  letter-spacing: 0.02em;
}
.death-save-pips .ds-successes,
.death-save-pips .ds-failures {
  display: inline-flex;
  gap: 2px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.death-save-pips .ds-successes { color: var(--primary-gold); }
.death-save-pips .ds-failures  { color: var(--color-danger-crimson); }
.death-save-pips .ds-stabilized {
  color: var(--color-success-green);
  font-weight: 600;
  margin-left: 4px;
}
.death-save-pips .ds-dead {
  color: var(--color-danger-crimson);
  font-weight: 700;
  margin-left: 4px;
  text-transform: uppercase;
}

/* v3.3.0 — Inline icons in the backpack list. */
.inventory-items .item-icon {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  margin-right: 8px;
  vertical-align: middle;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.04);
}

/* v3.12.0 — Loot rarity tiers. Drive border colour + a faint glow on
   rare+ items so they pop on the backpack list AND in the loot modal.
   Story 5.6 — also drive border *treatment* (thickness + line-style) per
   tier so rarity is legible without color discrimination. */
.rarity-common    { --rarity-color: hsl(0, 0%, 60%);       --rarity-glow: 0 0 0 rgba(0,0,0,0);            --rarity-border-treatment: 3px solid; }
.rarity-uncommon  { --rarity-color: hsl(142, 55%, 58%);    --rarity-glow: 0 0 6px hsla(142, 55%, 58%, 0.45); --rarity-border-treatment: 3px solid; }
.rarity-rare      { --rarity-color: hsl(212, 70%, 65%);    --rarity-glow: 0 0 8px hsla(212, 70%, 65%, 0.55); --rarity-border-treatment: 3px solid; }
.rarity-epic      { --rarity-color: hsl(280, 65%, 70%);    --rarity-glow: 0 0 10px hsla(280, 65%, 70%, 0.6); --rarity-border-treatment: 4px solid; }
.rarity-legendary { --rarity-color: hsl(36, 95%, 60%);     --rarity-glow: 0 0 12px hsla(36, 95%, 60%, 0.7); --rarity-border-treatment: 4px double; }
.inv-item.rarity-tier {
  border-left: var(--rarity-border-treatment) var(--rarity-color);
  padding-left: 6px;
  box-shadow: var(--rarity-glow);
  border-radius: 3px;
}
.inv-item.rarity-tier .rarity-label {
  display: inline-block;
  margin-left: 6px;
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rarity-color);
  font-weight: 700;
  vertical-align: middle;
}
.loot-row.rarity-tier {
  border-left: var(--rarity-border-treatment) var(--rarity-color);
  box-shadow: var(--rarity-glow);
}
.loot-row.rarity-tier .loot-rarity-label {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 6px;
  border: 1px solid var(--rarity-color);
  border-radius: 4px;
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rarity-color);
  font-weight: 700;
}
/* "Shared reveal" pulse for rare+ drops — a brief halo around the loot
   modal card when it first opens. Drives anticipation without blocking. */
@keyframes rarePulse {
  0%   { box-shadow: 0 0 0 0 var(--rarity-color); }
  60%  { box-shadow: 0 0 30px 6px var(--rarity-color); }
  100% { box-shadow: 0 0 0 0 var(--rarity-color); }
}
.modal-card.rarity-pulse-rare      { animation: rarePulse 1.6s ease 1; --rarity-color: hsl(212, 70%, 65%); }
.modal-card.rarity-pulse-epic      { animation: rarePulse 1.6s ease 2; --rarity-color: hsl(280, 65%, 70%); }
.modal-card.rarity-pulse-legendary { animation: rarePulse 1.6s ease 3; --rarity-color: hsl(36, 95%, 60%); }

/* Story 3.3 (AC #5 / Story 5.5 reduced-motion contract) — suppress rarePulse halo under reduce.
   Rarity label text and borders remain visible (non-motion signals preserved). */
@media (prefers-reduced-motion: reduce) {
  .modal-card.rarity-pulse-rare,
  .modal-card.rarity-pulse-epic,
  .modal-card.rarity-pulse-legendary { animation: none !important; }
}

/* v3.1.0 — Split-party UI styles. */
.split-party-controls {
  margin-top: 12px;
  padding: 10px 12px 12px;
  border: 1px dashed hsla(220, 72%, 60%, 0.55);
  border-radius: 8px;
  background: hsla(220, 72%, 60%, 0.04);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.split-party-controls.hidden { display: none; }
.split-party-controls-label {
  font-family: var(--font-fantasy);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: hsl(220, 72%, 75%);
  text-transform: uppercase;
}

/* Banner at the top of the chat terminal — visible when the party is split. */
.split-party-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin: 12px 16px 0;
  background: linear-gradient(90deg, hsla(220, 72%, 60%, 0.15), hsla(280, 50%, 50%, 0.12));
  border: 1px solid hsla(220, 72%, 60%, 0.5);
  border-radius: 10px;
  font-size: 0.85rem;
  flex-shrink: 0;
}
.split-party-banner.hidden { display: none; }
.split-party-icon { font-size: 1.15rem; }
.split-party-status { flex: 1; min-width: 0; }
.split-party-status strong { color: hsl(220, 72%, 80%); }
.split-party-counter {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  font-size: 0.75rem;
  color: var(--text-muted);
}
.split-party-tracks {
  display: flex;
  gap: 6px;
  font-size: 0.75rem;
  color: var(--text-muted);
}
.split-party-track-chip {
  padding: 2px 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  white-space: nowrap;
}
.split-party-track-chip.queued {
  background: hsla(43, 85%, 55%, 0.16);
  border-color: hsla(43, 85%, 55%, 0.45);
  color: var(--primary-gold);
}
.split-party-merge-btn {
  background: hsla(220, 72%, 60%, 0.18);
  border: 1px solid hsla(220, 72%, 60%, 0.5);
  color: hsl(220, 72%, 85%);
  padding: 5px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  flex-shrink: 0;
}
.split-party-merge-btn:hover { background: hsla(220, 72%, 60%, 0.28); }
.split-party-merge-btn.hidden { display: none; }

/* Modal: Track A / Track B member rows */
.split-track-members {
  flex-wrap: wrap;
  gap: 6px;
}
.split-track-member-chip {
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 0.8rem;
  color: var(--text-primary);
  cursor: pointer;
  transition: all 0.18s ease;
  user-select: none;
}
.split-track-member-chip.assigned {
  background: hsla(220, 72%, 60%, 0.18);
  border-color: hsla(220, 72%, 60%, 0.5);
  color: hsl(220, 72%, 85%);
}
.split-party-error {
  margin-top: 8px;
  padding: 6px 10px;
  border-radius: 6px;
  background: hsla(355, 72%, 48%, 0.12);
  border: 1px solid hsla(355, 72%, 48%, 0.45);
  color: hsl(355, 72%, 80%);
  font-size: 0.8rem;
}
.split-party-error.hidden { display: none; }

/* Party-roster per-character track badge — only visible when split. */
.party-track-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}
.party-track-badge.track-a {
  background: hsla(200, 70%, 55%, 0.2);
  color: hsl(200, 70%, 80%);
  border: 1px solid hsla(200, 70%, 55%, 0.5);
}
.party-track-badge.track-b {
  background: hsla(330, 60%, 60%, 0.2);
  color: hsl(330, 60%, 85%);
  border: 1px solid hsla(330, 60%, 60%, 0.5);
}
.danger-zone-label {
  font-family: var(--font-fantasy);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--color-danger-crimson);
  text-transform: uppercase;
}
.btn-danger {
  background: hsla(355, 72%, 48%, 0.14);
  border: 1px solid hsla(355, 72%, 48%, 0.55);
  color: #ffd0d0;
  padding: 10px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  transition: all 0.15s ease;
}
.btn-danger:hover {
  background: hsla(355, 72%, 48%, 0.28);
  border-color: hsla(355, 72%, 48%, 0.85);
  color: #ffe8e8;
  box-shadow: 0 0 12px hsla(355, 72%, 48%, 0.35);
}

.gm-msg {
  background: rgba(43, 85, 55, 0.03);
  border: 1px solid hsla(43, 85, 55, 0.15);
  border-left: 4px solid var(--primary-gold);
  align-self: flex-start;
  color: var(--text-primary);
}

.player-msg {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border-light);
  align-self: flex-end;
  color: var(--text-primary);
}

.msg-header {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
  color: var(--primary-gold);
}

.input-area {
  padding: 16px;
  border-top: 1px solid var(--border-light);
  display: flex;
  gap: 12px;
  /* v3.0.0-alpha.2.1 — Never let the input bar shrink. The message-feed
     above it is flex:1 and gives up space first when the viewport is tight,
     so the input remains visible and reachable. */
  flex-shrink: 0;
  /* Same defensive trick: stop overflow children from pushing the row wider
     than the column. */
  min-width: 0;
}

.input-area input {
  /* Cap how much room the text input takes so the mic + submit buttons stay
     visible on narrow viewports. flex: 1 1 0 + min-width:0 lets it shrink. */
  min-width: 0;
}

.input-area input {
  flex: 1;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 12px;
  color: #fff;
  font-family: inherit;
  font-size: 0.95rem;
  transition: var(--transition-smooth);
}

.input-area input:focus {
  outline: none;
  border-color: var(--primary-gold);
  box-shadow: 0 0 10px var(--primary-gold-glow);
}

/* --- Buttons Styles --- */
.btn-primary {
  background: linear-gradient(135deg, var(--primary-gold), hsl(43, 85%, 45%));
  color: #000;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  padding: 12px 24px;
  cursor: pointer;
  font-family: inherit;
  transition: var(--transition-smooth);
}

.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 15px var(--primary-gold-glow);
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-primary);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 10px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  transition: var(--transition-smooth);
}

.btn-secondary:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--primary-gold);
}

/* --- Tactical Board Sidebar --- */
.tactical-board {
  display: flex;
  flex-direction: column;
  padding: 16px;
  gap: 16px;
}

.board-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border-light);
  padding-bottom: 8px;
}

.board-header h2 {
  font-size: 1.25rem;
}

/* v1.8.3 — stone-tile / parchment frame around the canvas. The frame uses a
   layered linear-gradient pattern (no external image) to suggest cut stone
   blocks at the canvas border. The drawn canvas inside still owns its own
   pixel buffer; `drawTacticalGrid` and `drawWorldMap` paint a programmatic
   stone / parchment fill at the top of every frame so the texture is
   consistent whether you scroll, zoom, or pan. */
.canvas-container {
  background-color: #15110b;
  background-image:
    /* subtle radial vignette */
    radial-gradient(ellipse at center, rgba(212, 175, 55, 0.04) 0%, transparent 70%),
    /* faint diagonal "stone grout" hatching */
    repeating-linear-gradient(45deg,
      rgba(0,0,0,0.18) 0,
      rgba(0,0,0,0.18) 2px,
      transparent 2px,
      transparent 18px),
    /* warm parchment base */
    linear-gradient(135deg, #1a1612 0%, #0e0b08 100%);
  border: 1px solid rgba(212, 175, 55, 0.25);
  box-shadow:
    inset 0 0 0 2px rgba(0,0,0,0.55),
    inset 0 0 24px rgba(0,0,0,0.7),
    0 4px 16px rgba(0,0,0,0.45);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  /* v3.55.0 (Spec 28 slice 2) — Canvas container grows to fill the
     available right-rail width with a min-height floor so a 6×6 scene
     doesn't squish vertically. ResizeObserver in app.js syncs the
     canvas backing-store dimensions to match. */
  width: 100%;
  aspect-ratio: 1 / 1;
  min-height: 360px;
  max-height: calc(100vh - 280px);
}

#battlefield-canvas {
  width: 100%;
  height: 100%;
  display: block;
  border-radius: 6px;
}

/* P2: Added breathing room so the tip text isn't cramped at the panel bottom */
.board-instructions {
  font-size: 0.75rem;
  color: var(--text-muted);
  text-align: center;
  line-height: 1.4;
  padding: 4px 8px 10px;
}

.action-buttons-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: auto;
}

/* --- Setup Modal Overlay --- */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.85);
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Allow the overlay itself to scroll on tiny viewports — falls back to a
     scrollbar around the card when even the card can't shrink any further. */
  overflow-y: auto;
  padding: 16px;
  backdrop-filter: blur(8px);
}

.modal-card {
  width: 500px;
  max-width: 90%;
  /* Cap to the viewport (minus the overlay padding above) so the card never
     extends below the fold; the body of the card scrolls internally. */
  max-height: calc(100dvh - 32px);
  padding: 24px;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  /* Smooth-scroll keeps the click-to-scroll-to-buttons UX nice on long forms. */
  scroll-behavior: smooth;
}

.modal-card h2 {
  font-size: 1.75rem;
  color: var(--primary-gold);
  margin-bottom: 8px;
}

.modal-card .description {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin-bottom: 24px;
}

.setup-section h3 {
  font-size: 1.1rem;
  margin-bottom: 12px;
}

.campaign-cards-container {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.architect-degraded-banner {
  background: rgba(255, 180, 60, 0.08);
  border: 1px solid rgba(255, 180, 60, 0.35);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
  font-size: 0.9rem;
  color: rgba(255, 220, 170, 0.95);
}

.architect-degraded-banner strong {
  color: rgba(255, 200, 100, 1);
  margin-right: 4px;
}

.concept-card {
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-light);
  border-radius: 10px;
  padding: 12px;
  cursor: pointer;
  transition: var(--transition-smooth);
}

.concept-card:hover {
  border-color: var(--primary-gold);
  background: rgba(255, 255, 255, 0.04);
}

.concept-card.active {
  border-color: var(--primary-gold);
  background: rgba(43, 85, 55, 0.04);
  box-shadow: 0 0 12px rgba(43, 85, 55, 0.2);
}

.concept-card h4 {
  margin-bottom: 4px;
}

.concept-card p {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 6px;
  line-height: 1.4;
}
.concept-detail {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 4px;
  line-height: 1.35;
}
.concept-detail strong {
  color: var(--primary-gold);
  font-weight: 500;
}
.concept-stats {
  display: flex;
  gap: 10px;
  margin-top: 8px;
  font-size: 0.72rem;
  color: var(--text-muted);
  opacity: 0.85;
}
.concept-stats span {
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border-light);
  padding: 1px 8px;
  border-radius: 999px;
}

.modal-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.form-row label {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.form-row input, .form-row select {
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 10px 14px;
  color: #fff;
  font-family: inherit;
  font-size: 0.95rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.form-row select {
  height: 46px;
  padding-right: 38px;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d4af37' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 16px;
  cursor: pointer;
}

.form-row select option {
  background-color: #12131a;
  color: #fff;
  padding: 12px;
}

.form-row input:focus, .form-row select:focus {
  outline: none;
  border-color: var(--primary-gold);
  box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.15);
}

.mt-2 {
  margin-top: 16px;
}

.hidden {
  display: none !important;
}

/* --- Dice Roll Spinner Overlay --- */
.dice-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.88);
  z-index: 420;
  display: none;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(4px);
}

.dice-card {
  width: 280px;
  padding: 24px;
  text-align: center;
  background: rgba(12, 14, 20, 0.96);
  border: 1px solid rgba(244, 211, 122, 0.42);
  box-shadow:
    0 24px 80px rgba(0, 0, 0, 0.72),
    0 0 36px rgba(212, 175, 55, 0.18);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ===========================================================================
   Dice Reveal — Story 1.7 / Spec-38 (cinematic 2D glyph reveal).
   Replaces the prior 3D physics dice (@3d-dice/dice-box + dice3d.js). The
   die silhouette is a hex clip-path; the numeral is set in --font-display
   (Cormorant Garamond, redesign cohort) or the system fallback (legacy).
   See public/app.js triggerDiceRollAnimation for the phase machine.
   =========================================================================== */

/* Tier-color defaults at :root so the cascade works for .dice-card AND its
   descendants AND the sibling .dice-vignette outside .dice-card. The
   html.use-redesign-ui block below overrides with the Moonlight & Ember
   palette. */
:root {
  --tier-color-crit-success: var(--primary-gold);
  --tier-color-crit-failure: #c92a2a;
  --tier-color-success: var(--primary-gold);
  --tier-color-partial: #c9a76a;
  --tier-color-failure: #888;
  --moonlight-edge: var(--primary-gold);
  --ember-edge: var(--primary-gold);
  --dice-tier-color: var(--primary-gold);
  --dice-tumble-duration: 1200ms;
}

.dice-wrapper {
  position: relative;
  margin: 24px 0 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 200px;
}

@media (min-width: 1024px) {
  .dice-wrapper {
    height: 240px;
  }
}

.dice-die {
  position: relative;
  width: 120px;
  height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 35% 28%, #18213a, #050810);
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  /* Dual edge — moonlight upper-left, ember lower-right — achieved via
     stacked drop-shadows on the .dice-die element. The silhouette itself
     uses radial-gradient for depth. */
  filter:
    drop-shadow(-2px -2px 0 var(--moonlight-edge))
    drop-shadow(2px 2px 0 var(--ember-edge))
    drop-shadow(0 0 18px rgba(0, 0, 0, 0.7));
  transform-origin: center center;
  will-change: transform;
}

@media (min-width: 1024px) {
  .dice-die {
    width: 160px;
    height: 160px;
  }
}

.dice-numeral {
  font-family: var(--font-display, 'Cormorant Garamond', 'Spectral', 'EB Garamond', Georgia, serif);
  font-weight: 600;
  font-size: 3.6rem;
  line-height: 1;
  color: #f6f1d8;
  text-shadow:
    0 2px 0 #000,
    0 -1px 0 #000,
    2px 0 0 #000,
    -2px 0 0 #000,
    0 0 18px rgba(255, 199, 122, 0.45);
  user-select: none;
  pointer-events: none;
}

@media (min-width: 1024px) {
  .dice-numeral {
    font-size: 4.4rem;
  }
}

.dice-numeral.dice-numeral-settle {
  animation: dice-numeral-overshoot 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Tumble — 2D pseudo-perspective. Rotate Z + decaying Y-skew. */
.dice-die.dice-tumble-on {
  animation: dice-tumble var(--dice-tumble-duration, 1200ms) cubic-bezier(0.18, 0.85, 0.32, 1);
}

@keyframes dice-tumble {
  0%   { transform: rotate(0deg) skewY(0deg) scale(0.92); opacity: 0.85; }
  15%  { transform: rotate(120deg) skewY(12deg) scale(1.04); opacity: 1; }
  35%  { transform: rotate(280deg) skewY(-9deg) scale(1.06); }
  55%  { transform: rotate(440deg) skewY(6deg) scale(1.02); }
  75%  { transform: rotate(580deg) skewY(-3deg) scale(1.0); }
  100% { transform: rotate(720deg) skewY(0deg) scale(1.0); opacity: 1; }
}

@keyframes dice-numeral-overshoot {
  0%   { transform: scale(1.06); }
  100% { transform: scale(1.0); }
}

/* Formula label above the die. */
.dice-formula-label {
  font-family: var(--font-label-caps, var(--font-display, 'Cormorant Garamond', Georgia, serif));
  font-variant: small-caps;
  text-transform: lowercase;
  letter-spacing: 0.18em;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--color-frost-dim, #94a8c2);
  margin: 0 0 8px;
}

/* Equation row below the die (multi-die rolls only). */
.dice-equation-row {
  margin-top: 14px;
  font-family: var(--font-display, 'Cormorant Garamond', 'Spectral', Georgia, serif);
  font-size: 1.2rem;
  line-height: 1.4;
  color: var(--color-frost, #f6f1d8);
  letter-spacing: 0.04em;
}

@media (min-width: 1024px) {
  .dice-equation-row {
    font-size: 1.4rem;
  }
}

.dice-eq-term { color: var(--color-frost, #f6f1d8); }
.dice-eq-op   { color: var(--color-moon, var(--primary-gold)); margin: 0 4px; }
.dice-eq-mod  { color: var(--color-ember-hot, var(--primary-gold)); font-weight: 500; }
.dice-eq-eq   { color: var(--color-frost, #f6f1d8); margin: 0 6px; }
.dice-eq-total { color: var(--color-frost, #f6f1d8); font-weight: 600; }
.dice-eq-ellipsis { opacity: 0.7; }
.dice-eq-badge {
  font-family: var(--font-label-caps, Georgia, serif);
  font-variant: small-caps;
  letter-spacing: 0.12em;
  font-size: 0.85rem;
  color: var(--color-frost-dim, #94a8c2);
  margin-right: 6px;
}

/* Outcome label. Tier color set by the .outcome-* class on .dice-card. */
.dice-outcome-label {
  font-family: var(--font-label-caps, var(--font-display, 'Cormorant Garamond', Georgia, serif));
  font-variant: small-caps;
  text-transform: lowercase;
  letter-spacing: 0.22em;
  font-size: 1.1rem;
  font-weight: 500;
  margin: 14px 0 0;
  min-height: 1.4em; /* reserve space so the layout doesn't reflow */
  color: var(--dice-tier-color, var(--primary-gold));
}

.dice-card.outcome-critical_success { --dice-tier-color: var(--tier-color-crit-success); }
.dice-card.outcome-success          { --dice-tier-color: var(--tier-color-success); }
.dice-card.outcome-partial_success  { --dice-tier-color: var(--tier-color-partial); }
.dice-card.outcome-failure          { --dice-tier-color: var(--tier-color-failure); }
.dice-card.outcome-critical_failure { --dice-tier-color: var(--tier-color-crit-failure); }

/* Crit flourishes — wobble, ripple, vignette. */
.dice-card.dice-wobble-1x .dice-die { animation: dice-wobble-1x 250ms ease-in-out; }
.dice-card.dice-wobble-2x .dice-die { animation: dice-wobble-2x 400ms ease-in-out; }

@keyframes dice-wobble-1x {
  0%, 100% { transform: rotate(0deg); }
  50%      { transform: rotate(4deg); }
}
@keyframes dice-wobble-2x {
  0%, 100% { transform: rotate(0deg); }
  25%      { transform: rotate(-3deg); }
  50%      { transform: rotate(3deg); }
  75%      { transform: rotate(-2deg); }
}

.dice-ripple-slot {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.dice-ripple {
  position: absolute;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--dice-tier-color, var(--primary-gold)) 0%, transparent 70%);
  opacity: 0.6;
  pointer-events: none;
  animation: dice-ripple-expand 800ms ease-out forwards;
}

.dice-card.outcome-critical_failure .dice-ripple {
  animation-duration: 1000ms;
  opacity: 0.55;
}

@keyframes dice-ripple-expand {
  0%   { transform: scale(0.4); opacity: 0.6; }
  100% { transform: scale(3); opacity: 0; }
}

.dice-vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 421;
  opacity: 0;
  background: radial-gradient(ellipse at center,
    transparent 50%,
    var(--dice-tier-color, transparent) 100%);
  transition: opacity 700ms ease-in;
}

.dice-vignette.dice-vignette-on {
  opacity: 0.18;
  animation: dice-vignette-fade 1900ms ease-in-out forwards;
}

.dice-card.outcome-critical_failure ~ .dice-vignette.dice-vignette-on {
  opacity: 0.22;
}

@keyframes dice-vignette-fade {
  0%   { opacity: 0; }
  37%  { opacity: 0.18; }   /* peak at 700ms */
  100% { opacity: 0; }       /* fully out by 1900ms */
}

/* Dissolve — die fades, drifts up, scales down; scrim fades with modal. */
.dice-card.dice-dissolving {
  animation: dice-dissolve 600ms ease-in forwards;
}
.dice-overlay.dice-modal-dissolving {
  animation: dice-scrim-fade 600ms ease-in forwards;
}

@keyframes dice-dissolve {
  0%   { opacity: 1; transform: scale(1) translateY(0); }
  100% { opacity: 0; transform: scale(0.92) translateY(-12px); }
}
@keyframes dice-scrim-fade {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* Hero-beat dissolve handoff — when the next narration in pendingNarrations
   carries `tag: 'hero-beat'`, the renderer sets body.dice-to-hero-beat so
   the normal-chrome restoration is suppressed and the hero-beat dim takes
   over during the cross-fade. The existing hero-beat handler at app.js's
   _setHeroBeatMode owns the actual chrome dimming; this class only
   prevents the brief normal-chrome flash between the two. */
body.dice-to-hero-beat .dice-overlay { transition-duration: 0ms; }

/* Redesign-cohort palette override. Set on `html` so the cascade reaches
   .dice-card, .dice-wrapper, AND the sibling .dice-vignette. */
html.use-redesign-ui {
  --tier-color-crit-success: var(--color-ember-hot, #ffb86b);
  --tier-color-crit-failure: var(--color-danger-deep-text, #d07a7a);
  --tier-color-success: var(--color-moon-bright, #aadcf0);
  --tier-color-partial: var(--color-warn-mute, #c9a76a);
  --tier-color-failure: var(--color-frost-dim, #94a8c2);
  --moonlight-edge: var(--color-moon, #7fc6e6);
  --ember-edge: var(--color-ember, #f59e3a);
}

/* Reduced motion — collapse all motion to instant; disable decorative
   layers entirely. Tier color (via --dice-tier-color and the static
   .dice-outcome-label color) still applies. */
@media (prefers-reduced-motion: reduce) {
  .dice-die.dice-tumble-on,
  .dice-numeral.dice-numeral-settle,
  .dice-card.dice-wobble-1x .dice-die,
  .dice-card.dice-wobble-2x .dice-die,
  .dice-card.dice-dissolving,
  .dice-overlay.dice-modal-dissolving {
    animation: none !important;
  }
  .dice-ripple, .dice-vignette.dice-vignette-on { display: none !important; }
}

/* --- CSS3 Animations --- */
@keyframes messageSlideIn {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Orphaned dice keyframes (d20RollSpin, critSuccessPulse, critFailShake,
   finalNumberPulse, particleBurstGold, particleBurstRed) removed in Story
   1.7. Their replacements (dice-tumble, dice-wobble-*, dice-ripple-expand,
   dice-vignette-fade, dice-dissolve, dice-scrim-fade) live in the dice
   reveal block above. */

/* ==========================================================================
   v1.6.0 — CINEMATIC OVERLAYS, SCENE FLOW, ADDICTIVE FEEDBACK
   ========================================================================== */

/* Full-screen cinematic overlay used for: YOUR TURN, CRITICAL!, ENCOUNTER, etc. */
.cinematic-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  background: radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.55) 100%);
  transition: opacity 0.32s ease;
}
.cinematic-overlay.visible {
  opacity: 1;
  visibility: visible;
}
.cinematic-text {
  font-family: 'Outfit', sans-serif;
  font-weight: 800;
  font-size: 4rem;
  letter-spacing: 0.1em;
  color: var(--primary-gold);
  text-shadow: 0 0 24px rgba(212, 175, 55, 0.7), 0 4px 12px rgba(0,0,0,0.7);
  text-transform: uppercase;
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 0.28s ease, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.cinematic-overlay.visible .cinematic-text {
  opacity: 1;
  transform: scale(1);
}
.cinematic-sub {
  font-family: 'Outfit', sans-serif;
  font-size: 1.1rem;
  color: var(--text-primary);
  letter-spacing: 0.06em;
  margin-top: 12px;
  opacity: 0;
  transition: opacity 0.32s ease 0.1s;
}
.cinematic-overlay.visible .cinematic-sub { opacity: 0.92; }
/* Variant colour palettes for different cinematic events. */
.cinematic-overlay.kind-crit-success .cinematic-text { color: #ffd97a; text-shadow: 0 0 32px rgba(255, 215, 0, 0.85); }
.cinematic-overlay.kind-crit-fail .cinematic-text   { color: #ff6b6b; text-shadow: 0 0 28px rgba(201, 42, 42, 0.7); }
.cinematic-overlay.kind-encounter .cinematic-text    { color: hsl(355, 80%, 65%); text-shadow: 0 0 28px hsla(355, 85%, 50%, 0.55); }
.cinematic-overlay.kind-your-turn .cinematic-text    { color: #ffd97a; }

/* Crit slow-mo vignette — sits below the cinematic text but above everything else. */
.crit-vignette {
  position: fixed;
  inset: 0;
  z-index: 250;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(circle at center, transparent 35%, rgba(0,0,0,0.55) 100%);
  transition: opacity 0.18s ease;
}
.crit-vignette.visible { opacity: 1; }
.crit-vignette.success-tint { background: radial-gradient(circle at center, rgba(255, 215, 0, 0.12) 0%, rgba(0,0,0,0.55) 90%); }
.crit-vignette.fail-tint    { background: radial-gradient(circle at center, rgba(255, 60, 60, 0.10) 0%, rgba(0,0,0,0.55) 90%); }

/* Slow-mo: applied to the body so animation timings around the dice modal
   slow down briefly. Mild effect — full slow-mo on the chat would look
   broken; we just slow the animations that are mid-flight. */
body.slow-mo * { animation-duration: 1.8s !important; transition-duration: 0.45s !important; }

/* Confetti canvas — covers the whole viewport, transparent, no events. */
.confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 220;
  pointer-events: none;
  background: transparent;
}

/* XP bar — green/gold gradient distinct from the HP red. */
.progress-xp {
  background: linear-gradient(90deg, hsl(50, 80%, 50%), hsl(140, 80%, 50%));
  box-shadow: 0 0 6px rgba(120, 220, 100, 0.5);
}
.xp-level {
  display: inline-block;
  margin-left: 6px;
  background: rgba(120, 220, 100, 0.18);
  border: 1px solid rgba(120, 220, 100, 0.4);
  border-radius: 4px;
  padding: 0 5px;
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  color: hsl(120, 60%, 75%);
}

/* Animated "+X XP" floater when XP changes. */
.xp-floater {
  position: absolute;
  font-family: 'Outfit', sans-serif;
  font-weight: 700;
  color: hsl(120, 80%, 70%);
  text-shadow: 0 0 6px rgba(120, 220, 100, 0.6);
  pointer-events: none;
  animation: xpFloat 1.4s ease-out forwards;
  z-index: 5;
}
@keyframes xpFloat {
  0%   { opacity: 0; transform: translateY(0) scale(0.95); }
  20%  { opacity: 1; transform: translateY(-6px) scale(1.05); }
  100% { opacity: 0; transform: translateY(-32px) scale(1); }
}

/* Combo / momentum streak indicator — pulses when active. */
.combo-indicator {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 6px 10px;
  background: linear-gradient(135deg, rgba(255, 100, 30, 0.18), rgba(255, 60, 30, 0.10));
  border: 1px solid rgba(255, 100, 30, 0.5);
  border-radius: 8px;
  font-size: 0.78rem;
  font-weight: 600;
  color: #ffb37a;
  animation: comboPulse 1.4s ease-in-out infinite;
  cursor: default;
}
.combo-indicator.hidden { display: none; }
.combo-fire { font-size: 1.05rem; }
.combo-count {
  font-family: 'Outfit', sans-serif;
  color: #ffd97a;
  font-size: 0.95rem;
}
.combo-label {
  margin-left: auto;
  opacity: 0.75;
  font-size: 0.66rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
@keyframes comboPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 100, 30, 0.4); }
  50%      { box-shadow: 0 0 12px 2px rgba(255, 100, 30, 0.5); }
}

/* Scene fade — applied to the tactical board container during scene change. */
.tactical-board.scene-fading {
  animation: sceneFade 0.7s ease-in-out;
}
@keyframes sceneFade {
  0%   { opacity: 1; }
  45%  { opacity: 0; transform: scale(0.985); }
  100% { opacity: 1; transform: scale(1); }
}

/* Tab transition fade — 200ms between Tactical / World Map. */
.canvas-container { transition: opacity 0.2s ease; }
.canvas-container.fading { opacity: 0; }

/* v1.8.3 — Campaign Codex / Adventure Journal pane (3rd Map-HUD tab).
   Lives where .canvas-container sits; the tab switcher toggles `.hidden`. */
.journal-view {
  background-color: #15110b;
  background-image:
    radial-gradient(ellipse at center, rgba(212, 175, 55, 0.05) 0%, transparent 75%),
    linear-gradient(135deg, #1c1610 0%, #0c0907 100%);
  border: 1px solid rgba(212, 175, 55, 0.28);
  box-shadow:
    inset 0 0 0 2px rgba(0,0,0,0.55),
    inset 0 0 28px rgba(0,0,0,0.7);
  border-radius: 12px;
  padding: 14px;
  max-height: 380px;
  overflow: hidden;
  transition: opacity 0.2s ease;
}
.journal-view.fading { opacity: 0; }
.journal-scroll {
  max-height: 100%;
  overflow-y: auto;
  padding-right: 6px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.journal-scroll::-webkit-scrollbar { width: 6px; }
.journal-scroll::-webkit-scrollbar-thumb {
  background: rgba(212, 175, 55, 0.25);
  border-radius: 3px;
}
.journal-section {
  border-left: 2px solid rgba(212, 175, 55, 0.4);
  padding-left: 10px;
}
.journal-section-title {
  font-family: var(--font-fantasy);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  color: var(--primary-gold);
  margin-bottom: 6px;
  text-transform: uppercase;
}
.journal-paragraph {
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-primary);
  margin-bottom: 6px;
}
.journal-paragraph .muted { color: var(--text-muted); }
.journal-quest-card {
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 8px;
}
.journal-quest-card.active { border-color: hsla(43, 85%, 60%, 0.55); }
.journal-quest-card.completed { border-color: hsla(142, 45%, 55%, 0.45); }
.journal-quest-card.failed { border-color: hsla(355, 70%, 60%, 0.45); }
.journal-quest-card.hidden-quest { border-color: hsla(220, 14%, 58%, 0.45); opacity: 0.7; }
.journal-quest-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.journal-quest-title {
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: capitalize;
  color: var(--text-primary);
}
.journal-quest-body {
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.journal-quest-body strong { color: var(--text-primary); }
.journal-empty {
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
  padding: 20px 8px;
  text-align: center;
}

/* v1.8.3 — collapsible HUD accordion used for Quests + Factions in the
   right column. Native <details>/<summary>, themed to match the panel. */
.hud-accordion {
  background: rgba(0, 0, 0, 0.18);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 0;
  overflow: hidden;
  transition: all 0.2s ease;
}
.hud-accordion + .hud-accordion { margin-top: 8px; }
.hud-accordion[open] {
  border-color: rgba(212, 175, 55, 0.3);
  background: rgba(0, 0, 0, 0.28);
}
.hud-accordion > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  font-family: var(--font-fantasy);
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  color: var(--primary-gold);
  user-select: none;
}
.hud-accordion > summary::-webkit-details-marker { display: none; }
.hud-accordion > summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 8px;
  color: var(--primary-gold);
  transition: transform 0.18s ease;
  width: 10px;
}
.hud-accordion[open] > summary::before {
  transform: rotate(90deg);
}
.hud-accordion-title { flex: 1; }
.hud-accordion-count {
  font-family: 'Inter', sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.35);
  color: var(--primary-gold);
  padding: 1px 8px;
  border-radius: 10px;
}
.hud-accordion[open] > summary + * {
  padding: 4px 12px 12px;
}
.hud-accordion .quest-items,
.hud-accordion .relations-container {
  font-size: 0.8rem;
}

/* Modal slide-up entry. Combines with the existing modal-overlay rules. */
@keyframes modalSlideIn {
  from { opacity: 0; transform: translateY(20px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}
.modal-card { animation: modalSlideIn 0.32s cubic-bezier(0.25, 0.8, 0.25, 1); }

/* Story 3.3 (AC #5 / Story 5.5 reduced-motion contract) — suppress modalSlideIn under reduce. */
@media (prefers-reduced-motion: reduce) {
  .modal-card { animation: none !important; }
}

/* Story 3.6 — Combat bundle slide-in / slide-out transitions.
   Applied to #encounter-hud and #initiative-strip when combat starts/ends.
   Both instant under prefers-reduced-motion (Story 5.5 contract). */
@keyframes combatBundleSlideIn {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes combatBundleSlideOut {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(24px); }
}
.combat-bundle-entering { animation: combatBundleSlideIn  0.4s cubic-bezier(0.25, 0.8, 0.25, 1) forwards; }
.combat-bundle-exiting  { animation: combatBundleSlideOut 0.6s cubic-bezier(0.25, 0.8, 0.25, 1) forwards; }
@media (prefers-reduced-motion: reduce) {
  .combat-bundle-entering,
  .combat-bundle-exiting { animation: none !important; }
}

/* Story 3.6 — Redesign-cohort palette for combat surfaces (AC #1). */
html.use-redesign-ui #encounter-hud {
  background: var(--color-bg-primary, #0a1020);
  border: 1px solid rgba(127, 198, 230, 0.2);
  border-radius: 6px;
}

html.use-redesign-ui #encounter-hud .encounter-hud-archetype {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  color: var(--color-moon, #c9dff0);
}

html.use-redesign-ui #encounter-hud .encounter-hud-col-label {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  color: var(--color-moon, #c9dff0);
  font-size: 0.8rem;
  opacity: 0.7;
}

html.use-redesign-ui .initiative-strip {
  background: var(--color-bg-primary, #0a1020);
  border: 1px solid rgba(127, 198, 230, 0.15);
  border-radius: 4px;
}

html.use-redesign-ui .init-token {
  color: var(--color-moon, #c9dff0);
}

/* --- Biome-driven background tint -----------------------------------------
   Set by JS via `document.documentElement.style.setProperty('--biome-tint',...)`
   on every sync_state. Subtle warm/cool shift across the page tied to the
   current scene's biome — so a swamp feels different from a citadel. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  background: var(--biome-tint, transparent);
  pointer-events: none;
  transition: background 1.2s ease;
  opacity: 0.65;
}

/* --- End Turn button --- */
.btn-end-turn {
  background: linear-gradient(180deg, hsl(43, 80%, 50%), hsl(35, 75%, 40%));
  color: #1a1408;
  border: none;
  padding: 6px 12px;
  border-radius: 6px;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.78rem;
  cursor: pointer;
  margin-left: auto;
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.4);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  animation: endTurnPulse 1.6s ease-in-out infinite;
}
.btn-end-turn:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 18px rgba(212, 175, 55, 0.7);
}
.btn-end-turn.hidden { display: none; }

/* v1.8.4 — React button. Available during combat to non-spotlight
   characters; click to buffer a reaction that resolves before the next
   advanceCombatTurn. Same visual register as End Turn but a contrasting
   amber→blue accent so players don't confuse them. */
.btn-react {
  background: hsla(210, 70%, 50%, 0.14);
  border: 1px solid hsla(210, 70%, 60%, 0.5);
  color: hsl(210, 70%, 80%);
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.78rem;
  margin-left: 6px;
  transition: all 0.15s ease;
}
.btn-react:hover {
  background: hsla(210, 70%, 50%, 0.28);
  border-color: hsla(210, 70%, 60%, 0.85);
  color: hsl(210, 80%, 92%);
}
.btn-react.hidden { display: none; }

/* v2.0.5 — Manual Start Combat button. Creator-only; appears in the map
   HUD header when combat is offline. Visually distinct from the End Turn
   pulse so players don't confuse them. */
.btn-start-combat {
  background: hsla(0, 70%, 45%, 0.18);
  border: 1px solid hsla(0, 70%, 55%, 0.55);
  color: hsl(0, 70%, 82%);
  padding: 6px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.78rem;
  margin-left: 6px;
  transition: all 0.15s ease;
}
.btn-start-combat:hover {
  background: hsla(0, 80%, 55%, 0.30);
  border-color: hsla(0, 80%, 65%, 0.9);
  color: hsl(0, 80%, 92%);
}
.btn-start-combat.hidden { display: none; }

/* v2.2.0 — XP progress bar (character sheet) + floating XP gain numbers
   + level-up modal styling. The bar is subtle so it doesn't fight the HP
   bar above; level-up modal is gold-trimmed to signal achievement. */
.xp-progress-host {
  margin: 8px 0 12px;
}
.xp-progress-bar {
  height: 6px;
  background: rgba(212, 175, 55, 0.08);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
}
.xp-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #d4af37 0%, #f5d76e 100%);
  border-radius: 3px;
  width: 0;
  transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
  box-shadow: 0 0 8px rgba(212, 175, 55, 0.4);
}
.xp-progress-label {
  display: flex;
  justify-content: space-between;
  margin-top: 3px;
  font-size: 0.7rem;
  opacity: 0.7;
  color: #f5e6b3;
}
.xp-progress-level { font-weight: 700; }

.xp-float {
  position: absolute;
  pointer-events: none;
  font-family: inherit;
  font-weight: 800;
  font-size: 1.05rem;
  color: #f5d76e;
  text-shadow: 0 0 6px rgba(245, 215, 110, 0.7), 0 2px 4px rgba(0, 0, 0, 0.85);
  transform: translate(-50%, 0);
  animation: xpFloat 1.4s ease-out forwards;
  z-index: 50;
}
@keyframes xpFloat {
  0%   { opacity: 0; transform: translate(-50%, 10px) scale(0.6); }
  25%  { opacity: 1; transform: translate(-50%, -10px) scale(1.1); }
  100% { opacity: 0; transform: translate(-50%, -60px) scale(1.0); }
}

.levelup-card {
  border: 2px solid #d4af37;
  box-shadow: 0 0 30px rgba(212, 175, 55, 0.5), 0 8px 32px rgba(0, 0, 0, 0.6);
}
.levelup-card h2 {
  color: #f5d76e;
  text-shadow: 0 0 12px rgba(245, 215, 110, 0.6);
}
.levelup-section {
  margin: 12px 0;
  padding: 10px;
  background: rgba(212, 175, 55, 0.05);
  border: 1px solid rgba(212, 175, 55, 0.15);
  border-radius: 6px;
}
.levelup-section label {
  display: block;
  margin-bottom: 6px;
  font-weight: 700;
  color: #f5e6b3;
}
.levelup-section input[type=text],
.levelup-section select {
  width: 100%;
  padding: 8px;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid rgba(212, 175, 55, 0.3);
  color: #e8e3d4;
  border-radius: 4px;
  font-family: inherit;
}
.levelup-section .description {
  font-size: 0.75rem;
  margin-top: 4px;
  opacity: 0.7;
}
#levelup-asi-block {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}
#levelup-asi-block.hidden { display: none; }
#levelup-asi-block select { flex-grow: 1; }

/* v2.3.0 — Point-buy ability-score allocator. Six rows of ability with
   ± buttons; live budget chip at the top. Each row shows ability label,
   current value, modifier, and points spent. */
.pointbuy-block .pointbuy-budget {
  float: right;
  font-size: 0.78rem;
  color: #f5e6b3;
  font-weight: 400;
}
.pointbuy-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  margin-top: 6px;
}
.pointbuy-row {
  display: grid;
  grid-template-columns: 44px 28px 38px 28px 42px 44px;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  background: rgba(14, 16, 22, 0.5);
  border: 1px solid rgba(212, 175, 55, 0.18);
  border-radius: 5px;
  font-size: 0.82rem;
  color: #e8e3d4;
}
.pointbuy-label {
  font-weight: 800;
  color: #f5e6b3;
  letter-spacing: 0.04em;
}
.pointbuy-btn {
  width: 26px;
  height: 26px;
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.4);
  color: #f5e6b3;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1.0rem;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  font-family: inherit;
}
.pointbuy-btn:hover:not([disabled]) {
  background: rgba(212, 175, 55, 0.25);
}
.pointbuy-btn[disabled] {
  opacity: 0.25;
  cursor: not-allowed;
}
.pointbuy-value {
  text-align: center;
  font-size: 1.05rem;
  font-weight: 800;
  color: #fff;
}
.pointbuy-mod {
  text-align: center;
  opacity: 0.78;
  font-size: 0.78rem;
  font-style: italic;
}
.pointbuy-cost {
  text-align: right;
  font-size: 0.7rem;
  opacity: 0.65;
}

/* v2.2.2 — Persistent selection panel. Sits below the tactical canvas,
   above the legend. Shows the currently selected token's live stats so
   the player always knows what they have selected without re-hovering. */
.tactical-selection {
  margin: 8px 4px;
  font-size: 0.78rem;
}
.tactical-selection .selection-empty {
  padding: 10px 12px;
  /* Story 5.3 (Layer-3 axe-core): alpha 0.5 fails WCAG 2.2 AA contrast
     against the dark game-pane background. Bumped to 0.9 to clear 4.5:1. */
  color: rgba(232, 227, 212, 0.9);
  background: rgba(14, 16, 22, 0.4);
  border: 1px dashed rgba(212, 175, 55, 0.18);
  border-radius: 6px;
  text-align: center;
  font-style: italic;
}
.tactical-selection .selection-card {
  position: relative;
  padding: 10px 32px 10px 12px;
  background: rgba(14, 16, 22, 0.7);
  border: 1px solid rgba(212, 175, 55, 0.35);
  border-radius: 6px;
  color: #e8e3d4;
  line-height: 1.4;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.tactical-selection .selection-card strong { color: #f5e6b3; }
.tactical-selection .tt-type { font-size: 0.7rem; opacity: 0.8; margin-left: 4px; }
.tactical-selection .tt-sub  { font-size: 0.72rem; opacity: 0.75; }
.tactical-selection .selection-hint {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(212, 175, 55, 0.12);
  font-size: 0.72rem;
  color: rgba(232, 227, 212, 0.75);
}
.tactical-selection .selection-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  background: rgba(212, 175, 55, 0.08);
  border: 1px solid rgba(212, 175, 55, 0.3);
  border-radius: 4px;
  color: #f5e6b3;
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0;
  font-family: inherit;
}
.tactical-selection .selection-close:hover {
  background: rgba(212, 175, 55, 0.2);
}

/* v2.2.1 — Tactical-grid legend. A compact <details> beneath the canvas
   that explains what the colours and glyphs mean. Collapsed by default;
   the browser remembers `open` state through normal <details> behaviour. */
.tactical-legend {
  background: rgba(14, 16, 22, 0.55);
  border: 1px solid rgba(212, 175, 55, 0.18);
  border-radius: 6px;
  margin: 8px 4px;
  padding: 0;
  font-size: 0.78rem;
}
.tactical-legend summary {
  cursor: pointer;
  padding: 8px 10px;
  font-weight: 700;
  color: #f5e6b3;
  user-select: none;
  list-style: none; /* hide default disclosure triangle */
}
.tactical-legend summary::-webkit-details-marker { display: none; }
.tactical-legend summary::before {
  content: "▸ ";
  transition: transform 200ms ease;
  display: inline-block;
}
.tactical-legend[open] summary::before {
  transform: rotate(90deg);
}
.tactical-legend .legend-grid {
  padding: 4px 10px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tactical-legend .legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
}
.tactical-legend .legend-divider {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid rgba(212, 175, 55, 0.12);
}
.tactical-legend .legend-swatch {
  display: inline-block;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.45);
  flex-shrink: 0;
}
.tactical-legend .legend-swatch.legend-wall {
  background: #1e1f26 !important;
  border-color: rgba(212, 175, 55, 0.4);
  border-radius: 3px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
}
.tactical-legend .legend-glyph {
  font-size: 0.95rem;
  width: 16px;
  text-align: center;
  flex-shrink: 0;
}
.tactical-legend .legend-label {
  color: #e8e3d4;
  flex-grow: 1;
}
.tactical-legend .legend-label strong { color: #f5e6b3; }

/* v2.0.5 — Tactical-grid hover tooltip. Fixed-positioned so it follows
   the cursor across the canvas; updated on canvas mousemove. */
.tactical-tooltip {
  position: fixed;
  z-index: 9999;
  pointer-events: none;
  background: rgba(14, 16, 22, 0.96);
  border: 1px solid rgba(212, 175, 55, 0.55);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 0.8rem;
  line-height: 1.35;
  color: #e8e3d4;
  max-width: 280px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
}
.tactical-tooltip.hidden { display: none; }
.tactical-tooltip strong { color: #f5e6b3; font-weight: 700; }
.tactical-tooltip .tt-type { font-size: 0.7rem; opacity: 0.8; margin-left: 4px; }
.tactical-tooltip .tt-sub  { font-size: 0.74rem; opacity: 0.75; }

@keyframes endTurnPulse {
  0%, 100% { box-shadow: 0 0 8px rgba(212, 175, 55, 0.4); }
  50%      { box-shadow: 0 0 16px rgba(212, 175, 55, 0.8); }
}

/* --- Initiative strip --- */
.initiative-strip {
  display: flex;
  gap: 6px;
  padding: 8px;
  overflow-x: auto;
  border-bottom: 1px solid var(--border-light);
}
.initiative-strip.hidden { display: none; }
.init-token {
  flex: 0 0 auto;
  width: 52px;
  text-align: center;
  font-size: 0.7rem;
  color: var(--text-muted);
  cursor: default;
  opacity: 0.7;
  transition: all 0.18s ease;
}
.init-token.active {
  opacity: 1;
  color: var(--primary-gold);
  transform: scale(1.06);
}
.init-token .init-portrait {
  width: 38px; height: 38px;
  border-radius: 50%;
  margin: 0 auto 4px;
  background: rgba(255, 255, 255, 0.04);
  border: 2px solid var(--border-light);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem;
  overflow: hidden;
}
.init-token .init-portrait img { width: 100%; height: 100%; }
.init-token.active .init-portrait {
  border-color: var(--primary-gold);
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.6);
}
.init-token.dead {
  opacity: 0.3;
  text-decoration: line-through;
}

/* --- Achievement toast --- */
#achievement-toast.achievement-toast {
  position: fixed;
  top: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(-20px);
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 22px;
  background: linear-gradient(135deg, rgba(212, 175, 55, 0.95), rgba(180, 130, 30, 0.95));
  color: #1a1408;
  border-radius: 12px;
  box-shadow: 0 8px 28px rgba(212, 175, 55, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.15) inset;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.32s ease, transform 0.32s ease;
  width: auto;
  max-width: min(360px, calc(100vw - 32px));
  height: auto;
  min-height: 0;
}
#achievement-toast.achievement-toast.hidden { display: none; }
#achievement-toast.achievement-toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.achievement-icon { font-size: 2.2rem; filter: drop-shadow(0 0 6px rgba(255,255,255,0.6)); }
.achievement-title { font-family: 'Outfit', sans-serif; font-weight: 700; font-size: 1.1rem; letter-spacing: 0.02em; }
.achievement-sub { font-size: 0.85rem; opacity: 0.85; margin-top: 2px; }

/* --- Push-to-talk mic button --- */
.btn-mic {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-light);
  color: var(--text-primary);
  border-radius: 8px;
  width: 42px;
  cursor: pointer;
  font-size: 1.15rem;
  transition: all 0.15s ease;
}
.btn-mic:hover { border-color: var(--primary-gold); }
.btn-mic.recording {
  background: rgba(201, 42, 42, 0.2);
  border-color: rgba(201, 42, 42, 0.6);
  animation: micPulse 0.9s ease-in-out infinite;
}
@keyframes micPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(201, 42, 42, 0.5); }
  50%      { box-shadow: 0 0 0 8px rgba(201, 42, 42, 0); }
}

/* --- Party Roster --- */
.party-roster-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.party-roster-list li {
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border-light);
  border-radius: 10px;
  padding: 8px 10px;
  transition: all 0.18s ease;
}
/* FIX 5 — Solo-player empty state hint */
.party-empty-state {
  color: var(--text-muted);
  font-size: 0.8rem;
  font-style: italic;
  text-align: center;
  padding: 16px 8px;
  border: 1px dashed var(--border-light);
  border-radius: 10px;
  background: transparent;
}
.party-roster-list li.roster-spot {
  border-color: rgba(212, 175, 55, 0.6);
  background: rgba(212, 175, 55, 0.07);
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.18);
}
.roster-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 0.85rem; }
.roster-name { font-weight: 500; }
.roster-me {
  margin-left: 6px;
  background: rgba(212, 175, 55, 0.18);
  border: 1px solid rgba(212, 175, 55, 0.5);
  color: var(--primary-gold);
  border-radius: 4px;
  padding: 0 5px;
  font-size: 0.6rem;
  letter-spacing: 0.05em;
  vertical-align: middle;
}
.roster-hp { font-variant-numeric: tabular-nums; color: var(--text-muted); font-size: 0.78rem; }
.roster-bar { height: 5px; margin-top: 4px; }
.roster-meta { font-size: 0.7rem; color: var(--text-muted); margin-top: 4px; }
.roster-meta em { color: hsl(355, 70%, 65%); font-style: normal; }

/* --- Message header: author + timestamp + (GM only) copy button --- */
.msg-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--primary-gold);
  opacity: 0.95;
}
.msg-author { flex: 1; }
.msg-time {
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  opacity: 0.55;
  font-size: 0.72rem;
}
.msg-copy-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.85rem;
  opacity: 0.5;
  transition: opacity 0.15s ease;
  padding: 0 2px;
}
.msg-copy-btn:hover { opacity: 1; color: var(--primary-gold); }

.msg-regen-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.85rem;
  opacity: 0.5;
  transition: opacity 0.15s ease, transform 0.3s ease;
  padding: 0 2px;
}
.msg-regen-btn:hover { opacity: 1; color: var(--primary-gold); transform: rotate(-90deg); }
.msg-regen-btn:disabled { opacity: 0.3; cursor: wait; }

/* v1.8.9 — 🔊 replay-narration button on every GM bubble. Greyed when no
   audio URL has been bound yet (e.g. WaveNet still synthesizing, or the
   server has no TTS key); fully clickable once activateReplayButtonForNarration
   patches in the data-audio-url. */
.msg-replay-btn {
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.85rem;
  opacity: 0.5;
  transition: opacity 0.15s ease, transform 0.15s ease;
  padding: 0 2px;
}
.msg-replay-btn:hover:not(:disabled) {
  opacity: 1;
  color: var(--primary-gold);
  transform: scale(1.1);
}
.msg-replay-btn:disabled {
  opacity: 0.2;
  cursor: not-allowed;
}

/* --- Action chips (one-click "What do you do?" options) --- */
.action-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.action-chip {
  background: rgba(212, 175, 55, 0.08);
  border: 1px solid rgba(212, 175, 55, 0.35);
  color: var(--text-primary);
  padding: 8px 12px;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
  line-height: 1.4;
  text-align: left;
  transition: all 0.15s ease;
}
.action-chip:hover {
  background: rgba(212, 175, 55, 0.18);
  border-color: rgba(212, 175, 55, 0.7);
  transform: translateY(-1px);
}
.action-chip:active {
  transform: translateY(0);
  background: rgba(212, 175, 55, 0.25);
}
.action-chip strong {
  color: var(--primary-gold);
  margin-right: 4px;
}

/* Resolved chip: the one the player actually picked. Other chips fade away
   after click; this one stays as a permanent breadcrumb of the choice. */
.action-chip.chosen {
  background: rgba(212, 175, 55, 0.20);
  border-color: rgba(212, 175, 55, 0.80);
  color: var(--primary-gold);
  cursor: default;
  pointer-events: none;
  transform: none;
  box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.45) inset;
}
.action-chip.chosen:hover { transform: none; }

/* v1.8.2 — historical action chips fade once a newer GM message has landed.
   Before, every old chip stayed bright yellow and looked clickable, making
   the timeline feel chaotic. This rule mutes any chip group inside a
   non-last GM card; the still-active chips on the most recent card keep
   their full styling. `:not(:has(...))` guards lets the chosen breadcrumb
   row keep its existing styling. */
.message-feed .message.gm-msg:not(:last-of-type) .action-chips,
.message-feed .message.gm-msg:not(:last-of-type) .action-chips-resolved-label {
  display: none;
}

/* v1.8.5 — placeholder shown during streaming where the action chips will
   eventually render. Lets the player know more is coming without flashing
   raw markdown that flips into buttons at finalise time. */
.streaming-options-hint {
  margin-top: 10px;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
  letter-spacing: 0.02em;
  opacity: 0.7;
}
.chip-tick {
  display: inline-block;
  color: var(--primary-gold);
  font-weight: 700;
  margin-right: 2px;
}

/* "→ Your choice:" lead-in label inserted above the chosen chip so the
   resolution reads as part of the narration thread. */
.action-chips-resolved-label {
  font-family: 'Outfit', sans-serif;
  font-size: 0.78rem;
  color: var(--primary-gold);
  opacity: 0.7;
  margin-top: 10px;
  margin-bottom: 2px;
  letter-spacing: 0.04em;
  font-variant: small-caps;
}

/* Camera shake — applied to the tactical board container on critical rolls. */
.shake { animation: cameraShake 0.32s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
@keyframes cameraShake {
  10%, 90% { transform: translate3d(-1px, 0, 0); }
  20%, 80% { transform: translate3d(2px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
  40%, 60% { transform: translate3d(4px, 0, 0); }
}

/* Streaming GM bubble — visually identical to a finished one, just renders
   raw newline-joined text while chunks arrive. The cursor inside indicates
   live streaming; finaliseStreamingNarration removes both. */
.gm-msg.streaming .streaming-text {
  white-space: normal;
  line-height: 1.55;
  font-family: inherit;
  color: var(--text-primary);
}

/* Teletype blinking cursor (used during the GM message reveal animation) */
.gm-msg .cursor {
  display: inline-block;
  width: 0.55ch;
  color: var(--primary-gold);
  animation: cursor-blink 0.95s steps(2, end) infinite;
  margin-left: 1px;
  vertical-align: baseline;
}
@keyframes cursor-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* --- GM "is composing" typing indicator --- */
.gm-typing {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(43, 85, 55, 0.04);
  border: 1px solid hsla(43, 85, 55, 0.18);
  border-left: 4px solid var(--primary-gold);
  border-radius: 12px;
  padding: 12px 16px;
  color: var(--text-muted);
  font-style: italic;
  font-size: 0.92rem;
  animation: messageSlideIn 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.gm-typing .thinking-verb {
  font-variant: small-caps;
  letter-spacing: 0.04em;
  color: var(--primary-gold);
  opacity: 0.9;
  transition: opacity 0.22s ease;
}
.gm-typing .thinking-verb.verb-out { opacity: 0; }
.gm-typing .dots { display: inline-flex; gap: 4px; }
.gm-typing .dots span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--primary-gold);
  opacity: 0.4;
  animation: typingDot 1.2s ease-in-out infinite;
}
.gm-typing .dots span:nth-child(2) { animation-delay: 0.18s; }
.gm-typing .dots span:nth-child(3) { animation-delay: 0.36s; }
@keyframes typingDot {
  0%, 80%, 100% { opacity: 0.35; transform: translateY(0); }
  40%           { opacity: 1;    transform: translateY(-3px); }
}

/* --- Campaign Creator Extras --- */
.w-full {
  width: 100%;
}

.flex-row {
  display: flex;
  gap: 12px;
}

.flex-grow {
  flex: 1;
}

.section-desc {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 12px;
}

.architect-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: rgba(145, 80, 45, 0.08);
  border: 1px dashed rgba(145, 80, 45, 0.3);
  border-radius: 8px;
  padding: 10px;
  color: var(--primary-gold);
  font-size: 0.85rem;
  font-weight: 500;
}

.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid rgba(212, 175, 55, 0.2);
  border-top-color: var(--primary-gold);
  border-radius: 50%;
  animation: spinner-rotate 0.8s linear infinite;
}

@keyframes spinner-rotate {
  to { transform: rotate(360deg); }
}

/* --- Ability Edit Grid and Edit Button --- */
.ability-edit-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
  text-align: center;
}

.ability-edit-grid label {
  font-size: 0.7rem;
  display: block;
  margin-bottom: 4px;
  color: var(--text-muted);
}

.ability-edit-grid input {
  width: 100%;
  padding: 6px;
  text-align: center;
}

/* v1.8.4 — Loot drop claim modal rows. Each item shows name + remaining
   quantity + a "Claim" button; rows that hit 0 fade out and disable. */
.loot-items {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}
.loot-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  background: rgba(0,0,0,0.28);
  border: 1px solid rgba(212, 175, 55, 0.3);
  border-radius: 8px;
  transition: opacity 0.2s ease;
}
.loot-row.depleted {
  opacity: 0.35;
  border-color: var(--border-light);
  filter: grayscale(0.5);
}
.loot-row-meta {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.loot-row-name { font-weight: 600; font-size: 0.9rem; }
.loot-row-desc { font-size: 0.75rem; color: var(--text-muted); }
.loot-row-claimed {
  font-size: 0.7rem;
  color: var(--primary-gold);
  font-style: italic;
  margin-top: 2px;
}
.loot-row-qty {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--primary-gold);
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.35);
  padding: 2px 8px;
  border-radius: 10px;
  text-transform: uppercase;
}
.btn-claim-loot {
  background: rgba(212, 175, 55, 0.14);
  border: 1px solid rgba(212, 175, 55, 0.5);
  color: var(--primary-gold);
  padding: 6px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.8rem;
}
.btn-claim-loot:hover:not(:disabled) {
  background: rgba(212, 175, 55, 0.28);
  border-color: rgba(212, 175, 55, 0.85);
}
.btn-claim-loot:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Point-buy budget pill rendered next to the Ability Scores label. Turns red
   when the player has spent more than their locked budget. */
.point-buy-badge {
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 600;
  background: rgba(80, 200, 120, 0.18);
  border: 1px solid rgba(80, 200, 120, 0.5);
  color: #b6f0c8;
  letter-spacing: 0.02em;
  vertical-align: middle;
}
.point-buy-badge.point-buy-over {
  background: rgba(220, 70, 70, 0.22);
  border-color: rgba(220, 70, 70, 0.6);
  color: #ffc4c4;
}

.mini-btn {
  font-size: 0.7rem;
  padding: 4px 8px;
  margin-left: 8px;
  border-radius: 4px;
}

/* --- Overworld Map / Grid navigation tabs --- */
.tab-navigation {
  display: flex;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border-light);
  border-radius: 6px;
  padding: 3px;
  margin-bottom: 12px;
}

.tab-btn {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.tab-btn.active {
  background: rgba(145, 80, 45, 0.18);
  color: var(--primary-gold);
  border: 1px solid rgba(212, 175, 55, 0.35);
}

/* P1: Standardized hover — inactive hover uses dim amber glow, not harsh white */
.tab-btn:hover:not(.active) {
  color: hsl(43, 60%, 75%);
  background: rgba(212, 175, 55, 0.06);
  border: 1px solid rgba(212, 175, 55, 0.2);
  border-radius: 4px;
}

/* ==========================================================================
   MOBILE RESPONSIVENESS — P0 Critical Fix
   A bottom tab bar lets players switch between Party / Story / Map panels
   on small screens without any layout breakage.
   ========================================================================== */

/* Mobile bottom nav: hidden on desktop, visible on mobile */
.mobile-bottom-nav {
  display: none;
}

/* v3.0.0-alpha.2.1 — Mobile breakpoint raised 1024 → 1100. At 1024-1100px
   widths the 3-col desktop grid was technically rendering, but the center
   chat column was so cramped that the input could be clipped behind the
   right sidebar's content. Single-panel mobile layout takes over earlier
   now so there is always enough horizontal room for the input row. */
@media (max-width: 1100px) {
  /* Prevent viewport overflow on mobile */
  body {
    overflow: hidden;
  }

  /* Single-column layout: only the active panel is visible */
  .game-container {
    display: flex;
    flex-direction: column;
    padding: 0;
    height: calc(100dvh - 64px); /* leave room for the bottom nav; dynamic-viewport-height safe */
    gap: 0;
    max-width: 100%;
  }

  /* All three panels become full-height blocks; only active one is shown */
  .game-container .sidebar,
  .game-container .chat-terminal,
  .game-container .tactical-board {
    display: none;
    width: 100%;
    height: 100%;
    border-radius: 0;
    border: none;
    flex: 1;
  }

  /* The panel marked active by JS is shown */
  .game-container .sidebar.mobile-active,
  .game-container .chat-terminal.mobile-active,
  .game-container .tactical-board.mobile-active {
    display: flex;
  }

  /* Bottom navigation bar */
  .mobile-bottom-nav {
    display: flex;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 64px;
    z-index: 50;
    background: hsla(222, 24%, 9%, 0.97);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid hsla(222, 20%, 25%, 0.35);
    align-items: stretch;
  }

  .mobile-nav-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-family: var(--font-fantasy);
    font-size: 0.6rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color 0.18s ease, background 0.18s ease;
    padding: 10px 0;
    min-height: 48px; /* High-quality touch target */
  }

  .mobile-nav-btn svg {
    transition: stroke 0.18s ease, transform 0.18s ease;
  }

  .mobile-nav-btn:hover {
    color: hsl(43, 60%, 75%);
    background: rgba(212, 175, 55, 0.05);
  }

  .mobile-nav-btn.active {
    color: var(--primary-gold);
    background: rgba(212, 175, 55, 0.07);
  }

  .mobile-nav-btn.active svg {
    filter: drop-shadow(0 0 4px rgba(212, 175, 55, 0.6));
    transform: translateY(-1px);
  }

  /* Touch-optimized button scales */
  .action-btn, .msg-regen-btn, .btn-claim-loot, .btn-edit-character, .btn-short-rest, .btn-long-rest {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* Smaller font sizes for packed mobile layouts */
  .terminal-header h1 { font-size: 1rem; }
  .sidebar-header h2, .board-header h2 { font-size: 1rem; }

  /* Prevent board instructions from being cramped on mobile */
  .board-instructions { padding: 8px 16px 12px; }

  /* Make the action input comfortably tall on touch */
  .input-area input { padding: 14px 12px; font-size: 1rem; }
  .btn-primary { padding: 14px 20px; font-size: 1rem; }

  /* Make Tactical Board container flex-layout in mobile view */
  .game-container .tactical-board.mobile-active {
    display: flex;
    flex-direction: column;
    height: 100%;
    box-sizing: border-box;
    padding: 12px;
    gap: 12px;
    overflow-y: auto; /* let overall panel scroll if accordions are expanded */
  }

  /* Stretch map and node canvases vertically to utilize the whole tall screen */
  .game-container .tactical-board.mobile-active .canvas-container {
    flex: 1;
    min-height: 420px; /* Comfortably tall layout */
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
  }

  /* Make sure the drawing canvas scales nicely up and down inside the container */
  #battlefield-canvas {
    max-width: 100%;
    max-height: 100%;
    width: auto !important;
    height: auto !important;
    aspect-ratio: 1 / 1;
  }

  /* Stretch the Quest Journal / Codex view to cover the whole viewport */
  .game-container .tactical-board.mobile-active .journal-view {
    flex: 1;
    max-height: none; /* remove fixed 380px constraint */
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    min-height: 420px;
  }

  .game-container .tactical-board.mobile-active .journal-scroll {
    flex: 1;
    max-height: none;
    overflow-y: auto;
  }

  /* ===================================================
     FIX 1 (P0) — Collapsed mobile header
     Only show: title + location chip + settings button.
     Everything else moves into the settings modal.
     =================================================== */
  .terminal-header {
    flex-wrap: nowrap;
    padding: 8px 10px;
    min-height: 52px;
    gap: 8px;
    align-items: center;
    overflow: hidden;
  }

  /* Keep the status dot but shrink it */
  .status-indicator { flex-shrink: 0; }

  /* Title shrinks to fit, no wrapping */
  .terminal-header h1 {
    font-size: 0.85rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
  }

  /* Hide: room-ID inside the h1, voice/ambient toggles, calendar date, invite btn */
  #campaign-room-badge    { display: none; }
  /* Keep the underlying checkboxes accessible (for JS/tests) but visually hidden */
  .voice-toggle-label     {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
  }
  .campaign-date          { display: none; }
  #btn-invite             { display: none; }

  /* Keep the scene location chip and settings gear */
  #scene-chip             { display: inline-flex !important; font-size: 0.72rem; }
  #btn-open-settings      {
    flex-shrink: 0;
    font-size: 1.2rem;
    padding: 6px 8px;
    min-height: 40px;
    min-width: 40px;
  }

  /* The header-controls row: only the settings button remains */
  .header-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
  }

  /* ===================================================
     FIX 2 (P0) — Hide native scrollbar on all panels
     =================================================== */
  .message-feed,
  .sidebar,
  .tactical-board {
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .message-feed::-webkit-scrollbar,
  .sidebar::-webkit-scrollbar,
  .tactical-board::-webkit-scrollbar { display: none; }

  /* ===================================================
     FIX 3 (P1) — Compact input row: icon-only send btn
     =================================================== */
  .input-area {
    padding: 8px 10px;
    gap: 6px;
  }
  /* Shrink the Declare button to icon-only on mobile */
  .btn-primary .btn-declare-label { display: none; }
  .btn-primary {
    padding: 14px 14px;
    min-width: 48px;
    flex-shrink: 0;
  }

  /* ===================================================
     FIX 6 (P2) — Hide token inspector hint bar on mobile
     The "👆 Click a token to inspect it." box wastes ~70px
     =================================================== */
  #tactical-selection { display: none; }

  /* ===================================================
     FIX 7 (P3) — Compress the Legend accordion
     =================================================== */
  #tactical-legend > summary {
    padding: 6px 10px;
    font-size: 0.78rem;
  }
}

/* --- Asymmetric Private Whisper bubble --- */
.private-whisper-msg {
  background: rgba(212, 175, 55, 0.08);
  border: 1px solid rgba(212, 175, 55, 0.35);
  box-shadow: 0 0 10px rgba(212, 175, 55, 0.1);
  border-left: 4px solid var(--primary-gold);
  border-radius: 6px;
  padding: 10px 14px;
  margin-bottom: 12px;
  animation: privateWhisperPulse 2.5s infinite alternate ease-in-out;
}

.private-whisper-msg .msg-header {
  color: var(--primary-gold);
  font-weight: bold;
  font-size: 0.85rem;
  margin-bottom: 4px;
  display: flex;
  align-items: center;
  gap: 6px;
}

@keyframes privateWhisperPulse {
  0% {
    box-shadow: 0 0 8px rgba(212, 175, 55, 0.08);
    background: rgba(212, 175, 55, 0.06);
  }
  100% {
    box-shadow: 0 0 14px rgba(212, 175, 55, 0.22);
    background: rgba(212, 175, 55, 0.11);
  }
}

/* v3.23.0 — World map peril chips */
.world-loc {
  border-left: 3px solid rgba(255, 255, 255, 0.08);
  padding-left: 0.6rem;
}
.world-loc.peril-high  { border-left-color: #E74C3C; }
.world-loc.peril-medium{ border-left-color: #F39C12; }
.world-loc.peril-low   { border-left-color: #2ECC71; }
.world-loc.peril-unknown { border-left-color: #95A5A6; }
.map-current { color: #F1C40F; font-weight: 600; margin-left: 0.4rem; }
.map-visited { color: #BDC3C7; margin-left: 0.4rem; font-size: 0.85em; }
.map-known   { color: #7F8C8D; margin-left: 0.4rem; font-size: 0.85em; }

/* v3.24.0 — Combat event strip */
.combat-event-strip {
  max-height: 6.2em;
  overflow-y: auto;
  font-size: 0.78em;
  border-top: 1px solid rgba(255,255,255,0.06);
  margin-top: 0.4rem;
  padding: 0.3rem 0.4rem;
  color: #BDC3C7;
  font-family: 'Menlo', 'Consolas', monospace;
}
.combat-event-row {
  line-height: 1.35;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}
.combat-event-row.evt-unit_defeated { color: #E74C3C; }
.combat-event-row.evt-damage_dealt  { color: #F39C12; }
.combat-event-row.evt-healing_applied { color: #2ECC71; }

/* v3.32.0 — Backpack pager (▲/▼ at the bottom of the inventory list when
   the player has > 6 items). Keeps the side panel a predictable height
   on long sessions. */
.inv-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-top: 4px;
  padding: 4px 6px;
  border-top: 1px dashed var(--border-light);
  list-style: none;
}
.inv-pager-btn {
  background: rgba(255,255,255,0.05);
  color: var(--text-muted);
  border: 1px solid var(--border-light);
  border-radius: 3px;
  width: 28px;
  height: 22px;
  line-height: 1;
  cursor: pointer;
  font-size: 0.8rem;
}
.inv-pager-btn:hover:not(:disabled) {
  background: rgba(212, 175, 55, 0.15);
  color: var(--accent-gold, #D4AF37);
}
.inv-pager-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.inv-pager-label {
  font-size: 0.75rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.inv-empty {
  color: var(--text-muted);
  font-style: italic;
  font-size: 0.78rem;
}

/* v3.33.0 (Spec 30 / G13) — Collapsed Web Speech voice picker. Lives
   inside the settings panel and stays inline; the disclosure triangle is
   the only visible affordance until expanded. */
.webspeech-details {
  margin: 6px 0 0;
}
.webspeech-summary {
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text-muted);
  list-style-position: outside;
  padding: 4px 0;
  user-select: none;
}
.webspeech-summary::marker { color: var(--text-muted); }
.webspeech-details[open] > .webspeech-summary {
  color: var(--text-primary, #ecf0f1);
}

/* v3.36.0 (Spec 23 / Slice 1) — Companions panel under the party HUD. */
.followers-items {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.78rem;
  padding: 0;
  margin: 0;
}
.follower-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px;
  background: rgba(255, 255, 255, 0.03);
  border-left: 2px solid rgba(212, 175, 55, 0.35);
  border-radius: 3px;
}
.follower-icon { font-size: 1.05em; }
.follower-name { flex: 1; color: var(--text-primary, #ecf0f1); }
.follower-meta {
  color: var(--text-muted);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
}

/* v3.40.0 (Spec 24 / Slice 3) — Starter-quest card sits above the
   architect wizard and offers a 1-click path into Hollowmere. */
.starter-card {
  border: 1px solid rgba(212, 175, 55, 0.35);
  background: linear-gradient(135deg, rgba(212, 175, 55, 0.08), rgba(212, 175, 55, 0.02));
  border-radius: 6px;
  padding: 14px 16px;
  margin-bottom: 12px;
}
.starter-card h3 {
  margin-top: 0;
  color: #F1C40F;
}

/* v3.46.0 (Spec 24 / Slice 4) — Hint overlay tooltip. Absolutely
   positioned near its anchor; styled like the achievement toast but
   gold + readable on dark backgrounds. Non-modal — gameplay never
   blocks on it. */
.hint-bubble {
  position: absolute;
  z-index: 999;
  max-width: 320px;
  padding: 10px 14px;
  background: linear-gradient(135deg, rgba(212, 175, 55, 0.14), rgba(212, 175, 55, 0.04));
  border: 1px solid rgba(212, 175, 55, 0.55);
  border-radius: 6px;
  color: var(--text-primary, #ecf0f1);
  font-size: 0.85rem;
  line-height: 1.4;
  box-shadow: 0 4px 18px rgba(0,0,0,0.45);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  transition: opacity 0.22s ease;
  /* Advisory coachmark: must NOT intercept taps meant for the control it points
     at (Story 10.5 / GA fix — on narrow viewports the absolute-positioned bubble
     could overlay interactive controls like #btn-start-combat and eat the tap).
     Only the dismiss ✕ stays interactive (below). */
  pointer-events: none;
}
.hint-bubble .hint-text { flex: 1; }
.hint-bubble .hint-text em { color: var(--accent-gold, #D4AF37); font-style: normal; }
.hint-bubble .hint-text strong { color: var(--accent-gold, #D4AF37); }
.hint-bubble .hint-dismiss {
  background: transparent;
  color: var(--text-muted);
  border: 0;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 2px 4px;
  pointer-events: auto; /* re-enable taps on the dismiss ✕ (bubble body is none) */
}
.hint-bubble .hint-dismiss:hover { color: var(--accent-gold, #D4AF37); }

/* v3.47.0 (Spec 23 / Slice 5) — Follower command chip rail. Appears on
   rows the active PC owns; one-click chips for the four basic verbs. */
.follower-row { flex-wrap: wrap; }
.follower-cmd-rail {
  display: flex;
  gap: 4px;
  margin-top: 4px;
  flex: 1 0 100%;
}
.follower-cmd {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border-light);
  border-radius: 3px;
  width: 26px;
  height: 22px;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.follower-cmd:hover {
  background: rgba(212, 175, 55, 0.15);
  border-color: var(--accent-gold, #D4AF37);
}

/* =============================================================
 * v3.54.0 (Spec 28) — UI consolidation
 * =============================================================
 *  - Right rail is map-only at full available height
 *  - Aux features live in the slide-out Overview drawer
 *  - Condensed top bar
 */

/* Overview button in the top bar — looks like a chip, not just an icon. */
.overview-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.4);
  border-radius: 4px;
  padding: 4px 10px;
}
.overview-btn:hover {
  background: rgba(212, 175, 55, 0.22);
}
.overview-btn-text {
  font-size: 0.82rem;
  font-weight: 600;
}

/* Backdrop dims the rest of the UI when the drawer is open. */
.overview-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.42);
  z-index: 950;
  opacity: 0;
  transition: opacity 0.22s ease;
  pointer-events: none;
}
.overview-backdrop.visible {
  opacity: 1;
  pointer-events: auto;
}

/* Drawer slides in from the right edge. */
.overview-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(440px, 86vw);
  background: linear-gradient(145deg, #11151c, #161a22);
  border-left: 1px solid rgba(212, 175, 55, 0.28);
  box-shadow: -8px 0 32px rgba(0, 0, 0, 0.55);
  z-index: 960;
  transform: translateX(100%);
  transition: transform 0.28s cubic-bezier(0.2, 0.65, 0.3, 1);
  display: flex;
  flex-direction: column;
  color: var(--text-primary, #ecf0f1);
}
.overview-drawer.visible {
  transform: translateX(0);
}
.overview-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
}
.overview-drawer-header h2 { margin: 0; }

.overview-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
  background: rgba(0,0,0,0.18);
}
.overview-tab {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 4px;
  padding: 5px 10px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.8rem;
  white-space: nowrap;
}
.overview-tab:hover { background: rgba(212,175,55,0.10); color: var(--text-primary, #ecf0f1); }
.overview-tab.active {
  background: rgba(212,175,55,0.18);
  border-color: rgba(212,175,55,0.55);
  color: #F1C40F;
}

.overview-pane-container {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
}
.overview-pane { display: none; }
.overview-pane.active { display: block; }
.overview-pane h3 { margin-top: 0; }
.overview-pane .description {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 8px;
}
.w-full { width: 100%; }

/* Right rail grows. Existing .tactical-board grid column gets more space. */
.app-layout .tactical-board {
  min-width: 380px;
}
/* v3.55.0 (Spec 28 slice 2) — Right rail expands on wider screens; the
   canvas inside .canvas-container resizes responsively via the
   container's aspect-ratio + the ResizeObserver in app.js. The legacy
   fixed-px overrides (`width: 440px !important`) were locking the
   canvas at small sizes and have been removed. */
@media (min-width: 1400px) {
  .app-layout {
    grid-template-columns: 280px 1fr 520px !important;
  }
}
@media (min-width: 1700px) {
  .app-layout {
    grid-template-columns: 300px 1fr 620px !important;
  }
}

/* v3.56.0 (Spec 28 Option B) — World-map modal upgrade. The card grows
   to ~90vw with a healthy max-width so the canvas has room to draw
   nodes at a readable size; the legend collapses into <details>. */
.world-map-card {
  width: min(900px, 92vw);
  max-width: 92vw;
}
.world-map-canvas-wrap {
  width: 100%;
  margin: 12px 0 8px;
  background: linear-gradient(135deg, #14110d 0%, #0b0907 100%);
  border: 1px solid rgba(212, 175, 55, 0.25);
  border-radius: 8px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 2;
  min-height: 360px;
}
#world-map-canvas {
  width: 100%;
  height: 100%;
  display: block;
}
.world-map-legend > summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text-muted);
  padding: 6px 0;
  user-select: none;
}
.world-map-legend[open] > summary { color: var(--text-primary, #ecf0f1); }

/* v3.59.0 — Mobile scene-image chip. Hidden on desktop (the left-rail
   strip is the canonical view). On mobile (≤1100px) pinned above the
   chat feed; collapsed by default, expands inline on click. */
.scene-image-chip {
  display: none;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 10px;
  background: hsla(222, 30%, 8%, 0.6);
  border: 1px solid hsla(220, 30%, 30%, 0.4);
  border-radius: 8px;
  color: var(--text-muted);
  font-size: 0.78rem;
  text-align: left;
  cursor: pointer;
}
.scene-image-chip.hidden { display: none !important; }
.scene-image-chip img {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.scene-image-chip.expanded img {
  width: 100%;
  height: auto;
  max-height: 55vh;
  object-fit: contain;
}
.scene-image-chip.expanded {
  flex-direction: column;
  align-items: stretch;
}
.scene-image-chip:hover { color: var(--text-primary, #ecf0f1); }
@media (max-width: 1100px) {
  .scene-image-chip:not(.hidden) { display: flex; }
}

/* v3.66.0 — agent watchdog stall banner */
.chat-line.agent-stall {
  background: rgba(212, 175, 55, 0.08);
  border-left: 3px solid var(--accent-gold, #D4AF37);
  padding: 0.5rem 0.75rem;
  display: flex;
  gap: 0.5rem;
  align-items: center;
  font-style: italic;
  margin: 0.5rem 0;
}
.chat-line.agent-stall .stall-retry {
  margin-left: auto;
  padding: 0.25rem 0.5rem;
  background: transparent;
  border: 1px solid var(--accent-gold, #D4AF37);
  color: var(--accent-gold, #D4AF37);
  cursor: pointer;
  font-family: inherit;
}
.chat-line.agent-stall .stall-retry:hover {
  background: rgba(212, 175, 55, 0.15);
}

/* --- Story 2.1 (v3.68.0): Hero lobby cold-open ---------------------------
   Full-bleed landing surface shown when useRedesignUI=true, no creator-token
   and no active character. Sibling to #setup-modal (NOT a modal). References
   the existing --font-fantasy token; Story 1.2 swaps that token's value to
   Cormorant Garamond's fallback chain — no CSS change needed here.
   Mobile-first single column; dark-only; light prefers-color-scheme only
   mutes contrast, never flips palette (NFR-10).
-------------------------------------------------------------------------- */
.hero-lobby {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bg-deep);
  overflow: hidden;
}
.hero-lobby.hidden {
  display: none;
}
.hero-lobby-art {
  position: absolute;
  inset: 0;
  background-color: var(--bg-deep);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Vignette so the title remains legible regardless of cover art. */
  box-shadow: inset 0 0 200px 60px rgba(0, 0, 0, 0.65);
}
.hero-lobby-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.25) 0%,
    rgba(0, 0, 0, 0.55) 60%,
    rgba(0, 0, 0, 0.85) 100%
  );
}
.hero-lobby-overlay {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2.5rem;
  padding: 2rem 1.5rem;
  width: 100%;
  max-width: 720px;
  text-align: center;
}
.hero-title {
  font-family: var(--font-fantasy);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-primary);
  font-size: clamp(2rem, 7vw, 3.75rem);
  line-height: 1.15;
  margin: 0;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.75);
}
.hero-begin {
  font-family: var(--font-fantasy);
  font-size: 1.125rem;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  background: transparent;
  border: 1px solid var(--border-active);
  border-radius: 2px;
  padding: 0.875rem 2.5rem;
  min-width: 9rem;
  min-height: 48px;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
.hero-begin:hover,
.hero-begin:focus-visible {
  background-color: rgba(255, 255, 255, 0.06);
  border-color: var(--primary-gold);
  outline: none;
}
.hero-begin:focus-visible {
  box-shadow: 0 0 0 2px var(--primary-gold-glow);
}
/* Patch P2 (review iteration 1) — calm storyteller-voice status note
   surfaced when beginFlow can't reach the server. No role="alert"
   (NFR-15); aria-live="polite" lives on the element. */
.hero-status {
  font-family: var(--font-fantasy);
  color: var(--text-secondary, hsl(220, 14%, 70%));
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  margin: 0;
  opacity: 0.85;
}

/* Mobile portrait (≤639px): single column, button reachable without
   scrolling. Tap target ≥44×44 per WCAG 2.2 AA. */
@media (max-width: 639px) {
  .hero-lobby-overlay {
    gap: 2rem;
    padding: 1.5rem 1.25rem;
  }
  .hero-begin {
    width: auto;
    min-width: 8rem;
  }
}

/* Light-scheme preference only mutes contrast on the title; never flips
   the palette. Dark-only theme persists (NFR-10). */
@media (prefers-color-scheme: light) {
  .hero-title {
    color: hsl(220, 16%, 88%);
  }
}

/* Reduced motion: instant paint, no transitions. The lobby has none by
   default — this rule guards against any inherited animations. */
@media (prefers-reduced-motion: reduce) {
  .hero-lobby,
  .hero-begin {
    transition: none;
  }
}

/* Story 5.9 — architecture-explainer link below the begin button. Footer-
   styled, not a CTA. Subtle by design (smaller serif, secondary color) but
   keeps contrast ≥ 4.5:1 against the lobby backdrop (WCAG 2.2 AA per Story
   5.3 axe-core gate). No `opacity` dim — the dimmer hue alone reads as
   secondary; opacity-dimming would drop contrast below AA. */
.hero-lobby-explainer-link {
  font-family: var(--font-display, 'Cormorant Garamond', 'Spectral', Georgia, serif);
  font-size: 14px;
  color: var(--color-frost-dim, hsl(220, 14%, 65%));
  text-decoration: none;
  margin-top: 24px;
  letter-spacing: 0.02em;
  transition: color 0.2s ease;
}
.hero-lobby-explainer-link:hover,
.hero-lobby-explainer-link:focus-visible {
  color: var(--color-frost, hsl(220, 20%, 94%));
  outline: none;
}
.hero-lobby-explainer-link:focus-visible {
  text-decoration: underline;
  text-underline-offset: 4px;
}
/* Reduced-motion gate is co-located with the rule that has the transition,
   not bundled with unrelated selectors above — the !important is needed here
   because .hero-lobby-explainer-link's own `transition: color 0.2s ease`
   above wins specificity against a plain `transition: none`. */
@media (prefers-reduced-motion: reduce) {
  .hero-lobby-explainer-link {
    transition: none !important;
  }
}

/* --- Story 2.2 (v3.68.0): Two-starter card stack -------------------------
   Selection surface reached only after `begin` from the hero lobby. NOT a
   modal — role="radiogroup" with two role="radio" cards. Mobile-first
   horizontal scroll-snap track; tablet/desktop side-by-side. All rules are
   scoped under .starter-card-stack so the legacy .starter-card (a
   setup-modal section container at style.css:3793) keeps its own visual.
-------------------------------------------------------------------------- */
.starter-card-stack {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  background-color: var(--bg-deep);
  overflow: hidden;
}
.starter-card-stack.hidden {
  display: none;
}
.starter-card-track {
  display: flex;
  gap: 1.5rem;
  width: 100%;
  max-width: 1100px;
  padding: 2rem 1.5rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.starter-card-track::-webkit-scrollbar {
  display: none;
}
.starter-card-stack .starter-card {
  flex: 0 0 88%;
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(20, 22, 30, 0.65), rgba(8, 10, 14, 0.85));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  padding: 0;
  min-height: 320px;
  cursor: pointer;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
  overflow: hidden;
  outline: none;
  /* Override the legacy .starter-card cosmetic so cascade collisions don't
     leak the gold-border setup-modal styling onto these cards. */
  margin-bottom: 0;
  background-color: transparent;
}
.starter-card-stack .starter-card-art {
  width: 100%;
  height: 160px;
  background-color: rgba(212, 175, 55, 0.05);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  flex-shrink: 0;
}
/* Per-template default art tint (cover URLs land at runtime if available).
   The tint gives each card a distinct silhouette without color-only state. */
.starter-card-stack .starter-card-art[data-template="sunken-library"] {
  background-image: linear-gradient(135deg, rgba(40, 80, 120, 0.55), rgba(15, 30, 50, 0.85));
}
.starter-card-stack .starter-card-art[data-template="hollowmere"] {
  background-image: linear-gradient(135deg, rgba(120, 80, 40, 0.55), rgba(50, 30, 15, 0.85));
}
.starter-card-stack .starter-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.25rem 1.25rem 1.5rem;
  flex: 1;
}
.starter-card-stack .starter-card-title {
  font-family: var(--font-fantasy);
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--text-primary);
  font-size: clamp(1.25rem, 3vw, 1.5rem);
  margin: 0;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
}
.starter-card-stack .starter-card-hook {
  font-family: 'Inter', system-ui, sans-serif;
  color: var(--text-secondary, hsl(220, 14%, 75%));
  font-size: 0.95rem;
  line-height: 1.55;
  margin: 0;
}
/* Text indicator for the selected card — only visible when aria-checked
   is true. Pairs with the glow ring so the cue is not color-only (FR-6.1
   / NFR-6 WCAG floor). */
.starter-card-stack .starter-card-selected-label {
  display: none;
  font-family: var(--font-fantasy);
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  color: var(--primary-gold, #d4af37);
  text-transform: lowercase;
  margin-top: auto;
}
.starter-card-stack .starter-card[aria-checked="true"] .starter-card-selected-label {
  display: inline-block;
}
/* Selected state: glow ring + (above) text indicator. Color is not the
   only cue. Reserved-ember accent token if Story 1.2 has shipped it,
   otherwise the gold-glow fallback used elsewhere. */
.starter-card-stack .starter-card[aria-checked="true"] {
  border-color: var(--primary-gold, #d4af37);
  box-shadow: 0 0 0 2px var(--ember-accent, var(--primary-gold-glow, rgba(212, 175, 55, 0.55))),
              0 8px 32px rgba(0, 0, 0, 0.45);
}
.starter-card-stack .starter-card:hover {
  border-color: rgba(212, 175, 55, 0.55);
}
.starter-card-stack .starter-card:focus-visible {
  border-color: var(--primary-gold, #d4af37);
  box-shadow: 0 0 0 3px var(--primary-gold-glow, rgba(212, 175, 55, 0.55)),
              0 8px 32px rgba(0, 0, 0, 0.45);
}
.starter-card-stack .starter-card[data-busy="true"] {
  pointer-events: none;
  opacity: 0.6;
}

/* Tablet / desktop (≥640px): side-by-side, no scroll-snap needed. */
@media (min-width: 640px) {
  .starter-card-track {
    overflow-x: visible;
    scroll-snap-type: none;
    justify-content: center;
  }
  .starter-card-stack .starter-card {
    flex: 1 1 0;
    max-width: 420px;
    scroll-snap-align: none;
  }
}

/* Mobile portrait (≤639px): tighter padding so both cards fit the viewport
   width with the card-edge peek that signals "swipe to compare". */
@media (max-width: 639px) {
  .starter-card-track {
    padding: 1.5rem 1.25rem;
    gap: 1rem;
  }
  .starter-card-stack .starter-card {
    flex: 0 0 88%;
    min-height: 280px;
  }
}

/* Light-scheme preference mutes contrast only; no theme flip (NFR-10). */
@media (prefers-color-scheme: light) {
  .starter-card-stack .starter-card-title {
    color: hsl(220, 16%, 88%);
  }
}

/* Reduced motion: no card-swap transitions. */
@media (prefers-reduced-motion: reduce) {
  .starter-card-stack .starter-card {
    transition: none;
  }
}

/* ===================================================================
   Story 2.3: Four-tap onboarding (#onboarding-flow).
   Reached via the card-stack confirm. Two screens toggled via .hidden.
   All rules scoped under .onboarding-flow to avoid bleed into the
   legacy #setup-modal's #char-form rules. Mirrors the .starter-card-stack
   pattern (full-bleed, column flex, single source of truth for inert
   chrome via _uxLockChrome()).
   =================================================================== */

.onboarding-flow {
  position: fixed;
  inset: 0;
  z-index: 1450; /* above .starter-card-stack (1400), below modals (>= 2000) */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 2rem 1rem;
  background:
    radial-gradient(ellipse at top, hsla(220, 30%, 12%, 0.7) 0%, hsla(220, 35%, 6%, 0.95) 100%),
    hsl(220, 35%, 6%);
  overflow-y: auto;
}

.onboarding-flow.hidden {
  display: none;
}

.onboarding-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  width: 100%;
  max-width: 880px;
}

.onboarding-screen.hidden {
  display: none;
}

/* --- Screen 1: name + class radiogrid --- */

.onboarding-name-label {
  font-family: var(--font-ui, 'Inter', sans-serif);
  font-size: 0.85rem;
  color: hsl(220, 16%, 72%);
  text-transform: lowercase;
  letter-spacing: 0.03em;
  align-self: center;
}

.onboarding-name-input {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-size: 1.4rem;
  color: hsl(220, 16%, 92%);
  background: hsla(220, 30%, 14%, 0.7);
  border: 1px solid hsl(220, 24%, 26%);
  border-radius: 4px;
  padding: 0.65rem 1rem;
  width: min(420px, 90vw);
  text-align: center;
  outline: none;
}

.onboarding-name-input:focus-visible {
  border-color: hsl(42, 60%, 60%);
  box-shadow: 0 0 0 3px hsla(42, 60%, 60%, 0.35);
}

.onboarding-class-grid {
  display: flex;
  flex-direction: row;
  gap: 1rem;
  width: 100%;
  justify-content: center;
  flex-wrap: wrap;
}

.onboarding-class-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: hsla(220, 30%, 14%, 0.7);
  border: 1px solid hsl(220, 24%, 26%);
  border-radius: 6px;
  width: min(360px, 90vw);
  min-height: 220px;
  padding: 0;
  cursor: pointer;
  transition: border-color 200ms ease, box-shadow 200ms ease;
  overflow: hidden;
}

.onboarding-class-art {
  width: 100%;
  height: 110px;
  background: linear-gradient(135deg, hsl(220, 30%, 22%) 0%, hsl(220, 30%, 12%) 100%);
}

.onboarding-class-art[data-class="fighter"] {
  background: linear-gradient(135deg, hsl(15, 30%, 28%) 0%, hsl(220, 30%, 12%) 100%);
}

.onboarding-class-art[data-class="cleric"] {
  background: linear-gradient(135deg, hsl(48, 30%, 28%) 0%, hsl(220, 30%, 12%) 100%);
}

.onboarding-class-body {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.9rem 1rem 1rem 1rem;
}

.onboarding-class-title {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-size: 1.5rem;
  margin: 0;
  color: hsl(42, 50%, 78%);
  text-transform: lowercase;
  font-weight: 500;
}

.onboarding-class-hook {
  font-family: var(--font-ui, 'Inter', sans-serif);
  font-size: 0.9rem;
  color: hsl(220, 16%, 78%);
  line-height: 1.45;
  margin: 0;
}

.onboarding-class-selected-label {
  display: none;
  margin-top: 0.4rem;
  font-family: var(--font-ui, 'Inter', sans-serif);
  font-size: 0.75rem;
  color: hsl(42, 60%, 70%);
  text-transform: lowercase;
  letter-spacing: 0.08em;
}

.onboarding-class-card[aria-checked="true"] .onboarding-class-selected-label {
  display: inline-block;
}

.onboarding-class-card[aria-checked="true"] {
  border-color: hsl(42, 60%, 60%);
  box-shadow: 0 0 0 1px hsl(42, 60%, 60%), 0 0 18px hsla(42, 60%, 60%, 0.35);
}

.onboarding-class-card:hover {
  border-color: hsl(220, 24%, 40%);
}

.onboarding-class-card:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px hsla(42, 60%, 60%, 0.55), 0 0 14px hsla(42, 60%, 60%, 0.25);
}

.onboarding-class-card[data-busy="true"] {
  pointer-events: none;
  opacity: 0.6;
}

/* --- Screen 2: ability spread + begin + customize --- */

.onboarding-screen-2-title {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-size: 1.6rem;
  color: hsl(42, 50%, 78%);
  margin: 0;
  text-align: center;
  font-weight: 500;
  text-transform: lowercase;
}

.onboarding-ability-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(64px, 1fr));
  gap: 0.5rem;
  width: 100%;
  max-width: 560px;
  font-family: var(--font-ui, 'Inter', sans-serif);
}

.onboarding-ability-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  padding: 0.6rem 0.4rem;
  background: hsla(220, 30%, 14%, 0.7);
  border: 1px solid hsl(220, 24%, 26%);
  border-radius: 4px;
  color: hsl(220, 16%, 92%);
}

.onboarding-ability-label {
  font-size: 0.7rem;
  color: hsl(220, 16%, 70%);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.onboarding-ability-value {
  font-size: 1.3rem;
  font-weight: 600;
}

.onboarding-ability-mod {
  font-size: 0.78rem;
  color: hsl(220, 16%, 70%);
}

.onboarding-customize-toggle {
  background: transparent;
  color: hsl(220, 16%, 78%);
  border: 1px solid hsl(220, 24%, 26%);
  border-radius: 4px;
  /* NFR-8: ≥44×44 tap target */
  min-height: 44px;
  min-width: 44px;
  padding: 0.5rem 1rem;
  font-family: var(--font-ui, 'Inter', sans-serif);
  font-size: 0.85rem;
  cursor: pointer;
  text-transform: lowercase;
}

.onboarding-customize-toggle:hover {
  border-color: hsl(220, 24%, 40%);
  color: hsl(220, 16%, 92%);
}

.onboarding-customize-toggle:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px hsla(42, 60%, 60%, 0.55);
}

.onboarding-customize-toggle[disabled],
.onboarding-customize-toggle:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.onboarding-customize-host {
  width: 100%;
  max-width: 720px;
  padding: 1rem;
  background: hsla(220, 30%, 12%, 0.6);
  border: 1px solid hsl(220, 24%, 22%);
  border-radius: 6px;
}

.onboarding-customize-host.hidden {
  display: none;
}

.onboarding-begin-btn {
  background: hsl(42, 50%, 32%);
  color: hsl(42, 50%, 92%);
  border: 1px solid hsl(42, 60%, 60%);
  border-radius: 4px;
  padding: 0.75rem 2.5rem;
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-size: 1.3rem;
  letter-spacing: 0.08em;
  cursor: pointer;
  text-transform: lowercase;
  min-width: 200px;
  min-height: 44px;
}

.onboarding-begin-btn:hover {
  background: hsl(42, 50%, 38%);
}

.onboarding-begin-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px hsla(42, 60%, 60%, 0.55), 0 0 14px hsla(42, 60%, 60%, 0.25);
}

.onboarding-begin-btn[disabled],
.onboarding-begin-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Status note (calm aria-live="polite" — e.g. validator error, server error). */
.onboarding-flow #onboarding-status {
  font-family: var(--font-ui, 'Inter', sans-serif);
  font-size: 0.9rem;
  color: hsl(220, 16%, 78%);
  text-align: center;
  margin: 0;
}

/* Mobile portrait: single-column stack of cards. */
@media (max-width: 639px) {
  .onboarding-class-grid {
    flex-direction: column;
    align-items: center;
  }
  .onboarding-class-card {
    width: min(420px, 95vw);
  }
  .onboarding-ability-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Light scheme: mute contrast, do not theme-flip (NFR-10). */
@media (prefers-color-scheme: light) {
  .onboarding-class-title,
  .onboarding-screen-2-title {
    color: hsl(220, 16%, 88%);
  }
}

/* Reduced motion: no transitions. */
@media (prefers-reduced-motion: reduce) {
  .onboarding-class-card,
  .onboarding-begin-btn {
    transition: none;
  }
}

/* Black → scene-image fade-in after starter_campaign_ready (Story 2.3 AC #6).
   Triggered by adding .scene-art-fading-in class on #scene-image at the moment
   the world starts loading. Removed after ~3s. Story 1.3 may later replace
   this with its narration-cadence-defined transition. */
#scene-image.scene-art-fading-in {
  animation: scene-art-fade-in 3s ease-in forwards;
}

@keyframes scene-art-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  #scene-image.scene-art-fading-in {
    animation: none;
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Resume card (Story 2.4)
   Full-bleed returning-user landing — sibling to .hero-lobby, .starter-card-
   stack, .onboarding-flow. z-index sits between the cold-open lobby and the
   downstream selection surfaces. Mirrors .hero-lobby's art + overlay pattern
   so the visual rhythm of the redesign chain stays consistent.
-------------------------------------------------------------------------- */
.resume-card {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bg-deep);
  overflow: hidden;
}

.resume-card.hidden {
  display: none;
}

.resume-card-art {
  position: absolute;
  inset: 0;
  background-color: var(--bg-deep);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Vignette so the recap remains legible regardless of source image. */
  box-shadow: inset 0 0 200px 60px rgba(0, 0, 0, 0.7);
}

.resume-card-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.30) 0%,
    rgba(0, 0, 0, 0.65) 55%,
    rgba(0, 0, 0, 0.90) 100%
  );
}

.resume-card-overlay {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 2rem 1.5rem;
  width: 100%;
  max-width: 720px;
  text-align: center;
}

.resume-card-tag {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-style: italic;
  color: hsl(220, 16%, 70%);
  font-size: 1rem;
  letter-spacing: 0.06em;
  margin: 0;
}

/* Story 6.5 — world-tick dispatches surfaced above the resume header.
   Plain prose; rich Voiced-Letter typography is Epic 7. */
.resume-card-dispatches {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0 0 0.25rem;
  max-width: 36em;
}

.resume-card-dispatch {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  color: hsl(220, 16%, 82%);
  font-size: clamp(1rem, 2vw, 1.125rem);
  line-height: 1.6;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.75);
}

/* Story 7.2 — Voiced Letter typography variant (addendum §A.3). Letter-only;
   plain world-tick dispatches keep the rule above. The accent border COLOR is
   set inline per NPC (deterministic from name — _voicedLetterAccent). */
.resume-card-dispatch.voiced-letter {
  font-style: italic;
  font-size: clamp(1rem, 2.2vw, 1.125rem); /* ~18px desktop / 16px mobile */
  border-left: 3px solid var(--accent-gold, hsl(45, 60%, 60%)); /* color overridden inline */
  padding-left: 0.75rem;
  animation: voicedLetterFadeIn 420ms ease-out both;
}

.voiced-letter-from {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-style: italic;
  text-transform: lowercase;
  color: hsl(220, 16%, 70%);
  font-size: 0.85em;
  letter-spacing: 0.04em;
  margin: 0 0 0.15rem;
}

@keyframes voicedLetterFadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Story 5.5 contract — reduced-motion gate for the letter fade-in. */
@media (prefers-reduced-motion: reduce) {
  .resume-card-dispatch.voiced-letter {
    animation: none;
  }
}

.resume-card-summary {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  color: var(--text-primary);
  font-size: clamp(1.125rem, 2.4vw, 1.375rem);
  line-height: 1.7;
  max-width: 36em;
  margin: 0;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.75);
}

.resume-card-cta {
  font-family: var(--font-fantasy, 'Cormorant Garamond', serif);
  font-size: 1.125rem;
  letter-spacing: 0.08em;
  color: var(--text-primary);
  background: transparent;
  border: 1px solid var(--border-active);
  border-radius: 2px;
  /* NFR-8: ≥44×44 tap target. */
  min-width: 12rem;
  min-height: 48px;
  padding: 0.875rem 2.5rem;
  cursor: pointer;
  text-transform: lowercase;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}

.resume-card-cta:hover,
.resume-card-cta:focus-visible {
  background-color: rgba(255, 255, 255, 0.06);
  border-color: var(--primary-gold);
  outline: none;
}

.resume-card-cta:focus-visible {
  box-shadow: 0 0 0 2px var(--primary-gold-glow);
}

/* Mobile portrait: tighter gaps so the recap + CTA fit without scrolling. */
@media (max-width: 639px) {
  .resume-card-overlay {
    gap: 1.25rem;
    padding: 1.5rem 1.25rem;
  }
  .resume-card-cta {
    width: auto;
    min-width: 10rem;
  }
}

/* Light-scheme preference: mute contrast only — no theme flip (NFR-10). */
@media (prefers-color-scheme: light) {
  .resume-card-summary,
  .resume-card-cta {
    color: hsl(220, 16%, 88%);
  }
}

/* Reduced motion: no transitions on the CTA. */
@media (prefers-reduced-motion: reduce) {
  .resume-card-cta {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Moonlight & Ember tokens + signature typography (Story 1.2)
   Source: _bmad-output/planning-artifacts/ux-designs/ux-DnD-2026-05-26/DESIGN.md
   Frontmatter `colors:` + `typography:` blocks.

   Scoped under `html.use-redesign-ui` so the legacy `:root` tokens at the
   top of this file remain byte-identical. The class is added/removed in
   `public/app.js` based on `sync_state.roomConfig.useRedesignUI`. Zero
   regression for legacy `useRedesignUI=false` campaigns.

   This story exposes the tokens + applies the narration prose font + the
   ember accent on the one currently-existing reserved surface
   (`.rarity-legendary`). Hero-beat emphasis word (Story 1.4), director
   stall badge (Epic 3), and recovered toast (forward-looking) are
   explicitly out of slice-1 scope — they will consume `--color-ember`
   when their stories ship.
-------------------------------------------------------------------------- */
html.use-redesign-ui {
  /* Palette — see DESIGN.md `colors` block. */
  --color-void: #050810;
  --color-ink: #0a1020;
  --color-ink-rise: #111a2e;
  --color-ink-panel: #0c1322;
  --color-ink-edge: #15203a;
  --color-frost: #e8edf6;
  --color-frost-dim: #94a8c2;
  --color-slate: #4d617f;
  --color-moon: #7fc6e6;
  --color-moon-bright: #aadcf0;
  --color-rune-violet: #8c8bd6;
  --color-ember: #f59e3a;
  --color-ember-hot: #ffb86b;
  --color-success-deep: #5ba88f;
  --color-danger-deep-fill: #a14545;
  --color-danger-deep-text: #d07a7a;
  --color-warn-mute: #c9a76a;
  --color-rarity-common: #94a8c2;
  --color-rarity-uncommon: #7fc6e6;
  --color-rarity-rare: #8c8bd6;
  --color-rarity-legendary: #f59e3a;
  --color-hp-full: #5ba88f;
  --color-hp-wounded: #c9a76a;
  --color-hp-critical: #a14545;
  --color-hp-critical-text: #d07a7a;

  /* Typography — see DESIGN.md `typography` block. The serif chain is
     Cormorant Garamond → Spectral (both loaded via Google Fonts in
     index.html) → EB Garamond / Georgia (system fallbacks). */
  --font-prose: 'Cormorant Garamond', 'Spectral', 'EB Garamond', Georgia, serif;
  --font-display: 'Cormorant Garamond', 'Spectral', 'EB Garamond', Georgia, serif;
  --font-utterance: 'Cormorant Garamond', 'Spectral', Georgia, serif;
  --font-label-caps: 'Cormorant Garamond', Georgia, serif;
  /* Body / labels / mono remain Inter / system mono — already loaded. */
  --font-body: 'Inter', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
}

/* Narration column — GM message prose only. Player bubbles stay on the
   body cascade (Inter sans). Mobile-first sizing; desktop bumps up.
   AC #3: ≥1024px viewport gets 20px / 1.75; <1024px gets 18px / 1.7. */
html.use-redesign-ui .gm-msg .msg-content {
  font-family: var(--font-prose);
  font-size: 18px;
  line-height: 1.7;
}
@media (min-width: 1024px) {
  html.use-redesign-ui .gm-msg .msg-content {
    font-size: 20px;
    line-height: 1.75;
  }
}

/* Ember accent — applied to the ONE reserved surface that currently
   exists in the codebase. Other reserved surfaces (hero-beat emphasis,
   director stall, recovered toast) will consume `--color-ember` when
   their stories ship. Legacy `.rarity-legendary` at line ~1246 retains
   its gold hue for non-redesign campaigns. */
html.use-redesign-ui .rarity-legendary {
  --rarity-color: var(--color-ember);
  --rarity-glow: 0 0 12px hsla(31, 91%, 59%, 0.55);
}

/* Light-scheme contrast mute (NFR-10 / UX-DR10). Mute the brightest
   text token; never flip palette. Existing redesign surfaces' own
   light-scheme blocks (hero lobby, card-stack, onboarding, resume) are
   self-contained and stay correct. */
@media (prefers-color-scheme: light) {
  html.use-redesign-ui {
    --color-frost: #cdd5e3;
    --color-frost-dim: #7d8da6;
  }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Hero Beat narration mode (Story 1.4 / Spec-34)
   Source: docs/specs/34-hero-beat-narration-tag.md + DESIGN.md
   `hero-beat-mode` component pattern.

   Activated by `_setHeroBeatMode('hero-beat')` in public/app.js, which adds
   `.hero-beat-active` to <body>. Released by `_setHeroBeatMode('hero-beat-
   end' | undefined)`. The class flip drives all visual transitions; the
   client gates the helper on `_useRedesignUI === true`, but we additionally
   scope every rule under `html.use-redesign-ui` for defense-in-depth — a
   stray class flip in legacy code paths produces no visual change.

   Reserved-for surfaces (DESIGN.md):
     - peripheral chrome dims (party chips → 0.3, header → 0.4)
     - scene art expands to full-bleed vignetted mode
     - suggested action chips suppressed (Story 1.5 NEXT-SPEC; forward-compat)
     - input row goes naked (de-emphasized)

   Transitions: 400ms ease-in on arm, 600ms ease-out on release. Asymmetric
   per PRD FR-2.2. Under `prefers-reduced-motion: reduce`, transitions are
   disabled (instant) but the class flip still toggles state.

   .visually-hidden is the standard a11y recipe (clip-path + 1×1px) so the
   #hero-beat-aria-live region is exposed to AT but invisible.
-------------------------------------------------------------------------- */
.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Peripheral chrome dim. Transitions sit on the targeted elements so the
   class flip animates value changes. Both directions use opacity; the
   asymmetric duration comes from a property-specific transition declared
   below the dim rules. */
html.use-redesign-ui .game-container > .sidebar,
html.use-redesign-ui .game-container > .tactical-board,
html.use-redesign-ui .sidebar-header,
html.use-redesign-ui .board-header {
  transition: opacity 600ms ease-out;
}
html.use-redesign-ui body.hero-beat-active .game-container > .sidebar,
html.use-redesign-ui body.hero-beat-active .game-container > .tactical-board {
  opacity: 0.4;
  transition: opacity 400ms ease-in;
}
html.use-redesign-ui body.hero-beat-active .sidebar-header,
html.use-redesign-ui body.hero-beat-active .board-header {
  opacity: 0.4;
  transition: opacity 400ms ease-in;
}

/* Party-presence chips (Story 3.2 — v1 sync_party-driven).
   Transition/hero-beat opacity rules below are the forward-compat selectors
   that shipped with Story 1.4. Shape/layout rules added here in Story 3.2. */
html.use-redesign-ui .party-presence-chips {
  display: flex;
  gap: 6px;
  align-items: center;
  transition: opacity 600ms ease-out;
}
html.use-redesign-ui body.hero-beat-active .party-presence-chips {
  opacity: 0.3;
  transition: opacity 400ms ease-in;
}
html.use-redesign-ui .party-presence-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 15px;
  color: var(--color-moon);
  background: transparent;
  border: 1px solid rgba(127, 198, 230, 0.4);
  user-select: none;
  pointer-events: none;
  opacity: 0.6;
}
html.use-redesign-ui .party-presence-chip--self {
  width: 36px;
  height: 36px;
  border-color: rgba(127, 198, 230, 0.8);
  opacity: 1;
}

/* Suggested action chips (Story 1.5 NEXT-SPEC — forward-compat). When chips
   ship, they're pre-suppressed under hero beats. `display: none` here is
   sufficient because the chips render and dismiss as a unit; we don't need
   a transition on appearance/disappearance. */
html.use-redesign-ui body.hero-beat-active .action-chips,
html.use-redesign-ui body.hero-beat-active #suggested-actions {
  display: none;
}

/* Scene art full-bleed under hero beat. The existing #scene-image-strip is
   a constrained rail/chip in normal mode; under hero beat the constraints
   drop and the image owns the viewport. Transitions are on the strip
   container; the inner #scene-image inherits via parent. */
html.use-redesign-ui #scene-image-strip {
  transition: max-height 600ms ease-out, transform 600ms ease-out;
}
html.use-redesign-ui body.hero-beat-active #scene-image-strip {
  max-height: 60vh;
  transform: scale(1);
  transition: max-height 400ms ease-in, transform 400ms ease-in;
  z-index: 50;
}

/* `prefers-reduced-motion: reduce` — kill all hero-beat transitions. The
   class flip still toggles state; only the animation is bypassed (NFR-6
   + AC #6). */
@media (prefers-reduced-motion: reduce) {
  html.use-redesign-ui .game-container > .sidebar,
  html.use-redesign-ui .game-container > .tactical-board,
  html.use-redesign-ui .sidebar-header,
  html.use-redesign-ui .board-header,
  html.use-redesign-ui .party-presence-chips,
  html.use-redesign-ui #scene-image-strip,
  html.use-redesign-ui body.hero-beat-active .game-container > .sidebar,
  html.use-redesign-ui body.hero-beat-active .game-container > .tactical-board,
  html.use-redesign-ui body.hero-beat-active .sidebar-header,
  html.use-redesign-ui body.hero-beat-active .board-header,
  html.use-redesign-ui body.hero-beat-active .party-presence-chips,
  html.use-redesign-ui body.hero-beat-active #scene-image-strip {
    transition: none !important;
  }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Calm, summon-on-demand play surface (Story 11.4 / §1.5 + FR-2).
   Default play is narration-dominant; the Party HUD (.sidebar) and Map HUD
   (.tactical-board) are off-canvas drawers summoned on demand via edge tabs
   (or auto-summoned in combat). DESKTOP redesign only (min-width:1101px — below
   that the existing single-panel mobile nav owns layout). Legacy cohort + the
   3-column VTT are untouched (no html.use-redesign-ui → none of this applies).
-------------------------------------------------------------------------- */
/* Edge-tab summon buttons: hidden by default (legacy + mobile). */
.summon-tab { display: none; }
.summon-scrim { display: none; }

@media (min-width: 1101px) {
  /* Narration becomes the single dominant column. */
  html.use-redesign-ui .game-container {
    grid-template-columns: minmax(0, 1fr);
    max-width: 940px;
    margin: 0 auto;
  }
  /* Party + Map HUDs → off-canvas drawers (collapsed by default, NOT removed). */
  html.use-redesign-ui .game-container > .sidebar,
  html.use-redesign-ui .game-container > .tactical-board {
    position: fixed;
    top: 0;
    bottom: 0;
    width: min(380px, 90vw);
    z-index: 60;
    overflow-y: auto;
    /* a11y (Story 11.4 review): visibility:hidden takes the collapsed drawer
       OUT of the tab order (transform alone leaves invisible content focusable).
       Delay the hide until after the slide-out so the animation still plays. */
    visibility: hidden;
    transition: transform 360ms ease-out, visibility 0s linear 360ms;
  }
  html.use-redesign-ui .game-container > .sidebar { left: 0; transform: translateX(-100%); }
  html.use-redesign-ui .game-container > .tactical-board { right: 0; transform: translateX(100%); }
  /* Summoned in (body class set by initRedesignSummon / combat auto-summon). */
  html.use-redesign-ui body.summon-party .game-container > .sidebar,
  html.use-redesign-ui body.summon-map .game-container > .tactical-board {
    transform: translateX(0);
    visibility: visible;
    transition: transform 360ms ease-out, visibility 0s; /* show immediately on open */
  }
  /* Combat-blind fix (Story 11.4 review): a summoned drawer stays full-opacity
     even under a hero beat (which otherwise dims the HUDs to 0.4) — combat
     auto-summons the map and must never be dimmed-blind. */
  html.use-redesign-ui body.hero-beat-active.summon-party .game-container > .sidebar,
  html.use-redesign-ui body.hero-beat-active.summon-map .game-container > .tactical-board {
    opacity: 1;
  }

  /* Edge tabs — unobtrusive, vertically centered on each edge. */
  html.use-redesign-ui .summon-tab {
    display: block;
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    z-index: 58;
    padding: 14px 8px;
    font-family: var(--font-serif, serif);
    font-size: 13px;
    letter-spacing: 0.08em;
    writing-mode: vertical-rl;
    color: var(--color-moon, #cfe);
    background: rgba(20, 28, 38, 0.7);
    border: 1px solid rgba(127, 198, 230, 0.35);
    cursor: pointer;
    transition: opacity 360ms ease-out, background 200ms;
  }
  html.use-redesign-ui .summon-tab:hover { background: rgba(40, 56, 76, 0.85); }
  html.use-redesign-ui .summon-tab--party { left: 0; border-left: none; border-radius: 0 8px 8px 0; }
  html.use-redesign-ui .summon-tab--map { right: 0; border-right: none; border-radius: 8px 0 0 8px; }
  /* The open side's tab hides while its drawer is in. */
  html.use-redesign-ui body.summon-party .summon-tab--party,
  html.use-redesign-ui body.summon-map .summon-tab--map { opacity: 0; pointer-events: none; }
  /* Hero beats already dim chrome — keep the tabs out of the way too. */
  html.use-redesign-ui body.hero-beat-active .summon-tab { opacity: 0; pointer-events: none; }

  /* Scrim behind an open drawer (click to dismiss). */
  html.use-redesign-ui body.summon-party .summon-scrim,
  html.use-redesign-ui body.summon-map .summon-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 59;
    background: rgba(0, 0, 0, 0.45);
  }
}

@media (prefers-reduced-motion: reduce) {
  html.use-redesign-ui .game-container > .sidebar,
  html.use-redesign-ui .game-container > .tactical-board,
  html.use-redesign-ui .summon-tab { transition: none !important; }
}

/* The summon tabs belong to the PLAY surface only. While any pre-play overlay
   (hero lobby / starter card-stack / onboarding) owns the viewport, suppress
   them — otherwise they'd add stray CTAs over the cold-open (Stories 2.1/2.2). */
html.use-redesign-ui:has(#hero-lobby:not(.hidden)) .summon-tab,
html.use-redesign-ui:has(#starter-card-stack:not(.hidden)) .summon-tab,
html.use-redesign-ui:has(#onboarding-flow:not(.hidden)) .summon-tab {
  display: none !important;
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Narration as a page, not a chat log (Story 12.1 / Epic 12).
   Closes the immersion tell flagged in Story 11.4 (GAME MASTER labels +
   timestamps + chat bubbles + the "Game Master AI" header). Presentation
   only — the render path (appendMessage) is unchanged; every affordance
   stays in the DOM and stays functional, it just recedes to hover/focus.
   Redesign cohort only; legacy chat-log untouched. The narration TEXT
   pipeline (Stories 11-1/11-2/11-3/11-6) is NOT touched.
-------------------------------------------------------------------------- */

/* AC#1/#3 — author label + timestamp leave the visual page but stay for AT
   (keeps the Story 1.3 per-replay aria-label disambiguation + role="log"
   reading order). Same clip-path recipe as .visually-hidden. */
html.use-redesign-ui .gm-msg .msg-author,
html.use-redesign-ui .gm-msg .msg-time,
html.use-redesign-ui .player-msg .msg-author,
html.use-redesign-ui .player-msg .msg-time {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* AC#1 — the GM block stops being a bordered/left-barred card and reads as
   continuous typeset prose. Compose with the redesign .msg-content serif
   (style.css:5211) + the 11.4 summon column. */
html.use-redesign-ui .message.gm-msg {
  background: transparent;
  border: none;
  border-left: none;
  max-width: 100%;
  padding: 4px 0;
  position: relative; /* anchor for the floated control cluster */
}

/* AC#2 — operator controls (replay/regen/copy/share) float into the top-right
   corner, hidden at rest, revealed on hover OR keyboard focus (focus-within →
   keyboard parity). A faint backdrop keeps them legible over prose. Capability
   is preserved; only resting visibility changes. */
html.use-redesign-ui .gm-msg .msg-header {
  position: absolute;
  top: 2px;
  right: 2px;
  z-index: 2;
  margin: 0;
  min-height: 0;
  gap: 2px;
  padding: 2px 5px;
  border-radius: 6px;
  background: transparent;
  transition: background 0.15s ease;
}
html.use-redesign-ui .gm-msg:hover .msg-header,
html.use-redesign-ui .gm-msg:focus-within .msg-header {
  background: rgba(10, 14, 20, 0.88);
}
html.use-redesign-ui .gm-msg .msg-header > button {
  opacity: 0;
  pointer-events: none;
}
html.use-redesign-ui .gm-msg:hover .msg-header > button,
html.use-redesign-ui .gm-msg:focus-within .msg-header > button {
  opacity: 0.6;
  pointer-events: auto;
}
html.use-redesign-ui .gm-msg .msg-header > button:hover,
html.use-redesign-ui .gm-msg .msg-header > button:focus-visible {
  opacity: 1;
}

/* AC#4 — the player's intent recedes to a quiet inline lead-in in the prose
   thread (mirrors the "→ Your choice:" register at .action-chips-resolved-
   label), NOT a right-aligned named+timestamped bubble. */
html.use-redesign-ui .message.player-msg {
  background: transparent;
  border: none;
  align-self: stretch;
  max-width: 100%;
  padding: 8px 0 2px;
}
html.use-redesign-ui .player-msg .msg-header {
  margin: 0;
  min-height: 0;
}
html.use-redesign-ui .player-msg .msg-content {
  font-family: 'Outfit', sans-serif;
  font-size: 0.86rem;
  color: var(--primary-gold);
  opacity: 0.72;
  font-variant: small-caps;
  letter-spacing: 0.04em;
}
html.use-redesign-ui .player-msg .msg-content::before {
  content: "→ ";
  opacity: 0.7;
}

/* AC#5 — the "🏰 Game Master AI" header tell recedes on the calm play screen;
   kept for AT + legacy heading semantics via the clip-path recipe. The room
   badge + scene-chip (location) + invite/settings/overview stay visible. */
html.use-redesign-ui .app-title-gm {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Story 5.5 reduced-motion contract — the only new transition above is the
   control-cluster backdrop fade; disable it under reduce (state still flips). */
@media (prefers-reduced-motion: reduce) {
  html.use-redesign-ui .gm-msg .msg-header { transition: none !important; }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Per-character sensory transition (Story 9.5 / LW-6.3 / LW-6.4)
   The active character's signature (server-derived in Story 9.4, rides
   sync_state.party[*].sensoryProfile) drives the character-name accent colour
   + typography quirk. applySensoryProfile() in app.js sets the three custom
   properties on :root; the 600ms transition animates the swap on character
   change. Colour is the redundant amplifier — the typography quirk (weight /
   italic) and the name text itself are the load-bearing non-colour signals
   (Story 5.6 colour-cue independence). Redesign cohort only (legacy
   grandfathered, per Stories 5.5 / 5.6).
-------------------------------------------------------------------------- */
html.use-redesign-ui #char-name {
  color: var(--character-accent, var(--color-frost));
  font-weight: var(--character-weight, 600);
  font-style: var(--character-italic, normal);
  transition: color 600ms ease-out, font-weight 600ms ease-out, font-style 600ms ease-out;
}

/* Name-mention glyph tint in the narration column — a single character of a
   mentioned party member's name, tinted with their accent. Subtle, never
   decorative noise (LW-6.4). */
html.use-redesign-ui .char-mention-accent {
  color: var(--char-mention-color, inherit);
  transition: color 600ms ease-out;
}

/* `prefers-reduced-motion: reduce` — the swap is instant; the accent/typography
   state still applies, only the 600ms animation is bypassed (Story 5.5
   contract, LW-6.3 "instant under prefers-reduced-motion"). */
@media (prefers-reduced-motion: reduce) {
  html.use-redesign-ui #char-name,
  html.use-redesign-ui .char-mention-accent {
    transition: none !important;
  }
}

/* --------------------------------------------------------------------------
   UX-REDESIGN: Suggested action chips (Story 1.5 / Spec-35)
   Source: docs/specs/35-suggested-action-chips.md.

   Cohort-gated under html.use-redesign-ui. Hero-beat suppression already
   lives in Story 1.4's CSS block above (search `#suggested-actions` for
   the `display: none` rule under `body.hero-beat-active`). Chips render
   in Cormorant Garamond serif (Story 1.2 token) with the `--color-moon`
   focus accent (also from Story 1.2). NFR-8 tap-target floor enforced.
-------------------------------------------------------------------------- */
html.use-redesign-ui .suggested-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  align-items: center;
  justify-content: flex-start;
}

html.use-redesign-ui .suggested-actions.hidden {
  display: none;
}

html.use-redesign-ui .action-chip {
  font-family: var(--font-prose, 'Cormorant Garamond', Georgia, serif);
  font-size: 0.95rem;
  color: var(--color-frost, hsl(220, 16%, 92%));
  background: hsla(220, 24%, 22%, 0.45);
  border: 1px solid hsl(220, 24%, 30%);
  border-radius: 4px;
  /* NFR-8 tap-target floor */
  min-height: 44px;
  min-width: 44px;
  padding: 0.5rem 0.875rem;
  cursor: pointer;
  text-transform: lowercase;
  letter-spacing: 0.01em;
  transition: background-color 0.18s ease, border-color 0.18s ease;
}

html.use-redesign-ui .action-chip:hover,
html.use-redesign-ui .action-chip:focus-visible {
  background: hsla(220, 24%, 28%, 0.7);
  border-color: var(--color-moon, hsl(196, 65%, 70%));
  outline: none;
}

html.use-redesign-ui .action-chip:focus-visible {
  box-shadow: 0 0 0 2px hsla(196, 65%, 70%, 0.45);
}

@media (prefers-reduced-motion: reduce) {
  html.use-redesign-ui .action-chip {
    transition: none;
  }
}

/* =====================================================================
   Story 5.4 — audio-cue captions (FR-6.5).
   Default: bracketed audio cues render as plain prose (the wrap spans
   are present in the DOM but unstyled). When the user opts in via the
   "your seat" toggle, `html.audio-cue-captions-on` is set and captions
   take on the voice-tag styling (italic serif, moon-tone) referenced
   in EXPERIENCE.md lines 157-158. Redesign cohort uses design tokens;
   legacy cohort falls back to hex values for the same visual treatment.
   ===================================================================== */
.audio-cue-caption {
  /* Unstyled by default — matches surrounding prose. */
}
html.audio-cue-captions-on .audio-cue-caption {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  color: var(--color-moon, #7fc6e6);
  opacity: 0.9;
}
html:not(.use-redesign-ui).audio-cue-captions-on .audio-cue-caption {
  font-family: Georgia, serif;
  font-style: italic;
  color: #7fc6e6;
  opacity: 0.9;
}

/* Story 9.2 — ambient diegetic cue caption line. A subtle, centered standalone
   line in the narration column (the inner .audio-cue-caption span carries the
   italic moon-tone styling above, only when captions are on). No animation —
   audio-only slice; nothing to gate under prefers-reduced-motion. */
.ambient-cue-line {
  text-align: center;
  margin: 0.4rem 0;
  font-size: 0.9em;
}
/* Captions-off contract: a cue line inserted while captions were on must hide
   the moment captions are turned off (the inner .audio-cue-caption only loses
   styling, not visibility — so gate the whole line). */
html:not(.audio-cue-captions-on) .ambient-cue-line {
  display: none;
}

/* "your seat" section heading takes the lowercased storyteller register
   in the redesign cohort. Legacy cohort keeps the default h3 chrome. */
html.use-redesign-ui .your-seat-section h3 {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.01em;
}

/* =============================================================
   Story 3.1 — Director Panel (S1: slide-out drawer, right edge)
   Scoped under .use-redesign-ui — legacy cohort never sees it.
   ============================================================= */

.use-redesign-ui .director-panel-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 970;
}

.use-redesign-ui .director-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  width: 420px;
  background: linear-gradient(145deg, #11151c, #161a22);
  border-left: 1px solid rgba(212, 175, 55, 0.28);
  box-shadow: -8px 0 32px rgba(0, 0, 0, 0.55);
  z-index: 980;
  display: flex;
  flex-direction: column;
  color: var(--text-primary, #ecf0f1);
  transform: translateX(100%);
  transition: transform 0.28s ease-out;
}

.use-redesign-ui .director-panel.director-panel-open {
  transform: translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .use-redesign-ui .director-panel {
    transition: none;
  }
}

@media (max-width: 768px) {
  .use-redesign-ui .director-panel {
    width: 360px;
  }
}

@media (max-width: 480px) {
  .use-redesign-ui .director-panel {
    width: 100vw;
  }
}

.use-redesign-ui .director-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

.use-redesign-ui .director-panel-title {
  margin: 0;
  font-size: 1.1rem;
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  color: var(--primary-gold, #d4af37);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: lowercase;
}

.use-redesign-ui .director-panel-close {
  background: none;
  border: none;
  color: var(--text-muted, #8b9cb0);
  cursor: pointer;
  font-size: 1rem;
  padding: 4px 8px;
  border-radius: 4px;
}
.use-redesign-ui .director-panel-close:hover {
  color: var(--text-primary, #ecf0f1);
  background: rgba(255, 255, 255, 0.06);
}

.use-redesign-ui .director-panel-tabs {
  display: flex;
  gap: 2px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(0, 0, 0, 0.18);
  flex-wrap: wrap;
}

.use-redesign-ui .director-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 6px 10px;
  color: var(--text-muted, #8b9cb0);
  cursor: pointer;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  border-radius: 2px 2px 0 0;
}
.use-redesign-ui .director-tab[aria-selected="true"] {
  color: var(--primary-gold, #f59e3a);
  border-bottom-color: var(--primary-gold, #f59e3a);
}
.use-redesign-ui .director-tab:hover:not([aria-selected="true"]) {
  color: var(--text-primary, #ecf0f1);
}

.use-redesign-ui .director-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 20px 18px;
}

.use-redesign-ui .director-tab-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Story 8.4 — read-only Reputation tab. Summary-led, narrative. Faded deeds
   carry their state via order + a text caption (Story 5.6); the opacity dim is a
   redundant amplifier, never the sole signal. */
.use-redesign-ui .reputation-heading {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-size: 1.1rem;
  color: var(--primary-gold, #d4af37);
  margin: 0;
}
.use-redesign-ui .reputation-summary {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-size: 1.02rem;
  line-height: 1.5;
  margin: 0;
}
.use-redesign-ui .reputation-deeds-heading {
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: lowercase;
  opacity: 0.75;
  margin: 6px 0 0;
}
.use-redesign-ui .reputation-empty {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  opacity: 0.85;
  margin: 0;
}
.use-redesign-ui .reputation-deeds {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.use-redesign-ui .reputation-deed {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-size: 0.95rem;
}
.use-redesign-ui .reputation-deed-kind {
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.7;
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 0 5px;
}
.use-redesign-ui .reputation-deed.is-faded {
  opacity: 0.55; /* redundant amplifier; the caption below is the real signal */
}
.use-redesign-ui .reputation-deed-faded-caption {
  font-style: italic;
  font-size: 0.8rem;
  opacity: 0.8;
}

.use-redesign-ui .director-invite-heading {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-size: 1.05rem;
  color: var(--primary-gold, #d4af37);
  margin: 0;
}

.use-redesign-ui .director-invite-url-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.use-redesign-ui .director-invite-url {
  flex: 1;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 4px;
  padding: 7px 10px;
  color: var(--text-primary, #ecf0f1);
  font-size: 0.8rem;
  font-family: monospace;
}

.use-redesign-ui .director-invite-copy {
  background: rgba(245, 158, 58, 0.14);
  border: 1px solid rgba(245, 158, 58, 0.4);
  border-radius: 4px;
  padding: 7px 12px;
  color: var(--primary-gold, #f59e3a);
  cursor: pointer;
  font-size: 0.8rem;
  white-space: nowrap;
}
.use-redesign-ui .director-invite-copy:hover {
  background: rgba(245, 158, 58, 0.22);
}

.use-redesign-ui .director-invite-cadence {
  font-size: 0.85rem;
  color: var(--text-secondary, #b0bec5);
  margin: 0;
  line-height: 1.5;
  font-style: italic;
}

.use-redesign-ui .director-invite-tip {
  font-size: 0.75rem;
  color: var(--text-muted, #8b9cb0);
  margin: 0;
  line-height: 1.4;
}

/* Story 3.4 — GM-private stall recovery panel in Director Panel DevOps tab */
#stall-recovery-panel {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem 0;
}

#stall-recovery-panel .devops-stall-heading {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  color: var(--color-moon, #c9dff0);
  margin: 0;
}

#stall-retry-btn,
#stall-rephrase-btn {
  align-self: flex-start;
  background: transparent;
  border: 1px solid var(--color-moon, #c9dff0);
  color: var(--color-moon, #c9dff0);
  border-radius: 4px;
  padding: 0.4rem 0.9rem;
  font-size: 0.85rem;
  cursor: pointer;
}

#stall-retry-btn:hover,
#stall-rephrase-btn:hover {
  background: rgba(201, 223, 240, 0.1);
}

/* Story 3.7 — Discord webhook configuration in Director Panel DevOps tab */
#devops-webhook-section {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.devops-section-label {
  font-size: 0.85rem;
  color: var(--text-secondary, #b0bec5);
  margin: 0;
  text-transform: lowercase;
  letter-spacing: 0.02em;
}

.devops-url-input {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  color: var(--text-primary, #ecf0f1);
  font-size: 0.8rem;
  font-family: monospace;
}

.devops-url-input::placeholder {
  color: var(--text-muted, #8b9cb0);
}

#devops-webhook-save {
  align-self: flex-start;
  background: transparent;
  border: 1px solid var(--color-moon, #c9dff0);
  color: var(--color-moon, #c9dff0);
  border-radius: 4px;
  padding: 0.4rem 0.9rem;
  font-size: 0.85rem;
  cursor: pointer;
}

#devops-webhook-save:hover {
  background: rgba(201, 223, 240, 0.1);
}

.devops-webhook-status {
  font-size: 0.8rem;
  color: var(--text-secondary, #b0bec5);
  margin: 0;
  min-height: 1rem;
}

/* Story 3.3 — Redesign-cohort loot modal cinematic frame (AC #1, #2, UX-DR12) */
html.use-redesign-ui #loot-modal .modal-card {
  background: var(--color-bg-primary, #0a1020);
  border: 1px solid rgba(127, 198, 230, 0.2);
}

html.use-redesign-ui #loot-modal #loot-modal-title {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  font-size: 1.4rem;
  color: var(--color-moon, #c9dff0);
}

html.use-redesign-ui #loot-modal .loot-row-name {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-style: italic;
  color: var(--color-moon, #c9dff0);
}

html.use-redesign-ui #loot-modal .btn-claim-loot {
  background: transparent;
  border: 1px solid var(--color-moon, #c9dff0);
  color: var(--color-moon, #c9dff0);
  border-radius: 4px;
  padding: 0.3rem 0.75rem;
  font-size: 0.85rem;
  cursor: pointer;
}

html.use-redesign-ui #loot-modal #btn-close-loot {
  background: transparent;
  border: none;
  color: var(--text-secondary, #b0bec5);
  font-size: 0.85rem;
  cursor: pointer;
}

/* Story 3.3 — Redesign-cohort level-up modal cinematic frame (AC #6) */
html.use-redesign-ui #levelup-modal .modal-card {
  background: var(--color-bg-primary, #0a1020);
  border: 1px solid rgba(127, 198, 230, 0.2);
}

html.use-redesign-ui #levelup-modal #levelup-modal-title {
  font-family: var(--font-serif, Cormorant, Georgia, serif);
  font-size: 1.6rem;
  color: var(--color-moon, #c9dff0);
}

html.use-redesign-ui #levelup-modal #btn-claim-levelup {
  background: transparent;
  border: 1px solid var(--color-moon, #c9dff0);
  color: var(--color-moon, #c9dff0);
  border-radius: 4px;
  padding: 0.4rem 1rem;
  font-size: 0.9rem;
  cursor: pointer;
}

/* Ember accent on the level-up "moment of choice" keyword (UX spine / Dev Notes). */
html.use-redesign-ui .levelup-choice-word {
  color: var(--color-ember, #f59e3a);
  font-style: normal;
}
