/* =========================================
   Quack Stack — game styles
   =========================================
   Loaded via public/index.html alongside main.css.
   Game-namespaced via .qs-* (single-player) and .qsm-* (multi) prefixes
   so it can't collide with main.css or battle-ducks.css.

   Tokens live in body { ... } so they cascade through everything and
   themes can be swapped at runtime via body.qs-theme-X class.
   (Carved out of main.css in J.2 of the visual refactor.)
   ========================================= */

/* ── DESIGN TOKENS — DEFAULT THEME IS CREAM (set in J.7) ─────────────────
   Tokens defined on `body` so they cascade through both #page-quack-stack
   and any dev-only QS surfaces. Theme switching is done by toggling a
   `qs-theme-*` class on `body`; the overrides at the bottom of this file
   define the alternate themes (dark, light).

   Reverting to dark in production: add class="qs-theme-dark" to the body
   in public/index.html. One-line change.

   Sections:
     STRUCTURE     — type / spacing / radius / shadow scales
     SEMANTIC      — surface / text / border roles
     ACCENT        — platform brand passthrough
     STATUS        — bleed / rage / shield / damage / heal / sandstorm
     RARITY        — common → legendary
     CARD TYPE     — bash / quick / guard / relic / trinket
     FLOATER TIERS — H.5 magnitude tier colors
     LEGACY        — original --qs-* names kept for migration; J.4 sweep
                     will gradually retire these in favor of semantic names
   ────────────────────────────────────────────────────────────────────── */
