← Shadows results

Shadows

Kimi k2.7 code · composer

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 122,689 bytes · SHA-256 3a3ae500b1f5
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shadows of the Abyss</title>
<style>
/* ── css: map ── */
.map-screen {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding-top: 10px;
}
.map-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 10px;
  max-width: 900px;
}
.mapNode {
  width: 64px; height: 64px;
  border-radius: 50%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  border: 2px solid var(--text-dim);
  background: var(--panel);
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.15s;
  font-size: 11px; text-align: center;
}
.mapNode:hover { transform: scale(1.08); }
.mapNode.current { border-color: var(--accent-gold); box-shadow: 0 0 14px rgba(201,162,39,0.5); }
.mapNode.visited { opacity: 0.3; }
.mapNode.available { border-color: var(--energy); }
.mapNode.enemy { color: #ff7777; }
.mapNode.elite { color: #ff5555; background: #2a1010; }
.mapNode.rest { color: #77ff77; }
.mapNode.shop { color: #ffdd77; }
.mapNode.event { color: #aaaaff; }
.mapNode.boss { color: #ff3333; background: #2a0505; }
.map-row {
  display: flex; gap: 10px; justify-content: center;
  align-items: center;
}
.floor-label {
  width: 80px; color: var(--text-dim); font-size: 13px;
  text-align: right;
}
.path-svg {
  position: absolute; top: 0; left: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: -1;
}
/* ── css: screens ── */
.class-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  max-width: 960px;
}
.class-card {
  background: var(--panel);
  border: 2px solid var(--text-dim);
  border-radius: 12px;
  padding: 24px;
  cursor: pointer;
  transition: transform 0.1s, border-color 0.15s;
}
.class-card:hover { transform: translateY(-4px); border-color: var(--accent-gold); }
.class-name { font-size: 24px; color: var(--accent-gold); margin-bottom: 8px; }
.class-desc { font-size: 15px; color: var(--text-dim); line-height: 1.4; }
.shop-grid, .reward-grid, .relic-grid, .potion-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px;
  max-width: 900px;
  width: 100%;
}
.shop-item, .reward-item, .relic-item, .potion-item {
  background: var(--panel);
  border: 1px solid var(--text-dim);
  border-radius: 10px;
  padding: 14px;
  text-align: center;
  cursor: pointer;
  transition: transform 0.1s, border-color 0.15s;
}
.shop-item:hover, .reward-item:hover, .relic-item:hover, .potion-item:hover { transform: translateY(-3px); border-color: var(--accent-gold); }
.shop-price { color: var(--accent-gold); font-weight: bold; margin-top: 6px; }
.event-text {
  max-width: 700px;
  font-size: 18px;
  background: var(--panel);
  border: 1px solid var(--text-dim);
  border-radius: 10px;
  padding: 24px;
  margin-bottom: 20px;
  text-align: center;
}
.event-choices { display: flex; flex-direction: column; gap: 10px; max-width: 500px; width: 100%; }
.rest-options { display: flex; gap: 16px; }
.boss-relic-pick { display: flex; gap: 16px; justify-content: center; margin-top: 20px; }
.run-over {
  text-align: center;
}
.run-over h1 { font-size: 48px; color: var(--accent); }
.victory h1 { color: var(--accent-gold); }
.fade-in { animation: fadeIn 0.3s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.shake { animation: shake 0.3s; }
@keyframes shake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
/* ── css: base ── */
:root {
  --bg: #0d0d14;
  --bg-light: #161622;
  --panel: #1e1e2e;
  --panel-light: #2a2a3e;
  --text: #e6e6e6;
  --text-dim: #9999aa;
  --accent: #8b0000;
  --accent-gold: #c9a227;
  --hp: #cc3333;
  --block: #4a90d9;
  --energy: #44aa44;
  --card-basic: #6b6b7c;
  --card-attack: #8b2a2a;
  --card-skill: #2a5a8b;
  --card-power: #6b2a8b;
  --card-curse: #3a3a3a;
  --card-status: #4a4a4a;
  --rare: #ffaa00;
  --uncommon: #55aa55;
  --common: #aaaaaa;
}
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0;
  width: 100%; height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  overflow: hidden;
}
#game { width: 100%; height: 100%; position: relative; }
#screens { width: 100%; height: 100%; }
.screen {
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  padding: 20px;
  overflow-y: auto;
}
.hidden { display: none !important; }
button {
  background: var(--panel-light);
  color: var(--text);
  border: 1px solid var(--text-dim);
  padding: 10px 18px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 16px;
  transition: background 0.15s, transform 0.05s;
}
button:hover { background: #35354a; }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
.tooltip {
  position: fixed;
  pointer-events: none;
  background: rgba(10,10,16,0.95);
  border: 1px solid var(--text-dim);
  border-radius: 8px;
  padding: 10px 14px;
  max-width: 280px;
  z-index: 1000;
  font-size: 14px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.6);
}
.modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.75);
  display: flex; align-items: center; justify-content: center;
  z-index: 2000;
}
.modal-content {
  background: var(--panel);
  border: 1px solid var(--text-dim);
  border-radius: 12px;
  padding: 24px;
  max-width: 90vw;
  max-height: 90vh;
  overflow-y: auto;
  min-width: 320px;
}
.title {
  font-size: 42px;
  text-align: center;
  margin-bottom: 30px;
  color: var(--accent-gold);
  text-shadow: 0 2px 10px rgba(0,0,0,0.6);
}
.subtitle { font-size: 22px; color: var(--text-dim); margin-bottom: 16px; text-align: center; }
/* ── css: card ── */
.card {
  width: 140px;
  height: 200px;
  border-radius: 10px;
  background: linear-gradient(145deg, var(--panel-light), var(--panel));
  border: 2px solid var(--text-dim);
  display: flex;
  flex-direction: column;
  padding: 8px;
  cursor: pointer;
  user-select: none;
  transition: transform 0.1s, box-shadow 0.15s;
  position: relative;
  box-shadow: 0 3px 10px rgba(0,0,0,0.4);
  flex-shrink: 0;
}
.card:hover { transform: translateY(-8px); box-shadow: 0 8px 24px rgba(0,0,0,0.5); }
.card.selected { border-color: var(--accent-gold); box-shadow: 0 0 14px rgba(201,162,39,0.5); }
.card.unplayable { opacity: 0.55; }
.card-cost {
  position: absolute; top: 6px; left: 6px;
  width: 28px; height: 28px;
  background: var(--energy);
  color: #000;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: bold; font-size: 15px;
}
.card-name {
  font-size: 15px; font-weight: bold;
  margin-top: 22px;
  text-align: center;
  line-height: 1.1;
}
.card-type {
  font-size: 11px; color: var(--text-dim);
  text-align: center; text-transform: uppercase;
  margin-top: 2px;
}
.card-rarity-common { border-color: var(--common); }
.card-rarity-uncommon { border-color: var(--uncommon); }
.card-rarity-rare { border-color: var(--rare); }
.card-rarity-basic { border-color: #888; }
.card-type-Attack { background: linear-gradient(145deg, #3a1a1a, #2a1010); }
.card-type-Skill { background: linear-gradient(145deg, #1a2a3a, #101a2a); }
.card-type-Power { background: linear-gradient(145deg, #2a1a3a, #1a102a); }
.card-type-Curse { background: linear-gradient(145deg, #2a2a2a, #1a1a1a); }
.card-type-Status { background: linear-gradient(145deg, #3a3a3a, #2a2a2a); }
.card-desc {
  flex: 1;
  font-size: 13px;
  margin-top: 8px;
  text-align: center;
  color: var(--text);
  line-height: 1.35;
  overflow: hidden;
}
.card-upgrade { color: var(--accent-gold); font-weight: bold; }
.card-row {
  display: flex; flex-wrap: wrap;
  gap: 12px; justify-content: center;
  margin: 12px 0;
}
.card-small { width: 110px; height: 156px; font-size: 12px; }
.card-small .card-name { font-size: 13px; margin-top: 18px; }
.card-small .card-desc { font-size: 11px; }
.card-pile {
  width: 80px; height: 116px;
  font-size: 11px;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  color: var(--text-dim);
}
.card-token { font-style: italic; }
/* ── css: hud ── */
.hud {
  display: flex; gap: 20px; flex-wrap: wrap;
  justify-content: center; align-items: center;
  background: var(--panel);
  border-bottom: 1px solid var(--text-dim);
  padding: 10px 20px;
  width: 100%;
}
.hud-stat {
  display: flex; align-items: center; gap: 6px;
  font-size: 18px; font-weight: bold;
}
.stat-hp { color: var(--hp); }
.stat-block { color: var(--block); }
.stat-energy { color: var(--energy); }
.stat-gold { color: var(--accent-gold); }
.combat-frame {
  display: flex;
  flex-direction: column;
  width: 100%; height: 100%;
}
.enemy-area {
  flex: 1;
  display: flex; align-items: center; justify-content: center;
  gap: 30px;
  padding: 20px;
}
.enemy {
  width: 140px;
  background: var(--panel);
  border: 2px solid var(--text-dim);
  border-radius: 12px;
  padding: 12px;
  text-align: center;
  position: relative;
}
.enemy.active { border-color: var(--accent-gold); }
.enemy-hp-bar, .player-hp-bar {
  height: 10px;
  background: #333;
  border-radius: 5px;
  overflow: hidden;
  margin: 6px 0;
}
.enemy-hp-fill, .player-hp-fill {
  height: 100%;
  background: var(--hp);
  transition: width 0.2s;
}
.enemy-block, .player-block {
  color: var(--block); font-weight: bold;
}
.enemy-intent {
  font-size: 13px;
  color: var(--text-dim);
  margin-top: 6px;
  min-height: 36px;
}
.player-zone {
  display: flex; align-items: center; justify-content: center;
  gap: 40px;
  padding: 12px;
}
.player-avatar {
  width: 180px;
  background: var(--panel);
  border: 2px solid var(--accent-gold);
  border-radius: 12px;
  padding: 14px;
  text-align: center;
}
.status-list {
  display: flex; flex-wrap: wrap; gap: 4px;
  justify-content: center; margin-top: 6px;
}
.status-pill {
  background: var(--bg-light);
  border: 1px solid var(--text-dim);
  border-radius: 10px;
  padding: 2px 7px;
  font-size: 12px;
  color: var(--text);
}
.hand-zone {
  display: flex; gap: 10px;
  justify-content: center;
  padding: 16px;
  min-height: 220px;
  align-items: flex-end;
  flex-wrap: wrap;
}
.controls {
  display: flex; gap: 12px; justify-content: center;
  padding: 10px;
}
.log-box {
  width: 260px; height: 140px;
  background: var(--bg-light);
  border: 1px solid var(--text-dim);
  border-radius: 8px;
  padding: 8px;
  font-size: 13px;
  overflow-y: auto;
}
.pile-zone {
  display: flex; gap: 12px; justify-content: center;
  align-items: flex-end;
}
.pile-info { text-align: center; font-size: 13px; color: var(--text-dim); }
</style>
</head>
<body>
<div id="game">
  <div id="screens"></div>
  <div id="tooltip" class="tooltip hidden"></div>
  <div id="modal" class="modal hidden">
    <div class="modal-content"></div>
  </div>
</div>
<div id="modal-overlay" class="hidden"><div id="modal-content"></div></div>
<script>
'use strict';

// ── const: RNG ──
const RNG = {
  int(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; },
  choice(arr) { return arr[Math.floor(Math.random() * arr.length)]; },
  weightedChoice(items) {
    const total = items.reduce((s, i) => s + i.weight, 0);
    let r = Math.random() * total;
    for (const item of items) { r -= item.weight; if (r <= 0) return item.value; }
    return items[items.length - 1].value;
  },
  shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  },
  chance(p) { return Math.random() < p; }
};

// ── const: CLASSES ──
const CLASSES = {
  bloodreaper: { name: "Blood Reaper", desc: "Sacrifice HP for devastating attacks and life steal." },
  shadowmancer: { name: "Shadowmancer", desc: "Control the battlefield with Weak, Vulnerable, and Intangible." },
  hellforged: { name: "Hellforged", desc: "Burn enemies and summon imps to swarm your foes." }
};

// ── const: CLASS_COLORS ──
const CLASS_COLORS = {
  bloodreaper: "#8b0000",
  shadowmancer: "#4a2a6b",
  hellforged: "#8b4500"
};

// ── function: createCard ──
function createCard(id, opts) {
  return {
    uid: Math.random().toString(36).slice(2),
    id,
    name: opts.name,
    type: opts.type,
    cost: opts.cost,
    baseCost: opts.cost,
    rarity: opts.rarity || "Common",
    class: opts.class || null,
    upgraded: false,
    ethereal: opts.ethereal || false,
    exhaust: opts.exhaust || false,
    unplayable: opts.unplayable || false,
    target: opts.target || "enemy",
    desc: opts.desc,
    descFn: opts.descFn || null,
    onTurnEnd: opts.onTurnEnd || null,
    play: opts.play,
    getDescription() {
      return this.descFn ? this.descFn(this) : this.desc;
    },
    getCost() { return this.upgraded ? (opts.costUp ?? this.baseCost) : this.baseCost; },
    clone() {
      const c = createCard(this.id, opts);
      c.upgraded = this.upgraded;
      c.uid = Math.random().toString(36).slice(2);
      return c;
    }
  };
}

// ── function: damageAmount ──
function damageAmount(base, attacker, defender) {
  let dmg = base + (attacker.statuses.strength || 0) + (attacker.statuses.tempStrength || 0);
  if (attacker.statuses.weak > 0) dmg = Math.floor(dmg * 0.75);
  if (defender.statuses.vulnerable > 0) dmg = Math.floor(dmg * 1.5);
  if (defender.statuses.intangible > 0) dmg = 1;
  return Math.max(0, dmg);
}

// ── function: dealDamage ──
function dealDamage(ctx, source, target, amount, onHit) {
  const actual = damageAmount(amount, source, target);
  if (target.statuses.block > 0) {
    const absorbed = Math.min(target.statuses.block, actual);
    target.statuses.block -= absorbed;
    const remaining = actual - absorbed;
    target.hp -= remaining;
    if (remaining > 0 && source === ctx.player) {
      relicTrigger(ctx, "onHit", { target, damage: remaining });
    }
    if (remaining > 0 && onHit) onHit(remaining);
  } else {
    target.hp -= actual;
    if (actual > 0 && source === ctx.player) relicTrigger(ctx, "onHit", { target, damage: actual });
    if (actual > 0 && onHit) onHit(actual);
  }
  if (target.hp < 0) target.hp = 0;
}

// ── function: dealDamageAll ──
function dealDamageAll(ctx, source, amount, onHit) {
  for (const enemy of ctx.enemies) {
    if (enemy.hp > 0) dealDamage(ctx, source, enemy, amount, onHit);
  }
}

// ── function: loseHp ──
function loseHp(ctx, target, amount) {
  let actual = amount;
  if (target.statuses.intangible > 0) actual = 1;
  target.hp -= actual;
  if (target.hp < 0) target.hp = 0;
  return actual;
}

// ── function: gainBlock ──
function gainBlock(target, amount, ctx) {
  let block = amount;
  if (target === ctx.player) block += target.statuses.dexterity || 0;
  target.statuses.block = (target.statuses.block || 0) + Math.max(0, block);
}

// ── function: applyStatus ──
function applyStatus(target, name, amount) {
  target.statuses[name] = (target.statuses[name] || 0) + amount;
}

// ── function: drawCards ──
function drawCards(ctx, count) {
  for (let i = 0; i < count; i++) {
    if (ctx.drawPile.length === 0) {
      ctx.drawPile = RNG.shuffle(ctx.discardPile);
      ctx.discardPile = [];
    }
    if (ctx.drawPile.length > 0) {
      const card = ctx.drawPile.pop();
      ctx.hand.push(card);
    }
  }
}

// ── function: addCardToHand ──
function addCardToHand(ctx, card, place = "hand") {
  if (place === "hand") ctx.hand.push(card);
  else if (place === "drawTop") ctx.drawPile.push(card);
  else if (place === "discard") ctx.discardPile.push(card);
}

// ── function: exhaustCard ──
function exhaustCard(ctx, card) {
  const pile = ctx.exhaustPile || (ctx.exhaustPile = []);
  pile.push(card);
}

// ── function: discardHand ──
function discardHand(ctx) {
  const newHand = [];
  for (const card of ctx.hand) {
    if (card.ethereal || card.exhaust) {
      exhaustCard(ctx, card);
      if (card.onTurnEnd) card.onTurnEnd(ctx, card);
    } else {
      if (card.onTurnEnd) card.onTurnEnd(ctx, card);
      ctx.discardPile.push(card);
    }
  }
  ctx.hand = newHand;
}

// ── function: relicTrigger ──
function relicTrigger(ctx, event, data) {
  if (!ctx.player || !ctx.player.relics) return;
  for (const relic of ctx.player.relics) {
    if (relic.onEvent && relic.onEvent[event]) {
      relic.onEvent[event](ctx, relic, data);
    }
  }
}

// ── function: payHpCost ──
function payHpCost(ctx, amount, isFromCard = true) {
  if (!isFromCard) return loseHp(ctx, ctx.player, amount);
  const orig = amount;
  const alreadyPaid = ctx.player.flags?.sacrificialDaggerUsed;
  if (!alreadyPaid) {
    const sd = ctx.player.relics.find(r => r.id === "sacrificial_dagger");
    if (sd) {
      amount = Math.max(0, amount - 1);
      ctx.player.flags.sacrificialDaggerUsed = true;
    }
  }
  const actual = loseHp(ctx, ctx.player, amount);
  // Bloodstone relic
  const bloodstone = ctx.player.relics.find(r => r.id === "bloodstone");
  if (bloodstone && actual > 0) ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + 1);
  // Blood Pact power
  const bp = (ctx.powers || []).find(p => p.name === "blood_pact");
  if (bp && actual > 0) applyStatus(ctx.player, "strength", bp.upgraded ? 2 : 1);
  return actual;
}

// ── function: createImpClaw ──
function createImpClaw(upgraded = false) {
  return createCard("imp_claw", {
    name: upgraded ? "Imp Claw+" : "Imp Claw",
    type: "Attack",
    cost: 0,
    rarity: "Special",
    class: "hellforged",
    exhaust: true,
    target: "enemy",
    desc: `Deal ${upgraded ? 6 : 4} damage. Exhaust.`,
    descFn(c) { return `Deal ${c.upgraded ? 6 : 4} damage. Exhaust.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, upgraded ? 6 : 4); }
  });
}

// ── const: CARD_TEMPLATES ──
const CARD_TEMPLATES = {
  strike: createCard("strike", {
    name: "Strike", type: "Attack", cost: 1, rarity: "Basic", target: "enemy",
    desc: "Deal 6 damage.",
    descFn(c) { return `Deal ${c.upgraded ? 8 : 6} damage.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 8 : 6); }
  }),
  defend: createCard("defend", {
    name: "Defend", type: "Skill", cost: 1, rarity: "Basic", target: "self",
    desc: "Gain 5 Block.",
    descFn(c) { return `Gain ${c.upgraded ? 8 : 5} Block.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 8 : 5, ctx); }
  }),
  // Blood Reaper
  sanguine_strike: createCard("sanguine_strike", {
    name: "Sanguine Strike", type: "Attack", cost: 1, rarity: "Common", class: "bloodreaper", target: "enemy",
    desc: "Deal 8 damage. Heal 2 HP.",
    descFn(c) { return `Deal ${c.upgraded ? 10 : 8} damage. Heal ${c.upgraded ? 3 : 2} HP.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 10 : 8); ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + (this.upgraded ? 3 : 2)); }
  }),
  hemorrhage: createCard("hemorrhage", {
    name: "Hemorrhage", type: "Attack", cost: 1, rarity: "Common", class: "bloodreaper", target: "enemy",
    desc: "Deal 5 damage. Apply 3 Bleed.",
    descFn(c) { return `Deal ${c.upgraded ? 6 : 5} damage. Apply ${c.upgraded ? 4 : 3} Bleed.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 6 : 5); applyStatus(target, "bleed", this.upgraded ? 4 : 3); }
  }),
  bloodletting: createCard("bloodletting", {
    name: "Bloodletting", type: "Attack", cost: 1, rarity: "Uncommon", class: "bloodreaper", target: "enemy",
    desc: "Lose 3 HP. Deal 12 damage.",
    descFn(c) { return `Lose ${c.upgraded ? 2 : 3} HP. Deal ${c.upgraded ? 15 : 12} damage.`; },
    play(ctx, target) { payHpCost(ctx, this.upgraded ? 2 : 3); dealDamage(ctx, ctx.player, target, this.upgraded ? 15 : 12); }
  }),
  crimson_eruption: createCard("crimson_eruption", {
    name: "Crimson Eruption", type: "Attack", cost: 2, rarity: "Rare", class: "bloodreaper", target: "all",
    desc: "Deal 8 damage to ALL enemies. Lose 2 HP.",
    descFn(c) { return `Deal ${c.upgraded ? 10 : 8} damage to ALL enemies. Lose 2 HP.`; },
    play(ctx) { payHpCost(ctx, 2); dealDamageAll(ctx, ctx.player, this.upgraded ? 10 : 8); }
  }),
  crimson_shield: createCard("crimson_shield", {
    name: "Crimson Shield", type: "Skill", cost: 1, rarity: "Common", class: "bloodreaper", target: "self",
    desc: "Gain 6 Block. Lose 2 HP.",
    descFn(c) { return `Gain ${c.upgraded ? 8 : 6} Block. Lose 2 HP.`; },
    play(ctx) { payHpCost(ctx, 2); gainBlock(ctx.player, this.upgraded ? 8 : 6, ctx); }
  }),
  blood_ward: createCard("blood_ward", {
    name: "Blood Ward", type: "Skill", cost: 1, rarity: "Common", class: "bloodreaper", target: "self",
    desc: "Gain 3 Block. Gain 1 Regeneration.",
    descFn(c) { return `Gain ${c.upgraded ? 4 : 3} Block. Gain ${c.upgraded ? 2 : 1} Regeneration.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 4 : 3, ctx); applyStatus(ctx.player, "regeneration", this.upgraded ? 2 : 1); }
  }),
  sacrificial_barrier: createCard("sacrificial_barrier", {
    name: "Sacrificial Barrier", type: "Skill", cost: 2, rarity: "Uncommon", class: "bloodreaper", target: "self",
    desc: "Gain 10 Block. Lose 5 HP.",
    descFn(c) { return `Gain ${c.upgraded ? 13 : 10} Block. Lose 5 HP.`; },
    play(ctx) { payHpCost(ctx, 5); gainBlock(ctx.player, this.upgraded ? 13 : 10, ctx); }
  }),
  vital_surge: createCard("vital_surge", {
    name: "Vital Surge", type: "Skill", cost: "X", rarity: "Rare", class: "bloodreaper", exhaust: true, target: "self",
    desc: "Gain Block equal to your missing HP. Exhaust.",
    descFn(c) { return `Gain Block equal to your missing HP${c.upgraded ? " + 3" : ""}. Exhaust.`; },
    play(ctx) { gainBlock(ctx.player, (ctx.player.maxHp - ctx.player.hp) + (this.upgraded ? 3 : 0), ctx); }
  }),
  life_tap: createCard("life_tap", {
    name: "Life Tap", type: "Skill", cost: 0, rarity: "Common", class: "bloodreaper", target: "self",
    desc: "Draw 2 cards. Lose 3 HP.",
    descFn(c) { return `Draw ${c.upgraded ? 3 : 2} cards. Lose 3 HP.`; },
    play(ctx) { payHpCost(ctx, 3); drawCards(ctx, this.upgraded ? 3 : 2); }
  }),
  rend_flesh: createCard("rend_flesh", {
    name: "Rend Flesh", type: "Skill", cost: 1, rarity: "Uncommon", class: "bloodreaper", target: "all",
    desc: "Apply 5 Bleed to ALL enemies. Lose 2 HP.",
    descFn(c) { return `Apply ${c.upgraded ? 7 : 5} Bleed to ALL enemies. Lose 2 HP.`; },
    play(ctx) { payHpCost(ctx, 2); for (const e of ctx.enemies) applyStatus(e, "bleed", this.upgraded ? 7 : 5); }
  }),
  soul_siphon: createCard("soul_siphon", {
    name: "Soul Siphon", type: "Power", cost: 2, rarity: "Uncommon", class: "bloodreaper", target: "self",
    desc: "At the start of your turn, gain 1 Energy and lose 1 HP.",
    costUp: 1,
    descFn(c) { return "At the start of your turn, gain 1 Energy and lose 1 HP."; },
    play(ctx) { ctx.powers.push({ name: "soul_siphon", onTurnStart(c2) { c2.energy += 1; payHpCost(c2, 1); } }); }
  }),
  blood_pact: createCard("blood_pact", {
    name: "Blood Pact", type: "Power", cost: 1, rarity: "Rare", class: "bloodreaper", target: "self",
    desc: "Whenever you lose HP from a card, gain 1 Strength.",
    descFn(c) { return `Whenever you lose HP from a card, gain ${c.upgraded ? 2 : 1} Strength.`; },
    play(ctx) { /* passive power handled by payHpCost via finding this power as relic? We mutate card to act as power */ ctx.powers.push({ name: "blood_pact", upgraded: this.upgraded }); }
  }),
  // Shadowmancer
  shadow_bolt: createCard("shadow_bolt", {
    name: "Shadow Bolt", type: "Attack", cost: 1, rarity: "Common", class: "shadowmancer", target: "enemy",
    desc: "Deal 6 damage. Apply 1 Weak.",
    descFn(c) { return `Deal ${c.upgraded ? 8 : 6} damage. Apply ${c.upgraded ? 2 : 1} Weak.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 8 : 6); applyStatus(target, "weak", this.upgraded ? 2 : 1); }
  }),
  grasp_of_despair: createCard("grasp_of_despair", {
    name: "Grasp of Despair", type: "Attack", cost: 1, rarity: "Common", class: "shadowmancer", target: "enemy",
    desc: "Deal 4 damage. Apply 1 Vulnerable.",
    descFn(c) { return `Deal ${c.upgraded ? 5 : 4} damage. Apply ${c.upgraded ? 2 : 1} Vulnerable.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 5 : 4); applyStatus(target, "vulnerable", this.upgraded ? 2 : 1); }
  }),
  nightmare_lash: createCard("nightmare_lash", {
    name: "Nightmare Lash", type: "Attack", cost: 2, rarity: "Uncommon", class: "shadowmancer", target: "enemy",
    desc: "Deal 7 damage. If enemy has Weak, deal 7 more damage.",
    descFn(c) { const d = c.upgraded ? 9 : 7; return `Deal ${d} damage. If enemy has Weak, deal ${d} more damage.`; },
    play(ctx, target) { const d = this.upgraded ? 9 : 7; dealDamage(ctx, ctx.player, target, target.statuses.weak > 0 ? d * 2 : d); }
  }),
  eternal_darkness: createCard("eternal_darkness", {
    name: "Eternal Darkness", type: "Attack", cost: 2, rarity: "Rare", class: "shadowmancer", target: "all_random",
    desc: "Deal 3 damage to a random enemy 3 times.",
    descFn(c) { return `Deal ${c.upgraded ? 4 : 3} damage to a random enemy 3 times.`; },
    play(ctx) { const alive = ctx.enemies.filter(e => e.hp > 0); for (let i = 0; i < 3; i++) if (alive.length) dealDamage(ctx, ctx.player, RNG.choice(alive), this.upgraded ? 4 : 3); }
  }),
  cloak_of_shadows: createCard("cloak_of_shadows", {
    name: "Cloak of Shadows", type: "Skill", cost: 1, rarity: "Common", class: "shadowmancer", target: "self",
    desc: "Gain 5 Block. Draw 1 card.",
    descFn(c) { return `Gain ${c.upgraded ? 7 : 5} Block. Draw 1 card.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 7 : 5, ctx); drawCards(ctx, 1); }
  }),
  haunting_mist: createCard("haunting_mist", {
    name: "Haunting Mist", type: "Skill", cost: 1, rarity: "Common", class: "shadowmancer", target: "self",
    desc: "Gain 7 Block. Apply 1 Weak to ALL enemies.",
    descFn(c) { return `Gain ${c.upgraded ? 9 : 7} Block. Apply 1 Weak to ALL enemies.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 9 : 7, ctx); for (const e of ctx.enemies) applyStatus(e, "weak", 1); }
  }),
  phantom_armor: createCard("phantom_armor", {
    name: "Phantom Armor", type: "Skill", cost: 1, rarity: "Uncommon", class: "shadowmancer", target: "self",
    desc: "Gain 8 Block. Next turn, gain 2 Dexterity.",
    descFn(c) { return `Gain ${c.upgraded ? 10 : 8} Block. Next turn, gain ${c.upgraded ? 3 : 2} Dexterity.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 10 : 8, ctx); ctx.player.statuses.dexterityNextTurn = (ctx.player.statuses.dexterityNextTurn || 0) + (this.upgraded ? 3 : 2); }
  }),
  whispering_shroud: createCard("whispering_shroud", {
    name: "Whispering Shroud", type: "Skill", cost: 1, rarity: "Rare", class: "shadowmancer", target: "self",
    desc: "Gain 4 Block. Next turn, gain 4 Block.",
    descFn(c) { return `Gain ${c.upgraded ? 5 : 4} Block. Next turn, gain ${c.upgraded ? 5 : 4} Block.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 5 : 4, ctx); ctx.player.statuses.blockNextTurn = (ctx.player.statuses.blockNextTurn || 0) + (this.upgraded ? 5 : 4); }
  }),
  shadowform: createCard("shadowform", {
    name: "Shadowform", type: "Skill", cost: 2, rarity: "Uncommon", class: "shadowmancer", exhaust: true, target: "self",
    desc: "Gain 2 Intangible. Exhaust.",
    descFn(c) { return `Gain ${c.upgraded ? 3 : 2} Intangible. Exhaust.`; },
    play(ctx) { applyStatus(ctx.player, "intangible", this.upgraded ? 3 : 2); }
  }),
  curse_of_misfortune: createCard("curse_of_misfortune", {
    name: "Curse of Misfortune", type: "Skill", cost: 1, rarity: "Common", class: "shadowmancer", target: "enemy",
    desc: "Enemy loses 3 Strength this turn. Apply 1 Vulnerable.",
    descFn(c) { return `Enemy loses ${c.upgraded ? 4 : 3} Strength this turn. Apply ${c.upgraded ? 2 : 1} Vulnerable.`; },
    play(ctx, target) { applyStatus(target, "tempStrength", -(this.upgraded ? 4 : 3)); applyStatus(target, "vulnerable", this.upgraded ? 2 : 1); }
  }),
  dark_ritual: createCard("dark_ritual", {
    name: "Dark Ritual", type: "Skill", cost: 0, rarity: "Uncommon", class: "shadowmancer", target: "self",
    desc: "Gain 2 Energy. Shuffle a Dazed into your draw pile.",
    descFn(c) { return `Gain 2 Energy. Shuffle a Dazed into your ${c.upgraded ? "discard pile" : "draw pile"}.`; },
    play(ctx) { ctx.energy += 2; addCardToHand(ctx, CARD_TEMPLATES.dazed.clone(), this.upgraded ? "discard" : "drawTop"); }
  }),
  umbral_mastery: createCard("umbral_mastery", {
    name: "Umbral Mastery", type: "Power", cost: 2, rarity: "Rare", class: "shadowmancer", target: "self",
    desc: "At the start of your turn, apply 1 Weak to ALL enemies.",
    descFn(c) { return `At the start of your turn, apply ${c.upgraded ? 2 : 1} Weak to ALL enemies.`; },
    play(ctx) { const amt = this.upgraded ? 2 : 1; ctx.powers.push({ name: "umbral_mastery", onTurnStart(c2) { for (const e of c2.enemies) applyStatus(e, "weak", amt); } }); }
  }),
  // Hellforged
  firebolt: createCard("firebolt", {
    name: "Firebolt", type: "Attack", cost: 1, rarity: "Common", class: "hellforged", target: "enemy",
    desc: "Deal 7 damage. Apply 2 Burn.",
    descFn(c) { return `Deal ${c.upgraded ? 9 : 7} damage. Apply ${c.upgraded ? 3 : 2} Burn.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 9 : 7); applyStatus(target, "burn", this.upgraded ? 3 : 2); }
  }),
  hellfire_blast: createCard("hellfire_blast", {
    name: "Hellfire Blast", type: "Attack", cost: 2, rarity: "Uncommon", class: "hellforged", target: "all",
    desc: "Deal 9 damage to ALL enemies.",
    descFn(c) { return `Deal ${c.upgraded ? 12 : 9} damage to ALL enemies.`; },
    play(ctx) { dealDamageAll(ctx, ctx.player, this.upgraded ? 12 : 9); }
  }),
  searing_strike: createCard("searing_strike", {
    name: "Searing Strike", type: "Attack", cost: 1, rarity: "Common", class: "hellforged", target: "enemy",
    desc: "Deal 6 damage. Gain 1 Energy next turn.",
    descFn(c) { return `Deal ${c.upgraded ? 8 : 6} damage. Gain 1 Energy next turn.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 8 : 6); ctx.player.statuses.energyNextTurn = (ctx.player.statuses.energyNextTurn || 0) + 1; }
  }),
  immolate: createCard("immolate", {
    name: "Immolate", type: "Attack", cost: 2, rarity: "Rare", class: "hellforged", exhaust: true, target: "enemy",
    desc: "Deal 12 damage. Exhaust.",
    descFn(c) { return `Deal ${c.upgraded ? 16 : 12} damage. Exhaust.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 16 : 12); }
  }),
  obsidian_skin: createCard("obsidian_skin", {
    name: "Obsidian Skin", type: "Skill", cost: 1, rarity: "Common", class: "hellforged", target: "self",
    desc: "Gain 7 Block.",
    descFn(c) { return `Gain ${c.upgraded ? 10 : 7} Block.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 10 : 7, ctx); }
  }),
  flame_ward: createCard("flame_ward", {
    name: "Flame Ward", type: "Skill", cost: 1, rarity: "Common", class: "hellforged", target: "self",
    desc: "Gain 5 Block. Apply 2 Burn to a random enemy.",
    descFn(c) { return `Gain ${c.upgraded ? 6 : 5} Block. Apply ${c.upgraded ? 3 : 2} Burn to a random enemy.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 6 : 5, ctx); const alive = ctx.enemies.filter(e => e.hp > 0); if (alive.length) applyStatus(RNG.choice(alive), "burn", this.upgraded ? 3 : 2); }
  }),
  demonic_resilience: createCard("demonic_resilience", {
    name: "Demonic Resilience", type: "Skill", cost: 1, rarity: "Uncommon", class: "hellforged", target: "self",
    desc: "Gain 4 Block. Heal 2 HP.",
    descFn(c) { return `Gain ${c.upgraded ? 5 : 4} Block. Heal ${c.upgraded ? 3 : 2} HP.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 5 : 4, ctx); ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + (this.upgraded ? 3 : 2)); }
  }),
  brimstone_shield: createCard("brimstone_shield", {
    name: "Brimstone Shield", type: "Skill", cost: 1, rarity: "Rare", class: "hellforged", target: "self",
    desc: "Gain 3 Block. Gain 2 Strength this turn.",
    descFn(c) { return `Gain ${c.upgraded ? 4 : 3} Block. Gain ${c.upgraded ? 3 : 2} Strength this turn.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 4 : 3, ctx); ctx.player.statuses.tempStrengthThisTurn = (ctx.player.statuses.tempStrengthThisTurn || 0) + (this.upgraded ? 3 : 2); }
  }),
  summon_imp: createCard("summon_imp", {
    name: "Summon Imp", type: "Skill", cost: 1, rarity: "Common", class: "hellforged", exhaust: true, target: "self",
    desc: "Add an Imp Claw to your hand. Exhaust.",
    descFn(c) { return `Add an Imp Claw${c.upgraded ? "+" : ""} to your hand. Exhaust.`; },
    play(ctx) { addCardToHand(ctx, createImpClaw(this.upgraded), "hand"); }
  }),
  infernal_pact: createCard("infernal_pact", {
    name: "Infernal Pact", type: "Skill", cost: 0, rarity: "Uncommon", class: "hellforged", target: "self",
    desc: "Lose 2 HP. Gain 2 Energy. Add an Imp Claw to your hand.",
    descFn(c) { return `Lose ${c.upgraded ? 1 : 2} HP. Gain 2 Energy. Add an Imp Claw${c.upgraded ? "+" : ""} to your hand.`; },
    play(ctx) { payHpCost(ctx, this.upgraded ? 1 : 2); ctx.energy += 2; addCardToHand(ctx, createImpClaw(this.upgraded), "hand"); }
  }),
  hellfire_aura: createCard("hellfire_aura", {
    name: "Hellfire Aura", type: "Power", cost: 2, rarity: "Rare", class: "hellforged", target: "self",
    desc: "At the start of your turn, deal 3 damage to ALL enemies.",
    descFn(c) { return `At the start of your turn, deal ${c.upgraded ? 5 : 3} damage to ALL enemies.`; },
    play(ctx) { const dmg = this.upgraded ? 5 : 3; ctx.powers.push({ name: "hellfire_aura", onTurnStart(c2) { dealDamageAll(c2, c2.player, dmg); } }); }
  }),
  soul_burn: createCard("soul_burn", {
    name: "Soul Burn", type: "Skill", cost: 1, rarity: "Uncommon", class: "hellforged", target: "enemy",
    desc: "Apply 8 Burn to an enemy. Draw 1 card.",
    descFn(c) { return `Apply ${c.upgraded ? 10 : 8} Burn to an enemy. Draw 1 card.`; },
    play(ctx, target) { applyStatus(target, "burn", this.upgraded ? 10 : 8); drawCards(ctx, 1); }
  }),
  // Colorless
  flash_of_insight: createCard("flash_of_insight", {
    name: "Flash of Insight", type: "Skill", cost: 1, rarity: "Common", class: "colorless", target: "self",
    desc: "Draw 2 cards.",
    descFn(c) { return `Draw ${c.upgraded ? 3 : 2} cards.`; },
    play(ctx) { drawCards(ctx, this.upgraded ? 3 : 2); }
  }),
  iron_will: createCard("iron_will", {
    name: "Iron Will", type: "Skill", cost: 1, rarity: "Common", class: "colorless", target: "self",
    desc: "Gain 8 Block.",
    descFn(c) { return `Gain ${c.upgraded ? 11 : 8} Block.`; },
    play(ctx) { gainBlock(ctx.player, this.upgraded ? 11 : 8, ctx); }
  }),
  quick_slash: createCard("quick_slash", {
    name: "Quick Slash", type: "Attack", cost: 1, rarity: "Common", class: "colorless", target: "enemy",
    desc: "Deal 7 damage.",
    descFn(c) { return `Deal ${c.upgraded ? 9 : 7} damage.`; },
    play(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 9 : 7); }
  }),
  potent_brew: createCard("potent_brew", {
    name: "Potent Brew", type: "Skill", cost: 0, rarity: "Uncommon", class: "colorless", exhaust: true, target: "self",
    desc: "Gain 1 Energy. Exhaust.",
    descFn(c) { return `Gain ${c.upgraded ? 2 : 1} Energy. Exhaust.`; },
    play(ctx) { ctx.energy += this.upgraded ? 2 : 1; }
  }),
  // Curses / Status
  wound: createCard("wound", {
    name: "Wound", type: "Curse", cost: -1, rarity: "Curse", unplayable: true, ethereal: true,
    desc: "Unplayable. Ethereal. Exhaust."
  }),
  dazed: createCard("dazed", {
    name: "Dazed", type: "Status", cost: -1, rarity: "Curse", unplayable: true, ethereal: true,
    desc: "Unplayable. Ethereal. Exhaust."
  }),
  doubt: createCard("doubt", {
    name: "Doubt", type: "Curse", cost: -1, rarity: "Curse", unplayable: true,
    desc: "Unplayable. At the end of your turn, lose 1 HP.",
    onTurnEnd(ctx, card) { if (ctx.hand.includes(card)) loseHp(ctx, ctx.player, 1); }
  })
};

// ── function: applyStatusWrapped ──
function applyStatusWrapped(ctx, target, name, amount) {
  if (name === "burn" && target.hp !== undefined && amount > 0) {
    const se = ctx.player.relics.find(r => r.id === "soul_ember");
    if (se) dealDamage(ctx, ctx.player, target, 2);
  }
  applyStatus(target, name, amount);
}

// ── function: patchBurnCards ──
function patchBurnCards() {
  CARD_TEMPLATES.firebolt.play = function(ctx, target) { dealDamage(ctx, ctx.player, target, this.upgraded ? 9 : 7); applyStatusWrapped(ctx, target, "burn", this.upgraded ? 3 : 2); };
  CARD_TEMPLATES.flame_ward.play = function(ctx) {
    gainBlock(ctx.player, this.upgraded ? 6 : 5, ctx);
    const alive = ctx.enemies.filter(e => e.hp > 0);
    if (alive.length) applyStatusWrapped(ctx, RNG.choice(alive), "burn", this.upgraded ? 3 : 2);
  };
  CARD_TEMPLATES.soul_burn.play = function(ctx, target) { applyStatusWrapped(ctx, target, "burn", this.upgraded ? 10 : 8); drawCards(ctx, 1); };
}

// ── function: createEnemy ──
function createEnemy(type, opts) {
  const e = {
    uid: Math.random().toString(36).slice(2),
    type,
    name: opts.name,
    maxHp: opts.hp,
    hp: opts.hp,
    block: 0,
    statuses: { strength: 0, dexterity: 0, regeneration: 0, intangible: 0, weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, tempStrength: 0 },
    intentIndex: 0,
    intents: opts.intents,
    phase: 1,
    threshold: opts.threshold || 0,
    phase2Intents: opts.phase2Intents || null,
    enragePerTurn: opts.enragePerTurn || 0,
    isMinion: opts.isMinion || false,
    summonType: opts.summonType || null,
    getIntent() {
      const list = (this.phase === 2 && this.phase2Intents) ? this.phase2Intents : this.intents;
      return list[this.intentIndex % list.length];
    }
  };
  return e;
}

// ── function: nextIntent ──
function nextIntent(enemy) {
  enemy.intentIndex++;
}

// ── function: intentText ──
function intentText(intent) {
  if (!intent) return "";
  if (intent.type === "attack") return `Attack ${intent.value}`;
  if (intent.type === "multi") return `${intent.count}x${intent.value}`;
  if (intent.type === "defend") return `Defend ${intent.value}`;
  if (intent.type === "buff") return `Buff: ${intent.buff} ${intent.value}`;
  if (intent.type === "debuff") return `Debuff: ${intent.debuff} ${intent.value}`;
  if (intent.type === "summon") return `Summon ${intent.summonName}`;
  return "";
}

// ── function: executeIntent ──
function executeIntent(ctx, enemy, intent) {
  if (enemy.hp <= 0) return;
  if (intent.type === "attack") {
    dealDamage(ctx, enemy, ctx.player, intent.value);
  } else if (intent.type === "multi") {
    for (let i = 0; i < intent.count; i++) {
      if (ctx.player.hp <= 0) break;
      dealDamage(ctx, enemy, ctx.player, intent.value);
    }
  } else if (intent.type === "defend") {
    enemy.block += intent.value;
  } else if (intent.type === "buff") {
    if (intent.buff === "strength") applyStatus(enemy, "strength", intent.value);
    else if (intent.buff === "intangible") applyStatus(enemy, "intangible", intent.value);
    else if (intent.buff === "block") enemy.block += intent.value;
    else if (intent.buff === "regeneration") applyStatus(enemy, "regeneration", intent.value);
    else if (intent.buff === "all_strength") for (const e of ctx.enemies) if (e.hp > 0) applyStatus(e, "strength", intent.value);
    else if (intent.buff === "ally_block_regen") {
      const allies = ctx.enemies.filter(e => e.hp > 0 && e !== enemy);
      const target = allies.length ? RNG.choice(allies) : enemy;
      target.block += intent.value;
      applyStatus(target, "regeneration", 2);
    }
  } else if (intent.type === "debuff") {
    const debuffTarget = intent.self ? enemy : ctx.player;
    if (intent.debuff === "weak") applyStatus(debuffTarget, "weak", intent.value);
    else if (intent.debuff === "vulnerable") applyStatus(debuffTarget, "vulnerable", intent.value);
    else if (intent.debuff === "bleed") applyStatus(debuffTarget, "bleed", intent.value);
    else if (intent.debuff === "burn") applyStatus(debuffTarget, "burn", intent.value);
    else if (intent.debuff === "poison") applyStatus(debuffTarget, "poison", intent.value);
  } else if (intent.type === "mixed") {
    if (intent.attack) dealDamage(ctx, enemy, ctx.player, intent.attack);
    if (intent.block) enemy.block += intent.block;
    if (intent.heal) enemy.hp = Math.min(enemy.maxHp, enemy.hp + intent.heal);
    if (intent.debuff) {
      const debuffTarget = intent.debuffSelf ? enemy : ctx.player;
      if (intent.debuff === "weak") applyStatus(debuffTarget, "weak", intent.debuffVal || 1);
      if (intent.debuff === "vulnerable") applyStatus(debuffTarget, "vulnerable", intent.debuffVal || 1);
      if (intent.debuff === "burn") applyStatus(debuffTarget, "burn", intent.debuffVal || 1);
    }
    if (intent.debuff2) {
      const debuffTarget = intent.debuff2Self ? enemy : ctx.player;
      if (intent.debuff2 === "weak") applyStatus(debuffTarget, "weak", intent.debuff2Val || 1);
      if (intent.debuff2 === "vulnerable") applyStatus(debuffTarget, "vulnerable", intent.debuff2Val || 1);
    }
    if (intent.buff) {
      if (intent.buff === "strength") applyStatus(enemy, "strength", intent.buffVal || 1);
      if (intent.buff === "regeneration") applyStatus(enemy, "regeneration", intent.buffVal || 1);
    }
  } else if (intent.type === "summon") {
    const count = ctx.enemies.filter(e => e.hp > 0 && e.isMinion).length;
    const max = intent.max || 2;
    if (count < max) {
      const minion = ENEMY_TEMPLATES[intent.summonType]();
      minion.isMinion = true;
      ctx.enemies.push(minion);
      ctx.log && ctx.log(`Summoned ${minion.name}`);
    }
  }
}

// ── const: ENEMY_TEMPLATES ──
const ENEMY_TEMPLATES = {
  // Act 1
  skeleton_warrior: () => createEnemy("skeleton_warrior", { name: "Skeleton Warrior", hp: 22, intents: [{ type: "attack", value: 6 }, { type: "attack", value: 6 }, { type: "defend", value: 8 }] }),
  zombie_brute: () => createEnemy("zombie_brute", { name: "Zombie Brute", hp: 28, intents: [{ type: "attack", value: 8 }, { type: "debuff", debuff: "weak", value: 2 }, { type: "attack", value: 8 }] }),
  dark_cultist: () => createEnemy("dark_cultist", { name: "Dark Cultist", hp: 20, intents: [{ type: "buff", buff: "strength", value: 3 }, { type: "attack", value: 5 }, { type: "attack", value: 5 }] }),
  rat_swarm: () => createEnemy("rat_swarm", { name: "Rat Swarm", hp: 15, intents: [{ type: "multi", count: 2, value: 3 }, { type: "multi", count: 2, value: 3 }] }),
  tomb_guardian: () => createEnemy("tomb_guardian", { name: "Tomb Guardian", hp: 42, intents: [{ type: "mixed", attack: 6, block: 8 }, { type: "multi", count: 3, value: 4 }, { type: "attack", value: 12 }] }),
  blood_witch: () => createEnemy("blood_witch", { name: "Blood Witch", hp: 36, intents: [{ type: "mixed", attack: 5, heal: 4 }, { type: "mixed", debuff: "vulnerable", debuffVal: 1 }, { type: "attack", value: 7 }] }),
  lich_king: () => createEnemy("lich_king", {
    name: "Lich King", hp: 65,
    intents: [{ type: "summon", summonName: "Skeleton Warrior", summonType: "skeleton_warrior", max: 2 }, { type: "attack", value: 8 }, { type: "defend", value: 12 }],
    threshold: 30,
    phase2Intents: [{ type: "attack", value: 10 }, { type: "attack", value: 10 }, { type: "defend", value: 15 }],
    enragePerTurn: 2
  }),
  // Act 2
  wraith: () => createEnemy("wraith", { name: "Wraith", hp: 30, intents: [{ type: "mixed", attack: 7, debuff: "weak", debuffVal: 1 }, { type: "attack", value: 7 }, { type: "defend", value: 10 }] }),
  corrupted_beast: () => createEnemy("corrupted_beast", { name: "Corrupted Beast", hp: 38, intents: [{ type: "attack", value: 12 }, { type: "mixed", block: 5, debuff: "vulnerable", debuffVal: 1 }, { type: "attack", value: 12 }] }),
  thorned_horror: () => createEnemy("thorned_horror", { name: "Thorned Horror", hp: 25, intents: [{ type: "multi", count: 3, value: 4 }, { type: "debuff", debuff: "bleed", value: 3 }, { type: "attack", value: 8 }] }),
  witch_doctor: () => createEnemy("witch_doctor", {
    name: "Witch Doctor", hp: 22,
    intents: [{ type: "buff", buff: "ally_block_regen", value: 4 }, { type: "mixed", attack: 6, debuff: "poison", debuffVal: 2 }, { type: "attack", value: 6 }]
  }),
  treant_protector: () => createEnemy("treant_protector", { name: "Treant Protector", hp: 50, intents: [{ type: "mixed", attack: 9, block: 6 }, { type: "debuff", debuff: "vulnerable", value: 2 }, { type: "multi", count: 2, value: 10 }] }),
  plague_shaman: () => createEnemy("plague_shaman", { name: "Plague Shaman", hp: 42, intents: [{ type: "debuff", debuff: "poison", value: 4 }, { type: "summon", summonName: "Plague Rat", summonType: "plague_rat", max: 2 }, { type: "attack", value: 8 }] }),
  plague_rat: () => createEnemy("plague_rat", { name: "Plague Rat", hp: 18, intents: [{ type: "attack", value: 5 }, { type: "attack", value: 5 }] }),
  eldritch_treant: () => createEnemy("eldritch_treant", {
    name: "Eldritch Treant", hp: 75,
    intents: [{ type: "mixed", attack: 10, debuff: "weak", debuffVal: 2 }, { type: "mixed", block: 15, buff: "regeneration", value: 2 }, { type: "multi", count: 2, value: 8 }],
    threshold: 40,
    phase2Intents: [{ type: "mixed", attack: 14, debuff: "vulnerable", debuffVal: 1 }, { type: "mixed", block: 20, heal: 5 }, { type: "mixed", attack: 14, debuff: "vulnerable", debuffVal: 1 }],
    enragePerTurn: 1
  }),
  // Act 3
  hellhound: () => createEnemy("hellhound", { name: "Hellhound", hp: 26, intents: [{ type: "mixed", attack: 8, debuff: "burn", debuffVal: 2 }, { type: "attack", value: 8 }, { type: "defend", value: 9 }] }),
  shadow_demon: () => createEnemy("shadow_demon", { name: "Shadow Demon", hp: 30, intents: [{ type: "mixed", attack: 7, debuff: "weak", debuffVal: 1 }, { type: "attack", value: 7 }, { type: "buff", buff: "intangible", value: 1 }] }),
  infernal_imp: () => createEnemy("infernal_imp", { name: "Infernal Imp", hp: 18, intents: [{ type: "attack", value: 5 }, { type: "attack", value: 5 }, { type: "summon", summonName: "Infernal Imp", summonType: "infernal_imp", max: 3 }] }),
  abyssal_cultist: () => createEnemy("abyssal_cultist", { name: "Abyssal Cultist", hp: 24, intents: [{ type: "buff", buff: "all_strength", value: 2 }, { type: "attack", value: 7 }, { type: "attack", value: 7 }] }),
  flamespawn: () => createEnemy("flamespawn", { name: "Flamespawn", hp: 48, intents: [{ type: "mixed", attack: 9, debuff: "burn", debuffVal: 3 }, { type: "mixed", block: 10, buff: "strength", value: 2 }, { type: "multi", count: 2, value: 8 }] }),
  dread_knight: () => createEnemy("dread_knight", { name: "Dread Knight", hp: 55, intents: [{ type: "attack", value: 12 }, { type: "debuff", debuff: "bleed", value: 3 }, { type: "mixed", block: 12, heal: 4 }] }),
  archdemon: () => createEnemy("archdemon", {
    name: "Archdemon", hp: 85,
    intents: [{ type: "summon", summonName: "Hellhound", summonType: "hellhound", max: 1 }, { type: "mixed", attack: 12, debuff: "burn", debuffVal: 2 }, { type: "mixed", block: 15, buff: "strength", value: 3 }],
    threshold: 45,
    phase2Intents: [{ type: "attack", value: 16 }, { type: "multi", count: 3, value: 7 }, { type: "attack", value: 16 }],
    enragePerTurn: 2
  })
};

// ── function: patchEnemyIntents ──
function patchEnemyIntents() {
  ENEMY_TEMPLATES.corrupted_beast = () => createEnemy("corrupted_beast", { name: "Corrupted Beast", hp: 38, intents: [{ type: "attack", value: 12 }, { type: "mixed", block: 5, debuff: "vulnerable", debuffVal: 1, debuffSelf: true }, { type: "attack", value: 12 }] });
  ENEMY_TEMPLATES.blood_witch = () => createEnemy("blood_witch", { name: "Blood Witch", hp: 36, intents: [{ type: "mixed", attack: 5, heal: 4 }, { type: "mixed", debuff: "vulnerable", debuffVal: 1, debuff2: "weak", debuff2Val: 1 }, { type: "mixed", attack: 7, heal: 4 }] });
}

// ── function: createRelic ──
function createRelic(id, opts) {
  return { id, name: opts.name, desc: opts.desc, rarity: opts.rarity || "Common", class: opts.class || null, unique: opts.unique || false, used: false, onEvent: opts.onEvent || {} };
}

// ── const: RELIC_POOL ──
const RELIC_POOL = [
  createRelic("bloodstone", {
    name: "Bloodstone", desc: "Whenever you lose HP from a card, heal 1 HP.", rarity: "Common"
  }),
  createRelic("demon_horns", {
    name: "Demon Horns", desc: "At the start of each combat, add a random Imp Claw to your hand.", rarity: "Uncommon", class: "hellforged",
    onEvent: { combatStart(ctx) { addCardToHand(ctx, createImpClaw(Math.random() < 0.5), "hand"); } }
  }),
  createRelic("shadow_cloak", {
    name: "Shadow Cloak", desc: "At the start of each combat, gain 1 Intangible.", rarity: "Rare",
    onEvent: { combatStart(ctx) { applyStatus(ctx.player, "intangible", 1); } }
  }),
  createRelic("cursed_amulet", {
    name: "Cursed Amulet", desc: "At the start of each combat, apply 1 Weak to ALL enemies.", rarity: "Common",
    onEvent: { combatStart(ctx) { for (const e of ctx.enemies) applyStatus(e, "weak", 1); } }
  }),
  createRelic("tome_of_dark_rituals", {
    name: "Tome of Dark Rituals", desc: "Whenever you play a Power card, draw 1 card.", rarity: "Uncommon"
  }),
  createRelic("bleeding_edge", {
    name: "Bleeding Edge", desc: "At the start of your turn, apply 1 Bleed to a random enemy.", rarity: "Common",
    onEvent: { playerTurnStart(ctx) { const alive = ctx.enemies.filter(e => e.hp > 0); if (alive.length) applyStatus(RNG.choice(alive), "bleed", 1); } }
  }),
  createRelic("soul_ember", {
    name: "Soul Ember", desc: "Whenever you apply Burn to an enemy, deal 2 additional damage.", rarity: "Rare"
  }),
  createRelic("eternal_hourglass", {
    name: "Eternal Hourglass", desc: "At the start of your turn, if you have no cards in hand, gain 1 Energy.", rarity: "Uncommon",
    onEvent: { playerTurnStart(ctx) { if (ctx.hand.length === 0) ctx.energy += 1; } }
  }),
  createRelic("sacrificial_dagger", {
    name: "Sacrificial Dagger", desc: "Your first Attack that costs HP each combat costs 1 less HP.", rarity: "Uncommon", class: "bloodreaper"
  }),
  createRelic("vial_of_shadows", {
    name: "Vial of Shadows", desc: "Upon pickup, choose a card to remove from your deck.", rarity: "Rare", unique: true,
    onEvent: { pickup(ctx, relic) { if (!relic.used) { relic.used = true; ctx.pendingReward = { type: "removeCard", relic: true }; } } }
  }),
  createRelic("ashen_armor", {
    name: "Ashen Armor", desc: "At the end of combat, heal 6 HP.", rarity: "Common",
    onEvent: { combatEnd(ctx) { ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + 6); } }
  }),
  createRelic("greedy_imp", {
    name: "Greedy Imp", desc: "Enemies drop 10 extra Gold.", rarity: "Common"
  })
];

// ── const: BOSS_RELICS ──
const BOSS_RELICS = [
  { id: "cursed_crown", name: "Cursed Crown", desc: "Gain 1 Energy at the start of each turn. Curses can no longer be removed.", energyPerTurn: 1, cursesRemovable: false },
  { id: "ectoplasm", name: "Ectoplasm", desc: "Gain 1 Energy at the start of each turn. Gain no more Gold this run.", energyPerTurn: 1, noGold: true },
  { id: "philosophers_stone", name: "Philosopher's Stone", desc: "Gain 1 Energy at the start of each turn. All enemies start combat with 1 Strength.", energyPerTurn: 1, enemyStrength: 1 },
  { id: "black_blood", name: "Black Blood", desc: "At the start of combat, lose 10% max HP. Whenever you play an Attack, heal 1 HP.", combatHpLoss: 0.1, attackHeal: 1 },
  { id: "shadow_amulet", name: "Shadow Amulet", desc: "Start each turn with 1 Intangible. Reduce max HP by 15.", intangiblePerTurn: 1, maxHpMod: -15 }
];

// ── function: createPotion ──
function createPotion(id, opts) {
  return { id, name: opts.name, desc: opts.desc, effect: opts.effect };
}

// ── const: POTION_POOL ──
const POTION_POOL = [
  createPotion("blood_vial", { name: "Blood Vial", desc: "Heal 15 HP.", effect: (ctx) => { ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + 15); } }),
  createPotion("firebomb", { name: "Firebomb", desc: "Deal 10 damage to ALL enemies.", effect: (ctx) => { for (const e of ctx.enemies) if (e.hp > 0) dealDamage(ctx, ctx.player, e, 10); } }),
  createPotion("dex_tonic", { name: "Dex Tonic", desc: "Gain 3 Dexterity for 1 turn.", effect: (ctx) => { applyStatus(ctx.player, "dexterity", 3); ctx.player.statuses.dexterityTemporal = true; } }),
  createPotion("energy_elixir", { name: "Energy Elixir", desc: "Gain 2 Energy this turn.", effect: (ctx) => { ctx.energy += 2; } }),
  createPotion("cursed_brew", { name: "Cursed Brew", desc: "Apply 3 Vulnerable to all enemies.", effect: (ctx) => { for (const e of ctx.enemies) if (e.hp > 0) applyStatus(e, "vulnerable", 3); } }),
  createPotion("bleeding_flask", { name: "Bleeding Flask", desc: "Apply 6 Bleed to an enemy.", effect: (ctx, target) => { if (target) applyStatus(target, "bleed", 6); } }),
  createPotion("imp_in_a_bottle", { name: "Imp-in-a-Bottle", desc: "Add an Imp Claw+ to your hand.", effect: (ctx) => { addCardToHand(ctx, createImpClaw(true), "hand"); } }),
  createPotion("smoke_bomb", { name: "Smoke Bomb", desc: "Escape from a non-boss combat, no rewards.", effect: (ctx) => { if (!ctx.isBoss) ctx.escaped = true; } })
];

// ── function: createEvent ──
function createEvent(id, opts) {
  return { id, name: opts.name, text: opts.text, choices: opts.choices };
}

// ── const: EVENT_POOL ──
const EVENT_POOL = [
  createEvent("forgotten_altar", {
    name: "The Forgotten Altar",
    text: "A blood-stained altar hums with power.",
    choices: [
      { text: "Offer 15 HP", effect: (ctx, cb) => { const actual = loseHp(ctx, ctx.player, 15); const relic = RNG.choice(usableRelics(ctx)); addRelic(ctx, relic); cb(); } },
      { text: "Pray", effect: (ctx, cb) => { ctx.player.hp = Math.min(ctx.player.maxHp, ctx.player.hp + 10); addCardToDeck(ctx, CARD_TEMPLATES.doubt.clone()); cb(); } },
      { text: "Leave", effect: (ctx, cb) => { cb(); } }
    ]
  }),
  createEvent("shadowy_peddler", {
    name: "Shadowy Peddler",
    text: "A hooded figure offers a trade.",
    choices: [
      { text: "Trade a card", effect: (ctx, cb) => { ctx.pendingReward = { type: "removeCard", callback: () => { ctx.player.gold += 100; cb(); } }; } },
      { text: "Buy his wares (50 Gold)", effect: (ctx, cb) => { if (ctx.player.gold >= 50) { ctx.player.gold -= 50; addPotion(ctx, RNG.choice(POTION_POOL)); addRelic(ctx, RNG.choice(RELIC_POOL.filter(r => !r.class || r.class === ctx.player.className))); cb(); } else cb(); } },
      { text: "Attack", effect: (ctx, cb) => { ctx.pendingCombat = { enemies: [ENEMY_TEMPLATES.shadow_thief ? ENEMY_TEMPLATES.shadow_thief() : createShadowThief()], rewardGold: 75, onWin: cb }; } }
    ]
  }),
  createEvent("cursed_spring", {
    name: "Cursed Spring",
    text: "A spring bubbles with dark water.",
    choices: [
      { text: "Drink", effect: (ctx, cb) => { loseHp(ctx, ctx.player, 10); ctx.player.deck = ctx.player.deck.filter(c => c.type !== "Curse"); cb(); } },
      { text: "Bottle it", effect: (ctx, cb) => { addPotion(ctx, POTION_POOL.find(p => p.id === "cursed_brew")); cb(); } },
      { text: "Leave", effect: (ctx, cb) => { cb(); } }
    ]
  }),
  createEvent("restless_dead", {
    name: "Restless Dead",
    text: "Ghosts offer a bargain.",
    choices: [
      { text: "Accept", effect: (ctx, cb) => { loseHp(ctx, ctx.player, 20); const cards = RNG.shuffle(ctx.player.deck.filter(c => !c.upgraded)).slice(0, 2); for (const c of cards) c.upgraded = true; cb(); } },
      { text: "Refuse", effect: (ctx, cb) => { cb(); } }
    ]
  }),
  createEvent("impish_gambler", {
    name: "Impish Gambler",
    text: "A grinning imp challenges you.",
    choices: [
      { text: "Bet 30 Gold", effect: (ctx, cb) => { if (ctx.player.gold >= 30) { ctx.player.gold -= 30; if (RNG.chance(0.5)) ctx.player.gold += 60; } cb(); } },
      { text: "Wager a card", effect: (ctx, cb) => { if (ctx.player.deck.length) { const c = RNG.choice(ctx.player.deck); ctx.player.deck = ctx.player.deck.filter(x => x !== c); if (c.type === "Curse") ctx.player.gold += 100; } cb(); } },
      { text: "Leave", effect: (ctx, cb) => { cb(); } }
    ]
  }),
  createEvent("ancient_library", {
    name: "Ancient Library",
    text: "Dusty tomes contain forgotten knowledge.",
    choices: [
      { text: "Study", effect: (ctx, cb) => { loseHp(ctx, ctx.player, 5); ctx.player.statuses.energyNextTurnCombat = (ctx.player.statuses.energyNextTurnCombat || 0) + 1; cb(); } },
      { text: "Read forbidden page", effect: (ctx, cb) => { const pool = classCardPool(ctx.player.className); const powers = pool.filter(c => c.type === "Power"); if (powers.length) { const c = RNG.choice(powers).clone(); c.upgraded = false; addCardToDeck(ctx, c); } addCardToDeck(ctx, CARD_TEMPLATES.doubt.clone()); cb(); } },
      { text: "Leave", effect: (ctx, cb) => { cb(); } }
    ]
  })
];

// ── function: createShadowThief ──
function createShadowThief() {
  return createEnemy("shadow_thief", { name: "Shadow Thief", hp: 32, intents: [{ type: "attack", value: 8 }, { type: "debuff", debuff: "weak", value: 1 }, { type: "multi", count: 2, value: 5 }] });
}

// ── function: usableRelics ──
function usableRelics(ctx) {
  return RELIC_POOL.filter(r => !r.class || r.class === ctx.player.className);
}

// ── function: addRelic ──
function addRelic(ctx, relic) {
  ctx.player.relics.push(relic.clone ? relic.clone() : { ...relic });
}

// ── function: addPotion ──
function addPotion(ctx, potion) {
  if (ctx.player.potions.length < 3) ctx.player.potions.push({ ...potion });
}

// ── function: addCardToDeck ──
function addCardToDeck(ctx, card) {
  ctx.player.deck.push(card);
}

// ── const: ACT_DEFINITIONS ──
const ACT_DEFINITIONS = [
  {
    name: "The Crypts",
    enemyPool: ["skeleton_warrior", "zombie_brute", "dark_cultist", "rat_swarm"],
    floorEncounters: [
      { type: "enemy", groups: [["skeleton_warrior"], ["dark_cultist"]] },
      { type: "enemy", groups: [["zombie_brute"]] },
      { type: "event" },
      { type: "shop" },
      { type: "enemy", groups: [["rat_swarm"]] },
      { type: "rest" },
      { type: "enemy", groups: [["skeleton_warrior", "skeleton_warrior"], ["zombie_brute"]] },
      { type: "elite", groups: [["tomb_guardian"]], alt: [["skeleton_warrior", "rat_swarm"]] },
      { type: "event" },
      { type: "enemy", groups: [["dark_cultist", "rat_swarm"]] },
      { type: "elite", groups: [["blood_witch"]], alt: [["zombie_brute", "skeleton_warrior"]] },
      { type: "enemy", groups: [["dark_cultist", "dark_cultist"]] },
      { type: "rest" },
      { type: "boss", boss: "lich_king" }
    ]
  },
  {
    name: "Blighted Woods",
    enemyPool: ["wraith", "corrupted_beast", "thorned_horror", "witch_doctor"],
    floorEncounters: [
      { type: "enemy", groups: [["wraith"], ["thorned_horror"]] },
      { type: "enemy", groups: [["corrupted_beast"]] },
      { type: "event" },
      { type: "shop" },
      { type: "enemy", groups: [["witch_doctor"]] },
      { type: "rest" },
      { type: "enemy", groups: [["wraith", "thorned_horror"], ["corrupted_beast"]] },
      { type: "elite", groups: [["treant_protector"]], alt: [["wraith", "witch_doctor"]] },
      { type: "event" },
      { type: "enemy", groups: [["thorned_horror", "witch_doctor"]] },
      { type: "elite", groups: [["plague_shaman"]], alt: [["corrupted_beast", "wraith"]] },
      { type: "enemy", groups: [["witch_doctor", "witch_doctor"]] },
      { type: "rest" },
      { type: "boss", boss: "eldritch_treant" }
    ]
  },
  {
    name: "Abyssal Spire",
    enemyPool: ["hellhound", "shadow_demon", "infernal_imp", "abyssal_cultist"],
    floorEncounters: [
      { type: "enemy", groups: [["hellhound"], ["abyssal_cultist"]] },
      { type: "enemy", groups: [["shadow_demon"]] },
      { type: "event" },
      { type: "shop" },
      { type: "enemy", groups: [["infernal_imp"]] },
      { type: "rest" },
      { type: "enemy", groups: [["hellhound", "infernal_imp"], ["shadow_demon"]] },
      { type: "elite", groups: [["flamespawn"]], alt: [["hellhound", "abyssal_cultist"]] },
      { type: "event" },
      { type: "enemy", groups: [["shadow_demon", "infernal_imp"]] },
      { type: "elite", groups: [["dread_knight"]], alt: [["shadow_demon", "abyssal_cultist"]] },
      { type: "enemy", groups: [["abyssal_cultist", "abyssal_cultist"]] },
      { type: "rest" },
      { type: "boss", boss: "archdemon" }
    ]
  }
];

// ── function: generateMap ──
function generateMap(actIndex) {
  const act = ACT_DEFINITIONS[actIndex];
  const rows = [];
  // Start row
  rows.push([{ floor: 0, col: 1, type: "start", label: "Start" }]);
  for (let i = 0; i < act.floorEncounters.length; i++) {
    const enc = act.floorEncounters[i];
    const floor = i + 1;
    const nodes = [];
    if (enc.type === "enemy") {
      for (let g = 0; g < enc.groups.length; g++) {
        nodes.push({ floor, col: g * 2, type: "enemy", label: "Enemy", enemies: enc.groups[g] });
      }
    } else if (enc.type === "elite") {
      nodes.push({ floor, col: 0, type: "elite", label: "Elite", enemies: enc.groups[0], alt: false });
      if (enc.alt) nodes.push({ floor, col: 2, type: "enemy", label: "Enemy", enemies: enc.alt[0], altNode: true });
    } else {
      nodes.push({ floor, col: 1, type: enc.type, label: enc.type.charAt(0).toUpperCase() + enc.type.slice(1) });
    }
    rows.push(nodes);
  }
  // Connections
  for (let r = 0; r < rows.length - 1; r++) {
    for (const node of rows[r]) {
      node.next = [];
      const nextRow = rows[r + 1];
      for (const next of nextRow) {
        if (Math.abs(next.col - node.col) <= 2) node.next.push(next);
      }
    }
  }
  return rows;
}

// ── function: getAvailableNodes ──
function getAvailableNodes(mapRows, currentNode) {
  if (!currentNode) return mapRows[1];
  return currentNode.next || [];
}

// ── function: createPlayer ──
function createPlayer(className) {
  const deck = [];
  for (let i = 0; i < 5; i++) deck.push(CARD_TEMPLATES.strike.clone());
  for (let i = 0; i < 4; i++) deck.push(CARD_TEMPLATES.defend.clone());
  if (className === "bloodreaper") deck.push(CARD_TEMPLATES.sanguine_strike.clone());
  else if (className === "shadowmancer") deck.push(CARD_TEMPLATES.shadow_bolt.clone());
  else if (className === "hellforged") deck.push(CARD_TEMPLATES.firebolt.clone());
  return {
    className,
    hp: 70, maxHp: 70,
    gold: 99,
    deck,
    potions: [],
    relics: [],
    statuses: { strength: 0, dexterity: 0, regeneration: 0, intangible: 0, weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, tempStrength: 0, thisTurnStrength: 0, blockNextTurn: 0, dexterityNextTurn: 0, energyNextTurn: 0, energyNextTurnCombat: 0 },
    flags: {}
  };
}

// ── function: classCardPool ──
function classCardPool(className) {
  return Object.values(CARD_TEMPLATES).filter(c => c.class === className && !["Basic", "Curse", "Special"].includes(c.rarity));
}

// ── function: rewardCardPool ──
function rewardCardPool(className, isElite, isBoss) {
  const pool = classCardPool(className);
  if (isBoss) {
    const rare = pool.filter(c => c.rarity === "Rare");
    const others = pool.filter(c => c.rarity !== "Rare");
    return [RNG.choice(rare), RNG.choice(others), RNG.choice(others)];
  }
  const result = [];
  for (let i = 0; i < 3; i++) {
    const rareChance = isElite ? 0.2 : 0.1;
    const rarity = RNG.chance(rareChance) ? "Rare" : (RNG.chance(0.5) ? "Uncommon" : "Common");
    const sub = pool.filter(c => c.rarity === rarity);
    result.push(RNG.choice(sub.length ? sub : pool));
  }
  return result;
}

// ── function: generateRewardCards ──
function generateRewardCards(className, isElite, isBoss) {
  const cards = rewardCardPool(className, isElite, isBoss);
  return cards.map(c => c.clone());
}

// ── function: startRun ──
function startRun(className) {
  state.player = createPlayer(className);
  state.act = 0;
  state.mapRows = generateMap(0);
  state.currentNode = state.mapRows[0][0];
  state.combat = null;
  state.pending = null;
  state.log = [];
  renderMap();
}

// ── function: advanceAct ──
function advanceAct() {
  state.act++;
  if (state.act >= ACT_DEFINITIONS.length) {
    renderVictory();
    return;
  }
  state.player.hp = state.player.maxHp; // full heal after boss
  state.mapRows = generateMap(state.act);
  state.currentNode = state.mapRows[0][0];
  renderMap();
}

// ── function: selectNode ──
function selectNode(node) {
  const available = getAvailableNodes(state.mapRows, state.currentNode);
  if (!available.includes(node)) return;
  state.currentNode = node;
  if (node.type === "enemy" || node.type === "elite" || node.type === "boss") {
    startCombatFromNode(node);
  } else if (node.type === "event") {
    startEvent();
  } else if (node.type === "rest") {
    renderRest();
  } else if (node.type === "shop") {
    renderShop();
  }
}

// ── function: startCombatFromNode ──
function startCombatFromNode(node) {
  const enemyIds = node.enemies || [node.boss];
  startCombat(enemyIds.map(id => ENEMY_TEMPLATES[id] ? id : null).filter(Boolean), node.type === "boss");
}

// ── function: startCombat ──
function startCombat(enemyIdList, isBoss) {
  state.screen = "combat";
  const enemies = enemyIdList.map(id => ENEMY_TEMPLATES[id]()).filter(e => e);
  const bossRelic = state.player.relics.find(r => BOSS_RELICS.some(br => br.id === r.id));
  state.combat = {
    enemies,
    hand: [],
    drawPile: RNG.shuffle(state.player.deck.map(c => c.clone())),
    discardPile: [],
    exhaustPile: [],
    powers: [],
    energy: 0,
    turn: 0,
    isBoss,
    escaped: false,
    log: []
  };
  state.combat.player = state.player;
  state.combat.flags = { sacrificialDaggerUsed: false };
  state.player.statuses.block = 0;
  state.player.flags.sacrificialDaggerUsed = false;

  // Boss relic combat start
  if (bossRelic) {
    const br = BOSS_RELICS.find(x => x.id === bossRelic.id);
    if (br.combatHpLoss) {
      loseHp(state.combat, state.player, Math.floor(state.player.maxHp * br.combatHpLoss));
    }
    if (br.enemyStrength) {
      for (const e of enemies) applyStatus(e, "strength", br.enemyStrength);
    }
  }
  // Relic combat starts
  for (const relic of state.player.relics) {
    if (relic.onEvent && relic.onEvent.combatStart) relic.onEvent.combatStart(state.combat);
  }
  // Black Blood start hp loss handled above; max hp mod for Shadow Amulet handled at pickup
  startPlayerTurn();
  renderCombat();
}

// ── function: startPlayerTurn ──
function startPlayerTurn() {
  const c = state.combat;
  if (c.escaped) { endCombat(true); return; }
  c.turn++;
  c.log.push(`--- Turn ${c.turn} ---`);
  // Reset block
  state.player.statuses.block = 0;
  // Decay duration statuses
  decayStatuses(state.player, true);
  // Start-of-turn poison/bleed for player
  applyBleedDamage(state.player);
  applyPoisonDamage(state.player);
  // Next turn bonuses
  if (state.player.statuses.blockNextTurn) { state.player.statuses.block += state.player.statuses.blockNextTurn; state.player.statuses.blockNextTurn = 0; }
  if (state.player.statuses.dexterityNextTurn) { state.player.statuses.dexterity += state.player.statuses.dexterityNextTurn; state.player.statuses.dexterityNextTurn = 0; }
  // Energy
  let energyGain = 3;
  const bossRelic = state.player.relics.find(r => BOSS_RELICS.some(br => br.id === r.id));
  if (bossRelic) {
    const br = BOSS_RELICS.find(x => x.id === bossRelic.id);
    if (br.energyPerTurn) energyGain += br.energyPerTurn;
  }
  energyGain += state.player.statuses.energyNextTurn || 0;
  state.player.statuses.energyNextTurn = 0;
  c.energy = energyGain;
  state.player.flags.sacrificialDaggerUsed = false;
  // Relics and powers start of turn
  for (const relic of state.player.relics) {
    if (relic.onEvent && relic.onEvent.playerTurnStart) relic.onEvent.playerTurnStart(c);
  }
  c.powers.forEach(p => { if (p.onTurnStart) p.onTurnStart(c); });
  // Draw
  drawCards(c, 5);
  // Dex Tonic temporary clear? It was applied as permanent dexterity; for one turn we'll subtract at end of turn
  renderCombat();
}

// ── function: decayStatuses ──
function decayStatuses(entity, isPlayer) {
  if (entity.statuses.weak > 0) entity.statuses.weak--;
  if (entity.statuses.vulnerable > 0) entity.statuses.vulnerable--;
  if (entity.statuses.poison > 0) {
    // Handled separately
  }
  if (entity.statuses.bleed > 0) {
    // Handled separately
  }
  if (entity.statuses.tempStrength !== 0) entity.statuses.tempStrength = 0;
  if (isPlayer && entity.statuses.intangible > 0) entity.statuses.intangible--;
  if (!isPlayer && entity.statuses.intangible > 0) entity.statuses.intangible--;
}

// ── function: applyBleedDamage ──
function applyBleedDamage(entity) {
  if (entity.hp <= 0) return;
  const bleed = entity.statuses.bleed || 0;
  if (bleed > 0) {
    loseHp(state.combat, entity, bleed);
    state.combat.log.push(`${entity.name || "You"} took ${bleed} Bleed damage.`);
    entity.statuses.bleed = Math.floor(bleed / 2);
  }
}

// ── function: applyPoisonDamage ──
function applyPoisonDamage(entity) {
  if (entity.hp <= 0) return;
  const poison = entity.statuses.poison || 0;
  if (poison > 0) {
    loseHp(state.combat, entity, poison);
    state.combat.log.push(`${entity.name || "You"} took ${poison} Poison damage.`);
    entity.statuses.poison--;
  }
}

// ── function: applyBurnDamage ──
function applyBurnDamage(entity) {
  if (entity.hp <= 0) return;
  const burn = entity.statuses.burn || 0;
  if (burn > 0) {
    loseHp(state.combat, entity, burn);
    state.combat.log.push(`${entity.name || "You"} took ${burn} Burn damage.`);
    entity.statuses.burn = Math.floor(burn / 2);
  }
}

// ── function: applyRegeneration ──
function applyRegeneration(entity) {
  const regen = entity.statuses.regeneration || 0;
  if (regen > 0) {
    entity.hp = Math.min(entity.maxHp, entity.hp + regen);
    state.combat.log.push(`${entity.name || "You"} regenerated ${regen} HP.`);
    entity.statuses.regeneration--;
  }
}

// ── function: enemyTurn ──
function enemyTurn() {
  const c = state.combat;
  for (const enemy of c.enemies) {
    if (enemy.hp <= 0) continue;
    // Phase check
    if (enemy.phase === 1 && enemy.threshold && enemy.hp <= enemy.threshold) {
      enemy.phase = 2;
      enemy.intentIndex = 0;
      c.log.push(`${enemy.name} enrages!`);
    }
    // Enrage strength in phase 2
    if (enemy.phase === 2 && enemy.enragePerTurn) {
      applyStatus(enemy, "strength", enemy.enragePerTurn);
    }
    // Decay statuses and start-of-turn damage
    decayStatuses(enemy, false);
    applyBleedDamage(enemy);
    applyPoisonDamage(enemy);
    if (enemy.hp <= 0) continue;
    // Execute intent
    const intent = enemy.getIntent();
    c.log.push(`${enemy.name} uses ${intentText(intent)}.`);
    executeIntent(c, enemy, intent);
    nextIntent(enemy);
    applyBurnDamage(enemy);
    applyRegeneration(enemy);
    if (state.player.hp <= 0) break;
  }
  if (state.player.hp <= 0) { renderGameOver(); return; }
  if (c.enemies.every(e => e.hp <= 0)) { endCombat(true); return; }
  startPlayerTurn();
}

// ── function: playCard ──
function playCard(cardIndex, target) {
  const c = state.combat;
  const card = c.hand[cardIndex];
  if (!card || card.unplayable) return false;
  const cost = card.getCost();
  if (cost !== "X" && c.energy < cost) return false;
  if (cost === "X") {
    card.xEnergy = c.energy;
    c.energy = 0;
  } else {
    c.energy -= cost;
  }
  c.hand.splice(cardIndex, 1);
  c.log.push(`Played ${card.name}${card.upgraded ? "+" : ""}.`);

  // Tome of Dark Rituals
  if (card.type === "Power") {
    const tome = state.player.relics.find(r => r.id === "tome_of_dark_rituals");
    if (tome) drawCards(c, 1);
  }
  // Black Blood
  const bb = state.player.relics.find(r => r.id === "black_blood");
  if (bb && card.type === "Attack") {
    state.player.hp = Math.min(state.player.maxHp, state.player.hp + 1);
  }

  if (card.type === "Power") {
    card.play(c, target);
    c.powers.push({ name: card.id, upgraded: card.upgraded, onTurnStart: card.onTurnStart ? (ctx) => card.onTurnStart(ctx) : null });
    // Move to powers zone - do not discard/exhaust
  } else {
    card.play(c, target);
    if (card.exhaust) {
      exhaustCard(c, card);
    } else {
      c.discardPile.push(card);
    }
  }

  if (c.enemies.every(e => e.hp <= 0)) { endCombat(true); return true; }
  renderCombat();
  return true;
}

// ── function: endPlayerTurnAction ──
function endPlayerTurnAction() {
  const c = state.combat;
  // Burn at end of player turn
  applyBurnDamage(state.player);
  applyRegeneration(state.player);
  // Doubt curse damage handled in discardHand via onTurnEnd
  discardHand(c);
  state.player.statuses.thisTurnStrength = 0;
  c.selectedCardIndex = -1;
  if (state.player.hp <= 0) { renderGameOver(); return; }
  if (c.escaped) { endCombat(true); return; }
  enemyTurn();
}

// ── function: endCombat ──
function endCombat(victory) {
  const c = state.combat;
  if (victory) {
    // Ashen Armor
    const ashen = state.player.relics.find(r => r.id === "ashen_armor");
    if (ashen) state.player.hp = Math.min(state.player.maxHp, state.player.hp + 6);
    if (c.isBoss) {
      // Boss rewards
      const gold = 50;
      state.player.gold += gold + (state.player.relics.some(r => r.id === "greedy_imp") ? 10 : 0);
      const relicChoices = RNG.shuffle(BOSS_RELICS).slice(0, 3).map(r => ({ ...r }));
      const potions = [RNG.choice(POTION_POOL), RNG.choice(POTION_POOL)].map(p => ({ ...p }));
      renderBossRewards(gold, relicChoices, potions);
    } else {
      const isElite = c.enemies.some(e => ["tomb_guardian", "blood_witch", "treant_protector", "plague_shaman", "flamespawn", "dread_knight"].includes(e.type));
      const gold = isElite ? RNG.int(25, 35) : RNG.int(10, 20);
      state.player.gold += gold + (state.player.relics.some(r => r.id === "greedy_imp") ? 10 : 0);
      const relic = isElite ? RNG.choice(usableRelics({ player: state.player })) : null;
      if (relic) addRelic({ player: state.player }, relic);
      const potionChance = isElite ? 0.7 : 0.4;
      const potion = RNG.chance(potionChance) ? RNG.choice(POTION_POOL) : null;
      if (potion) addPotion({ player: state.player }, potion);
      const cards = generateRewardCards(state.player.className, isElite, false);
      renderCombatRewards(gold, cards, potion, relic);
    }
  } else {
    renderGameOver();
  }
  state.player.statuses.block = 0;
}

// ── function: usePotion ──
function usePotion(index, target) {
  const p = state.player.potions[index];
  if (!p) return;
  p.effect(state.combat, target);
  state.player.potions.splice(index, 1);
  if (state.combat.escaped && !state.combat.isBoss) { endCombat(true); return; }
  renderCombat();
}

// ── const: state ──
const state = {
  screen: "class_select",
  player: null,
  act: 0,
  mapRows: [],
  currentNode: null,
  combat: null,
  pending: null,
  log: []
};

// ── function: initState ──
function initState() {
  state.screen = "class_select";
  state.player = null;
  state.act = 0;
  state.mapRows = [];
  state.currentNode = null;
  state.combat = null;
  state.pending = null;
  state.log = [];
}

// ── function: startEvent ──
function startEvent() {
  const event = RNG.choice(EVENT_POOL);
  state.pending = { type: "event", event };
  renderEvent();
}

// ── function: pickEventChoice ──
function pickEventChoice(choiceIndex) {
  const ev = state.pending.event;
  const choice = ev.choices[choiceIndex];
  choice.effect(state, () => {
    if (state.pending && state.pending.type === "removeCard") {
      // special case handled by render
    } else if (state.pending && state.pending.type === "combat") {
      const pc = state.pending;
      startCombatFromPending(pc);
    } else if (!state.pending || state.pending.type === "event") {
      state.pending = null;
      renderMap();
    }
  });
}

// ── function: startCombatFromPending ──
function startCombatFromPending(pc) {
  state.pending = null;
  startCombat(pc.enemies.map(e => e.type), false);
  state.combat.pendingGold = pc.rewardGold;
}

// ── function: getCardBaseCost ──
function getCardBaseCost(card) {
  if (card.id === "vital_surge") return "X";
  return card.upgraded ? (card.costUp ?? card.baseCost) : card.baseCost;
}

// ── function: renderClassSelect ──
function renderClassSelect() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">Shadows of the Abyss</h1><div class="subtitle">Choose your cursed archetype</div>`;
  const grid = document.createElement("div");
  grid.className = "class-grid";
  for (const key of Object.keys(CLASSES)) {
    const card = document.createElement("div");
    card.className = "class-card";
    card.style.borderColor = CLASS_COLORS[key];
    card.innerHTML = `<div class="class-name">${CLASSES[key].name}</div><div class="class-desc">${CLASSES[key].desc}</div>`;
    card.onclick = () => { startRun(key); };
    grid.appendChild(card);
  }
  div.appendChild(grid);
  screens.appendChild(div);
}

// ── function: formatStatuses ──
function formatStatuses(statuses) {
  const arr = [];
  if (statuses.strength) arr.push(`Str ${statuses.strength}`);
  if (statuses.dexterity) arr.push(`Dex ${statuses.dexterity}`);
  if (statuses.regeneration) arr.push(`Regen ${statuses.regeneration}`);
  if (statuses.intangible) arr.push(`Intangible ${statuses.intangible}`);
  if (statuses.weak) arr.push(`Weak ${statuses.weak}`);
  if (statuses.vulnerable) arr.push(`Vuln ${statuses.vulnerable}`);
  if (statuses.bleed) arr.push(`Bleed ${statuses.bleed}`);
  if (statuses.burn) arr.push(`Burn ${statuses.burn}`);
  if (statuses.poison) arr.push(`Poison ${statuses.poison}`);
  if (statuses.blockNextTurn) arr.push(`Blk+ ${statuses.blockNextTurn}`);
  if (statuses.dexterityNextTurn) arr.push(`Dex+ ${statuses.dexterityNextTurn}`);
  if (statuses.energyNextTurn) arr.push(`En+ ${statuses.energyNextTurn}`);
  return arr;
}

// ── function: statusPills ──
function statusPills(statuses) {
  const s = formatStatuses(statuses);
  if (!s.length) return "";
  return `<div class="status-list">${s.map(x => `<span class="status-pill">${x}</span>`).join("")}</div>`;
}

// ── function: cardElement ──
function cardElement(card, opts = {}) {
  const el = document.createElement("div");
  el.className = `card ${card.upgraded ? "card-upgrade" : ""} card-type-${card.type} ${card.rarity ? "card-rarity-" + card.rarity.toLowerCase() : ""} ${opts.small ? "card-small" : ""} ${opts.selected ? "selected" : ""} ${opts.unplayable ? "unplayable" : ""}`;
  const cost = card.getCost();
  el.innerHTML = `
    <div class="card-cost">${cost}</div>
    <div class="card-name">${card.name}${card.upgraded ? "+" : ""}</div>
    <div class="card-type">${card.type}</div>
    <div class="card-desc">${card.getDescription()}</div>
  `;
  return el;
}

// ── function: renderMap ──
function renderMap() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen map-screen fade-in";
  const act = ACT_DEFINITIONS[state.act];
  div.innerHTML = `<h1 class="title">${act.name}</h1><div class="subtitle">Act ${state.act + 1} — Floor ${state.currentNode ? state.currentNode.floor + 1 : 1}</div>`;
  const grid = document.createElement("div");
  grid.className = "map-grid";
  const available = getAvailableNodes(state.mapRows, state.currentNode);
  for (let r = 0; r < state.mapRows.length; r++) {
    const row = state.mapRows[r];
    for (const node of row) {
      const n = document.createElement("div");
      n.className = `mapNode ${node.type} ${state.currentNode === node ? "current" : ""} ${available.includes(node) ? "available" : ""} ${state.currentNode !== node && node.visited ? "visited" : ""}`;
      n.innerHTML = `<div>${node.label}</div><div style="font-size:9px">${node.floor + 1}</div>`;
      if (available.includes(node)) {
        n.onclick = () => selectNode(node);
      }
      grid.appendChild(n);
    }
  }
  div.appendChild(grid);
  if (state.player) {
    const hud = document.createElement("div");
    hud.className = "hud";
    hud.style.marginTop = "20px";
    hud.innerHTML = `
      <div class="hud-stat stat-hp">HP: ${state.player.hp}/${state.player.maxHp}</div>
      <div class="hud-stat stat-gold">Gold: ${state.player.gold}</div>
      <div class="hud-stat">Deck: ${state.player.deck.length}</div>
      <div class="hud-stat">Relics: ${state.player.relics.length}</div>
      <div class="hud-stat">Potions: ${state.player.potions.length}/3</div>
    `;
    div.appendChild(hud);
  }
  screens.appendChild(div);
}

// ── function: renderCombat ──
function renderCombat() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const c = state.combat;
  const div = document.createElement("div");
  div.className = "screen combat-frame fade-in";

  // HUD
  const hud = document.createElement("div");
  hud.className = "hud";
  const deckCount = c.drawPile.length;
  const discardCount = c.discardPile.length;
  const exhaustCount = c.exhaustPile.length;
  hud.innerHTML = `
    <div class="hud-stat stat-hp">HP: ${state.player.hp}/${state.player.maxHp}</div>
    <div class="hud-stat stat-block">Block: ${state.player.statuses.block}</div>
    <div class="hud-stat stat-energy">Energy: ${c.energy}</div>
    <div class="hud-stat stat-gold">Gold: ${state.player.gold}</div>
    <div class="hud-stat">Deck: ${deckCount}</div>
    <div class="hud-stat">Discard: ${discardCount}</div>
    <div class="hud-stat">Turn: ${c.turn}</div>
  `;
  div.appendChild(hud);

  // Enemy area
  const enemyArea = document.createElement("div");
  enemyArea.className = "enemy-area";
  for (const enemy of c.enemies) {
    const e = document.createElement("div");
    e.className = "enemy" + (state.combat.selectedTarget === enemy.uid ? " active" : "");
    const hpPct = Math.max(0, (enemy.hp / enemy.maxHp) * 100);
    const intent = enemy.getIntent();
    e.innerHTML = `
      <div style="font-weight:bold">${enemy.name}</div>
      <div class="enemy-hp-bar"><div class="enemy-hp-fill" style="width:${hpPct}%"></div></div>
      <div>${enemy.hp}/${enemy.maxHp} HP</div>
      <div class="enemy-block">Block: ${enemy.statuses.block}</div>
      <div class="enemy-intent">Next: ${intentText(intent)}</div>
      ${statusPills(enemy.statuses)}
    `;
    e.onclick = () => selectTarget(enemy);
    enemyArea.appendChild(e);
  }
  div.appendChild(enemyArea);

  // Player zone
  const playerZone = document.createElement("div");
  playerZone.className = "player-zone";
  const logBox = document.createElement("div");
  logBox.className = "log-box";
  logBox.innerHTML = c.log.slice(-10).map(x => `<div>${x}</div>`).join("");
  const avatar = document.createElement("div");
  avatar.className = "player-avatar";
  const hpPct2 = Math.max(0, (state.player.hp / state.player.maxHp) * 100);
  avatar.innerHTML = `
    <div style="font-weight:bold;font-size:18px">${CLASSES[state.player.className].name}</div>
    <div class="player-hp-bar"><div class="player-hp-fill" style="width:${hpPct2}%"></div></div>
    <div>${state.player.hp}/${state.player.maxHp} HP</div>
    <div class="player-block">Block: ${state.player.statuses.block}</div>
    ${statusPills(state.player.statuses)}
  `;
  const piles = document.createElement("div");
  piles.className = "pile-zone";
  piles.innerHTML = `
    <div><div class="card card-pile">Draw ${deckCount}</div><div class="pile-info">Draw</div></div>
    <div><div class="card card-pile">Discard ${discardCount}</div><div class="pile-info">Discard</div></div>
    <div><div class="card card-pile">Exhaust ${exhaustCount}</div><div class="pile-info">Exhaust</div></div>
  `;
  playerZone.appendChild(logBox);
  playerZone.appendChild(avatar);
  playerZone.appendChild(piles);
  div.appendChild(playerZone);

  // Hand
  const hand = document.createElement("div");
  hand.className = "hand-zone";
  c.hand.forEach((card, idx) => {
    const cost = card.getCost();
    const unplayable = card.unplayable || (cost !== "X" && c.energy < cost) || (cost === "X" && c.energy <= 0);
    const el = cardElement(card, { selected: c.selectedCardIndex === idx, unplayable });
    if (!unplayable) {
      el.onclick = () => selectCard(idx);
    }
    hand.appendChild(el);
  });
  div.appendChild(hand);

  // Controls
  const controls = document.createElement("div");
  controls.className = "controls";
  const endBtn = document.createElement("button");
  endBtn.textContent = "End Turn";
  endBtn.onclick = endPlayerTurnAction;
  controls.appendChild(endBtn);
  // Potions
  state.player.potions.forEach((potion, idx) => {
    const btn = document.createElement("button");
    btn.textContent = potion.name;
    btn.title = potion.desc;
    btn.onclick = () => usePotion(idx, c.enemies.find(e => e.hp > 0));
    controls.appendChild(btn);
  });
  div.appendChild(controls);

  screens.appendChild(div);
  logBox.scrollTop = logBox.scrollHeight;
}

// ── function: selectCard ──
function selectCard(index) {
  const c = state.combat;
  const card = c.hand[index];
  if (!card) return;
  if (card.target === "enemy" || card.target === "all_random") {
    // For enemy target, need selection
    if (card.target === "enemy") {
      if (c.selectedCardIndex === index) {
        // Try play first enemy if only one
        const alive = c.enemies.filter(e => e.hp > 0);
        if (alive.length === 1) { playCard(index, alive[0]); return; }
      }
      c.selectedCardIndex = index;
      c.selectedTarget = null;
      renderCombat();
      return;
    }
  }
  playCard(index, null);
}

// ── function: selectTarget ──
function selectTarget(enemy) {
  const c = state.combat;
  if (c.selectedCardIndex >= 0) {
    playCard(c.selectedCardIndex, enemy);
  } else {
    c.selectedTarget = enemy.uid;
    renderCombat();
  }
}

// ── function: renderEvent ──
function renderEvent() {
  const ev = state.pending.event;
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">${ev.name}</h1><div class="event-text">${ev.text}</div>`;
  const choices = document.createElement("div");
  choices.className = "event-choices";
  ev.choices.forEach((ch, idx) => {
    const btn = document.createElement("button");
    btn.textContent = ch.text;
    btn.onclick = () => {
      pickEventChoice(idx);
      if (state.pending && state.pending.type === "removeCard") {
        showCardPicker(state.player.deck, "Choose a card to remove", (card) => {
          state.player.deck = state.player.deck.filter(c => c !== card);
          if (state.pending.callback) state.pending.callback();
          state.pending = null;
          renderMap();
        });
      } else if (state.pending && state.pending.type === "combat") {
        // combat start handled by pickEventChoice already
      }
    };
    choices.appendChild(btn);
  });
  div.appendChild(choices);
  screens.appendChild(div);
}

// ── function: renderRest ──
function renderRest() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">Rest Site</h1><div class="subtitle">Choose one action</div>`;
  const opts = document.createElement("div");
  opts.className = "rest-options";
  const restBtn = document.createElement("button");
  restBtn.textContent = "Rest (Heal 25%)";
  restBtn.onclick = () => { state.player.hp = Math.min(state.player.maxHp, state.player.hp + Math.floor(state.player.maxHp * 0.25)); renderMap(); };
  const forgeBtn = document.createElement("button");
  forgeBtn.textContent = "Forge (Upgrade card)";
  forgeBtn.onclick = () => showCardPicker(state.player.deck, "Choose a card to upgrade", (card) => { card.upgraded = true; renderMap(); });
  const medBtn = document.createElement("button");
  medBtn.textContent = "Meditate (Remove card)";
  medBtn.onclick = () => showCardPicker(state.player.deck, "Choose a card to remove", (card) => { state.player.deck = state.player.deck.filter(c => c !== card); renderMap(); });
  opts.appendChild(restBtn);
  opts.appendChild(forgeBtn);
  opts.appendChild(medBtn);
  div.appendChild(opts);
  screens.appendChild(div);
}

