/* =======================================================================
   ENDLESS CHAT RPG — "Arcane Dark" design system
   A single coherent dark aesthetic (deep indigo surfaces, gold primary
   accent, violet for magic/story, teal for commerce, danger red for
   combat) applied throughout: setup, HUD, event cards, combat, inventory,
   shop, character sheet, transformations. Token scale first, components
   after.
   ======================================================================= */

:root {
  /* ---- surfaces (elevation ramp) ---- */
  --bg:#0a0913;
  --surface-0:#141227;   /* base card */
  --surface-1:#1c1836;   /* panel, one step up */
  --surface-2:#252042;   /* nested / nav / hovered */
  --surface-3:#2d284e;   /* highest elevation (modal content) */
  --card:var(--surface-0);   /* legacy alias — still referenced directly */
  --panel:var(--surface-1);  /* legacy alias — still referenced directly */

  --border:rgba(255,255,255,0.09);
  --border-strong:rgba(255,255,255,0.20);

  /* ---- ink ---- */
  --ink:#eef0fb;
  --muted:#a6a3c9;
  --muted-2:#726f96;

  /* ---- accents ---- */
  --accent:#f0b429;          /* gold — primary CTA, xp, currency */
  --accent-ink:#241a02;      /* text on gold background */
  --accent-soft:rgba(240,180,41,0.14);
  --violet:#8b7cf6;          /* story / magic / transforms */
  --violet-soft:rgba(139,124,246,0.16);
  --portal:#d946ef;          /* rift / convergence — distinct from violet */
  --portal-soft:rgba(217,70,239,0.16);
  --teal:#2dd4bf;            /* merchant / commerce */
  --teal-soft:rgba(45,212,191,0.16);
  --sky:#38bdf8;             /* training / calm / player-side */
  --sky-soft:rgba(56,189,248,0.16);

  --danger:#f8607a;
  --danger-fg:#fecaca;       /* legacy alias, still referenced */
  --danger-soft:rgba(248,96,122,0.15);
  --success:#34d399;
  --success-soft:rgba(52,211,153,0.15);
  --warning:#fbbf24;
  --warning-soft:rgba(251,191,36,0.15);
  --sel:#34d399;

  --hp-full:#34d399;
  --hp-mid:#fbbf24;
  --hp-low:#f8607a;

  /* ---- rarity ramp (common -> mythic) ---- */
  --r-common:#9ca3af;
  --r-uncommon:#4ade80;
  --r-rare:#38bdf8;
  --r-epic:#a78bfa;
  --r-legendary:#f0b429;
  --r-mythic:#fb7185;

  /* ---- geometry scale ---- */
  --radius-sm:6px;
  --radius-md:10px;
  --radius-lg:16px;
  --radius-full:999px;
  --radius:var(--radius-md); /* legacy alias */
  --gap:10px;                /* legacy alias */

  /* ---- type scale ---- */
  --text-xs:0.72rem;
  --text-sm:0.83rem;
  --text-base:0.95rem;
  --text-md:1.05rem;
  --text-lg:1.25rem;
  --text-xl:1.55rem;
  --text-2xl:1.85rem;

  /* ---- elevation shadows ---- */
  --shadow-sm:0 2px 8px rgba(0,0,0,0.35);
  --shadow-md:0 8px 24px rgba(0,0,0,0.45);
  --shadow-lg:0 18px 44px rgba(0,0,0,0.55);
}

* { box-sizing:border-box; }

body {
  margin:0;
  min-height:100vh;
  font-family:system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;
  color:var(--ink);
  background-color:var(--bg);
  background-image:
    radial-gradient(1100px 760px at 10% -12%, rgba(139,124,246,0.16), transparent 60%),
    radial-gradient(900px 680px at 106% 0%, rgba(240,180,41,0.10), transparent 55%);
  background-attachment:fixed;
}

.container {
  max-width:900px;
  margin:10px auto;
  padding:12px;
}

.card {
  background:linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02)), var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  padding:14px;
  box-shadow:var(--shadow-lg);
}

h2, h3, h4 { margin:0 0 8px; font-weight:800; letter-spacing:-0.01em; color:var(--ink); }
h2 { font-size:var(--text-2xl); }
h3 { font-size:var(--text-lg); }
h4 { font-size:var(--text-md); }

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

.btn {
  background:var(--accent);
  color:var(--accent-ink);
  padding:8px 12px;
  border-radius:var(--radius-sm);
  border:none;
  font-weight:700;
  font-size:var(--text-base);
  cursor:pointer;
  transition:transform .05s ease, filter .15s ease, opacity .15s ease;
}
.btn:hover { filter:brightness(1.08); }
.btn:active { transform:translateY(1px); }
.btn:disabled { opacity:0.4; cursor:not-allowed; filter:none; transform:none; }
.btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

.small {
  font-size:var(--text-sm);
  color:var(--muted);
}

/* secondary / outline / danger button variants */
.btn-ghost {
  background:transparent;
  color:var(--accent);
  border:1px solid rgba(240,180,41,0.4);
}
.btn-ghost:hover { background:var(--accent-soft); }

.btn-secondary {
  background:var(--surface-2);
  color:var(--ink);
  border:1px solid var(--border-strong);
}
.btn-secondary:hover { border-color:var(--violet); }

.btn-outline-danger {
  background:transparent;
  color:var(--danger);
  border:1px solid rgba(248,96,122,0.5);
}
.btn-outline-danger:hover { background:var(--danger-soft); }

.btn-danger {
  background:var(--danger);
  color:#3b0a0a;
}

.controls {
  display:flex;
  gap:8px;
  flex-wrap:wrap;
  margin-top:8px;
}

hr {
  border:0;
  border-top:1px solid var(--border);
  margin:14px 0;
}

/* ---- log ---- */
.log-line { margin-bottom:2px; }
.log-player { color:#7dd3fc; }
.log-enemy { color:#fecaca; }
.log-system { color:#e5e7eb; }

.log-panel {
  margin-top:12px;
  background:var(--panel);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  overflow:hidden;
}
.log-panel .log-panel-head {
  padding:6px 10px;
  font-size:var(--text-xs);
  color:var(--muted);
  background:rgba(255,255,255,0.03);
  border-bottom:1px solid var(--border);
  letter-spacing:0.04em;
  text-transform:uppercase;
}
.log-panel .log-panel-body {
  max-height:220px;
  overflow-y:auto;
  padding:8px 10px;
  font-size:0.9rem;
  white-space:pre-wrap;
}

/* ---- form controls ---- */
input, select, textarea {
  background:var(--surface-0);
  color:var(--ink);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-sm);
  padding:8px;
  font-family:inherit;
  font-size:0.95rem;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline:2px solid var(--accent);
  outline-offset:1px;
}

textarea { width:100%; box-sizing:border-box; resize:vertical; }
optgroup { color:var(--accent); font-style:normal; }

/* =======================================================================
   Setup / theme picker (largely structural carry-over, re-skinned)
   ======================================================================= */
.setup-fields { display:flex; gap:8px; flex-wrap:wrap; }
.setup-fields input { flex:1 1 160px; }

.setup-heading { margin:14px 0 2px; font-size:1.05rem; color:var(--ink); }

.setup-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }

.theme-grid {
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(240px, 1fr));
  gap:10px;
  margin-top:10px;
}

.theme-card {
  background:linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02)), var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  padding:12px;
  transition:border-color .15s, box-shadow .15s, transform .05s;
}
.theme-card:hover { border-color:rgba(240,180,41,0.35); }
.theme-card.selected {
  border-color:var(--sel);
  box-shadow:0 0 0 1px var(--sel), var(--shadow-md);
}

.theme-card-head {
  display:flex;
  align-items:center;
  gap:6px;
  flex-wrap:wrap;
  margin-bottom:10px;
}
.theme-card-head strong { font-size:1.02rem; }

.class-chips { display:flex; gap:8px; flex-wrap:wrap; }

.chip {
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  text-align:left;
  gap:2px;
  background:rgba(255,255,255,0.04);
  color:var(--ink);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-md);
  padding:8px 12px;
  cursor:pointer;
  font-family:inherit;
  transition:border-color .12s, background .12s, transform .05s;
}
.chip:hover { border-color:var(--accent); }
.chip:active { transform:translateY(1px); }
.chip:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.chip.selected {
  border-color:var(--sel);
  background:rgba(52,211,153,0.14);
  box-shadow:inset 0 0 0 1px var(--sel);
}
.chip-name { font-weight:700; }
.chip-stats { font-size:0.76rem; color:var(--muted); }

.selection-bar {
  align-items:center;
  justify-content:space-between;
  gap:10px;
  flex-wrap:wrap;
  margin-top:14px;
  padding:10px 12px;
  background:rgba(52,211,153,0.08);
  border:1px solid rgba(52,211,153,0.35);
  border-radius:var(--radius-md);
}
.selection-bar #selectionInfo { color:var(--ink); }