body {

  /* ─── STRUCTURE ─── */
  /* Type scale (rem). Borrowed from BD's validated scale where matching. */
  --qs-text-xs:   0.7rem;
  --qs-text-sm:   0.8rem;
  --qs-text-base: 0.9rem;
  --qs-text-md:   1.0rem;
  --qs-text-lg:   1.1rem;
  --qs-text-xl:   1.3rem;
  --qs-text-2xl:  1.6rem;
  --qs-text-3xl:  2.0rem;

  /* Spacing scale (px, 4px base). */
  --qs-space-1: 4px;
  --qs-space-2: 8px;
  --qs-space-3: 12px;
  --qs-space-4: 16px;
  --qs-space-5: 24px;
  --qs-space-6: 32px;
  --qs-space-7: 48px;
  --qs-space-8: 64px;

  /* Radius scale. */
  --qs-radius-sm:   4px;
  --qs-radius-md:   8px;
  --qs-radius-lg:   12px;
  --qs-radius-xl:   16px;
  --qs-radius-pill: 999px;

  /* Shadow scale. */
  --qs-shadow-card:  0 2px 6px rgba(0,0,0,0.4);
  --qs-shadow-panel: 0 4px 12px rgba(0,0,0,0.5);
  --qs-shadow-float: 0 6px 20px rgba(0,0,0,0.6);

  /* ─── SEMANTIC SURFACE & TEXT ───
     DEFAULT = CREAM (warm parchment), set per J.7. Dark + light values
     live in `body.qs-theme-*` overrides below. To revert to dark in
     production, add class="qs-theme-dark" to body in public/index.html
     (one-line change). */
  --qs-surface-page:   #f6efe4;
  --qs-surface-panel:  #fbf6ec;
  --qs-surface-card:   #fff9ee;
  --qs-surface-raised: #fff4dc;

  --qs-text-primary:   #2d2419;
  --qs-text-secondary: #5b4f3c;
  --qs-text-muted:     #948775;

  --qs-border-subtle:  #ece3d0;
  --qs-border-normal:  #d9ccb1;
  --qs-border-strong:  #b6a583;

  /* ─── ACCENT ───
     Cream needs a darker accent than the platform gold (#FFD700) — bright
     gold against warm parchment fails WCAG (1.4:1). Use a deeper amber
     that hits ~3:1 for large headings. Button-bg uses --qs-accent-bg
     (which stays as the bright gold for buttons that have dark text on
     them — that combination has good contrast). */
  --qs-accent:         #b88736;
  --qs-accent-hover:   #9c7126;
  --qs-accent-bg:      var(--qw-gold);
  --qs-accent-bg-hover: var(--qw-gold-hover);

  /* ─── STATUS COLORS (cream-tuned) ─── */
  --qs-bleed:          #a72020;
  --qs-bleed-soft:     rgba(167, 32, 32, 0.40);
  --qs-rage:           #d9660a;
  --qs-rage-soft:      rgba(213, 74, 24, 0.40);
  /* M.3 — Cleric: regen = emerald (distinct from heal's lighter green);
     conviction = gold (distinct from rage's orange). */
  --qs-regen:          #0f7a4f;
  --qs-regen-soft:     rgba(15, 122, 79, 0.40);
  --qs-conviction:     #d99a00;
  --qs-conviction-soft: rgba(217, 154, 0, 0.40);
  --qs-shield:         #1e8fbf;
  --qs-damage:         #c83838;
  --qs-heal:           #2d9d4f;
  --qs-sandstorm:      #b88736;
  --qs-bleed-mult:     #962b56;

  /* ─── ON-FILL FOREGROUNDS (J.14) ───
     Text/numbers that sit ON a saturated status fill (the stat chips that
     overhang card tops) need a foreground chosen per-fill, not the body text
     color. A single shared color can't clear WCAG AA across themes because
     fills vary in lightness: a mid-tone blue wants white in one theme and
     near-black in another. Each is hand-picked so chip text >= 4.5:1.
     Cream fills: damage is dark enough for white; shield/heal are light
     enough to want near-black. (See j14-onfill-contrast.test.js.) */
  --qs-on-damage:      #ffffff;
  --qs-on-shield:      #15120c;
  --qs-on-heal:        #15120c;
  --qs-on-bleed:       #ffffff; /* white clears AA on the crimson --qs-bleed fill */
  --qs-on-rage:        #15120c; /* dark text on the orange cream --qs-rage */
  --qs-on-regen:       #ffffff; /* white clears AA on the emerald --qs-regen */
  --qs-on-conviction:  #15120c; /* dark text on the gold --qs-conviction */

  /* ─── RARITY COLORS (cream-tuned) ─── */
  --qs-rarity-common:    #8a8170;
  --qs-rarity-uncommon:  #2d9d4f;
  --qs-rarity-rare:      #3460c8;
  --qs-rarity-epic:      #8b3fcb;
  --qs-rarity-legendary: #c8851e;

  /* ─── CARD TYPE ACCENTS (cream-tuned) ─── */
  --qs-type-bash:    #b8472a;
  --qs-type-quick:   #1ea38e;
  --qs-type-guard:   #2868c8;
  --qs-type-relic:   #7a3ab8;
  --qs-type-trinket: #a6541c;
  --qs-type-item:    #a6541c;     /* legacy: was "item" pre-type-split */

  /* ─── FLOATER TIER COLORS (H.5 magnitude escalation, cream-tuned) ─── */
  --qs-floater-tier-0: #2d2419;                   /* warm ink baseline */
  --qs-floater-tier-1: #c89a1c;                   /* gold */
  --qs-floater-tier-2: #d97228;                   /* orange */
  --qs-floater-tier-3: #c83838;                   /* red */
  --qs-floater-tier-4: #960c0c;                   /* deep red */
  --qs-floater-glow-1: rgba(255, 200, 60, 0.55);
  --qs-floater-glow-2: rgba(255, 140, 40, 0.65);
  --qs-floater-glow-3: rgba(255, 70, 40, 0.75);
  --qs-floater-glow-4: rgba(160, 10, 10, 0.85);

  /* Shadows — softer, warmer than the dark theme */
  --qs-shadow-card:  0 2px 6px rgba(80, 50, 20, 0.15);
  --qs-shadow-panel: 0 4px 12px rgba(80, 50, 20, 0.20);
  --qs-shadow-float: 0 6px 20px rgba(80, 50, 20, 0.30);

  /* ─── TEXT-SHADOW TOKENS ───
     Cream needs no text-shadow on the surface (dark text on light bg
     reads cleanly). On colored bars (HP/shield fill), a soft white halo
     lifts dark text. Floaters get a 4-direction black outline (matches
     the platform --qw-brand-stroke pattern). Themes override these. */
  --qs-text-shadow-on-surface: none;
  --qs-text-shadow-on-color:
    -1px -1px 0 rgba(255,255,255,0.6),
     1px -1px 0 rgba(255,255,255,0.6),
    -1px  1px 0 rgba(255,255,255,0.6),
     1px  1px 0 rgba(255,255,255,0.6);
  --qs-floater-outline:
    -1px -1px 0 #000,
     1px -1px 0 #000,
    -1px  1px 0 #000,
     1px  1px 0 #000;

  /* ─── HP / SHIELD BAR FILL GRADIENTS ───
     Cream tones the fills warmer/less neon so they read natural on
     parchment. Dark theme restores the vibrant originals. */
  --qs-hp-fill:     linear-gradient(90deg, #2d9d4f, #5fb567);
  --qs-shield-fill: linear-gradient(90deg, var(--qs-shield), #6fa7d6);

  /* ─── LEGACY token names (kept for backward compat during J.4 sweep) ─── */
  --qs-bg-page:     var(--qs-surface-page);
  --qs-bg-surface:  var(--qs-surface-card);
  --qs-bg-hud:      var(--qs-surface-panel);
  --qs-border:      var(--qs-border-normal);
  --qs-text:        var(--qs-text-primary);
  /* --qs-text-muted is now in SEMANTIC, mapping to #888 instead of #ccc.
     Old #ccc users get --qs-text-secondary; cleanup happens in J.4. */
  --qs-stat-shield: var(--qs-shield);
  --qs-stat-attack: var(--qs-damage);
  --qs-stat-heal:   var(--qs-heal);
  /* --qs-stat-spd / --qs-stat-crit (reserved, never used) — dropped */
}

/* ── THEME OVERRIDES ─────────────────────────────────────────────────
   Theme variants live as `body.qs-theme-*` overrides. Toggled either
   via /dev/qs-tokens (preview) or by setting the class on the live game
   page. **Default (no class) = cream** (J.7 decision).

   Tuning rationale:
     - Cream: warm parchment, brand-aligned with the duck identity. Keeps
       headroom for glow effects (rarity, status). Slightly darker text
       than pure-light for contrast against warm bg.
     - Dark: the v0.13 baseline, kept available as `body.qs-theme-dark`.
     - Light: pure platform-cohesion play. Crisper but less brand-warm.
   Floater tier colors are re-tuned per theme — white→yellow→orange→red
   doesn't work on a light bg, so cream/light push the tier-0 to a soft
   ink color and let yellow take over from tier-1.
   ─────────────────────────────────────────────────────────────────── */

/* Cream is the DEFAULT theme (set in body{} above). The named class
   `qs-theme-cream` is kept as a no-op for completeness — switching to
   cream from another theme just removes the other class. */
body.qs-theme-cream {
  /* No overrides — cream values are the default in body{}. */
}

/* Dark theme — the previous default, now an explicit opt-in. */
body.qs-theme-dark {
  /* Accent — restore platform gold passthrough; gold-on-dark has good contrast */
  --qs-accent:         var(--qw-gold);
  --qs-accent-hover:   var(--qw-gold-hover);
  /* --qs-accent-bg stays var(--qw-gold) — buttons need bright fill */
  /* Surface */
  --qs-surface-page:   #16161f;
  --qs-surface-panel:  #1e1e2a;
  --qs-surface-card:   #2c2c3a;
  --qs-surface-raised: #34344a;
  /* Text */
  --qs-text-primary:   #eeeeee;
  --qs-text-secondary: #cccccc;
  --qs-text-muted:     #888888;
  /* Border */
  --qs-border-subtle:  #2e2e3e;
  --qs-border-normal:  #444444;
  --qs-border-strong:  #666666;
  /* Status */
  --qs-bleed:          #c84030;
  --qs-bleed-soft:     rgba(200, 40, 40, 0.55);
  --qs-rage:           #ff8a33;
  --qs-rage-soft:      rgba(255, 106, 58, 0.55);
  /* M.3 — Cleric regen (bright emerald, dark text) + conviction (bright gold). */
  --qs-regen:          #2ecc8f;
  --qs-regen-soft:     rgba(46, 204, 143, 0.55);
  --qs-conviction:     #f0b51f;
  --qs-conviction-soft: rgba(240, 181, 31, 0.55);
  --qs-shield:         #6cdfff;
  --qs-damage:         #ff6a6a;
  --qs-heal:           #6cff6a;
  --qs-sandstorm:      #d4a04d;
  --qs-bleed-mult:     #b03060;
  /* On-fill foregrounds — dark theme's status fills are bright neons
     (cyan shield, light-red damage, light-green heal), so all three chips
     want near-black text. */
  --qs-on-damage:      #15120c;
  --qs-on-shield:      #15120c;
  --qs-on-heal:        #15120c;
  --qs-on-bleed:       #ffffff; /* white clears AA on the brighter dark-theme --qs-bleed */
  --qs-on-rage:        #15120c; /* near-black: the bright dark-theme --qs-rage needs dark text */
  --qs-on-regen:       #08130d; /* near-black on the bright dark-theme --qs-regen */
  --qs-on-conviction:  #15120c; /* near-black on the bright dark-theme --qs-conviction */
  /* Rarity */
  --qs-rarity-common:    #b0b0b0;
  --qs-rarity-uncommon:  #5cdb5c;
  --qs-rarity-rare:      #5c8cff;
  --qs-rarity-epic:      #d4a8ff;
  --qs-rarity-legendary: #ffb84d;
  /* Card type */
  --qs-type-bash:    #d96a4a;
  --qs-type-quick:   #4ad9b6;
  --qs-type-guard:   #4a8ad9;
  --qs-type-relic:   #9b6cd6;
  --qs-type-trinket: #c97a3a;
  --qs-type-item:    #c97a3a;
  /* Floater tiers — white baseline, yellow→orange→red escalation */
  --qs-floater-tier-0: #eeeeee;
  --qs-floater-tier-1: #ffeb6c;
  --qs-floater-tier-2: #ffa552;
  --qs-floater-tier-3: #ff6a3a;
  --qs-floater-tier-4: #ff2d2d;
  --qs-floater-glow-1: rgba(255, 220, 100, 0.4);
  --qs-floater-glow-2: rgba(255, 160, 60, 0.55);
  --qs-floater-glow-3: rgba(255, 100, 50, 0.7);
  --qs-floater-glow-4: rgba(255, 60, 40, 0.85);
  /* Shadows */
  --qs-shadow-card:  0 2px 6px rgba(0,0,0,0.4);
  --qs-shadow-panel: 0 4px 12px rgba(0,0,0,0.5);
  --qs-shadow-float: 0 6px 20px rgba(0,0,0,0.6);
  /* Text shadows — restore the H.5 / dark-theme behavior */
  --qs-text-shadow-on-surface: 0 1px 2px rgba(0, 0, 0, 0.85);
  --qs-text-shadow-on-color:   0 1px 2px rgba(0, 0, 0, 0.85);
  --qs-floater-outline: 0 2px 4px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.6);
  /* Bar fills — vibrant on dark */
  --qs-hp-fill:     linear-gradient(90deg, #5cb85c, #8bd17c);
  --qs-shield-fill: linear-gradient(90deg, var(--qs-shield), #6c9bff);
}

body.qs-theme-light {
  /* Accent — same problem as cream: bright gold fails on white. Use a
     deeper amber for headings/text. */
  --qs-accent:         #b8860b;
  --qs-accent-hover:   #9c7308;
  /* Surface */
  --qs-surface-page:   #f1f3f5;
  --qs-surface-panel:  #ffffff;
  --qs-surface-card:   #ffffff;
  --qs-surface-raised: #f8f9fa;
  /* Text */
  --qs-text-primary:   #1a1a1a;
  --qs-text-secondary: #495057;
  --qs-text-muted:     #868e96;
  /* Border */
  --qs-border-subtle:  #e9ecef;
  --qs-border-normal:  #ced4da;
  --qs-border-strong:  #adb5bd;
  /* Status — saturated for pop on white */
  --qs-bleed:          #b71c1c;
  --qs-bleed-soft:     rgba(183, 28, 28, 0.40);
  --qs-rage:           #e06600;
  --qs-rage-soft:      rgba(230, 74, 25, 0.40);
  /* M.3 — Cleric regen (emerald) + conviction (gold). */
  --qs-regen:          #0e7a4d;
  --qs-regen-soft:     rgba(14, 122, 77, 0.40);
  --qs-conviction:     #d99400;
  --qs-conviction-soft: rgba(217, 148, 0, 0.40);
  --qs-shield:         #0277bd;
  --qs-damage:         #c62828;
  --qs-heal:           #2e7d32;
  --qs-sandstorm:      #c19a47;
  --qs-bleed-mult:     #ad1457;
  /* On-fill foregrounds — light theme's status fills are saturated mid-tones
     dark enough that white text clears AA on all three. */
  --qs-on-damage:      #ffffff;
  --qs-on-shield:      #ffffff;
  --qs-on-heal:        #ffffff;
  --qs-on-bleed:       #ffffff; /* white clears AA on the light-theme --qs-bleed */
  --qs-on-rage:        #15120c; /* near-black: the light-theme --qs-rage needs dark text */
  --qs-on-regen:       #ffffff; /* white clears AA on the emerald light-theme --qs-regen */
  --qs-on-conviction:  #15120c; /* near-black on the gold light-theme --qs-conviction */
  /* Rarity */
  --qs-rarity-common:    #757575;
  --qs-rarity-uncommon:  #2e7d32;
  --qs-rarity-rare:      #1565c0;
  --qs-rarity-epic:      #6a1b9a;
  --qs-rarity-legendary: #f57c00;
  /* Card type */
  --qs-type-bash:    #d84315;
  --qs-type-quick:   #00897b;
  --qs-type-guard:   #1976d2;
  --qs-type-relic:   #6a1b9a;
  --qs-type-trinket: #c97a3a;
  --qs-type-item:    #c97a3a;
  /* Floater tiers */
  --qs-floater-tier-0: #1a1a1a;
  --qs-floater-tier-1: #f9a825;
  --qs-floater-tier-2: #ef6c00;
  --qs-floater-tier-3: #c62828;
  --qs-floater-tier-4: #880e0e;
  --qs-floater-glow-1: rgba(255, 200, 60, 0.55);
  --qs-floater-glow-2: rgba(255, 130, 40, 0.65);
  --qs-floater-glow-3: rgba(255, 50, 50, 0.75);
  --qs-floater-glow-4: rgba(160, 10, 10, 0.85);
  /* Shadows */
  --qs-shadow-card:  0 1px 3px rgba(0, 0, 0, 0.12);
  --qs-shadow-panel: 0 2px 8px rgba(0, 0, 0, 0.15);
  --qs-shadow-float: 0 4px 16px rgba(0, 0, 0, 0.22);
  /* Text shadows — same approach as cream (no surface shadow,
     white halo on color, black outline on floaters) */
  --qs-text-shadow-on-surface: none;
  --qs-text-shadow-on-color:
    -1px -1px 0 rgba(255,255,255,0.7),
     1px -1px 0 rgba(255,255,255,0.7),
    -1px  1px 0 rgba(255,255,255,0.7),
     1px  1px 0 rgba(255,255,255,0.7);
  --qs-floater-outline:
    -1px -1px 0 #000,
     1px -1px 0 #000,
    -1px  1px 0 #000,
     1px  1px 0 #000;
  /* Bar fills — slightly cooler than cream */
  --qs-hp-fill:     linear-gradient(90deg, #2e7d32, #66bb6a);
  --qs-shield-fill: linear-gradient(90deg, var(--qs-shield), #5fa8cc);
}

/* (Dark theme now lives above, near cream-stub.) */

/* ══════════════════════════════════════════════════════════════════════════
   Quack Stack (qs-*) — autobattler with 8-slot displacement shop
   ══════════════════════════════════════════════════════════════════════════ */

/* QS page wrapper — width container only. The dark frame moves to #qs-run
   so the landing screen (name input) stays on the platform's white body. */
.qs-page {
  max-width: 720px;
  margin: 0 auto;
  padding: 14px;
}

/* Dark frame applies only during an active run. Landing screen
   (#qs-landing) keeps the platform's light bg. */
.qs-run {
  background: var(--qs-bg-page);
  color: var(--qs-text);
  min-height: calc(100vh - 80px);
  border-radius: 8px;
  padding: 12px;
  margin: -8px -2px;
}

.qs-landing { text-align: center; padding: 32px 0; }
.qs-name-input {
  max-width: 280px;
  margin: 0 auto;
  text-align: center;
  font-size: var(--qs-text-md);
  font-weight: 600;
  letter-spacing: 0.04em;
  border: 2px solid var(--qs-accent);
  background: var(--qs-surface-panel);
  color: var(--qs-text-primary);
}
.qs-name-input::placeholder { color: var(--qs-text-muted); font-weight: 400; letter-spacing: normal; }
.qs-name-input:focus {
  border-color: var(--qs-text-primary);
  box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.4);
  background: var(--qs-surface-panel);
  color: var(--qs-text-primary);
}

.qs-hud {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  background: var(--qs-bg-hud);
  border-radius: 8px;
  padding: 8px;
  margin-bottom: 10px;
}
.qs-hud-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 4px;
  color: var(--qs-text);
}
.qs-hud-label {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--qs-accent);
}
.qs-hud-value {
  font-size: var(--qs-text-lg);
  font-weight: 800;
  color: var(--qs-text);
}

.qs-phase-banner {
  text-align: center;
  font-weight: 800;
  font-size: var(--qs-text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px;
  color: var(--qs-accent);
  background: var(--qs-bg-hud);
  border-radius: 6px;
  margin-bottom: 10px;
  transition: background 0.2s;
}
.qs-banner-error {
  background: #5a1f1f !important;
  color: var(--qs-damage) !important;
}

.qs-zone-label {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  color: #9ba3c4;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 18px 0 14px;
}

/* v0.3 board: 2x2 item grid stacked over a centered trinket row.
   The previous "trinket overlaid in the center" layout obscured the items
   it sat on top of — confirmed by catalog visual review. Trinket lives
   outside the adjacency graph and now sits below the items, which matches
   how it's always rendered in combat and summary screens. */
.qs-board {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 18px 0 14px;
}
.qs-board-items.qs-board-2x2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  grid-auto-rows: 1fr;
}
.qs-board-trinket-row {
  display: flex;
  justify-content: center;
}
/* ── Trinket slot ────────────────────────────────────────────────────────
   Lives in its own centered row below the 2x2 item grid (qs-board-trinket-row).
   Sized to match an item slot's footprint so the card inside reads at the
   same scale players see for items — same canonical anatomy: corner chips,
   direction badge, rarity star, upgrade badge. Identity is carried by the
   gold border on the card itself (.qs-type-trinket).
   ────────────────────────────────────────────────────────────────────────── */
.qs-trinket-slot {
  /* Match item-slot width so the trinket reads at the same scale as items.
     The 2x2 item grid uses 1fr columns ≈ 150px wide on a typical phone;
     fixed 150px here keeps the trinket visually paired with the items. */
  width: 150px;
  min-height: 100px;
  border-radius: 10px;
  background: var(--qs-bg-page);
  border: 2px dashed var(--qs-border);
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  cursor: pointer;
  transition: box-shadow 0.15s, border-color 0.15s;
}
.qs-trinket-slot.qs-slot-filled {
  /* Filled state: drop the dashed frame — the card's gold border carries
     identity. Allow overflow so corner chips at top:-8 can overhang. */
  background: transparent;
  border: none;
  overflow: visible;
}
.qs-trinket-slot.qs-slot-target {
  box-shadow: 0 0 0 3px var(--qs-accent);
  animation: qs-pulse 1s infinite;
}
.qs-trinket-slot .qs-card {
  width: 100%;
  /* Canonical anatomy via .qs-card-size-small (or whatever opts.size sets) —
     no slot-specific overrides. */
}
.qs-empty-trinket {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  color: var(--qs-border-normal);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

/* Legacy .qs-zone retained for any in-flight rendering — kept simple */
.qs-zone {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  grid-auto-rows: 1fr;
  margin-bottom: 8px;
}
/* Slot wrappers no longer carry a border — the card itself owns the
   visual frame. Slots only provide the click-target and drop-target hooks. */
.qs-slot {
  min-height: 90px;
  border-radius: 10px;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  cursor: pointer;
  transition: box-shadow 0.15s;
}
.qs-slot-target {
  box-shadow: 0 0 0 3px var(--qs-accent);
  animation: qs-pulse 1s infinite;
}
/* Bonus drop target — layered on top of qs-slot-target. Indicates that
   dropping here would activate the dragged card's positional bonus
   (rolled slot exact-match, or a slot with a neighbor in the rolled
   direction). Distinct green ring distinguishes "valid" vs "valid + bonus." */
.qs-slot-target-bonus {
  box-shadow: 0 0 0 4px var(--qs-rarity-uncommon), 0 0 14px rgba(92, 219, 92, 0.55);
  animation: qs-pulse-bonus 0.85s infinite;
}
@keyframes qs-pulse-bonus {
  0%, 100% { box-shadow: 0 0 0 3px var(--qs-rarity-uncommon), 0 0 8px  rgba(92, 219, 92, 0.45); }
  50%      { box-shadow: 0 0 0 5px var(--qs-rarity-uncommon), 0 0 18px rgba(92, 219, 92, 0.85); }
}
.qs-empty-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  font-size: 0.75rem;
  color: var(--qs-border-strong);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

@keyframes qs-pulse {
  0%,100% { box-shadow: inset 0 0 0 2px rgba(255, 215, 0, 0.5); }
  50%     { box-shadow: inset 0 0 0 4px rgba(255, 215, 0, 0.9); }
}

/* ── Canonical QS card anatomy ─────────────────────────────────────────────
   Per design system (preview/colors-qs.html):
   - Full 2px ochre border on items, full 2px purple border on relics
     (replaces the old left-stripe accent)
   - Padding reserves vertical breathing room for overhanging stat chips
     at top: -10px and bottom: -10px (chips are absolute-positioned children)
   - Title row: cost (gold mono) inline with name (white-bold) on one line
   - Description below in muted var(--qs-text-secondary)
   - All sizes (small/medium/large) follow the same anatomy at different
     scale — see size tokens further down.
   ────────────────────────────────────────────────────────────────────────── */
.qs-card {
  position: relative;
  width: 100%;
  background: var(--qs-bg-surface);
  border: 2px solid var(--qs-type-item);    /* default item color; relic class overrides */
  border-radius: 10px;
  padding: 26px 10px 28px;
  min-height: 90px;
  color: var(--qs-text);
  font-size: 0.78rem;
  box-sizing: border-box;
}
.qs-card-empty {
  background: transparent;
  border: 2px dashed var(--qs-border);
  color: var(--qs-border-normal);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  min-height: 72px;
  padding: 0;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Title row — type/cost label sits ABOVE the card name on its own line.
   Stacking universally instead of inline solves three problems at once:
   1. Long card names ("Cleaver of the Berserker") no longer compete with
      a (Type) suffix for horizontal width — name uses the full card width.
   2. Cards in the 2x2 board grid have uniform title heights regardless of
      name length, so the grid reads cleanly.
   3. Combat slots (narrow) get the same readability as shop cards.
   In small/medium contexts (combat/board/shop) the name forces single-line
   ellipsis to keep the grid uniform; large (inspect) lets the name wrap. */
.qs-card-title {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}
.qs-card-name {
  font-weight: 800;
  font-size: var(--qs-text-sm);
  color: var(--qs-text);
  line-height: 1.15;
}
.qs-card-size-small .qs-card-name,
.qs-card-size-medium .qs-card-name {
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.qs-card-text {
  font-size: var(--qs-text-xs);
  color: var(--qs-text-muted);
  line-height: 1.3;
  margin-top: 3px;
}
.qs-card-cost-inline {
  font-family: 'Courier New', Courier, monospace;
  font-weight: 800;
  font-size: var(--qs-text-xs);
  color: var(--qs-accent);
  letter-spacing: 0.02em;
}
/* Auto-generated rule lines (one sentence each). */
.qs-card-line {
  display: block;
  margin-top: 1px;
}

/* v0.7 — colored stat words inside card body text. Reinforces chip color
   coding so "damage" reads as the red number, "shield" the blue, "heal"
   the green. These spans wrap individual words in linesHtml output. */
.qs-text-damage { color: var(--qs-stat-attack); font-weight: 600; }
.qs-text-shield { color: var(--qs-stat-shield); font-weight: 600; }
.qs-text-heal   { color: var(--qs-stat-heal);   font-weight: 600; }
/* v0.11 — status-effect colors. Match the badge colors below the HP bar
   so card text and badge speak the same visual language. */
.qs-text-bleed  { color: #d04040; font-weight: 600; }
.qs-text-rage   { color: var(--qs-rage); font-weight: 600; }
/* M.3 — Cleric: regen (emerald) + conviction (gold) colored keywords. */
.qs-text-regen  { color: var(--qs-regen); font-weight: 600; }
.qs-text-conviction { color: var(--qs-conviction); font-weight: 700; }

/* v0.7 — italic scope keywords (All / Adjacent / Bash / Quick / Guard).
   Marks the target group in body text. Bold fallback if italics blur at
   small sizes. */
.qs-text-scope {
  font-style: italic;
  font-weight: 600;
}

/* v0.7 — slot-bonus arrow inline in body text (↖↗↙↘). Green to match the
   top-left slot-bonus badge. */
.qs-text-slot-arrow {
  color: var(--qs-rarity-uncommon);
  font-weight: 700;
}

/* v0.7 — direction arrow inline in body text (→←↑↓). Yellow to match the
   top-right direction badge. */
.qs-text-direction-arrow {
  color: var(--qs-accent);
  font-weight: 700;
}

/* ── Edge-overhanging stat chips ───────────────────────────────────────────
   Three different edges so chips never collide:
     damage  → top-edge center  (red)
     shield  → mid-left edge    (blue)
     heal    → mid-right edge   (green)
   Chip border color matches page background so chips read as floating
   tokens above the card edge. Reactive: data-stat lets the combat playback
   loop update the value text without re-rendering the whole card.
   ────────────────────────────────────────────────────────────────────────── */
.qs-chip {
  position: absolute;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 11px;
  /* color set per-variant via --qs-on-* tokens (J.14): chips sit on a
     saturated status fill, so the foreground is picked per-fill per-theme
     to clear WCAG AA, NOT inherited from the body text color. */
  border: 1.5px solid var(--qs-bg-page);
  border-radius: 6px;
  z-index: 2;
  box-sizing: border-box;
  line-height: 1;
}
/* All three chips overhang the top edge: shield (center-left), damage (center),
   heal (center-right). Keeps stat values in one easy-to-scan band. */
.qs-chip-damage {
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--qs-stat-attack);
  color: var(--qs-on-damage);
}
/* K.9 — bleed items show a blood-droplet chip in the damage chip's slot
   (an item's offensive output is damage XOR bleed). Crimson --qs-bleed fill +
   a sharp bottom corner so it reads as a falling drop, distinct from the gold
   damage chip. The icon, not the number, is what flags "this item bleeds." */
.qs-chip-bleed {
  top: -12px;
  left: 50%;
  /* Sharp bottom-RIGHT corner; the 45° rotation swings that corner to dead
     bottom-center → a teardrop that points straight DOWN (rounded top + sides). */
  transform: translateX(-50%) rotate(45deg);
  background: var(--qs-bleed);
  color: var(--qs-on-bleed);
  border-radius: 50% 50% 0 50%;
}
/* K.10 — rage is an offensive output (damage XOR bleed XOR rage), so the red
   flame shares the top-center slot with damage/bleed (exactly one shows). Sharp
   TOP-LEFT corner + 45° rotation → the point swings to top-center, so the flame
   points straight UP (rounded base + sides). */
.qs-chip-rage {
  top: -12px;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
  background: var(--qs-rage);
  color: var(--qs-on-rage);
  border-radius: 0 50% 50% 50%;
}
/* Counter-rotate the number so it stays upright inside the rotated droplet/flame. */
.qs-chip-bleed .qs-chip-glyph,
.qs-chip-rage .qs-chip-glyph {
  display: block;
  transform: rotate(-45deg);
}
/* M.3 — regen is a self heal-over-time (damage XOR bleed XOR rage XOR regen XOR
   conviction). Emerald CIRCLE in the top-center slot → a soft "healing orb,"
   distinct from the angular damage/bleed/rage chips. The green plus identity
   lives in the HP-bar badge icon. */
.qs-chip-regen {
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--qs-regen);
  color: var(--qs-on-regen);
  border-radius: 50%;
}
/* M.3 — conviction is the Cleric's snowball counter. Gold chip in the top-center
   slot; the up-chevron "rising" identity lives in the HP-bar badge icon. Flat-
   bottom rounded-top hints at "rising" while keeping the number legible. */
.qs-chip-conviction {
  top: -12px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--qs-conviction);
  color: var(--qs-on-conviction);
  border-radius: 9px 9px 4px 4px;
}
.qs-chip-shield {
  top: -12px;
  left: 28%;
  transform: translateX(-50%);
  background: var(--qs-stat-shield);
  color: var(--qs-on-shield);
}
.qs-chip-heal {
  top: -12px;
  left: 72%;
  transform: translateX(-50%);
  background: var(--qs-stat-heal);
  color: var(--qs-on-heal);
}

/* Item / relic full-border colors. All items share an ochre border; relics
   share purple. The type tag in the name (e.g. "(Bash)") still tells the
   player which subtype an item belongs to. */
.qs-type-item    { border-color: var(--qs-type-item); }
.qs-type-relic   { border-color: var(--qs-type-relic); }
/* Trinket cards get a gold border to mark their iconic role */
.qs-type-trinket { border-color: var(--qs-accent); }

.qs-shop {
  background: var(--qs-bg-hud);
  border-radius: 8px;
  padding: 10px;
  margin-top: 18px;
  margin-bottom: 12px;
}
.qs-shop-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 14px;
  font-weight: 800;
  font-size: var(--qs-text-base);
  letter-spacing: 0.06em;
  color: var(--qs-accent);
}
.qs-shop-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.qs-shop-section-label {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  color: #9ba3c4;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin: 14px 0 12px;
}
/* v0.3 shop: 4 fixed slots in a 2x2 grid (2 items, 1 relic, 1 trinket).
   Card height is FIXED — long descriptions clamp; tap card for full text.

   Sizing math: padding 26+28 = 54 reserved. Title row ≈ 18-22 (single line
   forced via nowrap/ellipsis below). Body 3 clamped lines @ 0.7rem×1.3 ≈
   42px. Slack for cooldown bar / star / direction badge in the bottom
   padding. 140px gives the body room to render 3 full lines without the
   bottom of letters being clipped (the bug at 110px).
   ────────────────────────────────────────────────────────────────────────── */
.qs-shop-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  grid-auto-rows: 1fr;
}
@media (min-width: 600px) {
  .qs-shop-grid { grid-template-columns: repeat(4, 1fr); }
}
.qs-shop-grid .qs-card {
  height: 140px;
  /* No overflow:hidden — would clip the corner chip overhang at top:-10.
     Body text clips on its own via -webkit-line-clamp on .qs-card-text. */
}
/* Shop card name uses the same single-line ellipsis as combat/board (all
   medium-size). The stacked-title rule lives at the global .qs-card-title
   level — no shop-specific override needed. */
.qs-shop-grid .qs-card-text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.qs-shop-card-wrap {
  cursor: pointer;
  border-radius: 10px;
  transition: transform 0.1s, box-shadow 0.15s;
}
.qs-shop-card-selected {
  transform: scale(1.04);
  box-shadow: 0 0 0 3px var(--qs-accent), 0 0 14px rgba(255, 215, 0, 0.5);
  border-radius: 10px;
}

/* Refresh pulse — applied to the shop slot that just got replaced after a
   buy (server's incremental refresh). One-shot 0.5s scale up→down so the
   player notices the new card. The animation is removed on `animationend`,
   AND immediately removed on `pointerdown` so a tap or drag-start during
   the pulse interrupts cleanly without blocking the interaction. */
@keyframes qs-shop-refresh-pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.07); box-shadow: 0 0 18px rgba(255, 215, 0, 0.55); }
  100% { transform: scale(1); }
}
.qs-shop-card-wrap.qs-shop-just-refreshed {
  animation: qs-shop-refresh-pulse 0.5s ease-out 1;
  pointer-events: auto;  /* ensure clicks/drags still pass through */
}