// ── function: renderShop ──
function renderShop() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">Shop</h1><div class="subtitle">Gold: ${state.player.gold}</div>`;
  const grid = document.createElement("div");
  grid.className = "shop-grid";
  // Class cards
  const pool = classCardPool(state.player.className);
  const shopCards = [RNG.choice(pool), RNG.choice(pool), RNG.choice(pool)];
  const colorless = [CARD_TEMPLATES.flash_of_insight, CARD_TEMPLATES.iron_will, CARD_TEMPLATES.quick_slash, CARD_TEMPLATES.potent_brew];
  shopCards.push(RNG.choice(colorless));
  for (const cardT of shopCards) {
    const card = cardT.clone();
    const price = card.rarity === "Rare" ? 150 : (card.rarity === "Uncommon" ? 75 : 50);
    const item = document.createElement("div");
    item.className = "shop-item";
    item.appendChild(cardElement(card));
    item.innerHTML += `<div class="shop-price">${price}g</div>`;
    item.onclick = () => { if (state.player.gold >= price) { state.player.gold -= price; addCardToDeck({ player: state.player }, card.clone()); renderShop(); } };
    grid.appendChild(item);
  }
  // Relics
  for (let i = 0; i < 2; i++) {
    const relic = RNG.choice(usableRelics({ player: state.player }));
    const item = document.createElement("div");
    item.className = "shop-item";
    item.innerHTML = `<div style="font-weight:bold;color:var(--accent-gold)">${relic.name}</div><div style="font-size:13px;color:var(--text-dim)">${relic.desc}</div><div class="shop-price">150g</div>`;
    item.onclick = () => { if (state.player.gold >= 150) { state.player.gold -= 150; addRelic({ player: state.player }, relic); renderShop(); } };
    grid.appendChild(item);
  }
  // Potions
  for (let i = 0; i < 3; i++) {
    const potion = RNG.choice(POTION_POOL);
    const item = document.createElement("div");
    item.className = "shop-item";
    item.innerHTML = `<div style="font-weight:bold">${potion.name}</div><div style="font-size:13px;color:var(--text-dim)">${potion.desc}</div><div class="shop-price">50g</div>`;
    item.onclick = () => { if (state.player.gold >= 50 && state.player.potions.length < 3) { state.player.gold -= 50; addPotion({ player: state.player }, potion); renderShop(); } };
    grid.appendChild(item);
  }
  // Remove card service
  const removeBtn = document.createElement("button");
  removeBtn.textContent = "Remove a card (75g)";
  removeBtn.onclick = () => { if (state.player.gold >= 75) { state.player.gold -= 75; showCardPicker(state.player.deck, "Choose a card to remove", (card) => { state.player.deck = state.player.deck.filter(c => c !== card); renderMap(); }); } };
  div.appendChild(grid);
  div.appendChild(document.createElement("br"));
  div.appendChild(removeBtn);
  const leaveBtn = document.createElement("button");
  leaveBtn.textContent = "Leave";
  leaveBtn.onclick = () => renderMap();
  div.appendChild(leaveBtn);
  screens.appendChild(div);
}