/* ---- tags / badges ---- */
.tag {
  font-size:0.7rem;
  padding:2px 7px;
  border-radius:var(--radius-full);
  background:var(--accent-soft);
  color:var(--accent);
  vertical-align:middle;
  white-space:nowrap;
}
.tag-official  { background:var(--sky-soft); color:var(--sky); }
.tag-community { background:var(--violet-soft); color:#c4b5fd; }
.tag-local     { background:var(--warning-soft); color:#fcd34d; }
.tag-transform { background:var(--violet-soft); color:#a5b4fc; }
.tag-author    { background:rgba(255,255,255,0.08); color:var(--muted); }
.tag-inrun     { background:var(--portal-soft); color:var(--portal); }
.tag-diff-easy   { background:var(--success-soft); color:#6ee7b7; }
.tag-diff-normal { background:rgba(255,255,255,0.08); color:var(--muted); }
.tag-diff-hard   { background:rgba(251,146,60,0.18);  color:#fdba74; }
.tag-diff-brutal { background:var(--danger-soft); color:#fca5a5; }
.tag-trait  { background:var(--danger-soft); color:var(--danger-fg); }
.tag-active { background:var(--violet-soft); color:#a5b4fc; }
.tag-buff     { background:var(--sky-soft); color:var(--sky); }
.tag-cooldown { background:rgba(255,255,255,0.08); color:var(--muted); }
.tag-paths    { background:var(--danger-soft); color:var(--danger-fg); }
.tag-quests   { background:var(--teal-soft); color:var(--teal); }

/* rarity ramp — text color utility (item names, tags) */
.rarity-common    { color:var(--r-common); }
.rarity-uncommon  { color:var(--r-uncommon); }
.rarity-rare      { color:var(--r-rare); }
.rarity-epic      { color:var(--r-epic); }
.rarity-legendary { color:var(--r-legendary); }
.rarity-mythic    { color:var(--r-mythic); }

/* rarity ramp — card border/glow treatment, shared by bag/shop item cards
   and filled equipment slots */
.item-card[data-rarity="common"],
.paperdoll-slot.filled[data-rarity="common"] { border-color:rgba(156,163,175,0.35); }
.item-card[data-rarity="uncommon"],
.paperdoll-slot.filled[data-rarity="uncommon"] { border-color:rgba(74,222,128,0.5); box-shadow:0 0 0 1px rgba(74,222,128,0.12); }
.item-card[data-rarity="rare"],
.paperdoll-slot.filled[data-rarity="rare"] { border-color:rgba(56,189,248,0.55); box-shadow:0 0 0 1px rgba(56,189,248,0.14); }
.item-card[data-rarity="epic"],
.paperdoll-slot.filled[data-rarity="epic"] { border-color:rgba(167,139,250,0.6); box-shadow:0 0 14px -2px rgba(167,139,250,0.4); }
.item-card[data-rarity="legendary"],
.paperdoll-slot.filled[data-rarity="legendary"] { border-color:rgba(240,180,41,0.65); box-shadow:0 0 16px -2px rgba(240,180,41,0.5); }
.item-card[data-rarity="mythic"],
.paperdoll-slot.filled[data-rarity="mythic"] { border-color:rgba(251,113,133,0.7); box-shadow:0 0 20px -2px rgba(251,113,133,0.6); }

/* Permanent consumables (permanent_max_hp/atk/def/spd/random_stat) — rare,
   one-time stat-ups; a distinct gold treatment so they read as exciting. */
.item-card.item-permanent {
  border-color:rgba(240,180,41,0.75);
  box-shadow:0 0 18px -2px rgba(240,180,41,0.55), inset 0 0 0 1px rgba(240,180,41,0.25);
  background:linear-gradient(180deg, rgba(240,180,41,0.10), transparent 60%);
}
.tag-permanent { background:rgba(240,180,41,0.22); color:#f0b429; font-weight:800; letter-spacing:.03em; }

/* =======================================================================
   Loading indicator — thin sweeping bar fixed to the true viewport top
   ======================================================================= */
.loading-bar {
  position:fixed;
  top:0; left:0;
  height:3px; width:100%;
  background:linear-gradient(90deg, transparent, var(--accent), var(--violet), transparent);
  background-size:200% 100%;
  opacity:0;
  transform:scaleX(0.2);
  transform-origin:left;
  transition:opacity .15s ease;
  z-index:300;
  pointer-events:none;
}
.loading-bar.is-active {
  opacity:1;
  animation:loading-sweep 1.1s ease-in-out infinite;
}
@keyframes loading-sweep {
  0%   { transform:scaleX(0.15); background-position:0% 0; }
  50%  { transform:scaleX(0.85); background-position:100% 0; }
  100% { transform:scaleX(0.15); background-position:200% 0; }
}

/* =======================================================================
   HUD — Run Command Center (sticky header)
   ======================================================================= */
.hud {
  position:sticky;
  top:0;
  z-index:20;
  display:flex;
  flex-direction:column;
  gap:10px;
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  padding:12px 14px;
  margin-bottom:12px;
  box-shadow:var(--shadow-md);
}
.hud-top { display:flex; justify-content:space-between; align-items:flex-start; gap:10px; flex-wrap:wrap; }
.hud-namerow { display:flex; align-items:baseline; gap:6px; flex-wrap:wrap; }
.hud-name { font-size:var(--text-lg); font-weight:800; }
.hud-connector { color:var(--muted); font-style:italic; }
.hud-class { font-size:var(--text-md); color:var(--muted); }
.hud-badges { display:flex; gap:6px; margin-top:4px; flex-wrap:wrap; align-items:center; }
.hud-badges .tag-active { padding:4px 10px; font-size:0.75rem; }
.hud-meta { display:flex; flex-direction:column; align-items:flex-end; gap:6px; }
.hud-gold { font-weight:700; color:var(--accent); font-size:var(--text-md); }
.hud-bars { display:flex; flex-direction:column; gap:6px; }
.hud-actions { display:flex; gap:6px; flex-wrap:wrap; }
.hud-actions .btn { flex:1 1 auto; font-size:var(--text-sm); padding:7px 10px; white-space:nowrap; }

/* ---- progress bars: HP / XP / condition — shared track look ---- */
.hp-bar, .xp-bar, .condition-bar {
  position:relative;
  min-width:0;
  height:14px;
  background:rgba(255,255,255,0.06);
  border:1px solid var(--border);
  border-radius:var(--radius-full);
  overflow:hidden;
}
.condition-bar { height:6px; margin-top:4px; }
.hp-bar-fill, .xp-bar-fill, .condition-bar-fill {
  display:block;
  height:100%;
  transition:width .25s ease, background .25s ease;
}
.hp-bar-fill { background:var(--hp-full); }
.hp-bar-fill.mid { background:var(--hp-mid); }
.hp-bar-fill.low { background:var(--hp-low); }
.xp-bar-fill { background:linear-gradient(90deg, var(--accent), #ffd166); }
.convergence-bar-fill { background:linear-gradient(90deg, var(--portal), #f0abfc); }
.condition-bar-fill { background:var(--violet); }
.hp-bar-label, .xp-bar-label {
  position:absolute;
  inset:0;
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:0.72rem;
  color:#0b1220;
  font-weight:700;
  text-shadow:0 0 2px rgba(255,255,255,0.35);
}

.energy-pips { display:inline-flex; gap:3px; align-items:center; }
.energy-pip { width:8px; height:8px; border-radius:50%; background:rgba(255,255,255,0.16); }
.energy-pip.on { background:var(--accent); box-shadow:0 0 6px var(--accent); }

/* =======================================================================
   Event card — the "what's happening" surface. Accent + icon vary by
   ev.type via data-type; story/merchant choices render inside the card.
   ======================================================================= */
.event-card {
  position:relative;
  background:linear-gradient(180deg, var(--surface-1), var(--surface-0));
  border:1px solid var(--border);
  border-left:4px solid var(--evt, var(--muted-2));
  border-radius:var(--radius-lg);
  padding:14px 16px;
  margin-top:0;
  box-shadow:var(--shadow-md);
}
.event-card[data-type="danger"]  { --evt:var(--danger); }
.event-card[data-type="gold"]    { --evt:var(--accent); }
.event-card[data-type="violet"]  { --evt:var(--violet); }
.event-card[data-type="portal"]  { --evt:var(--portal); }
.event-card[data-type="shop"]    { --evt:var(--teal); }
.event-card[data-type="calm"]    { --evt:var(--sky); }
.event-card[data-type="neutral"] { --evt:var(--muted-2); }

.event-card-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.event-card-icon { font-size:1.35rem; line-height:1; filter:drop-shadow(0 0 6px var(--evt)); }
.event-card-kicker {
  font-size:var(--text-xs);
  text-transform:uppercase;
  letter-spacing:.08em;
  font-weight:800;
  color:var(--evt);
}
.event-card-body {
  white-space:pre-wrap;
  max-height:38vh;
  overflow-y:auto;
  line-height:1.5;
}
.event-card-extra { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
.event-card-extra:empty { display:none; margin:0; }

/* World-entry notice (8E) — client-side chrome shown once after a successful
   Step Through; layered on top of the normal portal accent. */
.event-card.world-unlocked-card {
  border-left-color:var(--portal);
  box-shadow:0 0 20px -4px var(--portal), var(--shadow-md);
}
.world-unlocked-stats {
  width:100%;
  color:var(--portal);
  font-weight:700;
}

/* ---- theme-builder preview box (was .event; renamed to avoid collision
   with the new event card system above; #previewBody is referenced only
   by ID from themes.js, so retargeting its class here is safe) ---- */
.builder-preview {
  white-space:pre-wrap;
  padding:12px;
  border-radius:var(--radius-md);
  background:rgba(15,23,42,0.4);
  margin-top:10px;
  max-height:300px;
  overflow-y:auto;
}

/* ---- design-chat notes (validation drop reasons, one-shot + chat panes) ---- */
.draft-notes {
  display:flex;
  flex-direction:column;
  gap:4px;
  margin-top:8px;
}
.draft-notes:empty { display:none; margin:0; }
.draft-note-line {
  color:#fbbf24;
  font-size:var(--text-sm);
  line-height:1.4;
}

/* ---- admin design-chat (Theme Builder "Design Chat" tab) ---- */
.chat-log {
  display:flex;
  flex-direction:column;
  gap:8px;
  max-height:42vh;
  overflow-y:auto;
  padding:10px;
  margin-top:6px;
  background:rgba(15,23,42,0.4);
  border-radius:var(--radius-md);
  border:1px solid var(--border);
}
.chat-log:empty::before {
  content:"No messages yet — say hello.";
  color:var(--muted);
  font-size:var(--text-sm);
}
.chat-msg {
  max-width:88%;
  border-radius:var(--radius-md);
  padding:8px 10px;
  font-size:var(--text-sm);
  line-height:1.45;
  white-space:pre-wrap;
  overflow-wrap:anywhere;
}
.chat-msg-role {
  font-size:var(--text-xs);
  text-transform:uppercase;
  letter-spacing:.05em;
  font-weight:800;
  opacity:0.7;
  margin-bottom:2px;
}
.chat-msg-user {
  align-self:flex-end;
  background:var(--accent-soft);
  color:var(--ink);
  border:1px solid rgba(240,180,41,0.35);
}
.chat-msg-assistant {
  align-self:flex-start;
  background:var(--surface-0);
  border:1px solid var(--border);
}
.chat-json {
  white-space:pre;
  overflow-x:auto;
  background:rgba(0,0,0,0.35);
  border-radius:var(--radius-sm);
  padding:8px;
  margin-top:6px;
  font-size:0.78em;
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
}
@media (max-width:479px) {
  .chat-msg { max-width:96%; }
  .chat-log { max-height:34vh; }
}

/* ---- typing indicator (shown while a design-chat turn / one-shot job is
   running; polled, not blocking — see themes.js) ---- */
.chat-typing-line {
  display:flex;
  align-items:center;
  gap:6px;
  font-style:italic;
  color:var(--muted);
}
.chat-typing {
  display:inline-flex;
  align-items:center;
  gap:3px;
  height:14px;
}
.chat-typing span {
  width:5px;
  height:5px;
  border-radius:50%;
  background:currentColor;
  opacity:0.35;
  animation:chat-typing-bounce 1.1s infinite ease-in-out;
}
.chat-typing span:nth-child(2) { animation-delay:0.15s; }
.chat-typing span:nth-child(3) { animation-delay:0.3s; }
@keyframes chat-typing-bounce {
  0%, 60%, 100% { opacity:0.35; transform:translateY(0); }
  30% { opacity:1; transform:translateY(-3px); }
}

/* =======================================================================
   Shop storefront (merchant events) — nested inside the event card
   ======================================================================= */
.shop-storefront {
  display:none;
  flex-direction:column;
  gap:10px;
  margin-top:12px;
  padding-top:12px;
  border-top:1px dashed var(--border);
}
.shop-gold-row { font-weight:700; color:var(--accent); }
.shop-grid { display:grid; grid-template-columns:1fr; gap:10px; }
@media (min-width:480px) { .shop-grid { grid-template-columns:repeat(2, 1fr); } }
.shop-leave-btn { align-self:flex-start; }

/* =======================================================================
   Item cards — shared by the shop grid, bag grid, and treasure chip
   ======================================================================= */
.item-card {
  min-width:0;
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  padding:10px 12px;
  display:flex;
  flex-direction:column;
  gap:6px;
  box-shadow:var(--shadow-sm);
}
.item-card-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.item-card-name { font-size:var(--text-base); overflow-wrap:anywhere; }
.item-card-meta { color:var(--muted); }
.item-card-desc { color:var(--muted-2); font-style:italic; overflow-wrap:anywhere; }
.item-card-price { font-weight:700; color:var(--accent); }
.item-card-actions { display:flex; gap:6px; flex-wrap:wrap; margin-top:auto; padding-top:4px; }

/* rarity ramp — the small corner badge on item cards (distinct from the
   border/glow treatment above, same six-step ramp) */
.tag-rarity { text-transform:capitalize; }
.tag-rarity-common    { background:rgba(156,163,175,0.16); color:var(--r-common); }
.tag-rarity-uncommon  { background:rgba(74,222,128,0.16);  color:var(--r-uncommon); }
.tag-rarity-rare      { background:rgba(56,189,248,0.16);  color:var(--r-rare); }
.tag-rarity-epic      { background:rgba(167,139,250,0.18); color:var(--r-epic); }
.tag-rarity-legendary { background:rgba(240,180,41,0.18);  color:var(--r-legendary); }
.tag-rarity-mythic    { background:rgba(251,113,133,0.18); color:var(--r-mythic); }

.treasure-chip { max-width:320px; }

/* =======================================================================
   Two-sided combat panel — the flagship "not a reskin" surface
   ======================================================================= */
.combat-panel {
  display:none;
  margin-top:12px;
  gap:12px;
  grid-template-columns:1fr;
}
.combat-panel.is-visible { display:grid; }
@media (min-width:560px) {
  .combat-panel { grid-template-columns:1fr auto 1fr; align-items:center; }
}
.combatant-card {
  min-width:0;
  background:linear-gradient(180deg, var(--surface-1), var(--surface-0));
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  padding:12px 14px;
  box-shadow:var(--shadow-sm);
}
.combatant-card--player { border-top:3px solid var(--sky); }
.combatant-card--enemy  { border-top:3px solid var(--danger); }
.combatant-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
.combatant-role { font-size:var(--text-xs); letter-spacing:.08em; color:var(--muted); font-weight:800; }
.combatant-head strong { font-size:var(--text-md); overflow-wrap:anywhere; }
.combatant-pills { display:flex; gap:4px; flex-wrap:wrap; margin-top:8px; }
.enemy-traits { display:flex; gap:4px; flex-wrap:wrap; margin-top:8px; }
.tag-archetype { background:var(--sky-soft); color:var(--sky); }
.enemy-intent {
  margin-top:8px;
  padding:5px 9px;
  border-radius:var(--radius-md);
  font-size:var(--text-xs);
  color:var(--muted);
  background:rgba(255,255,255,0.04);
  overflow-wrap:anywhere;
}
.enemy-intent-warning {
  color:var(--danger-fg);
  background:var(--danger-soft);
  font-weight:700;
  box-shadow:inset 0 0 0 1px rgba(248,96,122,0.4);
}
.vs-divider { display:flex; align-items:center; justify-content:center; padding:2px 0; }
.vs-divider span {
  font-weight:900;
  font-size:var(--text-sm);
  color:var(--muted);
  letter-spacing:.08em;
  background:var(--surface-2);
  border:1px solid var(--border-strong);
  border-radius:var(--radius-full);
  padding:4px 12px;
}

/* =======================================================================
   BATTLE STAGE — presentation-only visual strip above the combat cards.
   Sprites are theme-supplied glyph strings, always set via textContent in
   game.js (never HTML). Stage tint (.stage-*) and fx animation classes
   (.fx-*) are only ever applied from a hardcoded whitelist in game.js —
   any unrecognized theme value falls back to a safe default, so nothing
   theme-controlled reaches raw CSS/HTML here.

   Layer stack (paint order, back to front): ambience (::before/::after on
   .battle-stage, z:0) -> figures + their ground shadows (.stage-fig, z:1)
   -> spawned particle layers (.fxp, z:2) -> the edge vignette
   (.stage-vignette, z:3). Every animation in this section is
   transform/opacity/filter only (GPU-friendly) and is killed outright by
   the prefers-reduced-motion block at the end.
   ======================================================================= */
.battle-stage {
  /* spans both grid columns of .combat-panel so it sits as one strip above
     the player/enemy cards instead of getting auto-placed into a column */
  grid-column:1 / -1;
  position:relative;
  display:flex;
  align-items:center;
  justify-content:space-between;
  min-height:140px;
  max-height:170px;
  padding:10px 24px;
  margin-bottom:4px;
  border-radius:var(--radius-lg);
  border:1px solid var(--border);
  background:linear-gradient(180deg, var(--surface-1), var(--surface-0));
  /* the "floor" — a soft contact-shadow band the figures visually stand on */
  box-shadow:inset 0 -26px 30px -24px rgba(0,0,0,0.55), inset 0 -1px 0 rgba(255,255,255,0.07);
  overflow:hidden;
  transition:background .25s ease, filter .3s ease;
}
.battle-stage.stage-dimmed { filter:brightness(0.45) saturate(0.6); }

.battle-stage::before, .battle-stage::after {
  content:"";
  position:absolute;
  inset:0;
  z-index:0;
  pointer-events:none;
}

/* ---- per-stage-style living background (≤2 layered pseudo-elements,
   slow loops, kept subtle so they never compete with the fx layer).
   Phase 10D adds a dark, low-contrast silhouette backdrop into each of
   these same pseudo-elements (extra comma-separated gradient layers —
   first-listed paints on top, so the original sparkle/shimmer/ray/lightning
   effect always stays in front of the new silhouette shapes) plus a slow
   `transform:translateX` parallax drift on whichever pseudo-element doesn't
   already animate `transform` for something else (ocean/void's ::after
   already drive `transform` for the caustic/nebula pulse — two animations
   on one element both touching the same property don't compose, so the
   backdrop + drift live on ::before there instead). Everything here is
   killed by the generic
   `.battle-stage::before, .battle-stage::after { animation:none }` rule
   in the reduced-motion block below — no per-style opt-out needed. ---- */
.stage-default::before {
  /* rolling hills silhouette */
  background:
    radial-gradient(220px 70px at 15% 118%, rgba(10,14,24,0.5), transparent 72%) no-repeat,
    radial-gradient(300px 90px at 52% 128%, rgba(10,14,24,0.42), transparent 74%) no-repeat,
    radial-gradient(240px 75px at 88% 120%, rgba(10,14,24,0.48), transparent 72%) no-repeat;
  animation:defaultBackdropDrift 48s ease-in-out infinite;
}
.stage-default::after {
  background:linear-gradient(180deg, transparent 55%, rgba(255,255,255,0.035) 100%);
}
@keyframes defaultBackdropDrift {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-2.5%); }
}

.stage-energy { background:linear-gradient(180deg, rgba(240,180,41,0.18), var(--surface-0)); }
.stage-energy::before {
  background-image:radial-gradient(circle, rgba(240,180,41,0.55) 1px, transparent 1.6px);
  background-size:26px 26px;
  opacity:.5;
  animation:ambienceDriftUp 7s linear infinite;
}
.stage-energy::after {
  /* small gold sparkle (front) + distant crags + a rising ember glow (back) */
  background:
    radial-gradient(circle, rgba(255,214,120,0.4) 1px, transparent 1.6px) 0 0/17px 17px repeat,
    radial-gradient(180px 90px at 18% 122%, rgba(18,10,6,0.55), transparent 72%) no-repeat,
    radial-gradient(220px 110px at 82% 128%, rgba(18,10,6,0.5), transparent 74%) no-repeat,
    radial-gradient(160px 160px at 50% 150%, rgba(255,140,40,0.28), transparent 68%) no-repeat;
  opacity:.35;
  animation:ambienceDriftUp 4.5s linear infinite reverse, emberBackdropDrift 52s ease-in-out infinite;
}
@keyframes emberBackdropDrift {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-2.5%); }
}

.stage-ocean { background:linear-gradient(180deg, rgba(45,212,191,0.18), var(--surface-0)); }
.stage-ocean::before {
  /* wave shimmer (front) + layered wave bands + a horizon line (back) */
  background:
    repeating-linear-gradient(100deg, rgba(45,212,191,0.12) 0 12px, transparent 12px 26px) 0 0/160px 100% repeat,
    linear-gradient(180deg, transparent 60%, rgba(6,20,26,0.55) 78%, rgba(6,20,26,0.32) 100%) no-repeat,
    radial-gradient(260px 60px at 30% 108%, rgba(6,20,26,0.5), transparent 75%) no-repeat,
    radial-gradient(300px 70px at 78% 112%, rgba(6,20,26,0.45), transparent 75%) no-repeat;
  animation:ambienceShimmer 5s linear infinite, oceanBackdropDrift 46s ease-in-out infinite;
}
.stage-ocean::after {
  background:radial-gradient(ellipse at 30% 35%, rgba(45,212,191,0.28), transparent 60%);
  animation:ambienceCaustic 6s ease-in-out infinite;
}
@keyframes oceanBackdropDrift {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-3%); }
}

.stage-dojo { background:linear-gradient(180deg, rgba(248,96,122,0.12), var(--surface-0)); }
.stage-dojo::before {
  /* paper glow (front) + vertical panel dividers + a floor line (back) */
  background:
    linear-gradient(180deg, rgba(255,180,120,0.10), transparent 65%) no-repeat,
    repeating-linear-gradient(90deg, rgba(40,20,18,0.16) 0 2px, transparent 2px 64px) no-repeat,
    linear-gradient(180deg, transparent 82%, rgba(30,14,12,0.4) 100%) no-repeat;
  animation:dojoBackdropDrift 58s ease-in-out infinite;
}
.stage-dojo::after {
  background:linear-gradient(100deg, transparent 42%, rgba(255,214,150,0.14) 48%, transparent 54%);
  animation:ambienceRaySweep 8s ease-in-out infinite;
}
@keyframes dojoBackdropDrift {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-2%); }
}

.stage-void { background:linear-gradient(180deg, rgba(217,70,239,0.20), var(--surface-0)); }
.stage-void::before {
  /* starfield (front) + two faint nebula swirl blobs (back) */
  background:
    radial-gradient(circle, rgba(238,240,251,0.55) 1px, transparent 1.6px) 0 0/34px 34px repeat,
    radial-gradient(240px 140px at 25% 30%, rgba(120,40,160,0.22), transparent 70%) no-repeat,
    radial-gradient(200px 120px at 75% 65%, rgba(90,20,140,0.18), transparent 72%) no-repeat;
  opacity:.4;
  animation:ambienceDriftUp 11s linear infinite, voidBackdropSwirl 55s ease-in-out infinite;
}
.stage-void::after {
  background:radial-gradient(circle at 68% 45%, rgba(217,70,239,0.32), transparent 65%);
  animation:ambienceNebulaPulse 6.5s ease-in-out infinite;
}
@keyframes voidBackdropSwirl {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-3%); }
}

.stage-storm { background:linear-gradient(180deg, rgba(56,189,248,0.18), var(--surface-0)); }
.stage-storm::before {
  /* dark overlay + distant cloud banks */
  background:
    linear-gradient(180deg, rgba(12,18,38,0.4), transparent 70%) no-repeat,
    radial-gradient(320px 70px at 20% 18%, rgba(8,10,16,0.5), transparent 75%) no-repeat,
    radial-gradient(360px 80px at 65% 10%, rgba(8,10,16,0.45), transparent 75%) no-repeat,
    radial-gradient(280px 65px at 92% 22%, rgba(8,10,16,0.5), transparent 75%) no-repeat;
  animation:stormBackdropDrift 34s ease-in-out infinite;
}
.stage-storm::after {
  background:rgba(226,240,255,0.7);
  opacity:0;
  animation:ambienceLightning 6s linear infinite;
}
@keyframes stormBackdropDrift {
  0%, 100% { transform:translateX(0); }
  50% { transform:translateX(-3.5%); }
}

@keyframes ambienceDriftUp {
  0% { background-position:0 0; }
  100% { background-position:0 -260px; }
}
@keyframes ambienceShimmer {
  0% { background-position:0 0; }
  100% { background-position:160px 0; }
}
@keyframes ambienceCaustic {
  0%, 100% { transform:translateX(0); opacity:.5; }
  50% { transform:translateX(24%); opacity:.85; }
}
@keyframes ambienceRaySweep {
  0%, 100% { transform:translateX(-30%); opacity:.35; }
  50% { transform:translateX(30%); opacity:.75; }
}
@keyframes ambienceNebulaPulse {
  0%, 100% { opacity:.35; transform:scale(1); }
  50% { opacity:.65; transform:scale(1.12); }
}
@keyframes ambienceLightning {
  0%, 92%, 100% { opacity:0; }
  93% { opacity:.9; }
  94% { opacity:.1; }
  95% { opacity:.55; }
  96% { opacity:0; }
}

/* ---- edge vignette — a quick red pulse on a landed heavy hit ("vig-hit"),
   or a slower ominous darken around the OTHER figure during a wind-up
   ("vig-danger"). Positioned via --vig-x (set from JS). ---- */
.stage-vignette {
  position:absolute;
  inset:0;
  z-index:3;
  opacity:0;
  pointer-events:none;
  --vig-x:50%;
}
.stage-vignette.vig-hit {
  background:radial-gradient(ellipse at var(--vig-x) 50%, transparent 35%, rgba(248,96,122,0.4) 100%);
  animation:vigHitPulse .34s ease-out forwards;
}
.stage-vignette.vig-danger {
  background:radial-gradient(ellipse at var(--vig-x) 50%, transparent 28%, rgba(120,10,20,0.42) 100%);
  animation:vigDangerRamp .75s ease-in forwards;
}

/* ---- Phase 10D — ambient stage particles. Pure CSS (no JS timers, no
   global interval): a handful of small radial-gradient dots/streaks on
   .stage-vignette::before (unused until now — the element's own background
   is only ever the hit/danger flash above), style-scoped via the ancestor
   .stage-<style> class, all moving together as one drifting layer. Each
   layer draws ≤5 discrete shapes, well under the "≤6 concurrent" budget.
   Killed by the reduced-motion block below (added to that same selector
   list) same as everything else on this stage. ---- */
.stage-energy .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    radial-gradient(2.5px 2.5px at 12% 82%, rgba(255,170,60,0.85), transparent 70%),
    radial-gradient(2px 2px at 34% 68%, rgba(255,200,90,0.8), transparent 70%),
    radial-gradient(2.5px 2.5px at 58% 88%, rgba(255,150,40,0.75), transparent 70%),
    radial-gradient(2px 2px at 76% 60%, rgba(255,190,80,0.8), transparent 70%),
    radial-gradient(2.5px 2.5px at 90% 78%, rgba(255,160,50,0.7), transparent 70%);
  animation:emberParticles 9s ease-in infinite;
}
@keyframes emberParticles {
  0% { transform:translateY(0); opacity:0; }
  10% { opacity:.9; }
  85% { opacity:.5; }
  100% { transform:translateY(-90px); opacity:0; }
}

.stage-ocean .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    radial-gradient(3px 3px at 18% 85%, rgba(210,250,245,0.65), transparent 70%),
    radial-gradient(2px 2px at 40% 92%, rgba(210,250,245,0.6), transparent 70%),
    radial-gradient(2.5px 2.5px at 62% 80%, rgba(210,250,245,0.55), transparent 70%),
    radial-gradient(2px 2px at 82% 90%, rgba(210,250,245,0.6), transparent 70%);
  animation:bubbleParticles 11s ease-in-out infinite;
}
@keyframes bubbleParticles {
  0% { transform:translateY(10px); opacity:0; }
  15% { opacity:.8; }
  100% { transform:translateY(-70px); opacity:0; }
}

.stage-dojo .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    radial-gradient(3px 2px at 20% 10%, rgba(255,180,190,0.75), transparent 70%),
    radial-gradient(3px 2px at 45% 4%, rgba(255,200,150,0.7), transparent 70%),
    radial-gradient(3px 2px at 68% 12%, rgba(255,180,190,0.7), transparent 70%),
    radial-gradient(3px 2px at 88% 6%, rgba(255,200,150,0.65), transparent 70%);
  animation:petalParticles 13s linear infinite;
}
@keyframes petalParticles {
  0% { transform:translate(0,0) rotate(0); opacity:0; }
  12% { opacity:.85; }
  100% { transform:translate(-18px, 96px) rotate(35deg); opacity:0; }
}