/* Tier-up button — purple to distinguish from reroll (yellow) and combat (green) */
.qs-tier-up-btn {
  background: var(--qs-type-relic);
  color: var(--qs-text-primary);
  border: 1px solid var(--qs-type-relic);
  font-weight: 700;
  font-size: 0.75rem;
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
}
.qs-tier-up-btn:hover:not(:disabled) {
  background: #8554c2;
  border-color: #8554c2;
}
.qs-tier-up-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Record banner — sits above the HUD, holds wins/losses since RECORD was
   replaced by TIER in the HUD itself. */
.qs-record-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--qs-bg-hud);
  padding: 4px 8px;
  border-radius: 6px;
  margin-bottom: 6px;
}
.qs-record-banner .qs-record-label {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--qs-accent);
}
.qs-record-banner .qs-record-value {
  font-size: var(--qs-text-sm);
  font-weight: 800;
  color: var(--qs-text);
}

/* Top-left corner — slot-bonus badge (green). Diagonal arrow indicating
   which slot index activates the card's in-rolled-slot effects. */
.qs-slot-bonus-badge {
  position: absolute;
  top: 3px;
  left: 6px;
  font-size: var(--qs-text-base);
  line-height: 1;
  z-index: 3;
  color: var(--qs-rarity-uncommon);
  text-shadow: var(--qs-text-shadow-on-surface);
  pointer-events: none;
}