// ── function: renderCombatRewards ──
function renderCombatRewards(gold, cards, potion, relic) {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">Victory!</h1><div class="subtitle">Loot: ${gold}g</div>`;
  const grid = document.createElement("div");
  grid.className = "reward-grid";
  for (const card of cards) {
    const item = document.createElement("div");
    item.className = "reward-item";
    item.appendChild(cardElement(card));
    item.onclick = () => { state.player.deck.push(card); renderMap(); };
    grid.appendChild(item);
  }
  const skip = document.createElement("div");
  skip.className = "reward-item";
  skip.innerHTML = `<div style="padding-top:60px;font-weight:bold">Skip card</div>`;
  skip.onclick = () => renderMap();
  grid.appendChild(skip);
  div.appendChild(grid);
  if (potion) {
    const pdiv = document.createElement("div");
    pdiv.innerHTML = `<h3>Potion obtained: ${potion.name}</h3><p>${potion.desc}</p>`;
    div.appendChild(pdiv);
  }
  if (relic) {
    const rdiv = document.createElement("div");
    rdiv.innerHTML = `<h3>Relic obtained: ${relic.name}</h3><p>${relic.desc}</p>`;
    div.appendChild(rdiv);
  }
  const cont = document.createElement("button");
  cont.textContent = "Continue";
  cont.onclick = () => {
    if (potion && !state.player.potions.some(p => p.name === potion.name && p.id === potion.id)) {
      // auto-add potion if not shown
    }
    renderMap();
  };
  div.appendChild(cont);
  screens.appendChild(div);
}