.stage-void .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    radial-gradient(2px 2px at 15% 30%, rgba(230,180,250,0.8), transparent 70%),
    radial-gradient(2px 2px at 40% 55%, rgba(200,140,240,0.75), transparent 70%),
    radial-gradient(2.5px 2.5px at 65% 25%, rgba(230,180,250,0.7), transparent 70%),
    radial-gradient(2px 2px at 85% 50%, rgba(200,140,240,0.75), transparent 70%);
  animation:moteParticles 14s ease-in-out infinite;
}
@keyframes moteParticles {
  0%, 100% { transform:translate(0,0); opacity:.4; }
  50% { transform:translate(6px,-10px); opacity:.85; }
}

.stage-storm .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    linear-gradient(200deg, transparent 0%, rgba(210,230,255,0.5) 45%, transparent 55%),
    linear-gradient(200deg, transparent 0%, rgba(210,230,255,0.4) 45%, transparent 55%),
    linear-gradient(200deg, transparent 0%, rgba(210,230,255,0.45) 45%, transparent 55%);
  background-size:2px 46px, 2px 60px, 2px 38px;
  background-position:20% -20%, 55% -30%, 82% -15%;
  background-repeat:no-repeat;
  opacity:.55;
  animation:rainParticles 1.8s linear infinite;
}
@keyframes rainParticles {
  0% { background-position:20% -20%, 55% -30%, 82% -15%; }
  100% { background-position:20% 120%, 55% 120%, 82% 120%; }
}

.stage-default .stage-vignette::before {
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background-image:
    radial-gradient(1.5px 1.5px at 20% 40%, rgba(255,255,255,0.35), transparent 70%),
    radial-gradient(1.5px 1.5px at 50% 65%, rgba(255,255,255,0.3), transparent 70%),
    radial-gradient(1.5px 1.5px at 75% 35%, rgba(255,255,255,0.32), transparent 70%),
    radial-gradient(1.5px 1.5px at 90% 60%, rgba(255,255,255,0.28), transparent 70%);
  animation:dustParticles 16s ease-in-out infinite;
}
@keyframes dustParticles {
  0%, 100% { transform:translate(0,0); opacity:.3; }
  50% { transform:translate(4px, -6px); opacity:.6; }
}
@keyframes vigHitPulse { 0% { opacity:.95; } 100% { opacity:0; } }
@keyframes vigDangerRamp { 0% { opacity:0; } 70% { opacity:.85; } 100% { opacity:.85; } }

/* ---- whole-stage jolt on impact ---- */
.battle-stage.stage-shake { animation:stageShakeSm .34s cubic-bezier(.36,0,.66,1); }
.battle-stage.stage-shake-big { animation:stageShakeLg .46s cubic-bezier(.36,0,.66,1); }
@keyframes stageShakeSm {
  0%, 100% { transform:translate(0,0); }
  20% { transform:translate(-3px,1px); }
  40% { transform:translate(3px,-1px); }
  60% { transform:translate(-2px,1px); }
  80% { transform:translate(2px,0); }
}
@keyframes stageShakeLg {
  0%, 100% { transform:translate(0,0) rotate(0); }
  15% { transform:translate(-6px,2px) rotate(-.4deg); }
  30% { transform:translate(6px,-2px) rotate(.4deg); }
  45% { transform:translate(-5px,2px) rotate(-.3deg); }
  60% { transform:translate(4px,-1px) rotate(.2deg); }
  75% { transform:translate(-2px,1px); }
  90% { transform:translate(1px,0); }
}

/* ---- figures ---- */
.stage-fig {
  position:relative;
  display:flex;
  align-items:center;
  justify-content:center;
  width:68px;
  height:68px;
  flex:0 0 auto;
  z-index:1;
  will-change:transform, opacity, filter;
}
.stage-player { --dir:1; margin-right:auto; }
.stage-enemy  { --dir:-1; margin-left:auto; }