/* Top-right corner — direction badge (yellow). Cardinal arrow indicating
   the direction this card outputs an aura/buff toward. */
.qs-direction-badge {
  position: absolute;
  top: 3px;
  right: 6px;
  font-size: var(--qs-text-base);
  line-height: 1;
  z-index: 3;
  color: var(--qs-accent);
  text-shadow: var(--qs-text-shadow-on-surface);
  pointer-events: none;
}

/* Bottom-left — cost badge (gold) shown only in shop view. Frozen ❄ glyph
   renders inline if the offer is frozen. */
.qs-cost-badge {
  position: absolute;
  bottom: 3px;
  left: 6px;
  font-family: 'Courier New', Courier, monospace;
  font-weight: 800;
  font-size: 0.72rem;
  color: var(--qs-accent);
  text-shadow: var(--qs-text-shadow-on-surface);
  z-index: 3;
  pointer-events: none;
  letter-spacing: 0.02em;
}
.qs-frozen-inline {
  margin-left: 4px;
  color: #6cf;
}

/* Bottom-center — cooldown text "1.50s". Sits just above the cooldown bar
   inside the card. data-cd-base lets the combat playback loop update it
   without re-rendering. */
.qs-cd-text {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  font-family: 'Courier New', Courier, monospace;
  font-weight: 700;
  font-size: 0.68rem;
  color: var(--qs-text-muted);
  letter-spacing: 0.02em;
  z-index: 3;
  pointer-events: none;
  text-shadow: var(--qs-text-shadow-on-surface);
}