// ── function: renderBossRewards ──
function renderBossRewards(gold, relicChoices, potions) {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen fade-in";
  div.innerHTML = `<h1 class="title">Boss Defeated!</h1><div class="subtitle">Choose a Boss Relic. Gold: ${gold}g</div>`;
  const grid = document.createElement("div");
  grid.className = "boss-relic-pick";
  for (const relic of relicChoices) {
    const item = document.createElement("div");
    item.className = "reward-item";
    item.style.maxWidth = "260px";
    item.innerHTML = `<div style="font-weight:bold;color:var(--rare)">${relic.name}</div><div style="font-size:13px;color:var(--text-dim)">${relic.desc}</div>`;
    item.onclick = () => {
      state.player.relics.push({ id: relic.id, name: relic.name, desc: relic.desc, onEvent: {} });
      if (relic.maxHpMod) { state.player.maxHp += relic.maxHpMod; state.player.hp = Math.min(state.player.maxHp, state.player.hp); }
      for (const p of potions) addPotion({ player: state.player }, p);
      state.player.gold += gold;
      advanceAct();
    };
    grid.appendChild(item);
  }
  div.appendChild(grid);
  screens.appendChild(div);
}

// ── function: renderGameOver ──
function renderGameOver() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen run-over fade-in";
  div.innerHTML = `<h1>Run Over</h1><p>You fell in the ${ACT_DEFINITIONS[state.act].name}.</p>`;
  const btn = document.createElement("button");
  btn.textContent = "Try Again";
  btn.onclick = () => { initState(); renderClassSelect(); };
  div.appendChild(btn);
  screens.appendChild(div);
}

