← Shadows results

Shadows

muse spark 1.2 contributor · full

Initial view of Shadows

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 100,176 bytes · SHA-256 fe22aea384ed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadows of the Abyss</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700;900&family=Cinzel+Decorative:wght@700&family=JetBrains+Mono:wght@400;700&family=Crimson+Pro:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#0a0a12;--panel:#14141f;--panel2:#1c1c2e;--gold:#d4a65a;--gold2:#8a6a2e;--crimson:#c0392b;--violet:#7a3cff;--teal:#2ecc71;--muted:#8a8aa0}
body{background:var(--bg);color:#e8e0d0;font-family:'Crimson Pro',serif;overflow-x:hidden;min-height:100vh}
h1,h2,h3{font-family:'Cinzel',serif}
.mono{font-family:'JetBrains Mono',monospace}

/* header */
.topbar{position:sticky;top:0;z-index:50;background:linear-gradient(180deg,#0f0f1e 0%,#0a0a12 100%);border-bottom:1px solid #2a2a40;display:flex;align-items:center;justify-content:space-between;padding:10px 18px;gap:12px;flex-wrap:wrap}
.brand{font-family:'Cinzel Decorative',serif;font-size:20px;letter-spacing:2px;color:var(--gold);text-shadow:0 0 18px rgba(212,166,90,.4)}
.stats{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.stat-pill{background:#1a1a2e;border:1px solid #2a2a40;border-radius:999px;padding:6px 12px;display:flex;align-items:center;gap:8px;font-family:'JetBrains Mono',monospace;font-size:12px}
.hp-bar{width:140px;height:10px;background:#1a1a1a;border-radius:999px;overflow:hidden;border:1px solid #333}
.hp-fill{height:100%;background:linear-gradient(90deg,#c0392b,#e74c3c);transition:width .4s}
.gold{color:var(--gold);font-weight:700}
.act-badge{background:var(--violet);color:#fff;padding:4px 10px;border-radius:999px;font-size:11px;letter-spacing:1px}

/* layout */
.wrap{max-width:1280px;margin:0 auto;padding:18px}
.view{display:none}
.view.active{display:block}

/* class select */
.hero{text-align:center;padding:30px 10px}
.hero h1{font-size:42px;color:var(--gold);text-shadow:0 0 30px rgba(212,166,90,.25)}
.hero p{color:var(--muted);max-width:640px;margin:10px auto;line-height:1.6}
.class-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:22px}
@media(max-width:900px){.class-grid{grid-template-columns:1fr}}
.class-card{background:linear-gradient(180deg,#1a1a2e 0%,#12121f 100%);border:1px solid #2a2a40;border-radius:16px;padding:18px;cursor:pointer;transition:.2s;position:relative;overflow:hidden}
.class-card:hover{transform:translateY(-4px);border-color:var(--gold);box-shadow:0 12px 40px rgba(0,0,0,.6),0 0 0 1px rgba(212,166,90,.2)}
.class-card h3{color:var(--gold);letter-spacing:1px}
.class-icon{font-size:44px;margin:10px 0}
.class-desc{color:#a8a8c0;font-size:14px;line-height:1.5;min-height:48px}
.class-deck{margin-top:10px;display:flex;flex-wrap:wrap;gap:6px}
.mini-tag{background:#0f0f1a;border:1px solid #2a2a40;border-radius:999px;padding:3px 8px;font-size:11px;font-family:'JetBrains Mono',monospace;color:#c8c8d8}
.btn{appearance:none;border:none;cursor:pointer;padding:10px 16px;border-radius:10px;font-family:'Cinzel',serif;font-weight:700;letter-spacing:1px;transition:.2s}
.btn-gold{background:linear-gradient(180deg,#d4a65a,#8a6a2e);color:#0a0a12;box-shadow:0 4px 16px rgba(212,166,90,.3)}
.btn-gold:hover{filter:brightness(1.1);transform:translateY(-1px)}
.btn-ghost{background:#1a1a2e;color:#e8e0d0;border:1px solid #2a2a40}
.btn-ghost:hover{border-color:var(--gold);color:var(--gold)}
.btn-danger{background:#c0392b;color:#fff}
.btn-small{padding:6px 10px;font-size:12px;border-radius:8px}

/* map */
.map-wrap{background:linear-gradient(180deg,#0f0f1f 0%,#0a0a12 100%);border:1px solid #2a2a40;border-radius:16px;padding:16px;position:relative;overflow:hidden}
.map-title{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.map-grid{display:flex;flex-direction:column;gap:8px;max-height:560px;overflow:auto;padding-right:6px}
.floor{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:10px;background:rgba(255,255,255,.02);border:1px solid transparent}
.floor.current{border-color:var(--gold);background:rgba(212,166,90,.08);box-shadow:0 0 0 1px rgba(212,166,90,.15) inset}
.floor.done{opacity:.55}
.floor-label{width:70px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.nodes{display:flex;gap:8px;flex-wrap:wrap;flex:1}
.node{flex:1;min-width:140px;max-width:260px;display:flex;align-items:center;gap:8px;padding:10px 12px;border-radius:10px;border:1px solid #2a2a40;background:#141428;cursor:pointer;transition:.2s}
.node:hover{border-color:var(--gold);transform:translateY(-1px)}
.node.disabled{opacity:.4;pointer-events:none}
.node.active-choice{border-color:var(--gold);background:rgba(212,166,90,.12);box-shadow:0 0 12px rgba(212,166,90,.25)}
.node-icon{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:16px;flex-shrink:0}
.node-enemy .node-icon{background:#2a1a1a;color:#e74c3c}
.node-elite .node-icon{background:#3a1a2a;color:#ff6b9d}
.node-rest .node-icon{background:#1a2a1a;color:#2ecc71}
.node-shop .node-icon{background:#1a1a2a;color:var(--gold)}
.node-event .node-icon{background:#1a1a2e;color:#7a3cff}
.node-boss .node-icon{background:linear-gradient(135deg,#c0392b,#7a3cff);color:#fff}
.node-name{font-family:'Cinzel',serif;font-size:12px;letter-spacing:.5px}
.node-sub{font-size:11px;color:var(--muted);font-family:'JetBrains Mono',monospace}

/* combat */
.combat-layout{display:grid;grid-template-columns:1fr 340px;gap:16px}
@media(max-width:1000px){.combat-layout{grid-template-columns:1fr}}
.battlefield{background:radial-gradient(800px 400px at 50% 0%,#1a1430 0%,#0f0f1e 55%,#0a0a12 100%);border:1px solid #2a2a40;border-radius:16px;padding:14px;position:relative;overflow:hidden}
.enemy-row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;min-height:180px;align-items:flex-start}
.enemy-card{background:linear-gradient(180deg,#1e1e32,#141428);border:1px solid #2a2a40;border-radius:14px;padding:12px;width:210px;position:relative;transition:.2s}
.enemy-card.targetable{cursor:pointer;box-shadow:0 0 0 2px var(--gold)}
.enemy-card.dead{opacity:.35;filter:grayscale(1)}
.enemy-top{display:flex;justify-content:space-between;align-items:center}
.enemy-name{font-family:'Cinzel',serif;font-size:13px;color:#fff}
.enemy-hp{height:8px;background:#111;border-radius:999px;overflow:hidden;border:1px solid #333;margin:8px 0}
.enemy-hp-fill{height:100%;background:linear-gradient(90deg,#c0392b,#ff6b6b);transition:width .3s}
.intent{margin-top:8px;padding:7px 8px;border-radius:8px;background:#0f0f1a;border:1px solid #2a2a40;font-family:'JetBrains Mono',monospace;font-size:11px;display:flex;align-items:center;gap:6px}
.intent-atk{border-color:#c0392b;color:#ff8a80}
.intent-def{border-color:#2ecc71;color:#a5d6a7}
.intent-buff{border-color:#7a3cff;color:#b39ddb}
.intent-debuff{border-color:#ff9800;color:#ffcc80}
.block-badge{background:#2c3e50;color:#aed6f1;border:1px solid #34495e;padding:2px 6px;border-radius:999px;font-size:11px;font-family:'JetBrains Mono',monospace}
.status-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.status{font-size:10px;padding:2px 6px;border-radius:999px;border:1px solid #333;background:#0f0f1a;font-family:'JetBrains Mono',monospace}
.s-buff{border-color:#2ecc71;color:#a5d6a7}
.s-debuff{border-color:#c0392b;color:#ff8a80}
.s-special{border-color:#7a3cff;color:#d1c4e9}

.player-area{margin-top:14px;background:linear-gradient(180deg,#141428,#0f0f1e);border:1px solid #2a2a40;border-radius:14px;padding:12px;display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.player-info{display:flex;align-items:center;gap:12px}
.avatar{width:52px;height:52px;border-radius:12px;background:linear-gradient(135deg,#2a1a3a,#1a2a3a);display:grid;place-items:center;font-size:26px;border:1px solid #2a2a40}
.energy{display:flex;gap:6px;align-items:center}
.energy-orb{width:32px;height:32px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#ffd54f,#ff8f00);display:grid;place-items:center;font-weight:900;color:#3e2723;box-shadow:0 0 12px rgba(255,143,0,.5);font-family:'Cinzel',serif}
.hand{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;padding:14px 6px;min-height:170px}
.card{width:148px;min-height:188px;background:linear-gradient(180deg,#1e1e32 0%,#12121f 100%);border:1px solid #2a2a40;border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:6px;cursor:pointer;transition:.18s;position:relative;overflow:hidden}
.card:hover{transform:translateY(-6px) scale(1.02);border-color:var(--gold);box-shadow:0 12px 30px rgba(0,0,0,.5)}
.card.disabled{opacity:.45;pointer-events:none;filter:grayscale(.3)}
.card.selected{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold),0 12px 30px rgba(0,0,0,.5)}
.card-top{display:flex;justify-content:space-between;align-items:center}
.card-cost{width:26px;height:26px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#ffd54f,#ff8f00);display:grid;place-items:center;font-weight:900;font-size:12px;color:#3e2723;font-family:'Cinzel',serif;box-shadow:0 0 8px rgba(255,143,0,.4)}
.card-type{font-size:9px;letter-spacing:1px;text-transform:uppercase;font-family:'JetBrains Mono',monospace;padding:2px 6px;border-radius:999px;border:1px solid #2a2a40}
.type-attack{color:#ff8a80;border-color:#4a1a1a;background:rgba(192,57,43,.15)}
.type-skill{color:#82b1ff;border-color:#1a2a4a;background:rgba(52,73,94,.4)}
.type-power{color:#ce93d8;border-color:#3a1a4a;background:rgba(122,60,255,.18)}
.card-name{font-family:'Cinzel',serif;font-size:12px;color:#fff;line-height:1.2;min-height:28px}
.card-desc{font-size:11.5px;line-height:1.35;color:#c8c8d8;flex:1}
.card-up{position:absolute;top:6px;right:6px;font-size:9px;background:var(--gold);color:#0a0a12;padding:1px 5px;border-radius:999px;font-family:'JetBrains Mono',monospace;font-weight:700}
.pile-info{display:flex;gap:8px;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--muted)}
.combat-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.log{max-height:220px;overflow:auto;background:#0f0f1a;border:1px solid #2a2a40;border-radius:12px;padding:10px;font-family:'JetBrains Mono',monospace;font-size:11px;line-height:1.5;color:#c8c8d8}
.log b{color:#fff}

/* side panel */
.side{display:flex;flex-direction:column;gap:12px}
.panel{background:#14141f;border:1px solid #2a2a40;border-radius:14px;padding:12px}
.panel h4{font-family:'Cinzel',serif;font-size:12px;letter-spacing:1px;color:var(--gold);margin-bottom:8px}
.relic-grid{display:flex;flex-wrap:wrap;gap:6px}
.relic{width:32px;height:32px;border-radius:8px;background:#1a1a2e;border:1px solid #2a2a40;display:grid;place-items:center;font-size:14px;cursor:help}
.potion-row{display:flex;gap:8px;flex-wrap:wrap}
.potion{flex:1;min-width:90px;background:#1a1a2e;border:1px solid #2a2a40;border-radius:10px;padding:8px;text-align:center;cursor:pointer}
.potion:hover{border-color:var(--gold)}
.potion-name{font-size:11px;font-family:'Cinzel',serif;color:#fff}
.potion-desc{font-size:10px;color:var(--muted);margin-top:2px;line-height:1.3}

/* shop / rest / event / reward */
.shop-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
@media(max-width:900px){.shop-grid{grid-template-columns:1fr}}
.shop-card{background:#141428;border:1px solid #2a2a40;border-radius:12px;padding:12px}
.price{font-family:'JetBrains Mono',monospace;color:var(--gold);font-weight:700;margin-top:6px}
.reward-choices{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.choice-card{width:170px}
.overlay{position:fixed;inset:0;background:rgba(5,5,12,.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:100;padding:18px}
.overlay.open{display:flex}
.modal{background:linear-gradient(180deg,#1a1a2e,#12121f);border:1px solid #2a2a40;border-radius:16px;padding:18px;max-width:720px;width:100%;max-height:90vh;overflow:auto;box-shadow:0 20px 60px rgba(0,0,0,.6)}
.deck-grid{display:flex;flex-wrap:wrap;gap:8px}
.tag{font-size:10px;padding:2px 6px;border-radius:999px;border:1px solid #2a2a40;background:#0f0f1a;color:var(--muted);font-family:'JetBrains Mono',monospace}
.divider{height:1px;background:#2a2a40;margin:12px 0}
.kbd{font-family:'JetBrains Mono',monospace;background:#0f0f1a;border:1px solid #2a2a40;border-bottom-width:2px;padding:2px 6px;border-radius:6px;font-size:11px}
</style>
</head>
<body>
<div class="topbar">
  <div class="brand">SHADOWS OF THE ABYSS</div>
  <div class="stats">
    <div class="stat-pill"><span>♥</span><div class="hp-bar"><div id="hpFill" class="hp-fill" style="width:100%"></div></div><span id="hpText" class="mono">70/70</span><span id="blockText" class="block-badge" style="display:none">0 Block</span></div>
    <div class="stat-pill"><span>◈</span><span id="goldText" class="gold mono">99 Gold</span></div>
    <div class="stat-pill"><span id="actText" class="act-badge">ACT I — THE CRYPTS</span></div>
    <div class="stat-pill mono" id="deckInfo">Deck 10</div>
    <button class="btn btn-ghost btn-small" onclick="showDeck()">Deck</button>
    <button class="btn btn-ghost btn-small" onclick="abandonRun()">Abandon</button>
  </div>
</div>

<div class="wrap">
  <!-- CLASS SELECT -->
  <div id="view-class" class="view active">
    <div class="hero">
      <h1>Choose Your Curse</h1>
      <p>Fallen heroes wielding forbidden magic. Each archetype bears a unique deck, relics and damnation. Survive 3 acts. The Archdemon awaits at the Spire's peak.</p>
      <div class="class-grid" id="classGrid"></div>
      <div style="margin-top:18px;color:var(--muted);font-size:13px">Energy 3 • Draw 5 • Permadeath • Predictable enemy intents</div>
    </div>
  </div>

  <!-- MAP -->
  <div id="view-map" class="view">
    <div class="map-wrap">
      <div class="map-title">
        <h2 style="color:var(--gold)">World Map</h2>
        <div style="display:flex;gap:8px;align-items:center">
          <span class="tag" id="mapProgress">Floor 2 / 15</span>
          <button class="btn btn-ghost btn-small" onclick="showDeck()">View Deck</button>
        </div>
      </div>
      <div id="mapGrid" class="map-grid"></div>
      <div class="divider"></div>
      <div style="display:flex;gap:8px;flex-wrap:wrap">
        <span class="tag">◈ Enemy</span><span class="tag" style="color:#ff6b9d">⬢ Elite (Relic)</span><span class="tag" style="color:#2ecc71">✦ Rest</span><span class="tag" style="color:var(--gold)">⌖ Shop</span><span class="tag" style="color:#7a3cff">✧ Event</span><span class="tag" style="color:#c0392b">♛ Boss</span>
      </div>
    </div>
  </div>

  <!-- COMBAT -->
  <div id="view-combat" class="view">
    <div class="combat-layout">
      <div class="battlefield">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;gap:8px;flex-wrap:wrap">
          <h3 style="color:var(--gold);font-size:14px;letter-spacing:1px" id="combatTitle">Combat</h3>
          <div class="pile-info"><span id="drawCount">Draw 0</span><span>•</span><span id="discardCount">Discard 0</span><span>•</span><span id="exhaustCount">Exhaust 0</span></div>
        </div>
        <div id="enemyRow" class="enemy-row"></div>
        <div class="player-area">
          <div class="player-info">
            <div class="avatar" id="playerAvatar">☠</div>
            <div>
              <div style="font-family:'Cinzel',serif;font-size:13px" id="playerName">Blood Reaper</div>
              <div class="status-row" id="playerStatuses"></div>
              <div style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--muted)" id="playerBlockLine">Block 0 • Powers 0</div>
            </div>
          </div>
          <div class="energy"><div class="energy-orb" id="energyOrb">3</div><div class="mono" style="font-size:12px;color:var(--muted)">Energy</div></div>
          <div class="combat-actions">
            <button class="btn btn-gold" onclick="endTurn()">End Turn <span class="kbd">E</span></button>
            <button class="btn btn-ghost btn-small" onclick="viewDiscard()">Piles</button>
          </div>
        </div>
        <div id="hand" class="hand"></div>
        <div style="text-align:center;color:var(--muted);font-size:11px;font-family:'JetBrains Mono',monospace">Click a card, then click an enemy if it requires a target. Unplayable cards are dimmed.</div>
      </div>
      <div class="side">
        <div class="panel">
          <h4>Combat Log</h4>
          <div id="combatLog" class="log"></div>
        </div>
        <div class="panel">
          <h4>Relics & Potions</h4>
          <div id="relicList" class="relic-grid"></div>
          <div style="height:8px"></div>
          <div id="potionList" class="potion-row"></div>
        </div>
        <div class="panel">
          <h4>Tips</h4>
          <div style="font-size:12px;color:#a8a8c0;line-height:1.5">
            • Block resets at start of your turn.<br>
            • Weak −25% dmg, Vulnerable +50% taken.<br>
            • Bleed/Burn halve each turn, Poison −1.<br>
            • Intangible reduces all damage to 1.
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- SHOP -->
  <div id="view-shop" class="view">
    <div class="map-wrap">
      <div style="display:flex;justify-content:space-between;align-items:center">
        <h2 style="color:var(--gold)">Shop — The Veiled Bazaar</h2>
        <button class="btn btn-ghost btn-small" onclick="leaveShop()">Leave</button>
      </div>
      <p style="color:var(--muted);margin:8px 0">Spend gold wisely. Card removal costs 75g. Potions 50g. Relics 150g.</p>
      <h4 style="color:var(--gold);margin:10px 0">Cards</h4>
      <div id="shopCards" class="shop-grid"></div>
      <h4 style="color:var(--gold);margin:14px 0 10px">Relics</h4>
      <div id="shopRelics" class="shop-grid"></div>
      <h4 style="color:var(--gold);margin:14px 0 10px">Potions</h4>
      <div id="shopPotions" class="shop-grid"></div>
      <div class="divider"></div>
      <button class="btn btn-ghost" onclick="buyRemoval()">Remove a Card — 75g</button>
      <span id="shopMsg" class="mono" style="margin-left:10px;color:var(--gold);font-size:12px"></span>
    </div>
  </div>

  <!-- REST -->
  <div id="view-rest" class="view">
    <div class="map-wrap" style="text-align:center;padding:28px">
      <h2 style="color:var(--gold)">Rest Site — Flickering Campfire</h2>
      <p style="color:var(--muted);margin:8px 0">Choose one. The fire will not wait.</p>
      <div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:16px">
        <button class="btn btn-gold" onclick="restHeal()">Rest — Heal 25% (17 HP)</button>
        <button class="btn btn-ghost" onclick="restForge()">Forge — Upgrade a Card</button>
        <button class="btn btn-ghost" onclick="restMeditate()">Meditate — Remove a Card</button>
      </div>
      <div style="margin-top:16px"><button class="btn btn-ghost btn-small" onclick="leaveRest()">Leave</button></div>
    </div>
  </div>

  <!-- EVENT -->
  <div id="view-event" class="view">
    <div class="map-wrap" style="max-width:720px;margin:0 auto">
      <h2 id="eventTitle" style="color:var(--gold)"></h2>
      <p id="eventText" style="color:#c8c8d8;margin:10px 0;line-height:1.6"></p>
      <div id="eventChoices" style="display:flex;flex-direction:column;gap:8px;margin-top:12px"></div>
      <div style="margin-top:12px"><button class="btn btn-ghost btn-small" onclick="leaveEvent()">Leave</button></div>
    </div>
  </div>

  <!-- REWARD -->
  <div id="view-reward" class="view">
    <div class="map-wrap" style="text-align:center">
      <h2 style="color:var(--gold)" id="rewardTitle">Victory!</h2>
      <p id="rewardSubtitle" style="color:var(--muted)"></p>
      <div id="rewardGold" style="margin:10px 0;font-family:'JetBrains Mono',monospace;color:var(--gold)"></div>
      <h4 style="color:var(--gold);margin:12px 0">Choose a Card (or Skip)</h4>
      <div id="rewardCards" class="reward-choices"></div>
      <div style="margin-top:12px;display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
        <button class="btn btn-ghost" onclick="skipCardReward()">Skip</button>
        <button class="btn btn-gold" onclick="continueFromReward()">Continue</button>
      </div>
      <div id="rewardExtra" style="margin-top:12px"></div>
    </div>
  </div>

  <!-- VICTORY / DEFEAT -->
  <div id="view-end" class="view">
    <div class="map-wrap" style="text-align:center;padding:40px">
      <h1 id="endTitle" style="font-family:'Cinzel Decorative',serif;color:var(--gold)"></h1>
      <p id="endText" style="color:var(--muted);margin:10px 0"></p>
      <button class="btn btn-gold" onclick="resetToClassSelect()">Return to Title</button>
    </div>
  </div>
</div>

<!-- MODALS -->
<div id="deckModal" class="overlay" onclick="if(event.target===this)closeDeck()">
  <div class="modal">
    <div style="display:flex;justify-content:space-between;align-items:center">
      <h3 style="color:var(--gold)">Your Deck</h3>
      <button class="btn btn-ghost btn-small" onclick="closeDeck()">Close</button>
    </div>
    <div id="deckModalContent" class="deck-grid" style="margin-top:12px"></div>
    <div class="divider"></div>
    <div style="font-size:12px;color:var(--muted)" id="deckStats"></div>
  </div>
</div>

<div id="choiceModal" class="overlay">
  <div class="modal">
    <h3 id="choiceTitle" style="color:var(--gold)"></h3>
    <p id="choiceDesc" style="color:var(--muted);margin:6px 0"></p>
    <div id="choiceGrid" class="deck-grid" style="margin-top:12px"></div>
    <div style="margin-top:12px;text-align:right"><button class="btn btn-ghost btn-small" onclick="closeChoice()">Cancel</button></div>
  </div>
</div>

<script>
/* ===== DATA ===== */
const CLASSES={
  blood:{name:'Blood Reaper',icon:'🩸',color:'#c0392b',desc:'Sacrifice HP for overwhelming power. Bleed, life-steal and Strength scaling.',sig:'Sanguine Strike'},
  shadow:{name:'Shadowmancer',icon:'🌑',color:'#7a3cff',desc:'Cripple foes with Weak & Vulnerable. Intangible and card manipulation.',sig:'Shadow Bolt'},
  hell:{name:'Hellforged',icon:'🔥',color:'#ff6a00',desc:'Burn enemies, summon Imps and manipulate Energy. Fire is your ally.',sig:'Firebolt'}
};

const CARD_DB={
  // basics
  Strike:{name:'Strike',type:'Attack',cost:1,rarity:'Basic',desc:'Deal 6 damage.',upDesc:'Deal 8 damage.',dmg:6,upDmg:8},
  Defend:{name:'Defend',type:'Skill',cost:1,rarity:'Basic',desc:'Gain 5 Block.',upDesc:'Gain 8 Block.',block:5,upBlock:8},
  // Blood
  'Sanguine Strike':{name:'Sanguine Strike',type:'Attack',cost:1,rarity:'Common',class:'blood',desc:'Deal 8 damage. Heal 2 HP.',upDesc:'Deal 10 damage. Heal 3 HP.',dmg:8,upDmg:10,heal:2,upHeal:3},
  Hemorrhage:{name:'Hemorrhage',type:'Attack',cost:1,rarity:'Common',class:'blood',desc:'Deal 5 damage. Apply 3 Bleed.',upDesc:'Deal 6 damage. Apply 4 Bleed.',dmg:5,upDmg:6,bleed:3,upBleed:4},
  Bloodletting:{name:'Bloodletting',type:'Attack',cost:1,rarity:'Uncommon',class:'blood',desc:'Lose 3 HP. Deal 12 damage.',upDesc:'Lose 2 HP. Deal 15 damage.',dmg:12,upDmg:15,hpCost:3,upHpCost:2},
  'Crimson Eruption':{name:'Crimson Eruption',type:'Attack',cost:2,rarity:'Rare',class:'blood',desc:'Deal 8 damage to ALL enemies. Lose 2 HP.',upDesc:'Deal 10 damage to ALL enemies. Lose 2 HP.',dmg:8,upDmg:10,aoe:true,hpCost:2},
  'Crimson Shield':{name:'Crimson Shield',type:'Skill',cost:1,rarity:'Common',class:'blood',desc:'Gain 6 Block. Lose 2 HP.',upDesc:'Gain 8 Block. Lose 2 HP.',block:6,upBlock:8,hpCost:2},
  'Blood Ward':{name:'Blood Ward',type:'Skill',cost:1,rarity:'Common',class:'blood',desc:'Gain 3 Block. Gain 1 Regeneration.',upDesc:'Gain 4 Block. Gain 2 Regeneration.',block:3,upBlock:4,regen:1,upRegen:2},
  'Sacrificial Barrier':{name:'Sacrificial Barrier',type:'Skill',cost:2,rarity:'Uncommon',class:'blood',desc:'Gain 10 Block. Lose 5 HP.',upDesc:'Gain 13 Block. Lose 5 HP.',block:10,upBlock:13,hpCost:5},
  'Vital Surge':{name:'Vital Surge',type:'Skill',cost:'X',rarity:'Rare',class:'blood',desc:'Gain Block equal to missing HP. Exhaust.',upDesc:'Gain Block equal to missing HP +3. Exhaust.',exhaust:true,isX:true},
  'Life Tap':{name:'Life Tap',type:'Skill',cost:0,rarity:'Common',class:'blood',desc:'Draw 2 cards. Lose 3 HP.',upDesc:'Draw 3 cards. Lose 3 HP.',draw:2,upDraw:3,hpCost:3},
  'Rend Flesh':{name:'Rend Flesh',type:'Skill',cost:1,rarity:'Uncommon',class:'blood',desc:'Apply 5 Bleed to ALL enemies. Lose 2 HP.',upDesc:'Apply 7 Bleed to ALL enemies. Lose 2 HP.',bleed:5,upBleed:7,aoe:true,hpCost:2},
  'Soul Siphon':{name:'Soul Siphon',type:'Power',cost:2,rarity:'Uncommon',class:'blood',desc:'At start of your turn, gain 1 Energy and lose 1 HP.',upDesc:'At start of your turn, gain 1 Energy and lose 1 HP. (Cost 1)',upCost:1},
  'Blood Pact':{name:'Blood Pact',type:'Power',cost:1,rarity:'Rare',class:'blood',desc:'Whenever you lose HP from a card, gain 1 Strength.',upDesc:'Whenever you lose HP from a card, gain 2 Strength.',strPerHp:1,upStrPerHp:2},
  // Shadow
  'Shadow Bolt':{name:'Shadow Bolt',type:'Attack',cost:1,rarity:'Common',class:'shadow',desc:'Deal 6 damage. Apply 1 Weak.',upDesc:'Deal 8 damage. Apply 2 Weak.',dmg:6,upDmg:8,weak:1,upWeak:2},
  'Grasp of Despair':{name:'Grasp of Despair',type:'Attack',cost:1,rarity:'Common',class:'shadow',desc:'Deal 4 damage. Apply 1 Vulnerable.',upDesc:'Deal 5 damage. Apply 2 Vulnerable.',dmg:4,upDmg:5,vuln:1,upVuln:2},
  'Nightmare Lash':{name:'Nightmare Lash',type:'Attack',cost:2,rarity:'Uncommon',class:'shadow',desc:'Deal 7 damage. If enemy has Weak, deal 7 more.',upDesc:'Deal 9 damage. If Weak, deal 9 more.',dmg:7,upDmg:9,bonusIfWeak:true},
  'Eternal Darkness':{name:'Eternal Darkness',type:'Attack',cost:2,rarity:'Rare',class:'shadow',desc:'Deal 3 damage to random enemy 3 times.',upDesc:'Deal 4 damage to random enemy 3 times.',dmg:3,upDmg:4,hits:3,random:true},
  'Cloak of Shadows':{name:'Cloak of Shadows',type:'Skill',cost:1,rarity:'Common',class:'shadow',desc:'Gain 5 Block. Draw 1 card.',upDesc:'Gain 7 Block. Draw 1 card.',block:5,upBlock:7,draw:1},
  'Haunting Mist':{name:'Haunting Mist',type:'Skill',cost:1,rarity:'Common',class:'shadow',desc:'Gain 7 Block. Apply 1 Weak to ALL.',upDesc:'Gain 9 Block. Apply 1 Weak to ALL.',block:7,upBlock:9,weak:1,aoe:true},
  'Phantom Armor':{name:'Phantom Armor',type:'Skill',cost:1,rarity:'Uncommon',class:'shadow',desc:'Gain 8 Block. Next turn gain 2 Dexterity.',upDesc:'Gain 10 Block. Next turn gain 3 Dexterity.',block:8,upBlock:10,nextDex:2,upNextDex:3},
  'Whispering Shroud':{name:'Whispering Shroud',type:'Skill',cost:1,rarity:'Rare',class:'shadow',desc:'Gain 4 Block. Next turn gain 4 Block.',upDesc:'Gain 5 Block. Next turn gain 5 Block.',block:4,upBlock:5,nextBlock:4,upNextBlock:5},
  Shadowform:{name:'Shadowform',type:'Skill',cost:2,rarity:'Uncommon',class:'shadow',desc:'Gain 2 Intangible. Exhaust.',upDesc:'Gain 3 Intangible. Exhaust.',exhaust:true,intangible:2,upIntangible:3},
  'Curse of Misfortune':{name:'Curse of Misfortune',type:'Skill',cost:1,rarity:'Common',class:'shadow',desc:'Enemy loses 3 Strength this turn. Apply 1 Vulnerable.',upDesc:'Enemy loses 4 Strength, Apply 2 Vulnerable.',strDown:3,upStrDown:4,vuln:1,upVuln:2},
  'Dark Ritual':{name:'Dark Ritual',type:'Skill',cost:0,rarity:'Uncommon',class:'shadow',desc:'Gain 2 Energy. Shuffle Dazed into draw pile.',upDesc:'Gain 2 Energy. Shuffle Dazed into discard pile.',energyGain:2},
  'Umbral Mastery':{name:'Umbral Mastery',type:'Power',cost:2,rarity:'Rare',class:'shadow',desc:'At start of your turn, apply 1 Weak to ALL.',upDesc:'At start of your turn, apply 2 Weak to ALL.',weakAura:1,upWeakAura:2},
  // Hellforged
  Firebolt:{name:'Firebolt',type:'Attack',cost:1,rarity:'Common',class:'hell',desc:'Deal 7 damage. Apply 2 Burn.',upDesc:'Deal 9 damage. Apply 3 Burn.',dmg:7,upDmg:9,burn:2,upBurn:3},
  'Hellfire Blast':{name:'Hellfire Blast',type:'Attack',cost:2,rarity:'Uncommon',class:'hell',desc:'Deal 9 damage to ALL enemies.',upDesc:'Deal 12 damage to ALL.',dmg:9,upDmg:12,aoe:true},
  'Searing Strike':{name:'Searing Strike',type:'Attack',cost:1,rarity:'Common',class:'hell',desc:'Deal 6 damage. Gain 1 Energy next turn.',upDesc:'Deal 8 damage. Gain 1 Energy next turn.',dmg:6,upDmg:8,nextEnergy:1},
  Immolate:{name:'Immolate',type:'Attack',cost:2,rarity:'Rare',class:'hell',desc:'Deal 12 damage. Exhaust.',upDesc:'Deal 16 damage. Exhaust.',dmg:12,upDmg:16,exhaust:true},
  'Obsidian Skin':{name:'Obsidian Skin',type:'Skill',cost:1,rarity:'Common',class:'hell',desc:'Gain 7 Block.',upDesc:'Gain 10 Block.',block:7,upBlock:10},
  'Flame Ward':{name:'Flame Ward',type:'Skill',cost:1,rarity:'Common',class:'hell',desc:'Gain 5 Block. Apply 2 Burn to random enemy.',upDesc:'Gain 6 Block. Apply 3 Burn.',block:5,upBlock:6,burn:2,upBurn:3,randomBurn:true},
  'Demonic Resilience':{name:'Demonic Resilience',type:'Skill',cost:1,rarity:'Uncommon',class:'hell',desc:'Gain 4 Block. Heal 2 HP.',upDesc:'Gain 5 Block. Heal 3 HP.',block:4,upBlock:5,heal:2,upHeal:3},
  'Brimstone Shield':{name:'Brimstone Shield',type:'Skill',cost:1,rarity:'Rare',class:'hell',desc:'Gain 3 Block. Gain 2 Strength this turn.',upDesc:'Gain 4 Block. Gain 3 Strength.',block:3,upBlock:4,str:2,upStr:3},
  'Summon Imp':{name:'Summon Imp',type:'Skill',cost:1,rarity:'Common',class:'hell',desc:'Add Imp Claw (0-cost 4 dmg) to hand. Exhaust.',upDesc:'Add Imp Claw+ (6 dmg) instead. Exhaust.',exhaust:true,summonImp:true},
  'Infernal Pact':{name:'Infernal Pact',type:'Skill',cost:0,rarity:'Uncommon',class:'hell',desc:'Lose 2 HP. Gain 2 Energy. Add Imp Claw.',upDesc:'Lose 1 HP. Gain 2 Energy. Add Imp Claw+.',hpCost:2,upHpCost:1,energyGain:2,summonImp:true},
  'Hellfire Aura':{name:'Hellfire Aura',type:'Power',cost:2,rarity:'Rare',class:'hell',desc:'At start of turn, deal 3 damage to ALL.',upDesc:'At start of turn, deal 5 damage to ALL.',auraDmg:3,upAuraDmg:5},
  'Soul Burn':{name:'Soul Burn',type:'Skill',cost:1,rarity:'Uncommon',class:'hell',desc:'Apply 8 Burn to an enemy. Draw 1.',upDesc:'Apply 10 Burn. Draw 1.',burn:8,upBurn:10,draw:1},
  // Colorless
  'Flash of Insight':{name:'Flash of Insight',type:'Skill',cost:1,rarity:'Common',desc:'Draw 2 cards.',upDesc:'Draw 3 cards.',draw:2,upDraw:3,colorless:true},
  'Iron Will':{name:'Iron Will',type:'Skill',cost:1,rarity:'Common',desc:'Gain 8 Block.',upDesc:'Gain 11 Block.',block:8,upBlock:11,colorless:true},
  'Quick Slash':{name:'Quick Slash',type:'Attack',cost:1,rarity:'Common',desc:'Deal 7 damage.',upDesc:'Deal 9 damage.',dmg:7,upDmg:9,colorless:true},
  'Potent Brew':{name:'Potent Brew',type:'Skill',cost:0,rarity:'Uncommon',desc:'Gain 1 Energy. Exhaust.',upDesc:'Gain 2 Energy. Exhaust.',exhaust:true,energyGain:1,upEnergyGain:2,colorless:true},
  // Curses/Status
  Wound:{name:'Wound',type:'Curse',cost:99,rarity:'Curse',desc:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true},
  Doubt:{name:'Doubt',type:'Curse',cost:99,rarity:'Curse',desc:'Unplayable. At end of turn, lose 1 HP.',unplayable:true,doubt:true},
  Dazed:{name:'Dazed',type:'Status',cost:99,rarity:'Status',desc:'Unplayable. Ethereal. Exhaust.',unplayable:true,ethereal:true,exhaust:true},
  'Imp Claw':{name:'Imp Claw',type:'Attack',cost:0,rarity:'Status',desc:'Deal 4 damage. Exhaust.',dmg:4,exhaust:true,token:true},
  'Imp Claw+':{name:'Imp Claw+',type:'Attack',cost:0,rarity:'Status',desc:'Deal 6 damage. Exhaust.',dmg:6,exhaust:true,token:true},
};

const RELICS=[
  {id:'Bloodstone',name:'Bloodstone',icon:'🩸',desc:'When you lose HP from a card, heal 1 HP.',rarity:'Common'},
  {id:'Demon Horns',name:'Demon Horns',icon:'� horns',desc:'Start of combat: add Imp Claw to hand.',rarity:'Common',cls:'hell'},
  {id:'Shadow Cloak',name:'Shadow Cloak',icon:'🥷',desc:'Start of combat: gain 1 Intangible.',rarity:'Rare'},
  {id:'Cursed Amulet',name:'Cursed Amulet',icon:'📿',desc:'Start of combat: apply 1 Weak to ALL enemies.',rarity:'Common'},
  {id:'Tome of Dark Rituals',name:'Tome of Dark Rituals',icon:'📖',desc:'When you play a Power, draw 1 card.',rarity:'Uncommon'},
  {id:'Bleeding Edge',name:'Bleeding Edge',icon:'🗡️',desc:'Start of turn: apply 1 Bleed to random enemy.',rarity:'Common'},
  {id:'Soul Ember',name:'Soul Ember',icon:'🔥',desc:'When you apply Burn, deal 2 extra damage.',rarity:'Rare'},
  {id:'Eternal Hourglass',name:'Eternal Hourglass',icon:'⏳',desc:'Start of turn if hand empty: gain 1 Energy.',rarity:'Uncommon'},
  {id:'Sacrificial Dagger',name:'Sacrificial Dagger',icon:'🗡️',desc:'First HP-cost Attack each combat costs 1 less HP.',rarity:'Uncommon',cls:'blood'},
  {id:'Ashen Armor',name:'Ashen Armor',icon:'🛡️',desc:'End of combat: heal 6 HP.',rarity:'Common'},
  {id:'Greedy Imp',name:'Greedy Imp',icon:'👹',desc:'Enemies drop +10 Gold.',rarity:'Common'},
  {id:'Vial of Shadows',name:'Vial of Shadows',icon:'🧪',desc:'On pickup: remove a card from deck.',rarity:'Rare'},
];

const BOSS_RELICS=[
  {id:'Cursed Crown',name:'Cursed Crown',icon:'👑',desc:'+1 Energy each turn. Curses cannot be removed.',energy:1},
  {id:'Ectoplasm',name:'Ectoplasm',icon:'👻',desc:'+1 Energy. Lose all Gold and gain no more.',energy:1},
  {id:'Philosopher Stone',name:'Philosopher Stone',icon:'💎',desc:'+1 Energy. Enemies start with 1 Strength.',energy:1},
  {id:'Black Blood',name:'Black Blood',icon:'🖤',desc:'Start combat −10% HP, but Attacks heal 1 HP.',energy:0},
  {id:'Shadow Amulet',name:'Shadow Amulet',icon:'🔮',desc:'Start each turn +1 Intangible. −15 Max HP.',energy:0},
];

const POTIONS=[
  {id:'Blood Vial',name:'Blood Vial',icon:'🧴',desc:'Heal 15 HP.'},
  {id:'Firebomb',name:'Firebomb',icon:'💣',desc:'Deal 10 damage to ALL.'},
  {id:'Dex Tonic',name:'Dex Tonic',icon:'🍷',desc:'Gain 3 Dexterity for 2 turns.'},
  {id:'Energy Elixir',name:'Energy Elixir',icon:'⚡',desc:'Gain 2 Energy this turn.'},
  {id:'Cursed Brew',name:'Cursed Brew',icon:'☠️',desc:'Apply 3 Vulnerable to ALL.'},
  {id:'Bleeding Flask',name:'Bleeding Flask',icon:'🧪',desc:'Apply 6 Bleed to an enemy.'},
  {id:'Imp-in-a-Bottle',name:'Imp-in-a-Bottle',icon:'😈',desc:'Add Imp Claw+ to hand.'},
  {id:'Smoke Bomb',name:'Smoke Bomb',icon:'💨',desc:'Escape non-boss combat.'},
];

const ENEMIES={
  // Act1
  Skeleton:{name:'Skeleton Warrior',hp:22,pattern:[{t:'atk',dmg:6},{t:'atk',dmg:6},{t:'def',block:8}]},
  Zombie:{name:'Zombie Brute',hp:28,pattern:[{t:'atk',dmg:8},{t:'debuff',weak:2},{t:'atk',dmg:8}]},
  Cultist:{name:'Dark Cultist',hp:20,pattern:[{t:'buff',str:3},{t:'atk',dmg:5},{t:'atk',dmg:5}]},
  Rat:{name:'Rat Swarm',hp:15,pattern:[{t:'multi',hits:2,dmg:3},{t:'multi',hits:2,dmg:3}]},
  Tomb:{name:'Tomb Guardian',hp:42,elite:true,pattern:[{t:'atk',dmg:6,block:8},{t:'multi',hits:3,dmg:4},{t:'atk',dmg:12}]},
  BloodWitch:{name:'Blood Witch',hp:36,elite:true,pattern:[{t:'atk',dmg:5,heal:4},{t:'debuff',vuln:1,weak:1},{t:'atk',dmg:7,heal:4}]},
  Lich:{name:'Lich King',hp:65,boss:true,pattern:[{t:'summon'},{t:'atk',dmg:8},{t:'def',block:12}]},
  // Act2
  Wraith:{name:'Wraith',hp:30,pattern:[{t:'atk',dmg:7,weak:1},{t:'atk',dmg:7},{t:'def',block:10}]},
  Beast:{name:'Corrupted Beast',hp:38,pattern:[{t:'atk',dmg:12},{t:'def',block:5,vulnSelf:1},{t:'atk',dmg:12}]},
  Thorn:{name:'Thorned Horror',hp:25,pattern:[{t:'multi',hits:3,dmg:4},{t:'debuff',bleed:3},{t:'atk',dmg:8}]},
  WitchDoc:{name:'Witch Doctor',hp:22,pattern:[{t:'buff',block:4,regen:2},{t:'atk',dmg:6,poison:2},{t:'atk',dmg:6}]},
  Treant:{name:'Treant Protector',hp:50,elite:true,pattern:[{t:'atk',dmg:9,block:6},{t:'debuff',vuln:2},{t:'multi',hits:2,dmg:10}]},
  Shaman:{name:'Plague Shaman',hp:42,elite:true,pattern:[{t:'debuff',poison:4},{t:'summon'},{t:'atk',dmg:8}]},
  Eldritch:{name:'Eldritch Treant',hp:75,boss:true,pattern:[{t:'atk',dmg:10,weak:2},{t:'def',block:15,regen:2},{t:'multi',hits:2,dmg:8}]},
  // Act3
  Hellhound:{name:'Hellhound',hp:26,pattern:[{t:'atk',dmg:8,burn:2},{t:'atk',dmg:8},{t:'def',block:9}]},
  ShadowDemon:{name:'Shadow Demon',hp:30,pattern:[{t:'atk',dmg:7,weak:1},{t:'atk',dmg:7},{t:'buff',intangible:1}]},
  ImpEnemy:{name:'Infernal Imp',hp:18,pattern:[{t:'atk',dmg:5},{t:'atk',dmg:5},{t:'summon'}]},
  AbyssCult:{name:'Abyssal Cultist',hp:24,pattern:[{t:'buff',str:2,aoeStr:true},{t:'atk',dmg:7},{t:'atk',dmg:7}]},
  Flamespawn:{name:'Flamespawn',hp:48,elite:true,pattern:[{t:'atk',dmg:9,burn:3},{t:'def',block:10,str:2},{t:'multi',hits:2,dmg:8}]},
  DreadKnight:{name:'Dread Knight',hp:55,elite:true,pattern:[{t:'atk',dmg:12},{t:'debuff',bleed:3},{t:'def',block:12,heal:4}]},
  Archdemon:{name:'Archdemon',hp:85,boss:true,pattern:[{t:'summon'},{t:'atk',dmg:12,burn:2},{t:'def',block:15,str:3}]},
};

const EVENTS=[
  {title:'The Forgotten Altar',text:'A blood-stained altar hums with power. Dark veins pulse beneath the stone.',choices:[
    {label:'Offer 15 HP → Random Rare Relic',act:s=>{if(s.hp<=15)return msg('Not enough HP');changeHp(-15);gainRelic(randomRelic('Rare'));logEvent('You bleed onto the altar. A relic emerges.');closeEventToMap();}},
    {label:'Pray → Heal 10 HP, gain Doubt',act:s=>{changeHp(10);addCardToDeck('Doubt');logEvent('You pray. A cold doubt seeps into your deck.');closeEventToMap();}},
    {label:'Leave',act:s=>{logEvent('You leave the altar untouched.');closeEventToMap();}},
  ]},
  {title:'Shadowy Peddler',text:'A hooded figure offers a trade, eyes glinting beneath the cowl.',choices:[
    {label:'Trade a card → Remove 1 card, +100 Gold',act:s=>{openChoice('Choose card to remove',s.deck, c=>{removeCard(c);s.gold+=100;updateTop();logEvent('Traded card for 100 Gold.');closeChoice();closeEventToMap();});}},
    {label:'Buy wares → −50 Gold, Potion + Common Relic',act:s=>{if(s.gold<50)return msg('Need 50 Gold');s.gold-=50;gainPotion();gainRelic(randomRelic('Common'));updateTop();logEvent('You buy the peddler\'s wares.');closeEventToMap();}},
    {label:'Attack → Fight Shadow Thief (+75 Gold)',act:s=>{closeEventToMap();startCombat([{key:'Cultist',hp:24}],true,75);}},
  ]},
  {title:'Cursed Spring',text:'A spring bubbles with dark water. It smells of iron and rot.',choices:[
    {label:'Drink → −10 HP, remove all Curses',act:s=>{changeHp(-10);let before=s.deck.length;s.deck=s.deck.filter(c=>!['Wound','Doubt','Dazed'].includes(baseName(c)));logEvent(`Curses purged: ${before-s.deck.length} removed.`);closeEventToMap();updateTop();}},
    {label:'Bottle it → Gain Cursed Brew',act:s=>{gainPotion('Cursed Brew');logEvent('You bottle the cursed water.');closeEventToMap();}},
    {label:'Leave',act:s=>{closeEventToMap();}},
  ]},
  {title:'Restless Dead',text:'Ghosts offer a bargain: vitality for knowledge.',choices:[
    {label:'Accept → −20 HP, upgrade 2 random cards',act:s=>{changeHp(-20);let upgradable=s.deck.filter(c=>!isUpgraded(c)&&CARD_DB[baseName(c)]);shuffle(upgradable);for(let i=0;i<Math.min(2,upgradable.length);i++)upgradeCard(upgradable[i]);logEvent('Ghosts upgrade your cards.');closeEventToMap();}},
    {label:'Refuse',act:s=>{closeEventToMap();}},
  ]},
  {title:'Impish Gambler',text:'A grinning imp challenges you to a game of chance.',choices:[
    {label:'Bet 30 Gold → 50% to win 60',act:s=>{if(s.gold<30)return msg('Need 30 Gold');s.gold-=30;if(Math.random()<0.5){s.gold+=60;logEvent('You won the bet! +30 Gold net.');}else logEvent('You lost the bet.');updateTop();closeEventToMap();}},
    {label:'Wager a card → Remove random card',act:s=>{if(!s.deck.length)return;let idx=Math.floor(Math.random()*s.deck.length);let c=s.deck[idx];let isCurse=['Wound','Doubt','Dazed'].includes(baseName(c));s.deck.splice(idx,1);if(isCurse){s.gold+=100;logEvent('Cursed card wagered! +100 Gold.');}else logEvent(`Wagered ${c} — lost.`);updateTop();closeEventToMap();}},
    {label:'Leave',act:s=>{closeEventToMap();}},
  ]},
  {title:'Ancient Library',text:'Dusty tomes contain forgotten knowledge. The air crackles.',choices:[
    {label:'Study → +1 Energy next combat, −5 HP',act:s=>{s.nextCombatEnergy=(s.nextCombatEnergy||0)+1;changeHp(-5);logEvent('You study. Next combat +1 Energy.');closeEventToMap();}},
    {label:'Read forbidden page → Random Power + Doubt',act:s=>{let pool=Object.keys(CARD_DB).filter(k=>CARD_DB[k].type==='Power'&&CARD_DB[k].class===s.cls);let pick=pool[Math.floor(Math.random()*pool.length)];addCardToDeck(pick);addCardToDeck('Doubt');logEvent(`Gained ${pick} and Doubt.`);closeEventToMap();}},
    {label:'Leave',act:s=>{closeEventToMap();}},
  ]},
];

/* ===== STATE ===== */
let state={
  cls:null,act:1,floor:1,maxHp:70,hp:70,gold:99,
  deck:[],relics:[],potions:[],bossRelics:[],
  map:[],currentFloorIdx:0,
  nextCombatEnergy:0,
  combat:null,
  reward:null,
  shop:null,
  event:null,
  pendingChoice:null
};

function baseName(c){return c.replace(/\+$/,'');}
function isUpgraded(c){return c.endsWith('+');}
function cardData(name){let b=baseName(name);let d=CARD_DB[b];if(!d)return null;let up=isUpgraded(name);return {...d,upgraded:up,name:name,displayName:name};}
function cardCost(d){if(d.cost==='X')return 'X';if(d.upgraded&&d.upCost!==undefined)return d.upCost;return d.cost;}
function cardDesc(d){
  let b=CARD_DB[baseName(d.name)];
  return d.upgraded? b.upDesc: b.desc;
}

/* ===== INIT ===== */
function initClassGrid(){
  const g=document.getElementById('classGrid');
  g.innerHTML='';
  Object.entries(CLASSES).forEach(([k,v])=>{
    let deckText={blood:'5 Strike, 4 Defend, 1 Sanguine Strike',shadow:'5 Strike, 4 Defend, 1 Shadow Bolt',hell:'5 Strike, 4 Defend, 1 Firebolt'}[k];
    let el=document.createElement('div');
    el.className='class-card';
    el.innerHTML=`<div class="class-icon">${v.icon}</div><h3>${v.name}</h3><div class="class-desc">${v.desc}</div>
    <div class="class-deck"><span class="mini-tag">${deckText}</span><span class="mini-tag">70 HP</span><span class="mini-tag">Sig: ${v.sig}</span></div>
    <div style="margin-top:12px"><button class="btn btn-gold btn-small">Choose ${v.name}</button></div>`;
    el.onclick=()=>chooseClass(k);
    g.appendChild(el);
  });
}
initClassGrid();

function chooseClass(k){
  state.cls=k;state.act=1;state.floor=1;state.hp=70;state.maxHp=70;state.gold=99;state.relics=[];state.potions=[];state.bossRelics=[];state.nextCombatEnergy=0;
  let sig={blood:'Sanguine Strike',shadow:'Shadow Bolt',hell:'Firebolt'}[k];
  state.deck=[];
  for(let i=0;i<5;i++)state.deck.push('Strike');
  for(let i=0;i<4;i++)state.deck.push('Defend');
  state.deck.push(sig);
  generateMap();
  updateTop();
  showView('map');
  renderMap();
  logEvent(`Began run as ${CLASSES[k].name}.`);
}
function updateTop(){
  document.getElementById('hpText').textContent=`${state.hp}/${state.maxHp}`;
  document.getElementById('hpFill').style.width=`${Math.max(0,state.hp/state.maxHp*100)}%`;
  let block = state.combat? state.combat.player.block : 0;
  let bt=document.getElementById('blockText');
  if(block>0){bt.style.display='inline-block';bt.textContent=`${block} Block`;}else bt.style.display='none';
  document.getElementById('goldText').textContent=`${state.gold} Gold`;
  document.getElementById('actText').textContent=`ACT ${state.act} — ${['THE CRYPTS','BLIGHTED WOODS','ABYSSAL SPIRE'][state.act-1]}`;
  document.getElementById('deckInfo').textContent=`Deck ${state.deck.length}`;
  document.getElementById('mapProgress').textContent=`Floor ${state.floor} / 15 • Act ${state.act}`;
  renderRelicsPotions();
}
function showView(id){
  document.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));
  document.getElementById('view-'+id).classList.add('active');
}

/* ===== MAP ===== */
function generateMap(){
  // fixed floor types per spec
  let types=['start','enemy','enemy','event','shop','enemy','rest','enemy','elite','event','enemy','elite','enemy','rest','boss'];
  state.map=[];
  for(let i=0;i<15;i++){
    let t=types[i];
    let floor={idx:i+1,type:t,options:[]};
    if(t==='start'){floor.options=[{label:'Start',sub:'Begin'}];}
    else if(t==='enemy'){
      // branching floors 2 and 8
      if(i+1===2) floor.options=[{label:'Skeleton Warrior',sub:'Enemy',enemies:[{key:'Skeleton'}]},{label:'Zombie Brute',sub:'Enemy',enemies:[{key:'Zombie'}]}];
      else if(i+1===8) floor.options=[{label:'Skeleton Pair',sub:'2× Skeleton',enemies:[{key:'Skeleton'},{key:'Skeleton'}]},{label:'Zombie Brute',sub:'Enemy',enemies:[{key:'Zombie'}]}];
      else if(i+1===3) floor.options=[{label:'Dark Cultist',sub:'Enemy',enemies:[{key:'Cultist'}]}];
      else if(i+1===6) floor.options=[{label:'Rat Swarm',sub:'Enemy',enemies:[{key:'Rat'}]}];
      else if(i+1===11) floor.options=[{label:'Cultist + Rat',sub:'Duo',enemies:[{key:'Cultist'},{key:'Rat'}]}];
      else if(i+1===13) floor.options=[{label:'Cultist Duo',sub:'2× Cultist',enemies:[{key:'Cultist'},{key:'Cultist'}]}];
      else floor.options=[{label:'Enemy',sub:'Combat',enemies:[{key:'Skeleton'}]}];
    } else if(t==='elite'){
      if(i+1===9) floor.options=[{label:'Tomb Guardian',sub:'Elite — Relic',enemies:[{key:'Tomb'}],elite:true},{label:'Skeleton + Rat',sub:'Skip Elite',enemies:[{key:'Skeleton'},{key:'Rat'}]}];
      else floor.options=[{label:'Blood Witch',sub:'Elite — Relic',enemies:[{key:'BloodWitch'}],elite:true},{label:'Zombie + Skeleton',sub:'Skip Elite',enemies:[{key:'Zombie'},{key:'Skeleton'}]}];
    } else if(t==='event'){ floor.options=[{label:'Mysterious Event',sub:'Choices & risk',event:true}];}
    else if(t==='shop'){ floor.options=[{label:'Veiled Bazaar',sub:'Shop',shop:true}];}
    else if(t==='rest'){ floor.options=[{label:'Campfire',sub:'Rest / Forge / Remove',rest:true}];}
    else if(t==='boss'){
      let bossKey=['Lich','Eldritch','Archdemon'][state.act-1];
      floor.options=[{label:ENEMIES[bossKey].name,sub:'Boss',enemies:[{key:bossKey}],boss:true}];
    }
    // act 2/3 enemy remap for non-branch floors
    if(state.act===2 && t==='enemy'){
      const remap={Skeleton:'Wraith',Zombie:'Beast',Cultist:'WitchDoc',Rat:'Thorn'};
      floor.options.forEach(o=>o.enemies=o.enemies.map(e=>({key:remap[e.key]||e.key})));
    }
    if(state.act===3 && t==='enemy'){
      const remap={Skeleton:'Hellhound',Zombie:'ShadowDemon',Cultist:'AbyssCult',Rat:'ImpEnemy'};
      floor.options.forEach(o=>o.enemies=o.enemies.map(e=>({key:remap[e.key]||e.key})));
    }
    if(state.act===2 && t==='elite'){
      floor.options.forEach(o=>{if(o.elite){o.enemies=[{key: i+1===9?'Treant':'Shaman'}];o.label=ENEMIES[o.enemies[0].key].name;}});
    }
    if(state.act===3 && t==='elite'){
      floor.options.forEach(o=>{if(o.elite){o.enemies=[{key: i+1===9?'Flamespawn':'DreadKnight'}];o.label=ENEMIES[o.enemies[0].key].name;}});
    }
    state.map.push(floor);
  }
  state.currentFloorIdx=1; // start at floor 2 index 1
  state.floor=1;
}
function renderMap(){
  const grid=document.getElementById('mapGrid');
  grid.innerHTML='';
  state.map.forEach((floor,idx)=>{
    let row=document.createElement('div');
    row.className='floor '+(idx<state.currentFloorIdx?'done':idx===state.currentFloorIdx?'current':'');
    let label=document.createElement('div');
    label.className='floor-label';
    label.textContent=`Floor ${floor.idx}`;
    row.appendChild(label);
    let nodes=document.createElement('div');
    nodes.className='nodes';
    floor.options.forEach((opt,oi)=>{
      let n=document.createElement('div');
      let typeCls='node-enemy';
      if(floor.type==='elite'&&opt.elite)typeCls='node-elite';
      else if(floor.type==='rest')typeCls='node-rest';
      else if(floor.type==='shop')typeCls='node-shop';
      else if(floor.type==='event')typeCls='node-event';
      else if(floor.type==='boss')typeCls='node-boss';
      n.className='node '+typeCls+(idx!==state.currentFloorIdx?' disabled':'');
      let icon='⚔';
      if(typeCls.includes('elite'))icon='⬢';
      if(typeCls.includes('rest'))icon='🔥';
      if(typeCls.includes('shop'))icon='◈';
      if(typeCls.includes('event'))icon='?';
      if(typeCls.includes('boss'))icon='♛';
      n.innerHTML=`<div class="node-icon">${icon}</div><div><div class="node-name">${opt.label}</div><div class="node-sub">${opt.sub}</div></div>`;
      if(idx===state.currentFloorIdx){
        n.onclick=()=>enterFloor(idx,oi);
      }
      nodes.appendChild(n);
    });
    row.appendChild(nodes);
    grid.appendChild(row);
  });
  updateTop();
}
function enterFloor(floorIdx,optIdx){
  let floor=state.map[floorIdx];
  let opt=floor.options[optIdx];
  state.currentFloorIdx=floorIdx;
  state.floor=floor.idx;
  if(floor.type==='enemy' || floor.type==='elite' || floor.type==='boss'){
    let enemies=opt.enemies.map(e=>{
      let base=ENEMIES[e.key];
      return {key:e.key,name:base.name,hp:base.hp,maxHp:base.hp,block:0,pattern:base.pattern,idx:0,status:{},elite:!!base.elite,boss:!!base.boss};
    });
    let isElite=!!opt.elite;
    let isBoss=!!opt.boss;
    startCombat(enemies,isElite?false:false,isBoss);
    // mark elite/boss flags for rewards
    state._lastWasElite=isElite;
    state._lastWasBoss=isBoss;
  } else if(floor.type==='shop'){
    openShop();
  } else if(floor.type==='rest'){
    showView('rest');
  } else if(floor.type==='event'){
    openEvent();
  }
  // start floor auto-advance after combat etc handled
}
function advanceMap(){
  state.currentFloorIdx++;
  if(state.currentFloorIdx>=state.map.length){
    // act complete -> next act or victory
    if(state.act<3){
      state.act++;state.hp=state.maxHp; // full heal after boss per spec
      generateMap();
      showView('map');
      renderMap();
      logEvent(`Entered Act ${state.act}. HP fully restored.`);
    } else {
      showEnd(true);
    }
  } else {
    state.floor=state.map[state.currentFloorIdx].idx;
    showView('map');
    renderMap();
  }
}

/* ===== COMBAT ===== */
function startCombat(enemyDefs,isEventFight,eventGold){
  // setup combat state
  let drawPile=shuffle([...state.deck]);
  let combat={
    player:{hp:state.hp,maxHp:state.maxHp,block:0,energy:3,baseEnergy:3,status:{strength:0,dex:0,weak:0,vuln:0,intangible:0,regen:0,poison:0,bleed:0,burn:0,dexNext:0,blockNext:0,energyNext:0},powers:[],nextTurn:{dex:0,block:0,energy:0}},
    enemies:enemyDefs.map(e=>({...e,block:0,status:{strength:0,weak:0,vuln:0,bleed:0,burn:0,poison:0,regen:0,intangible:0}})),
    draw:drawPile,discard:[],exhaust:[],hand:[],turn:1,
    selectedCard:null,selectedIdx:-1,
    isBoss:enemyDefs.some(e=>e.boss),
    isElite:enemyDefs.some(e=>e.elite),
    eventGold:eventGold||0
  };
  // relic start of combat effects
  if(hasRelic('Shadow Cloak')) combat.player.status.intangible+=1;
  if(hasRelic('Cursed Amulet')) combat.enemies.forEach(en=>en.status.weak=(en.status.weak||0)+1);
  if(hasRelic('Demon Horns') && state.cls==='hell'){combat.hand.push('Imp Claw');}
  if(state.bossRelics.includes('Shadow Amulet')) combat.player.status.intangible+=1;
  if(state.bossRelics.includes('Philosopher Stone')) combat.enemies.forEach(en=>en.status.strength=(en.status.strength||0)+1);
  if(state.bossRelics.includes('Black Blood')) combat.player.hp=Math.max(1,combat.player.hp - Math.floor(combat.player.maxHp*0.1));
  if(state.nextCombatEnergy){combat.player.energy+=state.nextCombatEnergy; combat.player.baseEnergy+=state.nextCombatEnergy; state.nextCombatEnergy=0;}
  // boss relic ectoplasm etc handled elsewhere
  state.combat=combat;
  // apply boss phase tracking
  combat.enemies.forEach(en=>en.phase=1);
  showView('combat');
  document.getElementById('combatTitle').textContent=(combat.isBoss?'BOSS — ':'')+combat.enemies.map(e=>e.name).join(' & ');
  document.getElementById('playerName').textContent=CLASSES[state.cls].name;
  document.getElementById('playerAvatar').textContent=CLASSES[state.cls].icon;
  logClear();log(`Combat started vs ${combat.enemies.map(e=>e.name).join(', ')}`);
  // initial draw
  drawCards(5);
  renderCombat();
}
function hasRelic(id){return state.relics.some(r=>r.id===id);}
function drawCards(n){
  let c=state.combat;
  for(let i=0;i<n;i++){
    if(c.draw.length===0){
      if(c.discard.length===0) break;
      c.draw=shuffle([...c.discard]);c.discard=[];
      log('Shuffled discard into draw pile.');
    }
    let card=c.draw.pop();
    // ethereal check? handled on end turn
    c.hand.push(card);
    // Wound/Doubt no on-draw effect except Doubt end turn
  }
}
function renderCombat(){
  let c=state.combat; if(!c) return;
  // enemies
  let row=document.getElementById('enemyRow');row.innerHTML='';
  c.enemies.forEach((en,idx)=>{
    let div=document.createElement('div');
    div.className='enemy-card'+(en.hp<=0?' dead':'')+(c.selectedCard&&needsTarget(c.selectedCard)?' targetable':'');
    div.onclick=()=>enemyClicked(idx);
    let intent=computeIntent(en,c);
    let intentHtml='';
    if(en.hp>0){
      let cls='intent-def';
      let txt=intent.text;
      if(intent.type==='atk' || intent.type==='multi') cls='intent-atk';
      else if(intent.type==='buff') cls='intent-buff';
      else if(intent.type==='debuff') cls='intent-debuff';
      intentHtml=`<div class="intent ${cls}"><span>${intent.icon}</span><span>${txt}</span></div>`;
    } else intentHtml=`<div class="intent" style="opacity:.6">Defeated</div>`;
    let statusHtml=renderStatus(en.status);
    let blockHtml=en.block>0?`<span class="block-badge">${en.block} Block</span>`:'';
    div.innerHTML=`
      <div class="enemy-top"><span class="enemy-name">${en.name}</span>${blockHtml}</div>
      <div class="enemy-hp"><div class="enemy-hp-fill" style="width:${Math.max(0,en.hp/en.maxHp*100)}%"></div></div>
      <div class="mono" style="font-size:11px;color:var(--muted)">${Math.max(0,en.hp)}/${en.maxHp} HP</div>
      ${intentHtml}
      <div class="status-row">${statusHtml}</div>
    `;
    row.appendChild(div);
  });
  // player
  document.getElementById('energyOrb').textContent=c.player.energy;
  document.getElementById('playerStatuses').innerHTML=renderStatus(c.player.status);
  document.getElementById('playerBlockLine').textContent=`Block ${c.player.block} • Powers ${c.player.powers.length} • Turn ${c.turn}`;
  document.getElementById('drawCount').textContent=`Draw ${c.draw.length}`;
  document.getElementById('discardCount').textContent=`Discard ${c.discard.length}`;
  document.getElementById('exhaustCount').textContent=`Exhaust ${c.exhaust.length}`;
  // hand
  let handEl=document.getElementById('hand');handEl.innerHTML='';
  c.hand.forEach((cardName,idx)=>{
    let d=cardData(cardName);
    let cost=cardCost(d);
    let desc=cardDesc(d);
    let canPlay = cost!=='X' ? c.player.energy>=cost : true;
    if(d.unplayable) canPlay=false;
    let div=document.createElement('div');
    div.className='card'+(!canPlay?' disabled':'')+(c.selectedIdx===idx?' selected':'');
    let typeCls=d.type==='Attack'?'type-attack':d.type==='Power'?'type-power':'type-skill';
    div.innerHTML=`
      <div class="card-top"><div class="card-cost">${cost}</div><div class="card-type ${typeCls}">${d.type}</div></div>
      <div class="card-name">${d.displayName}${d.upgraded?' +':''}</div>
      <div class="card-desc">${desc}</div>
      ${d.upgraded?'<div class="card-up">UPGRADED</div>':''}
    `;
    div.onclick=()=>cardClicked(idx);
    handEl.appendChild(div);
  });
  updateTop();
}
function renderStatus(s){
  let parts=[];
  if(s.strength) parts.push(`<span class="status s-buff">STR ${s.strength>0?'+':''}${s.strength}</span>`);
  if(s.dex) parts.push(`<span class="status s-buff">DEX ${s.dex>0?'+':''}${s.dex}</span>`);
  if(s.weak) parts.push(`<span class="status s-debuff">Weak ${s.weak}</span>`);
  if(s.vuln) parts.push(`<span class="status s-debuff">Vuln ${s.vuln}</span>`);
  if(s.intangible) parts.push(`<span class="status s-special">Intangible ${s.intangible}</span>`);
  if(s.regen) parts.push(`<span class="status s-buff">Regen ${s.regen}</span>`);
  if(s.bleed) parts.push(`<span class="status s-debuff">Bleed ${s.bleed}</span>`);
  if(s.burn) parts.push(`<span class="status s-debuff">Burn ${s.burn}</span>`);
  if(s.poison) parts.push(`<span class="status s-debuff">Poison ${s.poison}</span>`);
  return parts.join('');
}
function computeIntent(en,combat){
  if(en.hp<=0) return {type:'dead',icon:'✕',text:'Defeated'};
  let base=ENEMIES[en.key];
  // boss phase handling
  if(en.boss){
    // Lich phase 2, Eldritch phase2, Archdemon phase2
    if(en.key==='Lich' && en.hp<=30 && en.phase===1){en.phase=2;en.idx=0;log(`${en.name} enrages! +2 Strength per turn.`);}
    if(en.key==='Eldritch' && en.hp<=40 && en.phase===1){en.phase=2;en.idx=0;log(`${en.name} roots crack and empower!`);}
    if(en.key==='Archdemon' && en.hp<=45 && en.phase===1){en.phase=2;en.idx=0;log(`${en.name} unleashes hellfire!`);}
    if(en.phase===2){
      if(en.key==='Lich') base={pattern:[{t:'atk',dmg:10},{t:'atk',dmg:10},{t:'def',block:15}]};
      if(en.key==='Eldritch') base={pattern:[{t:'atk',dmg:14,vuln:1},{t:'def',block:20,heal:5},{t:'atk',dmg:14,vuln:1}]};
      if(en.key==='Archdemon') base={pattern:[{t:'atk',dmg:16},{t:'multi',hits:3,dmg:7},{t:'atk',dmg:16}]};
    }
  }
  let pat=base.pattern[en.idx % base.pattern.length];
  if(pat.t==='atk') return {type:'atk',icon:'⚔',text:`Attack ${pat.dmg + (en.status.strength||0)}${pat.burn?' +Burn':''}${pat.weak?' +Weak':''}`};
  if(pat.t==='multi') return {type:'multi',icon:'⚔',text:`Attack ${pat.hits}×${pat.dmg}`};
  if(pat.t==='def') return {type:'def',icon:'🛡',text:`Defend ${pat.block||0}${pat.heal?' Heal':''}`};
  if(pat.t==='buff') return {type:'buff',icon:'✦',text:`Buff${pat.str?` +${pat.str} STR`:''}${pat.block?` +${pat.block} Block`:''}${pat.intangible?' Intangible':''}`};
  if(pat.t==='debuff') return {type:'debuff',icon:'☠',text:`Debuff${pat.weak?' Weak':''}${pat.vuln?' Vulnerable':''}${pat.bleed?' Bleed':''}${pat.poison?' Poison':''}`};
  if(pat.t==='summon') return {type:'buff',icon:' summon',text:'Summon Minion'};
  return {type:'atk',icon:'?',text:'Unknown'};
}
function needsTarget(cardName){
  let d=cardData(cardName);
  let b=CARD_DB[baseName(cardName)];
  // single target attacks/skills that apply to one enemy
  let single=['Sanguine Strike','Hemorrhage','Bloodletting','Shadow Bolt','Grasp of Despair','Nightmare Lash','Firebolt','Searing Strike','Immolate','Curse of Misfortune','Soul Burn','Bleeding Flask'];
  if(b.aoe || b.random || b.randomBurn) return false;
  if(d.type==='Attack' && !b.aoe && !b.random) return true;
  if(['Hemorrhage','Shadow Bolt','Grasp of Despair','Firebolt','Soul Burn','Curse of Misfortune','Bleeding Flask'].includes(baseName(cardName))) return true;
  return false;
}
function cardClicked(idx){
  let c=state.combat;
  let name=c.hand[idx];
  let d=cardData(name);
  if(d.unplayable){log('Unplayable card.');return;}
  let cost=cardCost(d);
  if(cost!=='X' && c.player.energy<cost){log('Not enough Energy.');return;}
  if(needsTarget(name)){
    c.selectedCard=name;c.selectedIdx=idx;
    log(`Selected ${name} — choose a target.`);
    renderCombat();
  } else {
    playCard(idx,null);
  }
}
function enemyClicked(eIdx){
  let c=state.combat;
  if(c.selectedIdx<0) return;
  let en=c.enemies[eIdx];
  if(en.hp<=0){log('Target is defeated.');return;}
  playCard(c.selectedIdx,eIdx);
}
function playCard(handIdx,targetIdx){
  let c=state.combat;
  let name=c.hand[handIdx];
  let d=cardData(name);
  let b=CARD_DB[baseName(name)];
  let cost=cardCost(d);
  let Xval=c.player.energy;
  if(cost==='X'){
    // Vital Surge: spend all energy
    cost=Xval;
  }
  if(c.player.energy<cost){log('Not enough Energy.');return;}
  c.player.energy-=cost;
  // remove from hand
  c.hand.splice(handIdx,1);
  c.selectedCard=null;c.selectedIdx=-1;
  // handle hp cost
  let hpCost = d.upgraded && b.upHpCost!==undefined ? b.upHpCost : b.hpCost;
  if(hpCost){
    // Sacrificial Dagger relic
    if(hasRelic('Sacrificial Dagger') && !c._daggerUsed && hpCost>0){
      hpCost=Math.max(0,hpCost-1);c._daggerUsed=true;
    }
    let dmgHp=hpCost;
    // Intangible reduces to 1
    if(c.player.status.intangible>0) dmgHp=Math.min(dmgHp,1);
    c.player.hp-=dmgHp;
    log(`<b>${name}</b> costs ${dmgHp} HP.`);
    // Blood Pact / Bloodstone
    if(c.player.powers.includes('Blood Pact')||c.player.powers.includes('Blood Pact+')){
      let amt = c.player.powers.includes('Blood Pact+')?2:1;
      c.player.status.strength=(c.player.status.strength||0)+amt;
      log(`Blood Pact grants +${amt} Strength.`);
    } else if(state.relics.some(r=>r.id==='Blood Pact' || r.id==='Blood Pact Power')){}
    // check relic Bloodstone heal 1
    if(hasRelic('Bloodstone')){
      c.player.hp=Math.min(c.player.maxHp,c.player.hp+1);
      log('Bloodstone heals 1 HP.');
    }
    // also check Blood Pact power via combat powers array
    // handle generic Blood Pact relic? already
    if(c.player.hp<=0){checkPlayerDeath();return;}
  }
  // apply card effect
  applyCardEffect(name,targetIdx,c);
  // handle exhaust vs discard
  let isPower = d.type==='Power';
  if(isPower){
    c.player.powers.push(name);
    log(`<b>${name}</b> becomes a Power.`);
    if(hasRelic('Tome of Dark Rituals')){drawCards(1);log('Tome draws 1 card.');}
  } else if(b.exhaust){
    c.exhaust.push(name);
  } else {
    c.discard.push(name);
  }
  // post-play checks
  if(c.player.hp<=0){checkPlayerDeath();return;}
  checkEnemiesDead();
  if(c.enemies.every(e=>e.hp<=0)){
    winCombat();
    return;
  }
  renderCombat();
}
function applyCardEffect(name,targetIdx,c){
  let bName=baseName(name);
  let d=cardData(name);
  let b=CARD_DB[bName];
  let up=d.upgraded;
  let dmg = up && b.upDmg!==undefined ? b.upDmg : b.dmg;
  let block = up && b.upBlock!==undefined ? b.upBlock : b.block;
  let heal = up && b.upHeal!==undefined ? b.upHeal : b.heal;
  // helper to deal damage to enemy with calc
  function dealToEnemy(en,baseDmg){
    if(!en||en.hp<=0) return;
    let dmgCalc=baseDmg + (c.player.status.strength||0);
    if(c.player.status.weak) dmgCalc=Math.floor(dmgCalc*0.75);
    if(en.status.vuln) dmgCalc=Math.floor(dmgCalc*1.5);
    if(en.status.intangible) dmgCalc=Math.min(dmgCalc,1);
    // block
    let blocked=Math.min(en.block,dmgCalc);
    en.block-=blocked; dmgCalc-=blocked;
    en.hp-=dmgCalc;
    log(`Dealt ${baseDmg} → ${dmgCalc+blocked} (${blocked} blocked) to ${en.name}.`);
    // Black Blood heal
    if(state.bossRelics.includes('Black Blood') && d.type==='Attack'){
      c.player.hp=Math.min(c.player.maxHp,c.player.hp+1);
      log('Black Blood heals 1 HP.');
    }
  }
  function gainBlock(amt){
    let add=amt + (c.player.status.dex||0);
    if(c.player.status.dex) add=amt + c.player.status.dex;
    c.player.block+=Math.max(0,add);
    log(`Gained ${add} Block.`);
  }
  // --- effects ---
  if(bName==='Strike' || bName==='Quick Slash'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
  } else if(bName==='Defend' || bName==='Iron Will' || bName==='Obsidian Skin'){
    gainBlock(block);
  } else if(bName==='Sanguine Strike'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    c.player.hp=Math.min(c.player.maxHp,c.player.hp+(up?3:2));
    log(`Healed ${up?3:2} HP.`);
  } else if(bName==='Hemorrhage'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    en.status.bleed=(en.status.bleed||0)+(up?4:3);
    log(`Applied ${up?4:3} Bleed.`);
  } else if(bName==='Bloodletting'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
  } else if(bName==='Crimson Eruption' || bName==='Hellfire Blast'){
    c.enemies.forEach(en=>{if(en.hp>0)dealToEnemy(en,dmg);});
  } else if(bName==='Crimson Shield'){
    gainBlock(block);
  } else if(bName==='Blood Ward'){
    gainBlock(block);
    c.player.status.regen=(c.player.status.regen||0)+(up?2:1);
    log(`Gained ${up?2:1} Regeneration.`);
  } else if(bName==='Sacrificial Barrier'){
    gainBlock(block);
  } else if(bName==='Vital Surge'){
    let missing=c.player.maxHp - c.player.hp;
    let amt=missing + (up?3:0);
    c.player.block+=amt;
    log(`Vital Surge gains ${amt} Block.`);
  } else if(bName==='Life Tap'){
    let dr=up?3:2;
    drawCards(dr);
    log(`Drew ${dr} cards.`);
  } else if(bName==='Rend Flesh'){
    c.enemies.forEach(en=>{if(en.hp>0) en.status.bleed=(en.status.bleed||0)+(up?7:5);});
    log(`Applied ${up?7:5} Bleed to ALL.`);
  } else if(bName==='Soul Siphon' || bName==='Blood Pact' || bName==='Umbral Mastery' || bName==='Hellfire Aura'){
    // powers handled via turn start
    log(`Power ${bName} active.`);
  } else if(bName==='Shadow Bolt'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    en.status.weak=(en.status.weak||0)+(up?2:1);
  } else if(bName==='Grasp of Despair'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    en.status.vuln=(en.status.vuln||0)+(up?2:1);
  } else if(bName==='Nightmare Lash'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    let extra = en.status.weak? (up?9:7) : 0;
    dealToEnemy(en,dmg);
    if(extra) dealToEnemy(en,extra);
  } else if(bName==='Eternal Darkness'){
    for(let i=0;i<3;i++){
      let alive=c.enemies.filter(e=>e.hp>0);
      if(!alive.length) break;
      let en=alive[Math.floor(Math.random()*alive.length)];
      dealToEnemy(en,dmg);
    }
  } else if(bName==='Cloak of Shadows'){
    gainBlock(block);
    drawCards(1);
  } else if(bName==='Haunting Mist'){
    gainBlock(block);
    c.enemies.forEach(en=>{if(en.hp>0) en.status.weak=(en.status.weak||0)+1;});
  } else if(bName==='Phantom Armor'){
    gainBlock(block);
    c.player.nextTurn.dex=(c.player.nextTurn.dex||0)+(up?3:2);
    log(`Next turn +${up?3:2} Dex.`);
  } else if(bName==='Whispering Shroud'){
    gainBlock(block);
    c.player.nextTurn.block=(c.player.nextTurn.block||0)+(up?5:4);
  } else if(bName==='Shadowform'){
    c.player.status.intangible=(c.player.status.intangible||0)+(up?3:2);
  } else if(bName==='Curse of Misfortune'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    en.status.strength=(en.status.strength||0)-(up?4:3);
    en.status.vuln=(en.status.vuln||0)+(up?2:1);
    log(`Enemy −${up?4:3} Strength, +${up?2:1} Vulnerable.`);
  } else if(bName==='Dark Ritual'){
    c.player.energy+=2;
    let dazed='Dazed';
    if(up) c.discard.push(dazed); else c.draw.push(dazed);
    shuffle(c.draw);
    log('Gained 2 Energy, shuffled Dazed.');
  } else if(bName==='Firebolt'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    let br=up?3:2;
    en.status.burn=(en.status.burn||0)+br;
    if(hasRelic('Soul Ember')){en.hp-=2;log('Soul Ember +2 damage.');}
  } else if(bName==='Searing Strike'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
    c.player.nextTurn.energy=(c.player.nextTurn.energy||0)+1;
  } else if(bName==='Immolate'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
  } else if(bName==='Flame Ward'){
    gainBlock(block);
    let br=up?3:2;
    let alive=c.enemies.filter(e=>e.hp>0);
    if(alive.length){let en=alive[Math.floor(Math.random()*alive.length)];en.status.burn=(en.status.burn||0)+br; if(hasRelic('Soul Ember')){en.hp-=2;}}
  } else if(bName==='Demonic Resilience'){
    gainBlock(block);
    c.player.hp=Math.min(c.player.maxHp,c.player.hp+(up?3:2));
  } else if(bName==='Brimstone Shield'){
    gainBlock(block);
    c.player.status.strength=(c.player.status.strength||0)+(up?3:2);
  } else if(bName==='Summon Imp'){
    let claw=up?'Imp Claw+':'Imp Claw';
    c.hand.push(claw);
    log(`Added ${claw} to hand.`);
  } else if(bName==='Infernal Pact'){
    c.player.energy+=2;
    let claw=up?'Imp Claw+':'Imp Claw';
    c.hand.push(claw);
    log('Gained 2 Energy and Imp Claw.');
  } else if(bName==='Soul Burn'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    let br=up?10:8;
    en.status.burn=(en.status.burn||0)+br;
    if(hasRelic('Soul Ember')){en.hp-=2;log('Soul Ember +2 damage.');}
    drawCards(1);
  } else if(bName==='Flash of Insight'){
    drawCards(up?3:2);
  } else if(bName==='Potent Brew'){
    c.player.energy+=(up?2:1);
  } else if(bName==='Imp Claw' || bName==='Imp Claw+'){
    let en=c.enemies[targetIdx!==null?targetIdx:c.enemies.findIndex(e=>e.hp>0)];
    dealToEnemy(en,dmg);
  } else if(heal){
    c.player.hp=Math.min(c.player.maxHp,c.player.hp+heal);
    log(`Healed ${heal} HP.`);
  } else if(block){
    gainBlock(block);
  }
  // generic draw
  if(b.draw && !['Life Tap','Flash of Insight','Cloak of Shadows','Soul Burn'].includes(bName)){
    drawCards(up && b.upDraw!==undefined ? b.upDraw : b.draw);
  }
  if(b.energyGain && !['Dark Ritual','Infernal Pact','Potent Brew'].includes(bName)){
    c.player.energy+= b.energyGain;
  }
  // handle next turn dex/block via status
  // burn/weak etc already
  if(b.weak && !['Shadow Bolt','Haunting Mist'].includes(bName)){
    if(b.aoe) c.enemies.forEach(en=>{if(en.hp>0) en.status.weak=(en.status.weak||0)+(up&&b.upWeak!==undefined?b.upWeak:b.weak);});
    else if(targetIdx!==null){c.enemies[targetIdx].status.weak=(c.enemies[targetIdx].status.weak||0)+(up&&b.upWeak!==undefined?b.upWeak:b.weak);}
  }
  if(b.vuln && !['Grasp of Despair','Curse of Misfortune'].includes(bName)){
    if(b.aoe) c.enemies.forEach(en=>{if(en.hp>0) en.status.vuln=(en.status.vuln||0)+(up&&b.upVuln!==undefined?b.upVuln:b.vuln);});
  }
  if(b.burn && !['Firebolt','Flame Ward','Soul Burn'].includes(bName)){
    let br=up&&b.upBurn!==undefined?b.upBurn:b.burn;
    if(b.aoe) c.enemies.forEach(en=>{if(en.hp>0){en.status.burn=(en.status.burn||0)+br; if(hasRelic('Soul Ember'))en.hp-=2;}});
    else if(targetIdx!==null){c.enemies[targetIdx].status.burn=(c.enemies[targetIdx].status.burn||0)+br; if(hasRelic('Soul Ember'))c.enemies[targetIdx].hp-=2;}
  }
  if(b.bleed && !['Hemorrhage','Rend Flesh'].includes(bName)){
    let br=up&&b.upBleed!==undefined?b.upBleed:b.bleed;
    if(b.aoe) c.enemies.forEach(en=>{if(en.hp>0)en.status.bleed=(en.status.bleed||0)+br;});
  }
  if(b.str){
    c.player.status.strength=(c.player.status.strength||0)+(up&&b.upStr!==undefined?b.upStr:b.str);
  }
}

function endTurn(){
  let c=state.combat; if(!c) return;
  // Doubt damage
  let doubtCount=c.hand.filter(x=>baseName(x)==='Doubt').length;
  if(doubtCount){c.player.hp-=doubtCount;log(`Doubt deals ${doubtCount} damage.`); if(c.player.hp<=0){checkPlayerDeath();return;}}
  // discard hand (ethereal handling)
  c.hand.forEach(card=>{
    let b=CARD_DB[baseName(card)];
    if(b.ethereal){
      c.exhaust.push(card);
    } else {
      c.discard.push(card);
    }
  });
  c.hand=[];
  c.player.block=0;
  // decrement player debuff durations at start of enemy turn? We'll handle at start of player turn
  // enemy turn
  enemyTurn();
}
function enemyTurn(){
  let c=state.combat;
  // apply enemy bleed/burn/poison at start of their turn? spec: Bleed at start, Burn at end, Poison at start
  c.enemies.forEach(en=>{
    if(en.hp<=0) return;
    // Bleed
    if(en.status.bleed){
      let dmg=en.status.bleed;
      if(en.status.intangible) dmg=Math.min(dmg,1);
      // block? bleed bypasses block but intangible already
      en.hp-=dmg;
      log(`${en.name} suffers ${dmg} Bleed.`);
      en.status.bleed=Math.floor(en.status.bleed/2);
      if(en.status.bleed===0) delete en.status.bleed;
    }
    if(en.status.poison){
      let dmg=en.status.poison;
      en.hp-=dmg;
      log(`${en.name} suffers ${dmg} Poison.`);
      en.status.poison-=1;
      if(en.status.poison<=0) delete en.status.poison;
    }
  });
  checkEnemiesDead();
  if(c.enemies.every(e=>e.hp<=0)){winCombat();return;}
  // each enemy acts
  c.enemies.forEach(en=>{
    if(en.hp<=0) return;
    let base=ENEMIES[en.key];
    // handle boss phase already
    let pat=base.pattern[en.idx % base.pattern.length];
    // special summon
    if(pat.t==='summon'){
      let alive=c.enemies.filter(e=>e.hp>0).length;
      if(alive<3){
        let summonKey = en.key==='Lich'?'Skeleton': en.key==='Shaman'?'Rat': en.key==='Archdemon'?'Hellhound': en.key==='ImpEnemy'?'ImpEnemy':'Rat';
        // infer
        if(en.key==='Lich') summonKey='Skeleton';
        if(en.key==='Shaman') summonKey='Rat';
        if(en.key==='Archdemon') summonKey='Hellhound';
        if(en.key==='ImpEnemy') summonKey='ImpEnemy';
        let sBase=ENEMIES[summonKey];
        c.enemies.push({key:summonKey,name:sBase.name,hp:15,maxHp:15,block:0,pattern:sBase.pattern,idx:0,status:{}});
        log(`${en.name} summons ${sBase.name}!`);
      } else {
        // attack instead
        pat={t:'atk',dmg:5};
      }
    }
    if(pat.t==='atk'){
      let dmg=pat.dmg + (en.status.strength||0);
      if(en.status.weak) dmg=Math.floor(dmg*0.75);
      // player vulnerable
      if(c.player.status.vuln) dmg=Math.floor(dmg*1.5);
      if(c.player.status.intangible) dmg=Math.min(dmg,1);
      let blocked=Math.min(c.player.block,dmg);
      c.player.block-=blocked; dmg-=blocked;
      c.player.hp-=dmg;
      log(`${en.name} attacks for ${pat.dmg + (en.status.strength||0)} → ${dmg+blocked} (${blocked} blocked).`);
      if(pat.burn){c.player.status.burn=(c.player.status.burn||0)+pat.burn; log(`Burn +${pat.burn}`);}
      if(pat.weak){c.player.status.weak=(c.player.status.weak||0)+pat.weak; log(`Weak +${pat.weak}`);}
      if(pat.heal){en.hp=Math.min(en.maxHp,en.hp+pat.heal);log(`${en.name} heals ${pat.heal}.`);}
    } else if(pat.t==='multi'){
      for(let i=0;i<pat.hits;i++){
        let dmg=pat.dmg + (en.status.strength||0);
        if(en.status.weak) dmg=Math.floor(dmg*0.75);
        if(c.player.status.vuln) dmg=Math.floor(dmg*1.5);
        if(c.player.status.intangible) dmg=Math.min(dmg,1);
        let blocked=Math.min(c.player.block,dmg);
        c.player.block-=blocked; dmg-=blocked;
        c.player.hp-=dmg;
        log(`${en.name} hits ${i+1}/${pat.hits} for ${dmg+blocked}.`);
        if(c.player.hp<=0) break;
      }
    } else if(pat.t==='def'){
      en.block+=(pat.block||0);
      log(`${en.name} gains ${pat.block||0} Block.`);
      if(pat.heal){en.hp=Math.min(en.maxHp,en.hp+pat.heal);log(`${en.name} heals ${pat.heal}.`);}
      if(pat.regen){en.status.regen=(en.status.regen||0)+pat.regen;}
      if(pat.str){en.status.strength=(en.status.strength||0)+pat.str;log(`${en.name} +${pat.str} Strength.`);}
    } else if(pat.t==='buff'){
      if(pat.str){en.status.strength=(en.status.strength||0)+pat.str;log(`${en.name} +${pat.str} Strength.`);}
      if(pat.block){en.block+=pat.block;log(`${en.name} +${pat.block} Block.`);}
      if(pat.regen){en.status.regen=(en.status.regen||0)+pat.regen;}
      if(pat.intangible){en.status.intangible=(en.status.intangible||0)+pat.intangible;log(`${en.name} gains Intangible.`);}
      if(pat.aoeStr){c.enemies.forEach(e=>e.status.strength=(e.status.strength||0)+pat.str);log(`All enemies +${pat.str} Strength.`);}
    } else if(pat.t==='debuff'){
      if(pat.weak){c.player.status.weak=(c.player.status.weak||0)+pat.weak;log(`Player Weak +${pat.weak}`);}
      if(pat.vuln){c.player.status.vuln=(c.player.status.vuln||0)+pat.vuln;log(`Player Vulnerable +${pat.vuln}`);}
      if(pat.bleed){c.player.status.bleed=(c.player.status.bleed||0)+pat.bleed;log(`Player Bleed +${pat.bleed}`);}
      if(pat.poison){c.player.status.poison=(c.player.status.poison||0)+pat.poison;log(`Player Poison +${pat.poison}`);}
      if(pat.vulnSelf){en.status.vuln=(en.status.vuln||0)+pat.vulnSelf;}
    }
    // Burn at end of enemy turn
    if(en.status.burn){
      let dmg=en.status.burn;
      if(en.status.intangible) dmg=Math.min(dmg,1);
      en.hp-=dmg;
      log(`${en.name} suffers ${dmg} Burn.`);
      en.status.burn=Math.floor(en.status.burn/2);
      if(en.status.burn===0) delete en.status.burn;
    }
    // regen
    if(en.status.regen){
      en.hp=Math.min(en.maxHp,en.hp+en.status.regen);
      log(`${en.name} regenerates ${en.status.regen}.`);
      en.status.regen-=1;
      if(en.status.regen<=0) delete en.status.regen;
    }
    en.idx=(en.idx+1)%base.pattern.length;
    if(c.player.hp<=0) return;
  });
  checkEnemiesDead();
  if(c.player.hp<=0){checkPlayerDeath();return;}
  if(c.enemies.every(e=>e.hp<=0)){winCombat();return;}
  // start player turn
  startPlayerTurn();
}
function startPlayerTurn(){
  let c=state.combat;
  c.turn++;
  // decrement statuses, apply regen, bleed, poison, etc at start of player turn
  // Bleed
  if(c.player.status.bleed){
    let dmg=c.player.status.bleed;
    if(c.player.status.intangible) dmg=Math.min(dmg,1);
    c.player.hp-=dmg;
    log(`You suffer ${dmg} Bleed.`);
    c.player.status.bleed=Math.floor(c.player.status.bleed/2);
    if(c.player.status.bleed===0) delete c.player.status.bleed;
    if(c.player.hp<=0){checkPlayerDeath();return;}
  }
  if(c.player.status.poison){
    let dmg=c.player.status.poison;
    if(c.player.status.intangible) dmg=Math.min(dmg,1);
    c.player.hp-=dmg;
    log(`You suffer ${dmg} Poison.`);
    c.player.status.poison-=1;
    if(c.player.status.poison<=0) delete c.player.status.poison;
    if(c.player.hp<=0){checkPlayerDeath();return;}
  }
  if(c.player.status.burn){
    let dmg=c.player.status.burn;
    if(c.player.status.intangible) dmg=Math.min(dmg,1);
    c.player.hp-=dmg;
    log(`You suffer ${dmg} Burn.`);
    c.player.status.burn=Math.floor(c.player.status.burn/2);
    if(c.player.status.burn===0) delete c.player.status.burn;
    if(c.player.hp<=0){checkPlayerDeath();return;}
  }
  // regen
  if(c.player.status.regen){
    c.player.hp=Math.min(c.player.maxHp,c.player.hp+c.player.status.regen);
    log(`Regeneration heals ${c.player.status.regen}.`);
    c.player.status.regen-=1;
    if(c.player.status.regen<=0) delete c.player.status.regen;
  }
  // decrement weak/vuln/intangible
  ['weak','vuln','intangible'].forEach(k=>{
    if(c.player.status[k]){c.player.status[k]-=1; if(c.player.status[k]<=0) delete c.player.status[k];}
  });
  c.enemies.forEach(en=>{
    ['weak','vuln','intangible'].forEach(k=>{if(en.status[k]){en.status[k]-=1; if(en.status[k]<=0) delete en.status[k];}});
  });
  // apply next turn buffs
  if(c.player.nextTurn.dex){c.player.status.dex=(c.player.status.dex||0)+c.player.nextTurn.dex;log(`Gained ${c.player.nextTurn.dex} Dexterity.`);c.player.nextTurn.dex=0;}
  if(c.player.nextTurn.block){c.player.block+=c.player.nextTurn.block;log(`Gained ${c.player.nextTurn.block} Block next turn.`);c.player.nextTurn.block=0;}
  if(c.player.nextTurn.energy){c.player.energy+=c.player.nextTurn.energy;log(`Gained ${c.player.nextTurn.energy} Energy next turn.`);c.player.nextTurn.energy=0;}
  // reset energy
  c.player.energy=c.player.baseEnergy;
  if(state.bossRelics.includes('Cursed Crown')) c.player.energy+=1;
  if(state.bossRelics.includes('Ectoplasm')) c.player.energy+=1;
  if(state.bossRelics.includes('Philosopher Stone')) c.player.energy+=1;
  // powers start of turn
  if(c.player.powers.includes('Soul Siphon')||c.player.powers.includes('Soul Siphon+')){
    c.player.energy+=1; c.player.hp-=1; if(c.player.status.intangible) c.player.hp+=0; // already min 1
    if(c.player.status.intangible) c.player.hp=Math.min(c.player.maxHp,c.player.hp+0); // intangible reduces to 1 but we already subtracted 1
    log('Soul Siphon: +1 Energy, −1 HP.');
    if(c.player.hp<=0){checkPlayerDeath();return;}
  }
  if(c.player.powers.includes('Umbral Mastery')||c.player.powers.includes('Umbral Mastery+')){
    let amt=c.player.powers.includes('Umbral Mastery+')?2:1;
    c.enemies.forEach(en=>{if(en.hp>0)en.status.weak=(en.status.weak||0)+amt;});
    log(`Umbral Mastery applies ${amt} Weak to all.`);
  }
  if(c.player.powers.includes('Hellfire Aura')||c.player.powers.includes('Hellfire Aura+')){
    let dmg=c.player.powers.includes('Hellfire Aura+')?5:3;
    c.enemies.forEach(en=>{if(en.hp>0){let d=dmg; if(en.status.vuln) d=Math.floor(d*1.5); en.hp-=d; log(`Hellfire Aura ${d} to ${en.name}.`);}});
    checkEnemiesDead();
    if(c.enemies.every(e=>e.hp<=0)){winCombat();return;}
  }
  if(c.player.powers.includes('Blood Pact')||c.player.powers.includes('Blood Pact+')){/* handled on hp cost */}
  // Bleeding Edge relic
  if(hasRelic('Bleeding Edge')){
    let alive=c.enemies.filter(e=>e.hp>0);
    if(alive.length){let en=alive[Math.floor(Math.random()*alive.length)];en.status.bleed=(en.status.bleed||0)+1;log('Bleeding Edge +1 Bleed.');}
  }
  // Eternal Hourglass
  if(hasRelic('Eternal Hourglass') && c.hand.length===0){c.player.energy+=1;log('Hourglass +1 Energy (empty hand).');}
  // boss relic Shadow Amulet
  if(state.bossRelics.includes('Shadow Amulet')){c.player.status.intangible=(c.player.status.intangible||0)+1;log('Shadow Amulet +1 Intangible.');}
  // draw
  drawCards(5);
  renderCombat();
}
function checkEnemiesDead(){
  // nothing extra
}
function checkPlayerDeath(){
  state.hp=0;
  showEnd(false);
}
function winCombat(){
  let c=state.combat;
  // persist hp
  state.hp=Math.max(1,c.player.hp);
  // Ashen Armor heal
  if(hasRelic('Ashen Armor')){state.hp=Math.min(state.maxHp,state.hp+6);log('Ashen Armor heals 6.');}
  // gold
  let goldEarned=0;
  if(c.isBoss) goldEarned=50;
  else if(c.isElite) goldEarned= 25+Math.floor(Math.random()*11);
  else goldEarned=10+Math.floor(Math.random()*11);
  if(hasRelic('Greedy Imp')) goldEarned+=10;
  if(c.eventGold) goldEarned+=c.eventGold;
  if(!state.bossRelics.includes('Ectoplasm')) state.gold+=goldEarned;
  // potion chance
  let potionChance=c.isBoss?1:c.isElite?0.7:0.4;
  let gotPotion = Math.random()<potionChance;
  // relic if elite
  let gotRelic=null;
  if(c.isElite){gotRelic=randomRelic(); state.relics.push(gotRelic); if(gotRelic.id==='Vial of Shadows'){/* handled on pickup */}}
  // boss relic choice handled via reward screen
  state.combat=null;
  updateTop();
  if(c.isBoss){
    // boss rewards: card choice + boss relic choice + 2 potions
    showBossReward(goldEarned,gotPotion);
  } else {
    showReward(goldEarned,gotPotion,gotRelic,c.isElite);
  }
}

/* ===== REWARDS ===== */
function showReward(gold,gotPotion,gotRelic,isElite){
  showView('reward');
  document.getElementById('rewardTitle').textContent=isElite?'Elite Victory!':'Victory!';
  document.getElementById('rewardSubtitle').textContent='Choose a card to add to your deck.';
  document.getElementById('rewardGold').textContent=`+${gold} Gold${gotRelic?' • Relic: '+gotRelic.name:''}${gotPotion?' • Potion obtained!':''}`;
  if(gotPotion) gainPotion();
  if(gotRelic) logEvent(`Obtained relic: ${gotRelic.name}`);
  // card choices
  let choices=generateCardChoices(isElite);
  state.reward={choices,chosen:false,gold,gotPotion,gotRelic};
  let container=document.getElementById('rewardCards');container.innerHTML='';
  choices.forEach((name,idx)=>{
    let d=cardData(name);
    let el=makeCardEl(d,()=>chooseRewardCard(idx));
    container.appendChild(el);
  });
  document.getElementById('rewardExtra').innerHTML=gotRelic?`<div class="tag">Relic: ${gotRelic.icon} ${gotRelic.name} — ${gotRelic.desc}</div>`:'';
}
function showBossReward(gold,gotPotion){
  showView('reward');
  document.getElementById('rewardTitle').textContent='Boss Defeated!';
  document.getElementById('rewardSubtitle').textContent='Choose a card and a Boss Relic. HP fully restored.';
  document.getElementById('rewardGold').textContent=`+${gold} Gold • +2 Potions`;
  gainPotion();gainPotion();
  state.hp=state.maxHp;
  let choices=generateCardChoices(true,true);
  state.reward={choices,chosen:false,isBoss:true};
  let container=document.getElementById('rewardCards');container.innerHTML='';
  choices.forEach((name,idx)=>{
    let d=cardData(name);
    let el=makeCardEl(d,()=>chooseRewardCard(idx));
    container.appendChild(el);
  });
  // boss relic choices
  let extra=document.getElementById('rewardExtra');
  extra.innerHTML='<h4 style="color:var(--gold);margin:10px 0">Choose a Boss Relic</h4><div id="bossRelicChoices" style="display:flex;gap:10px;flex-wrap:wrap;justify-content:center"></div>';
  let pool=shuffle([...BOSS_RELICS]).slice(0,3);
  state._bossChoices=pool;
  let brc=document.getElementById('bossRelicChoices');
  pool.forEach((r,idx)=>{
    let div=document.createElement('div');
    div.className='shop-card';
    div.style.cursor='pointer';div.style.minWidth='180px';
    div.innerHTML=`<div style="font-size:24px">${r.icon}</div><div style="font-family:'Cinzel',serif;color:#fff">${r.name}</div><div style="font-size:12px;color:var(--muted)">${r.desc}</div>`;
    div.onclick=()=>chooseBossRelic(idx);
    brc.appendChild(div);
  });
}
function generateCardChoices(isElite,isBoss){
  let pool=Object.keys(CARD_DB).filter(k=>{
    let d=CARD_DB[k];
    if(d.rarity==='Basic'||d.rarity==='Curse'||d.rarity==='Status'||d.token) return false;
    if(d.colorless) return false;
    if(d.class && d.class!==state.cls) return false;
    return true;
  });
  let rareChance=isBoss?1:isElite?0.2:0.1;
  let picks=[];
  for(let i=0;i<3;i++){
    let roll=Math.random();
    let rarity= roll<rareChance?'Rare': roll<rareChance+0.3?'Uncommon':'Common';
    let filtered=pool.filter(k=>CARD_DB[k].rarity===rarity);
    if(!filtered.length) filtered=pool;
    let choice=filtered[Math.floor(Math.random()*filtered.length)];
    picks.push(choice);
  }
  return picks;
}
function makeCardEl(d,onClick){
  let div=document.createElement('div');
  div.className='card choice-card';
  div.style.cursor='pointer';
  let typeCls=d.type==='Attack'?'type-attack':d.type==='Power'?'type-power':'type-skill';
  div.innerHTML=`<div class="card-top"><div class="card-cost">${cardCost(d)}</div><div class="card-type ${typeCls}">${d.type}</div></div>
  <div class="card-name">${d.displayName}</div><div class="card-desc">${cardDesc(d)}</div><div class="tag" style="margin-top:6px">${d.rarity}</div>`;
  div.onclick=onClick;
  return div;
}
function chooseRewardCard(idx){
  if(state.reward.chosen) return;
  state.reward.chosen=true;
  let name=state.reward.choices[idx];
  addCardToDeck(name);
  logEvent(`Added ${name} to deck.`);
  document.getElementById('rewardCards').querySelectorAll('.card').forEach((el,i)=>{el.style.opacity=i===idx?'1':'.4';el.style.borderColor=i===idx?'var(--gold)':'';});
}
function chooseBossRelic(idx){
  let r=state._bossChoices[idx];
  state.bossRelics.push(r.id);
  if(r.id==='Ectoplasm'){state.gold=0;logEvent('Ectoplasm: Gold lost!');}
  if(r.id==='Shadow Amulet'){state.maxHp=Math.max(1,state.maxHp-15);state.hp=Math.min(state.hp,state.maxHp);}
  logEvent(`Boss Relic: ${r.name}`);
  document.getElementById('bossRelicChoices').querySelectorAll('.shop-card').forEach((el,i)=>{el.style.opacity=i===idx?'1':'.4';el.style.borderColor=i===idx?'var(--gold)':'';});
  updateTop();
}
function skipCardReward(){state.reward.chosen=true;logEvent('Skipped card reward.');}
function continueFromReward(){
  if(state.reward && state.reward.isBoss && !state.bossRelics.length){
    // allow without? require
  }
  advanceMap();
}

/* ===== SHOP ===== */
function openShop(){
  showView('shop');
  let cards=shuffle(Object.keys(CARD_DB).filter(k=>{
    let d=CARD_DB[k];
    if(d.rarity==='Curse'||d.rarity==='Status'||d.token||d.rarity==='Basic') return false;
    if(d.colorless) return false;
    if(d.class && d.class!==state.cls) return false;
    return true;
  })).slice(0,3);
  let colorlessPool=Object.keys(CARD_DB).filter(k=>CARD_DB[k].colorless);
  let colorless=colorlessPool[Math.floor(Math.random()*colorlessPool.length)];
  let allCards=[...cards,colorless];
  state.shop={cards:allCards,relics:shuffle([...RELICS]).slice(0,2),potions:shuffle([...POTIONS]).slice(0,3)};
  let cardDiv=document.getElementById('shopCards');cardDiv.innerHTML='';
  allCards.forEach((name,idx)=>{
    let d=cardData(name);
    let price=d.colorless?60: d.rarity==='Common'?50: d.rarity==='Uncommon'?75:150;
    let el=document.createElement('div');el.className='shop-card';
    el.innerHTML=`${makeCardInner(d)}<div class="price">${price} Gold</div><button class="btn btn-gold btn-small" style="margin-top:6px">Buy</button>`;
    el.querySelector('button').onclick=()=>buyCard(idx,price);
    cardDiv.appendChild(el);
  });
  let relicDiv=document.getElementById('shopRelics');relicDiv.innerHTML='';
  state.shop.relics.forEach((r,idx)=>{
    let el=document.createElement('div');el.className='shop-card';
    el.innerHTML=`<div style="font-size:28px">${r.icon}</div><div style="font-family:'Cinzel',serif;color:#fff">${r.name}</div><div style="font-size:12px;color:var(--muted)">${r.desc}</div><div class="price">150 Gold</div><button class="btn btn-gold btn-small" style="margin-top:6px">Buy</button>`;
    el.querySelector('button').onclick=()=>buyRelic(idx);
    relicDiv.appendChild(el);
  });
  let potDiv=document.getElementById('shopPotions');potDiv.innerHTML='';
  state.shop.potions.forEach((p,idx)=>{
    let el=document.createElement('div');el.className='shop-card';
    el.innerHTML=`<div style="font-size:28px">${p.icon}</div><div style="font-family:'Cinzel',serif;color:#fff">${p.name}</div><div style="font-size:12px;color:var(--muted)">${p.desc}</div><div class="price">50 Gold</div><button class="btn btn-gold btn-small" style="margin-top:6px">Buy</button>`;
    el.querySelector('button').onclick=()=>buyPotion(idx);
    potDiv.appendChild(el);
  });
  document.getElementById('shopMsg').textContent='';
}
function makeCardInner(d){
  let typeCls=d.type==='Attack'?'type-attack':d.type==='Power'?'type-power':'type-skill';
  return `<div style="display:flex;justify-content:space-between"><span class="card-cost" style="width:24px;height:24px;font-size:11px">${cardCost(d)}</span><span class="card-type ${typeCls}" style="font-size:9px">${d.type}</span></div>
  <div style="font-family:'Cinzel',serif;color:#fff;margin:6px 0">${d.displayName}</div><div style="font-size:12px;color:#c8c8d8">${cardDesc(d)}</div><div class="tag" style="margin-top:6px">${d.rarity}</div>`;
}
function buyCard(idx,price){
  if(state.gold<price){shopMsg('Not enough Gold');return;}
  if(state.shop.cards[idx]==null){shopMsg('Sold out');return;}
  state.gold-=price;addCardToDeck(state.shop.cards[idx]);state.shop.cards[idx]=null;updateTop();shopMsg(`Bought ${state.deck[state.deck.length-1]}`);openShop();
}
function buyRelic(idx){
  if(state.gold<150){shopMsg('Not enough Gold');return;}
  let r=state.shop.relics[idx];if(!r){shopMsg('Sold out');return;}
  state.gold-=150;state.relics.push(r);if(r.id==='Vial of Shadows'){openChoice('Vial of Shadows: Remove a card',state.deck,c=>{removeCard(c);closeChoice();});}
  state.shop.relics[idx]=null;updateTop();shopMsg(`Bought relic ${r.name}`);openShop();
}
function buyPotion(idx){
  if(state.gold<50){shopMsg('Not enough Gold');return;}
  if(state.potions.length>=3){shopMsg('Potion slots full (3)');return;}
  let p=state.shop.potions[idx];if(!p){shopMsg('Sold out');return;}
  state.gold-=50;state.potions.push(p);state.shop.potions[idx]=null;updateTop();shopMsg(`Bought ${p.name}`);openShop();
}
function buyRemoval(){
  if(state.gold<75){shopMsg('Need 75 Gold');return;}
  openChoice('Choose card to remove (75g)',state.deck,c=>{
    state.gold-=75;removeCard(c);updateTop();closeChoice();shopMsg(`Removed ${c}`);openShop();
  });
}
function shopMsg(t){document.getElementById('shopMsg').textContent=t;setTimeout(()=>document.getElementById('shopMsg').textContent='',2000);}
function leaveShop(){advanceMap();}

/* ===== REST ===== */
function restHeal(){state.hp=Math.min(state.maxHp,state.hp+Math.floor(state.maxHp*0.25));updateTop();logEvent('Rested: healed 25%.');advanceMap();}
function restForge(){openChoice('Choose card to upgrade',state.deck,c=>{upgradeCard(c);closeChoice();advanceMap();});}
function restMeditate(){openChoice('Choose card to remove',state.deck,c=>{removeCard(c);closeChoice();advanceMap();});}
function leaveRest(){advanceMap();}

/* ===== EVENT ===== */
function openEvent(){
  showView('event');
  let ev=EVENTS[Math.floor(Math.random()*EVENTS.length)];
  state.event=ev;
  document.getElementById('eventTitle').textContent=ev.title;
  document.getElementById('eventText').textContent=ev.text;
  let ch=document.getElementById('eventChoices');ch.innerHTML='';
  ev.choices.forEach(c=>{
    let btn=document.createElement('button');
    btn.className='btn btn-ghost';
    btn.textContent=c.label;
    btn.onclick=()=>c.act(state);
    ch.appendChild(btn);
  });
}
function leaveEvent(){advanceMap();}
function closeEventToMap(){advanceMap();}

/* ===== UTIL ===== */
function addCardToDeck(name){state.deck.push(name);updateTop();}
function removeCard(name){
  let idx=state.deck.indexOf(name);
  if(idx>=0) state.deck.splice(idx,1);
  updateTop();
}
function upgradeCard(name){
  let idx=state.deck.indexOf(name);
  if(idx>=0 && !isUpgraded(name)){state.deck[idx]=name+'+';updateTop();logEvent(`Upgraded ${name} → ${name}+`);}
}
function randomRelic(rarity){
  let pool=RELICS.filter(r=>!rarity||r.rarity===rarity);
  if(state.cls==='hell') pool=RELICS; // allow class specific
  // avoid duplicates? allow
  return pool[Math.floor(Math.random()*pool.length)];
}
function gainRelic(r){
  if(!r) r=randomRelic();
  state.relics.push(r);
  if(r.id==='Vial of Shadows'){
    openChoice('Vial of Shadows: Remove a card',state.deck,c=>{removeCard(c);closeChoice();});
  }
  updateTop();
}
function gainPotion(id){
  if(state.potions.length>=3) return;
  let p=id? POTIONS.find(x=>x.id===id) : POTIONS[Math.floor(Math.random()*POTIONS.length)];
  state.potions.push(p);
  updateTop();
}
function changeHp(delta){
  state.hp=Math.max(0,Math.min(state.maxHp,state.hp+delta));
  if(state.combat) state.combat.player.hp=state.hp;
  updateTop();
  if(state.hp<=0) showEnd(false);
}
function shuffle(a){for(let i=a.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;}
function log(t){let el=document.getElementById('combatLog');if(el){let d=document.createElement('div');d.innerHTML=t;el.appendChild(d);el.scrollTop=el.scrollHeight;}}
function logClear(){let el=document.getElementById('combatLog');if(el)el.innerHTML='';}
function logEvent(t){log(t);}
function msg(t){log(t);}

/* deck modal */
function showDeck(){
  document.getElementById('deckModal').classList.add('open');
  let cont=document.getElementById('deckModalContent');cont.innerHTML='';
  state.deck.forEach(name=>{
    let d=cardData(name);
    let div=document.createElement('div');
    div.className='card';div.style.width='140px';div.style.minHeight='160px';
    let typeCls=d.type==='Attack'?'type-attack':d.type==='Power'?'type-power':'type-skill';
    div.innerHTML=`<div class="card-top"><div class="card-cost">${cardCost(d)}</div><div class="card-type ${typeCls}">${d.type}</div></div><div class="card-name">${d.displayName}${d.upgraded?' +':''}</div><div class="card-desc">${cardDesc(d)}</div>`;
    cont.appendChild(div);
  });
  document.getElementById('deckStats').textContent=`${state.deck.length} cards • ${state.relics.length} relics • ${state.potions.length}/3 potions`;
}
function closeDeck(){document.getElementById('deckModal').classList.remove('open');}
function openChoice(title,list,onPick){
  document.getElementById('choiceModal').classList.add('open');
  document.getElementById('choiceTitle').textContent=title;
  document.getElementById('choiceDesc').textContent='Click a card to choose.';
  let grid=document.getElementById('choiceGrid');grid.innerHTML='';
  list.forEach(name=>{
    let d=cardData(name);
    let div=document.createElement('div');
    div.className='card';div.style.width='140px';div.style.minHeight='150px';div.style.cursor='pointer';
    let typeCls=d.type==='Attack'?'type-attack':d.type==='Power'?'type-power':'type-skill';
    div.innerHTML=`<div class="card-top"><div class="card-cost">${cardCost(d)}</div><div class="card-type ${typeCls}">${d.type}</div></div><div class="card-name">${d.displayName}${d.upgraded?' +':''}</div><div class="card-desc">${cardDesc(d)}</div>`;
    div.onclick=()=>onPick(name);
    grid.appendChild(div);
  });
}
function closeChoice(){document.getElementById('choiceModal').classList.remove('open');}
function viewDiscard(){
  let c=state.combat; if(!c) return;
  let info=`Draw: ${c.draw.join(', ')||'empty'}\nDiscard: ${c.discard.join(', ')||'empty'}\nExhaust: ${c.exhaust.join(', ')||'empty'}\nPowers: ${c.player.powers.join(', ')||'none'}`;
  alert(info);
}
function renderRelicsPotions(){
  let rL=document.getElementById('relicList'); if(!rL) return;
  rL.innerHTML='';
  [...state.relics,...state.bossRelics.map(id=>BOSS_RELICS.find(b=>b.id===id)).filter(Boolean)].forEach(r=>{
    let div=document.createElement('div');div.className='relic';div.title=`${r.name}: ${r.desc}`;div.textContent=r.icon||'◈';
    rL.appendChild(div);
  });
  let pL=document.getElementById('potionList'); if(!pL) return;
  pL.innerHTML='';
  state.potions.forEach((p,idx)=>{
    let div=document.createElement('div');div.className='potion';
    div.innerHTML=`<div class="potion-name">${p.icon} ${p.name}</div><div class="potion-desc">${p.desc}</div>`;
    div.onclick=()=>usePotion(idx);
    pL.appendChild(div);
  });
  if(state.potions.length===0) pL.innerHTML='<span class="tag">No potions</span>';
}
function usePotion(idx){
  let c=state.combat;
  let p=state.potions[idx];
  if(!p) return;
  // allow use outside combat too for heal
  if(p.id==='Blood Vial'){
    let heal=15;
    if(c) c.player.hp=Math.min(c.player.maxHp,c.player.hp+heal);
    state.hp=Math.min(state.maxHp,state.hp+heal);
    log(`Used Blood Vial: +15 HP`);
  } else if(p.id==='Firebomb'){
    if(c) c.enemies.forEach(en=>{if(en.hp>0)en.hp-=10;});
    log('Firebomb deals 10 to ALL.');
    if(c) checkEnemiesDead();
  } else if(p.id==='Dex Tonic'){
    if(c){c.player.status.dex=(c.player.status.dex||0)+3; setTimeout(()=>{if(state.combat)state.combat.player.status.dex-=3;},0);}
    log('Dex Tonic +3 Dex.');
  } else if(p.id==='Energy Elixir'){
    if(c) c.player.energy+=2;
    log('+2 Energy.');
  } else if(p.id==='Cursed Brew'){
    if(c) c.enemies.forEach(en=>{if(en.hp>0)en.status.vuln=(en.status.vuln||0)+3;});
    log('Cursed Brew: +3 Vulnerable to ALL.');
  } else if(p.id==='Bleeding Flask'){
    if(c){
      // need target - pick first alive
      let en=c.enemies.find(e=>e.hp>0);
      if(en) en.status.bleed=(en.status.bleed||0)+6;
      log('Bleeding Flask +6 Bleed.');
    }
  } else if(p.id==='Imp-in-a-Bottle'){
    if(c) c.hand.push('Imp Claw+');
    log('Imp-in-a-Bottle: +Imp Claw+');
  } else if(p.id==='Smoke Bomb'){
    if(c && !c.isBoss){log('Escaped combat!');state.combat=null;advanceMap();state.potions.splice(idx,1);updateTop();return;}
    else {log('Cannot escape boss!');return;}
  }
  state.potions.splice(idx,1);
  updateTop();
  if(c){
    if(c.enemies.every(e=>e.hp<=0)) winCombat();
    else renderCombat();
  }
}
function showEnd(victory){
  showView('end');
  document.getElementById('endTitle').textContent=victory?'VICTORY':'DEFEAT';
  document.getElementById('endText').textContent=victory?'You have defeated the Archdemon and cleansed the Abyss. The curses are lifted... for now.':'Your HP reached 0. The Abyss claims another soul. Permadeath — run over.';
}
function resetToClassSelect(){
  state.combat=null;
  showView('class');
  updateTop();
}
function abandonRun(){if(confirm('Abandon run? Progress lost.')) resetToClassSelect();}

// keyboard
document.addEventListener('keydown',e=>{
  if(e.key.toLowerCase()==='e' && state.combat) endTurn();
});

// initial top
updateTop();
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2-contributor","provider":"openrouter","persona":"full","sessionId":"e7294f42-2eff-442b-87f5-2f366905c8f7","tokensIn":104753,"tokensOut":32737,"tokensTotal":137490,"cost":0.0170227,"turns":3,"toolCalls":2,"failedToolCalls":0,"timestamp":"2026-08-23T01:47:42.410Z"} -->