/* Bottom-right — rarity star (uncommon+). Commons get no star. */
.qs-rarity-star {
  position: absolute;
  bottom: 2px;
  right: 6px;
  font-size: var(--qs-text-sm);
  line-height: 1;
  z-index: 3;
  text-shadow: var(--qs-text-shadow-on-surface);
  pointer-events: none;
}
.qs-rarity-uncommon  { color: var(--qs-rarity-uncommon); }
.qs-rarity-rare      { color: var(--qs-rarity-rare); }
.qs-rarity-epic      { color: var(--qs-type-relic); }
.qs-rarity-legendary { color: var(--qs-accent); }

.qs-card-size-large .qs-rarity-star  { font-size: var(--qs-text-lg); bottom: 4px; right: 8px; }
.qs-card-size-small .qs-rarity-star  { font-size: 0.65rem; bottom: 1px; right: 4px; }
.qs-card-size-large .qs-slot-bonus-badge,
.qs-card-size-large .qs-direction-badge { font-size: var(--qs-text-lg); }
.qs-card-size-small .qs-slot-bonus-badge,
.qs-card-size-small .qs-direction-badge { font-size: var(--qs-text-xs); }
.qs-card-size-large .qs-cd-text { font-size: var(--qs-text-sm); bottom: 10px; }
.qs-card-size-small .qs-cd-text { font-size: 0.55rem; bottom: 6px; }

.qs-hint {
  font-size: 0.75rem;
  text-align: center;
}