// ── function: renderVictory ──
function renderVictory() {
  const screens = document.getElementById("screens");
  screens.innerHTML = "";
  const div = document.createElement("div");
  div.className = "screen victory fade-in";
  div.innerHTML = `<h1>Victory</h1><p>You have defeated the Archdemon and conquered the Abyss.</p>`;
  const btn = document.createElement("button");
  btn.textContent = "New Run";
  btn.onclick = () => { initState(); renderClassSelect(); };
  div.appendChild(btn);
  screens.appendChild(div);
}

// ── function: showModal ──
function showModal(title, contentHtml, buttons) {
  const modal = document.getElementById("modal");
  const mc = modal.querySelector(".modal-content");
  mc.innerHTML = `<h2>${title}</h2>${contentHtml}`;
  const btnRow = document.createElement("div");
  btnRow.style.marginTop = "15px";
  btnRow.style.display = "flex";
  btnRow.style.gap = "10px";
  for (const b of buttons) {
    const btn = document.createElement("button");
    btn.textContent = b.text;
    btn.onclick = () => { hideModal(); if (b.action) b.action(); };
    btnRow.appendChild(btn);
  }
  mc.appendChild(btnRow);
  modal.classList.remove("hidden");
}

// ── function: hideModal ──
function hideModal() {
  document.getElementById("modal").classList.add("hidden");
}