/* Phase 10D — transform aura: a soft glow ring behind the player figure,
   toggled by game.js (has-aura) whenever transform_state.active has any
   entry. Lives on .stage-fig::before (unused until now — the ground shadow
   below owns ::after) so it paints BEHIND the rig/glyph without any extra
   markup. Color prefers the rig's own --accent (copied onto this element by
   applyBattleVisuals so it's actually reachable — a descendant <svg>'s custom
   property isn't visible to a sibling pseudo-element); the global --accent
   token (gold) is what's already there otherwise, so no explicit fallback is
   needed. Static (no animation) under reduced-motion, per the media query at
   the end of this section. */
.stage-fig::before {
  content:"";
  position:absolute;
  inset:-16px;
  z-index:0;
  border-radius:50%;
  opacity:0;
  pointer-events:none;
  background:radial-gradient(circle, var(--accent) 0%, transparent 70%);
  filter:blur(6px);
  transition:opacity .35s ease;
}
.stage-player.has-aura::before {
  opacity:.55;
  animation:auraPulse 2.4s ease-in-out infinite;
}
@keyframes auraPulse {
  0%, 100% { opacity:.45; transform:scale(1); }
  50% { opacity:.75; transform:scale(1.14); }
}

/* ground contact shadow — squashes/stretches on jumps & dashes */
.stage-fig::after {
  content:"";
  position:absolute;
  left:50%;
  bottom:-6px;
  width:58%;
  height:11px;
  transform:translateX(-50%);
  border-radius:50%;
  background:radial-gradient(ellipse at center, rgba(0,0,0,0.5), transparent 72%);
  filter:blur(1px);
  pointer-events:none;
}
/* Phase 10D — boss presence: a bigger, darker pedestal shadow under a boss
   enemy (the rig itself scales up 1.18x via fighters.js's .boss-rig; this
   just grounds it). */
.stage-enemy.is-boss::after {
  width:74%;
  height:15px;
  bottom:-8px;
  background:radial-gradient(ellipse at center, rgba(0,0,0,0.72), transparent 74%);
  filter:blur(1.5px);
}
.fx-dash::after     { animation:shadowPulse .5s ease-out; }
.fx-lunge::after     { animation:shadowPulseBig .62s ease-out; }
.fx-sidestep::after  { animation:shadowSlide .44s ease-out; }
.fx-blink::after, .fx-teleport::after { animation:shadowFade .5s ease-in-out; }
@keyframes shadowPulse {
  0%, 100% { transform:translateX(-50%) scaleX(1); }
  45% { transform:translateX(-50%) scaleX(.65); }
}
@keyframes shadowPulseBig {
  0%, 100% { transform:translateX(-50%) scaleX(1) scaleY(1); }
  45% { transform:translateX(-50%) scaleX(.5) scaleY(1.35); }
}
@keyframes shadowSlide {
  0%, 100% { transform:translateX(-50%) scaleX(1); }
  50% { transform:translateX(calc(-50% + var(--dir,1) * -10px)) scaleX(.82); }
}
@keyframes shadowFade {
  0%, 100% { opacity:.55; transform:translateX(-50%) scaleX(1); }
  45%, 60% { opacity:.12; transform:translateX(-50%) scaleX(.55); }
}

/* mirroring wrapper — flips the enemy glyph so both figures face each
   other, WITHOUT touching .fig-glyph's own transform (idle bob + fx
   keyframes live there and must stay untouched by the static flip). */
.fig-mirror { display:inline-block; line-height:1; }
.stage-enemy .fig-mirror { transform:scaleX(-1); }

.fig-glyph {
  display:inline-block;
  font-size:54px;
  line-height:1;
  will-change:transform, opacity, filter;
  animation-duration:2.4s;
  animation-timing-function:ease-in-out;
  animation-iteration-count:infinite;
}
/* idle breathing bob — low specificity on purpose: any .fx-<name> rule
   below re-declares `animation` on `.fig-glyph` at equal specificity and
   (by appearing later in the file) wins for the duration of that class;
   once the class is stripped, idle resumes with no JS bookkeeping. */
.stage-player .fig-glyph { animation-name:figBreathe; filter:drop-shadow(0 2px 7px rgba(56,189,248,0.5)); }
.stage-enemy  .fig-glyph { animation-name:figBreathe; animation-delay:-1.2s; filter:drop-shadow(0 2px 7px rgba(248,96,122,0.5)); }
@keyframes figBreathe {
  0%, 100% { transform:translateY(0) scale(1); }
  50% { transform:translateY(-2px) scale(1.015); }
}

/* hurt reaction — white flash + brief tint + recoil lean, applied to the
   OUTER figure (matches the .fx-* pattern) so it composes with, and (by
   source order) wins over, whatever fx-* the figure was just playing. */
.is-hurt .fig-glyph {
  animation:hitFlashGlyph .32s ease-out;
}
.is-hurt-heavy .fig-glyph {
  animation:hitFlashGlyphHeavy .38s ease-out;
}
@keyframes hitFlashGlyph {
  0% { filter:brightness(1) saturate(1); transform:translateX(0); }
  25% { filter:brightness(2.6) saturate(.3) drop-shadow(0 0 6px rgba(248,96,122,0.85)); transform:translateX(calc(var(--dir,1) * -8px)); }
  100% { filter:brightness(1) saturate(1); transform:translateX(0); }
}
@keyframes hitFlashGlyphHeavy {
  0% { filter:brightness(1) saturate(1); transform:translateX(0) rotate(0); }
  22% { filter:brightness(3) saturate(.2) drop-shadow(0 0 10px rgba(248,96,122,0.95)); transform:translateX(calc(var(--dir,1) * -14px)) rotate(calc(var(--dir,1) * -6deg)); }
  100% { filter:brightness(1) saturate(1); transform:translateX(0) rotate(0); }
}

/* Phase 10 — a downgraded (couldn't-afford) queued beat gets a quick
   off-balance wobble on the player figure during round replay. */
.is-stumble .fig-glyph {
  animation:stumbleWobble .42s ease-in-out;
}
@keyframes stumbleWobble {
  0%, 100% { transform:translateX(0) rotate(0); }
  20% { transform:translateX(-5px) rotate(-3deg); }
  45% { transform:translateX(4px) rotate(2deg); }
  70% { transform:translateX(-2px) rotate(-1deg); }
}

/* Phase 10B — SVG fighter rigs live INSIDE .fig-glyph (fighters.js turns
   the span into a .rig-holder). Size the rig to the stage and let the feet
   reach the ground shadow; every whole-figure fx/hurt/KO keyframe above
   still applies (the span moves, carrying the rig), while joint animations
   layer on the bones inside. */
.stage-fig .fig-glyph.rig-holder {
  width:82px;
  height:114px;
  margin-bottom:-20px; /* feet meet the ::after ground shadow */
}
/* Rim glow — separates dark rig palettes from the dark stage (replaces the
   emoji drop-shadow, which is too faint behind a full body). */
.stage-player .fig-glyph.rig-holder {
  filter:drop-shadow(0 0 5px rgba(56,189,248,0.55)) drop-shadow(0 2px 12px rgba(56,189,248,0.3));
}
.stage-enemy .fig-glyph.rig-holder {
  filter:drop-shadow(0 0 5px rgba(248,96,122,0.55)) drop-shadow(0 2px 12px rgba(248,96,122,0.3));
}

/* KO — the enemy figure desaturates and falls/fades. */
.stage-enemy.is-defeated .fig-glyph {
  animation:koFall .7s cubic-bezier(.55,0,.85,.35) forwards;
}
/* Rig mode: skip the emoji fall/fade — the rig plays its own crumple
   (rig-ko) and should stay visible on the ground, not fade to nothing. */
.stage-enemy.is-defeated .fig-glyph.rig-holder {
  animation:koFallRig .7s ease-out forwards;
}
@keyframes koFallRig {
  0% { filter:grayscale(0) brightness(1); }
  100% { filter:grayscale(.7) brightness(.75); }
}
@keyframes koFall {
  0% { opacity:1; transform:translateY(0) rotate(0); filter:grayscale(0) brightness(1); }
  30% { transform:translateY(-6px) rotate(calc(var(--dir,1) * -10deg)); }
  100% { opacity:0; transform:translateY(22px) rotate(calc(var(--dir,1) * -80deg)); filter:grayscale(1) brightness(.6); }
}

/* =======================================================================
   fx-* — one class per whitelisted name, still applied on the OUTER
   `.stage-fig` (id'd) element, same as before. Each targets `.fig-glyph`
   via a descendant selector for the figure's own motion (anticipation ->
   action -> impact -> follow-through, per the design brief), using
   cubic-bezier easing rather than linear. Cross-figure spawned layers
   (streaks/orbs/rings/shards/motes/…) and impact timing are handled by
   the FX_BUILDERS orchestrators in game.js, which add/remove `.fxp` nodes
   — see that section for the JS-side half of each storyboard.
   ======================================================================= */
.fx-dash .fig-glyph      { animation:fxDash .52s cubic-bezier(.34,1.1,.64,1); }
.fx-lunge .fig-glyph     { animation:fxLunge .64s cubic-bezier(.32,1.2,.6,1); }
.fx-blink .fig-glyph     { animation:fxBlink .5s cubic-bezier(.5,0,.5,1); }
.fx-teleport .fig-glyph  { animation:fxTeleport .6s cubic-bezier(.5,0,.5,1); }
.fx-sidestep .fig-glyph  { animation:fxSidestep .44s cubic-bezier(.3,1,.5,1); }
.fx-flash .fig-glyph     { animation:fxFlash .34s ease-out; }
.fx-projectile .fig-glyph{ animation:fxWindupArm .4s ease-out; }
.fx-beam .fig-glyph      { animation:fxWindupGlow .5s ease-out; }
.fx-aura .fig-glyph      { animation:fxAura .8s ease-in-out; }
.fx-charge .fig-glyph    { animation:fxCharge .72s ease-in-out; }
.fx-shield .fig-glyph    { animation:fxDomePop .3s cubic-bezier(.34,1.6,.64,1); }
.fx-barrier .fig-glyph   { animation:fxDomePop .34s cubic-bezier(.34,1.6,.64,1); }
.fx-crack .fig-glyph     { animation:fxCrack .42s cubic-bezier(.36,0,.66,1); }
.fx-shake .fig-glyph     { animation:fxShakeDecay .48s cubic-bezier(.36,0,.66,1); }
.fx-glow .fig-glyph      { animation:fxGlow .68s ease-in-out; }
.fx-afterimage .fig-glyph{ animation:fxAfterimageGlyph .42s ease-out; }
.fx-flicker .fig-glyph   { animation:fxFlicker .5s steps(1, end); }

/* Movement keyframes use --travel (the measured px gap between the fighters,
   set by playFx) so strikes visibly CROSS the stage on any screen size. */
@keyframes fxDash {
  0% { transform:translateX(0) rotate(0); }
  22% { transform:translateX(calc(var(--dir,1) * -14px)) rotate(calc(var(--dir,1) * -6deg)); }
  55% { transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .72)) rotate(calc(var(--dir,1) * 5deg)); }
  72% { transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .8)) rotate(calc(var(--dir,1) * 2deg)); }
  100% { transform:translateX(0) rotate(0); }
}
@keyframes fxLunge {
  0% { transform:translateX(0) scale(1) rotate(0); }
  22% { transform:translateX(calc(var(--dir,1) * -22px)) scale(.94) rotate(calc(var(--dir,1) * -8deg)); }
  55% { transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .82)) scale(1.14) rotate(calc(var(--dir,1) * 6deg)); }
  74% { transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .9)) scale(1.08); }
  100% { transform:translateX(0) scale(1) rotate(0); }
}
@keyframes fxBlink {
  0% { opacity:1; transform:scaleY(1) scaleX(1); filter:brightness(1); }
  16% { opacity:1; transform:scaleY(1.5) scaleX(.6); filter:brightness(2.4); }
  17% { opacity:0; transform:scaleY(1.5) scaleX(.6); }
  55% { opacity:0; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .42)) scaleY(1.5) scaleX(.6); }
  60% { opacity:1; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .42)) scaleY(1.3) scaleX(.75); filter:brightness(1.8); }
  82% { opacity:1; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .42)) scaleY(1) scaleX(1); }
  100% { opacity:1; transform:translateX(0) scaleY(1) scaleX(1); filter:brightness(1); }
}
@keyframes fxTeleport {
  0% { opacity:1; transform:rotate(0) scale(1); filter:brightness(1) hue-rotate(0deg); }
  18% { opacity:1; transform:rotate(90deg) scale(.7); filter:brightness(2) hue-rotate(40deg); }
  22% { opacity:0; transform:rotate(180deg) scale(.5); }
  60% { opacity:0; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .45)) rotate(180deg) scale(.5); }
  66% { opacity:1; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .45)) rotate(300deg) scale(.75); filter:brightness(2) hue-rotate(-30deg); }
  84% { opacity:1; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .45)) rotate(360deg) scale(1); }
  100% { opacity:1; transform:translateX(0) rotate(360deg) scale(1); filter:brightness(1) hue-rotate(0deg); }
}
@keyframes fxSidestep {
  0% { transform:translateX(0) rotate(0); }
  45% { transform:translateX(calc(var(--dir,1) * -44px)) rotate(calc(var(--dir,1) * -10deg)) skewX(calc(var(--dir,1) * -4deg)); }
  70% { transform:translateX(calc(var(--dir,1) * -38px)) rotate(calc(var(--dir,1) * -6deg)); }
  100% { transform:translateX(0) rotate(0); }
}
@keyframes fxFlash {
  0%, 100% { filter:brightness(1) saturate(1); }
  40% { filter:brightness(3) saturate(0); }
}
@keyframes fxWindupArm {
  0%, 100% { transform:translateX(0) rotate(0); filter:brightness(1); }
  30% { transform:translateX(calc(var(--dir,1) * -8px)) rotate(calc(var(--dir,1) * -6deg)); filter:brightness(1.3); }
}
@keyframes fxWindupGlow {
  0%, 100% { transform:scale(1); filter:brightness(1); }
  60% { transform:scale(1.06); filter:brightness(1.5) drop-shadow(0 0 10px var(--sky)); }
}
@keyframes fxAura {
  0%, 100% { transform:scale(1); filter:brightness(1) drop-shadow(0 0 0 transparent); }
  50% { transform:scale(1.07); filter:brightness(1.35) drop-shadow(0 0 16px var(--accent)); }
}
@keyframes fxCharge {
  0% { transform:scale(1) translateX(0); filter:brightness(1); }
  20% { transform:scale(1.02) translateX(-1px); }
  40% { transform:scale(1.04) translateX(1px); }
  60% { transform:scale(1.05) translateX(-1px); }
  85% { transform:scale(1.06) translateX(1px); filter:brightness(1.4) drop-shadow(0 0 14px var(--danger)); }
  100% { transform:scale(1.06) translateX(0); filter:brightness(1.6) drop-shadow(0 0 18px var(--danger)); }
}
@keyframes fxDomePop {
  0% { transform:scale(1); }
  55% { transform:scale(1.1); }
  100% { transform:scale(1); }
}
@keyframes fxCrack {
  0% { transform:rotate(0); }
  30% { transform:rotate(calc(var(--dir,1) * -14deg)); }
  55% { transform:rotate(calc(var(--dir,1) * 10deg)); }
  100% { transform:rotate(0); }
}
@keyframes fxShakeDecay {
  0%, 100% { transform:translateX(0); }
  14% { transform:translateX(calc(var(--dir,1) * -9px)); }
  28% { transform:translateX(calc(var(--dir,1) * 7px)); }
  42% { transform:translateX(calc(var(--dir,1) * -5px)); }
  56% { transform:translateX(calc(var(--dir,1) * 3px)); }
  70% { transform:translateX(calc(var(--dir,1) * -2px)); }
  84% { transform:translateX(calc(var(--dir,1) * 1px)); }
}
@keyframes fxGlow {
  0%, 100% { transform:scale(1); filter:brightness(1) drop-shadow(0 0 0 transparent); }
  50% { transform:scale(1.04); filter:brightness(1.3) drop-shadow(0 0 14px var(--danger)); }
}
@keyframes fxAfterimageGlyph {
  0% { opacity:1; transform:translateX(0); }
  30% { opacity:.35; transform:translateX(calc(var(--dir,1) * -26px)); }
  58% { opacity:.9; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .68)) rotate(calc(var(--dir,1) * 6deg)); }
  76% { opacity:1; transform:translateX(calc(var(--dir,1) * var(--travel,220px) * .74)); }
  100% { opacity:1; transform:translateX(0); }
}
@keyframes fxFlicker {
  0%, 100% { opacity:1; transform:translateX(0); }
  15% { opacity:.2; transform:translateX(2px); }
  30% { opacity:1; transform:translateX(-2px); }
  50% { opacity:.25; transform:translateX(1px); }
  65% { opacity:.95; transform:translateX(-1px); }
  85% { opacity:.3; transform:translateX(1px); }
}