/* Combat playback */
.qs-combat {
  background: var(--qs-surface-page);
  border: 2px solid var(--qs-border-normal);
  border-radius: 10px;
  padding: 12px;
  margin-bottom: 12px;
}
.qs-combat-boards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.qs-combat-name { color: var(--qs-accent); margin-bottom: 4px; font-size: var(--qs-text-md); }
.qs-hp-bar {
  position: relative;
  height: 18px;
  background: var(--qs-surface-card);
  border-radius: 4px;
  overflow: hidden;
  border: 1px solid var(--qs-border-normal);
}
.qs-hp-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  background: var(--qs-hp-fill);
  transition: width 0.2s;
}
.qs-hp-text {
  position: absolute;
  inset: 0;
  text-align: center;
  font-size: 0.75rem;
  color: var(--qs-text-primary);
  font-weight: 700;
  text-shadow: var(--qs-text-shadow-on-color);
}
.qs-mini-board {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.qs-mini-zone {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
}
.qs-mini-relics .qs-mini-slot { background: #1f2a2a; }
.qs-mini-slot {
  min-height: 24px;
  background: #2a1f1f;
  border: 1px solid var(--qs-border-normal);
  border-radius: 3px;
  padding: 2px 4px;
  font-size: 0.62rem;
  color: var(--qs-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background 0.15s, box-shadow 0.15s;
}
.qs-mini-fire {
  background: var(--qs-accent) !important;
  color: #000 !important;
  box-shadow: 0 0 8px rgba(255, 215, 0, 0.9);
  animation: qs-mini-flash 0.4s ease-out;
}
@keyframes qs-mini-flash {
  0% { transform: scale(1); }
  40% { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* Cooldown progress bar — fills 0%→100% over the card's cooldown,
   resets to 0% the instant the card fires. Pinned to the bottom of the slot. */
.qs-mini-slot { position: relative; }
.qs-mini-name {
  display: block;
  position: relative;
  z-index: 1;
}
.qs-mini-cd-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 4px;
  width: 0%;
  background: linear-gradient(90deg, var(--qs-accent), #ffae00);
  border-radius: 0 0 3px 3px;
  transition: width 50ms linear;
  z-index: 0;
  pointer-events: none;
}
/* When a card fires, snap to 100% briefly via the firing animation,
   then the next render cycle drops it back to 0%. The "fire" pulse
   makes the firing moment unmistakable. */
.qs-mini-fire .qs-mini-cd-bar {
  background: var(--qs-text-primary);
  transition: none;
}

.qs-combat-clock {
  text-align: center;
  font-family: monospace;
  font-size: var(--qs-text-sm);
  color: var(--qs-accent);
  margin: 8px 0;
}
.qs-combat-log {
  max-height: 180px;
  overflow-y: auto;
  background: #0a0a14;
  border-radius: 6px;
  padding: 6px;
  font-size: 0.75rem;
  font-family: monospace;
  color: var(--qs-text-secondary);
}
.qs-log-line { padding: 1px 2px; }
.qs-log-dmg     { color: var(--qs-damage); }
.qs-log-heal    { color: #8bd17c; }
.qs-log-shield  { color: #6cf; }
.qs-log-perm    { color: var(--qs-accent); }
.qs-log-sandstorm { color: var(--qs-sandstorm); font-weight: 700; }

/* Screen containers — exactly one visible at a time */
.qs-screen {
  width: 100%;
  margin-bottom: 12px;
}

/* ── Combat stage (full takeover) ───────────────────────────── */
.qs-combat-stage {
  background: #0a0a14;
  border: 2px solid var(--qs-accent);
  border-radius: 10px;
  padding: 12px 10px;
  position: relative;
}
/* New stacked layout: ghost on top, divider, player on bottom. */
.qs-combat-stage-stacked {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.qs-combat-side {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.qs-combat-header {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.qs-combat-name {
  text-align: center;
  color: var(--qs-accent);
  font-weight: 800;
  font-size: var(--qs-text-base);
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.qs-combat-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0;
}
.qs-combat-divider-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--qs-accent), transparent);
}
.qs-combat-clock {
  font-family: monospace;
  font-size: var(--qs-text-sm);
  color: var(--qs-accent);
  background: var(--qs-surface-panel);
  padding: 3px 10px;
  border-radius: 4px;
  border: 1px solid var(--qs-border-normal);
  white-space: nowrap;
}

/* ── Combat board: full-readable cards in row layout ────────────── */
.qs-combat-board { display: flex; flex-direction: column; gap: 4px; }
/* Combat / summary trinket sub-rows: match the standalone shop trinket —
   item-slot width, centered, sits below the 2x2 items. Without these
   constraints the trinket card stretched full-width and looked oversized
   relative to the items above it. */
.qs-combat-trinket {
  display: flex;
  justify-content: center;
  margin-top: 6px;
}
.qs-combat-trinket .qs-combat-slot {
  width: 150px;
}
.qs-summary-trinket {
  display: flex;
  justify-content: center;
  margin-top: 8px;
}
.qs-summary-trinket .qs-summary-slot {
  width: 150px;
}
.qs-combat-zone-label {
  font-size: 0.65rem;
  font-weight: 700;
  color: var(--qs-text-muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
}
/* Combat zones use the same 2x2 layout as the shop board so positional
   relationships (Adjacency, Mirror) are consistent across views. */
.qs-combat-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}
.qs-combat-slot {
  position: relative;
  min-height: 72px;
  background: transparent;
  border: none;
  border-radius: 6px;
  display: flex;
  align-items: stretch;
  /* overflow: visible (default) — corner stat chips overhang the card at
     top:-10 and would be clipped by overflow:hidden. The card inside owns
     its own border + background; no need for the slot to draw its own
     frame. Body text still clips via -webkit-line-clamp on .qs-card-text. */
}
.qs-combat-relics .qs-combat-slot { background: #14191a; }
.qs-combat-slot-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  font-size: 0.65rem;
  color: var(--qs-border-normal);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* ── Card size tokens — applied via class on .qs-card ──────────────
   All sizes share the canonical anatomy (full border + overhanging chips).
   Chips are reserved via top/bottom padding regardless of whether they
   render — keeps cards the same shape across zones, which lets `1fr`
   rows align reliably.

   small  = combat playback cards (tight)
   medium = shop offering / board / summary (default-ish)
   large  = inspect modal (zoomed-up readable)
   ─────────────────────────────────────────────────────────────────── */
.qs-card-size-small {
  font-size: var(--qs-text-xs);
  padding: 18px 6px 18px;
  min-height: 70px;
  border-radius: 8px;
}
.qs-card-size-small .qs-card-name {
  font-size: 0.72rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.qs-card-size-small .qs-card-text { font-size: 0.62rem; line-height: 1.2; }
.qs-card-size-small .qs-card-cost-inline { font-size: 0.62rem; }
.qs-card-size-small .qs-card-type-tag { font-size: 0.6rem; }
.qs-card-size-small .qs-card-line { font-size: 0.62rem; line-height: 1.2; }
.qs-card-size-small .qs-chip {
  width: 20px; height: 20px; font-size: var(--qs-text-xs);
  border-width: 1.25px;
}
.qs-card-size-small .qs-chip-damage { top: -10px; }
.qs-card-size-small .qs-chip-bleed  { top: -10px; }
.qs-card-size-small .qs-chip-rage   { top: -10px; }
.qs-card-size-small .qs-chip-regen  { top: -10px; }
.qs-card-size-small .qs-chip-conviction { top: -10px; }
.qs-card-size-small .qs-chip-shield { top: -10px; }
.qs-card-size-small .qs-chip-heal   { top: -10px; }

.qs-card-size-medium {
  font-size: 0.78rem;
  padding: 26px 10px 28px;
  min-height: 90px;
}
.qs-card-size-medium .qs-card-name { font-size: var(--qs-text-sm); }
.qs-card-size-medium .qs-card-text { font-size: var(--qs-text-xs); }

.qs-card-size-large {
  font-size: var(--qs-text-base);
  padding: 32px 14px 32px;
  min-height: 120px;
  border-radius: 12px;
}
.qs-card-size-large .qs-card-name { font-size: var(--qs-text-lg); }
.qs-card-size-large .qs-card-text { font-size: var(--qs-text-base); line-height: 1.4; }
.qs-card-size-large .qs-card-cost-inline { font-size: var(--qs-text-base); }
.qs-card-size-large .qs-chip {
  width: 30px; height: 30px; font-size: var(--qs-text-base);
  border-width: 2px;
}
.qs-card-size-large .qs-chip-damage { top: -15px; }
.qs-card-size-large .qs-chip-bleed  { top: -15px; }
.qs-card-size-large .qs-chip-rage   { top: -15px; }
.qs-card-size-large .qs-chip-regen  { top: -15px; }
.qs-card-size-large .qs-chip-conviction { top: -15px; }
.qs-card-size-large .qs-chip-shield { top: -15px; }
.qs-card-size-large .qs-chip-heal   { top: -15px; }

/* Cooldown bar — shared across all sizes; same data-cd-driven progress
   updated by the combat playback. Static at 0% in shop / board (cosmetic
   only — signals "this card has a cooldown"). */
.qs-cd-bar {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 5px;
  width: 0%;
  background: linear-gradient(90deg, var(--qs-accent), #ffae00);
  border-radius: 0 0 5px 5px;
  transition: width 50ms linear;
  z-index: 1;
  pointer-events: none;
}

/* (qs-combat-cd-bar removed — bar now lives inside each card via cardHtml,
   styled by .qs-cd-bar in the size-token section above.) */

/* F.17 — Accumulator progress bar. Mounts in the same slot as the cooldown
   bar but for tickless cards (Hemorrhage Sigil, Resonant Coil) that
   progress on item-fire events instead of a periodic cooldown. Visually
   distinct (green) so the player knows it's not a cooldown. Updated by
   updateCooldownBars during playback from snapshot.accumulatorStacks. */
.qs-cd-bar.qs-acc-bar {
  background: linear-gradient(90deg, #4CAF50, #2E7D32);
  transition: width 150ms ease-out;
}

/* Fire pulse — only triggered on first fire per slot */
.qs-combat-fire {
  outline: 2px solid var(--qs-accent);
  outline-offset: -2px;
  animation: qs-combat-flash 0.5s ease-out;
}
@keyframes qs-combat-flash {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 215, 0, 0); }
  40%  { transform: scale(1.06); box-shadow: 0 0 14px rgba(255, 215, 0, 0.7); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 215, 0, 0); }
}

/* Big HP bars during combat */
.qs-hp-bar-big {
  height: 28px !important;
  border-width: 2px !important;
}
.qs-hp-bar-big .qs-hp-text {
  font-size: var(--qs-text-base) !important;
  line-height: 28px;
}

/* Shield bar — sits above the HP bar; reserves space even at 0 shield. */
.qs-shield-bar {
  position: relative;
  height: 14px;
  background: #1a1a25;
  border: 1px solid var(--qs-border-normal);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 4px;
  transition: opacity 0.2s, border-color 0.2s;
}
.qs-shield-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  background: var(--qs-shield-fill);
  transition: width 0.18s ease-out;
}
.qs-shield-text {
  position: absolute;
  inset: 0;
  text-align: center;
  font-size: var(--qs-text-xs);
  font-weight: 700;
  color: var(--qs-text-primary);
  text-shadow: var(--qs-text-shadow-on-color);
  line-height: 14px;
  letter-spacing: 0.05em;
}
/* When shield is 0, dim the empty bar so it's visibly inert */
.qs-shield-bar-empty {
  opacity: 0.35;
  border-color: var(--qs-surface-card);
}

/* ── v0.11: Status badges (bleed + rage) ────────────────────────────
   A flex row immediately below the HP bar. Each badge holds an icon
   (emoji) + a numeric count. Hidden via `qs-status-empty` when the
   value is 0 — `display:none` keeps the row collapsed so it doesn't
   reserve dead space. Apply/gain animations are CSS keyframes
   triggered by adding a one-shot class from JS. */
.qs-status-row {
  display: flex;
  gap: 6px;
  margin-top: 4px;
  min-height: 0;            /* row collapses when both badges are empty */
  font-size: var(--qs-text-sm);
  font-weight: 700;
  line-height: 1;
}
.qs-bleed-badge,
.qs-rage-badge,
.qs-regen-badge,
.qs-conviction-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 7px;
  border-radius: 10px;
  border: 1px solid;
  text-shadow: var(--qs-text-shadow-on-color);
  transition: transform 0.15s ease-out, box-shadow 0.2s ease-out;
}
.qs-bleed-badge {
  background: linear-gradient(180deg, #5a1a1a, #3a0e0e);
  border-color: #b03030;
  color: #ffcccc;
}
.qs-rage-badge {
  background: linear-gradient(180deg, #5a3614, #3a1f0a);
  border-color: #e08a30;
  color: #ffd9aa;
}
/* M.3 — Cleric regen (emerald) + conviction (gold) badges, same dark-gradient
   token treatment as bleed/rage so the four read as one status family. */
.qs-regen-badge {
  background: linear-gradient(180deg, #134a35, #0a2c20);
  border-color: #2a9d6e;
  color: #c8f5e0;
}
.qs-conviction-badge {
  background: linear-gradient(180deg, #5a4410, #3a2c08);
  border-color: #e0b030;
  color: #ffeaab;
}
.qs-bleed-icon,
.qs-rage-icon,
.qs-regen-icon,
.qs-conviction-icon {
  font-size: var(--qs-text-base);
  line-height: 1;
}
.qs-bleed-count,
.qs-rage-count,
.qs-regen-count,
.qs-conviction-count {
  font-variant-numeric: tabular-nums;
  min-width: 1ch;
  text-align: center;
}
/* Empty (value=0) = collapsed. display:none over visibility:hidden
   because we don't want it reserving width in the flex row. */
.qs-status-empty {
  display: none !important;
}

/* Animations. apply-pulse fires when a stack is added; tick-shake when
   bleed ticks; rage-flash when rage gain. Keep durations short (≤300ms)
   so they don't fight the 200ms HP bar transition or the slot pulses. */
@keyframes qs-bleed-apply-pulse {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 60, 60, 0); }
  35%  { transform: scale(1.22); box-shadow: 0 0 14px rgba(255, 60, 60, 0.85); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 60, 60, 0); }
}
@keyframes qs-bleed-tick-shake {
  0%   { transform: translateX(0); }
  20%  { transform: translateX(-2px) scale(1.06); }
  50%  { transform: translateX(2px); }
  80%  { transform: translateX(-1px); }
  100% { transform: translateX(0); }
}
@keyframes qs-rage-gain-flash {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 160, 50, 0); }
  35%  { transform: scale(1.22); box-shadow: 0 0 14px rgba(255, 160, 50, 0.95); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(255, 160, 50, 0); }
}
/* M.3 — regen mirrors bleed (apply-pulse + tick-shake) in green; conviction
   mirrors rage's gain-flash in gold. */
@keyframes qs-regen-apply-pulse {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(60, 220, 150, 0); }
  35%  { transform: scale(1.22); box-shadow: 0 0 14px rgba(60, 220, 150, 0.85); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(60, 220, 150, 0); }
}
@keyframes qs-regen-tick-pulse {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(60, 220, 150, 0); }
  40%  { transform: scale(1.10); box-shadow: 0 0 9px rgba(60, 220, 150, 0.7); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(60, 220, 150, 0); }
}
@keyframes qs-conviction-gain-flash {
  0%   { transform: scale(1);    box-shadow: 0 0 0 rgba(240, 190, 50, 0); }
  35%  { transform: scale(1.22); box-shadow: 0 0 14px rgba(240, 190, 50, 0.95); }
  100% { transform: scale(1);    box-shadow: 0 0 0 rgba(240, 190, 50, 0); }
}
.qs-bleed-apply-anim { animation: qs-bleed-apply-pulse 0.32s ease-out; }
.qs-bleed-tick-anim  { animation: qs-bleed-tick-shake  0.28s ease-out; }
.qs-rage-gain-anim   { animation: qs-rage-gain-flash   0.32s ease-out; }
.qs-regen-apply-anim { animation: qs-regen-apply-pulse 0.32s ease-out; }
.qs-regen-tick-anim  { animation: qs-regen-tick-pulse  0.28s ease-out; }
.qs-conviction-gain-anim { animation: qs-conviction-gain-flash 0.32s ease-out; }

/* ── Floating damage numbers (BD-style) ─────────────────────── */
.qs-floater-zone {
  position: absolute;
  left: 0;
  right: 0;
  height: 80px;
  pointer-events: none;
  overflow: visible;
  z-index: 10;
}
/* Both sides have header (HP bar) at the top of their section — keeps
   layout consistent with the shop. Floaters spawn near each side's HP
   bar so damage numbers land where damage was taken. */
.qs-combat-ghost  .qs-floater-zone { top: 36px; }
.qs-combat-player .qs-floater-zone { top: 36px; bottom: auto; }
.qs-floater {
  position: absolute;
  left: 50%;
  font-weight: 900;
  font-size: var(--qs-text-xl);
  text-shadow: var(--qs-floater-outline);
  transform: translateX(-50%);
  animation: qs-floater-rise 1.2s ease-out forwards;
  white-space: nowrap;
}
.qs-floater-damage     { color: var(--qs-damage); }
.qs-floater-heal       { color: var(--qs-heal); }
.qs-floater-shield     { color: var(--qs-shield); }
.qs-floater-sandstorm  { color: var(--qs-sandstorm); font-size: var(--qs-text-lg); }
/* v0.11: bleed tick floater — distinct deeper red + droplet prefix so
   players can attribute DoT damage to bleed, not weapon attacks. */
.qs-floater-bleed      { color: var(--qs-bleed); text-shadow: var(--qs-floater-outline); }

@keyframes qs-floater-rise {
  0%   { opacity: 0; transform: translate(-50%, 8px) scale(0.7); }
  15%  { opacity: 1; transform: translate(-50%, 0) scale(1.15); }
  30%  { transform: translate(-50%, -4px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -52px) scale(0.95); }
}

/* H.5 — Magnitude tiers for floaters. tier-0 is unchanged baseline; tiers
   1-4 progressively bump font size + weight + letter-spacing so big
   numbers read as "wow that's big" without the player having to parse
   the digits. The black outline (--qs-floater-outline) is shared by all
   tiers via .qs-floater above — no per-tier glow shadows anymore (those
   were dark-theme-tuned and turned to halo-blur on cream). */
.qs-floater-tier-0 { /* baseline; no override */ }
.qs-floater-tier-1 { font-size: 1.5rem; }
.qs-floater-tier-2 { font-size: 1.8rem; }
.qs-floater-tier-3 {
  font-size: 2.2rem;
  animation: qs-floater-rise 1.4s ease-out forwards, qs-floater-pulse-big 0.45s ease-out;
}
.qs-floater-tier-4 {
  font-size: 2.7rem;
  font-weight: 900;
  letter-spacing: -1px;
  animation: qs-floater-rise 1.6s ease-out forwards, qs-floater-pulse-big 0.55s ease-out;
}
@keyframes qs-floater-pulse-big {
  0%   { transform: translateX(-50%) scale(0.4); }
  40%  { transform: translateX(-50%) scale(1.4); }
  100% { transform: translateX(-50%) scale(1); }
}

/* H.5 — Late-game HP/shield bar shimmer. When the bar's max is in the
   millions or higher, a subtle background sheen plays so the player
   notices the bar is in the "scaled" tier. Animation is non-blocking and
   visually quiet — just a soft moving highlight every few seconds. */
.qs-hp-bar.qs-bar-shimmer,
.qs-shield-bar.qs-bar-shimmer {
  position: relative;
  overflow: hidden;
}
.qs-hp-bar.qs-bar-shimmer::after,
.qs-shield-bar.qs-bar-shimmer::after {
  content: '';
  position: absolute;
  top: 0; left: -40%;
  width: 30%; height: 100%;
  background: linear-gradient(
    90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,0.18) 50%,
    rgba(255,255,255,0) 100%
  );
  animation: qs-bar-shimmer-slide 3.2s ease-in-out infinite;
  pointer-events: none;
}
@keyframes qs-bar-shimmer-slide {
  0%   { left: -40%; }
  100% { left: 110%; }
}

/* ── Reward screen (full takeover) ──────────────────────────── */
.qs-reward-card {
  text-align: center;
  padding: 32px 16px;
  background: linear-gradient(180deg, var(--qs-surface-panel) 0%, var(--qs-surface-page) 100%);
  border: 3px solid var(--qs-accent);
  border-radius: 14px;
  color: var(--qs-text-primary);
  box-shadow: 0 0 40px rgba(255, 215, 0, 0.2);
  margin: 16px auto;
  max-width: 480px;
}
.qs-reward-result {
  font-size: 2.2rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  margin: 0 0 6px 0;
}
.qs-reward-win   { color: var(--qs-heal); }
.qs-reward-loss  { color: #ff5c5c; }
.qs-reward-tie   { color: var(--qs-accent); }
.qs-reward-sub {
  color: var(--qs-text-muted);
  font-size: var(--qs-text-base);
  margin-bottom: 22px;
}
.qs-reward-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin: 0 auto;
  max-width: 360px;
}
.qs-reward-item {
  background: var(--qs-surface-card);
  border: 2px solid var(--qs-accent);
  border-radius: 10px;
  padding: 16px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.qs-reward-icon {
  font-size: var(--qs-text-2xl);
  font-weight: 900;
  color: var(--qs-accent);
}
.qs-reward-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--qs-text-secondary);
}

/* ── v0.5 boon picker (folded into reward screen) ──────────────────────
   Two-card grid matching the post-combat reward styling. Selected boon
   gets green border; unselected get gold. Tap any card to pick (locked
   after pick — must continue to next round). */
.qs-boon-section {
  margin: 22px auto 4px;
  max-width: 480px;
}
.qs-boon-title {
  font-size: var(--qs-text-base);
  font-weight: 800;
  letter-spacing: 0.14em;
  color: var(--qs-accent);
  margin: 0 0 12px 0;
  text-transform: uppercase;
}
.qs-boon-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  max-width: 400px;
  margin: 0 auto;
}
.qs-boon-card {
  background: var(--qs-surface-card);
  border: 2px solid var(--qs-accent);
  border-radius: 10px;
  padding: 14px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  transition: transform 0.12s, box-shadow 0.12s, border-color 0.12s;
}
.qs-boon-card:hover:not(.qs-boon-disabled) {
  transform: scale(1.04);
  box-shadow: 0 0 14px rgba(255, 215, 0, 0.45);
}
.qs-boon-card.qs-boon-picked {
  border-color: var(--qs-heal);
  background: linear-gradient(180deg, #2a3a30 0%, #1f2a25 100%);
  box-shadow: 0 0 16px rgba(92, 255, 141, 0.5);
}
.qs-boon-card.qs-boon-disabled {
  opacity: 0.4;
  cursor: not-allowed;
  filter: grayscale(0.5);
}
.qs-boon-icon {
  font-size: 1.5rem;
  font-weight: 900;
  color: var(--qs-accent);
}
.qs-boon-card.qs-boon-picked .qs-boon-icon { color: var(--qs-heal); }
.qs-boon-name {
  font-size: var(--qs-text-sm);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--qs-text-primary);
}
.qs-boon-effect {
  font-size: 0.75rem;
  color: var(--qs-text-muted);
  text-align: center;
  line-height: 1.2;
}