// ── function: showCardPicker ──
function showCardPicker(cards, title, onPick) {
  const modal = document.getElementById("modal");
  const mc = modal.querySelector(".modal-content");
  mc.innerHTML = `<h2>${title}</h2><div class="card-row" id="picker-cards"></div>`;
  const row = mc.querySelector("#picker-cards");
  for (const card of cards) {
    const el = cardElement(card);
    el.onclick = () => { hideModal(); onPick(card); };
    row.appendChild(el);
  }
  modal.classList.remove("hidden");
}

// ── function: patchGameLogic ──
function patchGameLogic() {
  // Fix playCard reset selection
  const originalPlayCard = playCard;
  playCard = function(cardIndex, target) {
    const res = originalPlayCard(cardIndex, target);
    if (state.combat) state.combat.selectedCardIndex = -1;
    return res;
  };
  // Fix end of player turn: clear temporal dexterity
  const originalEndTurn = endPlayerTurnAction;
  endPlayerTurnAction = function() {
    if (state.player.statuses.dexterityTemporal) {
      state.player.statuses.dexterity -= state.player.statuses.dexterityTemporal;
      state.player.statuses.dexterityTemporal = 0;
    }
    originalEndTurn();
  };
  // Fix decayStatuses: don't reset tempStrength; reset after enemy action
  decayStatuses = function(entity, isPlayer) {
    if (entity.statuses.weak > 0) entity.statuses.weak--;
    if (entity.statuses.vulnerable > 0) entity.statuses.vulnerable--;
    if (isPlayer && entity.statuses.intangible > 0) entity.statuses.intangible--;
    if (!isPlayer && entity.statuses.intangible > 0) entity.statuses.intangible--;
  };
  // Reset enemy tempStrength after action
  const oldEnemyTurn = enemyTurn;
  enemyTurn = function() {
    oldEnemyTurn();
    if (state.combat) {
      for (const e of state.combat.enemies) e.statuses.tempStrength = 0;
    }
  };
  // Fix generateMap to include boss id
  const oldGenerateMap = generateMap;
  generateMap = function(actIndex) {
    const act = ACT_DEFINITIONS[actIndex];
    const rows = [];
    rows.push([{ floor: 0, col: 1, type: "start", label: "Start" }]);
    for (let i = 0; i < act.floorEncounters.length; i++) {
      const enc = act.floorEncounters[i];
      const floor = i + 1;
      const nodes = [];
      if (enc.type === "enemy") {
        for (let g = 0; g < enc.groups.length; g++) {
          nodes.push({ floor, col: g * 2, type: "enemy", label: "Enemy", enemies: enc.groups[g] });
        }
      } else if (enc.type === "elite") {
        nodes.push({ floor, col: 0, type: "elite", label: "Elite", enemies: enc.groups[0] });
        if (enc.alt) nodes.push({ floor, col: 2, type: "enemy", label: "Enemy", enemies: enc.alt[0], altNode: true });
      } else if (enc.type === "boss") {
        nodes.push({ floor, col: 1, type: "boss", label: "Boss", boss: enc.boss, enemies: [enc.boss] });
      } else {
        nodes.push({ floor, col: 1, type: enc.type, label: enc.type.charAt(0).toUpperCase() + enc.type.slice(1) });
      }
      rows.push(nodes);
    }
    for (let r = 0; r < rows.length - 1; r++) {
      for (const node of rows[r]) {
        node.next = [];
        const nextRow = rows[r + 1];
        for (const next of nextRow) {
          if (Math.abs(next.col - node.col) <= 2) node.next.push(next);
        }
      }
    }
    return rows;
  };
  // Patch event definitions to use state
  EVENT_POOL[0].choices[0].effect = (ctx, cb) => { const actual = loseHp(state, state.player, 15); const relic = RNG.choice(usableRelics({ player: state.player })); addRelic({ player: state.player }, relic); cb(); };
  EVENT_POOL[0].choices[1].effect = (ctx, cb) => { state.player.hp = Math.min(state.player.maxHp, state.player.hp + 10); addCardToDeck({ player: state.player }, CARD_TEMPLATES.doubt.clone()); cb(); };
  EVENT_POOL[0].choices[2].effect = (ctx, cb) => { cb(); };
  EVENT_POOL[1].choices[0].effect = (ctx, cb) => { state.pending = { type: "removeCard", callback: () => { state.player.gold += 100; cb(); } }; };
  EVENT_POOL[1].choices[1].effect = (ctx, cb) => { if (state.player.gold >= 50) { state.player.gold -= 50; addPotion({ player: state.player }, RNG.choice(POTION_POOL)); addRelic({ player: state.player }, RNG.choice(usableRelics({ player: state.player }))); } cb(); };
  EVENT_POOL[1].choices[2].effect = (ctx, cb) => { state.pending = { type: "combat", enemies: [createShadowThief()], rewardGold: 75, onWin: cb }; };
  EVENT_POOL[2].choices[0].effect = (ctx, cb) => { loseHp(state, state.player, 10); state.player.deck = state.player.deck.filter(c => c.type !== "Curse"); cb(); };
  EVENT_POOL[2].choices[1].effect = (ctx, cb) => { addPotion({ player: state.player }, POTION_POOL.find(p => p.id === "cursed_brew")); cb(); };
  EVENT_POOL[2].choices[2].effect = (ctx, cb) => { cb(); };
  EVENT_POOL[3].choices[0].effect = (ctx, cb) => { loseHp(state, state.player, 20); const cards = RNG.shuffle(state.player.deck.filter(c => !c.upgraded)).slice(0, 2); for (const c of cards) c.upgraded = true; cb(); };
  EVENT_POOL[3].choices[1].effect = (ctx, cb) => { cb(); };
  EVENT_POOL[4].choices[0].effect = (ctx, cb) => { if (state.player.gold >= 30) { state.player.gold -= 30; if (RNG.chance(0.5)) state.player.gold += 60; } cb(); };
  EVENT_POOL[4].choices[1].effect = (ctx, cb) => { if (state.player.deck.length) { const c = RNG.choice(state.player.deck); state.player.deck = state.player.deck.filter(x => x !== c); if (c.type === "Curse") state.player.gold += 100; } cb(); };
  EVENT_POOL[4].choices[2].effect = (ctx, cb) => { cb(); };
  EVENT_POOL[5].choices[0].effect = (ctx, cb) => { loseHp(state, state.player, 5); state.player.statuses.energyNextTurnCombat = (state.player.statuses.energyNextTurnCombat || 0) + 1; cb(); };
  EVENT_POOL[5].choices[1].effect = (ctx, cb) => { const pool = classCardPool(state.player.className).filter(c => c.type === "Power"); if (pool.length) { const c = RNG.choice(pool).clone(); addCardToDeck({ player: state.player }, c); } addCardToDeck({ player: state.player }, CARD_TEMPLATES.doubt.clone()); cb(); };
  EVENT_POOL[5].choices[2].effect = (ctx, cb) => { cb(); };
}