/* shield/barrier dome — a ring + translucent arc snapping in front of the
   figure (--dir aware), pop -> hold -> gentle shatter-out. shield-flash
   retriggers it (without the pop) on a blocked hit. */
.fx-shield::before, .fx-barrier::before {
  content:"";
  position:absolute;
  top:8%;
  bottom:8%;
  width:70%;
  left:calc(50% + var(--dir,1) * 18%);
  transform:translateX(-50%) scale(.85);
  border-radius:50%;
  border:2px solid var(--sky);
  box-shadow:0 0 14px rgba(56,189,248,0.5), inset 0 0 10px rgba(56,189,248,0.3);
  opacity:0;
  pointer-events:none;
  animation:fxShieldRing .56s cubic-bezier(.34,1.6,.64,1);
}
.fx-barrier::before {
  border-color:var(--violet);
  border-width:3px;
  box-shadow:0 0 18px rgba(139,124,246,0.55), inset 0 0 14px rgba(139,124,246,0.35), inset 0 0 0 6px rgba(139,124,246,0.12);
  animation-name:fxBarrierRing;
  animation-duration:.66s;
}
.fx-shield.shield-flash::before, .fx-barrier.shield-flash::before {
  animation-name:fxShieldFlash;
  animation-duration:.3s;
}
@keyframes fxShieldRing {
  0% { opacity:0; transform:translateX(-50%) scale(.6) rotate(0deg); }
  15% { opacity:.95; transform:translateX(-50%) scale(1.08) rotate(10deg); }
  30% { transform:translateX(-50%) scale(1) rotate(0deg); }
  80% { opacity:.85; transform:translateX(-50%) scale(1) rotate(-6deg); }
  100% { opacity:0; transform:translateX(-50%) scale(1.15) rotate(4deg); }
}
@keyframes fxBarrierRing {
  0% { opacity:0; transform:translateX(-50%) scale(.55) rotate(0deg); }
  14% { opacity:1; transform:translateX(-50%) scale(1.1) rotate(-14deg); }
  32% { transform:translateX(-50%) scale(1) rotate(0deg); }
  85% { opacity:.85; transform:translateX(-50%) scale(1) rotate(8deg); }
  100% { opacity:0; transform:translateX(-50%) scale(1.2) rotate(-6deg); }
}
@keyframes fxShieldFlash {
  0% { opacity:.9; box-shadow:0 0 26px rgba(255,255,255,0.9), inset 0 0 16px rgba(255,255,255,0.6); }
  100% { opacity:0; }
}

/* =======================================================================
   .fxp — spawned, short-lived presentation layers (game.js spawnFx()).
   Positioned via inline left/bottom % (or px for a fixed width/travel) at
   spawn time; every keyframe below only ever runs once (forwards) and the
   node is removed on animationend (+ a JS safety timeout). --dir/--travel/
   --span/--rot are set inline per spawn where relevant.
   ======================================================================= */
.fxp {
  position:absolute;
  pointer-events:none;
  will-change:transform, opacity, filter;
  z-index:2;
}

/* motion-trail streak (dash/lunge/sidestep/counter-slash/flash-slash) */
.fxp-streak {
  top:50%; left:0;
  width:26px; height:4px;
  margin:-2px 0 0 -13px;
  border-radius:var(--radius-full);
  background:linear-gradient(90deg, transparent, rgba(238,240,251,0.85), transparent);
  animation:fxpStreak .3s ease-out forwards;
}
.fxp-streak-big  { width:34px; height:5px; background:linear-gradient(90deg, transparent, rgba(240,180,41,0.9), transparent); }
.fxp-streak-side { background:linear-gradient(90deg, transparent, rgba(56,189,248,0.85), transparent); }
@keyframes fxpStreak {
  0% { opacity:.9; transform:translateX(calc(var(--dir,1) * -16px)) scaleX(.6); }
  100% { opacity:0; transform:translateX(0) scaleX(1.4); }
}

/* ground dust puff (lunge launch / sidestep feet) */
.fxp-dust {
  left:0; bottom:0;
  width:16px; height:8px;
  margin-left:-8px;
  border-radius:50%;
  background:radial-gradient(ellipse at center, rgba(255,255,255,0.35), transparent 70%);
  animation:fxpDust .4s ease-out forwards;
}
@keyframes fxpDust {
  0% { opacity:.7; transform:scale(.4); }
  100% { opacity:0; transform:scale(2.2); }
}

/* expanding ring (blink/teleport) */
.fxp-ring {
  top:50%; left:0;
  width:46px; height:46px;
  margin:-23px 0 0 -23px;
  border-radius:50%;
  border:3px solid rgba(238,240,251,0.95);
  box-shadow:0 0 14px rgba(148,190,255,0.6), inset 0 0 10px rgba(148,190,255,0.35);
  animation:fxpRing .45s ease-out forwards;
}
.fxp-ring-swirl { border-color:rgba(217,70,239,0.95); animation-name:fxpRingSwirl; }
.fxp-ring-rev   { animation-direction:reverse; }
@keyframes fxpRing {
  0% { opacity:1; transform:scale(.3); }
  100% { opacity:0; transform:scale(2.1); }
}
@keyframes fxpRingSwirl {
  0% { opacity:.9; transform:scale(.3) rotate(0deg); }
  100% { opacity:0; transform:scale(1.9) rotate(160deg); }
}

/* sparkle dot (blink/teleport) */
.fxp-spark {
  top:50%; left:0;
  width:4px; height:4px;
  margin:-2px 0 0 -2px;
  border-radius:50%;
  background:#eef0fb;
  box-shadow:0 0 6px 1px rgba(238,240,251,0.8);
  animation:fxpSpark .5s ease-out forwards;
}
@keyframes fxpSpark {
  0% { opacity:1; transform:translateY(0) scale(1); }
  100% { opacity:0; transform:translateY(-14px) scale(.4); }
}

/* silhouette clone (blink origin / afterimage counter split) — a simple
   shape, never the emoji glyph itself */
.fxp-ghost {
  top:0; left:0;
  width:26px; height:44px;
  margin-left:-13px;
  border-radius:40% 40% 50% 50%;
  background:rgba(56,189,248,0.28);
  filter:blur(1px);
  animation:fxpGhost .45s ease-out forwards;
}
.fxp-ghost-left  { animation-name:fxpGhostLeft; background:rgba(238,240,251,0.22); }
.fxp-ghost-right { animation-name:fxpGhostRight; background:rgba(238,240,251,0.22); }
@keyframes fxpGhost {
  0% { opacity:.55; transform:translateX(0) scale(1); }
  100% { opacity:0; transform:translateX(calc(var(--dir,1) * -22px)) scale(.85); }
}
@keyframes fxpGhostLeft  { 0% { opacity:.6; transform:translateX(0); } 100% { opacity:0; transform:translateX(-24px); } }
@keyframes fxpGhostRight { 0% { opacity:.6; transform:translateX(0); } 100% { opacity:0; transform:translateX(24px); } }

