← Shadows results

Shadows

Qwen 3.8 27B GGUF · typescript

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 109,478 bytes · SHA-256 4f12b6377bb7
<!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 — Roguelike Deckbuilder</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700;900&amp;display=swap">
<style>
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/data.ts":{"./types":"src/types.ts"},"src/state.ts":{"./types":"src/types.ts","./data":"src/data.ts","./utils":"src/utils.ts"},"src/engine.ts":{"./types":"src/types.ts","./data":"src/data.ts","./state":"src/state.ts","./fx":"src/fx.ts","./utils":"src/utils.ts"},"src/events.ts":{"./types":"src/types.ts","./state":"src/state.ts","./data":"src/data.ts","./utils":"src/utils.ts"},"src/ui.ts":{"./types":"src/types.ts","./data":"src/data.ts","./state":"src/state.ts","./audio":"src/audio.ts","./utils":"src/utils.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
// Entry point. Runs after the DOM is parsed and after any classic
// <script> imports, so document queries and CDN globals are available.
// Import your other modules from here.
Object.defineProperty(exports, "__esModule", { value: true });
exports.start = start;
function start() {
    // Replace this with the real startup sequence.
}
start();
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/utils.ts ──
__mods["src/utils.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.esc = void 0;
exports.uid = uid;
exports.rnd = rnd;
exports.rint = rint;
exports.pick = pick;
exports.shuffle = shuffle;
exports.chance = chance;
exports.clone = clone;
exports.clamp = clamp;
exports.plural = plural;
let uidCounter = 0;
function uid() {
    uidCounter += 1;
    return `u${uidCounter}_${Math.floor(Math.random() * 1e6)}`;
}
function rnd(n) { return Math.floor(Math.random() * n); }
function rint(min, max) { return min + rnd(max - min + 1); }
function pick(arr) { return arr[rnd(arr.length)]; }
function shuffle(arr) {
    const a = arr.slice();
    for (let i = a.length - 1; i > 0; i--) {
        const j = rnd(i + 1);
        const t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    return a;
}
function chance(p) { return Math.random() < p; }
function clone(o) {
    return JSON.parse(JSON.stringify(o));
}
function clamp(v, lo, hi) {
    return Math.max(lo, Math.min(hi, v));
}
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
exports.esc = esc;
function plural(n, s) { return n === 1 ? s : s + 's'; }
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sfx = void 0;
exports.setMuted = setMuted;
exports.isMuted = isMuted;
// Tiny WebAudio SFX synthesizer — no external assets.
let ctx = null;
let master = null;
let muted = false;
function ensure() {
    try {
        if (!ctx) {
            const AC = window.AudioContext || window.webkitAudioContext;
            ctx = new AC();
            master = ctx.createGain();
            master.gain.value = 0.35;
            master.connect(ctx.destination);
        }
        if (ctx.state === 'suspended')
            void ctx.resume();
        return ctx;
    }
    catch {
        return null;
    }
}
function setMuted(m) { muted = m; }
function isMuted() { return muted; }
function blip(freq, dur, type, vol, slideTo, delay = 0) {
    if (muted)
        return;
    const c = ensure();
    if (!c || !master)
        return;
    const t0 = c.currentTime + delay;
    const o = c.createOscillator();
    const g = c.createGain();
    o.type = type;
    o.frequency.setValueAtTime(freq, t0);
    if (slideTo !== undefined)
        o.frequency.exponentialRampToValueAtTime(Math.max(20, slideTo), t0 + dur);
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(vol, t0 + 0.012);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    o.connect(g);
    g.connect(master);
    o.start(t0);
    o.stop(t0 + dur + 0.05);
}
function noise(dur, vol, freq = 800, delay = 0) {
    if (muted)
        return;
    const c = ensure();
    if (!c || !master)
        return;
    const t0 = c.currentTime + delay;
    const len = Math.max(1, Math.floor(c.sampleRate * dur));
    const buf = c.createBuffer(1, len, c.sampleRate);
    const d = buf.getChannelData(0);
    for (let i = 0; i < len; i++)
        d[i] = (Math.random() * 2 - 1) * (1 - i / len);
    const src = c.createBufferSource();
    src.buffer = buf;
    const f = c.createBiquadFilter();
    f.type = 'lowpass';
    f.frequency.value = freq;
    const g = c.createGain();
    g.gain.setValueAtTime(vol, t0);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    src.connect(f);
    f.connect(g);
    g.connect(master);
    src.start(t0);
}
exports.Sfx = {
    click: () => blip(660, 0.06, 'square', 0.25),
    hover: () => blip(880, 0.03, 'sine', 0.1),
    draw: () => { blip(320, 0.05, 'triangle', 0.2); blip(420, 0.05, 'triangle', 0.15, undefined, 0.05); },
    play: () => { blip(220, 0.08, 'sawtooth', 0.2, 330); noise(0.08, 0.15, 1200); },
    hit: () => { noise(0.14, 0.5, 500); blip(110, 0.12, 'square', 0.3, 55); },
    blocked: () => { blip(500, 0.06, 'square', 0.3, 250); noise(0.06, 0.2, 2500); },
    heal: () => { blip(520, 0.09, 'sine', 0.25); blip(780, 0.12, 'sine', 0.2, undefined, 0.07); },
    fire: () => { noise(0.3, 0.35, 900); blip(180, 0.25, 'sawtooth', 0.15, 60); },
    poison: () => blip(200, 0.15, 'sine', 0.2, 120),
    buff: () => { blip(440, 0.07, 'triangle', 0.22); blip(660, 0.09, 'triangle', 0.22, undefined, 0.06); },
    debuff: () => { blip(300, 0.1, 'sawtooth', 0.2, 150); },
    turn: () => { blip(240, 0.1, 'triangle', 0.2, 180); blip(360, 0.1, 'triangle', 0.18, undefined, 0.09); },
    death: () => { blip(160, 0.5, 'sawtooth', 0.3, 40); noise(0.4, 0.3, 300); },
    win: () => { [392, 494, 587, 784].forEach((f, i) => blip(f, 0.22, 'triangle', 0.25, undefined, i * 0.11)); },
    lose: () => { [300, 240, 180, 120].forEach((f, i) => blip(f, 0.25, 'sawtooth', 0.22, f * 0.7, i * 0.14)); },
    gold: () => { blip(990, 0.06, 'square', 0.2); blip(1320, 0.08, 'square', 0.18, undefined, 0.05); },
    error: () => blip(140, 0.12, 'square', 0.2, 90),
    summon: () => { blip(90, 0.4, 'sawtooth', 0.25, 160); noise(0.3, 0.2, 400); },
    exhaust: () => noise(0.2, 0.2, 1500),
};
};

// ── module: src/data.ts ──
__mods["src/data.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ACT_NAMES = exports.ENEMIES = exports.POTIONS = exports.ALL_RELICS = exports.BOSS_RELICS = exports.RELICS = exports.ALL_CLASS_POOLS = exports.IMP_CLAW_UP = exports.IMP_CLAW = exports.CURSE_DEFS = exports.COLORLESS_POOL = exports.BASIC_POOL = exports.HELL_POOL = exports.SHADOW_POOL = exports.BLOOD_POOL = void 0;
exports.getDef = getDef;
exports.classOfCard = classOfCard;
exports.isUnplayable = isUnplayable;
exports.cardSpec = cardSpec;
exports.getRelic = getRelic;
exports.getPotion = getPotion;
exports.getEnemy = getEnemy;
exports.buildMap = buildMap;
// ---------------------------------------------------------------- CLASS CARDS
const c = (d) => d;
exports.BLOOD_POOL = [
    c({ id: 'sanguine_strike', name: 'Sanguine Strike', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 8 damage. Heal 2 HP.', n: { dmg: 8, heal: 2 }, nu: { dmg: 10, heal: 3 } }),
    c({ id: 'hemorrhage', name: 'Hemorrhage', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 5 damage. Apply 3 Bleed.', n: { dmg: 5, bleed: 3 }, nu: { dmg: 6, bleed: 4 } }),
    c({ id: 'bloodletting', name: 'Bloodletting', type: 'Attack', rarity: 'Uncommon', cost: 1, target: 'enemy', desc: 'Lose 3 HP. Deal 12 damage.', n: { dmg: 12, costHp: 3 }, nu: { dmg: 15, costHp: 2 } }),
    c({ id: 'crimson_eruption', name: 'Crimson Eruption', type: 'Attack', rarity: 'Rare', cost: 2, target: 'none', all: true, desc: 'Deal 8 damage to ALL enemies. Lose 2 HP.', n: { dmg: 8, costHp: 2 }, nu: { dmg: 10, costHp: 2 } }),
    c({ id: 'crimson_shield', name: 'Crimson Shield', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 6 Block. Lose 2 HP.', n: { block: 6, costHp: 2 }, nu: { block: 8, costHp: 2 } }),
    c({ id: 'blood_ward', name: 'Blood Ward', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 3 Block. Gain 1 Regeneration.', n: { block: 3, reg: 1 }, nu: { block: 4, reg: 2 } }),
    c({ id: 'sacrificial_barrier', name: 'Sacrificial Barrier', type: 'Skill', rarity: 'Uncommon', cost: 2, target: 'none', desc: 'Gain 10 Block. Lose 5 HP.', n: { block: 10, costHp: 5 }, nu: { block: 13, costHp: 5 } }),
    c({ id: 'vital_surge', name: 'Vital Surge', type: 'Skill', rarity: 'Rare', cost: 'X', target: 'none', exhaust: true, blockOnMissingHp: true, missingHpBonus: 0, desc: 'Gain Block equal to your missing HP. Exhaust.', nu: { block: 3, desc: 'Gain Block equal to your missing HP plus 3. Exhaust.' } }),
    c({ id: 'life_tap', name: 'Life Tap', type: 'Skill', rarity: 'Common', cost: 0, target: 'none', desc: 'Draw 2 cards. Lose 3 HP.', n: { draw: 2, costHp: 3 }, nu: { draw: 3, costHp: 3 }, drawOnPlay: 2 }),
    c({ id: 'rend_flesh', name: 'Rend Flesh', type: 'Skill', rarity: 'Uncommon', cost: 1, target: 'none', all: true, desc: 'Apply 5 Bleed to ALL enemies. Lose 2 HP.', n: { bleed: 5, costHp: 2 }, nu: { bleed: 7, costHp: 2 }, bleedAll: 5 }),
    c({ id: 'soul_siphon', name: 'Soul Siphon', type: 'Power', rarity: 'Uncommon', cost: 2, target: 'none', desc: 'At the start of your turn, gain 1 Energy and lose 1 HP.', power: { kind: 'soulSiphon' }, nu: { cost: 1, desc: 'At the start of your turn, gain 1 Energy and lose 1 HP.' } }),
    c({ id: 'blood_pact', name: 'Blood Pact', type: 'Power', rarity: 'Rare', cost: 1, target: 'none', desc: 'Whenever you lose HP from a card, gain 1 Strength.', power: { kind: 'bloodPact' }, nu: { str: 2, desc: 'Whenever you lose HP from a card, gain 2 Strength.' } }),
];
exports.SHADOW_POOL = [
    c({ id: 'shadow_bolt', name: 'Shadow Bolt', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 6 damage. Apply 1 Weak.', n: { dmg: 6, weak: 1 }, nu: { dmg: 8, weak: 2 } }),
    c({ id: 'grasp_of_despair', name: 'Grasp of Despair', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 4 damage. Apply 1 Vulnerable.', n: { dmg: 4, vuln: 1 }, nu: { dmg: 5, vuln: 2 } }),
    c({ id: 'nightmare_lash', name: 'Nightmare Lash', type: 'Attack', rarity: 'Uncommon', cost: 2, target: 'enemy', desc: 'Deal 7 damage. If the enemy has Weak, deal 7 more damage.', n: { dmg: 7 }, nu: { dmg: 9 }, ifWeakExtra: 7, ifWeakExtraUp: 9 }),
    c({ id: 'eternal_darkness', name: 'Eternal Darkness', type: 'Attack', rarity: 'Rare', cost: 2, target: 'none', random: true, desc: 'Deal 3 damage to a random enemy 3 times.', n: { dmg: 3 }, nu: { dmg: 4 }, randomHit: { hits: 3 } }),
    c({ id: 'cloak_of_shadows', name: 'Cloak of Shadows', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 5 Block. Draw 1 card.', n: { block: 5, draw: 1 }, nu: { block: 7, draw: 1 }, drawOnPlay: 1 }),
    c({ id: 'haunting_mist', name: 'Haunting Mist', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', all: true, desc: 'Gain 7 Block. Apply 1 Weak to ALL enemies.', n: { block: 7, weak: 1 }, nu: { block: 9, weak: 1 }, weakAll: 1 }),
    c({ id: 'phantom_armor', name: 'Phantom Armor', type: 'Skill', rarity: 'Uncommon', cost: 1, target: 'none', desc: 'Gain 8 Block. Next turn, gain 2 Dexterity.', n: { block: 8 }, nu: { block: 10 }, dexNextTurn: 2, dexNextTurnUp: 3 }),
    c({ id: 'whispering_shroud', name: 'Whispering Shroud', type: 'Skill', rarity: 'Rare', cost: 1, target: 'none', desc: 'Gain 4 Block. Next turn, gain 4 Block.', n: { block: 4 }, nu: { block: 5 }, blockNextTurn: 4, blockNextTurnUp: 5 }),
    c({ id: 'shadowform', name: 'Shadowform', type: 'Skill', rarity: 'Uncommon', cost: 2, target: 'none', exhaust: true, desc: 'Gain 2 Intangible. Exhaust.', n: { intang: 2 }, nu: { intang: 3, desc: 'Gain 3 Intangible. Exhaust.' } }),
    c({ id: 'curse_of_misfortune', name: 'Curse of Misfortune', type: 'Skill', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Enemy loses 3 Strength this turn. Apply 1 Vulnerable.', n: { str: -3, vuln: 1 }, nu: { str: -4, vuln: 2 } }),
    c({ id: 'dark_ritual', name: 'Dark Ritual', type: 'Skill', rarity: 'Uncommon', cost: 0, target: 'none', desc: 'Gain 2 Energy. Shuffle a Dazed into your draw pile.', n: { energy: 2 }, nu: { desc: 'Gain 2 Energy. Add a Dazed to your discard pile.' }, shuffleDazed: true, shuffleDazedDiscard: true }),
    c({ id: 'umbral_mastery', name: 'Umbral Mastery', type: 'Power', rarity: 'Rare', cost: 2, target: 'none', desc: 'At the start of your turn, apply 1 Weak to ALL enemies.', power: { kind: 'umbral' }, nu: { weak: 2, desc: 'At the start of your turn, apply 2 Weak to ALL enemies.' } }),
];
exports.HELL_POOL = [
    c({ id: 'firebolt', name: 'Firebolt', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 7 damage. Apply 2 Burn.', n: { dmg: 7, burn: 2 }, nu: { dmg: 9, burn: 3 } }),
    c({ id: 'hellfire_blast', name: 'Hellfire Blast', type: 'Attack', rarity: 'Uncommon', cost: 2, target: 'none', all: true, desc: 'Deal 9 damage to ALL enemies.', n: { dmg: 9 }, nu: { dmg: 12 } }),
    c({ id: 'searing_strike', name: 'Searing Strike', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 6 damage. Gain 1 Energy next turn.', n: { dmg: 6 }, nu: { dmg: 8 }, energyNextTurn: 1 }),
    c({ id: 'immolate', name: 'Immolate', type: 'Attack', rarity: 'Rare', cost: 2, target: 'enemy', exhaust: true, desc: 'Deal 12 damage. Exhaust.', n: { dmg: 12 }, nu: { dmg: 16, desc: 'Deal 16 damage. Exhaust.' } }),
    c({ id: 'obsidian_skin', name: 'Obsidian Skin', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 7 Block.', n: { block: 7 }, nu: { block: 10 } }),
    c({ id: 'flame_ward', name: 'Flame Ward', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 5 Block. Apply 2 Burn to a random enemy.', n: { block: 5, burn: 2 }, nu: { block: 6, burn: 3 } }),
    c({ id: 'demonic_resilience', name: 'Demonic Resilience', type: 'Skill', rarity: 'Uncommon', cost: 1, target: 'none', desc: 'Gain 4 Block. Heal 2 HP.', n: { block: 4, heal: 2 }, nu: { block: 5, heal: 3 } }),
    c({ id: 'brimstone_shield', name: 'Brimstone Shield', type: 'Skill', rarity: 'Rare', cost: 1, target: 'none', desc: 'Gain 3 Block. Gain 2 Strength this turn.', n: { block: 3, str: 2 }, nu: { block: 4, str: 3 }, strThisTurn: 2 }),
    c({ id: 'summon_imp', name: 'Summon Imp', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', exhaust: true, desc: 'Add an Imp Claw (0-cost Attack, 4 damage, Exhaust) to your hand. Exhaust.', addImp: true, nu: { desc: 'Add an Imp Claw+ (6 damage) to your hand. Exhaust.' } }),
    c({ id: 'infernal_pact', name: 'Infernal Pact', type: 'Skill', rarity: 'Uncommon', cost: 0, target: 'none', desc: 'Lose 2 HP. Gain 2 Energy. Add an Imp Claw to your hand.', n: { energy: 2, costHp: 2 }, nu: { costHp: 1 }, addImp: true, energyOnPlay: 2 }),
    c({ id: 'hellfire_aura', name: 'Hellfire Aura', type: 'Power', rarity: 'Rare', cost: 2, target: 'none', desc: 'At the start of your turn, deal 3 damage to ALL enemies.', power: { kind: 'hellfire' }, nu: { dmg: 5, desc: 'At the start of your turn, deal 5 damage to ALL enemies.' } }),
    c({ id: 'soul_burn', name: 'Soul Burn', type: 'Skill', rarity: 'Uncommon', cost: 1, target: 'enemy', desc: 'Apply 8 Burn to an enemy. Draw 1 card.', n: { burn: 8, draw: 1 }, nu: { burn: 10, draw: 1 }, drawOnPlay: 1 }),
];
exports.BASIC_POOL = [
    c({ id: 'strike', name: 'Strike', type: 'Attack', rarity: 'Basic', cost: 1, target: 'enemy', desc: 'Deal 6 damage.', n: { dmg: 6 }, nu: { dmg: 8 } }),
    c({ id: 'defend', name: 'Defend', type: 'Skill', rarity: 'Basic', cost: 1, target: 'none', desc: 'Gain 5 Block.', n: { block: 5 }, nu: { block: 8 } }),
];
exports.COLORLESS_POOL = [
    c({ id: 'flash_of_insight', name: 'Flash of Insight', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Draw 2 cards.', n: { draw: 2 }, nu: { draw: 3 }, drawOnPlay: 2 }),
    c({ id: 'iron_will', name: 'Iron Will', type: 'Skill', rarity: 'Common', cost: 1, target: 'none', desc: 'Gain 8 Block.', n: { block: 8 }, nu: { block: 11 } }),
    c({ id: 'quick_slash', name: 'Quick Slash', type: 'Attack', rarity: 'Common', cost: 1, target: 'enemy', desc: 'Deal 7 damage.', n: { dmg: 7 }, nu: { dmg: 9 } }),
    c({ id: 'potent_brew', name: 'Potent Brew', type: 'Skill', rarity: 'Uncommon', cost: 0, target: 'none', exhaust: true, desc: 'Gain 1 Energy. Exhaust.', n: { energy: 1 }, nu: { energy: 2, desc: 'Gain 2 Energy. Exhaust.' }, energyOnPlay: 1 }),
];
exports.CURSE_DEFS = [
    c({ id: 'wound', name: 'Wound', type: 'Curse', rarity: 'Curse', cost: -1, target: 'none', desc: 'Unplayable. Ethereal. Exhaust.', ethereal: true, exhaust: true }),
    c({ id: 'doubt', name: 'Doubt', type: 'Curse', rarity: 'Curse', cost: -1, target: 'none', desc: 'Unplayable. At the end of your turn, lose 1 HP.' }),
    c({ id: 'dazed', name: 'Dazed', type: 'Status', rarity: 'Status', cost: -1, target: 'none', desc: 'Unplayable. Ethereal. Exhaust.', ethereal: true, exhaust: true }),
];
exports.IMP_CLAW = c({ id: 'imp_claw', name: 'Imp Claw', type: 'Attack', rarity: 'Token', cost: 0, target: 'enemy', exhaust: true, desc: 'Deal 4 damage. Exhaust.', n: { dmg: 4 } });
exports.IMP_CLAW_UP = c({ id: 'imp_claw_up', name: 'Imp Claw+', type: 'Attack', rarity: 'Token', cost: 0, target: 'enemy', exhaust: true, desc: 'Deal 6 damage. Exhaust.', n: { dmg: 6 } });
exports.ALL_CLASS_POOLS = {
    blood: exports.BLOOD_POOL, shadow: exports.SHADOW_POOL, hell: exports.HELL_POOL,
};
function getDef(defId) {
    if (defId === 'imp_claw')
        return exports.IMP_CLAW;
    if (defId === 'imp_claw_up')
        return exports.IMP_CLAW_UP;
    for (const pool of [exports.BLOOD_POOL, exports.SHADOW_POOL, exports.HELL_POOL, exports.BASIC_POOL, exports.COLORLESS_POOL, exports.CURSE_DEFS]) {
        const f = pool.find(x => x.id === defId);
        if (f)
            return f;
    }
    throw new Error('unknown card ' + defId);
}
function classOfCard(def) {
    if (exports.BLOOD_POOL.includes(def))
        return 'blood';
    if (exports.SHADOW_POOL.includes(def))
        return 'shadow';
    if (exports.HELL_POOL.includes(def))
        return 'hell';
    return null;
}
function isUnplayable(def) {
    return def.type === 'Curse' || def.type === 'Status';
}
function cardSpec(def, upgraded) {
    const nu = def.nu;
    let name = def.name;
    let desc = def.desc;
    let cost = def.cost;
    const n = { ...(def.n || {}) };
    if (upgraded) {
        name = def.name + '+';
        if (nu) {
            if (nu.desc)
                desc = nu.desc;
            if (nu.cost !== undefined)
                cost = nu.cost;
            const keys = Object.keys(nu);
            for (const k of keys) {
                if (n[k] !== undefined && nu[k] !== undefined)
                    n[k] = nu[k];
            }
        }
    }
    return { def, name, desc, cost, n };
}
// ---------------------------------------------------------------- RELICS
exports.RELICS = [
    { id: 'bloodstone', name: 'Bloodstone', desc: 'Whenever you lose HP from a card, heal 1 HP.', rarity: 'Common' },
    { id: 'demon_horns', name: 'Demon Horns', desc: 'At the start of each combat, add an Imp Claw to your hand (once per combat).', rarity: 'Common', cls: 'hell' },
    { id: 'shadow_cloak', name: 'Shadow Cloak', desc: 'At the start of each combat, gain 1 Intangible.', rarity: 'Rare' },
    { id: 'cursed_amulet', name: 'Cursed Amulet', desc: 'At the start of each combat, apply 1 Weak to ALL enemies.', rarity: 'Common' },
    { id: 'tome', name: 'Tome of Dark Rituals', desc: 'Whenever you play a Power card, draw 1 card.', rarity: 'Uncommon' },
    { id: 'bleeding_edge', name: 'Bleeding Edge', desc: 'At the start of your turn, apply 1 Bleed to a random enemy.', rarity: 'Common' },
    { id: 'soul_ember', name: 'Soul Ember', desc: 'Whenever you apply Burn to an enemy, deal 2 additional damage.', rarity: 'Rare' },
    { id: 'hourglass', name: 'Eternal Hourglass', desc: 'At the start of your turn, if you have no cards in hand, gain 1 Energy.', rarity: 'Uncommon' },
    { id: 'sacrificial_dagger', name: 'Sacrificial Dagger', desc: 'Your first Attack that costs HP each combat costs 1 less HP.', rarity: 'Common', cls: 'blood' },
    { id: 'vial_of_shadows', name: 'Vial of Shadows', desc: 'When obtained, remove a card from your deck. It does nothing further.', rarity: 'Rare', unique: true },
    { id: 'ashen_armor', name: 'Ashen Armor', desc: 'At the end of combat, heal 6 HP.', rarity: 'Common' },
    { id: 'greedy_imp', name: 'Greedy Imp', desc: 'Enemies drop 10 extra Gold.', rarity: 'Common' },
];
exports.BOSS_RELICS = [
    { id: 'cursed_crown', name: 'Cursed Crown', desc: 'Gain 1 Energy at the start of each turn. Curses can no longer be removed.', rarity: 'Rare', boss: true, unique: true },
    { id: 'ectoplasm', name: 'Ectoplasm', desc: 'Gain 1 Energy at the start of each turn. Lose all Gold and gain no more Gold this run.', rarity: 'Rare', boss: true, unique: true },
    { id: 'philosopher', name: "Philosopher's Stone", desc: 'Gain 1 Energy at the start of each turn. All enemies start combat with 1 Strength.', rarity: 'Rare', boss: true, unique: true },
    { id: 'black_blood', name: 'Black Blood', desc: 'At start of combat, lose 10% max HP, but whenever you play an Attack, heal 1 HP.', rarity: 'Rare', boss: true, unique: true },
    { id: 'shadow_amulet', name: 'Shadow Amulet', desc: 'Start each turn with 1 Intangible. Reduce max HP by 15.', rarity: 'Rare', boss: true, unique: true },
];
exports.ALL_RELICS = [...exports.RELICS, ...exports.BOSS_RELICS];
function getRelic(id) {
    const r = exports.ALL_RELICS.find(x => x.id === id);
    if (!r)
        throw new Error('unknown relic ' + id);
    return r;
}
// ---------------------------------------------------------------- POTIONS
exports.POTIONS = [
    { id: 'blood_vial', name: 'Blood Vial', desc: 'Heal 15 HP.' },
    { id: 'firebomb', name: 'Firebomb', desc: 'Deal 10 damage to ALL enemies.' },
    { id: 'dex_tonic', name: 'Dex Tonic', desc: 'Gain 3 Dexterity for the rest of combat (1 turn).' },
    { id: 'energy_elixir', name: 'Energy Elixir', desc: 'Gain 2 Energy this turn.' },
    { id: 'cursed_brew', name: 'Cursed Brew', desc: 'Apply 3 Vulnerable to ALL enemies.' },
    { id: 'bleeding_flask', name: 'Bleeding Flask', desc: 'Apply 6 Bleed to an enemy.' },
    { id: 'imp_bottle', name: 'Imp-in-a-Bottle', desc: 'Add an Imp Claw+ to your hand.' },
    { id: 'smoke_bomb', name: 'Smoke Bomb', desc: 'Escape from a non-boss combat, no rewards.' },
];
function getPotion(id) {
    const p = exports.POTIONS.find(x => x.id === id);
    if (!p)
        throw new Error('unknown potion ' + id);
    return p;
}
// ---------------------------------------------------------------- ENEMIES
const E = (d) => d;
const atk = (text, dmg, extra) => ({ kind: 'attack', text, dmg, ...extra });
const multi = (text, dmg, hits, extra) => ({ kind: 'multi', text, dmg, hits, ...extra });
exports.ENEMIES = {};
function reg(def) { exports.ENEMIES[def.id] = def; }
reg(E({ id: 'skeleton', name: 'Skeleton Warrior', hp: 22, art: 'skeleton', pattern: [atk('Attack 6', 6), atk('Attack 6', 6), { kind: 'defend', text: 'Defend 8', block: 8 }] }));
reg(E({ id: 'zombie', name: 'Zombie Brute', hp: 28, art: 'zombie', pattern: [atk('Attack 8', 8), { kind: 'debuff', text: 'Apply 2 Weak', weak: 2 }, atk('Attack 8', 8)] }));
reg(E({ id: 'cultist', name: 'Dark Cultist', hp: 20, art: 'cultist', pattern: [{ kind: 'buff', text: 'Gain 3 Strength', str: 3 }, atk('Attack 5', 5), atk('Attack 5', 5)] }));
reg(E({ id: 'rats', name: 'Rat Swarm', hp: 15, art: 'rats', pattern: [multi('2×3', 3, 2), multi('2×3', 3, 2)] }));
reg(E({ id: 'guardian', name: 'Tomb Guardian', hp: 42, art: 'guardian', pattern: [{ kind: 'mixed', text: 'Shield Bash: 6 dmg, +8 Block', dmg: 6, block: 8 }, multi('Flurry: 3×4', 4, 3), atk('Heavy Slam 12', 12)] }));
reg(E({ id: 'witch', name: 'Blood Witch', hp: 36, art: 'witch', pattern: [{ kind: 'drain', text: 'Drain Life: 5 dmg, heal 4', dmg: 5, heal: 4 }, { kind: 'debuff', text: 'Curse: 1 Vuln, 1 Weak', vuln: 1, weak: 1 }, { kind: 'drain', text: 'Blood Ritual: 7 dmg, heal 4', dmg: 7, heal: 4 }] }));
reg(E({ id: 'lich', name: 'Lich King', hp: 65, art: 'lich', phase2: 'lich2', pattern: [{ kind: 'summon', text: 'Summon Skeleton Minion', summon: 'minion_skeleton' }, atk('Attack 8', 8), { kind: 'defend', text: 'Defend 12', block: 12 }] }));
reg(E({ id: 'lich2', name: 'Lich King (Enraged)', hp: 65, art: 'lich', pattern: [atk('Attack 10', 10), atk('Attack 10', 10), { kind: 'defend', text: 'Defend 15', block: 15 }] }));
reg(E({ id: 'minion_skeleton', name: 'Skeleton Minion', hp: 15, art: 'minion', pattern: [atk('Attack 5', 5)] }));
reg(E({ id: 'shadow_thief', name: 'Shadow Thief', hp: 30, art: 'thief', pattern: [{ kind: 'mixed', text: 'Backstab: 7 dmg, +4 Block', dmg: 7, block: 4 }, atk('Attack 6', 6), { kind: 'debuff', text: 'Sap: 1 Vuln, 1 Weak', vuln: 1, weak: 1 }] }));
// Act 2
reg(E({ id: 'wraith', name: 'Wraith', hp: 30, art: 'wraith', scale: { hp: 1, dmg: 0.25 }, pattern: [atk('Attack 7, 1 Weak', 7, { weak: 1 }), atk('Attack 7', 7), { kind: 'defend', text: 'Defend 10', block: 10 }] }));
reg(E({ id: 'beast', name: 'Corrupted Beast', hp: 38, art: 'beast', scale: { hp: 1, dmg: 0.25 }, pattern: [atk('Attack 12', 12), { kind: 'vulnSelf', text: 'Expose: +5 Block, 1 Vuln on self', block: 5, vulnSelf: 1 }, atk('Attack 12', 12)] }));
reg(E({ id: 'horror', name: 'Thorned Horror', hp: 25, art: 'horror', scale: { hp: 1, dmg: 0.25 }, pattern: [multi('3×4', 4, 3), { kind: 'debuff', text: 'Apply 3 Bleed', bleed: 3 }, atk('Attack 8', 8)] }));
reg(E({ id: 'shaman', name: 'Witch Doctor', hp: 22, art: 'shaman', scale: { hp: 1, dmg: 0.25 }, pattern: [{ kind: 'buff', text: 'Ritual: +4 Block, +2 Regen', block: 4, regen: 2 }, { kind: 'attack', text: 'Spit: 6 dmg, 2 Poison', dmg: 6, poison: 2 }, { kind: 'attack', text: 'Spit: 6 dmg, 2 Poison', dmg: 6, poison: 2 }] }));
reg(E({ id: 'treant_prot', name: 'Treant Protector', hp: 50, art: 'treant', scale: { hp: 1, dmg: 0.25 }, pattern: [{ kind: 'mixed', text: 'Branch Smash: 9 dmg, +6 Block', dmg: 9, block: 6 }, { kind: 'debuff', text: 'Wither: 2 Vulnerable', vuln: 2 }, multi('Rend: 2×10', 10, 2)] }));
reg(E({ id: 'plague', name: 'Plague Shaman', hp: 42, art: 'plague', scale: { hp: 1, dmg: 0.25 }, pattern: [{ kind: 'debuff', text: 'Plague Cloud: 4 Poison', poison: 4 }, { kind: 'summon', text: 'Summon Plague Rat', summon: 'plague_rat' }, atk('Attack 8', 8)] }));
reg(E({ id: 'plague_rat', name: 'Plague Rat', hp: 18, art: 'rats', pattern: [atk('Attack 5', 5)] }));
reg(E({ id: 'eldritch', name: 'Eldritch Treant', hp: 75, art: 'treant_boss', phase2: 'eldritch2', pattern: [atk('Attack 10, 2 Weak', 10, { weak: 2 }), { kind: 'defend', text: 'Defend 15, +2 Regen', block: 15, regen: 2 }, multi('2×8', 8, 2)] }));
reg(E({ id: 'eldritch2', name: 'Eldritch Treant (Awakened)', hp: 75, art: 'treant_boss', pattern: [atk('Attack 14, 1 Vuln', 14, { vuln: 1 }), { kind: 'defend', text: 'Defend 20, heal 5', block: 20, heal: 5 }, atk('Attack 14, 1 Vuln', 14, { vuln: 1 })] }));
// Act 3
reg(E({ id: 'hound', name: 'Hellhound', hp: 26, art: 'hound', scale: { hp: 1, dmg: 0.5 }, pattern: [atk('Attack 8, 2 Burn', 8, { burn: 2 }), atk('Attack 8', 8), { kind: 'defend', text: 'Defend 9', block: 9 }] }));
reg(E({ id: 'sdemon', name: 'Shadow Demon', hp: 30, art: 'sdemon', scale: { hp: 1, dmg: 0.5 }, pattern: [atk('Attack 7, 1 Weak', 7, { weak: 1 }), atk('Attack 7', 7), { kind: 'buff', text: 'Gain 1 Intangible', intang: 1 }] }));
reg(E({ id: 'imp3', name: 'Infernal Imp', hp: 18, art: 'imp', scale: { hp: 1, dmg: 0.5 }, pattern: [atk('Attack 5', 5), atk('Attack 5', 5), { kind: 'summon', text: 'Summon Infernal Imp', summon: 'imp3' }] }));
reg(E({ id: 'acultist', name: 'Abyssal Cultist', hp: 24, art: 'cultist3', scale: { hp: 1, dmg: 0.5 }, pattern: [{ kind: 'buff', text: 'Empower: +2 Str to ALL enemies', str: 2 }, atk('Attack 7', 7), atk('Attack 7', 7)] }));
reg(E({ id: 'flamespawn', name: 'Flamespawn', hp: 48, art: 'flamespawn', scale: { hp: 1, dmg: 0.5 }, pattern: [atk('Attack 9, 3 Burn', 9, { burn: 3 }), { kind: 'defend', text: 'Defend 10, +2 Str', block: 10, str: 2 }, multi('2×8', 8, 2)] }));
reg(E({ id: 'dread', name: 'Dread Knight', hp: 55, art: 'dread', scale: { hp: 1, dmg: 0.5 }, pattern: [atk('Attack 12', 12), { kind: 'debuff', text: 'Cleaver: 3 Bleed', bleed: 3 }, { kind: 'defend', text: 'Defend 12, heal 4', block: 12, heal: 4 }] }));
reg(E({ id: 'archdemon', name: 'Archdemon', hp: 85, art: 'archdemon', phase2: 'archdemon2', pattern: [{ kind: 'summon', text: 'Summon Hellhound', summon: 'hound' }, atk('Attack 12, 2 Burn', 12, { burn: 2 }), { kind: 'defend', text: 'Defend 15, +3 Str', block: 15, str: 3 }] }));
reg(E({ id: 'archdemon2', name: 'Archdemon (Unbound)', hp: 85, art: 'archdemon', pattern: [atk('Attack 16', 16), multi('3×7', 7, 3), atk('Attack 16', 16)] }));
function getEnemy(id) {
    const d = exports.ENEMIES[id];
    if (!d)
        throw new Error('unknown enemy ' + id);
    return d;
}
// ---------------------------------------------------------------- MAPS
// 15 rows; columns 0..4; player starts at floor 1 col 2 and moves upward.
const COLS = [
    [2], // f1 start
    [1, 3], // f2
    [0, 2, 4], // f3
    [2], // f4 event
    [1, 3], // f5 shop
    [2], // f6
    [0, 2, 4], // f7 rest
    [2], // f8
    [1, 3], // f9 elite / enemy
    [2], // f10 event
    [0, 2, 4], // f11
    [1, 3], // f12 elite / enemy
    [0, 2, 4], // f13
    [2], // f14 rest
    [2], // f15 boss
];
const FLOOR_TYPE = [
    'start', 'enemy', 'enemy', 'event', 'shop', 'enemy', 'rest', 'enemy',
    'elite', 'event', 'enemy', 'elite', 'enemy', 'rest', 'boss',
];
function buildMap(act) {
    const nodes = [];
    const edges = [];
    for (let f = 1; f <= 15; f++) {
        for (const col of COLS[f - 1]) {
            nodes.push({ id: `f${f}c${col}`, type: FLOOR_TYPE[f - 1], col, floor: f });
        }
    }
    // edges: connect adjacent floors when |colA - colB| <= 2
    for (let f = 2; f <= 15; f++) {
        for (const c1 of COLS[f - 2]) {
            for (const c2 of COLS[f - 1]) {
                if (Math.abs(c2 - c1) <= 2)
                    edges.push([`f${f - 1}c${c1}`, `f${f}c${c2}`]);
            }
        }
    }
    // On floors 9 and 12 the right node (col 3) is the safe enemy alternative
    for (const f of [9, 12]) {
        const n = nodes.find(x => x.floor === f && x.col === 3);
        if (n)
            n.type = 'enemy';
    }
    // enemy groups per act, indexed by floor
    const groups = {
        1: { 2: ['skeleton', 'zombie'], 3: ['cultist'], 6: ['rats'], 8: ['skeleton', 'skeleton', 'zombie'], 9: ['skeleton', 'rats'], 11: ['cultist', 'rats'], 12: ['zombie', 'skeleton'], 13: ['cultist', 'cultist'] },
        2: { 2: ['wraith', 'wraith'], 3: ['horror'], 6: ['beast'], 8: ['beast', 'horror'], 9: ['horror'], 11: ['wraith', 'horror'], 12: ['beast', 'shaman'], 13: ['wraith', 'wraith'] },
        3: { 2: ['hound', 'sdemon'], 3: ['acultist', 'imp3'], 6: ['sdemon', 'sdemon'], 8: ['acultist', 'acultist'], 9: ['sdemon'], 11: ['hound', 'imp3'], 12: ['hound', 'sdemon', 'acultist'], 13: ['sdemon', 'sdemon'] },
    };
    const elites = {
        1: { 9: 'guardian', 12: 'witch' },
        2: { 9: 'treant_prot', 12: 'plague' },
        3: { 9: 'flamespawn', 12: 'dread' },
    };
    const bosses = { 1: 'lich', 2: 'eldritch', 3: 'archdemon' };
    const events = { 1: ['altar', 'spring'], 2: ['peddler', 'dead'], 3: ['gambler', 'library'] };
    for (let f = 2; f <= 13; f++) {
        const floorNodes = nodes.filter(x => x.floor === f && (x.type === 'enemy' || x.type === 'elite')).sort((a, b) => a.col - b.col);
        floorNodes.forEach((n, i) => {
            if (n.type === 'enemy')
                n.group = (groups[act][f] && groups[act][f][i]) || 'skeleton';
            if (n.type === 'elite')
                n.group = elites[act][f];
        });
    }
    for (const n of nodes) {
        if (n.type === 'boss')
            n.group = bosses[act];
    }
    let evIdx = 0;
    for (const n of nodes) {
        if (n.type === 'event') {
            n.group = events[act][evIdx % events[act].length];
            evIdx++;
        }
    }
    return { nodes, edges };
}
exports.ACT_NAMES = ['', 'The Crypts', 'Blighted Woods', 'Abyssal Spire'];
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ACT_NAMES = void 0;
exports.setRunRef = setRunRef;
exports.run = run;
exports.newCharStats = newCharStats;
exports.newRun = newRun;
exports.makeEnemy = makeEnemy;
exports.newCombat = newCombat;
exports.hpLossCard = hpLossCard;
exports.rollReward = rollReward;
exports.rollCardChoices = rollCardChoices;
exports.makeShop = makeShop;
exports.rollRelicPool = rollRelicPool;
exports.addCardToDeck = addCardToDeck;
exports.removeCardFromDeck = removeCardFromDeck;
exports.upgradeCardInDeck = upgradeCardInDeck;
const data_1 = require("./data");
Object.defineProperty(exports, "ACT_NAMES", { enumerable: true, get: function () { return data_1.ACT_NAMES; } });
const utils_1 = require("./utils");
let runRef = null;
function setRunRef(r) { runRef = r; }
function run() { if (!runRef)
    throw new Error('no run'); return runRef; }
function newCharStats(name, hp) {
    return { name, maxHp: hp, hp, block: 0, str: 0, dex: 0, regen: 0, intangible: 0, weak: 0, vulnerable: 0, bleed: 0, burn: 0, poison: 0, alive: true };
}
function newRun(cls) {
    const deck = [];
    for (let i = 0; i < 5; i++)
        deck.push({ uid: (0, utils_1.uid)(), defId: 'strike', upgraded: false });
    for (let i = 0; i < 4; i++)
        deck.push({ uid: (0, utils_1.uid)(), defId: 'defend', upgraded: false });
    const sig = { blood: 'sanguine_strike', shadow: 'shadow_bolt', hell: 'firebolt' };
    deck.push({ uid: (0, utils_1.uid)(), defId: sig[cls], upgraded: false });
    return {
        s: {
            cls, hp: 70, maxHp: 70, gold: 99,
            deck, relics: [], potions: [],
            act: 1, nodeIdx: 0, curNodeId: 'f1c2', map: (0, data_1.buildMap)(1).nodes,
            nextEnergyBuff: 0, doubtInHand: false, over: false, victory: false,
        },
        pendingVial: false,
    };
}
function makeEnemy(defId, isMinion = false) {
    const def = (0, data_1.getEnemy)(defId);
    const act = runRef ? runRef.s.act : 1;
    const scale = def.scale || { hp: 1, dmg: 1 };
    const hp = def.hp + (act - 1) * Math.round(def.hp * scale.hp * 0.4);
    return {
        uid: (0, utils_1.uid)(), defId,
        stats: newCharStats(def.name, hp),
        turnIdx: 0, enraged: false, isMinion, isBoss: false, art: def.art,
    };
}
function newCombat(enemyGroup, nodeId, opts) {
    const r = run();
    const c = {
        enemies: [], draw: [], discard: [], exhaust: [], hand: [], powers: [],
        energy: 3, energyNextTurn: 0, turn: 0, blockNextTurn: 0,
        player: newCharStats('Hero', r.s.hp),
        bossRelic: opts?.bossRelic || null, elite: !!opts?.elite, nodeId,
        eventGold: opts?.eventGold || 0, strThisTurn: 0, daggerUsed: false, impClawGiven: false,
    };
    c.player.maxHp = r.s.maxHp;
    const group = enemyGroup.length ? enemyGroup : ['skeleton'];
    for (const g of group) {
        const e = makeEnemy(g);
        if (opts?.boss)
            e.isBoss = true;
        c.enemies.push(e);
    }
    c.draw = (0, utils_1.shuffle)((0, utils_1.clone)(r.s.deck).map(d => ({ ...d })));
    // start-of-combat relics
    if (r.s.relics.includes('shadow_cloak'))
        c.player.intangible += 1;
    if (r.s.relics.includes('cursed_amulet'))
        for (const e of c.enemies)
            e.stats.weak += 1;
    if (r.s.relics.includes('black_blood'))
        hpLossCard(c, Math.round(r.s.maxHp * 0.1), 'black_blood');
    if (r.s.relics.includes('shadow_amulet'))
        c.player.intangible += 1;
    if (r.s.relics.includes('philosopher'))
        for (const e of c.enemies)
            e.stats.str += 1;
    if (r.s.relics.includes('demon_horns') && r.s.cls === 'hell')
        c.hand.push({ uid: (0, utils_1.uid)(), defId: 'imp_claw', upgraded: false });
    return c;
}
// Player HP loss from a card / relic effect (bypasses Block; Intangible reduces to 1).
function hpLossCard(c, raw, src) {
    const p = c.player;
    let dmg = raw;
    if (p.intangible > 0)
        dmg = 1;
    p.hp -= dmg;
    if (src !== 'black_blood') {
        const pact = c.powers.find(cd => (0, data_1.getDef)(cd.defId).power?.kind === 'bloodPact');
        if (pact)
            p.str += (0, data_1.getDef)(pact.defId).nu?.str || 1;
        if (run().s.relics.includes('bloodstone'))
            p.hp = Math.min(p.maxHp, p.hp + 1);
    }
    return dmg;
}
function rollReward(kind, c) {
    const r = run();
    let gold = kind === 'normal' ? (0, utils_1.rint)(10, 20) : kind === 'elite' ? (0, utils_1.rint)(25, 35) : 50;
    if (r.s.relics.includes('greedy_imp'))
        gold += 10;
    if (r.s.relics.includes('ectoplasm'))
        gold = 0;
    gold += c.eventGold || 0;
    const cards = rollCardChoices(kind);
    let potion = null;
    let potionFull = false;
    if (kind === 'boss') {
        if (r.s.potions.length < 3)
            potion = (0, utils_1.pick)(data_1.POTIONS).id;
        else
            potionFull = true;
    }
    else if ((0, utils_1.chance)(kind === 'elite' ? 0.7 : 0.4)) {
        if (r.s.potions.length < 3)
            potion = (0, utils_1.pick)(data_1.POTIONS).id;
        else
            potionFull = true;
    }
    const relics = [];
    if (kind === 'elite') {
        const pool = data_1.RELICS.filter(x => !x.boss && (!x.cls || x.cls === r.s.cls) && !r.s.relics.includes(x.id));
        if (pool.length)
            relics.push((0, utils_1.pick)(pool).id);
    }
    let bossRelicChoices = null;
    if (kind === 'boss') {
        const pool = data_1.BOSS_RELICS.filter(x => !r.s.relics.includes(x.id));
        if (pool.length)
            bossRelicChoices = (0, utils_1.shuffle)(pool.map(x => x.id)).slice(0, 3);
    }
    return { gold, cards, potion, potionFull, relics, bossRelicChoices, eventGold: c.eventGold || 0 };
}
function rollCardChoices(kind) {
    const r = run();
    const pool = data_1.ALL_CLASS_POOLS[r.s.cls].slice();
    const out = [];
    const rareBoost = kind === 'elite' ? 0.2 : 0.1;
    const pickOne = () => {
        if (kind === 'boss' && !out.some(x => x.rarity === 'Rare')) {
            const rares = pool.filter(x => x.rarity === 'Rare');
            return (0, utils_1.pick)(rares.length ? rares : pool);
        }
        const roll = Math.random();
        let rarity;
        if (roll < rareBoost)
            rarity = 'Rare';
        else if (roll < rareBoost + 0.3)
            rarity = 'Uncommon';
        else
            rarity = 'Common';
        let cands = pool.filter(x => x.rarity === rarity);
        if (!cands.length)
            cands = pool;
        return (0, utils_1.pick)(cands);
    };
    for (let i = 0; i < 3; i++) {
        let card = pickOne();
        let tries = 0;
        while (out.some(x => x.id === card.id) && tries < 20) {
            card = pickOne();
            tries++;
        }
        out.push(card);
    }
    return out;
}
function makeShop() {
    const r = run();
    const pool = data_1.ALL_CLASS_POOLS[r.s.cls];
    const priceOf = (d) => d.rarity === 'Common' ? 50 : d.rarity === 'Uncommon' ? 75 : 150;
    const cards = [];
    for (let i = 0; i < 3; i++) {
        const d = (0, utils_1.pick)(pool);
        cards.push({ defId: d.id, price: priceOf(d), sold: false });
    }
    const cd = (0, utils_1.pick)(data_1.COLORLESS_POOL);
    const relicPool = data_1.RELICS.filter(x => !x.boss && (!x.cls || x.cls === r.s.cls));
    const relics = (0, utils_1.shuffle)(relicPool).slice(0, 2).map(x => ({ defId: x.id, price: 150, sold: false }));
    const potions = (0, utils_1.shuffle)(data_1.POTIONS).slice(0, 3).map(x => ({ defId: x.id, price: 50, sold: false }));
    return { cards, colorless: { defId: cd.id, price: 60, sold: false }, relics, potions, removalUsed: false };
}
function rollRelicPool() {
    const r = run();
    const pool = data_1.RELICS.filter(x => !x.boss && (!x.cls || x.cls === r.s.cls));
    return (0, utils_1.shuffle)(pool.map(x => x.id)).slice(0, 3);
}
// deck helpers
function addCardToDeck(defId) {
    run().s.deck.push({ uid: (0, utils_1.uid)(), defId, upgraded: false });
}
function removeCardFromDeck(card) {
    const d = run().s.deck;
    const i = d.findIndex(x => x.uid === card.uid);
    if (i >= 0)
        d.splice(i, 1);
}
function upgradeCardInDeck(card) {
    const d = run().s.deck;
    const i = d.findIndex(x => x.uid === card.uid);
    if (i >= 0)
        d[i].upgraded = true;
}
};

// ── module: src/fx.ts ──
__mods["src/fx.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fxQueue = void 0;
exports.pushFx = pushFx;
exports.clearFx = clearFx;
exports.drainFx = drainFx;
exports.fxQueue = [];
function pushFx(e) { exports.fxQueue.push(e); }
function clearFx() { exports.fxQueue.length = 0; }
function drainFx() {
    const out = exports.fxQueue.splice(0, exports.fxQueue.length);
    return out;
}
};

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setCombat = setCombat;
exports.getCombat = getCombat;
exports.aliveEnemies = aliveEnemies;
exports.allTargets = allTargets;
exports.calcPlayerDamage = calcPlayerDamage;
exports.damageEnemy = damageEnemy;
exports.enemyAttack = enemyAttack;
exports.gainPlayerBlock = gainPlayerBlock;
exports.healPlayer = healPlayer;
exports.playableCost = playableCost;
exports.canPlay = canPlay;
exports.playCard = playCard;
exports.startTurn = startTurn;
exports.endTurn = endTurn;
exports.currentIntent = currentIntent;
exports.usePotion = usePotion;
exports.isPlayerDead = isPlayerDead;
exports.allDefeated = allDefeated;
exports.playerStatList = playerStatList;
exports.enemyStatList = enemyStatList;
exports.cardDamagePreview = cardDamagePreview;
exports.intentText = intentText;
exports.intentKind = intentKind;
exports.intentDamage = intentDamage;
const data_1 = require("./data");
const state_1 = require("./state");
const fx_1 = require("./fx");
const utils_1 = require("./utils");
let combat;
function setCombat(c) { combat = c; }
function getCombat() { return combat; }
function aliveEnemies() { return combat.enemies.filter(e => e.stats.hp > 0); }
function allTargets() { return aliveEnemies(); }
function log(text, tone = 'info') {
    (0, fx_1.pushFx)({ kind: 'log', text, tone });
}
function float(target, text, tone = 'info') {
    (0, fx_1.pushFx)({ kind: 'float', target, text, tone });
}
function sfx(name) { (0, fx_1.pushFx)({ kind: 'sfx', name }); }
// ---------- damage ----------
function calcPlayerDamage(base, enemy) {
    const p = combat.player;
    let d = base + p.str + combat.strThisTurn;
    if (p.weak > 0)
        d = Math.floor(d * Math.pow(0.75, p.weak));
    if (d < 0)
        d = 0;
    if (enemy.stats.vulnerable > 0)
        d = Math.floor(d * 1.5);
    return d;
}
function damageEnemy(enemy, base, opts) {
    if (enemy.stats.hp <= 0)
        return 0;
    let d = calcPlayerDamage(base, enemy);
    let blocked = 0;
    if (enemy.stats.block > 0) {
        blocked = Math.min(enemy.stats.block, d);
        enemy.stats.block -= blocked;
        d -= blocked;
    }
    enemy.stats.hp -= d;
    if (blocked > 0)
        float(enemy.uid, `-${blocked} blk`, 'block');
    if (d > 0) {
        float(enemy.uid, `-${d}`, 'dmg');
        sfx('hit');
    }
    if (enemy.stats.hp <= 0) {
        enemy.stats.hp = 0;
        log(`${enemy.stats.name} is defeated!`, 'good');
        sfx('death');
    }
    void opts;
    return d;
}
// enemy attacks player (1 hit)
function enemyAttack(enemy, base, multiHits = false) {
    const p = combat.player;
    let d = base + enemy.stats.str + (enemy.tempStr || 0);
    if (d < 0)
        d = 0;
    let blocked = 0;
    if (p.block > 0) {
        blocked = Math.min(p.block, d);
        p.block -= blocked;
        d -= blocked;
    }
    if (d > 0) {
        p.hp -= d;
        float('player', `-${d}`, 'dmg');
        (0, fx_1.pushFx)({ kind: 'hitPlayer' });
        sfx('hit');
    }
    if (blocked > 0) {
        float('player', `-${blocked} blk`, 'block');
        sfx('blocked');
    }
    if (!multiHits && p.hp <= 0) {
        p.hp = 0;
        sfx('death');
    }
    return d;
}
function gainPlayerBlock(amount) {
    const amt = amount + combat.player.dex;
    combat.player.block += amt;
    float('player', `+${amt} block`, 'block');
    sfx('buff');
    return amt;
}
function healPlayer(amount, silent = false) {
    const p = combat.player;
    const amt = Math.min(amount, p.maxHp - p.hp);
    p.hp += amt;
    if (!silent) {
        float('player', `+${amt}`, 'heal');
        sfx('heal');
    }
    return amt;
}
function applyWeak(enemy, amt) {
    if (enemy.stats.hp <= 0)
        return;
    enemy.stats.weak += amt;
    float(enemy.uid, `+${amt} Weak`, 'debuff');
}
function applyVuln(enemy, amt) {
    if (enemy.stats.hp <= 0)
        return;
    enemy.stats.vulnerable += amt;
    float(enemy.uid, `+${amt} Vuln`, 'debuff');
}
function applyBleed(enemy, amt) {
    if (enemy.stats.hp <= 0)
        return;
    enemy.stats.bleed += amt;
    float(enemy.uid, `+${amt} Bleed`, 'debuff');
    if ((0, state_1.run)().s.relics.includes('soul_ember'))
        damageEnemy(enemy, 2);
}
function applyBurn(enemy, amt) {
    if (enemy.stats.hp <= 0)
        return;
    enemy.stats.burn += amt;
    float(enemy.uid, `+${amt} Burn`, 'debuff');
    if ((0, state_1.run)().s.relics.includes('soul_ember'))
        damageEnemy(enemy, 2);
}
function applyPoison(enemy, amt) {
    if (enemy.stats.hp <= 0)
        return;
    enemy.stats.poison += amt;
    float(enemy.uid, `+${amt} Poison`, 'debuff');
}
function drawCards(n) {
    const c = combat;
    for (let i = 0; i < n; i++) {
        if (c.draw.length === 0) {
            if (c.discard.length === 0)
                break;
            c.draw = (0, utils_1.shuffle)(c.discard.slice());
            c.discard = [];
            log('Draw pile reshuffled from discard.', 'info');
        }
        c.hand.push(c.draw.pop());
    }
    sfx('draw');
}
function addImpClaw(up) {
    const def = up ? data_1.IMP_CLAW_UP : data_1.IMP_CLAW;
    combat.hand.push({ uid: (0, utils_1.uid)(), defId: def.id, upgraded: false });
    log(`${def.name} added to hand.`, 'good');
}
// ---------- playing cards ----------
function playableCost(card) {
    const spec = (0, data_1.cardSpec)((0, data_1.getDef)(card.defId), card.upgraded);
    const cost = spec.cost;
    if (cost === 'X')
        return 0;
    return cost;
}
function canPlay(card) {
    const def = (0, data_1.getDef)(card.defId);
    if ((0, data_1.isUnplayable)(def))
        return false;
    if (specTargetMissing(def))
        return false;
    const cost = def.cost;
    if (cost === 'X')
        return true;
    return combat.energy >= cost;
}
function specTargetMissing(def) {
    return def.target === 'enemy' && allTargets().length === 0;
}
function playCard(card, target) {
    const c = combat;
    if (!canPlay(card)) {
        const dchk = def2(card);
        if (typeof dchk.cost === 'number' && c.energy < dchk.cost) {
            sfx('error');
            return false;
        }
        if ((0, data_1.isUnplayable)(dchk)) {
            sfx('error');
            log(`${dchk.name} is unplayable.`, 'bad');
            return false;
        }
        if (specTargetMissing(dchk)) {
            sfx('error');
            return false;
        }
        return false;
    }
    const def = def2(card);
    const spec = (0, data_1.cardSpec)(def, card.upgraded);
    const n = spec.n;
    const dmg = n.dmg ?? 0, blk = n.block ?? 0, weak = n.weak ?? 0, vuln = n.vuln ?? 0, bleed = n.bleed ?? 0, burn = n.burn ?? 0, healA = n.heal ?? 0, costHp = n.costHp ?? 0, energyG = n.energy ?? 0, intang = n.intang ?? 0, reg = n.reg ?? 0, strN = n.str ?? 0;
    const ti = c.hand.findIndex(x => x.uid === card.uid);
    if (ti < 0)
        return false;
    c.hand.splice(ti, 1);
    if (def.cost !== 'X')
        c.energy -= def.cost;
    else
        c.energy = 0;
    // HP cost (before effects; dagger relic)
    let hpCost = costHp;
    if (hpCost > 0 && !c.daggerUsed && def.type === 'Attack' && (0, state_1.run)().s.relics.includes('sacrificial_dagger')) {
        hpCost = Math.max(0, hpCost - 1);
        c.daggerUsed = true;
        log('Sacrificial Dagger: reduced HP cost by 1.', 'info');
    }
    if (hpCost > 0) {
        const lost = (0, state_1.hpLossCard)(c, hpCost, card.defId);
        float('player', `-${lost} HP`, 'dmg');
        log(`You lose ${lost} HP.`, 'bad');
    }
    // Black Blood: playing an Attack heals 1
    if (def.type === 'Attack' && (0, state_1.run)().s.relics.includes('black_blood'))
        healPlayer(1, true);
    log(`${spec.name}${card.upgraded ? '' : ''} played.`, 'info');
    // power card goes to power zone
    if (def.type === 'Power') {
        c.powers.push(card);
        sfx('buff');
        float('player', `${spec.name}`, 'buff');
        if ((0, state_1.run)().s.relics.includes('tome')) {
            drawCards(1);
            log('Tome of Dark Rituals: draw 1.', 'good');
        }
    }
    else if (def.exhaust) {
        c.exhaust.push(card);
        sfx('exhaust');
    }
    else if (def.ethereal) {
        c.exhaust.push(card);
    }
    else {
        c.discard.push(card);
    }
    // ---- effects ----
    const targets = def.all ? allTargets() : (target && target.stats.hp > 0 ? [target] : []);
    if (def.random) {
        const hits = def.randomHit?.hits ?? 1;
        for (let i = 0; i < hits; i++) {
            const t = (0, utils_1.pick)(allTargets());
            damageEnemy(t, dmg);
        }
    }
    else if (dmg > 0 && targets.length) {
        for (const t of targets)
            damageEnemy(t, dmg);
        if (def.id === 'nightmare_lash' && target && target.stats.weak > 0 && target.stats.hp > 0) {
            const extra = card.upgraded ? (def.ifWeakExtraUp ?? 9) : (def.ifWeakExtra ?? 7);
            log('Weakness intensifies the blow.', 'good');
            damageEnemy(target, extra);
        }
    }
    if (blk > 0)
        gainPlayerBlock(blk);
    if (def.blockOnMissingHp) {
        const missing = c.player.maxHp - c.player.hp;
        gainPlayerBlock(missing + (card.upgraded ? 3 : 0));
    }
    if (healA > 0)
        healPlayer(healA);
    if (weak > 0 && targets.length)
        for (const t of targets)
            applyWeak(t, weak);
    if (def.weakAll)
        for (const t of allTargets())
            applyWeak(t, card.upgraded ? (def.nu?.weak ?? def.weakAll) : def.weakAll);
    if (vuln > 0 && targets.length)
        for (const t of targets)
            applyVuln(t, vuln);
    if (bleed > 0 && targets.length)
        for (const t of targets)
            applyBleed(t, bleed);
    if (def.bleedAll)
        for (const t of allTargets())
            applyBleed(t, card.upgraded ? (def.nu?.bleed ?? def.bleedAll) : def.bleedAll);
    if (burn > 0 && targets.length)
        for (const t of targets)
            applyBurn(t, burn);
    if (strN < 0 && targets.length) {
        for (const t of targets) {
            t.tempStr = (t.tempStr || 0) + strN;
            float(t.uid, `${strN} Str (turn)`, 'debuff');
            sfx('debuff');
        }
    }
    if (energyG > 0) {
        c.energy += energyG;
        float('player', `+${energyG} energy`, 'buff');
        sfx('buff');
    }
    if (def.energyOnPlay) {
        c.energy += def.energyOnPlay;
        float('player', `+${def.energyOnPlay} energy`, 'buff');
    }
    if (def.energyNextTurn) {
        c.energyNextTurn += def.energyNextTurn;
        float('player', `+${def.energyNextTurn} energy next turn`, 'buff');
    }
    if (def.dexNextTurn) {
        c.player.dex += card.upgraded ? (def.dexNextTurnUp ?? def.dexNextTurn) : def.dexNextTurn;
        float('player', '+Dex (next turn)', 'buff');
    }
    if (def.strThisTurn) {
        c.strThisTurn += card.upgraded ? (def.nu?.str ?? def.strThisTurn) : def.strThisTurn;
        float('player', `+${def.strThisTurn} Str (turn)`, 'buff');
    }
    if (intang > 0) {
        c.player.intangible += intang;
        float('player', `+${intang} Intangible`, 'buff');
        sfx('buff');
    }
    if (reg > 0) {
        c.player.regen += reg;
        float('player', `+${reg} Regen`, 'buff');
        sfx('buff');
    }
    if (def.blockNextTurn) {
        c.blockNextTurn += card.upgraded ? (def.blockNextTurnUp ?? def.blockNextTurn) : def.blockNextTurn;
        float('player', `+${def.blockNextTurn} Block next turn`, 'buff');
    }
    if (def.shuffleDazed) {
        const d = { uid: (0, utils_1.uid)(), defId: 'dazed', upgraded: false };
        if (card.upgraded && def.shuffleDazedDiscard)
            c.discard.push(d);
        else {
            c.draw.push(d);
            c.draw = (0, utils_1.shuffle)(c.draw);
        }
        log('A Dazed card is shuffled into your draw pile.', 'bad');
    }
    if (def.addImp) {
        addImpClaw(card.upgraded);
        c.impClawGiven = true;
    }
    if (def.drawOnPlay)
        drawCards(card.upgraded ? (def.nu?.draw ?? def.drawOnPlay) : def.drawOnPlay);
    // check combat end
    if (allTargets().length === 0)
        log('All enemies defeated!', 'good');
    return true;
}
function def2(card) { return (0, data_1.getDef)(card.defId); }
// ---------- turns ----------
function startTurn() {
    const c = combat;
    c.turn += 1;
    const p = c.player;
    p.bleed = 0;
    p.block = 0;
    if (c.blockNextTurn > 0) {
        p.block += c.blockNextTurn;
        log(`Whispering Shroud: gain ${c.blockNextTurn} Block.`, 'good');
        c.blockNextTurn = 0;
    }
    if (p.intangible > 0)
        p.intangible -= 1;
    // powers
    for (const pw of c.powers) {
        const k = (0, data_1.getDef)(pw.defId).power?.kind;
        if (k === 'soulSiphon') {
            c.energy += 1;
            const lost = (0, state_1.hpLossCard)(c, 1, 'soul_siphon');
            float('player', `-${lost} HP`, 'dmg');
            log('Soul Siphon: +1 Energy, lose HP.', 'info');
        }
        if (k === 'umbral') {
            const amt = (0, data_1.getDef)(pw.defId).nu?.weak ?? 1;
            for (const e of allTargets())
                applyWeak(e, amt);
            log(`Umbral Mastery: ${amt} Weak to ALL.`, 'info');
        }
        if (k === 'hellfire') {
            const d = (0, data_1.getDef)(pw.defId).nu?.dmg ?? 3;
            for (const e of allTargets())
                damageEnemy(e, d);
            log(`Hellfire Aura: ${d} damage to ALL.`, 'info');
        }
        if (k === 'bloodPact') { /* passive */ }
    }
    // relics
    if ((0, state_1.run)().s.relics.includes('bleeding_edge')) {
        const t = (0, utils_1.pick)(allTargets());
        if (t)
            applyBleed(t, 1);
    }
    if ((0, state_1.run)().s.relics.includes('hourglass') && c.hand.length === 0) {
        c.energy += 1;
        log('Eternal Hourglass: +1 Energy (empty hand).', 'good');
    }
    if ((0, state_1.run)().s.relics.includes('cursed_crown') || (0, state_1.run)().s.relics.includes('ectoplasm') || (0, state_1.run)().s.relics.includes('philosopher'))
        c.energy += 1;
    if ((0, state_1.run)().s.relics.includes('shadow_amulet'))
        p.intangible += 1;
    c.energy += (0, state_1.run)().s.nextEnergyBuff;
    (0, state_1.run)().s.nextEnergyBuff = 0;
    if (c.energyNextTurn > 0) {
        c.energy += c.energyNextTurn;
        c.energyNextTurn = 0;
    }
    drawCards(5);
    (0, state_1.run)().s.doubtInHand = c.hand.some(h => (0, data_1.getDef)(h.defId).id === 'doubt');
    sfx('turn');
}
function endTurn() {
    const c = combat;
    // Doubt
    if ((0, state_1.run)().s.doubtInHand) {
        const lost = (0, state_1.hpLossCard)(c, 1, 'doubt');
        float('player', `-${lost} (Doubt)`, 'dmg');
        log('Doubt gnaws at you: lose 1 HP.', 'bad');
    }
    // Regeneration (end of player turn)
    if (c.player.regen > 0) {
        healPlayer(c.player.regen, true);
        c.player.regen -= 1;
    }
    // Burn end of player turn
    if (c.player.burn > 0) {
        c.player.hp -= c.player.burn;
        float('player', `-${c.player.burn} Burn`, 'dmg');
        c.player.burn = Math.floor(c.player.burn / 2);
        sfx('fire');
    }
    // discard hand
    for (const h of c.hand) {
        if ((0, data_1.getDef)(h.defId).ethereal || (0, data_1.getDef)(h.defId).exhaust)
            c.exhaust.push(h);
        else
            c.discard.push(h);
    }
    c.hand = [];
    c.player.block = 0;
    c.strThisTurn = 0;
    (0, state_1.run)().s.doubtInHand = false;
    enemyPhase();
}
function enemyPhase() {
    const c = combat;
    for (const e of c.enemies) {
        if (e.stats.hp <= 0)
            continue;
        // Bleed at start of enemy turn
        if (e.stats.bleed > 0) {
            e.stats.hp -= e.stats.bleed;
            float(e.uid, `-${e.stats.bleed} Bleed`, 'dmg');
            e.stats.bleed = Math.floor(e.stats.bleed / 2);
            if (e.stats.hp <= 0) {
                e.stats.hp = 0;
                log(`${e.stats.name} dies to bleeding out!`, 'good');
                sfx('death');
                continue;
            }
        }
        // poison start
        if (e.stats.poison > 0) {
            e.stats.hp -= e.stats.poison;
            float(e.uid, `-${e.stats.poison} Poison`, 'dmg');
            e.stats.poison -= 1;
            if (e.stats.hp <= 0) {
                e.stats.hp = 0;
                log(`${e.stats.name} is poisoned to death!`, 'good');
                sfx('death');
                continue;
            }
        }
        // burn end of enemy turn
        if (e.stats.burn > 0) {
            e.stats.hp -= e.stats.burn;
            float(e.uid, `-${e.stats.burn} Burn`, 'dmg');
            e.stats.burn = Math.floor(e.stats.burn / 2);
            if (e.stats.hp <= 0) {
                e.stats.hp = 0;
                log(`${e.stats.name} burns away!`, 'good');
                sfx('death');
                continue;
            }
        }
        // duration ticks
        if (e.stats.weak > 0)
            e.stats.weak -= 1;
        if (e.stats.vulnerable > 0)
            e.stats.vulnerable -= 1;
        if (e.stats.regen > 0) {
            e.stats.hp = Math.min(e.stats.maxHp, e.stats.hp + e.stats.regen);
            e.stats.regen -= 1;
        }
        // boss phase 2 checks
        if (e.isBoss && e.phase2 && e.stats.hp > 0 && e.stats.hp <= phaseThreshold(e.defId) && !e.enraged) {
            e.enraged = true;
            e.defId = e.phase2;
            e.stats.name = (0, data_1.getEnemy)(e.phase2).name;
            e.art = (0, data_1.getEnemy)(e.phase2).art;
            log(`${e.stats.name} ENRAGES!`, 'bad');
            sfx('summon');
        }
        if (e.enraged && (e.defId === 'lich2' || e.defId === 'archdemon2'))
            e.stats.str += 2;
        if (e.enraged && e.defId === 'eldritch2')
            e.stats.str += 1;
        const intent = currentIntent(e);
        doEnemyIntent(e, intent);
        e.tempStr = 0;
    }
    // player death check
    if (c.player.hp <= 0) {
        c.player.hp = 0;
        (0, fx_1.pushFx)({ kind: 'log', text: 'You collapse into darkness...', tone: 'bad' });
    }
}
function phaseThreshold(defId) {
    if (defId === 'lich')
        return 30;
    if (defId === 'eldritch')
        return 40;
    if (defId === 'archdemon')
        return 45;
    return 0;
}
function currentIntent(e) {
    const def = (0, data_1.getEnemy)(e.defId);
    let i = e.turnIdx % def.pattern.length;
    // Lich summon skip when 2+ minions present
    if (e.defId === 'lich') {
        const minions = aliveEnemies().filter(x => x.isMinion).length;
        if (def.pattern[i].kind === 'summon' && minions >= 2)
            i = 1;
    }
    if (e.defId === 'archdemon' && e.enraged)
        i = 0; // phase2 handled by defId swap
    return def.pattern[i];
}
function doEnemyIntent(e, it) {
    const c = combat;
    switch (it.kind) {
        case 'attack':
            enemyAttack(e, it.dmg || 0);
            if (it.weak) {
                c.player.weak += it.weak;
                float('player', `+${it.weak} Weak`, 'debuff');
                sfx('debuff');
            }
            if (it.vuln) {
                c.player.vulnerable += it.vuln;
                float('player', `+${it.vuln} Vuln`, 'debuff');
                sfx('debuff');
            }
            if (it.burn) {
                c.player.burn += it.burn;
                float('player', `+${it.burn} Burn`, 'debuff');
                sfx('fire');
            }
            break;
        case 'multi':
            for (let h = 0; h < (it.hits || 1); h++)
                enemyAttack(e, it.dmg || 0, true);
            if (it.weak)
                c.player.weak += it.weak;
            if (it.burn)
                c.player.burn += it.burn;
            break;
        case 'defend':
            e.stats.block += it.block || 0;
            float(e.uid, `+${it.block} block`, 'block');
            if (it.regen) {
                e.stats.regen += it.regen;
                float(e.uid, `+${it.regen} Regen`, 'buff');
            }
            if (it.heal) {
                e.stats.hp = Math.min(e.stats.maxHp, e.stats.hp + (it.heal || 0));
                float(e.uid, `+${it.heal}`, 'heal');
            }
            break;
        case 'mixed':
            e.stats.block += it.block || 0;
            float(e.uid, `+${it.block} block`, 'block');
            enemyAttack(e, it.dmg || 0);
            if (it.weak)
                c.player.weak += it.weak;
            if (it.vuln)
                c.player.vulnerable += it.vuln;
            if (it.burn)
                c.player.burn += it.burn;
            break;
        case 'buff':
            if (it.str) {
                if (e.defId === 'acultist') {
                    for (const t of aliveEnemies())
                        t.stats.str += it.str;
                    float(e.uid, `Empower +${it.str} Str (all)`, 'buff');
                }
                else {
                    e.stats.str += it.str;
                    float(e.uid, `+${it.str} Str`, 'buff');
                }
                sfx('buff');
            }
            if (it.block) {
                e.stats.block += it.block;
                float(e.uid, `+${it.block} block`, 'block');
            }
            if (it.regen) {
                e.stats.regen += it.regen;
                float(e.uid, `+${it.regen} Regen`, 'buff');
            }
            if (it.intang) {
                e.stats.intangible += it.intang;
                float(e.uid, `+${it.intang} Intangible`, 'buff');
            }
            break;
        case 'debuff':
            if (it.weak) {
                c.player.weak += it.weak;
                float('player', `+${it.weak} Weak`, 'debuff');
                sfx('debuff');
            }
            if (it.vuln) {
                c.player.vulnerable += it.vuln;
                float('player', `+${it.vuln} Vuln`, 'debuff');
                sfx('debuff');
            }
            if (it.bleed) {
                c.player.bleed += it.bleed;
                float('player', `+${it.bleed} Bleed`, 'debuff');
                sfx('debuff');
            }
            if (it.poison) {
                c.player.poison += it.poison;
                float('player', `+${it.poison} Poison`, 'debuff');
                sfx('poison');
            }
            break;
        case 'drain':
            enemyAttack(e, it.dmg || 0);
            if (it.heal) {
                e.stats.hp = Math.min(e.stats.maxHp, e.stats.hp + it.heal);
                float(e.uid, `+${it.heal}`, 'heal');
            }
            break;
        case 'summon': {
            const existing = aliveEnemies().filter(x => x.isMinion).length;
            if (it.summon && existing < 2) {
                const m = (0, state_1.makeEnemy)(it.summon, true);
                c.enemies.push(m);
                log(`${e.stats.name} summons ${m.stats.name}!`, 'bad');
                sfx('summon');
            }
            else {
                log(`${e.stats.name} cannot summon more.`, 'info');
            }
            break;
        }
        case 'vulnSelf':
            e.stats.block += it.block || 0;
            e.stats.vulnerable += it.vulnSelf || 0;
            float(e.uid, `+${it.vulnSelf} Vuln (self)`, 'debuff');
            break;
    }
    e.turnIdx += 1;
}
// ---------- potions ----------
function usePotion(potionId, target) {
    const c = combat;
    const r = (0, state_1.run)();
    const idx = r.s.potions.indexOf(potionId);
    if (idx < 0)
        return false;
    const enemies = allTargets();
    switch (potionId) {
        case 'blood_vial':
            healPlayer(15);
            break;
        case 'firebomb':
            for (const e of enemies)
                damageEnemy(e, 10);
            break;
        case 'dex_tonic':
            c.player.dex += 3;
            float('player', '+3 Dex', 'buff');
            break;
        case 'energy_elixir':
            c.energy += 2;
            float('player', '+2 energy', 'buff');
            break;
        case 'cursed_brew':
            for (const e of enemies)
                applyVuln(e, 3);
            break;
        case 'bleeding_flask':
            if (enemies.length) {
                const t = target && target.stats.hp > 0 ? target : enemies[0];
                applyBleed(t, 6);
            }
            break;
        case 'imp_bottle':
            addImpClaw(true);
            break;
        case 'smoke_bomb': {
            r.s.potions.splice(idx, 1);
            (0, fx_1.pushFx)({ kind: 'log', text: 'You vanish in a cloud of smoke...', tone: 'info' });
            c.escaped = true;
            return true;
        }
    }
    r.s.potions.splice(idx, 1);
    return true;
}
function isPlayerDead() { return combat.player.hp <= 0; }
function allDefeated() { return allTargets().length === 0; }
function playerStatList() {
    const p = combat.player;
    const out = [];
    if (p.str > 0)
        out.push({ id: 'str', label: 'Strength', value: p.str, tone: 'buff' });
    if (p.dex > 0)
        out.push({ id: 'dex', label: 'Dexterity', value: p.dex, tone: 'buff' });
    if (p.regen > 0)
        out.push({ id: 'regen', label: 'Regeneration', value: p.regen, tone: 'buff' });
    if (p.intangible > 0)
        out.push({ id: 'intang', label: 'Intangible', value: p.intangible, tone: 'buff' });
    if (p.weak > 0)
        out.push({ id: 'weak', label: 'Weak', value: p.weak, tone: 'debuff' });
    if (p.vulnerable > 0)
        out.push({ id: 'vuln', label: 'Vulnerable', value: p.vulnerable, tone: 'debuff' });
    if (p.bleed > 0)
        out.push({ id: 'bleed', label: 'Bleed', value: p.bleed, tone: 'debuff' });
    if (p.burn > 0)
        out.push({ id: 'burn', label: 'Burn', value: p.burn, tone: 'debuff' });
    if (p.poison > 0)
        out.push({ id: 'poison', label: 'Poison', value: p.poison, tone: 'debuff' });
    return out;
}
function enemyStatList(e) {
    const s = e.stats;
    const out = [];
    if (s.str > 0)
        out.push({ id: 'str', label: 'Str', value: s.str, tone: 'buff' });
    if (s.regen > 0)
        out.push({ id: 'regen', label: 'Regen', value: s.regen, tone: 'buff' });
    if (s.intangible > 0)
        out.push({ id: 'intang', label: 'Intang', value: s.intangible, tone: 'buff' });
    if (s.weak > 0)
        out.push({ id: 'weak', label: 'Weak', value: s.weak, tone: 'debuff' });
    if (s.vulnerable > 0)
        out.push({ id: 'vuln', label: 'Vuln', value: s.vulnerable, tone: 'debuff' });
    if (s.bleed > 0)
        out.push({ id: 'bleed', label: 'Bleed', value: s.bleed, tone: 'debuff' });
    if (s.burn > 0)
        out.push({ id: 'burn', label: 'Burn', value: s.burn, tone: 'debuff' });
    if (s.poison > 0)
        out.push({ id: 'poison', label: 'Poison', value: s.poison, tone: 'debuff' });
    return out;
}
function cardDamagePreview(card, enemy) {
    const def = (0, data_1.getDef)(card.defId);
    const spec = (0, data_1.cardSpec)(def, card.upgraded);
    if ((spec.n.dmg ?? 0) > 0)
        return `~${calcPlayerDamage(spec.n.dmg ?? 0, enemy)}`;
    return '';
}
function intentText(e) {
    return currentIntent(e).text;
}
function intentKind(e) {
    return currentIntent(e).kind;
}
function intentDamage(e) {
    const it = currentIntent(e);
    if (it.kind === 'attack' || it.kind === 'mixed' || it.kind === 'drain')
        return it.dmg || 0;
    if (it.kind === 'multi')
        return (it.dmg || 0) * (it.hits || 1);
    return 0;
}
};

// ── module: src/events.ts ──
__mods["src/events.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVENTS = void 0;
exports.makeCtx = makeCtx;
exports.getEvent = getEvent;
exports.availableEventIds = availableEventIds;
exports.rollEventId = rollEventId;
const state_1 = require("./state");
const data_1 = require("./data");
const utils_1 = require("./utils");
function makeCtx() {
    const r = (0, state_1.run)();
    return {
        hpLoss(n) { const h = Math.min(n, r.s.hp); r.s.hp -= h; return h; },
        heal(n) { const a = Math.min(n, r.s.maxHp - r.s.hp); r.s.hp += a; return a; },
        addGold(n) { r.s.gold += n; },
        spendGold(n) { if (r.s.gold < n)
            return false; r.s.gold -= n; return true; },
        addPotion(id) { if (r.s.potions.length >= 3)
            return false; r.s.potions.push(id); return true; },
        addRelic(id) { r.s.relics.push(id); },
        randomRareRelic() { const p = data_1.RELICS.filter(x => x.rarity === 'Rare' && !x.boss && (!x.cls || x.cls === r.s.cls)); return (0, utils_1.pick)(p.length ? p : data_1.RELICS.filter(x => !x.boss)).id; },
        randomCommonRelic() { const p = data_1.RELICS.filter(x => x.rarity === 'Common' && (!x.cls || x.cls === r.s.cls)); return (0, utils_1.pick)(p.length ? p : data_1.RELICS).id; },
        randomPotion() { return (0, utils_1.pick)(data_1.POTIONS).id; },
        addCurse(id) { r.s.deck.push({ uid: Math.random().toString(36).slice(2), defId: id, upgraded: false }); },
        upgradeRandomCards(n) {
            const deck = r.s.deck;
            const cands = deck.filter(x => !x.upgraded);
            const out = [];
            for (let i = 0; i < n && cands.length; i++) {
                const c = (0, utils_1.pick)(cands);
                c.upgraded = true;
                out.push((0, data_1.getDef)(c.defId).name);
                cands.splice(cands.indexOf(c), 1);
            }
            void deck;
            return out;
        },
        nextEnergyPlus() { r.s.nextEnergyBuff += 1; },
        addRandomPower() {
            const pool = data_1.ALL_CLASS_POOLS[r.s.cls].filter(x => x.type === 'Power');
            const d = (0, utils_1.pick)(pool);
            r.s.deck.push({ uid: Math.random().toString(36).slice(2), defId: d.id, upgraded: false });
            return d.name;
        },
        removeCurseDeck() {
            const before = r.s.deck.length;
            r.s.deck = r.s.deck.filter(x => (0, data_1.getDef)(x.defId).type !== 'Curse');
            return before - r.s.deck.length;
        },
    };
}
exports.EVENTS = {
    altar: {
        id: 'altar', title: 'The Forgotten Altar', art: 'altar',
        text: 'A blood-stained altar hums with power. Something ancient watches from the shadows, waiting to see what kind of hero you are.',
        choices: [
            { label: 'Offer 15 HP', sub: 'Gain a random Rare relic', result: (ctx) => { const h = ctx.hpLoss(15); const rel = ctx.randomRareRelic(); ctx.addRelic(rel); return `You press your bleeding hand to the altar. ${15 - h >= 0 ? '' : ''}It drinks deep of your life and rewards you with a relic: ${data_1.RELICS.find(x => x.id === rel)?.name}.`; } },
            { label: 'Pray', sub: 'Heal 10 HP, gain a Doubt curse', result: (ctx) => { const h = ctx.heal(10); ctx.addCurse('doubt'); return `Whispers answer your prayer. You feel ${h} vitality return... and a new doubt take root in your mind.`; } },
            { label: 'Leave', sub: 'Nothing happens', result: () => 'You step back from the altar. The humming fades behind you.' },
        ],
    },
    peddler: {
        id: 'peddler', title: 'Shadowy Peddler', art: 'peddler',
        text: 'A hooded figure leans against a dead tree, a satchel bulging with curious goods. "A trade, hero? I never ask twice."',
        choices: [
            { label: 'Trade a card', sub: 'Remove a card from your deck, gain 100 Gold', selectCard: { after: (ctx, card) => { (0, state_1.removeCardFromDeck)(card); ctx.addGold(100); return `The peddler takes your ${(0, data_1.getDef)(card.defId).name} and vanishes. Your purse grows by 100 gold.`; } } },
            { label: 'Buy his wares', sub: 'Lose 50 Gold: random Potion + random Common relic', disabled: (ctx) => (0, state_1.run)().s.gold < 50, result: (ctx) => { ctx.spendGold(50); const pot = ctx.randomPotion(); const rel = ctx.randomCommonRelic(); ctx.addPotion(pot); ctx.addRelic(rel); return `You pay 50 gold. The peddler slides you a ${data_1.POTIONS.find(x => x.id === pot)?.name} and a ${data_1.RELICS.find(x => x.id === rel)?.name}.`; } },
            { label: 'Attack', sub: 'Fight the Shadow Thief — +75 Gold on victory', combat: { group: ['shadow_thief'], eventGold: 75 } },
        ],
    },
    spring: {
        id: 'spring', title: 'Cursed Spring', art: 'spring',
        text: 'A spring bubbles with black water that never freezes. The surface reflects a world that is not this one.',
        choices: [
            { label: 'Drink', sub: 'Lose 10 HP, remove ALL Curses from your deck', disabled: (ctx) => (0, state_1.run)().s.hp <= 10, result: (ctx) => { ctx.hpLoss(10); const n = ctx.removeCurseDeck(); return `The water burns as it slides down, purging ${n} Curse${n === 1 ? '' : 's'} from your deck. The spring accepts your toll.`; } },
            { label: 'Bottle it', sub: 'Obtain a Cursed Brew potion', result: (ctx) => { const ok = ctx.addPotion('cursed_brew'); return ok ? 'You bottle the dark water. A Cursed Brew is yours.' : 'Your potion satchel is full — the water slips through your fingers and evaporates.'; } },
            { label: 'Leave', sub: 'Nothing happens', result: () => 'The spring bubbles on, indifferent.' },
        ],
    },
    dead: {
        id: 'dead', title: 'Restless Dead', art: 'ghosts',
        text: 'Spectral hands reach from the ground. Their voices are a chorus of the unfinished: "Let us... improve you... just a little more... before the end."',
        choices: [
            { label: 'Accept', sub: 'Lose 20 HP, upgrade 2 random cards in your deck', disabled: (ctx) => (0, state_1.run)().s.hp <= 20, result: (ctx) => { ctx.hpLoss(20); const ups = ctx.upgradeRandomCards(2); return ups.length ? `The ghosts swarm your deck and sharpen its edges. Upgraded: ${ups.join(', ')}. The toll is paid in blood.` : `The ghosts find nothing new to change in your deck. The toll is paid in blood anyway.`; } },
            { label: 'Refuse', sub: 'Nothing happens', result: () => 'The hands sink back into the earth. The whispers fade, unimpressed.' },
        ],
    },
    gambler: {
        id: 'gambler', title: 'Impish Gambler', art: 'imp',
        text: 'A grinning imp flips a gold coin that has heads on both sides. "One bet, hero. You can\'t lose. (You can lose.)"',
        choices: [
            { label: 'Bet 30 Gold', sub: '50% chance to win 60, else lose it all', disabled: (ctx) => (0, state_1.run)().s.gold < 30, result: (ctx) => { ctx.spendGold(30); if ((0, utils_1.chance)(0.5)) {
                    ctx.addGold(60);
                    return 'The coin lands heads-up. The imp hisses and pays 60 gold. "Lucky fool."';
                } return 'The coin lands... tails. The imp pockets your 30 gold and laughs in three octaves.'; } },
            { label: 'Wager a card', sub: 'Remove a random card — if a Curse, gain 100 Gold', result: (ctx) => { const d = (0, state_1.run)().s.deck; const card = (0, utils_1.pick)(d); (0, state_1.removeCardFromDeck)(card); const isCurse = (0, data_1.getDef)(card.defId).type === 'Curse'; if (isCurse) {
                    ctx.addGold(100);
                    return `You wager your ${(0, data_1.getDef)(card.defId).name}. The imp snatches it up, giddy, and pays 100 gold. Even imps hate curses.`;
                } return `You wager your ${(0, data_1.getDef)(card.defId).name}. The imp examines it, shrugs, and keeps it. No gold. Just an empty deck space.`; } },
            { label: 'Leave', sub: 'Nothing happens', result: () => 'The imp waves you off, muttering about unprincipled tourists.' },
        ],
    },
    library: {
        id: 'library', title: 'Ancient Library', art: 'library',
        text: 'Tomes drift open and closed on their own, pages whispering forbidden equations. A reading nook, mercifully, sits undisturbed.',
        choices: [
            { label: 'Study', sub: 'Gain 1 Energy in the next combat, lose 5 HP', disabled: (ctx) => (0, state_1.run)().s.hp <= 5, result: (ctx) => { ctx.hpLoss(5); ctx.nextEnergyPlus(); return 'The knowledge sears into your mind, costing 5 HP. Your next battle will burn brighter.'; } },
            { label: 'Read the forbidden page', sub: 'Gain a random Power card, gain a Doubt curse', result: (ctx) => { const name = ctx.addRandomPower(); ctx.addCurse('doubt'); return `You tear the page free. ${name} is yours now — as is the growing doubt of what else it changed in you.`; } },
            { label: 'Leave', sub: 'Nothing happens', result: () => 'The books close their eyes as you go.' },
        ],
    },
};
function getEvent(id) {
    return exports.EVENTS[id] || exports.EVENTS.altar;
}
function availableEventIds() { return Object.keys(exports.EVENTS); }
function rollEventId() { return (0, utils_1.pick)(Object.keys(exports.EVENTS)); }
void data_1.COLORLESS_POOL;
void utils_1.shuffle;
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.NODE_COLORS = exports.app = void 0;
exports.showScreen = showScreen;
exports.icon = icon;
exports.nodeIcon = nodeIcon;
exports.nodeLabel = nodeLabel;
exports.classArt = classArt;
exports.enemyArt = enemyArt;
exports.potionIcon = potionIcon;
exports.rarityColor = rarityColor;
exports.cardEl = cardEl;
exports.renderTopbar = renderTopbar;
exports.openDeckPanel = openDeckPanel;
exports.closeDeckPanel = closeDeckPanel;
const data_1 = require("./data");
const state_1 = require("./state");
const audio_1 = require("./audio");
const utils_1 = require("./utils");
exports.app = document.getElementById('app');
const overlays = {};
for (const id of ['menu', 'map', 'combat', 'reward', 'rest', 'shop', 'event', 'gameover', 'victory', 'deckpanel']) {
    const el = document.getElementById(id);
    overlays[id] = el;
}
function showScreen(name) {
    for (const k of Object.keys(overlays))
        overlays[k].classList.toggle('hidden', k !== name);
    exports.app.dataset.screen = name;
}
// ---------------------------------------------------------------- ICONS (inline svg)
let svgSeq = 1;
const gid = (p) => `${p}${svgSeq++}`;
function icon(name, size = 18) {
    const s = (body) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
    switch (name) {
        case 'skull': return s(`<path d="M12 2a8 8 0 0 0-8 8c0 3 1.6 5.2 4 6.4V20h2v-2h2v2h2v-2h2v2h2v-3.6c2.4-1.2 4-3.4 4-6.4a8 8 0 0 0-8-8z" fill="currentColor" stroke="none"/><circle cx="9" cy="10" r="1.6" fill="#1a1024" stroke="none"/><circle cx="15" cy="10" r="1.6" fill="#1a1024" stroke="none"/>`);
        case 'crown': return s(`<path d="M3 8l4 4 5-6 5 6 4-4v9H3z" fill="currentColor" stroke="none"/>`);
        case 'flame': return s(`<path d="M12 2c1 4-4 5-4 10a4 4 0 0 0 8 0c0-2-1-3-1-3s3 1 3 5a7 7 0 1 1-14 0C4 8 10 7 12 2z" fill="currentColor" stroke="none"/>`);
        case 'bag': return s(`<path d="M8 7V6a4 4 0 0 1 8 0v1h3l-1 14H6L5 7h3z" fill="currentColor" stroke="none"/><circle cx="12" cy="14" r="2" fill="#1a1024" stroke="none"/>`);
        case 'dice': return s(`<rect x="4" y="4" width="16" height="16" rx="3" fill="currentColor" stroke="none"/><circle cx="9" cy="9" r="1.4" fill="#1a1024" stroke="none"/><circle cx="15" cy="15" r="1.4" fill="#1a1024" stroke="none"/><circle cx="15" cy="9" r="1.4" fill="#1a1024" stroke="none"/><circle cx="9" cy="15" r="1.4" fill="#1a1024" stroke="none"/>`);
        case 'tent': return s(`<path d="M12 4L2 20h20L12 4z" fill="currentColor" stroke="none"/><path d="M12 10l-4 10h8l-4-10z" fill="#1a1024" stroke="none"/>`);
        case 'sword': return s(`<path d="M19 3l2 2-9 9 2 2-2 2-2-2-3 3-2-2 3-3-2-2 2-2 2 2 9-9z" fill="currentColor" stroke="none"/>`);
        case 'shield': return s(`<path d="M12 2l8 3v6c0 5-3.4 9.4-8 11-4.6-1.6-8-6-8-11V5l8-3z" fill="currentColor" stroke="none"/>`);
        case 'heart': return s(`<path d="M12 21C6 16 3 12.5 3 9a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 3.5-3 7-9 12z" fill="currentColor" stroke="none"/>`);
        case 'coin': return s(`<circle cx="12" cy="12" r="9" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="5" fill="#1a1024" stroke="none" opacity="0.35"/>`);
        case 'energy': return s(`<path d="M13 2L4 14h6l-1 8 9-12h-6l1-8z" fill="currentColor" stroke="none"/>`);
        case 'potion': return s(`<path d="M10 2h4v4l3 3v11a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9l3-3V2z" fill="currentColor" stroke="none"/><path d="M9 12h6v7H9z" fill="#1a1024" stroke="none" opacity="0.4"/>`);
        case 'relic': return s(`<path d="M12 2l3 6 6 1-4.5 4.5L18 20l-6-3-6 3 1.5-6.5L3 9l6-1 3-6z" fill="currentColor" stroke="none"/>`);
        case 'book': return s(`<path d="M4 4h13a3 3 0 0 1 3 3v13H7a3 3 0 0 1-3-3V4z" fill="currentColor" stroke="none"/><path d="M8 9h8M8 13h8" stroke="#1a1024" stroke-width="1.6"/>`);
        case 'weak': return s(`<circle cx="12" cy="12" r="9" fill="none"/><path d="M8 8l8 8M16 8l-8 8" stroke="currentColor" stroke-width="3"/>`);
        case 'vuln': return s(`<path d="M12 2l10 18H2L12 2z" fill="currentColor" stroke="none"/>`);
        case 'bleed': return s(`<path d="M12 3c3 5 6 8 6 11a6 6 0 1 1-12 0c0-3 3-6 6-11z" fill="currentColor" stroke="none"/>`);
        case 'burn': return s(`<path d="M12 2c1 4-4 5-4 10a4 4 0 0 0 8 0c0-2-1-3-1-3s3 1 3 5a7 7 0 1 1-14 0C4 8 10 7 12 2z" fill="currentColor" stroke="none"/>`);
        case 'poison': return s(`<circle cx="12" cy="13" r="7" fill="currentColor" stroke="none"/><path d="M9 3h2v4H9zM13 3h2v4h-2z" fill="currentColor" stroke="none"/>`);
        case 'intang': return s(`<circle cx="12" cy="12" r="8" fill="none" stroke-dasharray="3 3"/><circle cx="12" cy="12" r="3" fill="currentColor" stroke="none"/>`);
        case 'regen': return s(`<path d="M12 21C6 16 3 12.5 3 9a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 3.5-3 7-9 12z" fill="currentColor" stroke="none"/><path d="M12 8v8M8 12h8" stroke="#1a1024" stroke-width="2"/>`);
        case 'block': return s(`<path d="M12 2l8 3v6c0 5-3.4 9.4-8 11-4.6-1.6-8-6-8-11V5l8-3z" fill="currentColor" stroke="none"/>`);
        case 'str': return s(`<path d="M6 4h4v6h4V4h4v6a4 4 0 0 1-4 4v6h3v4H11v-4h-3v4H5v-4h3v-6a4 4 0 0 1-2-4V4z" fill="currentColor" stroke="none"/>`);
        case 'dex': return s(`<path d="M2 12l7-7 3 3-4 4 4 4 3-3 7 7-2 2-7-7-3 3-4-4-4-4z" fill="currentColor" stroke="none"/>`);
        case 'summon': return s(`<circle cx="12" cy="12" r="4" fill="currentColor" stroke="none"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4" stroke="currentColor" stroke-width="2.5"/>`);
        default: return s(`<circle cx="12" cy="12" r="8" fill="currentColor" stroke="none"/>`);
    }
}
function nodeIcon(type, size = 20) {
    switch (type) {
        case 'enemy': return icon('skull', size);
        case 'elite': return icon('crown', size);
        case 'rest': return icon('flame', size);
        case 'shop': return icon('bag', size);
        case 'event': return icon('dice', size);
        case 'boss': return icon('skull', size + 4);
        case 'start': return icon('tent', size);
        default: return icon('skull', size);
    }
}
exports.NODE_COLORS = {
    start: '#8a7a5c', enemy: '#a33d3d', elite: '#c98a2b', rest: '#e0562a',
    shop: '#c9a22b', event: '#7a4bc9', boss: '#e02a4a',
};
function nodeLabel(type) {
    switch (type) {
        case 'enemy': return 'Battle';
        case 'elite': return 'Elite';
        case 'rest': return 'Rest Site';
        case 'shop': return 'Shop';
        case 'event': return 'Mystery';
        case 'boss': return 'BOSS';
        case 'start': return 'Start';
    }
}
// ---------------------------------------------------------------- SVG ART
const ART_DEFS = (id, c1, c2) => `<defs><linearGradient id="${id}" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${c1}"/><stop offset="1" stop-color="${c2}"/></linearGradient></defs>`;
function classArt(cls, big = false) {
    const w = big ? 150 : 90;
    const id = gid('ca');
    const body = big ? '' : '';
    switch (cls) {
        case 'blood':
            return `<svg width="${w}" height="${w * 1.25}" viewBox="0 0 100 125" ${body}>${ART_DEFS(id, '#ff4d5e', '#7a1020')}
        <path d="M50 8c-16 0-26 12-26 28 0 10-6 16-8 26 10-4 12 2 14 6h40c2-4 4-10 14-6-2-10-8-16-8-26 0-16-10-28-26-28z" fill="url(#${id})"/>
        <path d="M36 40c4 10 4 24 0 36 4-4 8-6 14-6s10 2 14 6c-4-12-4-26 0-36 0 8-6 12-14 12s-14-4-14-12z" fill="#2b0710"/>
        <circle cx="42" cy="38" r="4" fill="#ffd9dd"/><circle cx="58" cy="38" r="4" fill="#ffd9dd"/>
        <path d="M30 92c10 8 30 8 40 0l6 22c-12 8-40 8-52 0l6-22z" fill="#40121c"/>
        <path d="M12 60l14 40 6-30-14-10z" fill="#8a1626"/></svg>`;
        case 'shadow':
            return `<svg width="${w}" height="${w * 1.25}" viewBox="0 0 100 125">${ART_DEFS(id, '#9a5bff', '#2d1257')}
        <path d="M50 6C30 6 22 24 22 40c0 14-10 20-12 34 8-6 16-4 20 2h40c4-6 12-8 20-2-2-14-12-20-12-34 0-16-8-34-28-34z" fill="url(#${id})"/>
        <path d="M34 38c2 12 2 26 0 38 4-6 10-8 16-8s12 2 16 8c-2-12-2-26 0-38-4 10-10 14-16 14s-12-4-16-14z" fill="#170a2b"/>
        <ellipse cx="42" cy="38" rx="4" ry="6" fill="#e6d4ff"/><ellipse cx="58" cy="38" rx="4" ry="6" fill="#e6d4ff"/>
        <path d="M28 92c12 8 32 8 44 0l4 24c-14 8-42 8-52 0l4-24z" fill="#33154f"/>
        <circle cx="82" cy="30" r="3" fill="#c9a2ff" opacity="0.7"/><circle cx="20" cy="52" r="2.5" fill="#c9a2ff" opacity="0.7"/></svg>`;
        case 'hell':
            return `<svg width="${w}" height="${w * 1.25}" viewBox="0 0 100 125">${ART_DEFS(id, '#ff7a2f', '#8a1c07')}
        <path d="M28 20C20 30 16 44 22 56c-4 2-6 6-4 10h64c2-4 0-8-4-10 6-12 2-26-6-36-4 8-6 12-8 18 2-10 0-20-4-28-4 12-10 20-20 24-10-4-16-12-20-24-4 8-6 18-4 28-2-6-4-10-8-18z" fill="url(#${id})"/>
        <path d="M36 44c2 10 2 22 0 32 4-4 8-6 14-6s10 2 14 6c-2-10-2-22 0-32-2 6-8 10-14 10s-12-4-14-10z" fill="#2b0d05"/>
        <circle cx="43" cy="44" r="4" fill="#ffe8b0"/><circle cx="57" cy="44" r="4" fill="#ffe8b0"/>
        <path d="M32 90c10 8 26 8 36 0l4 26c-12 8-36 8-48 0l4-26z" fill="#571c07"/>
        <path d="M84 20c4 6 4 14 0 20l-6-2c4-6 4-12 2-16l4-2z" fill="#ffb020"/></svg>`;
    }
}
const ENEMY_ART = {
    skeleton: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#e8e0d0', '#8a8070')}<circle cx="50" cy="34" r="18" fill="#ded6c4"/><path d="M38 44h24v10c0 4-3 6-6 6h-4v4h-4v-4h-4v4h-4v-4h-4c-3 0-6-2-6-6V44z" fill="#ded6c4"/><circle cx="43" cy="32" r="5" fill="#1a1024"/><circle cx="57" cy="32" r="5" fill="#1a1024"/><path d="M30 70h40M34 78h32M38 86h24" stroke="#b0a894" stroke-width="6" stroke-linecap="round"/></svg>`,
    zombie: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#7a9a4b', '#3d5724')}<circle cx="50" cy="36" r="20" fill="#6d8a42"/><path d="M36 46h28v12c0 3-2 5-5 5h-18c-3 0-5-2-5-5V46z" fill="#4c6330"/><circle cx="42" cy="32" r="4" fill="#ffd23e"/><circle cx="58" cy="32" r="4" fill="#ffd23e"/><path d="M36 74h28l6 16H30l6-16z" fill="#3d5724"/><path d="M24 60l12 10M76 60l-12 10" stroke="#4c6330" stroke-width="8" stroke-linecap="round"/></svg>`,
    cultist: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#5b3d8a', '#1e0f38')}<path d="M50 8C32 8 26 24 26 38c0 12-8 18-10 30 8-4 14-2 18 2h32c4-4 10-6 18-2-2-12-10-18-10-30 0-14-6-30-24-30z" fill="url(#${w === 100 ? 'g' : 'g'})"/><path d="M36 36c2 8 2 18 0 26 4-3 8-5 14-5s10 2 14 5c-2-8-2-18 0-26-6 8-10 10-14 10s-8-2-14-10z" fill="#140a26"/><circle cx="43" cy="36" r="3.5" fill="#e056e0"/><circle cx="57" cy="36" r="3.5" fill="#e056e0"/></svg>`,
    rats: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#8a6a4b', '#4b3624')}<ellipse cx="30" cy="40" rx="16" ry="11" fill="#7a5c40"/><ellipse cx="68" cy="52" rx="16" ry="11" fill="#8a6a4b"/><ellipse cx="42" cy="72" rx="16" ry="11" fill="#6a4e34"/><path d="M18 36c-6-2-8-6-6-10M78 48c6-2 8-6 6-10M52 68c6-2 8-6 6-10" stroke="#5a4230" stroke-width="3" fill="none"/><circle cx="24" cy="36" r="2" fill="#ffd23e"/><circle cx="74" cy="48" r="2" fill="#ffd23e"/><circle cx="36" cy="68" r="2" fill="#ffd23e"/></svg>`,
    guardian: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#8a8f9a', '#3d424d')}<rect x="30" y="14" width="40" height="34" rx="8" fill="#6d727d"/><rect x="24" y="48" width="52" height="34" rx="8" fill="#565b66"/><circle cx="42" cy="30" r="5" fill="#ffd23e"/><circle cx="58" cy="30" r="5" fill="#ffd23e"/><path d="M36 38h28" stroke="#3d424d" stroke-width="4"/><path d="M22 52l-14 20M78 52l14 20" stroke="#565b66" stroke-width="10" stroke-linecap="round"/><circle cx="14" cy="74" r="12" fill="#8a8f9a"/><path d="M14 64v20M4 74h20" stroke="#3d424d" stroke-width="3"/></svg>`,
    witch: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#4b7a5b', '#1e3824')}<path d="M50 8C34 8 28 22 28 36c0 12-8 18-10 30 8-4 14-2 18 2h32c4-4 10-6 18-2-2-12-10-18-10-30 0-14-6-28-26-28z" fill="url(#${'w'})"/><path d="M38 36c2 8 2 18 0 26 4-3 8-5 12-5s8 2 12 5c-2-8-2-18 0-26-4 8-8 10-12 10s-8-2-12-10z" fill="#0f1f14"/><circle cx="44" cy="36" r="3.5" fill="#ff4d5e"/><circle cx="56" cy="36" r="3.5" fill="#ff4d5e"/><path d="M44 48c4 3 8 3 12 0" stroke="#ff4d5e" stroke-width="2" fill="none"/></svg>`,
    lich: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#b8aef0', '#3d3572')}<path d="M50 2l10 12-10 8-10-8 10-12zM22 26l8 10-12 2 4-12zM78 26l-8 10 12 2-4-12z" fill="#ffd23e"/><path d="M50 22c-14 0-22 10-22 24 0 10-6 16-8 26 8-4 12-2 14 2h32c2-4 6-6 14-2-2-10-8-16-8-26 0-14-8-24-22-24z" fill="url(#${'l'})"/><path d="M38 44c2 8 2 18 0 26 4-3 8-5 12-5s8 2 12 5c-2-8-2-18 0-26-4 8-8 10-12 10s-8-2-12-10z" fill="#141026"/><circle cx="44" cy="44" r="4" fill="#b8f0ff"/><circle cx="56" cy="44" r="4" fill="#b8f0ff"/></svg>`,
    minion: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#cfc8b8', '#7a7264')}<circle cx="50" cy="40" r="15" fill="#cfc8b8"/><path d="M40 48h20v8c0 3-2 5-4 5h-12c-2 0-4-2-4-5v-8z" fill="#cfc8b8"/><circle cx="45" cy="38" r="3.5" fill="#1a1024"/><circle cx="55" cy="38" r="3.5" fill="#1a1024"/><path d="M34 66h32M38 74h24M42 82h16" stroke="#a89c88" stroke-width="5" stroke-linecap="round"/></svg>`,
    thief: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#4b5b8a', '#1a2440')}<path d="M50 8C34 8 28 22 28 36c0 12-8 18-10 30 8-4 14-2 18 2h32c4-4 10-6 18-2-2-12-10-18-10-30 0-14-6-28-26-28z" fill="url(#${'t'})"/><path d="M38 36c2 8 2 18 0 26 4-3 8-5 12-5s8 2 12 5c-2-8-2-18 0-26-4 8-8 10-12 10s-8-2-12-10z" fill="#0f1526"/><circle cx="44" cy="36" r="3.5" fill="#5be0ff"/><circle cx="56" cy="36" r="3.5" fill="#5be0ff"/><path d="M78 58l14 14-4 4-14-14 4-4z" fill="#c8d4e8"/></svg>`,
    wraith: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#c8d4e8', '#5b6b8a')}<path d="M50 10c-16 0-24 12-24 28v44c4-6 8-8 12-4s8 2 12-2 8-2 12 2 8 6 12 4V38c0-16-8-28-24-28z" fill="url(#${'r'})"/><circle cx="42" cy="36" r="5" fill="#141a26"/><circle cx="58" cy="36" r="5" fill="#141a26"/><path d="M30 26c-6-4-10-10-8-16 6 2 10 6 12 12M70 26c6-4 10-10 8-16-6 2-10 6-12 12" stroke="#8a9ab8" stroke-width="3" fill="none"/></svg>`,
    beast: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#5b8a4b', '#24401e')}<path d="M20 30l14-14 10 10h12l10-10 14 14-8 22c2 8 0 18-6 24l-10 14h-20L30 72c-6-6-8-16-6-24l6-18z" fill="url(#${'b'})"/><circle cx="38" cy="42" r="4" fill="#ffd23e"/><circle cx="62" cy="42" r="4" fill="#ffd23e"/><path d="M40 62l6 8 4-6 4 6 6-8" stroke="#14260f" stroke-width="3" fill="none"/><path d="M36 70l-6 8M64 70l6 8" stroke="#e8e0d0" stroke-width="4" stroke-linecap="round"/></svg>`,
    horror: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#3d5724', '#141f0a')}<circle cx="50" cy="50" r="28" fill="url(#${'h'})"/><path d="M50 22l6-14 6 14-6-2zM50 78l6 14 6-14-6 2zM22 50l-14-6 14-6 2 6zM78 50l14-6-14-6-2 6zM30 30L18 18l14 2 2 4zM70 30l12-12-14 2-2 4zM30 70L18 82l14-2 2-4zM70 70l12 12-14-2-2-4z" fill="#2d3f1a"/><circle cx="50" cy="46" r="7" fill="#ff2a4d"/><path d="M36 64c8 6 20 6 28 0" stroke="#1a0f0a" stroke-width="4" fill="none"/></svg>`,
    shaman: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#7a4b8a', '#2d1540')}<path d="M50 8C36 8 30 20 30 32c0 10-8 16-10 26 8-4 12-2 16 2h28c4-4 8-6 16-2-2-10-10-16-10-26 0-12-6-24-20-24z" fill="url(#${'s'})"/><path d="M38 32c2 6 2 14 0 20 4-2 8-4 12-4s8 2 12 4c-2-6-2-14 0-20-4 6-8 8-12 8s-8-2-12-8z" fill="#170a2b"/><circle cx="44" cy="32" r="3.5" fill="#7affc9"/><circle cx="56" cy="32" r="3.5" fill="#7affc9"/><path d="M78 44v44M70 56h16M70 70h16" stroke="#c8b8a8" stroke-width="5" stroke-linecap="round"/><circle cx="78" cy="40" r="7" fill="#7affc9" opacity="0.85"/></svg>`,
    treant: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#7a5230', '#3d2814')}<circle cx="50" cy="26" r="22" fill="#4b6b2d"/><circle cx="34" cy="34" r="14" fill="#5b7d35"/><circle cx="66" cy="34" r="14" fill="#5b7d35"/><rect x="36" y="44" width="28" height="40" rx="6" fill="url(#${'t2'})"/><circle cx="44" cy="58" r="4" fill="#ffb020"/><circle cx="56" cy="58" r="4" fill="#ffb020"/><path d="M44 70c4 3 8 3 12 0" stroke="#2b1a0a" stroke-width="3" fill="none"/><path d="M36 56l-16 12M64 56l16 12" stroke="#5a3a20" stroke-width="8" stroke-linecap="round"/></svg>`,
    plague: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#6b8a3d', '#2d4014')}<circle cx="50" cy="40" r="20" fill="#5b7a32"/><path d="M38 50h24v10c0 3-2 5-5 5h-14c-3 0-5-2-5-5V50z" fill="#4a6326"/><circle cx="43" cy="36" r="4" fill="#eaff7a"/><circle cx="57" cy="36" r="4" fill="#eaff7a"/><path d="M42 58h4M52 58h4" stroke="#1a260f" stroke-width="3"/><path d="M30 16c-4-4-4-8 0-12M50 12c-4-4-4-8 0-12M70 16c-4-4-4-8 0-12" stroke="#9aff5e" stroke-width="3" fill="none" opacity="0.6"/></svg>`,
    treant_boss: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#8a5a2f', '#402a12')}<circle cx="50" cy="24" r="24" fill="#3d5724"/><circle cx="30" cy="34" r="15" fill="#4b6b2d"/><circle cx="70" cy="34" r="15" fill="#4b6b2d"/><rect x="32" y="40" width="36" height="46" rx="8" fill="url(#${'tb'})"/><circle cx="43" cy="56" r="5" fill="#ff4d5e"/><circle cx="57" cy="56" r="5" fill="#ff4d5e"/><path d="M42 72l8 6 8-6" stroke="#1a0f0a" stroke-width="3" fill="none"/><path d="M32 56l-20 14M68 56l20 14M32 74l-16 16M68 74l16 16" stroke="#5a3a20" stroke-width="9" stroke-linecap="round"/></svg>`,
    hound: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#8a2f2f', '#3d1212')}<path d="M24 44l-10-16 16 4 8-10 8 12c14 0 22 8 22 20l10 14-12 2-4 12h-36l-4-12c-6-4-10-10-8-16z" fill="url(#${'h2'})"/><circle cx="40" cy="48" r="4" fill="#ffd23e"/><circle cx="56" cy="48" r="4" fill="#ffd23e"/><path d="M38 64c4 6 10 6 14 0" stroke="#1a0a0a" stroke-width="3" fill="none"/><path d="M40 62l4 6 4-6 4 6 4-6" fill="#e8e0d0"/></svg>`,
    sdemon: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#8a3dc9', '#2d0f40')}<path d="M34 20C28 28 26 38 30 46l-12 20 16-6c2 10 8 16 16 16s14-6 16-16l16 6-12-20c4-8 2-18-4-26-4 8-8 12-16 12s-12-4-16-12z" fill="url(#${'sd'})"/><circle cx="43" cy="40" r="3.5" fill="#e0b0ff"/><circle cx="57" cy="40" r="3.5" fill="#e0b0ff"/><path d="M44 52c3 3 9 3 12 0" stroke="#2d0f40" stroke-width="2.5" fill="none"/></svg>`,
    imp: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#c94b2f', '#571c07')}<path d="M38 18c-8 8-10 20-4 30l-8 14 12-4c2 8 8 12 12 12s10-4 12-12l12 4-8-14c6-10 4-22-4-30-4 6-8 8-12 8s-8-2-12-8z" fill="url(#${'i'})"/><circle cx="44" cy="36" r="3" fill="#ffd23e"/><circle cx="56" cy="36" r="3" fill="#ffd23e"/><path d="M44 48c3 2 9 2 12 0" stroke="#3d0f07" stroke-width="2" fill="none"/></svg>`,
    cultist3: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#6b2f8a', '#240a38')}<path d="M50 8C34 8 28 22 28 36c0 12-8 18-10 30 8-4 14-2 18 2h32c4-4 10-6 18-2-2-12-10-18-10-30 0-14-6-28-26-28z" fill="url(#${'c3'})"/><path d="M38 36c2 8 2 18 0 26 4-3 8-5 12-5s8 2 12 5c-2-8-2-18 0-26-4 8-8 10-12 10s-8-2-12-10z" fill="#140a26"/><circle cx="44" cy="36" r="3.5" fill="#ff4d5e"/><circle cx="56" cy="36" r="3.5" fill="#ff4d5e"/><path d="M50 62l3 6-3 6-3-6 3-6z" fill="#ff4d5e" opacity="0.8"/></svg>`,
    flamespawn: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#ff7a2f', '#8a1c07')}<path d="M28 30C22 40 20 52 26 62l-8 16 14-6c4 8 12 12 18 12s14-4 18-12l14 6-8-16c6-10 4-22-2-32-6 8-12 10-20 10s-14-2-20-10z" fill="url(#${'f'})"/><circle cx="42" cy="46" r="4" fill="#ffe8b0"/><circle cx="58" cy="46" r="4" fill="#ffe8b0"/><path d="M40 62c4 6 12 6 16 0l-4 8h-8l-4-8z" fill="#1a0a05"/><path d="M44 66c2 4 2 8 0 12M56 66c-2 4-2 8 0 12" stroke="#ffd23e" stroke-width="3" fill="none"/></svg>`,
    dread: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#5b6b8a', '#1a2440')}<path d="M50 6l14 10v14l-6 8H42l-6-8V16l14-10z" fill="url(#${'d'})"/><rect x="34" y="40" width="32" height="34" rx="6" fill="#2d3b57"/><path d="M34 46l-16 26 8 4 12-20zM66 46l16 26-8 4-12-20z" fill="#2d3b57"/><path d="M78 10v64" stroke="#c8d4e8" stroke-width="5" stroke-linecap="round"/><path d="M70 14h16" stroke="#c8d4e8" stroke-width="5" stroke-linecap="round"/><circle cx="44" cy="24" r="3.5" fill="#ff4d5e"/><circle cx="56" cy="24" r="3.5" fill="#ff4d5e"/></svg>`,
    archdemon: (w) => `<svg width="${w}" height="${w}" viewBox="0 0 100 100">${ART_DEFS(gid('ea'), '#e02a4a', '#400a14')}<path d="M20 10c2 14 8 22 18 26-4 10-4 22 4 32l-14 22 18-6c4 4 14 4 18 0l18 6-14-22c8-10 8-22 4-32 10-4 16-12 18-26l-16 8c-8-4-16-6-24-6s-16 2-24 6l-16-8z" fill="url(#${'a'})"/><circle cx="40" cy="44" r="5" fill="#ffd23e"/><circle cx="60" cy="44" r="5" fill="#ffd23e"/><path d="M38 62l6 8 6-6 6 6 6-8" stroke="#1a0508" stroke-width="3" fill="none"/></svg>`,
};
function enemyArt(artKey, w) {
    const fn = ENEMY_ART[artKey] || ENEMY_ART.skeleton;
    return fn(w);
}
function potionIcon(id) {
    const colors = {
        blood_vial: '#e02a4a', firebomb: '#ff7a2f', dex_tonic: '#5b8aff', energy_elixir: '#ffd23e',
        cursed_brew: '#7a4bc9', bleeding_flask: '#a33d3d', imp_bottle: '#c94b2f', smoke_bomb: '#8a9ab8',
    };
    const col = colors[id] || '#8a9ab8';
    return `<svg width="22" height="22" viewBox="0 0 24 24"><path d="M10 2h4v4l3 3v11a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9l3-3V2z" fill="${col}" stroke="#1a1024" stroke-width="1.2"/><path d="M10 12c1 1 3 1 4 0" stroke="#fff" stroke-width="1" fill="none" opacity="0.5"/></svg>`;
}
// ---------------------------------------------------------------- CARDS
function rarityColor(r) {
    switch (r) {
        case 'Common': return '#c8c8d0';
        case 'Uncommon': return '#5ba8ff';
        case 'Rare': return '#e8b83d';
        case 'Basic': return '#9a9aa8';
        case 'Curse': return '#c94be0';
        case 'Status': return '#9a9aa8';
        case 'Token': return '#5be08a';
        default: return '#c8c8d0';
    }
}
function cardEl(card, opts) {
    const def = (0, data_1.getDef)(card.defId);
    const spec = (0, data_1.cardSpec)(def, card.upgraded);
    const el = document.createElement('div');
    el.className = 'card' + (opts?.mini ? ' mini' : '') + (opts?.dimmed ? ' dimmed' : '') + (opts?.chosen ? ' chosen' : '');
    const cost = opts?.costOverride !== undefined ? opts.costOverride : (spec.cost === 'X' ? 'X' : spec.cost);
    const costCls = def.type === 'Attack' ? 'at' : def.type === 'Skill' ? 'sk' : 'pw';
    const name = opts?.mini ? spec.name : spec.name;
    el.innerHTML = `
    <div class="card-cost ${costCls}">${(0, utils_1.esc)(String(cost))}</div>
    <div class="card-name">${(0, utils_1.esc)(name)}</div>
    <div class="card-type">${def.type}</div>
    <div class="card-art">${iconCardArt(def.type, def.id)}</div>
    <div class="card-desc">${(0, utils_1.esc)(spec.desc)}</div>
    <div class="card-rar" style="color:${rarityColor(def.rarity)}">${def.rarity}</div>
  `;
    el.dataset.defid = def.id;
    if (opts?.onClick)
        el.addEventListener('click', opts.onClick);
    el.title = `${spec.name} (${def.type}, ${def.rarity})\n${spec.desc}`;
    return el;
}
function iconCardArt(type, defId) {
    if (type === 'Attack')
        return icon('sword', 34);
    if (type === 'Skill') {
        if (defId.includes('imp') || defId === 'infernal_pact')
            return icon('flame', 34);
        return icon('shield', 34);
    }
    if (type === 'Power')
        return icon('relic', 34);
    if (defId === 'doubt')
        return icon('book', 34);
    if (defId === 'wound' || defId === 'dazed')
        return icon('weak', 34);
    return icon('flame', 34);
}
// ---------------------------------------------------------------- TOP BAR
function renderTopbar() {
    const bar = document.getElementById('topbar');
    const r = (0, state_1.run)();
    if (!r) {
        bar.innerHTML = '';
        return;
    }
    const relicChips = r.s.relics.map(id => {
        const rel = (0, data_1.getRelic)(id);
        return `<span class="relic-chip" title="${(0, utils_1.esc)(rel.name)}: ${(0, utils_1.esc)(rel.desc)}">${icon('relic', 15)}</span>`;
    }).join('');
    const potionChips = r.s.potions.map(id => {
        const p = (0, data_1.getPotion)(id);
        return `<span class="potion-chip" title="${(0, utils_1.esc)(p.name)}: ${(0, utils_1.esc)(p.desc)}">${potionIcon(id)}</span>`;
    }).join('') + `<span class="potion-chip empty" title="Potion slots: ${r.s.potions.length}/3">·</span>`;
    bar.innerHTML = `
    <div class="tb-left">
      <span class="tb-act">${data_1.ACT_NAMES[r.s.act]}</span>
      <span class="tb-hp">${icon('heart', 14)} ${r.s.hp}/${r.s.maxHp}</span>
      <span class="tb-gold">${icon('coin', 14)} ${r.s.gold}</span>
    </div>
    <div class="tb-mid">${relicChips}</div>
    <div class="tb-right">
      ${potionChips}
      <button id="deckbtn" class="tb-btn" title="View deck (D)">${icon('book', 14)} Deck (${r.s.deck.length})</button>
      <button id="mutebtn" class="tb-btn">${document.body.dataset.muted === '1' ? '🔇' : '🔊'}</button>
    </div>
  `;
    document.getElementById('deckbtn').addEventListener('click', () => { audio_1.Sfx.click(); openDeckPanel('browse'); });
    document.getElementById('mutebtn').addEventListener('click', () => {
        const m = document.body.dataset.muted !== '1';
        document.body.dataset.muted = m ? '1' : '0';
        Promise.resolve().then(() => __importStar(require('./audio'))).then(a => a.setMuted(m));
        renderTopbar();
    });
}
// ---------------------------------------------------------------- DECK PANEL
let deckMode = 'browse';
let deckOnPick = null;
function openDeckPanel(mode, onPick) {
    deckMode = mode;
    deckOnPick = onPick || null;
    const titles = {
        browse: 'Your Deck', upgrade: 'Forge — choose a card to upgrade', remove: 'Meditate — choose a card to remove',
        vial: 'Vial of Shadows — remove a card', shopRemove: 'Remove a card (75g)',
    };
    const r = (0, state_1.run)();
    const panel = overlays.deckpanel;
    panel.classList.remove('hidden');
    const box = document.getElementById('decklist');
    const can = (card) => {
        const def = (0, data_1.getDef)(card.defId);
        if (deckMode === 'upgrade')
            return !card.upgraded && def.rarity !== 'Curse' && def.rarity !== 'Token';
        if (deckMode === 'remove' || deckMode === 'vial')
            return def.rarity !== 'Curse' ? true : !(r.s.relics.includes('cursed_crown'));
        if (deckMode === 'shopRemove')
            return !r.s.relics.includes('cursed_crown') ? def.type !== 'Curse' || true : false;
        return false;
    };
    const list = r.s.deck.slice().sort((a, b) => (0, data_1.getDef)(a.defId).name.localeCompare((0, data_1.getDef)(b.defId).name));
    box.innerHTML = '';
    box.appendChild(document.createElement('div')).className = 'panel-title';
    box.lastChild.textContent = titles[mode];
    for (const card of list) {
        const usable = can(card);
        const el = cardEl(card, { mini: true, onClick: usable ? (ev) => { audio_1.Sfx.click(); if (deckOnPick) {
                deckOnPick(card);
                closeDeckPanel();
            }
            else if (deckMode === 'upgrade') {
                (0, state_1.upgradeCardInDeck)(card);
                audio_1.Sfx.buff();
                renderTopbar();
                openDeckPanel(mode);
            }
            else if (deckMode === 'remove' || deckMode === 'vial') {
                (0, state_1.removeCardFromDeck)(card);
                audio_1.Sfx.click();
                renderTopbar();
                openDeckPanel(mode);
            } } : undefined });
        if (!usable)
            el.classList.add('dimmed');
        box.appendChild(el);
    }
    const close = document.getElementById('deckclose');
    close.onclick = () => closeDeckPanel();
}
function closeDeckPanel() { overlays.deckpanel.classList.add('hidden'); deckOnPick = null; }
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>