/* v0.10: class boon picker — visual variant of the boon card. Wider
   description, no icon, slightly different border to signal "this is
   a class identity choice, not a stat boost." */
.qs-boon-card.qs-class-boon-card {
  border-color: var(--qs-rarity-epic);
  box-shadow: 0 0 12px rgba(192, 138, 255, 0.25);
}
.qs-boon-card.qs-class-boon-card:hover:not(.qs-boon-disabled) {
  border-color: var(--qs-rarity-epic);
  box-shadow: 0 0 18px rgba(212, 168, 255, 0.4);
}
.qs-boon-card.qs-class-boon-card .qs-boon-name {
  color: var(--qs-rarity-epic);
}

/* v0.10: active class-boons banner above HP bar in combat. Shows the
   1-2 boons the player picked this run as small chips. Hidden when
   empty (qs-class-boons-empty class). */
.qs-class-boons-banner {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 4px 0;
  min-height: 0;
}
.qs-class-boons-banner.qs-class-boons-empty {
  display: none;
}
.qs-class-boon-chip {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--qs-rarity-epic);
  background: rgba(192, 138, 255, 0.15);
  border: 1px solid var(--qs-rarity-epic);
  border-radius: 10px;
  padding: 2px 8px;
  cursor: help;
}

@media (max-width: 500px) {
  .qs-combat-stage { padding: 10px 6px; min-height: 260px; }
  .qs-combat-name { font-size: var(--qs-text-base); }
  .qs-floater { font-size: var(--qs-text-lg); }
}

/* ── Type tag (parens after card name) ──────────────────────────── */
.qs-card-type-tag {
  font-size: var(--qs-text-xs);
  font-weight: 500;
  color: #ffd700b0;
  letter-spacing: 0.04em;
}

/* ── Drag preview ghost (follows pointer) ──────────────────────── */
.qs-drag-ghost {
  position: fixed;
  width: 200px;
  pointer-events: none;
  z-index: 5000;
  transform: translate(-50%, -50%);
  opacity: 0.85;
  filter: drop-shadow(0 4px 12px rgba(255, 215, 0, 0.6));
  transition: none;
}
.qs-drag-ghost .qs-card {
  border-width: 3px !important;
  box-shadow: 0 0 18px rgba(255, 215, 0, 0.5);
}
.qs-card-being-dragged {
  opacity: 0.35 !important;
  transform: scale(0.95);
}