/* projectile core + bloom, travels via --travel (px, set from JS) */
.fxp-orb {
  top:50%; left:0;
  width:14px; height:14px;
  margin:-7px 0 0 -7px;
  border-radius:50%;
  background:radial-gradient(circle, #fff, var(--accent) 55%, transparent 75%);
  box-shadow:0 0 12px 3px rgba(240,180,41,0.65);
  animation:fxpOrbTravel .3s ease-in forwards;
}
.fxp-orb-charge {
  background:radial-gradient(circle, #fff, var(--sky) 55%, transparent 75%);
  box-shadow:0 0 14px 4px rgba(56,189,248,0.65);
  animation:fxpOrbCharge .32s ease-in forwards;
}
@keyframes fxpOrbTravel {
  0% { opacity:0; transform:translateX(0) scale(.4); }
  15% { opacity:1; transform:translateX(0) scale(1); }
  100% { opacity:.9; transform:translateX(var(--travel, 200px)) scale(1); }
}
@keyframes fxpOrbCharge {
  0% { opacity:0; transform:scale(.5); }
  100% { opacity:1; transform:scale(1.15); }
}

/* projectile trail dots — staggered via JS animation-delay */
.fxp-trail-dot {
  top:50%; left:0;
  width:5px; height:5px;
  margin:-2.5px 0 0 -2.5px;
  border-radius:50%;
  background:rgba(240,180,41,0.75);
  animation:fxpTrailDot .3s ease-out forwards;
}
@keyframes fxpTrailDot { 0% { opacity:.8; transform:scale(1); } 100% { opacity:0; transform:scale(.3); } }

/* thick beam bar — fixed width set from JS (px), grows via scaleX from
   transform-origin (left for the player, right for the enemy). */
.fxp-beambar {
  top:50%; left:0;
  height:6px;
  margin-top:-3px;
  border-radius:var(--radius-full);
  background:linear-gradient(90deg, rgba(255,255,255,0.95), var(--sky) 40%, rgba(56,189,248,0.15));
  box-shadow:0 0 16px 3px rgba(56,189,248,0.6);
  transform-origin:left center;
  animation:fxpBeam .42s cubic-bezier(.2,.9,.3,1) forwards;
}
.fxp-beambar-rev { transform-origin:right center; }
@keyframes fxpBeam {
  0% { opacity:0; transform:scaleX(0); }
  10% { opacity:1; }
  35% { transform:scaleX(1); }
  55% { transform:scaleX(1.02); }
  65% { transform:scaleX(.99); }
  85% { opacity:1; transform:scaleX(1); }
  100% { opacity:0; transform:scaleX(1); }
}

/* rising energy motes (aura / charge) */
.fxp-mote {
  left:0; bottom:0;
  width:6px; height:6px;
  margin-left:-3px;
  border-radius:50%;
  animation:fxpMoteRise 1s ease-out forwards;
}
.fxp-mote-gold { background:radial-gradient(circle, #fff, var(--accent) 60%, transparent 75%); box-shadow:0 0 8px 2px rgba(240,180,41,0.6); }
.fxp-mote-red  { background:radial-gradient(circle, #fff, var(--danger) 60%, transparent 75%); box-shadow:0 0 8px 2px rgba(248,96,122,0.6); }
@keyframes fxpMoteRise {
  0% { opacity:0; transform:translateY(0) scale(.6); }
  20% { opacity:1; }
  100% { opacity:0; transform:translateY(-46px) scale(1); }
}

/* jagged shard lines bursting outward (crack / guard shatter), --rot set
   per-shard from JS */
.fxp-shard {
  top:50%; left:0;
  width:26px; height:3px;
  margin:-1px 0 0 -13px;
  background:linear-gradient(90deg, transparent, #fff 30%, var(--warning), transparent);
  box-shadow:0 0 8px rgba(250,204,120,0.8);
  animation:fxpShard .5s cubic-bezier(.2,.8,.4,1) forwards;
}
@keyframes fxpShard {
  0% { opacity:1; transform:rotate(var(--rot,0deg)) translateX(2px) scale(.5); }
  35% { opacity:1; transform:rotate(var(--rot,0deg)) translateX(24px) scale(1.4); }
  100% { opacity:0; transform:rotate(var(--rot,0deg)) translateX(44px) scale(1); }
}

/* radial impact burst (impactBurst helper) */
.fxp-burst {
  top:50%; left:0;
  width:20px; height:20px;
  margin:-10px 0 0 -10px;
  border-radius:50%;
  background:radial-gradient(circle, rgba(255,255,255,0.95), var(--accent) 45%, transparent 72%);
  animation:fxpBurst .32s ease-out forwards;
}
.fxp-burst-heavy {
  width:30px; height:30px;
  margin:-15px 0 0 -15px;
  background:radial-gradient(circle, rgba(255,255,255,0.95), var(--danger) 45%, transparent 72%);
  animation-duration:.4s;
}
@keyframes fxpBurst { 0% { opacity:1; transform:scale(.3); } 100% { opacity:0; transform:scale(2.2); } }

/* feint warning puff — "!" that dissolves into "?" */
.fxp-warn {
  top:0; left:0;
  transform:translate(-50%, -100%);
  font-size:20px;
  font-weight:900;
  color:var(--warning);
  text-shadow:0 0 8px rgba(251,191,36,0.7);
  animation:fxpWarn .56s ease-out forwards;
}
.fxp-warn-flip { color:var(--muted); text-shadow:none; }
@keyframes fxpWarn {
  0% { opacity:0; transform:translate(-50%,-90%) scale(.6); }
  20% { opacity:1; transform:translate(-50%,-110%) scale(1.1); }
  70% { opacity:1; transform:translate(-50%,-110%) scale(1); }
  100% { opacity:0; transform:translate(-50%,-130%) scale(.8); }
}

/* full-stage white flash + a sweeping slash streak (flash effect) */
.fxp-whiteflash {
  inset:0;
  background:rgba(255,255,255,0.15);
  animation:fxpWhiteFlash .32s ease-out forwards;
}
@keyframes fxpWhiteFlash { 0% { opacity:1; } 100% { opacity:0; } }
.fxp-slash {
  top:50%; left:0;
  width:70%; height:4px;
  margin-top:-2px;
  background:linear-gradient(90deg, transparent, #fff, transparent);
  animation:fxpSlash .3s ease-out forwards;
}
@keyframes fxpSlash {
  0% { opacity:0; transform:translateX(calc(var(--dir,1) * -20%)) scaleX(.3); }
  40% { opacity:1; transform:translateX(0) scaleX(1.1); }
  100% { opacity:0; transform:translateX(calc(var(--dir,1) * 20%)) scaleX(.6); }
}

/* Respect reduced-motion: kill every fx/idle/ambience/vignette/shake
   animation outright (base layout/tint is unaffected), and spawnFx() in
   game.js independently never creates a .fxp node in the first place. */
@media (prefers-reduced-motion: reduce) {
  .battle-stage,
  .battle-stage::before,
  .battle-stage::after,
  .stage-fig,
  .stage-fig::before,
  .stage-fig::after,
  .fig-glyph,
  .fig-mirror,
  [class*="fx-"],
  [class*="fx-"] .fig-glyph,
  [class*="fx-"]::before,
  [class*="fx-"]::after,
  .is-hurt .fig-glyph,
  .is-hurt-heavy .fig-glyph,
  .is-defeated .fig-glyph,
  .is-stumble .fig-glyph,
  .stage-vignette,
  .stage-vignette::before,
  .fxp {
    animation:none !important;
    transition:none !important;
  }
  /* Aura (.stage-fig::before) stays visible but static — that's the spec.
     Ambient particles (.stage-vignette::before) frozen mid-drift would just
     read as stray dots, not a deliberate state, so hide them outright. */
  .stage-vignette::before {
    opacity:0 !important;
  }
}

@media (max-width:479px) {
  .battle-stage { min-height:110px; padding:8px 12px; }
  .fig-glyph { font-size:38px; }
  .stage-fig { width:52px; height:52px; }
}

/* =======================================================================
   Action bar — Next/Heal (explore) + Attack/Skill/Item/Flee (combat).
   Both rows always rendered (disabled, never hidden) so nothing reshuffles.
   ======================================================================= */
.action-bar { display:flex; flex-direction:column; gap:8px; margin-top:12px; }
.action-row { display:grid; gap:8px; }
.action-row--explore { grid-template-columns:repeat(2, 1fr); }
/* 9 combat buttons (attack/skill/item/flee + block/dodge/counter/guard
   break/focus) — auto-fit lets the grid pick as many columns as fit the
   viewport and wrap the rest, so it never overflows on mobile. */
.action-row--combat { grid-template-columns:repeat(auto-fit, minmax(84px, 1fr)); }
.action-row--combat .btn { font-size:var(--text-sm); padding:7px 8px; }
.action-btn { width:100%; }
/* Energy-cost badge appended to combat-bar button labels (Attack/Skill/
   Block/Dodge/.../Focus) when that action costs energy — see
   setCombatBtnLabel() in game.js. Small + dim so the label stays the focal
   point on the dense mobile HUD bar. */
.btn-cost { font-size:0.75em; opacity:0.8; }
/* Phase 11I — cooldown badge on a blocked tactical-action button. */
.btn-cd { font-size:0.75em; opacity:0.85; color:#f8607a; }
/* Phase 12 — a path-gated choice option (still clickable; server sasses). */
.choice-locked { opacity:0.55; }
#gameArea.in-combat .action-row--combat {
  box-shadow:0 0 0 1px rgba(248,96,122,0.35);
  background:rgba(248,96,122,0.05);
  border-radius:var(--radius-md);
  padding:6px;
}
#gameArea.in-combat .action-row--explore { opacity:0.55; }

/* =======================================================================
   COMMITTED ROUNDS — the 3-slot queue between the battle stage/combatant
   cards and the action bar (see #roundQueue in index.html). Hidden out of
   combat via inline display:none, toggled by toggleCombat() in game.js.
   ======================================================================= */
.round-queue {
  margin-top:12px;
  padding:10px 12px;
  background:linear-gradient(180deg, var(--surface-1), var(--surface-0));
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
}
.round-slots {
  display:flex;
  gap:8px;
  flex-wrap:wrap;
}
.round-slot {
  flex:1 1 120px;
  min-width:96px;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:2px;
  padding:8px 10px;
  background:var(--surface-2);
  border:1px dashed var(--border-strong);
  border-radius:var(--radius-md);
  color:var(--muted);
  cursor:pointer;
  text-align:left;
  font-family:inherit;
}
.round-slot:hover:not(:disabled) { border-color:var(--accent); }
.round-slot:disabled { cursor:default; opacity:0.6; }
.round-slot-num {
  font-size:var(--text-xs);
  font-weight:800;
  letter-spacing:.05em;
  color:var(--muted-2);
}
.round-slot-label { font-size:var(--text-sm); font-weight:700; color:var(--ink); overflow-wrap:anywhere; }
.round-slot-cost { font-size:var(--text-xs); color:var(--muted); }
.round-slot.filled {
  border-style:solid;
  border-color:var(--sky);
  background:var(--sky-soft);
}
.round-slot.filled .round-slot-label { color:var(--ink); }
.round-slot.round-slot-warn {
  border-color:var(--warning);
  background:var(--warning-soft);
}
.round-slot.round-slot-warn .round-slot-cost { color:#fcd34d; font-weight:700; }
.round-slot.round-slot-error {
  border-color:var(--danger);
  background:var(--danger-soft);
}
.round-slot.round-slot-error .round-slot-cost { color:var(--danger-fg); font-weight:700; }
.round-energy-forecast {
  margin-top:8px;
  font-size:var(--text-xs);
  color:var(--muted);
}
.round-queue-actions {
  display:flex;
  gap:8px;
  flex-wrap:wrap;
  margin-top:10px;
}
.round-commit-btn {
  flex:1 1 160px;
  font-weight:800;
  box-shadow:0 0 14px -2px rgba(240,180,41,0.55);
}
.round-commit-btn:disabled { box-shadow:none; }
#skipReplayBtn { flex:0 0 auto; }

@media (max-width:479px) {
  .round-slot { flex:1 1 100%; }
}

/* =======================================================================
   PHASE 15A — mobile enemy strip (#enemyStrip, markup right after .hud).
   This whole block is intentionally UNSCOPED: #enemyStrip is display:none
   at every width, and the only rule anywhere that flips it visible lives
   inside the @media (max-width:559px) #gameArea.in-combat block far below
   — so styling it here can never leak onto desktop or the out-of-combat
   mobile screen. Mirrored live by game.js (renderCombatPanel/updateEnemyHp/
   renderEnemyIntent) from the same data that drives the full combatant
   card; beat-tag chips reuse the existing .beat-chip/.beat-chip-* classes.
   ======================================================================= */
#enemyStrip {
  display:none;
  flex-direction:column;
  gap:4px;
  padding:6px 12px 8px;
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border-bottom:2px solid var(--danger);
  box-shadow:var(--shadow-sm);
}
.enemy-strip-top { display:flex; align-items:baseline; justify-content:space-between; gap:8px; }
.enemy-strip-name { font-weight:800; font-size:var(--text-sm); overflow-wrap:anywhere; }
.enemy-strip-hp-text { font-size:var(--text-xs); color:var(--muted); white-space:nowrap; }
.enemy-strip-bar { height:6px; }
.enemy-strip-intent { font-size:var(--text-xs); color:var(--muted); overflow-wrap:anywhere; }
.enemy-strip-intent:empty { display:none; }
#enemyStripTags { display:flex; gap:4px; flex-wrap:wrap; }
#enemyStripTags:empty { display:none; }

/* Enemy telegraph extras (Phase 10) — beat chips + pattern-hint flavor line,
   rendered alongside the existing beat-1 .enemy-intent line. */
.enemy-beat-tags {
  display:flex;
  gap:6px;
  flex-wrap:wrap;
  margin-top:6px;
}
.beat-chip {
  padding:3px 8px;
  border-radius:var(--radius-full);
  font-size:var(--text-xs);
  font-weight:700;
  background:rgba(255,255,255,0.06);
  color:var(--muted);
  border:1px solid var(--border-strong);
}
.beat-chip-heavy { background:var(--danger-soft); color:var(--danger-fg); border-color:rgba(248,96,122,0.4); }
.beat-chip-guard { background:var(--sky-soft); color:var(--sky); border-color:rgba(56,189,248,0.4); }
.beat-chip-tricky { background:var(--violet-soft); color:#c4b5fd; border-color:rgba(139,124,246,0.4); }
.beat-chip-strike { background:rgba(255,255,255,0.08); color:var(--ink); }
.beat-chip-signature { background:var(--accent-soft, rgba(240,180,41,0.14)); color:var(--accent); border-color:rgba(240,180,41,0.45); font-weight:800; }
.beat-chip-unknown { opacity:0.6; }
.enemy-pattern-hint {
  margin-top:6px;
  font-size:var(--text-xs);
  font-style:italic;
  color:var(--muted-2);
}

/* Log lines for round replay (game.js classifyLine()). */
.log-beat-header {
  margin-top:6px;
  color:var(--muted-2);
  font-weight:800;
  letter-spacing:.04em;
  text-transform:uppercase;
  font-size:var(--text-xs);
}
.log-warning { color:#fcd34d; font-weight:700; }

.guide-intro { margin-bottom:8px; line-height:1.4; }

/* Convergence-Ready CTA — persistent, hidden until gameState.convergence_unlocked
   (see updateUI()); a loud, glowing call to action for the endgame gate. */
.convergence-cta {
  display:block;
  width:100%;
  margin-top:10px;
  padding:12px;
  font-size:var(--text-md);
  background:linear-gradient(90deg, var(--accent), var(--violet));
  color:#1a1024;
  box-shadow:0 0 22px -2px rgba(167,139,250,0.65), 0 0 22px -4px rgba(240,180,41,0.5);
  animation:convergence-pulse 2.2s ease-in-out infinite;
}
@keyframes convergence-pulse {
  0%, 100% { filter:brightness(1); }
  50% { filter:brightness(1.18); }
}

/* =======================================================================
   Real modal overlays
   ======================================================================= */
.modal {
  position:fixed;
  inset:0;
  background:rgba(6,4,14,0.72);
  display:none;
  align-items:flex-start;
  justify-content:center;
  padding:24px 12px;
  z-index:100;
  overflow-y:auto;
}
.modal.is-open { display:flex; }
.modal-content {
  width:min(560px, 100%);
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border:1px solid var(--border-strong);
  border-radius:var(--radius-lg);
  padding:16px;
  box-shadow:var(--shadow-lg);
}
.modal-content--wide { width:min(780px, 100%); }
.modal-content.danger {
  border-color:var(--danger);
  box-shadow:0 0 34px -8px rgba(248,96,122,0.5), var(--shadow-lg);
}
.modal-close-row { display:flex; justify-content:flex-end; margin-top:14px; }
.modal-close-row--split { justify-content:space-between; align-items:center; }

/* ---- section label (Equipped / Bag / Run stats headers) ---- */
.section-label {
  text-transform:uppercase;
  letter-spacing:0.05em;
  font-size:var(--text-xs);
  color:var(--muted);
  font-weight:800;
  margin:12px 0 6px;
}
.section-label:first-child { margin-top:0; }

/* ---- inventory modal: tabs + paper-doll + item grid ---- */
.inv-topbar { display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:4px; }
.inv-gold { font-weight:700; color:var(--accent); font-size:var(--text-md); }
.inv-tabs { display:flex; gap:6px; flex-wrap:wrap; }
.inv-tab {
  background:var(--surface-0);
  color:var(--muted);
  border:1px solid var(--border);
  border-radius:var(--radius-full);
  padding:6px 12px;
  font-size:var(--text-sm);
  font-weight:700;
  cursor:pointer;
  font-family:inherit;
}
.inv-tab:hover { border-color:var(--accent); color:var(--ink); }
.inv-tab.active { background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }

.paperdoll {
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:8px;
  margin:10px 0;
}
@media (min-width:480px) { .paperdoll { grid-template-columns:repeat(4, minmax(0,1fr)); } }
.paperdoll-slot {
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:center;
  gap:4px;
  justify-content:center;
  min-height:110px;
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  padding:10px 8px;
}
.paperdoll-icon { font-size:1.4rem; opacity:0.85; }
.paperdoll-label { font-size:var(--text-xs); text-transform:uppercase; letter-spacing:0.05em; color:var(--muted); }
.paperdoll-name { font-size:var(--text-sm); font-weight:700; overflow-wrap:anywhere; }
.paperdoll-empty { color:var(--muted-2); }
.paperdoll-slot .btn { font-size:0.72rem; padding:4px 8px; margin-top:2px; }
.paperdoll-bonus { color:var(--muted); font-weight:600; }
.paperdoll-totals { color:var(--muted); margin:-2px 0 10px; text-align:right; }

.item-grid {
  display:grid;
  grid-template-columns:1fr;
  gap:10px;
  max-height:48vh;
  overflow-y:auto;
  padding-right:2px;
  margin-top:6px;
}
@media (min-width:480px) { .item-grid { grid-template-columns:repeat(2, 1fr); } }
@media (min-width:760px) { .item-grid { grid-template-columns:repeat(3, 1fr); } }
.inv-hint { grid-column:1 / -1; }

/* ---- empty states ---- */
.empty-state {
  grid-column:1 / -1;
  padding:18px 12px;
  text-align:center;
  color:var(--muted);
  font-size:var(--text-sm);
  border:1px dashed var(--border);
  border-radius:var(--radius-md);
  background:rgba(255,255,255,0.02);
}

/* ---- character sheet ---- */
.sheet { display:flex; flex-direction:column; gap:12px; margin-top:6px; }
.sheet-section {
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  padding:10px 12px;
}
.sheet-section-head { font-weight:800; font-size:var(--text-md); margin-bottom:4px; }
.stat-grid { display:grid; grid-template-columns:1fr; gap:2px 16px; }
@media (min-width:480px) { .stat-grid { grid-template-columns:repeat(2, 1fr); } }
.stat-line {
  display:flex;
  justify-content:space-between;
  gap:10px;
  padding:4px 0;
  border-bottom:1px dashed var(--border);
  font-size:var(--text-sm);
}
.stat-line:last-child { border-bottom:none; }
.stat-label { color:var(--muted); }
.stat-value { font-weight:700; }
.raw-stats-pre {
  white-space:pre-wrap;
  margin-top:12px;
  background:rgba(0,0,0,0.25);
  border-radius:var(--radius-md);
  padding:10px;
  font-size:var(--text-sm);
  max-height:40vh;
  overflow:auto;
}

/* ---- arena (Phase 10 — async PvP) ---- */
.arena-tab-panel { margin-top:10px; }
.arena-row {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  flex-wrap:wrap;
  padding:10px 12px;
  margin-bottom:8px;
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
}
.arena-row-info { display:flex; flex-direction:column; gap:2px; }
.arena-row-name { font-weight:700; }
.arena-row-meta { font-size:var(--text-xs); color:var(--muted); }
.arena-history-row {
  display:flex;
  justify-content:space-between;
  gap:8px;
  padding:6px 0;
  border-bottom:1px dashed var(--border);
  font-size:var(--text-sm);
}
.arena-history-row:last-child { border-bottom:none; }
.arena-result-win  { color:var(--success); font-weight:800; }
.arena-result-loss { color:var(--danger); font-weight:800; }
.arena-result-draw { color:var(--muted); font-weight:800; }

/* ---- deathmatch (Phase 14C) ---- */
.chip-dm {
  background:var(--danger-soft);
  color:var(--danger);
  font-weight:800;
  white-space:nowrap;
}
.dm-check-row {
  display:flex;
  align-items:center;
  gap:8px;
  margin-top:10px;
  font-size:var(--text-sm);
  cursor:pointer;
}
.dm-check-row input[type="checkbox"] { width:16px; height:16px; accent-color:var(--danger); cursor:pointer; }
.arena-dm-warning {
  margin-top:8px;
  padding:8px 10px;
  background:var(--danger-soft);
  border:1px solid var(--danger);
  border-radius:var(--radius-md);
  color:var(--danger);
  font-size:var(--text-sm);
  font-weight:700;
  line-height:1.4;
}
.btn-dm-confirm {
  animation:dm-pulse 1s ease-in-out infinite;
}
@keyframes dm-pulse {
  0%, 100% { box-shadow:0 0 0 0 rgba(248,96,122,0.55); }
  50% { box-shadow:0 0 0 6px rgba(248,96,122,0); }
}
@media (prefers-reduced-motion: reduce) {
  .btn-dm-confirm { animation:none; }
}
.arena-dm-banner {
  text-align:center;
  font-weight:800;
  letter-spacing:0.03em;
  color:var(--danger);
  background:var(--danger-soft);
  border:1px solid var(--danger);
  border-radius:var(--radius-md);
  padding:6px 10px;
  margin-bottom:10px;
}
.dm-outcome-banner {
  font-weight:700;
  font-size:var(--text-md);
  line-height:1.4;
  margin-bottom:8px;
}
.dm-outcome-win  { color:var(--success); }
.dm-outcome-loss { color:var(--danger); }
.dm-outcome-draw { color:var(--muted); }
.dm-outcome-deleted-note {
  margin-top:8px;
  padding:8px 10px;
  background:var(--danger-soft);
  border:1px solid var(--danger);
  border-radius:var(--radius-md);
  color:var(--danger);
  font-weight:700;
  font-size:var(--text-sm);
}

/* ---- arena ladder (Phase 14C — season leaderboard) ---- */
.arena-podium-strip { margin:6px 0 4px; }
.arena-podium-row { display:flex; gap:12px; flex-wrap:wrap; }
.arena-podium-item {
  background:var(--accent-soft);
  border:1px solid var(--border);
  border-radius:var(--radius-full);
  padding:4px 12px;
  font-size:var(--text-sm);
  font-weight:700;
  color:var(--accent);
}
.arena-ladder-table { width:100%; border-collapse:collapse; font-size:var(--text-sm); }
.arena-ladder-table th, .arena-ladder-table td {
  text-align:left;
  padding:6px 8px;
  border-bottom:1px solid var(--border);
}
.arena-ladder-table th {
  color:var(--muted);
  font-weight:700;
  font-size:var(--text-xs);
  text-transform:uppercase;
  letter-spacing:0.03em;
}
.arena-ladder-table tbody tr:last-child td { border-bottom:none; }
.arena-ladder-table tbody tr:hover { background:rgba(255,255,255,0.03); }

/* ---- transformation screen ---- */
.transform-grid { display:grid; grid-template-columns:1fr; gap:10px; margin-top:10px; }
@media (min-width:560px) { .transform-grid { grid-template-columns:repeat(2, 1fr); } }
.transform-card {
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  padding:10px 12px;
}
.transform-card.is-active { border-color:var(--violet); box-shadow:0 0 16px -4px rgba(139,124,246,0.6); }
.transform-card.is-locked { opacity:0.88; }
.transform-card-head { display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:4px; }
.transform-unlock-model {
  margin-top:8px;
  text-transform:uppercase;
  letter-spacing:0.04em;
  font-size:0.7rem;
  color:var(--muted);
}
.transform-conditions { display:flex; flex-direction:column; gap:6px; margin-top:6px; }
.transform-condition { font-size:var(--text-sm); color:var(--muted); }
.transform-condition.is-met { color:var(--success); }
.transform-condition-label { display:flex; align-items:center; gap:6px; }
.transform-condition-check { font-weight:800; }

/* ---- skill modal rows (unchanged behavior, re-skinned) ---- */
.skill-row {
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:8px;
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  margin-bottom:6px;
  background:rgba(255,255,255,0.02);
}
.skill-row .skill-meta { font-size:0.78rem; color:var(--muted); }
.skill-row.on-cd { opacity:0.55; }

/* ---- game over ---- */
.gameover-badge { font-size:2.4rem; line-height:1; margin-bottom:4px; }
.gameover-title { color:var(--danger); }
.gameover-cause { color:var(--danger-fg); margin-bottom:8px; font-size:var(--text-sm); }
.gameover-stats { background:rgba(0,0,0,0.25); border-radius:var(--radius-md); padding:10px 12px; }
.gameover-note { opacity:0.85; margin-top:8px; }

/* ---- run stage chip (HUD) — client-computed from level, see runStage() ---- */
.tag-stage { font-weight:800; }
.tag-stage[data-stage="awakening"]         { background:rgba(255,255,255,0.08); color:var(--muted); }
.tag-stage[data-stage="rift-war"]          { background:var(--portal-soft); color:var(--portal); }
.tag-stage[data-stage="final-convergence"] { background:var(--warning-soft); color:var(--warning); }
.tag-stage[data-stage="ascendant"] {
  background:linear-gradient(90deg, var(--accent-soft), var(--portal-soft));
  color:var(--accent);
  box-shadow:inset 0 0 0 1px rgba(240,180,41,0.4);
}

/* ---- convergence gate — the endgame event card + in-card choices ---- */
.event-card[data-type="endgame"] {
  --evt:var(--accent);
  /* translucent gold/portal tint layered OVER the normal opaque event-card
     gradient (not instead of it) — a bare translucent background would let
     whatever sits behind the card show through. */
  background:
    linear-gradient(180deg, rgba(217,70,239,0.16), rgba(240,180,41,0.08)),
    linear-gradient(180deg, var(--surface-1), var(--surface-0));
  border-left-width:5px;
  box-shadow:0 0 26px -6px var(--portal), var(--shadow-md);
}
.event-card[data-type="endgame"] .event-card-icon {
  filter:drop-shadow(0 0 8px var(--portal)) drop-shadow(0 0 4px var(--accent));
}
.convergence-meter {
  width:100%;
  font-weight:800;
  color:var(--portal);
}

/* ---- ascension (victory) — celebratory counterpart to game over ---- */
.modal-content.ascend {
  border-color:var(--accent);
  /* translucent gold/portal tint layered OVER the normal opaque
     modal-content gradient — see the endgame event-card comment above. */
  background:
    linear-gradient(180deg, rgba(240,180,41,0.18), rgba(217,70,239,0.12)),
    linear-gradient(180deg, var(--surface-2), var(--surface-1));
  box-shadow:0 0 40px -8px var(--portal), 0 0 24px -6px var(--accent), var(--shadow-lg);
}
.ascend-badge { font-size:2.4rem; line-height:1; margin-bottom:4px; filter:drop-shadow(0 0 8px var(--portal)); }
.ascend-title { color:var(--accent); }
.ascend-cause { color:var(--ink); margin-bottom:8px; font-size:var(--text-sm); }
.ascend-stats { background:rgba(0,0,0,0.25); border-radius:var(--radius-md); padding:10px 12px; }
.ascend-note { opacity:0.85; margin-top:8px; }
.ascend-legacy-highlight { border-top:1px dashed var(--border); margin-top:2px; padding-top:8px; }
.ascend-legacy-highlight .stat-value { color:var(--accent); font-size:var(--text-lg); font-weight:900; }

/* ---- run journal (Phase 11F) — reverse-chronological milestone timeline ---- */
.journal-list { display:flex; flex-direction:column; gap:6px; margin-top:8px; max-height:60vh; overflow-y:auto; }
.journal-row {
  display:flex;
  align-items:flex-start;
  gap:10px;
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  background:rgba(255,255,255,0.02);
}
.journal-row-icon { font-size:1.1rem; line-height:1.3; flex:0 0 auto; }
.journal-row-body { min-width:0; }
.journal-row-text { font-size:var(--text-sm); color:var(--ink); word-break:break-word; }
.journal-row-time { color:var(--muted); margin-top:2px; }

/* ---- milestone toast banner — fixed, one at a time, auto-fades ---- */
.journal-toast {
  position:fixed;
  top:16px;
  left:50%;
  transform:translateX(-50%) translateY(-14px);
  display:flex;
  align-items:center;
  gap:8px;
  max-width:min(420px, calc(100vw - 24px));
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border:1px solid var(--border-strong);
  border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg);
  padding:10px 14px;
  font-size:var(--text-sm);
  color:var(--ink);
  opacity:0;
  z-index:200;
  pointer-events:none;
  transition:opacity .3s ease, transform .3s ease;
}
.journal-toast.is-visible { opacity:1; transform:translateX(-50%) translateY(0); }
.journal-toast-icon { font-size:1.1rem; flex:0 0 auto; }
.journal-toast-text { min-width:0; }

/* =======================================================================
   SCREEN TRANSITIONS (Phase 13 UI polish) — subtle fade + slight rise used
   by animateScreenSwap() in game.js when swapping #setup <-> #gameArea.
   Kept as a one-shot enter animation (classes are removed once it finishes)
   so it never fights with the elements' normal display toggling.
   ======================================================================= */
.screen-swap { opacity:0; transform:translateY(6px); }
.screen-swap-in {
  opacity:1;
  transform:translateY(0);
  transition:opacity 200ms ease, transform 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .screen-swap, .screen-swap-in { opacity:1; transform:none; transition:none; }
}

/* =======================================================================
   PROFILE (Phase 13B) — identity card, run card, journal timeline. Reuses
   .sheet-section / .stat-line / .tag for consistency with the character
   sheet; only the layout wrappers below are new. All content is built via
   textContent/createElement in static/social.js (handles + journal text
   are untrusted).
   ======================================================================= */
.profile-identity {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  flex-wrap:wrap;
}
.profile-handle { font-size:var(--text-xl); font-weight:900; color:var(--ink); }
.profile-arena-record { display:flex; flex-direction:column; align-items:flex-end; gap:2px; }
.profile-arena-rating { font-weight:800; color:var(--accent); font-size:var(--text-md); }
.profile-chip-row { display:flex; gap:6px; flex-wrap:wrap; margin-top:6px; }
.profile-empty { padding:14px; text-align:center; color:var(--muted); font-size:var(--text-sm); }
.profile-journal { display:flex; flex-direction:column; gap:6px; margin-top:8px; max-height:40vh; overflow-y:auto; }
.profile-journal-row {
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  background:rgba(255,255,255,0.02);
  font-size:var(--text-sm);
}
.profile-journal-time { color:var(--muted); font-size:var(--text-xs); margin-top:2px; }
.tag-ascended { background:var(--warning-soft); color:var(--warning); font-weight:800; }
.tag-flag { background:var(--violet-soft); color:#c4b5fd; }

/* ---- Hall of Souls (Phase 15E) — deathmatch trophy rows on the profile,
   reusing .profile-journal-row with a legendary-rarity accent stripe. ---- */
.trophy-row { border-left:3px solid var(--r-legendary); }
.trophy-record { margin-top:6px; }

/* =======================================================================
   WORLD CODEX (Phase 15E) — per-world sections (Forms/Abilities/Bosses)
   plus a run-wide Quests list. Reuses .sheet-section/.section-label/.small
   for consistency with the character sheet and profile; only the row/grid
   wrappers below are new. All content is built via textContent/
   createElement in static/codex.js — theme/user text is untrusted.
   ======================================================================= */
.codex-world { margin-bottom:14px; }
.codex-world:last-child { margin-bottom:0; }
.codex-grid { display:flex; flex-direction:column; gap:6px; margin-bottom:10px; }
.codex-empty { padding:6px 2px; }
.codex-row {
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  background:rgba(255,255,255,0.02);
  font-size:var(--text-sm);
}
.codex-row-head { display:flex; align-items:center; gap:8px; }
.codex-row-icon { font-size:1rem; line-height:1; }
.codex-row-name { font-weight:700; }
.codex-row-note { color:var(--muted); margin-top:4px; }

.codex-form-row.is-unlocked .codex-row-name,
.codex-ability-row.is-eaten .codex-row-name,
.codex-ability-row.is-owned .codex-row-name,
.codex-boss-row.is-defeated .codex-row-name { color:var(--success); }
.codex-form-row.is-blocked { border-color:var(--danger-soft); }
.codex-form-row.is-blocked .codex-row-name { color:var(--danger-fg); }
.codex-form-row.is-locked .codex-row-name,
.codex-ability-row.is-locked .codex-row-name { color:var(--muted); }
.codex-ability-row.is-undiscovered .codex-row-name { color:var(--muted-2); font-style:italic; }
.codex-boss-row.is-defeated .codex-row-name { text-decoration:line-through; }
.codex-quest-row.is-done .codex-row-name { color:var(--success); }

/* =======================================================================
   CHAT DRAWER (Phase 13B) — right-side slide-in panel (global chat + DMs).
   Distinct from the centered `.modal` pattern: fixed to the viewport edge,
   full height, slides in via transform (not display) so the transition is
   real. Owned by static/social.js.
   ======================================================================= */
.chat-drawer {
  position:fixed;
  top:0; right:0; bottom:0;
  width:340px;
  max-width:100vw;
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border-left:1px solid var(--border-strong);
  box-shadow:var(--shadow-lg);
  z-index:150;
  display:flex;
  flex-direction:column;
  gap:8px;
  padding:12px;
  transform:translateX(100%);
  transition:transform 220ms ease;
  visibility:hidden;
}
.chat-drawer.is-open { transform:translateX(0); visibility:visible; }
@media (prefers-reduced-motion: reduce) {
  .chat-drawer { transition:none; }
}
@media (max-width:480px) {
  .chat-drawer { width:100vw; }
}
.chat-drawer-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.chat-drawer-close { padding:4px 10px; }
.chat-tab-panel { display:flex; flex-direction:column; flex:1 1 auto; min-height:0; gap:8px; }
.chat-messages {
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  display:flex;
  flex-direction:column;
  gap:6px;
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  padding:8px;
}
.chat-msg { font-size:var(--text-sm); line-height:1.35; padding:3px 4px; border-radius:var(--radius-sm); word-break:break-word; }
.chat-msg.own { background:var(--accent-soft); }
.chat-msg-handle { font-weight:800; cursor:pointer; }
.chat-msg-handle:hover { text-decoration:underline; }
.chat-msg-time { color:var(--muted); font-size:var(--text-xs); margin-left:6px; }
.chat-msg-text { display:block; }

/* ---- system announcements (Phase 14C — "⚔ Arena" engine messages) ---- */
.chat-msg.chat-system {
  background:var(--warning-soft);
  border-left:2px solid var(--warning);
}
.chat-system-handle { color:var(--warning) !important; cursor:default; font-weight:800; }
.chat-msg.chat-system .chat-msg-text { font-style:italic; color:var(--warning); }
.chat-input-row { display:flex; gap:6px; }
.chat-input-row input { flex:1 1 auto; min-width:0; }
.chat-inline-error { color:var(--danger); min-height:1em; }
.chat-empty { padding:14px; text-align:center; color:var(--muted); font-size:var(--text-sm); }

.chat-newdm-row { display:flex; gap:6px; }
.chat-thread-list { display:flex; flex-direction:column; gap:6px; overflow-y:auto; margin-top:8px; }
.chat-thread-row {
  display:flex;
  flex-direction:column;
  gap:2px;
  padding:8px 10px;
  background:var(--surface-0);
  border:1px solid var(--border);
  border-radius:var(--radius-md);
  cursor:pointer;
  text-align:left;
}
.chat-thread-row:hover { border-color:var(--accent); }
.chat-thread-row-top { display:flex; justify-content:space-between; gap:8px; }
.chat-thread-handle { font-weight:800; }
.chat-thread-time { color:var(--muted); font-size:var(--text-xs); }
.chat-thread-preview { color:var(--muted); font-size:var(--text-sm); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.chat-dm-head { display:flex; align-items:center; gap:8px; margin-bottom:2px; }
#chatDmThreads, #chatDmConversation { display:flex; flex-direction:column; flex:1 1 auto; min-height:0; gap:8px; }

/* ---- unread chat badge (Phase 14D) — on the 💬 Chat toggle buttons ---- */
.chat-toggle-btn { position:relative; }
.chat-unread-badge {
  position:absolute;
  top:-6px;
  right:-6px;
  min-width:16px;
  height:16px;
  padding:0 4px;
  border-radius:var(--radius-full);
  background:var(--danger);
  color:#2a0a10;
  font-size:0.65rem;
  font-weight:900;
  line-height:16px;
  text-align:center;
  box-shadow:0 0 0 2px var(--surface-1);
}

/* ---- arena ladder clickable handles (Phase 14D) ---- */
.arena-ladder-handle { cursor:pointer; }
.arena-ladder-handle:hover { text-decoration:underline; color:var(--accent); }

/* ---- public handle profile popover (Phase 14D) — reuses .modal-content;
   this just spaces the stat rows built in static/social.js. ---- */
.handle-profile-body { display:flex; flex-direction:column; gap:6px; margin-top:8px; }
.handle-profile-body .profile-arena-rating { font-weight:800; color:var(--accent); font-size:var(--text-md); }

/* =======================================================================
   FIRST-FIGHT TUTORIAL (Phase 14D) — a lightweight coachmark sequence
   (3-4 cards) shown once via #tutorialModal (reuses .modal). No library;
   just a modal whose body swaps text per step (static/game.js).
   ======================================================================= */
.tutorial-modal-content { width:min(420px, 100%); }
.tutorial-dots { display:flex; gap:6px; justify-content:center; margin-bottom:10px; }
.tutorial-dot {
  width:7px; height:7px;
  border-radius:var(--radius-full);
  background:rgba(255,255,255,0.18);
}
.tutorial-dot.is-current { background:var(--accent); }

/* =======================================================================
   UNLOCK TOASTS (Phase 14D) — transient top-right stack, distinct from the
   top-center milestone banner (.journal-toast) above. Drained pending_unlock
   text (combat log lines / next_event's "⭐" summary) is classified and
   shown here in ADDITION to its existing inline rendering — never replaces
   it. See classifyUnlockLine()/maybeToastUnlock() in static/game.js.
   ======================================================================= */
.unlock-toast-stack {
  position:fixed;
  top:16px;
  right:16px;
  z-index:200;
  display:flex;
  flex-direction:column;
  gap:8px;
  max-width:min(340px, calc(100vw - 24px));
  pointer-events:none;
}
.unlock-toast {
  display:flex;
  align-items:flex-start;
  gap:8px;
  background:linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border:1px solid var(--border-strong);
  border-left:3px solid var(--accent);
  border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg);
  padding:9px 12px;
  font-size:var(--text-sm);
  color:var(--ink);
  opacity:0;
  transform:translateX(16px);
  transition:opacity .25s ease, transform .25s ease;
}
.unlock-toast.is-visible { opacity:1; transform:translateX(0); }
.unlock-toast-icon { font-size:1.05rem; flex:0 0 auto; }
.unlock-toast-text { min-width:0; word-break:break-word; }
.unlock-toast-form      { border-left-color:var(--accent); }
.unlock-toast-technique { border-left-color:var(--violet); }
.unlock-toast-quest     { border-left-color:var(--teal); }
.unlock-toast-lost      { border-left-color:var(--danger); }

/* =======================================================================
   PHASE 15A — MOBILE COMBAT LAYOUT (game-style bottom dock)

   Every rule in this block is double-scoped: inside
   @media (max-width:559px) AND under #gameArea.in-combat (the class
   toggleCombat() sets in static/game.js). Desktop is untouched (desktop
   opts into its own layouts via the existing min-width:560px queries
   above), and mobile OUT of combat is untouched too (no .in-combat class
   -> none of this applies) — only the mobile in-combat screen becomes the
   fixed, non-scrolling app shell described below.

   Shell: #gameArea.in-combat becomes a fixed-height flex column
   (top HUD -> enemy strip -> scrolling combat middle -> slim log ->
   bottom dock). flex `order` reflows the DOM's actual child order (hud,
   enemyStrip, combatPanel, roundQueue, eventCard, actionBar, convergeBtn,
   log-panel) into that visual stack; #eventCard is hidden outright since
   combat doesn't use it.
   ======================================================================= */
@media (max-width:559px) {
  #gameArea.in-combat {
    display:flex;
    flex-direction:column;
    height:100vh;
    height:100dvh;
    overflow:hidden;
  }

  /* ---- top (fixed): compact HUD — hide the 9 nav buttons, tighten
     padding, drop sticky (the shell itself doesn't scroll) ---- */
  #gameArea.in-combat .hud {
    order:1;
    flex:0 0 auto;
    position:static;
    padding:8px 10px;
    gap:6px;
    margin-bottom:0;
    border-radius:0;
  }
  #gameArea.in-combat .hud-actions { display:none; }

  /* ---- enemy strip (fixed, right under the HUD) ---- */
  #gameArea.in-combat #enemyStrip {
    order:2;
    flex:0 0 auto;
    display:flex;
  }

  /* ---- middle (scrolls): battle stage + both combatant cards ---- */
  #gameArea.in-combat #combatPanel {
    order:3;
    flex:1 1 auto;
    min-height:0;
    overflow-y:auto;
    margin-top:6px;
  }
  #gameArea.in-combat .battle-stage { min-height:96px; }

  /* ---- combat doesn't use the event card — hide it outright ---- */
  #gameArea.in-combat #eventCard { display:none; }

  /* ---- slim log strip, between the scrolling middle and the dock ---- */
  #gameArea.in-combat .log-panel {
    order:4;
    flex:0 0 auto;
    margin-top:6px;
  }
  #gameArea.in-combat .log-panel .log-panel-body {
    max-height:72px;
    font-size:0.82rem;
  }

  /* ---- bottom dock (fixed): round queue + action bar, stacked, with a
     top border + slightly elevated background ---- */
  #gameArea.in-combat #roundQueue {
    order:5;
    flex:0 0 auto;
    margin-top:0;
    border-top:1px solid var(--border-strong);
    border-radius:0;
    background:rgba(15,23,42,0.92);
    backdrop-filter:blur(6px);
    padding:8px 10px;
  }
  /* single 3-across row in the dock — overrides the ≤479px full-width
     stack (that rule and this one share specificity; this one wins by
     appearing later and only applying in-combat) */
  #gameArea.in-combat .round-slots { flex-wrap:nowrap; }
  #gameArea.in-combat .round-slot {
    flex:1 1 0;
    min-width:0;
    min-height:44px;   /* 44px touch-target minimum on mobile */
    padding:6px 6px;
  }
  #gameArea.in-combat .round-slot-label { font-size:0.72rem; }
  #gameArea.in-combat .round-queue-actions { margin-top:6px; }

  #gameArea.in-combat #actionBar {
    order:6;
    flex:0 0 auto;
    margin-top:0;
    gap:6px;
    padding:6px 10px 8px;
    border-top:1px solid var(--border-strong);
    background:rgba(15,23,42,0.92);
    backdrop-filter:blur(6px);
  }
  /* explore row (Next Event / Heal) is inert mid-combat — hide it here to
     keep the dock inside its height budget; unaffected out of combat and
     on desktop, where it stays visible-but-dimmed as before */
  #gameArea.in-combat .action-row--explore { display:none; }
  #gameArea.in-combat .action-row--combat {
    grid-template-columns:repeat(auto-fit, minmax(64px, 1fr));
    gap:6px;
  }
  #gameArea.in-combat .action-row--combat .btn {
    font-size:0.72rem;
    min-height:44px;   /* 44px touch-target minimum on mobile */
    padding:6px 4px;
  }

  /* convergence CTA rides the bottom of the dock when it's shown */
  #gameArea.in-combat #convergeBtn {
    order:7;
    flex:0 0 auto;
    margin:6px 10px 8px;
  }
}