// ── function: patchPowersAndRewards ──
function patchPowersAndRewards() {
  // Fix Power double push
  const originalPlayCard = playCard;
  playCard = function(cardIndex, target) {
    const c = state.combat;
    const card = c.hand[cardIndex];
    if (!card || card.unplayable) return false;
    const cost = card.getCost();
    if (cost !== "X" && c.energy < cost) return false;
    if (cost === "X") { c.energy = 0; }
    else { c.energy -= cost; }
    c.hand.splice(cardIndex, 1);
    c.log.push(`Played ${card.name}${card.upgraded ? "+" : ""}.`);
    // Tome of Dark Rituals
    if (card.type === "Power") {
      const tome = state.player.relics.find(r => r.id === "tome_of_dark_rituals");
      if (tome) drawCards(c, 1);
    }
    // Black Blood
    const bb = state.player.relics.find(r => r.id === "black_blood");
    if (bb && card.type === "Attack") {
      state.player.hp = Math.min(state.player.maxHp, state.player.hp + 1);
    }
    card.play(c, target);
    if (card.type !== "Power") {
      if (card.exhaust) exhaustCard(c, card);
      else c.discardPile.push(card);
    }
    if (c.enemies.every(e => e.hp <= 0)) { endCombat(true); return true; }
    c.selectedCardIndex = -1;
    renderCombat();
    return true;
  };
  // Fix endCombat pending gold
  const oldEndCombat = endCombat;
  endCombat = function(victory) {
    const pending = state.combat ? state.combat.pendingGold : 0;
    oldEndCombat(victory);
  };
  // Actually override endCombat fully to handle pending
  endCombat = function(victory) {
    const c = state.combat;
    if (victory) {
      const ashen = state.player.relics.find(r => r.id === "ashen_armor");
      if (ashen) state.player.hp = Math.min(state.player.maxHp, state.player.hp + 6);
      if (c.isBoss) {
        const gold = 50;
        state.player.gold += gold + (state.player.relics.some(r => r.id === "greedy_imp") ? 10 : 0);
        const relicChoices = RNG.shuffle(BOSS_RELICS).slice(0, 3).map(r => ({ ...r }));
        const potions = [RNG.choice(POTION_POOL), RNG.choice(POTION_POOL)].map(p => ({ ...p }));
        renderBossRewards(gold, relicChoices, potions);
      } else {
        const isElite = c.enemies.some(e => ["tomb_guardian", "blood_witch", "treant_protector", "plague_shaman", "flamespawn", "dread_knight"].includes(e.type));
        const baseGold = c.pendingGold || (isElite ? RNG.int(25, 35) : RNG.int(10, 20));
        state.player.gold += baseGold + (state.player.relics.some(r => r.id === "greedy_imp") ? 10 : 0);
        const relic = isElite ? RNG.choice(usableRelics({ player: state.player })) : null;
        if (relic) addRelic({ player: state.player }, relic);
        const potionChance = isElite ? 0.7 : 0.4;
        const potion = RNG.chance(potionChance) ? RNG.choice(POTION_POOL) : null;
        if (potion) addPotion({ player: state.player }, potion);
        const cards = generateRewardCards(state.player.className, isElite, false);
        renderCombatRewards(baseGold, cards, potion, relic);
      }
    } else {
      renderGameOver();
    }
    state.player.statuses.block = 0;
  };
  // Fix renderCombatRewards to auto add potion/relic
  renderCombatRewards = function(gold, cards, potion, relic) {
    if (potion) addPotion({ player: state.player }, potion);
    if (relic) addRelic({ player: state.player }, relic);
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">Victory!</h1><div class="subtitle">Loot: ${gold}g</div>`;
    const grid = document.createElement("div");
    grid.className = "reward-grid";
    for (const card of cards) {
      const item = document.createElement("div");
      item.className = "reward-item";
      item.appendChild(cardElement(card));
      item.onclick = () => { state.player.deck.push(card); renderMap(); };
      grid.appendChild(item);
    }
    const skip = document.createElement("div");
    skip.className = "reward-item";
    skip.innerHTML = `<div style="padding-top:60px;font-weight:bold">Skip card</div>`;
    skip.onclick = () => renderMap();
    grid.appendChild(skip);
    div.appendChild(grid);
    if (potion) {
      const pdiv = document.createElement("div");
      pdiv.innerHTML = `<h3>Potion obtained: ${potion.name}</h3>`;
      div.appendChild(pdiv);
    }
    if (relic) {
      const rdiv = document.createElement("div");
      rdiv.innerHTML = `<h3>Relic obtained: ${relic.name}</h3>`;
      div.appendChild(rdiv);
    }
    screens.appendChild(div);
  };
}