/* ── Drop target highlights ─────────────────────────────────────── */
.qs-slot-hover {
  border-color: var(--qs-heal) !important;
  box-shadow: inset 0 0 0 3px rgba(92, 255, 141, 0.6);
  background: rgba(92, 255, 141, 0.1) !important;
}

/* ── Inspect modal (tap any card) ───────────────────────────────── */
.qs-inspect-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.75);
  z-index: 4000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.qs-inspect-card {
  background: var(--qs-surface-panel);
  border: 3px solid var(--qs-accent);
  border-radius: 12px;
  padding: 24px 20px 20px;
  max-width: 360px;
  width: 100%;
  position: relative;
  box-shadow: 0 0 40px rgba(255, 215, 0, 0.3);
  color: var(--qs-text-primary);
}
.qs-inspect-close {
  position: absolute;
  top: 6px;
  right: 10px;
  background: transparent;
  border: none;
  color: var(--qs-accent);
  font-size: var(--qs-text-2xl);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.qs-inspect-name {
  font-size: var(--qs-text-xl);
  font-weight: 800;
  color: var(--qs-accent);
  margin-bottom: 4px;
}
.qs-inspect-cost {
  display: inline-block;
  background: var(--qs-accent);
  color: #222;
  font-weight: 800;
  padding: 2px 10px;
  border-radius: 4px;
  margin-bottom: 12px;
  min-height: 20px;
}
.qs-inspect-text {
  font-size: var(--qs-text-base);
  line-height: 1.4;
  color: #ddd;
  margin-bottom: 10px;
}
.qs-inspect-meta {
  font-size: var(--qs-text-sm);
  color: var(--qs-text-muted);
  font-family: monospace;
}
.qs-inspect-actions {
  margin-top: 16px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.qs-inspect-actions:empty { display: none; }

/* Modal renders a full card via cardHtml — wrapper just provides spacing.
   The card's own type-class (qs-type-item / qs-type-relic) drives the
   border color; no override needed here. */
.qs-inspect-card-render {
  margin: 0 0 14px;
}

/* ── Summary screen (post-combat, both builds visible) ──────────────
   You/Decoy stack vertically (design iterated away from side-by-side).
   Banner color reflects the result: green win, red loss, gold tie.
   Zones reuse the canonical 2x2 grid + chip anatomy from the shop. */
.qs-summary-banner {
  text-align: center;
  font-family: var(--qw-font-sans, 'Nunito', sans-serif);
  font-weight: 900;
  font-size: var(--qs-text-2xl);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 12px;
  margin-bottom: 12px;
  border-radius: 8px;
  /* Sticker outline + subtle drop ring against the dark page */
  box-shadow: 0 0 0 3px var(--qs-bg-hud), 0 4px 12px rgba(0, 0, 0, 0.35);
}
.qs-summary-banner.qs-reward-win  { background: var(--qs-rarity-uncommon); color: var(--qs-bg-page); }
.qs-summary-banner.qs-reward-loss { background: #e05555; color: var(--qs-text-primary); }
.qs-summary-banner.qs-reward-tie  { background: var(--qs-accent); color: var(--qw-ink); }

/* Stack player + decoy vertically. */
.qs-summary-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.qs-summary-side {
  background: var(--qs-bg-hud);
  border-radius: 8px;
  padding: 10px 12px 14px;
}
.qs-summary-name {
  text-align: center;
  color: var(--qs-accent);
  font-weight: 800;
  font-size: var(--qs-text-md);
  margin: 4px 0 0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.qs-summary-zone-label {
  font-size: var(--qs-text-xs);
  font-weight: 700;
  color: #9ba3c4;
  letter-spacing: 0.1em;
  margin: 18px 0 14px;
  text-transform: uppercase;
}
.qs-summary-zone {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  grid-auto-rows: 1fr;
  margin-bottom: 6px;
}
.qs-summary-slot {
  cursor: pointer;
  display: flex;
  align-items: stretch;
}

/* ── v0.9 Quack Stack 2P ────────────────────────────────────────────────── */
.qsm-opponent-banner {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  background: rgba(255, 215, 0, 0.08);
  border: 1px solid rgba(255, 215, 0, 0.25);
  border-radius: 6px;
  margin-bottom: 6px;
  font-size: var(--qs-text-sm);
}
.qsm-opp-label { color: var(--qs-accent); font-weight: 800; letter-spacing: 0.1em; }
.qsm-opp-name { color: var(--qs-text-primary); font-weight: 700; }
.qsm-opp-record { color: var(--qs-text-secondary); font-family: 'Courier New', monospace; }
.qsm-opp-phase {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--qs-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.qs-lobby-code-card {
  background: var(--qs-bg-hud);
  border: 2px solid var(--qs-accent);
  border-radius: 12px;
  padding: 24px;
  margin-top: 16px;
  text-align: center;
}
.qs-lobby-code-label {
  color: var(--qs-text-muted);
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.qs-lobby-code-value {
  font-family: 'Courier New', monospace;
  font-size: 3rem;
  font-weight: 900;
  color: var(--qs-accent);
  letter-spacing: 0.3em;
  margin-top: 8px;
}
/* "OR JOIN AN EXISTING ROOM" divider on the multi landing — separates
   the CREATE flow from the JOIN flow without making them feel like
   completely separate cards. */
.qsm-or-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 12px;
  color: var(--qs-text-muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
}
.qsm-or-divider::before,
.qsm-or-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: #333;
}
.qsm-waiting-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.85);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}
.qsm-waiting-card {
  background: var(--qs-bg-surface);
  border: 2px solid var(--qs-accent);
  border-radius: 12px;
  padding: 36px;
  text-align: center;
  max-width: 320px;
}
.qsm-waiting-spinner {
  font-size: 3rem;
  margin-bottom: 12px;
  animation: qsm-spin 2s linear infinite;
}
@keyframes qsm-spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* ── L.4 Class-select carousel (landing) ───────────────────────────────────
   Copies Battle Ducks' class+color carousel, QS-tokenized. Summary icons reuse
   the card-top chip shapes (bleed droplet / rage flame) — no emojis (pillar 5). */
.qs-class-select { margin: 0.75rem 0 1rem; }
.qs-duck-preview { width: 140px; height: 140px; margin: 0 auto 0.5rem; display: flex; align-items: center; justify-content: center; }
.qs-duck-preview svg { width: 100%; height: 100%; }
.qs-class-carousel { display: flex; align-items: center; justify-content: center; gap: 0.75rem; margin-bottom: 0.4rem; }
.qs-arrow-btn { background: var(--qs-bg-page); color: var(--qs-accent); border: 1.5px solid var(--qs-accent);
  border-radius: 8px; width: 38px; height: 38px; font-size: 1.1rem; font-weight: 800; cursor: pointer; line-height: 1; }
.qs-arrow-btn:hover { background: var(--qs-accent); color: var(--qs-bg-page); }
.qs-class-name-cell { min-width: 132px; }
.qs-class-name { font-size: 1.1rem; }
.qs-class-soon { display: block; font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; }
.qs-color-carousel .qs-color-name { min-width: 132px; font-size: 0.9rem; opacity: 0.85; }
.qs-class-summary { margin: 0.6rem auto 0; max-width: 300px; text-align: left; font-size: 0.9rem; min-height: 2.6em; }
.qs-summary-line { display: flex; align-items: center; gap: 0.5rem; margin: 0.25rem 0; }
.qs-summary-soon { text-align: center; opacity: 0.6; font-style: italic; }
.qs-summary-icon { display: inline-block; width: 14px; height: 14px; flex: 0 0 auto; }
.qs-summary-icon-bleed { background: var(--qs-bleed); border-radius: 50% 50% 0 50%; transform: rotate(45deg); }
.qs-summary-icon-rage  { background: var(--qs-rage);  border-radius: 0 50% 50% 50%; transform: rotate(45deg); }
/* M.3 — regen = green plus (cross); conviction = gold up-chevron (rising mark). */
.qs-summary-icon-regen { background: var(--qs-regen); clip-path: polygon(38% 0, 62% 0, 62% 38%, 100% 38%, 100% 62%, 62% 62%, 62% 100%, 38% 100%, 38% 62%, 0 62%, 0 38%, 38% 38%); }
.qs-summary-icon-conviction { background: var(--qs-conviction); clip-path: polygon(50% 5%, 95% 55%, 78% 72%, 50% 38%, 22% 72%, 5% 55%); }
.qs-summary-icon-dot   { background: var(--qs-accent); border-radius: 50%; opacity: 0.5; }
.qs-btn-disabled, #qs-start-run-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* L.5 — combat status-badge icons as CSS shapes (no emojis, pillar 5): a crimson
   droplet / orange flame on the badge's dark pill, matching the card chips. */
.qs-bleed-icon, .qs-rage-icon, .qs-regen-icon, .qs-conviction-icon { display: inline-block; width: 10px; height: 10px; }
.qs-bleed-icon { background: var(--qs-bleed); border-radius: 50% 50% 0 50%; transform: rotate(45deg); }
.qs-rage-icon  { background: var(--qs-rage);  border-radius: 0 50% 50% 50%; transform: rotate(45deg); }
/* M.3 — regen = green plus (cross); conviction = gold up-chevron (rising mark). */
.qs-regen-icon { background: var(--qs-regen); clip-path: polygon(38% 0, 62% 0, 62% 38%, 100% 38%, 100% 62%, 62% 62%, 62% 100%, 38% 100%, 38% 62%, 0 62%, 0 38%, 38% 38%); }
.qs-conviction-icon { background: var(--qs-conviction); clip-path: polygon(50% 5%, 95% 55%, 78% 72%, 50% 38%, 22% 72%, 5% 55%); }