/* =======================================================================
   Phase 15C — transform power-up burst: a brief warm glow on the whole
   battle stage when a form activates mid-fight. Motion-safe (opacity
   only) but still gated behind the JS REDUCED_MOTION check.
   ======================================================================= */
.battle-stage.stage-transform-glow {
  box-shadow: inset 0 0 42px rgba(255, 200, 80, 0.35);
  transition: box-shadow 0.15s ease-in;
}

/* =======================================================================
   Phase 16 — Theme Studio (public, login-gated design chat). Reuses the
   Phase 11 chat-log/chat-msg bubble styles; these are just the two-pane
   layout + drafts list + status badges.
   ======================================================================= */
.studio-layout { display:flex; flex-direction:column; gap:12px; margin-top:10px; }
@media (min-width: 700px) {
  .studio-layout { display:grid; grid-template-columns: 220px 1fr; align-items:start; }
}
.studio-sidebar { display:flex; flex-direction:column; gap:8px; min-width:0; }
.studio-main { min-width:0; }
.studio-draft-list {
  display:flex; flex-direction:column; gap:4px;
  max-height:240px; overflow-y:auto;
}
.studio-draft-item {
  padding:6px 8px; border-radius:8px; cursor:pointer;
  border:1px solid rgba(255,255,255,0.07);
  background:rgba(255,255,255,0.03);
}
.studio-draft-item:hover { background:rgba(255,255,255,0.07); }
.studio-draft-item.active {
  border-color: var(--accent, #7aa2f7);
  background:rgba(122,162,247,0.10);
}
.studio-draft-item-title {
  font-weight:700; font-size:var(--text-sm);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.studio-draft-item-meta { display:flex; gap:6px; align-items:center; }
.tag.studio-status-ready { background:rgba(74,222,128,0.14); color:#4ade80; }
.tag.studio-status-thinking { background:rgba(250,204,21,0.14); color:#facc15; }
.tag.studio-status-error { background:rgba(248,113,113,0.16); color:#f87171; }
.studio-json-toggle { font-size:var(--text-xs); padding:2px 8px; }