// ── function: patchBossRelicsAndMisc ──
function patchBossRelicsAndMisc() {
  // Shadow Amulet intangible each turn
  const oldStartPlayerTurn = startPlayerTurn;
  startPlayerTurn = function() {
    const amulet = state.player.relics.find(r => r.id === "shadow_amulet");
    oldStartPlayerTurn();
    if (amulet) applyStatus(state.player, "intangible", 1);
  };
  // Boss reward setup flags
  const oldRenderBossRewards = renderBossRewards;
  renderBossRewards = function(gold, relicChoices, potions) {
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">Boss Defeated!</h1><div class="subtitle">Choose a Boss Relic. Gold: ${gold}g</div>`;
    const grid = document.createElement("div");
    grid.className = "boss-relic-pick";
    for (const relic of relicChoices) {
      const item = document.createElement("div");
      item.className = "reward-item";
      item.style.maxWidth = "260px";
      item.innerHTML = `<div style="font-weight:bold;color:var(--rare)">${relic.name}</div><div style="font-size:13px;color:var(--text-dim)">${relic.desc}</div>`;
      item.onclick = () => {
        state.player.relics.push({ id: relic.id, name: relic.name, desc: relic.desc, onEvent: {} });
        if (relic.maxHpMod) { state.player.maxHp += relic.maxHpMod; state.player.hp = Math.min(state.player.maxHp, state.player.hp); }
        if (relic.id === "cursed_crown") state.player.cursesRemovable = false;
        if (relic.id === "ectoplasm") state.player.noGold = true;
        for (const p of potions) addPotion({ player: state.player }, p);
        state.player.gold += gold;
        if (gold > 0 && state.player.noGold) state.player.gold = 0;
        advanceAct();
      };
      grid.appendChild(item);
    }
    div.appendChild(grid);
    screens.appendChild(div);
  };
  // Prevent gold gain when Ectoplasm
  const oldEndCombat = endCombat;
  endCombat = function(victory) {
    oldEndCombat(victory);
    if (state.player.noGold) state.player.gold = 0;
  };
  // Vial of Shadows on pickup
  const oldAddRelic = addRelic;
  addRelic = function(ctx, relic) {
    state.player.relics.push(relic.clone ? relic.clone() : { ...relic });
    if (relic.id === "vial_of_shadows" || relic.id === "vial-of-shadows") {
      setTimeout(() => showCardPicker(state.player.deck, "Vial of Shadows: remove a card", (card) => {
        state.player.deck = state.player.deck.filter(c => c !== card);
      }), 0);
    }
  };
  // Cursed Crown: block curse removal
  // Patch Rest/Shop remove to check cursesRemovable
  // Already card picker just takes any card; add guard later if needed.
  // Ensure player has cursesRemovable default
  const oldCreatePlayer = createPlayer;
  createPlayer = function(className) {
    const p = oldCreatePlayer(className);
    p.cursesRemovable = true;
    p.noGold = false;
    return p;
  };
}

// ── function: patchEventFlow ──
function patchEventFlow() {
  pickEventChoice = function(choiceIndex) {
    const ev = state.pending.event;
    const choice = ev.choices[choiceIndex];
    choice.effect(state, () => {
      if (state.pending && state.pending.type === "removeCard") {
        showCardPicker(state.player.deck, "Choose a card to remove", (card) => {
          state.player.deck = state.player.deck.filter(c => c !== card);
          if (state.pending.callback) state.pending.callback();
          state.pending = null;
          renderMap();
        });
      } else if (state.pending && state.pending.type === "combat") {
        startCombatFromPending(state.pending);
      } else if (!state.pending || state.pending.type === "event") {
        state.pending = null;
        renderMap();
      }
    });
  };
  renderEvent = function() {
    const ev = state.pending.event;
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">${ev.name}</h1><div class="event-text">${ev.text}</div>`;
    const choices = document.createElement("div");
    choices.className = "event-choices";
    ev.choices.forEach((ch, idx) => {
      const btn = document.createElement("button");
      btn.textContent = ch.text;
      btn.onclick = () => {
        pickEventChoice(idx);
      };
      choices.appendChild(btn);
    });
    div.appendChild(choices);
    screens.appendChild(div);
  };
}

// ── function: patchMapVisited ──
function patchMapVisited() {
  const oldSelectNode = selectNode;
  selectNode = function(node) {
    const available = getAvailableNodes(state.mapRows, state.currentNode);
    if (!available.includes(node)) return;
    if (state.currentNode) state.currentNode.visited = true;
    oldSelectNode(node);
  };
}

// ── function: patchCurseRemoval ──
function patchCurseRemoval() {
  // Rest meditate
  const oldRenderRest = renderRest;
  renderRest = function() {
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">Rest Site</h1><div class="subtitle">Choose one action</div>`;
    const opts = document.createElement("div");
    opts.className = "rest-options";
    const restBtn = document.createElement("button");
    restBtn.textContent = "Rest (Heal 25%)";
    restBtn.onclick = () => { state.player.hp = Math.min(state.player.maxHp, state.player.hp + Math.floor(state.player.maxHp * 0.25)); renderMap(); };
    const forgeBtn = document.createElement("button");
    forgeBtn.textContent = "Forge (Upgrade card)";
    forgeBtn.onclick = () => showCardPicker(state.player.deck, "Choose a card to upgrade", (card) => { card.upgraded = true; renderMap(); });
    const medBtn = document.createElement("button");
    medBtn.textContent = "Meditate (Remove card)";
    medBtn.onclick = () => {
      const deck = state.player.cursesRemovable === false ? state.player.deck.filter(c => c.type !== "Curse") : state.player.deck;
      showCardPicker(deck, "Choose a card to remove", (card) => { state.player.deck = state.player.deck.filter(c => c !== card); renderMap(); });
    };
    opts.appendChild(restBtn);
    opts.appendChild(forgeBtn);
    opts.appendChild(medBtn);
    div.appendChild(opts);
    screens.appendChild(div);
  };
  // Shop remove
  const oldRenderShop = renderShop;
  renderShop = function() {
    oldRenderShop();
    const div = document.getElementById("screens").firstElementChild;
    const oldBtn = Array.from(div.querySelectorAll("button")).find(b => b.textContent.includes("Remove a card"));
    if (oldBtn) {
      oldBtn.onclick = () => { if (state.player.gold >= 75) { state.player.gold -= 75; const deck = state.player.cursesRemovable === false ? state.player.deck.filter(c => c.type !== "Curse") : state.player.deck; showCardPicker(deck, "Choose a card to remove", (card) => { state.player.deck = state.player.deck.filter(c => c !== card); renderMap(); }); } };
    }
  };
  // Cursed Spring respect curses
  EVENT_POOL[2].choices[0].effect = (ctx, cb) => { loseHp(state, state.player, 10); if (state.player.cursesRemovable !== false) state.player.deck = state.player.deck.filter(c => c.type !== "Curse"); cb(); };
}

// ── function: patchDexTonic ──
function patchDexTonic() {
  POTION_POOL[2].effect = (ctx) => { applyStatus(ctx.player, "dexterity", 3); ctx.player.statuses.dexterityTemporal = (ctx.player.statuses.dexterityTemporal || 0) + 3; };
}

// ── function: patchAncientLibrary ──
function patchAncientLibrary() {
  EVENT_POOL[5].choices[0].effect = (ctx, cb) => { loseHp(state, state.player, 5); state.player.statuses.energyNextTurn = (state.player.statuses.energyNextTurn || 0) + 1; cb(); };
}

// ── function: patchEnemyBlock ──
function patchEnemyBlock() {
  const oldCreateEnemy = createEnemy;
  createEnemy = function(type, opts) {
    const e = oldCreateEnemy(type, opts);
    e.statuses.block = 0;
    return e;
  };
}

// ── function: patchSelectCard ──
function patchSelectCard() {
  selectCard = function(index) {
    const c = state.combat;
    const card = c.hand[index];
    if (!card) return;
    if (card.target === "enemy") {
      const alive = c.enemies.filter(e => e.hp > 0);
      if (alive.length === 1) { playCard(index, alive[0]); return; }
      if (c.selectedCardIndex === index && alive.length > 0) { playCard(index, alive[0]); return; }
      c.selectedCardIndex = index;
      c.selectedTarget = null;
      renderCombat();
      return;
    }
    playCard(index, null);
  };
}

// ── function: patchRewardDisplay ──
function patchRewardDisplay() {
  renderCombatRewards = function(gold, cards, potion, relic) {
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">Victory!</h1><div class="subtitle">Loot: ${gold}g</div>`;
    const grid = document.createElement("div");
    grid.className = "reward-grid";
    for (const card of cards) {
      const item = document.createElement("div");
      item.className = "reward-item";
      item.appendChild(cardElement(card));
      item.onclick = () => { state.player.deck.push(card); renderMap(); };
      grid.appendChild(item);
    }
    const skip = document.createElement("div");
    skip.className = "reward-item";
    skip.innerHTML = `<div style="padding-top:60px;font-weight:bold">Skip card</div>`;
    skip.onclick = () => renderMap();
    grid.appendChild(skip);
    div.appendChild(grid);
    if (potion) {
      const pdiv = document.createElement("div");
      pdiv.innerHTML = `<h3>Potion obtained: ${potion.name}</h3>`;
      div.appendChild(pdiv);
    }
    if (relic) {
      const rdiv = document.createElement("div");
      rdiv.innerHTML = `<h3>Relic obtained: ${relic.name}</h3>`;
      div.appendChild(rdiv);
    }
    screens.appendChild(div);
  };
}

// ── function: patchBossRewardGold ──
function patchBossRewardGold() {
  const oldRenderBossRewards = renderBossRewards;
  renderBossRewards = function(gold, relicChoices, potions) {
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen fade-in";
    div.innerHTML = `<h1 class="title">Boss Defeated!</h1><div class="subtitle">Choose a Boss Relic. Gold: ${gold}g</div>`;
    const grid = document.createElement("div");
    grid.className = "boss-relic-pick";
    for (const relic of relicChoices) {
      const item = document.createElement("div");
      item.className = "reward-item";
      item.style.maxWidth = "260px";
      item.innerHTML = `<div style="font-weight:bold;color:var(--rare)">${relic.name}</div><div style="font-size:13px;color:var(--text-dim)">${relic.desc}</div>`;
      item.onclick = () => {
        state.player.relics.push({ id: relic.id, name: relic.name, desc: relic.desc, onEvent: {} });
        if (relic.maxHpMod) { state.player.maxHp += relic.maxHpMod; state.player.hp = Math.min(state.player.maxHp, state.player.hp); }
        if (relic.id === "cursed_crown") state.player.cursesRemovable = false;
        if (relic.id === "ectoplasm") { state.player.noGold = true; state.player.gold = 0; }
        if (relic.id !== "ectoplasm") state.player.gold += gold;
        for (const p of potions) addPotion({ player: state.player }, p);
        advanceAct();
      };
      grid.appendChild(item);
    }
    div.appendChild(grid);
    screens.appendChild(div);
  };
}

// ── function: patchMapRender ──
function patchMapRender() {
  renderMap = function() {
    const screens = document.getElementById("screens");
    screens.innerHTML = "";
    const div = document.createElement("div");
    div.className = "screen map-screen fade-in";
    const act = ACT_DEFINITIONS[state.act];
    div.innerHTML = `<h1 class="title">${act.name}</h1><div class="subtitle">Act ${state.act + 1} — Floor ${state.currentNode ? state.currentNode.floor + 1 : 1}</div>`;
    const container = document.createElement("div");
    container.style.display = "flex";
    container.style.flexDirection = "column";
    container.style.gap = "10px";
    container.style.alignItems = "center";
    const available = getAvailableNodes(state.mapRows, state.currentNode);
    for (let r = state.mapRows.length - 1; r >= 0; r--) {
      const row = state.mapRows[r];
      const rowDiv = document.createElement("div");
      rowDiv.className = "map-row";
      const label = document.createElement("div");
      label.className = "floor-label";
      label.textContent = `Floor ${r + 1}`;
      rowDiv.appendChild(label);
      for (const node of row) {
        const n = document.createElement("div");
        n.className = `mapNode ${node.type} ${state.currentNode === node ? "current" : ""} ${available.includes(node) ? "available" : ""} ${state.currentNode !== node && node.visited ? "visited" : ""}`;
        n.innerHTML = `<div>${node.label}</div><div style="font-size:9px">${node.floor + 1}</div>`;
        if (available.includes(node)) n.onclick = () => selectNode(node);
        rowDiv.appendChild(n);
      }
      container.appendChild(rowDiv);
    }
    div.appendChild(container);
    if (state.player) {
      const hud = document.createElement("div");
      hud.className = "hud";
      hud.style.marginTop = "20px";
      hud.innerHTML = `
        <div class="hud-stat stat-hp">HP: ${state.player.hp}/${state.player.maxHp}</div>
        <div class="hud-stat stat-gold">Gold: ${state.player.gold}</div>
        <div class="hud-stat">Deck: ${state.player.deck.length}</div>
        <div class="hud-stat">Relics: ${state.player.relics.length}</div>
        <div class="hud-stat">Potions: ${state.player.potions.length}/3</div>
      `;
      div.appendChild(hud);
    }
    screens.appendChild(div);
  };
}

// ── main ──
initState();
patchBurnCards();
patchEnemyIntents();
patchGameLogic();
patchPowersAndRewards();
patchBossRelicsAndMisc();
patchEventFlow();
patchMapVisited();
patchCurseRemoval();
patchDexTonic();
patchAncientLibrary();
patchEnemyBlock();
patchSelectCard();
patchRewardDisplay();
patchBossRewardGold();
patchMapRender();
renderClassSelect();
</script>
</body>
</html>
<!-- agent-meta {"model":"moonshotai/kimi-k2.7-code","provider":"openrouter","persona":"composer","sessionId":"71356d15-e3d4-473a-917a-04bbfb8e73ea","tokensIn":6274152,"tokensOut":75908,"tokensTotal":6350060,"turns":90,"toolCalls":91,"failedToolCalls":1,"timestamp":"2026-07-16T05:12:07.554Z"} -->