← Shadows results

Shadows

Qwen 3.6 27B MTP 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 126,114 bytes · SHA-256 62aaae4aa821
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shadows of the Abyss</title>
<style>
/* ── css: base ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a12; font-family: 'Segoe UI', Tahoma, sans-serif; color: #e0d8c8; }
#app { width: 100%; height: 100%; position: relative; cursor: default; }
#gameCanvas { width: 100%; height: 100%; display: block; }
#overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
#overlay > * { pointer-events: auto; }
</style>
</head>
<body>
<div id="app">
  <canvas id="gameCanvas"></canvas>
  <div id="overlay"></div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./engine":"src/engine.ts","./renderer":"src/renderer.ts","./cards":"src/cards.ts","./data":"src/data.ts"},"src/cards.ts":{"./types":"src/types.ts"},"src/data.ts":{"./types":"src/types.ts"},"src/engine.ts":{"./types":"src/types.ts","./cards":"src/cards.ts","./data":"src/data.ts"},"src/renderer.ts":{"./types":"src/types.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";
Object.defineProperty(exports, "__esModule", { value: true });
const engine_1 = require("./engine");
const renderer_1 = require("./renderer");
const cards_1 = require("./cards");
const data_1 = require("./data");
let state = (0, engine_1.createInitialState)();
function shuffle(arr) {
    const a = [...arr];
    for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
}
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
function generateCardChoices(isBoss, isElite = false) {
    const pool = (0, cards_1.getCardPool)(state.player.cls);
    const choices = [];
    for (let i = 0; i < 3; i++) {
        let rarity = 'Common';
        const roll = Math.random();
        if (isBoss && i === 0) {
            rarity = 'Rare';
        }
        else if (isElite ? roll < 0.2 : roll < 0.1) {
            rarity = 'Rare';
        }
        else if (roll < (isElite ? 0.5 : 0.4)) {
            rarity = 'Uncommon';
        }
        const filtered = pool.filter(c => c.rarity === rarity);
        if (filtered.length > 0)
            choices.push(pick(filtered));
        else
            choices.push(pick(pool));
    }
    return choices;
}
function generateShopItems() {
    const pool = (0, cards_1.getCardPool)(state.player.cls);
    const cards = [];
    for (let i = 0; i < 3; i++) {
        const roll = Math.random();
        let rarity = 'Common';
        if (roll < 0.1)
            rarity = 'Rare';
        else if (roll < 0.35)
            rarity = 'Uncommon';
        const filtered = pool.filter(c => c.rarity === rarity);
        const card = pick(filtered.length > 0 ? filtered : pool);
        const price = rarity === 'Rare' ? 150 : rarity === 'Uncommon' ? 75 : 50;
        cards.push({ card: { ...card, id: `shop_${i}` }, price });
    }
    const cc = pick(cards_1.colorlessCards);
    cards.push({ card: { ...cc, id: `shop_colorless` }, price: cc.rarity === 'Uncommon' ? 60 : 60 });
    const eligibleRelics = data_1.relicPool.filter(r => !r.cls || r.cls === state.player.cls);
    const relics = [];
    for (let i = 0; i < 2; i++)
        relics.push({ relic: pick(eligibleRelics), price: 150 });
    const potions = [];
    for (let i = 0; i < 3; i++)
        potions.push({ potion: pick(data_1.potionPool), price: 50 });
    state.shopItems = { cards, relics, potions };
    state.shopRemoveAvailable = true;
}
function getCombatReward() {
    const node = state.mapNodes[state.floor - 2];
    const isBoss = node?.type === 'boss';
    const isElite = node?.type === 'elite';
    const hasGreedyImp = state.player.relics.find(r => r.id === 'greedyImp');
    let gold = isBoss ? 50 : isElite ? randInt(25, 35) : randInt(10, 20);
    if (hasGreedyImp)
        gold += 10;
    if (state.player.relics.find(r => r.id === 'ectoplasm')) {
        gold = 0;
    }
    state.player.gold += gold;
    state.pendingRewards.gold = gold;
    if (state.player.relics.find(r => r.id === 'ashenArmor')) {
        state.player.hp = Math.min(state.player.maxHp, state.player.hp + 6);
    }
    if (isBoss)
        state.player.hp = state.player.maxHp;
    state.pendingRewards.cardChoices = generateCardChoices(isBoss, isElite);
    const potionChance = isBoss ? 1 : isElite ? 0.7 : 0.4;
    if (Math.random() < potionChance && state.player.potions.length < 3) {
        state.pendingRewards.potionChoice = pick(data_1.potionPool).id;
    }
    else {
        state.pendingRewards.potionChoice = undefined;
    }
    if (isElite) {
        const eligibleRelics = data_1.relicPool.filter(r => !r.cls || r.cls === state.player.cls);
        state.pendingRewards.relicChoices = [pick(eligibleRelics)];
    }
    else if (isBoss) {
        state.bossRelicChoices = shuffle(data_1.bossRelics).slice(0, 3);
    }
}
function usePotion(id) {
    switch (id) {
        case 'bloodVial':
            state.player.hp = Math.min(state.player.maxHp, state.player.hp + 15);
            break;
        case 'firebomb':
            state.enemies.forEach(e => { e.hp = Math.max(0, e.hp - 10); });
            break;
        case 'dexTonic':
            state.player.buffs.push({ type: 'dexterity', amount: 3, isDebuff: false, turnsRemaining: 1 });
            break;
        case 'energyElixir':
            state.player.energy += 2;
            break;
        case 'cursedBrew':
            state.enemies.forEach(e => {
                const ex = e.debuffs.find(d => d.type === 'vulnerable');
                if (ex)
                    ex.amount += 3;
                else
                    e.debuffs.push({ type: 'vulnerable', amount: 3, isDebuff: true, turnsRemaining: 99 });
            });
            break;
        case 'bleedingFlask':
            if (state.enemies.length > 0) {
                const ex = state.enemies[0].debuffs.find(d => d.type === 'bleed');
                if (ex)
                    ex.amount += 6;
                else
                    state.enemies[0].debuffs.push({ type: 'bleed', amount: 6, isDebuff: true, turnsRemaining: 0 });
            }
            break;
        case 'impInBottle': {
            const imp = (0, cards_1.createImpClaw)(true);
            state.player.hand.push({ ...imp, instanceId: `imp_${Date.now()}` });
            break;
        }
        case 'smokeBomb':
            const node = state.mapNodes[state.floor - 2];
            if (node && node.type !== 'boss') {
                node.completed = true;
                state.floor++;
                state.screen = 'map';
            }
            break;
    }
}
function enterNode(node) {
    node.completed = true;
    switch (node.type) {
        case 'enemy':
        case 'elite':
        case 'boss':
            if (node.enemyIds)
                state = (0, engine_1.beginCombat)(state, node.enemyIds);
            break;
        case 'rest':
            state.restAction = null;
            state.screen = 'rest';
            break;
        case 'shop':
            generateShopItems();
            state.screen = 'shop';
            break;
        case 'event':
            state.currentEvent = data_1.eventPool[(node.eventId ?? 0) % data_1.eventPool.length];
            state.screen = 'event';
            break;
    }
}
function processEventChoice(outcomes) {
    if (outcomes.hpGain)
        state.player.hp = Math.min(state.player.maxHp, state.player.hp + outcomes.hpGain);
    if (outcomes.hpLoss)
        state.player.hp = Math.max(0, state.player.hp - outcomes.hpLoss);
    if (outcomes.goldGain)
        state.player.gold += outcomes.goldGain;
    if (outcomes.goldLoss && state.player.gold >= outcomes.goldLoss)
        state.player.gold -= outcomes.goldLoss;
    if (outcomes.gainCurse)
        state.player.deck.push({ ...cards_1.DOUBT });
    if (outcomes.gainPotion && state.player.potions.length < 3)
        state.player.potions.push(pick(data_1.potionPool));
    if (outcomes.gainRelic) {
        const pool = data_1.relicPool.filter(r => r.rarity.toLowerCase() === outcomes.gainRelic);
        if (pool.length > 0)
            state.player.relics.push(pick(pool));
    }
    if (outcomes.upgradeCards) {
        for (let i = 0; i < outcomes.upgradeCards && i < state.player.deck.length; i++) {
            state.player.deck[i].upgraded = true;
        }
    }
    if (outcomes.removeCards && state.player.deck.length > 0) {
        state.player.deck.splice(Math.floor(Math.random() * state.player.deck.length), 1);
    }
    if (outcomes.gamble && Math.random() < 0.5) {
        state.player.gold += (outcomes.goldGain || 0);
    }
    if (outcomes.startCombat && outcomes.enemyId) {
        state = (0, engine_1.beginCombat)(state, [outcomes.enemyId]);
        return;
    }
    if (state.player.hp > 0) {
        const node = state.mapNodes[state.floor - 2];
        if (node)
            node.completed = true;
        state.floor++;
        state.screen = 'map';
    }
    else {
        state.screen = 'gameOver';
    }
}
function proceedAfterReward() {
    const node = state.mapNodes[state.floor - 2];
    if (node)
        node.completed = true;
    const wasBoss = node?.type === 'boss';
    if (wasBoss && state.pendingRewards.relicChoices && state.pendingRewards.relicChoices.length > 0) {
        state.player.relics.push(state.pendingRewards.relicChoices[0]);
        state.pendingRewards.relicChoices = undefined;
        state.screen = 'cardReward';
        return;
    }
    if (wasBoss && state.bossRelicChoices.length > 0) {
        state.screen = 'bossReward';
        return;
    }
    state.floor++;
    if (state.floor > 15) {
        if (state.act >= 3) {
            state.screen = 'victory';
            return;
        }
        state.act++;
        state.floor = 1;
        state.mapNodes = generateMap(state.act);
    }
    state.screen = 'map';
}
function leaveRest() {
    const node = state.mapNodes[state.floor - 2];
    if (node)
        node.completed = true;
    state.floor++;
    checkEndOfAct();
    state.screen = 'map';
}
function leaveShop() {
    const node = state.mapNodes[state.floor - 2];
    if (node)
        node.completed = true;
    state.floor++;
    checkEndOfAct();
    state.screen = 'map';
}
function checkEndOfAct() {
    if (state.floor > 15) {
        if (state.act >= 3) {
            state.screen = 'victory';
        }
        else {
            state.act++;
            state.floor = 1;
            state.mapNodes = generateMap(state.act);
        }
    }
}
function generateMap(act) {
    const floorsData = act === 1 ? data_1.act1Floors : act === 2 ? data_1.act2Floors : data_1.act3Floors;
    const nodes = [];
    const xPositions = [0, 40, -40, 30, -30, 20, -20];
    const yPositions = [];
    for (let i = 0; i < floorsData.length; i++) {
        yPositions.push(80 + i * 38);
    }
    floorsData.forEach((fd, idx) => {
        nodes.push({
            floor: fd.floor,
            type: fd.type,
            x: (0, renderer_1.getW)() / 2 + xPositions[idx % xPositions.length],
            y: yPositions[idx],
            completed: false,
            selected: false,
            enemyIds: fd.enemyIds,
            eventId: idx,
            id: `node_${idx}`,
        });
    });
    return nodes;
}
function handleClick(e) {
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    const W = (0, renderer_1.getW)();
    const H = (0, renderer_1.getH)();
    switch (state.screen) {
        case 'title':
            state = (0, engine_1.startRun)('Blood Reaper');
            state.screen = 'classSelect';
            break;
        case 'classSelect': {
            const classes = ['Blood Reaper', 'Shadowmancer', 'Hellforged'];
            const hover = (0, renderer_1.getHoverState)();
            for (let i = 0; i < 3; i++) {
                if (hover === `class_${i}`) {
                    state = (0, engine_1.startRun)(classes[i]);
                    return;
                }
            }
            break;
        }
        case 'map': {
            const hover = (0, renderer_1.getHoverState)();
            const nodeIdx = parseInt(hover.replace('mapnode_', ''));
            if (!isNaN(nodeIdx) && nodeIdx >= 0 && nodeIdx < state.mapNodes.length) {
                const node = state.mapNodes[nodeIdx];
                if (!node.completed && nodeIdx === state.floor - 2) {
                    enterNode(node);
                }
            }
            break;
        }
        case 'combat': {
            const hover = (0, renderer_1.getHoverState)();
            if (hover === 'endTurn' && state.isPlayerTurn) {
                state = (0, engine_1.endPlayerTurn)(state);
                state = (0, engine_1.processEnemyTurn)(state);
                if (state.player.hp <= 0) {
                    state.screen = 'gameOver';
                }
                else if (state.enemies.length === 0) {
                    getCombatReward();
                    state.screen = state.pendingRewards.potionChoice ? 'potionPick' : 'cardReward';
                }
                else {
                    state = (0, engine_1.startPlayerTurn)(state);
                    if (state.player.hp <= 0)
                        state.screen = 'gameOver';
                }
                return;
            }
            const pMatch = hover.match(/^potion_(\d+)$/);
            if (pMatch && state.isPlayerTurn) {
                const pIdx = parseInt(pMatch[1]);
                if (pIdx < state.player.potions.length) {
                    usePotion(state.player.potions[pIdx].id);
                    state.player.potions.splice(pIdx, 1);
                    return;
                }
            }
            if (!state.isPlayerTurn)
                break;
            const cardW = 100;
            const handY = H - 260;
            const handStartX = Math.max(20, W / 2 - state.player.hand.length * (cardW + 8) / 2);
            for (let i = 0; i < state.player.hand.length; i++) {
                const card = state.player.hand[i];
                const cx = handStartX + i * (cardW + 8);
                if (mx >= cx && mx <= cx + cardW && my >= handY && my <= handY + 140) {
                    if (!card.unplayable && state.player.energy >= card.cost) {
                        const targetIdx = state.enemies.length > 0 ? 0 : -1;
                        state = (0, engine_1.playCard)(state, card, targetIdx);
                        if (state.enemies.length === 0) {
                            getCombatReward();
                            state.screen = state.pendingRewards.potionChoice ? 'potionPick' : 'cardReward';
                        }
                        if (state.player.hp <= 0)
                            state.screen = 'gameOver';
                    }
                    return;
                }
            }
            break;
        }
        case 'rest': {
            const hover = (0, renderer_1.getHoverState)();
            if (hover === 'rest_rest') {
                state.player.hp = Math.min(state.player.maxHp, state.player.hp + Math.floor(state.player.maxHp * 0.25));
                state.restAction = null;
                leaveRest();
            }
            else if (hover === 'rest_forge' && !state.restAction)
                state.restAction = 'forge';
            else if (hover === 'rest_meditate' && !state.restAction)
                state.restAction = 'meditate';
            else if (state.restAction) {
                const cardMatch = hover.match(/^${state.restAction}_card_(\d+)$/);
                if (cardMatch) {
                    const idx = parseInt(cardMatch[1]);
                    if (idx < state.player.deck.length) {
                        if (state.restAction === 'forge')
                            state.player.deck[idx].upgraded = true;
                        else if (state.restAction === 'meditate')
                            state.player.deck.splice(idx, 1);
                        state.restAction = null;
                        leaveRest();
                    }
                }
            }
            break;
        }
        case 'shop': {
            const hover = (0, renderer_1.getHoverState)();
            if (hover === 'shop_leave') {
                leaveShop();
                return;
            }
            const cardMatch = hover.match(/^shop_card_(\d+)$/);
            if (cardMatch) {
                const idx = parseInt(cardMatch[1]);
                const item = state.shopItems.cards[idx];
                if (item && state.player.gold >= item.price) {
                    state.player.gold -= item.price;
                    state.player.deck.push(item.card);
                    state.shopItems.cards.splice(idx, 1);
                }
                return;
            }
            const relicMatch = hover.match(/^shop_relic_(\d+)$/);
            if (relicMatch) {
                const idx = parseInt(relicMatch[1]);
                const item = state.shopItems.relics[idx];
                if (item && state.player.gold >= item.price) {
                    state.player.gold -= item.price;
                    state.player.relics.push(item.relic);
                    state.shopItems.relics.splice(idx, 1);
                }
                return;
            }
            const potMatch = hover.match(/^shop_potion_(\d+)$/);
            if (potMatch) {
                const idx = parseInt(potMatch[1]);
                const item = state.shopItems.potions[idx];
                if (item && state.player.gold >= item.price && state.player.potions.length < 3) {
                    state.player.gold -= item.price;
                    state.player.potions.push(item.potion);
                    state.shopItems.potions.splice(idx, 1);
                }
                return;
            }
            break;
        }
        case 'event': {
            const hover = (0, renderer_1.getHoverState)();
            const evMatch = hover.match(/^event_(\d+)$/);
            if (evMatch && state.currentEvent) {
                const idx = parseInt(evMatch[1]);
                const choice = state.currentEvent.choices[idx];
                if (choice)
                    processEventChoice(choice.outcomes);
            }
            break;
        }
        case 'cardReward': {
            const hover = (0, renderer_1.getHoverState)();
            if (hover === 'reward_skip')
                proceedAfterReward();
            else {
                const match = hover.match(/^reward_(\d+)$/);
                if (match) {
                    const idx = parseInt(match[1]);
                    if (idx < state.pendingRewards.cardChoices.length) {
                        state.player.deck.push(state.pendingRewards.cardChoices[idx]);
                        proceedAfterReward();
                    }
                }
            }
            break;
        }
        case 'potionPick': {
            if ((0, renderer_1.getHoverState)() === 'potion_accept' && state.pendingRewards.potionChoice) {
                const potions = {
                    bloodVial: { id: 'bloodVial', name: 'Blood Vial', description: 'Heal 15 HP.' },
                    firebomb: { id: 'firebomb', name: 'Firebomb', description: 'Deal 10 damage to ALL enemies.' },
                    dexTonic: { id: 'dexTonic', name: 'Dex Tonic', description: 'Gain 3 Dexterity for 1 turn.' },
                    energyElixir: { id: 'energyElixir', name: 'Energy Elixir', description: 'Gain 2 Energy this turn.' },
                    cursedBrew: { id: 'cursedBrew', name: 'Cursed Brew', description: 'Apply 3 Vulnerable to all enemies.' },
                    bleedingFlask: { id: 'bleedingFlask', name: 'Bleeding Flask', description: 'Apply 6 Bleed to an enemy.' },
                    impInBottle: { id: 'impInBottle', name: 'Imp-in-a-Bottle', description: 'Add Imp Claw+ to hand.' },
                    smokeBomb: { id: 'smokeBomb', name: 'Smoke Bomb', description: 'Escape from combat, no rewards.' },
                };
                const p = potions[state.pendingRewards.potionChoice];
                if (p && state.player.potions.length < 3)
                    state.player.potions.push(p);
                state.pendingRewards.potionChoice = undefined;
                proceedAfterReward();
            }
            break;
        }
        case 'bossReward': {
            const hover = (0, renderer_1.getHoverState)();
            if (hover === 'bossrelic_skip')
                proceedAfterReward();
            else {
                const match = hover.match(/^bossrelic_(\d+)$/);
                if (match) {
                    const idx = parseInt(match[1]);
                    if (idx < state.bossRelicChoices.length) {
                        const relic = state.bossRelicChoices[idx];
                        state.player.relics.push(relic);
                        if (relic.id === 'cursedCrown')
                            state.player.maxEnergy++;
                        else if (relic.id === 'ectoplasm') {
                            state.player.maxEnergy++;
                            state.player.gold = 0;
                        }
                        else if (relic.id === 'philosopherStone')
                            state.player.maxEnergy++;
                        else if (relic.id === 'shadowAmulet')
                            state.player.maxHp -= 15;
                    }
                    proceedAfterReward();
                }
            }
            break;
        }
        case 'gameOver':
        case 'victory':
            if ((0, renderer_1.getHoverState)() === 'restart') {
                state = (0, engine_1.createInitialState)();
            }
            break;
    }
}
// Hover tracking
function handleMouseMove(e) {
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    const W = (0, renderer_1.getW)();
    const H = (0, renderer_1.getH)();
    let hover = '';
    switch (state.screen) {
        case 'title':
            if (mx > W / 2 - 140 && mx < W / 2 + 140 && my > H * 0.55 && my < H * 0.55 + 50)
                hover = 'startBtn';
            break;
        case 'classSelect': {
            const btnW = 260;
            const gap = 30;
            const totalW = 3 * btnW + 2 * gap;
            const startX = W / 2 - totalW / 2;
            const y = H * 0.25;
            for (let i = 0; i < 3; i++) {
                const x = startX + i * (btnW + gap);
                if (mx > x && mx < x + btnW && my > y && my < y + 180)
                    hover = `class_${i}`;
            }
            break;
        }
        case 'map':
            state.mapNodes.forEach((node, i) => {
                if (Math.sqrt((mx - node.x) ** 2 + (my - node.y) ** 2) < 22)
                    hover = `mapnode_${i}`;
            });
            break;
        case 'combat': {
            if (mx > W - 140 && mx < W - 20 && my > H - 30 && my < H - 30 + 35) {
                hover = 'endTurn';
                (0, renderer_1.setHoverState)(hover);
                return;
            }
            const cardW = 100;
            const handY = H - 260;
            const handStartX = Math.max(20, W / 2 - state.player.hand.length * (cardW + 8) / 2);
            for (let i = 0; i < state.player.hand.length; i++) {
                const cx = handStartX + i * (cardW + 8);
                if (mx >= cx && mx <= cx + cardW && my >= handY && my <= handY + 140) {
                    state.hoveredCard = state.player.hand[i];
                    (0, renderer_1.setHoverState)(`card_${state.player.hand[i].instanceId}`);
                    return;
                }
            }
            state.hoveredCard = null;
            state.player.potions.forEach((p, i) => {
                const px = 15 + i * 60;
                if (mx > px && mx < px + 55 && my > H - 25 && my < H - 3)
                    hover = `potion_${i}`;
            });
            break;
        }
        case 'rest': {
            const actions = ['rest', 'forge', 'meditate'];
            actions.forEach((a, i) => {
                const x = W / 2 - 220 + i * 160;
                if (mx > x && mx < x + 140 && my > 140 && my < 220)
                    hover = `rest_${a}`;
            });
            if (state.restAction) {
                const cols = Math.floor((W - 40) / 104);
                state.player.deck.slice(0, 20).forEach((_, i) => {
                    const col = i % cols;
                    const row = Math.floor(i / cols);
                    const x = 20 + col * 104;
                    const y = 300 + row * 124;
                    if (mx > x && mx < x + 100 && my > y && my < y + 120)
                        hover = `${state.restAction}_card_${i}`;
                });
            }
            break;
        }
        case 'shop': {
            const lx = W - 100;
            if (mx > lx && mx < lx + 80 && my > H - 40 && my < H - 10) {
                hover = 'shop_leave';
                (0, renderer_1.setHoverState)(hover);
                return;
            }
            state.shopItems.cards.forEach((_, i) => {
                const x = W / 2 - 200 + i * 140;
                if (mx > x && mx < x + 110 && my > 110 && my < 240)
                    hover = `shop_card_${i}`;
            });
            state.shopItems.relics.forEach((_, i) => {
                const x = W / 2 - 100 + i * 120;
                if (mx > x && mx < x + 90 && my > 310 && my < 410)
                    hover = `shop_relic_${i}`;
            });
            state.shopItems.potions.forEach((_, i) => {
                const x = W / 2 - 150 + i * 100;
                if (mx > x && mx < x + 75 && my > 480 && my < 540)
                    hover = `shop_potion_${i}`;
            });
            break;
        }
        case 'event':
            if (state.currentEvent) {
                state.currentEvent.choices.forEach((_, i) => {
                    const cy = 220 + i * 60;
                    if (mx > W / 2 - 200 && mx < W / 2 + 200 && my > cy && my < cy + 45)
                        hover = `event_${i}`;
                });
            }
            break;
        case 'cardReward': {
            state.pendingRewards.cardChoices.forEach((_, i) => {
                const totalW = state.pendingRewards.cardChoices.length * 140 + (state.pendingRewards.cardChoices.length - 1) * 20;
                const startX = W / 2 - totalW / 2;
                const x = startX + i * 160;
                if (mx > x && mx < x + 120 && my > 120 && my < 280)
                    hover = `reward_${i}`;
            });
            if (mx > W / 2 - 60 && mx < W / 2 + 60 && my > 350 && my < 385)
                hover = 'reward_skip';
            break;
        }
        case 'potionPick':
            if (mx > W / 2 - 80 && mx < W / 2 + 80 && my > 250 && my < 290)
                hover = 'potion_accept';
            break;
        case 'bossReward': {
            state.bossRelicChoices.forEach((_, i) => {
                const x = W / 2 - 240 + i * 170;
                if (mx > x && mx < x + 150 && my > 110 && my < 270)
                    hover = `bossrelic_${i}`;
            });
            if (mx > W / 2 - 50 && mx < W / 2 + 50 && my > 310 && my < 340)
                hover = 'bossrelic_skip';
            break;
        }
        case 'gameOver':
        case 'victory': {
            const btnX = W / 2 - 100;
            const btnY = state.screen === 'gameOver' ? H * 0.6 : H * 0.5;
            if (mx > btnX && mx < btnX + 200 && my > btnY && my < btnY + 45)
                hover = 'restart';
            break;
        }
    }
    (0, renderer_1.setHoverState)(hover);
}
// Setup
const canvas = document.getElementById('gameCanvas');
function renderLoop(timestamp) {
    const W = (0, renderer_1.getW)();
    const H = (0, renderer_1.getH)();
    if (canvas.width !== W || canvas.height !== H) {
        canvas.width = W;
        canvas.height = H;
    }
    (0, renderer_1.renderScreen)(state);
    requestAnimationFrame(renderLoop);
}
canvas.addEventListener('click', handleClick);
canvas.addEventListener('mousemove', handleMouseMove);
requestAnimationFrame(renderLoop);
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
// Core type definitions for Shadows of the Abyss
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/cards.ts ──
__mods["src/cards.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DAZED = exports.DOUBT = exports.WOUND = exports.colorlessCards = exports.hellforgedCards = exports.shadowmancerCards = exports.bloodReaperCards = exports.DEFEND = exports.STRIKE = void 0;
exports.getCardPool = getCardPool;
exports.getClassStartingDeck = getClassStartingDeck;
exports.getUpgradeVersion = getUpgradeVersion;
exports.createImpClaw = createImpClaw;
let cardCounter = 0;
function uid() { return `card_${++cardCounter}`; }
function makeCard(name, type, rarity, cost, description, cls, exhaust, ethereal, unplayable) {
    return { id: uid(), name, type, rarity, cost, description, upgraded: false, cls, exhaust, ethereal, unplayable };
}
// Basic cards
exports.STRIKE = makeCard('Strike', 'Attack', 'Basic', 1, 'Deal 6 damage.');
exports.DEFEND = makeCard('Defend', 'Skill', 'Basic', 1, 'Gain 5 Block.');
// --- Blood Reaper Cards ---
exports.bloodReaperCards = [
    // Attacks
    makeCard('Sanguine Strike', 'Attack', 'Common', 1, 'Deal 8 damage. Heal 2 HP.', 'Blood Reaper'),
    makeCard('Hemorrhage', 'Attack', 'Common', 1, 'Deal 5 damage. Apply 3 Bleed.', 'Blood Reaper'),
    makeCard('Bloodletting', 'Attack', 'Uncommon', 1, 'Lose 3 HP. Deal 12 damage.', 'Blood Reaper'),
    makeCard('Crimson Eruption', 'Attack', 'Rare', 2, 'Deal 8 damage to ALL enemies. Lose 2 HP.', 'Blood Reaper'),
    // Defenses
    makeCard('Crimson Shield', 'Skill', 'Common', 1, 'Gain 6 Block. Lose 2 HP.', 'Blood Reaper'),
    makeCard('Blood Ward', 'Skill', 'Common', 1, 'Gain 3 Block. Gain 1 Regeneration.', 'Blood Reaper'),
    makeCard('Sacrificial Barrier', 'Skill', 'Uncommon', 2, 'Gain 10 Block. Lose 5 HP.', 'Blood Reaper'),
    makeCard('Vital Surge', 'Skill', 'Rare', 0, 'Gain Block equal to your missing HP. Exhaust.', 'Blood Reaper', true),
    // Others
    makeCard('Life Tap', 'Skill', 'Common', 0, 'Draw 2 cards. Lose 3 HP.', 'Blood Reaper'),
    makeCard('Rend Flesh', 'Skill', 'Uncommon', 1, 'Apply 5 Bleed to ALL enemies. Lose 2 HP.', 'Blood Reaper'),
    makeCard('Soul Siphon', 'Power', 'Uncommon', 2, 'At start of turn, gain 1 Energy and lose 1 HP.', 'Blood Reaper'),
    makeCard('Blood Pact', 'Power', 'Rare', 1, 'Whenever you lose HP from a card, gain 1 Strength.', 'Blood Reaper'),
];
// --- Shadowmancer Cards ---
exports.shadowmancerCards = [
    makeCard('Shadow Bolt', 'Attack', 'Common', 1, 'Deal 6 damage. Apply 1 Weak.', 'Shadowmancer'),
    makeCard('Grasp of Despair', 'Attack', 'Common', 1, 'Deal 4 damage. Apply 1 Vulnerable.', 'Shadowmancer'),
    makeCard('Nightmare Lash', 'Attack', 'Uncommon', 2, 'Deal 7 damage. If enemy has Weak, deal 7 more.', 'Shadowmancer'),
    makeCard('Eternal Darkness', 'Attack', 'Rare', 2, 'Deal 3 damage to a random enemy 3 times.', 'Shadowmancer'),
    makeCard('Cloak of Shadows', 'Skill', 'Common', 1, 'Gain 5 Block. Draw 1 card.', 'Shadowmancer'),
    makeCard('Haunting Mist', 'Skill', 'Common', 1, 'Gain 7 Block. Apply 1 Weak to ALL enemies.', 'Shadowmancer'),
    makeCard('Phantom Armor', 'Skill', 'Uncommon', 1, 'Gain 8 Block. Next turn, gain 2 Dexterity.', 'Shadowmancer'),
    makeCard('Whispering Shroud', 'Skill', 'Rare', 1, 'Gain 4 Block. Next turn, gain 4 Block.', 'Shadowmancer'),
    makeCard('Shadowform', 'Skill', 'Uncommon', 2, 'Gain 2 Intangible. Exhaust.', 'Shadowmancer', true),
    makeCard('Curse of Misfortune', 'Skill', 'Common', 1, 'Enemy loses 3 Strength this turn. Apply 1 Vulnerable.', 'Shadowmancer'),
    makeCard('Dark Ritual', 'Skill', 'Uncommon', 0, 'Gain 2 Energy. Shuffle a Dazed into your discard pile.', 'Shadowmancer'),
    makeCard('Umbral Mastery', 'Power', 'Rare', 2, 'At start of turn, apply 1 Weak to ALL enemies.', 'Shadowmancer'),
];
// --- Hellforged Cards ---
exports.hellforgedCards = [
    makeCard('Firebolt', 'Attack', 'Common', 1, 'Deal 7 damage. Apply 2 Burn.', 'Hellforged'),
    makeCard('Hellfire Blast', 'Attack', 'Uncommon', 2, 'Deal 9 damage to ALL enemies.', 'Hellforged'),
    makeCard('Searing Strike', 'Attack', 'Common', 1, 'Deal 6 damage. Gain 1 Energy next turn.', 'Hellforged'),
    makeCard('Immolate', 'Attack', 'Rare', 2, 'Deal 12 damage. Exhaust.', 'Hellforged', true),
    makeCard('Obsidian Skin', 'Skill', 'Common', 1, 'Gain 7 Block.', 'Hellforged'),
    makeCard('Flame Ward', 'Skill', 'Common', 1, 'Gain 5 Block. Apply 2 Burn to a random enemy.', 'Hellforged'),
    makeCard('Demonic Resilience', 'Skill', 'Uncommon', 1, 'Gain 4 Block. Heal 2 HP.', 'Hellforged'),
    makeCard('Brimstone Shield', 'Skill', 'Rare', 1, 'Gain 3 Block. Gain 2 Strength this turn.', 'Hellforged'),
    makeCard('Summon Imp', 'Skill', 'Common', 1, 'Add an Imp Claw to your hand. Exhaust.', 'Hellforged', true),
    makeCard('Infernal Pact', 'Skill', 'Uncommon', 0, 'Lose 2 HP. Gain 2 Energy. Add an Imp Claw to your hand.', 'Hellforged'),
    makeCard('Hellfire Aura', 'Power', 'Rare', 2, 'At start of turn, deal 3 damage to ALL enemies.', 'Hellforged'),
    makeCard('Soul Burn', 'Skill', 'Uncommon', 1, 'Apply 8 Burn to an enemy. Draw 1 card.', 'Hellforged'),
];
// Colorless cards
exports.colorlessCards = [
    makeCard('Flash of Insight', 'Skill', 'Common', 1, 'Draw 2 cards.'),
    makeCard('Iron Will', 'Skill', 'Common', 1, 'Gain 8 Block.'),
    makeCard('Quick Slash', 'Attack', 'Common', 1, 'Deal 7 damage.'),
    makeCard('Potent Brew', 'Skill', 'Uncommon', 0, 'Gain 1 Energy. Exhaust.', undefined, true),
];
// Curses
exports.WOUND = makeCard('Wound', 'Curse', 'Common', 0, 'Unplayable. Ethereal. Exhaust.', undefined, true, true);
exports.DOUBT = makeCard('Doubt', 'Curse', 'Common', 0, 'Unplayable. At end of turn, lose 1 HP.');
exports.DAZED = makeCard('Dazed', 'Status', 'Common', 0, 'Unplayable. Ethereal. Exhaust.', undefined, true, true);
function getCardPool(cls) {
    switch (cls) {
        case 'Blood Reaper': return exports.bloodReaperCards;
        case 'Shadowmancer': return exports.shadowmancerCards;
        case 'Hellforged': return exports.hellforgedCards;
    }
}
function getClassStartingDeck(cls) {
    const deck = [];
    for (let i = 0; i < 5; i++)
        deck.push(makeCard('Strike', 'Attack', 'Basic', 1, 'Deal 6 damage.'));
    for (let i = 0; i < 4; i++)
        deck.push(makeCard('Defend', 'Skill', 'Basic', 1, 'Gain 5 Block.'));
    switch (cls) {
        case 'Blood Reaper':
            deck.push(makeCard('Sanguine Strike', 'Attack', 'Common', 1, 'Deal 8 damage. Heal 2 HP.', 'Blood Reaper'));
            break;
        case 'Shadowmancer':
            deck.push(makeCard('Shadow Bolt', 'Attack', 'Common', 1, 'Deal 6 damage. Apply 1 Weak.', 'Shadowmancer'));
            break;
        case 'Hellforged':
            deck.push(makeCard('Firebolt', 'Attack', 'Common', 1, 'Deal 7 damage. Apply 2 Burn.', 'Hellforged'));
            break;
    }
    return deck;
}
function getUpgradeVersion(card) {
    const upgrades = {
        'Strike': 'Deal 8 damage.',
        'Defend': 'Gain 8 Block.',
        'Sanguine Strike': 'Deal 10 damage. Heal 3 HP.',
        'Hemorrhage': 'Deal 6 damage. Apply 4 Bleed.',
        'Bloodletting': 'Lose 2 HP. Deal 15 damage.',
        'Crimson Eruption': 'Deal 10 damage to ALL enemies. Lose 2 HP.',
        'Crimson Shield': 'Gain 8 Block. Lose 2 HP.',
        'Blood Ward': 'Gain 4 Block. Gain 2 Regeneration.',
        'Sacrificial Barrier': 'Gain 13 Block. Lose 5 HP.',
        'Vital Surge': 'Gain Block equal to missing HP + 3. Exhaust.',
        'Life Tap': 'Draw 3 cards. Lose 3 HP.',
        'Rend Flesh': 'Apply 7 Bleed to ALL enemies. Lose 2 HP.',
        'Soul Siphon': 'Cost 1. At start of turn, gain 1 Energy and lose 1 HP.',
        'Blood Pact': 'Whenever you lose HP from a card, gain 2 Strength.',
        'Shadow Bolt': 'Deal 8 damage. Apply 2 Weak.',
        'Grasp of Despair': 'Deal 5 damage. Apply 2 Vulnerable.',
        'Nightmare Lash': 'Deal 9 damage. If Weak, deal 9 more.',
        'Eternal Darkness': 'Deal 4 damage to a random enemy 3 times.',
        'Cloak of Shadows': 'Gain 7 Block. Draw 1 card.',
        'Haunting Mist': 'Gain 9 Block. Apply 1 Weak to ALL enemies.',
        'Phantom Armor': 'Gain 10 Block. Next turn, gain 3 Dexterity.',
        'Whispering Shroud': 'Gain 5 Block. Next turn, gain 5 Block.',
        'Shadowform': 'Gain 3 Intangible. Exhaust.',
        'Curse of Misfortune': 'Enemy loses 4 Strength this turn. Apply 2 Vulnerable.',
        'Dark Ritual': 'Gain 2 Energy. Shuffle a Dazed into your discard pile.',
        'Umbral Mastery': 'At start of turn, apply 2 Weak to ALL enemies.',
        'Firebolt': 'Deal 9 damage. Apply 3 Burn.',
        'Hellfire Blast': 'Deal 12 damage to ALL enemies.',
        'Searing Strike': 'Deal 8 damage. Gain 1 Energy next turn.',
        'Immolate': 'Deal 16 damage. Exhaust.',
        'Obsidian Skin': 'Gain 10 Block.',
        'Flame Ward': 'Gain 6 Block. Apply 3 Burn to a random enemy.',
        'Demonic Resilience': 'Gain 5 Block. Heal 3 HP.',
        'Brimstone Shield': 'Gain 4 Block. Gain 3 Strength this turn.',
        'Summon Imp': 'Add an Imp Claw+ to your hand. Exhaust.',
        'Infernal Pact': 'Lose 1 HP. Gain 2 Energy. Add an Imp Claw+ to your hand.',
        'Hellfire Aura': 'At start of turn, deal 5 damage to ALL enemies.',
        'Soul Burn': 'Apply 10 Burn to an enemy. Draw 1 card.',
        'Flash of Insight': 'Draw 3 cards.',
        'Iron Will': 'Gain 11 Block.',
        'Quick Slash': 'Deal 9 damage.',
        'Potent Brew': 'Gain 2 Energy. Exhaust.',
    };
    return upgrades[card.name] || card.description;
}
// Imp Claw token
function createImpClaw(upgraded = false) {
    if (upgraded) {
        return makeCard('Imp Claw+', 'Attack', 'Common', 0, 'Deal 6 damage.', undefined, true);
    }
    return makeCard('Imp Claw', 'Attack', 'Common', 0, 'Deal 4 damage.', undefined, true);
}
};

// ── module: src/data.ts ──
__mods["src/data.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.act3Floors = exports.act2Floors = exports.act1Floors = exports.eventPool = exports.potionPool = exports.bossRelics = exports.relicPool = exports.act3Enemies = exports.act2Enemies = exports.act1Enemies = void 0;
exports.getEnemyData = getEnemyData;
// --- ACT 1 ENEMIES ---
exports.act1Enemies = {
    skeleton: {
        baseEnemy: { name: 'Skeleton Warrior', maxHp: 22, hp: 22, sprite: 'skeleton', act: 1 },
        intentPattern: [
            { type: 'attack', damage: 6, text: 'Attack 6' },
            { type: 'attack', damage: 6, text: 'Attack 6' },
            { type: 'defend', block: 8, text: 'Defend 8' },
        ],
    },
    zombie: {
        baseEnemy: { name: 'Zombie Brute', maxHp: 28, hp: 28, sprite: 'zombie', act: 1 },
        intentPattern: [
            { type: 'attack', damage: 8, text: 'Attack 8' },
            { type: 'debuff', damage: 0, text: 'Apply 2 Weak', applyDebuffs: [{ type: 'weak', amount: 2, target: 'player' }] },
            { type: 'attack', damage: 8, text: 'Attack 8' },
        ],
    },
    cultist: {
        baseEnemy: { name: 'Dark Cultist', maxHp: 20, hp: 20, sprite: 'cultist', act: 1 },
        intentPattern: [
            { type: 'buff', text: 'Buff +3 Str', buffSelf: [{ type: 'strength', amount: 3 }] },
            { type: 'attack', damage: 5, text: 'Attack 5' },
            { type: 'attack', damage: 5, text: 'Attack 5' },
        ],
    },
    ratSwarm: {
        baseEnemy: { name: 'Rat Swarm', maxHp: 15, hp: 15, sprite: 'rat', act: 1 },
        intentPattern: [
            { type: 'multiHit', damage: 3, hits: 2, text: '2x3 Attack' },
            { type: 'multiHit', damage: 3, hits: 2, text: '2x3 Attack' },
        ],
    },
    tombGuardian: {
        baseEnemy: { name: 'Tomb Guardian', maxHp: 42, hp: 42, sprite: 'tombGuardian', act: 1 },
        intentPattern: [
            { type: 'attack', damage: 6, block: 8, text: 'Shield Bash 6+8B' },
            { type: 'multiHit', damage: 4, hits: 3, text: '3x4 Flurry' },
            { type: 'attack', damage: 12, text: 'Heavy Slam 12' },
        ],
    },
    bloodWitch: {
        baseEnemy: { name: 'Blood Witch', maxHp: 36, hp: 36, sprite: 'bloodWitch', act: 1 },
        intentPattern: [
            { type: 'drain', damage: 5, heal: 4, text: 'Drain 5, Heal 4' },
            { type: 'debuff', text: 'Curse (1V+1W)', applyDebuffs: [{ type: 'vulnerable', amount: 1, target: 'player' }, { type: 'weak', amount: 1, target: 'player' }] },
            { type: 'drain', damage: 7, heal: 4, text: 'Ritual 7, Heal 4' },
        ],
    },
    lichKing: {
        baseEnemy: { name: 'Lich King', maxHp: 65, hp: 65, sprite: 'lichKing', act: 1 },
        intentPattern: [
            { type: 'summon', text: 'Summon Skeleton', summonEnemy: 'skeleton' },
            { type: 'attack', damage: 8, text: 'Attack 8' },
            { type: 'defend', block: 12, text: 'Defend 12' },
        ],
    },
    shadowThief: {
        baseEnemy: { name: 'Shadow Thief', maxHp: 30, hp: 30, sprite: 'shadowThief', act: 1 },
        intentPattern: [
            { type: 'attack', damage: 6, text: 'Attack 6' },
            { type: 'defend', block: 8, text: 'Defend 8' },
            { type: 'attack', damage: 8, text: 'Attack 8' },
        ],
    },
};
// --- ACT 2 ENEMIES ---
exports.act2Enemies = {
    wraith: {
        baseEnemy: { name: 'Wraith', maxHp: 30, hp: 30, sprite: 'wraith', act: 2 },
        intentPattern: [
            { type: 'attack', damage: 7, text: 'Attack 7+1W', applyDebuffs: [{ type: 'weak', amount: 1, target: 'player' }] },
            { type: 'attack', damage: 7, text: 'Attack 7' },
            { type: 'defend', block: 10, text: 'Defend 10' },
        ],
    },
    corruptedBeast: {
        baseEnemy: { name: 'Corrupted Beast', maxHp: 38, hp: 38, sprite: 'corruptedBeast', act: 2 },
        intentPattern: [
            { type: 'attack', damage: 12, text: 'Attack 12' },
            { type: 'defend', block: 5, text: 'Defend 5+1Vuln', applyDebuffs: [{ type: 'vulnerable', amount: 1, target: 'enemy' }] },
            { type: 'attack', damage: 12, text: 'Attack 12' },
        ],
    },
    thornedHorror: {
        baseEnemy: { name: 'Thorned Horror', maxHp: 25, hp: 25, sprite: 'thornedHorror', act: 2 },
        intentPattern: [
            { type: 'multiHit', damage: 4, hits: 3, text: '3x4 Attack' },
            { type: 'debuff', text: 'Apply 3 Bleed', applyDebuffs: [{ type: 'bleed', amount: 3, target: 'player' }] },
            { type: 'attack', damage: 8, text: 'Attack 8' },
        ],
    },
    witchDoctor: {
        baseEnemy: { name: 'Witch Doctor', maxHp: 22, hp: 22, sprite: 'witchDoctor', act: 2 },
        intentPattern: [
            { type: 'buff', text: 'Buff 5B+2Regen', buffSelf: [{ type: 'regeneration', amount: 2 }], block: 5 },
            { type: 'attack', damage: 6, text: 'Attack 6+2Pois', applyDebuffs: [{ type: 'poison', amount: 2, target: 'player' }] },
            { type: 'attack', damage: 6, text: 'Attack 6' },
        ],
    },
    treantProtector: {
        baseEnemy: { name: 'Treant Protector', maxHp: 50, hp: 50, sprite: 'treantProtector', act: 2 },
        intentPattern: [
            { type: 'attack', damage: 9, block: 6, text: 'Attack 9+6B' },
            { type: 'debuff', text: 'Apply 2 Vuln', applyDebuffs: [{ type: 'vulnerable', amount: 2, target: 'player' }] },
            { type: 'multiHit', damage: 10, hits: 2, text: '2x10 Multi' },
        ],
    },
    plagueShaman: {
        baseEnemy: { name: 'Plague Shaman', maxHp: 42, hp: 42, sprite: 'plagueShaman', act: 2 },
        intentPattern: [
            { type: 'debuff', text: 'Apply 4 Poison', applyDebuffs: [{ type: 'poison', amount: 4, target: 'player' }] },
            { type: 'summon', text: 'Summon Plague Rat', summonEnemy: 'plagueRat' },
            { type: 'attack', damage: 8, text: 'Attack 8' },
        ],
    },
    plagueRat: {
        baseEnemy: { name: 'Plague Rat', maxHp: 18, hp: 18, sprite: 'rat', act: 2 },
        intentPattern: [
            { type: 'attack', damage: 5, text: 'Attack 5' },
        ],
    },
    eldritchTreant: {
        baseEnemy: { name: 'Eldritch Treant', maxHp: 75, hp: 75, sprite: 'eldritchTreant', act: 2 },
        intentPattern: [
            { type: 'attack', damage: 10, text: 'Attack 10+2W', applyDebuffs: [{ type: 'weak', amount: 2, target: 'player' }] },
            { type: 'defend', block: 15, text: 'Defend 15+2R', buffSelf: [{ type: 'regeneration', amount: 2 }] },
            { type: 'multiHit', damage: 8, hits: 2, text: '2x8 Multi' },
        ],
    },
};
// --- ACT 3 ENEMIES ---
exports.act3Enemies = {
    hellhound: {
        baseEnemy: { name: 'Hellhound', maxHp: 26, hp: 26, sprite: 'hellhound', act: 3 },
        intentPattern: [
            { type: 'attack', damage: 8, text: 'Attack 8+2Burn', applyDebuffs: [{ type: 'burn', amount: 2, target: 'player' }] },
            { type: 'attack', damage: 8, text: 'Attack 8' },
            { type: 'defend', block: 9, text: 'Defend 9' },
        ],
    },
    shadowDemon: {
        baseEnemy: { name: 'Shadow Demon', maxHp: 30, hp: 30, sprite: 'shadowDemon', act: 3 },
        intentPattern: [
            { type: 'attack', damage: 7, text: 'Attack 7+1W', applyDebuffs: [{ type: 'weak', amount: 1, target: 'player' }] },
            { type: 'attack', damage: 7, text: 'Attack 7' },
            { type: 'buff', text: '1 Intangible', buffSelf: [{ type: 'intangible', amount: 1 }] },
        ],
    },
    infernalImp: {
        baseEnemy: { name: 'Infernal Imp', maxHp: 18, hp: 18, sprite: 'infernalImp', act: 3 },
        intentPattern: [
            { type: 'attack', damage: 5, text: 'Attack 5' },
            { type: 'attack', damage: 5, text: 'Attack 5' },
            { type: 'summon', text: 'Summon Imp', summonEnemy: 'infernalImp' },
        ],
    },
    abyssalCultist: {
        baseEnemy: { name: 'Abyssal Cultist', maxHp: 24, hp: 24, sprite: 'cultist', act: 3 },
        intentPattern: [
            { type: 'buff', text: 'Buff +2 Str', buffSelf: [{ type: 'strength', amount: 2 }] },
            { type: 'attack', damage: 7, text: 'Attack 7' },
            { type: 'attack', damage: 7, text: 'Attack 7' },
        ],
    },
    flamespawn: {
        baseEnemy: { name: 'Flamespawn', maxHp: 48, hp: 48, sprite: 'flamespawn', act: 3 },
        intentPattern: [
            { type: 'attack', damage: 9, text: 'Atk 9+3Burn', applyDebuffs: [{ type: 'burn', amount: 3, target: 'player' }] },
            { type: 'defend', block: 10, text: 'Def 10+2Str', buffSelf: [{ type: 'strength', amount: 2 }] },
            { type: 'multiHit', damage: 8, hits: 2, text: '2x8 Multi' },
        ],
    },
    dreadKnight: {
        baseEnemy: { name: 'Dread Knight', maxHp: 55, hp: 55, sprite: 'dreadKnight', act: 3 },
        intentPattern: [
            { type: 'attack', damage: 12, text: 'Attack 12' },
            { type: 'debuff', text: 'Apply 3 Bleed', applyDebuffs: [{ type: 'bleed', amount: 3, target: 'player' }] },
            { type: 'defend', block: 12, text: 'Def 12+Heal 4' },
        ],
    },
    archdemon: {
        baseEnemy: { name: 'Archdemon', maxHp: 85, hp: 85, sprite: 'archdemon', act: 3 },
        intentPattern: [
            { type: 'summon', text: 'Summon Hellhound', summonEnemy: 'hellhound' },
            { type: 'attack', damage: 12, text: 'Atk 12+2Burn', applyDebuffs: [{ type: 'burn', amount: 2, target: 'player' }] },
            { type: 'defend', block: 15, text: 'Def 15+3Str', buffSelf: [{ type: 'strength', amount: 3 }] },
        ],
    },
};
// --- RELICS ---
exports.relicPool = [
    { id: 'bloodstone', name: 'Bloodstone', description: 'Lose HP from card → heal 1 HP.', rarity: 'Common' },
    { id: 'shadowCloak', name: 'Shadow Cloak', description: 'Start combat with 1 Intangible.', rarity: 'Rare' },
    { id: 'cursedAmulet', name: 'Cursed Amulet', description: 'Start combat: apply 1 Weak to ALL enemies.', rarity: 'Common' },
    { id: 'tomeDark', name: 'Tome of Dark Rituals', description: 'Play a Power card → draw 1 card.', rarity: 'Uncommon' },
    { id: 'bleedingEdge', name: 'Bleeding Edge', description: 'Start of turn: apply 1 Bleed to random enemy.', rarity: 'Common' },
    { id: 'soulEmber', name: 'Soul Ember', description: 'Apply Burn → deal 2 extra damage.', rarity: 'Rare' },
    { id: 'eternalHourglass', name: 'Eternal Hourglass', description: 'Start turn: if hand empty, gain 1 Energy.', rarity: 'Uncommon' },
    { id: 'ashenArmor', name: 'Ashen Armor', description: 'End of combat: heal 6 HP.', rarity: 'Common' },
    { id: 'greedyImp', name: 'Greedy Imp', description: 'Enemies drop 10 extra Gold.', rarity: 'Common' },
    { id: 'demonHorns', name: 'Demon Horns', description: 'Start combat: add Imp Claw to hand.', rarity: 'Uncommon', cls: 'Hellforged' },
    { id: 'sacrificialDagger', name: 'Sacrificial Dagger', description: 'First Attack costing HP each combat costs 1 less HP.', rarity: 'Uncommon', cls: 'Blood Reaper' },
    { id: 'vialOfShadows', name: 'Vial of Shadows', description: 'Upon pickup, remove a card from deck.', rarity: 'Rare' },
];
exports.bossRelics = [
    { id: 'cursedCrown', name: 'Cursed Crown', description: '+1 Energy/turn. Curses unremovable.', rarity: 'Boss' },
    { id: 'ectoplasm', name: 'Ectoplasm', description: '+1 Energy. Lose all Gold.', rarity: 'Boss' },
    { id: 'philosopherStone', name: "Philosopher's Stone", description: '+1 Energy. Enemies start with 1 Str.', rarity: 'Boss' },
    { id: 'blackBlood', name: 'Black Blood', description: 'Start combat: lose 10% max HP. Play Attack: heal 1 HP.', rarity: 'Boss' },
    { id: 'shadowAmulet', name: 'Shadow Amulet', description: 'Start turn: 1 Intangible. -15 max HP.', rarity: 'Boss' },
];
// --- POTIONS ---
exports.potionPool = [
    { id: 'bloodVial', name: 'Blood Vial', description: 'Heal 15 HP.' },
    { id: 'firebomb', name: 'Firebomb', description: 'Deal 10 damage to ALL enemies.' },
    { id: 'dexTonic', name: 'Dex Tonic', description: 'Gain 3 Dexterity for 1 turn.' },
    { id: 'energyElixir', name: 'Energy Elixir', description: 'Gain 2 Energy this turn.' },
    { id: 'cursedBrew', name: 'Cursed Brew', description: 'Apply 3 Vulnerable to all enemies.' },
    { id: 'bleedingFlask', name: 'Bleeding Flask', description: 'Apply 6 Bleed to an enemy.' },
    { id: 'impInBottle', name: 'Imp-in-a-Bottle', description: 'Add Imp Claw+ to hand.' },
    { id: 'smokeBomb', name: 'Smoke Bomb', description: 'Escape from combat, no rewards.' },
];
// --- EVENTS ---
exports.eventPool = [
    {
        id: 'altar', title: 'The Forgotten Altar',
        description: 'A blood-stained altar hums with power.',
        choices: [
            { text: 'Offer 15 HP → Obtain a random Rare relic.', outcomes: { hpLoss: 15, gainRelic: 'rare' } },
            { text: 'Pray → Heal 10 HP, gain Doubt.', outcomes: { hpGain: 10, gainCurse: true } },
            { text: 'Leave.', outcomes: {} },
        ],
    },
    {
        id: 'peddler', title: 'Shadowy Peddler',
        description: 'A hooded figure offers a trade.',
        choices: [
            { text: 'Trade a card → Remove one, gain 100 Gold.', outcomes: { goldGain: 100, removeCards: 1 } },
            { text: 'Buy wares → Lose 50g, get Potion + Common Relic.', outcomes: { goldLoss: 50, gainPotion: true, gainRelic: 'common' } },
            { text: 'Attack → Combat with Shadow Thief.', outcomes: { startCombat: true, enemyId: 'shadowThief' } },
        ],
    },
    {
        id: 'cursedSpring', title: 'Cursed Spring',
        description: 'A spring bubbles with dark water.',
        choices: [
            { text: 'Drink → Lose 10 HP, remove all Curses.', outcomes: { hpLoss: 10 } },
            { text: 'Bottle it → Obtain Cursed Brew potion.', outcomes: { gainPotion: true } },
            { text: 'Leave.', outcomes: {} },
        ],
    },
    {
        id: 'restlessDead', title: 'Restless Dead',
        description: 'Ghosts offer a bargain.',
        choices: [
            { text: 'Accept → Lose 20 HP, upgrade 2 random cards.', outcomes: { hpLoss: 20, upgradeCards: 2 } },
            { text: 'Refuse.', outcomes: {} },
        ],
    },
    {
        id: 'impGambler', title: 'Impish Gambler',
        description: 'A grinning imp challenges you.',
        choices: [
            { text: 'Bet 30g → 50% to win 60g or lose it.', outcomes: { gamble: true, goldLoss: 30, goldGain: 60 } },
            { text: 'Wager a card → Remove random card.', outcomes: { removeCards: 1 } },
            { text: 'Leave.', outcomes: {} },
        ],
    },
    {
        id: 'library', title: 'Ancient Library',
        description: 'Dusty tomes contain forgotten knowledge.',
        choices: [
            { text: 'Study → Bonus Energy next combat. Lose 5 HP.', outcomes: { hpLoss: 5, gainExtraEnergy: true } },
            { text: 'Forbidden page → Get a Power card, gain Doubt.', outcomes: { gainPowerCard: true, gainCurse: true } },
            { text: 'Leave.', outcomes: {} },
        ],
    },
];
exports.act1Floors = [
    { floor: 2, type: 'enemy', enemyIds: ['skeleton'], branch: { enemyIds: ['zombie'], label: 'Zombie Brute' } },
    { floor: 3, type: 'enemy', enemyIds: ['cultist'] },
    { floor: 4, type: 'event', enemyIds: [] },
    { floor: 5, type: 'shop', enemyIds: [] },
    { floor: 6, type: 'enemy', enemyIds: ['ratSwarm'] },
    { floor: 7, type: 'rest', enemyIds: [] },
    { floor: 8, type: 'enemy', enemyIds: ['skeleton', 'cultist'], branch: { enemyIds: ['zombie'], label: 'Zombie Brute' } },
    { floor: 9, type: 'elite', enemyIds: ['tombGuardian'], branch: { enemyIds: ['skeleton', 'ratSwarm'], label: 'Skeleton + Rat' } },
    { floor: 10, type: 'event', enemyIds: [] },
    { floor: 11, type: 'enemy', enemyIds: ['cultist', 'ratSwarm'] },
    { floor: 12, type: 'elite', enemyIds: ['bloodWitch'], branch: { enemyIds: ['zombie', 'skeleton'], label: 'Zombie + Skeleton' } },
    { floor: 13, type: 'enemy', enemyIds: ['cultist', 'cultist'] },
    { floor: 14, type: 'rest', enemyIds: [] },
    { floor: 15, type: 'boss', enemyIds: ['lichKing'] },
];
exports.act2Floors = [
    { floor: 2, type: 'enemy', enemyIds: ['wraith'], branch: { enemyIds: ['corruptedBeast'], label: 'Corrupted Beast' } },
    { floor: 3, type: 'enemy', enemyIds: ['thornedHorror'] },
    { floor: 4, type: 'event', enemyIds: [] },
    { floor: 5, type: 'shop', enemyIds: [] },
    { floor: 6, type: 'enemy', enemyIds: ['witchDoctor'] },
    { floor: 7, type: 'rest', enemyIds: [] },
    { floor: 8, type: 'enemy', enemyIds: ['wraith', 'thornedHorror'], branch: { enemyIds: ['corruptedBeast'], label: 'Corrupted Beast' } },
    { floor: 9, type: 'elite', enemyIds: ['treantProtector'], branch: { enemyIds: ['wraith', 'thornedHorror'], label: 'Wraith + Thorn' } },
    { floor: 10, type: 'event', enemyIds: [] },
    { floor: 11, type: 'enemy', enemyIds: ['corruptedBeast', 'witchDoctor'] },
    { floor: 12, type: 'elite', enemyIds: ['plagueShaman'], branch: { enemyIds: ['corruptedBeast', 'wraith'], label: 'Beast + Wraith' } },
    { floor: 13, type: 'enemy', enemyIds: ['witchDoctor', 'witchDoctor'] },
    { floor: 14, type: 'rest', enemyIds: [] },
    { floor: 15, type: 'boss', enemyIds: ['eldritchTreant'] },
];
exports.act3Floors = [
    { floor: 2, type: 'enemy', enemyIds: ['hellhound'], branch: { enemyIds: ['shadowDemon'], label: 'Shadow Demon' } },
    { floor: 3, type: 'enemy', enemyIds: ['abyssalCultist'] },
    { floor: 4, type: 'event', enemyIds: [] },
    { floor: 5, type: 'shop', enemyIds: [] },
    { floor: 6, type: 'enemy', enemyIds: ['infernalImp'] },
    { floor: 7, type: 'rest', enemyIds: [] },
    { floor: 8, type: 'enemy', enemyIds: ['hellhound', 'abyssalCultist'], branch: { enemyIds: ['infernalImp'], label: 'Infernal Imp' } },
    { floor: 9, type: 'elite', enemyIds: ['flamespawn'], branch: { enemyIds: ['hellhound', 'infernalImp'], label: 'Hellhound + Imp' } },
    { floor: 10, type: 'event', enemyIds: [] },
    { floor: 11, type: 'enemy', enemyIds: ['shadowDemon', 'abyssalCultist'] },
    { floor: 12, type: 'elite', enemyIds: ['dreadKnight'], branch: { enemyIds: ['hellhound', 'shadowDemon'], label: 'Hellhound + Demon' } },
    { floor: 13, type: 'enemy', enemyIds: ['abyssalCultist', 'abyssalCultist'] },
    { floor: 14, type: 'rest', enemyIds: [] },
    { floor: 15, type: 'boss', enemyIds: ['archdemon'] },
];
function getEnemyData(act, enemyId) {
    if (act === 1)
        return exports.act1Enemies[enemyId];
    if (act === 2)
        return exports.act2Enemies[enemyId];
    if (act === 3)
        return exports.act3Enemies[enemyId];
    return undefined;
}
};

// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInitialState = createInitialState;
exports.startRun = startRun;
exports.beginCombat = beginCombat;
exports.getBuffAmount = getBuffAmount;
exports.getDebuffAmount = getDebuffAmount;
exports.playCard = playCard;
exports.startPlayerTurn = startPlayerTurn;
exports.processEnemyTurn = processEnemyTurn;
exports.endPlayerTurn = endPlayerTurn;
const cards_1 = require("./cards");
const data_1 = require("./data");
let instanceCounter = 0;
function cardInstance(card) {
    return { ...card, instanceId: `inst_${++instanceCounter}` };
}
let nodeCounter = 0;
function nodeUid() { return `node_${++nodeCounter}`; }
// Deep copy helpers
function cloneCard(c) { return { ...c, id: `card_${++instanceCounter}` }; }
function cloneCardInHand(c) { return { ...c, instanceId: `inst_${++instanceCounter}` }; }
function shuffle(arr) {
    const a = [...arr];
    for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
}
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
function createInitialState() {
    return {
        screen: 'title',
        player: createEmptyPlayer('Blood Reaper'),
        act: 1,
        floor: 1,
        mapNodes: [],
        currentEnemy: null,
        enemies: [],
        turn: 0,
        isPlayerTurn: true,
        selectedCard: null,
        hoveredCard: null,
        combatLog: [],
        pendingRewards: { gold: 0, cardChoices: [] },
        shopItems: { cards: [], relics: [], potions: [] },
        shopRemoveAvailable: false,
        currentEvent: null,
        restAction: null,
        forgeCards: [],
        meditateCards: [],
        bossRelicChoices: [],
        animation: null,
        mapBranches: [],
    };
}
function createEmptyPlayer(cls) {
    return {
        cls: cls,
        maxHp: 70,
        hp: 70,
        block: 0,
        buffs: [],
        debuffs: [],
        drawPile: [],
        discardPile: [],
        hand: [],
        power: [],
        energy: 3,
        maxEnergy: 3,
        gold: 99,
        relics: [],
        potions: [],
        deck: [],
    };
}
function startRun(cls) {
    const deck = (0, cards_1.getClassStartingDeck)(cls);
    const p = {
        cls,
        maxHp: 70,
        hp: 70,
        block: 0,
        buffs: [],
        debuffs: [],
        drawPile: [],
        discardPile: [],
        hand: [],
        power: [],
        energy: 3,
        maxEnergy: 3,
        gold: 99,
        relics: [],
        potions: [],
        deck,
    };
    const mapNodes = generateMap(1);
    return {
        screen: 'map',
        player: p,
        act: 1,
        floor: 1,
        mapNodes,
        currentEnemy: null,
        enemies: [],
        turn: 0,
        isPlayerTurn: true,
        selectedCard: null,
        hoveredCard: null,
        combatLog: [],
        pendingRewards: { gold: 0, cardChoices: [] },
        shopItems: { cards: [], relics: [], potions: [] },
        shopRemoveAvailable: false,
        currentEvent: null,
        restAction: null,
        forgeCards: [],
        meditateCards: [],
        bossRelicChoices: [],
        animation: null,
        mapBranches: [],
    };
}
// Map generation
function generateMap(act) {
    const floorsData = act === 1 ? data_1.act1Floors : act === 2 ? data_1.act2Floors : data_1.act3Floors;
    const nodes = [];
    const branchMap = [];
    let prevNodeIds = ['start'];
    const cols = [300, 420, 540, 360]; // x positions for branching
    floorsData.forEach((fd, idx) => {
        const y = 680 - idx * 42;
        const node = {
            floor: fd.floor,
            type: fd.type,
            x: cols[idx % cols.length],
            y,
            completed: false,
            selected: false,
            enemyIds: fd.enemyIds,
            eventId: idx % data_1.eventPool.length,
            id: nodeUid(),
        };
        nodes.push(node);
        // Connect previous nodes to this one
        prevNodeIds.forEach(pid => {
            branchMap.push({ fromId: pid, toIds: [node.id] });
        });
        prevNodeIds = [node.id];
    });
    // Store branch map for later
    return nodes;
}
// Start combat
function beginCombat(state, enemyIds) {
    const enemies = [];
    const actEnemyPool = state.act === 1 ? data_1.act1Floors : state.act === 2 ? data_1.act2Floors : data_1.act3Floors;
    for (const eid of enemyIds) {
        const data = (0, data_1.getEnemyData)(state.act, eid);
        if (!data)
            continue;
        const isBoss = data.baseEnemy.maxHp === 65 || data.baseEnemy.maxHp === 75 || data.baseEnemy.maxHp === 85;
        enemies.push({
            id: eid,
            name: data.baseEnemy.name,
            maxHp: data.baseEnemy.maxHp,
            hp: data.baseEnemy.maxHp,
            block: 0,
            buffs: [],
            debuffs: [],
            intent: data.intentPattern[0],
            intentIndex: 0,
            intentPattern: data.intentPattern,
            isBoss,
            isMinion: false,
            sprite: data.baseEnemy.sprite || 'skeleton',
            act: state.act,
        });
    }
    // Init combat deck
    const drawPile = shuffle(enemies.reduce((acc, _e) => {
        const powerCards = state.player.deck.filter(c => c.type === 'Power');
        const nonPowerCards = state.player.deck.filter(c => c.type !== 'Power');
        return acc;
    }, []));
    const pDeck = state.player.deck;
    const powerCards = pDeck.filter(c => c.type === 'Power').map(cardInstance);
    const otherCards = pDeck.filter(c => c.type !== 'Power').map(cardInstance);
    const shuffled = shuffle(otherCards);
    const newState = {
        ...state,
        enemies,
        screen: 'combat',
        turn: 1,
        isPlayerTurn: true,
        player: {
            ...state.player,
            block: 0,
            energy: 3,
            maxEnergy: 3,
            drawPile: shuffled,
            discardPile: [],
            hand: [],
            power: powerCards,
        },
        combatLog: ['Combat begins!'],
        selectedCard: null,
        hoveredCard: null,
        animation: null,
    };
    // Relic effects at start of combat
    // Shadow Cloak
    if (newState.player.relics.find(r => r.id === 'shadowCloak')) {
        newState.player.buffs.push({ type: 'intangible', amount: 1, isDebuff: false, turnsRemaining: 1 });
    }
    // Cursed Amulet
    if (newState.player.relics.find(r => r.id === 'cursedAmulet')) {
        newState.enemies.forEach(e => addDebuff(e, 'weak', 1, 99));
    }
    // Demon Horns
    if (newState.player.relics.find(r => r.id === 'demonHorns')) {
        const imp = cardInstance((0, cards_1.createImpClaw)(false));
        newState.player.hand.push(imp);
    }
    // Philosopher's Stone
    if (newState.player.relics.find(r => r.id === 'philosopherStone')) {
        newState.enemies.forEach(e => addBuff(e, 'strength', 1));
    }
    // Black Blood
    if (newState.player.relics.find(r => r.id === 'blackBlood')) {
        const loss = Math.floor(newState.player.maxHp * 0.1);
        newState.player.hp = Math.max(1, newState.player.hp - loss);
    }
    // Shadow Amulet
    if (newState.player.relics.find(r => r.id === 'shadowAmulet')) {
        newState.player.maxHp -= 15;
    }
    // Draw starting hand
    newState.player.hand = drawCards(newState.player, 5);
    return newState;
}
function drawCards(player, count) {
    const drawn = [];
    for (let i = 0; i < count; i++) {
        if (player.drawPile.length === 0) {
            if (player.discardPile.length === 0)
                break;
            player.drawPile = shuffle(player.discardPile.filter(c => !c.exhaust));
            player.discardPile = [];
        }
        if (player.drawPile.length > 0) {
            drawn.push(player.drawPile.pop());
        }
    }
    return drawn;
}
function getBuffAmount(buffs, type) {
    return buffs.filter(b => b.type === type).reduce((s, b) => s + b.amount, 0);
}
function getDebuffAmount(debuffs, type) {
    return debuffs.filter(d => d.type === type).reduce((s, d) => s + d.amount, 0);
}
function addDebuff(enemy, type, amount, duration) {
    const existing = enemy.debuffs.find(d => d.type === type);
    if (existing) {
        existing.amount += amount;
        if (duration)
            existing.turnsRemaining = duration;
    }
    else {
        enemy.debuffs.push({ type, amount, isDebuff: true, turnsRemaining: duration });
    }
}
function addBuff(enemy, type, amount) {
    const existing = enemy.buffs.find(b => b.type === type);
    if (existing) {
        existing.amount += amount;
    }
    else {
        enemy.buffs.push({ type, amount, isDebuff: false, duration: 0 });
    }
}
// Apply card effects
function playCard(state, card, targetEnemyIdx) {
    if (card.cost > 0 && state.player.energy < card.cost) {
        return state;
    }
    const newState = { ...state };
    newState.player = { ...state.player, energy: state.player.energy - card.cost };
    newState.combatLog = [...newState.combatLog, `Played ${card.name}`];
    // Tome of Dark Rituals
    if (card.type === 'Power' && newState.player.relics.find(r => r.id === 'tomeDark')) {
        const drawn = drawCards(newState.player, 1);
        newState.player.hand.push(...drawn);
    }
    // Process card effects
    processCardEffect(newState, card, targetEnemyIdx);
    // Black Blood: heal when playing Attack
    if (card.type === 'Attack' && newState.player.relics.find(r => r.id === 'blackBlood')) {
        newState.player.hp = Math.min(newState.player.maxHp, newState.player.hp + 1);
    }
    // Exhaust check
    if (card.exhaust || card.ethereal) {
        const hIdx = newState.player.hand.findIndex(h => h.instanceId === card.instanceId);
        if (hIdx >= 0)
            newState.player.hand.splice(hIdx, 1);
        // Don't add to discard
    }
    else {
        const hIdx = newState.player.hand.findIndex(h => h.instanceId === card.instanceId);
        if (hIdx >= 0) {
            newState.player.hand.splice(hIdx, 1);
            newState.player.discardPile.push(card);
        }
    }
    // Check for enemy deaths
    checkEnemyDeaths(newState);
    return newState;
}
function processCardEffect(state, card, targetEnemyIdx) {
    const enemies = state.enemies;
    const target = targetEnemyIdx >= 0 && targetEnemyIdx < enemies.length ? enemies[targetEnemyIdx] : null;
    const name = card.name;
    const up = card.upgraded;
    const playerLostHP = (amount) => {
        const intangible = state.player.buffs.find(b => b.type === 'intangible' && b.turnsRemaining && b.turnsRemaining > 0);
        const actual = intangible ? 1 : amount;
        state.player.hp = Math.max(0, state.player.hp - actual);
        // Bloodstone
        if (state.player.relics.find(r => r.id === 'bloodstone')) {
            state.player.hp = Math.min(state.player.maxHp, state.player.hp + 1);
        }
        // Blood Pact
        const bp = state.player.deck.find(c => c.name === 'Blood Pact');
        if (bp) {
            const bpUp = bp.upgraded;
            state.player.buffs.push({ type: 'strength', amount: bpUp ? 2 : 1, isDebuff: false, duration: 0 });
        }
    };
    // === ATTACKS ===
    const dealDamage = (enemy, dmg) => {
        const weakAmt = getDebuffAmount(enemy.debuffs, 'weak');
        const vulnAmt = getDebuffAmount(enemy.debuffs, 'vulnerable');
        let finalDmg = dmg;
        if (vulnAmt > 0)
            finalDmg = Math.floor(finalDmg * Math.pow(1.5, vulnAmt));
        // Soul Ember
        if (state.player.relics.find(r => r.id === 'soulEmber') && card.name.toLowerCase().includes('burn')) {
            finalDmg += 2;
        }
        const blocked = Math.min(finalDmg, enemy.block);
        enemy.block -= blocked;
        enemy.hp = Math.max(0, enemy.hp - (finalDmg - blocked));
        state.combatLog.push(`${enemy.name} takes ${finalDmg - (enemy.block < 0 ? 0 : blocked)} dmg`);
        enemy.block = Math.max(0, enemy.block);
    };
    const applyBleed = (enemy, amt) => {
        addDebuff(enemy, 'bleed', amt, 0);
    };
    const applyBurn = (enemy, amt) => {
        addDebuff(enemy, 'burn', amt, 0);
        // Soul Ember
        if (state.player.relics.find(r => r.id === 'soulEmber')) {
            dealDamage(enemy, 2);
        }
    };
    const applyWeak = (enemy, amt) => addDebuff(enemy, 'weak', amt, 99);
    const applyVulnerable = (enemy, amt) => addDebuff(enemy, 'vulnerable', amt, 99);
    const targetOrFirst = target || (enemies.length > 0 ? enemies[0] : null);
    if (name === 'Strike') {
        if (!targetOrFirst)
            return;
        const dmg = up ? 8 : 6;
        dealDamage(targetOrFirst, dmg);
    }
    else if (name === 'Defend') {
        state.player.block += up ? 8 : 5;
    }
    else if (name === 'Sanguine Strike') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 10 : 8);
        state.player.hp = Math.min(state.player.maxHp, state.player.hp + (up ? 3 : 2));
    }
    else if (name === 'Hemorrhage') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 6 : 5);
        applyBleed(targetOrFirst, up ? 4 : 3);
    }
    else if (name === 'Bloodletting') {
        if (!targetOrFirst)
            return;
        playerLostHP(up ? 2 : 3);
        dealDamage(targetOrFirst, up ? 15 : 12);
    }
    else if (name === 'Crimson Eruption') {
        playerLostHP(2);
        enemies.forEach(e => dealDamage(e, up ? 10 : 8));
    }
    else if (name === 'Crimson Shield') {
        state.player.block += up ? 8 : 6;
        playerLostHP(2);
    }
    else if (name === 'Blood Ward') {
        state.player.block += up ? 4 : 3;
        state.player.buffs.push({ type: 'regeneration', amount: up ? 2 : 1, isDebuff: false, duration: 0 });
    }
    else if (name === 'Sacrificial Barrier') {
        state.player.block += up ? 13 : 10;
        playerLostHP(5);
    }
    else if (name === 'Vital Surge') {
        const missing = state.player.maxHp - state.player.hp;
        state.player.block += missing + (up ? 3 : 0);
    }
    else if (name === 'Life Tap') {
        const drawn = drawCards(state.player, up ? 3 : 2);
        state.player.hand.push(...drawn);
        playerLostHP(3);
    }
    else if (name === 'Rend Flesh') {
        playerLostHP(2);
        enemies.forEach(e => applyBleed(e, up ? 7 : 5));
    }
    else if (name === 'Soul Siphon') {
        // Power card - already placed, effect happens at start of turn
    }
    else if (name === 'Blood Pact') {
        // Power card
    }
    else if (name === 'Shadow Bolt') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 8 : 6);
        applyWeak(targetOrFirst, up ? 2 : 1);
    }
    else if (name === 'Grasp of Despair') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 5 : 4);
        applyVulnerable(targetOrFirst, up ? 2 : 1);
    }
    else if (name === 'Nightmare Lash') {
        if (!targetOrFirst)
            return;
        let dmg = up ? 9 : 7;
        const hasWeak = getDebuffAmount(targetOrFirst.debuffs, 'weak') > 0;
        if (hasWeak)
            dmg += up ? 9 : 7;
        dealDamage(targetOrFirst, dmg);
    }
    else if (name === 'Eternal Darkness') {
        const dmg = up ? 4 : 3;
        for (let i = 0; i < 3; i++) {
            const t = pick(enemies);
            if (t)
                dealDamage(t, dmg);
        }
    }
    else if (name === 'Cloak of Shadows') {
        state.player.block += up ? 7 : 5;
        const drawn = drawCards(state.player, 1);
        state.player.hand.push(...drawn);
    }
    else if (name === 'Haunting Mist') {
        state.player.block += up ? 9 : 7;
        enemies.forEach(e => applyWeak(e, 1));
    }
    else if (name === 'Phantom Armor') {
        state.player.block += up ? 10 : 8;
        state.player.buffs.push({ type: 'dexterity', amount: up ? 3 : 2, isDebuff: false, turnsRemaining: 1 });
    }
    else if (name === 'Whispering Shroud') {
        state.player.block += up ? 5 : 4;
        state.player.buffs.push({ type: 'block_next', amount: up ? 5 : 4, isDebuff: false, turnsRemaining: 1 });
    }
    else if (name === 'Shadowform') {
        state.player.buffs.push({ type: 'intangible', amount: up ? 3 : 2, isDebuff: false, turnsRemaining: up ? 3 : 2 });
    }
    else if (name === 'Curse of Misfortune') {
        if (!targetOrFirst)
            return;
        applyVulnerable(targetOrFirst, up ? 2 : 1);
        // Temp strength loss this turn - apply as debuff with 1 duration
        const tmpDebuff = targetOrFirst.debuffs.find(d => d.type === 'weak' && d.turnsRemaining !== undefined && d.turnsRemaining <= 1);
        if (tmpDebuff)
            tmpDebuff.amount += up ? 4 : 3;
        else
            targetOrFirst.debuffs.push({ type: 'weak', amount: up ? 4 : 3, isDebuff: true, turnsRemaining: 1 });
    }
    else if (name === 'Dark Ritual') {
        state.player.energy += 2;
        state.player.discardPile.push(cardInstance(cards_1.DAZED));
    }
    else if (name === 'Umbral Mastery') {
        // Power - handled at turn start
    }
    else if (name === 'Firebolt') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 9 : 7);
        applyBurn(targetOrFirst, up ? 3 : 2);
    }
    else if (name === 'Hellfire Blast') {
        enemies.forEach(e => dealDamage(e, up ? 12 : 9));
    }
    else if (name === 'Searing Strike') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 8 : 6);
        state.player.buffs.push({ type: 'energy_next', amount: 1, isDebuff: false, turnsRemaining: 1 });
    }
    else if (name === 'Immolate') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 16 : 12);
    }
    else if (name === 'Obsidian Skin') {
        state.player.block += up ? 10 : 7;
    }
    else if (name === 'Flame Ward') {
        state.player.block += up ? 6 : 5;
        const t = pick(enemies);
        if (t)
            applyBurn(t, up ? 3 : 2);
    }
    else if (name === 'Demonic Resilience') {
        state.player.block += up ? 5 : 4;
        state.player.hp = Math.min(state.player.maxHp, state.player.hp + (up ? 3 : 2));
    }
    else if (name === 'Brimstone Shield') {
        state.player.block += up ? 4 : 3;
        state.player.buffs.push({ type: 'strength', amount: up ? 3 : 2, isDebuff: false, turnsRemaining: 1 });
    }
    else if (name === 'Summon Imp') {
        const imp = cardInstance((0, cards_1.createImpClaw)(up));
        state.player.hand.push(imp);
    }
    else if (name === 'Infernal Pact') {
        playerLostHP(up ? 1 : 2);
        state.player.energy += 2;
        const imp = cardInstance((0, cards_1.createImpClaw)(up));
        state.player.hand.push(imp);
    }
    else if (name === 'Hellfire Aura') {
        // Power - handled at turn start
    }
    else if (name === 'Soul Burn') {
        if (!targetOrFirst)
            return;
        applyBurn(targetOrFirst, up ? 10 : 8);
        const drawn = drawCards(state.player, 1);
        state.player.hand.push(...drawn);
    }
    else if (name === 'Flash of Insight') {
        const drawn = drawCards(state.player, up ? 3 : 2);
        state.player.hand.push(...drawn);
    }
    else if (name === 'Iron Will') {
        state.player.block += up ? 11 : 8;
    }
    else if (name === 'Quick Slash') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 9 : 7);
    }
    else if (name === 'Potent Brew') {
        state.player.energy += up ? 2 : 1;
    }
    else if (name === 'Imp Claw' || name === 'Imp Claw+') {
        if (!targetOrFirst)
            return;
        dealDamage(targetOrFirst, up ? 6 : 4);
    }
}
function checkEnemyDeaths(state) {
    const alive = state.enemies.filter(e => e.hp > 0);
    const dead = state.enemies.filter(e => e.hp <= 0);
    dead.forEach(e => state.combatLog.push(`${e.name} is defeated!`));
    state.enemies = alive;
}
// Start player turn
function startPlayerTurn(state) {
    const ns = { ...state };
    ns.turn++;
    ns.isPlayerTurn = true;
    ns.player = { ...state.player, block: 0 };
    // Draw 5 cards
    const drawn = drawCards(ns.player, 5);
    ns.player.hand.push(...drawn);
    // Energy
    let energy = 3;
    // Cursed Crown
    if (ns.player.relics.find(r => r.id === 'cursedCrown'))
        energy++;
    // Philosopher's Stone
    if (ns.player.relics.find(r => r.id === 'philosopherStone'))
        energy++;
    // Energy next turn buff
    const energyNext = ns.player.buffs.filter(b => b.type === 'energy_next');
    energyNext.forEach(b => energy += b.amount);
    ns.player.energy = energy;
    // Shadow Amulet intangible
    if (ns.player.relics.find(r => r.id === 'shadowAmulet')) {
        ns.player.buffs.push({ type: 'intangible', amount: 1, isDebuff: false, turnsRemaining: 1 });
    }
    // Process power cards
    // Soul Siphon
    if (ns.player.power.find(p => p.name === 'Soul Siphon')) {
        const cost = ns.player.power.find(p => p.name === 'Soul Siphon').upgraded ? 1 : 2;
        ns.player.energy += 1;
        // Lose 1 HP (simplified)
        const intangible = ns.player.buffs.find(b => b.type === 'intangible');
        ns.player.hp = Math.max(0, ns.player.hp - (intangible ? 1 : 1));
    }
    // Umbral Mastery
    if (ns.player.power.find(p => p.name === 'Umbral Mastery')) {
        const amt = ns.player.power.find(p => p.name === 'Umbral Mastery').upgraded ? 2 : 1;
        ns.enemies.forEach(e => {
            const ex = e.debuffs.find(d => d.type === 'weak');
            if (ex)
                ex.amount += amt;
            else
                e.debuffs.push({ type: 'weak', amount: amt, isDebuff: true, turnsRemaining: 99 });
        });
    }
    // Hellfire Aura
    if (ns.player.power.find(p => p.name === 'Hellfire Aura')) {
        const dmg = ns.player.power.find(p => p.name === 'Hellfire Aura').upgraded ? 5 : 3;
        ns.enemies.forEach(e => {
            const blocked = Math.min(dmg, e.block);
            e.block -= blocked;
            e.hp = Math.max(0, e.hp - (dmg - blocked));
        });
    }
    // Bleeding Edge relic
    if (ns.player.relics.find(r => r.id === 'bleedingEdge')) {
        const target = pick(ns.enemies);
        if (target) {
            const ex = target.debuffs.find(d => d.type === 'bleed');
            if (ex)
                ex.amount += 1;
            else
                target.debuffs.push({ type: 'bleed', amount: 1, isDebuff: true, turnsRemaining: 0 });
        }
    }
    // Process debuffs on player at start of turn
    // Bleed
    const bleed = ns.player.debuffs.find(d => d.type === 'bleed');
    if (bleed && bleed.amount > 0) {
        ns.player.hp = Math.max(0, ns.player.hp - bleed.amount);
        bleed.amount = Math.floor(bleed.amount / 2);
    }
    // Poison
    const poison = ns.player.debuffs.find(d => d.type === 'poison');
    if (poison && poison.amount > 0) {
        ns.player.hp = Math.max(0, ns.player.hp - poison.amount);
        poison.amount = Math.max(0, poison.amount - 1);
    }
    // Process burn at end of turn on player
    const burn = ns.player.debuffs.find(d => d.type === 'burn');
    if (burn && burn.amount > 0) {
        ns.player.hp = Math.max(0, ns.player.hp - burn.amount);
        burn.amount = Math.floor(burn.amount / 2);
    }
    // Regen
    const regen = ns.player.buffs.find(b => b.type === 'regeneration');
    if (regen && regen.amount > 0) {
        ns.player.hp = Math.min(ns.player.maxHp, ns.player.hp + regen.amount);
        regen.amount = Math.max(0, regen.amount - 1);
    }
    // Decrement duration buffs/debuffs
    ns.player.buffs.forEach(b => {
        if (b.turnsRemaining !== undefined && b.turnsRemaining > 0) {
            if (b.type === 'intangible') {
                b.turnsRemaining--;
            }
            else if (b.type !== 'regeneration' && b.type !== 'energy_next' && b.type !== 'block_next') {
                b.turnsRemaining--;
            }
        }
    });
    ns.player.debuffs.forEach(d => {
        if (d.turnsRemaining !== undefined && d.turnsRemaining > 0) {
            d.turnsRemaining--;
        }
    });
    // Remove expired
    ns.player.buffs = ns.player.buffs.filter(b => {
        if (b.type === 'regeneration')
            return b.amount > 0;
        if (b.type === 'intangible')
            return b.turnsRemaining !== undefined && b.turnsRemaining > 0;
        if (b.type === 'energy_next' || b.type === 'block_next')
            return false; // consumed
        if (b.type === 'dexterity')
            return b.turnsRemaining !== undefined && b.turnsRemaining > 0;
        return true;
    });
    ns.player.debuffs = ns.player.debuffs.filter(d => !(d.turnsRemaining !== undefined && d.turnsRemaining <= 0 && d.type !== 'bleed' && d.type !== 'burn'));
    // Keep bleed/burn until 0 amount
    ns.player.debuffs = ns.player.debuffs.filter(d => {
        if ((d.type === 'bleed' || d.type === 'burn'))
            return d.amount > 0;
        return true;
    });
    // Block next turn
    const blockNext = ns.player.buffs.find(b => b.type === 'block_next');
    if (blockNext) {
        ns.player.block += blockNext.amount;
    }
    // Eternal Hourglass
    if (ns.player.relics.find(r => r.id === 'eternalHourglass') && ns.player.hand.length === 0) {
        ns.player.energy += 1;
    }
    // Damped Turn (if applicable) - skipped for now
    checkEnemyDeaths(ns);
    return ns;
}
// Process enemy turn
function processEnemyTurn(state) {
    const ns = { ...state };
    ns.isPlayerTurn = false;
    for (const enemy of ns.enemies) {
        const intent = enemy.intent;
        // Process enemy buffs/debuffs at start of their turn
        // Bleed/Burn on enemy
        const eBleed = enemy.debuffs.find(d => d.type === 'bleed');
        if (eBleed && eBleed.amount > 0) {
            enemy.hp = Math.max(0, enemy.hp - eBleed.amount);
            eBleed.amount = Math.floor(eBleed.amount / 2);
        }
        const eBurn = enemy.debuffs.find(d => d.type === 'burn');
        if (eBurn && eBurn.amount > 0) {
            enemy.hp = Math.max(0, enemy.hp - eBurn.amount);
            eBurn.amount = Math.floor(eBurn.amount / 2);
        }
        // Regen
        const eRegen = enemy.buffs.find(b => b.type === 'regeneration');
        if (eRegen && eRegen.amount > 0) {
            enemy.hp = Math.min(enemy.maxHp, enemy.hp + eRegen.amount);
            eRegen.amount = Math.max(0, eRegen.amount - 1);
        }
        // Decrement debuff durations
        enemy.debuffs.forEach(d => {
            if (d.turnsRemaining !== undefined && d.turnsRemaining > 0 && d.type !== 'bleed' && d.type !== 'burn') {
                d.turnsRemaining--;
            }
        });
        enemy.debuffs = enemy.debuffs.filter(d => {
            if ((d.type === 'bleed' || d.type === 'burn'))
                return d.amount > 0;
            if (d.turnsRemaining !== undefined && d.turnsRemaining <= 0)
                return false;
            return true;
        });
        enemy.block = 0;
        // Boss phase handling
        if (enemy.isBoss) {
            if (enemy.name === 'Lich King') {
                if (enemy.hp <= 30) {
                    // Phase 2
                    enemy.buffs.push({ type: 'strength', amount: 2, isDebuff: false, turnsRemaining: 1 });
                    // Use phase 2 pattern
                    const p2Pattern = [
                        { type: 'attack', damage: 10, text: 'Attack 10' },
                        { type: 'attack', damage: 10, text: 'Attack 10' },
                        { type: 'defend', block: 15, text: 'Defend 15' },
                    ];
                    enemy.intentPattern = p2Pattern;
                }
                else {
                    // Phase 1: check minion count for summon
                    if (intent.type === 'summon') {
                        const minionCount = ns.enemies.filter(e => e.isMinion).length;
                        if (minionCount >= 2) {
                            // Skip summon, attack instead
                            intent.damage = 8;
                            intent.type = 'attack';
                        }
                    }
                }
            }
            else if (enemy.name === 'Eldritch Treant') {
                if (enemy.hp <= 40) {
                    const p2Pattern = [
                        { type: 'attack', damage: 14, text: 'Attack 14+1V', applyDebuffs: [{ type: 'vulnerable', amount: 1, target: 'player' }] },
                        { type: 'defend', block: 20, text: 'Defend 20+Heal 5' },
                        { type: 'attack', damage: 14, text: 'Attack 14+1V', applyDebuffs: [{ type: 'vulnerable', amount: 1, target: 'player' }] },
                    ];
                    enemy.intentPattern = p2Pattern;
                    enemy.buffs.push({ type: 'strength', amount: 1, isDebuff: false, turnsRemaining: 1 });
                }
            }
            else if (enemy.name === 'Archdemon') {
                if (enemy.hp <= 45) {
                    const p2Pattern = [
                        { type: 'attack', damage: 16, text: 'Attack 16' },
                        { type: 'multiHit', damage: 7, hits: 3, text: '3x7 Multi' },
                        { type: 'attack', damage: 16, text: 'Attack 16' },
                    ];
                    enemy.intentPattern = p2Pattern;
                    enemy.buffs.push({ type: 'strength', amount: 2, isDebuff: false, turnsRemaining: 1 });
                }
            }
        }
        // Calculate actual damage with enemy strength
        let enemyStrength = getBuffAmount(enemy.buffs, 'strength');
        // Temp strength debuff
        const strLoss = enemy.debuffs.find(d => d.type === 'strength' && d.isDebuff);
        if (strLoss)
            enemyStrength -= strLoss.amount;
        // Intangible on player
        const intangible = ns.player.buffs.find(b => b.type === 'intangible' && b.turnsRemaining && b.turnsRemaining > 0);
        // Dexterity
        const dexterity = getBuffAmount(ns.player.buffs, 'dexterity');
        // Execute intent
        switch (intent.type) {
            case 'attack': {
                const dmg = (intent.damage || 0) + enemyStrength;
                if (intent.block)
                    enemy.block += intent.block + dexterity;
                let finalDmg = dmg;
                if (intangible)
                    finalDmg = 1;
                const blocked = Math.min(finalDmg, ns.player.block);
                ns.player.block -= blocked;
                ns.player.hp = Math.max(0, ns.player.hp - (finalDmg - blocked));
                ns.player.block = Math.max(0, ns.player.block);
                ns.combatLog.push(`${enemy.name} attacks for ${dmg} (blocked ${blocked})`);
                break;
            }
            case 'multiHit': {
                const hits = intent.hits || 1;
                const dmgPerHit = (intent.damage || 0) + enemyStrength;
                if (intent.block)
                    enemy.block += intent.block + dexterity;
                for (let h = 0; h < hits; h++) {
                    let finalDmg = dmgPerHit;
                    if (intangible)
                        finalDmg = 1;
                    const blocked = Math.min(finalDmg, ns.player.block);
                    ns.player.block -= blocked;
                    ns.player.hp = Math.max(0, ns.player.hp - (finalDmg - blocked));
                    ns.player.block = Math.max(0, ns.player.block);
                }
                ns.combatLog.push(`${enemy.name} hits ${hits}x${dmgPerHit}`);
                break;
            }
            case 'defend': {
                enemy.block += (intent.block || 0) + dexterity;
                ns.combatLog.push(`${enemy.name} gains ${intent.block} Block`);
                if (enemy.name === 'Dread Knight' && enemy.block) {
                    enemy.hp = Math.min(enemy.maxHp, enemy.hp + 4);
                }
                if (enemy.name === 'Eldritch Treant' && enemy.hp <= 40) {
                    enemy.hp = Math.min(enemy.maxHp, enemy.hp + 5);
                }
                break;
            }
            case 'buff': {
                if (intent.buffSelf) {
                    intent.buffSelf.forEach(b => {
                        const existing = enemy.buffs.find(x => x.type === b.type);
                        if (existing)
                            existing.amount += b.amount;
                        else
                            enemy.buffs.push({ ...b, isDebuff: false, duration: 0 });
                    });
                }
                if (intent.block)
                    enemy.block += intent.block + dexterity;
                ns.combatLog.push(`${enemy.name} buffs self`);
                break;
            }
            case 'debuff': {
                if (intent.applyDebuffs) {
                    intent.applyDebuffs.forEach(ad => {
                        if (ad.target === 'player') {
                            const existing = ns.player.debuffs.find(d => d.type === ad.type);
                            if (existing) {
                                existing.amount += ad.amount;
                                if (ad.type === 'bleed' || ad.type === 'burn') { /* persist */ }
                            }
                            else {
                                ns.player.debuffs.push({ type: ad.type, amount: ad.amount, isDebuff: true, turnsRemaining: ad.type === 'bleed' || ad.type === 'burn' ? undefined : 99 });
                            }
                        }
                    });
                }
                ns.combatLog.push(`${enemy.name} applies debuffs`);
                break;
            }
            case 'summon': {
                if (intent.summonEnemy) {
                    const summonData = (0, data_1.getEnemyData)(enemy.act, intent.summonEnemy);
                    if (summonData) {
                        const newEnemy = {
                            id: `enemy_${intent.summonEnemy}_${instanceCounter}`,
                            name: summonData.baseEnemy.name,
                            maxHp: summonData.baseEnemy.maxHp,
                            hp: summonData.baseEnemy.maxHp,
                            block: 0,
                            buffs: [],
                            debuffs: [],
                            intent: summonData.intentPattern[0],
                            intentIndex: 0,
                            intentPattern: summonData.intentPattern,
                            isBoss: false,
                            isMinion: true,
                            sprite: summonData.baseEnemy.sprite || 'skeleton',
                            act: enemy.act,
                        };
                        ns.enemies.push(newEnemy);
                        ns.combatLog.push(`${enemy.name} summons ${newEnemy.name}`);
                    }
                }
                break;
            }
            case 'drain': {
                const dmg = (intent.damage || 0) + enemyStrength;
                let finalDmg = dmg;
                if (intangible)
                    finalDmg = 1;
                const blocked = Math.min(finalDmg, ns.player.block);
                ns.player.block -= blocked;
                ns.player.hp = Math.max(0, ns.player.hp - (finalDmg - blocked));
                ns.player.block = Math.max(0, ns.player.block);
                enemy.hp = Math.min(enemy.maxHp, enemy.hp + (intent.heal || 0));
                ns.combatLog.push(`${enemy.name} drains for ${dmg}, heals ${intent.heal}`);
                break;
            }
        }
        // Advance intent
        enemy.intentIndex = (enemy.intentIndex + 1) % enemy.intentPattern.length;
        enemy.intent = enemy.intentPattern[enemy.intentIndex];
    }
    checkEnemyDeaths(ns);
    return ns;
}
// End player turn
function endPlayerTurn(state) {
    const ns = { ...state };
    // Process Doubt in hand
    ns.player.hand.forEach(card => {
        if (card.name === 'Doubt') {
            ns.player.hp = Math.max(0, ns.player.hp - 1);
        }
    });
    // Discard hand
    ns.player.discardPile.push(...ns.player.hand);
    ns.player.hand = [];
    ns.player.block = 0;
    return ns;
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resize = resize;
exports.renderTitleScreen = renderTitleScreen;
exports.renderClassSelect = renderClassSelect;
exports.renderMapScreen = renderMapScreen;
exports.renderCombatScreen = renderCombatScreen;
exports.renderRestScreen = renderRestScreen;
exports.renderShopScreen = renderShopScreen;
exports.renderEventScreen = renderEventScreen;
exports.renderCardRewardScreen = renderCardRewardScreen;
exports.renderGameOverScreen = renderGameOverScreen;
exports.renderVictoryScreen = renderVictoryScreen;
exports.renderBossRewardScreen = renderBossRewardScreen;
exports.renderPotionPickScreen = renderPotionPickScreen;
exports.renderScreen = renderScreen;
exports.getW = getW;
exports.getH = getH;
exports.setHoverState = setHoverState;
exports.getHoverState = getHoverState;
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let W = 960;
let H = 640;
function resize() {
    W = window.innerWidth;
    H = window.innerHeight;
    canvas.width = W;
    canvas.height = H;
}
// Colors
const COLORS = {
    bg: '#0a0a12',
    darkBg: '#12121e',
    panel: '#1a1a2e',
    panelBorder: '#2a2a4e',
    cardBg: '#1e1e3a',
    cardBorder: '#3a3a6a',
    cardHover: '#2a2a5a',
    text: '#e0d8c8',
    textDim: '#8a8a9a',
    red: '#e04040',
    redDark: '#8b0000',
    green: '#40c040',
    blue: '#4080e0',
    gold: '#e0c040',
    purple: '#a040e0',
    orange: '#e08040',
    cyan: '#40c0c0',
    white: '#f0f0f0',
    attack: '#e04040',
    skill: '#40c040',
    power: '#4080e0',
    curse: '#a040a0',
    status: '#808080',
    common: '#40a040',
    uncommon: '#4080e0',
    rare: '#c040c0',
    basic: '#808080',
    energy: '#e0c040',
    hp: '#e04040',
    block: '#4080e0',
    enemyAttack: '#e04040',
    enemyDefend: '#4080e0',
    enemyBuff: '#c0c040',
    enemyDebuff: '#a040a0',
    enemyMulti: '#e08040',
    enemySummon: '#c040c0',
    enemyDrain: '#a040a0',
};
function getRarityColor(r) {
    switch (r) {
        case 'Basic': return COLORS.basic;
        case 'Common': return COLORS.common;
        case 'Uncommon': return COLORS.uncommon;
        case 'Rare': return COLORS.rare;
        case 'Boss': return COLORS.rare;
        default: return COLORS.textDim;
    }
}
function getTypeColor(t) {
    switch (t) {
        case 'Attack': return COLORS.attack;
        case 'Skill': return COLORS.skill;
        case 'Power': return COLORS.power;
        case 'Curse': return COLORS.curse;
        case 'Status': return COLORS.status;
        default: return COLORS.text;
    }
}
function getNodeTypeIcon(type) {
    switch (type) {
        case 'enemy': return '⚔';
        case 'elite': return '⚡';
        case 'boss': return '💀';
        case 'rest': return '✚';
        case 'shop': return '🛒';
        case 'event': return '?';
        default: return '?';
    }
}
function getNodeTypeColor(type) {
    switch (type) {
        case 'enemy': return '#e06060';
        case 'elite': return '#e0c040';
        case 'boss': return '#c04040';
        case 'rest': return '#40c040';
        case 'shop': return '#e0a040';
        case 'event': return '#a060c0';
        default: return '#808080';
    }
}
// Drawing helpers
function drawRect(x, y, w, h, fill, stroke) {
    ctx.fillStyle = fill;
    ctx.fillRect(x, y, w, h);
    if (stroke) {
        ctx.strokeStyle = stroke;
        ctx.lineWidth = 2;
        ctx.strokeRect(x, y, w, h);
    }
}
function drawText(text, x, y, size, color, align = 'left', bold = false) {
    ctx.font = `${bold ? 'bold ' : ''}${size}px 'Segoe UI', sans-serif`;
    ctx.fillStyle = color;
    ctx.textAlign = align;
    ctx.textBaseline = 'top';
    ctx.fillText(text, x, y);
}
function drawCenteredText(text, x, y, size, color, bold = false) {
    drawText(text, x, y, size, color, 'center', bold);
}
function drawRoundRect(x, y, w, h, r, fill, stroke) {
    ctx.beginPath();
    ctx.roundRect(x, y, w, h, r);
    ctx.fillStyle = fill;
    ctx.fill();
    if (stroke) {
        ctx.strokeStyle = stroke;
        ctx.lineWidth = 2;
        ctx.stroke();
    }
}
function drawHPBar(x, y, w, h, current, max, color) {
    drawRect(x, y, w, h, '#1a1a1a', '#333');
    const pct = Math.max(0, current / max);
    drawRect(x + 1, y + 1, (w - 2) * pct, h - 2, color);
}
// Card rendering
function drawCard(card, x, y, w, h, highlighted = false, canPlay = false) {
    const borderColor = canPlay ? '#40e040' : (card.unplayable ? '#604040' : getRarityColor(card.rarity));
    drawRoundRect(x, y, w, h, 6, highlighted ? '#2a2a4e' : COLORS.cardBg, borderColor);
    // Cost circle
    const costStr = card.cost === 0 ? '0' : String(card.cost);
    drawRoundRect(x + 4, y + 4, 28, 28, 14, '#1a1a2e', COLORS.energy);
    drawCenteredText(costStr, x + 18, y + 8, 16, COLORS.white, true);
    // Card name
    const nameColor = card.upgraded ? '#ffd700' : (card.unplayable ? '#606060' : COLORS.white);
    drawText(card.name, x + 38, y + 8, 12, nameColor, 'left', true);
    // Type badge
    drawText(card.type, x + 4, y + 36, 9, getTypeColor(card.type));
    // Description - word wrap
    ctx.save();
    ctx.beginPath();
    ctx.roundRect(x + 4, y + 50, w - 8, h - 60, 4);
    ctx.clip();
    const words = card.description.split(' ');
    let line = '';
    let ly = y + 52;
    ctx.font = '11px "Segoe UI", sans-serif';
    ctx.fillStyle = card.unplayable ? '#606060' : COLORS.textDim;
    ctx.textAlign = 'left';
    for (const word of words) {
        const test = line + word + ' ';
        if (ctx.measureText(test).width > w - 12 && line) {
            ctx.fillText(line.trim(), x + 6, ly);
            line = word + ' ';
            ly += 14;
        }
        else {
            line = test;
        }
    }
    ctx.fillText(line.trim(), x + 6, ly);
    ctx.restore();
    // Exhaust icon
    if (card.exhaust) {
        drawText('⟳', x + w - 20, y + 6, 14, '#c0c040');
    }
    // Unplayable indicator
    if (card.unplayable) {
        drawText('✕', x + w - 20, y + 6, 14, '#e04040');
    }
}
// Screen renderers
function renderTitleScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    // Ambient particles
    const time = Date.now() * 0.001;
    for (let i = 0; i < 30; i++) {
        const px = (Math.sin(time + i * 1.3) * 0.5 + 0.5) * W;
        const py = (Math.cos(time * 0.7 + i * 0.9) * 0.5 + 0.5) * H;
        const alpha = 0.15 + Math.sin(time + i) * 0.1;
        ctx.fillStyle = `rgba(160, 60, 60, ${alpha})`;
        ctx.beginPath();
        ctx.arc(px, py, 2 + Math.sin(time * 2 + i) * 1, 0, Math.PI * 2);
        ctx.fill();
    }
    drawCenteredText('SHADOWS OF THE ABYSS', W / 2, H * 0.3, 36, '#c04040', true);
    drawCenteredText('A Roguelike Deckbuilder', W / 2, H * 0.3 + 45, 16, '#8a8a9a');
    // Click to start
    const btnW = 280;
    const btnH = 50;
    const btnX = W / 2 - btnW / 2;
    const btnY = H * 0.55;
    const hover = getHoverState() === 'startBtn';
    drawRoundRect(btnX, btnY, btnW, btnH, 8, hover ? '#3a3a5e' : '#2a2a4e', hover ? '#e04040' : '#4a4a6e');
    drawCenteredText('BEGIN YOUR DESCENT', W / 2, btnY + 15, 16, hover ? '#e0e0e0' : '#c0c0c0', true);
    drawCenteredText('Click to start', W / 2, H * 0.75, 13, '#5a5a6a');
}
function renderClassSelect(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Choose Your Cursed Archetype', W / 2, 40, 22, '#c0c0c0', true);
    const classes = [
        { name: 'Blood Reaper', desc: 'Sacrifice HP for power. Life steal, Bleed, and self-harm strategies.', color: '#e04040', icon: '🩸', cls: 'Blood Reaper' },
        { name: 'Shadowmancer', desc: 'Debuff enemies with Weak and Vulnerable. Intangible defense.', color: '#a040e0', icon: '🌑', cls: 'Shadowmancer' },
        { name: 'Hellforged', desc: 'Fire damage, Burn, Imp summoning, and energy manipulation.', color: '#e08040', icon: '🔥', cls: 'Hellforged' },
    ];
    const btnW = 260;
    const btnH = 180;
    const gap = 30;
    const totalW = classes.length * btnW + (classes.length - 1) * gap;
    const startX = W / 2 - totalW / 2;
    classes.forEach((c, i) => {
        const x = startX + i * (btnW + gap);
        const y = H * 0.25;
        const hover = getHoverState() === `class_${i}`;
        drawRoundRect(x, y, btnW, btnH, 10, hover ? '#2a2a5e' : '#1a1a3e', c.color);
        drawCenteredText(`${c.icon}`, W / 2 + (i - 1) * (btnW + gap), y + 15, 36, '#c0c0c0');
        drawCenteredText(c.name, W / 2 + (i - 1) * (btnW + gap), y + 60, 16, c.color, true);
        drawCenteredText(c.desc, W / 2 + (i - 1) * (btnW + gap), y + 90, 11, '#8a8a9a');
        drawCenteredText('Starting: 70 HP, 3 Energy, 10 Cards', W / 2 + (i - 1) * (btnW + gap), y + 140, 10, '#6a6a7a');
    });
}
function renderMapScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    // Header bar
    drawRect(0, 0, W, 50, COLORS.darkBg, COLORS.panelBorder);
    drawText(`Act ${state.act}: ${['The Crypts', 'Blighted Woods', 'Abyssal Spire'][state.act - 1]}`, 20, 15, 16, '#c0c0c0', 'left', true);
    drawText(`HP: ${state.player.hp}/${state.player.maxHp}`, W - 250, 15, 13, COLORS.hp);
    drawText(`Gold: ${state.player.gold}`, W - 120, 15, 13, COLORS.gold);
    // Draw connections
    ctx.strokeStyle = '#2a2a4e';
    ctx.lineWidth = 2;
    for (let i = 0; i < state.mapNodes.length - 1; i++) {
        const a = state.mapNodes[i];
        const b = state.mapNodes[i + 1];
        ctx.beginPath();
        ctx.moveTo(a.x, a.y + 10);
        ctx.lineTo(b.x, b.y - 10);
        ctx.stroke();
    }
    // Draw nodes
    const currentIdx = state.floor - 2;
    state.mapNodes.forEach((node, i) => {
        const nx = node.x;
        const ny = node.y;
        const color = getNodeTypeColor(node.type);
        // Path glow for current floor
        if (i === currentIdx && !node.completed) {
            ctx.shadowColor = color;
            ctx.shadowBlur = 20;
        }
        ctx.beginPath();
        ctx.arc(nx, ny, node.completed ? 14 : 18, 0, Math.PI * 2);
        ctx.fillStyle = node.completed ? '#2a2a3e' : (color + '40');
        ctx.fill();
        ctx.strokeStyle = node.completed ? '#4a4a5e' : color;
        ctx.lineWidth = node.completed ? 2 : 3;
        ctx.stroke();
        ctx.shadowBlur = 0;
        // Icon
        drawCenteredText(getNodeTypeIcon(node.type), nx, ny - 8, node.completed ? 12 : 14, node.completed ? '#6a6a7a' : color);
        // Floor number
        drawCenteredText(`F${node.floor}`, nx, ny + 22, 9, '#6a6a7a');
    });
    // Selected node info
    const currentNodeIdx = state.floor - 2;
    if (currentNodeIdx >= 0 && currentNodeIdx < state.mapNodes.length) {
        const node = state.mapNodes[currentNodeIdx];
        drawCenteredText('Click the highlighted node to proceed', W / 2, H - 60, 13, '#5a5a6a');
    }
    // Relics display
    if (state.player.relics.length > 0) {
        const ry = H - 30;
        state.player.relics.slice(0, 6).forEach((r, i) => {
            drawRoundRect(10 + i * 32, ry, 28, 28, 4, '#2a2a3e', '#4a4a6e');
            drawCenteredText(`${i + 1}`, 24 + i * 32, ry + 7, 12, COLORS.text);
        });
    }
}
function renderCombatScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    const handH = 180;
    const topH = H - handH - 40;
    // Top area: enemies and their intents
    let ex = 80;
    const enemyW = 160;
    state.enemies.forEach((enemy, i) => {
        const ey = 60;
        const eH = topH - 80;
        // Enemy panel
        drawRoundRect(ex, ey, enemyW, eH, 8, '#1a1a2e', '#3a3a5e');
        // Enemy sprite (simple shape)
        drawEnemySprite(ex + enemyW / 2, ey + 60, enemy);
        // Enemy name and HP
        drawCenteredText(enemy.name, ex + enemyW / 2, ey + eH - 70, 11, '#c0c0c0', true);
        drawHPBar(ex + 10, ey + eH - 55, enemyW - 20, 12, enemy.hp, enemy.maxHp, COLORS.red);
        drawCenteredText(`${enemy.hp}/${enemy.maxHp}`, ex + enemyW / 2, ey + eH - 52, 10, COLORS.white);
        // Block indicator
        if (enemy.block > 0) {
            drawCenteredText(`🛡 ${enemy.block}`, ex + enemyW / 2, ey + eH - 38, 11, COLORS.block);
        }
        // Debuffs
        let dx = ex + 5;
        enemy.debuffs.forEach(d => {
            if (d.amount > 0) {
                drawDebuffIcon(dx, ey + 5, d);
                dx += 22;
            }
        });
        // Buffs
        enemy.buffs.forEach(b => {
            if (b.type !== 'regeneration') {
                drawBuffIcon(dx, ey + 28, b);
                dx += 22;
            }
        });
        // Intent
        drawIntentBar(ex + 10, ey + 20, enemyW - 20, enemy.intent);
        ex += enemyW + 20;
    });
    // Player area (bottom)
    const playerY = topH;
    // Player HP bar
    drawRect(20, playerY, W - 40, 25, '#1a1a2e', '#3a3a5e');
    drawText('YOU', 30, playerY + 5, 12, '#c0c0c0', 'left', true);
    drawHPBar(80, playerY + 4, 200, 16, state.player.hp, state.player.maxHp, COLORS.green);
    drawCenteredText(`${state.player.hp}/${state.player.maxHp}`, 180, playerY + 5, 10, COLORS.white);
    // Block
    if (state.player.block > 0) {
        drawText(`🛡 ${state.player.block}`, 300, playerY + 5, 13, COLORS.block);
    }
    // Energy
    for (let i = 0; i < state.player.maxEnergy; i++) {
        const ex2 = 420 + i * 28;
        const filled = i < state.player.energy;
        drawRoundRect(ex2, playerY + 3, 22, 18, filled ? '#2a2a1e' : '#1a1a2e', filled ? COLORS.energy : '#3a3a3e');
        drawCenteredText('⚡', ex2 + 11, playerY + 5, 12, filled ? COLORS.energy : '#4a4a4a');
    }
    // Player debuffs
    let pdx = 550;
    state.player.debuffs.forEach(d => {
        if (d.amount > 0) {
            drawDebuffIcon(pdx, playerY + 3, d);
            pdx += 22;
        }
    });
    // Player buffs
    state.player.buffs.forEach(b => {
        if (b.amount > 0 && b.type !== 'regeneration') {
            drawBuffIcon(pdx, playerY + 3, b);
            pdx += 22;
        }
    });
    // Draw pile / Discard info
    drawText(`Draw: ${state.player.drawPile.length}`, W - 180, playerY + 5, 11, COLORS.textDim);
    drawText(`Discard: ${state.player.discardPile.length}`, W - 80, playerY + 5, 11, COLORS.textDim);
    // Hand area
    const handY = topH + 30;
    const cardW = 100;
    const cardH = 140;
    const handW = state.player.hand.length * (cardW + 8);
    const handStartX = Math.max(20, W / 2 - handW / 2);
    state.player.hand.forEach((card, i) => {
        const canPlay = state.isPlayerTurn && !card.unplayable && state.player.energy >= card.cost;
        const hovered = state.hoveredCard?.instanceId === card.instanceId;
        const x = handStartX + i * (cardW + 8);
        const y = hovered ? handY - 15 : handY;
        drawCard(card, x, y, cardW, cardH, hovered, canPlay);
    });
    // End turn button
    const btnW = 120;
    const btnH = 35;
    const btnX = W - btnW - 20;
    const btnY = H - 30;
    const hoverBtn = getHoverState() === 'endTurn';
    drawRoundRect(btnX, btnY, btnW, btnH, 6, hoverBtn ? '#3a2020' : '#2a1a1a', hoverBtn ? '#e06060' : '#804040');
    drawCenteredText('END TURN', btnX + btnW / 2, btnY + 10, 13, hoverBtn ? '#e0e0e0' : '#c0a0a0', true);
    // Combat log
    const logY = topH + 25;
    const logX = state.enemies.length * 180 + 200;
    if (logX < W - 200) {
        drawRect(logX, logY, W - logX - 20, handY - logY - 5, '#0e0e1a', '#2a2a3e');
        const recentLogs = state.combatLog.slice(-8);
        recentLogs.forEach((log, i) => {
            drawText(log, logX + 5, logY + 5 + i * 14, 10, '#7a7a8a');
        });
    }
    // Potions
    const potionY = H - 25;
    state.player.potions.forEach((p, i) => {
        const px = 15 + i * 60;
        const hover = getHoverState() === `potion_${i}`;
        drawRoundRect(px, potionY, 55, 22, hover ? '#2a2a4e' : '#1a1a2e', '#3a3a5e');
        drawCenteredText(p.name.split(' ')[0], px + 27, potionY + 5, 9, '#a0a0c0');
    });
}
function drawEnemySprite(x, y, enemy) {
    ctx.save();
    ctx.translate(x, y);
    // Simple geometric enemy sprite
    const colors = {
        skeleton: '#c0b090', zombie: '#608040', cultist: '#6040a0', rat: '#807060',
        tombGuardian: '#808080', bloodWitch: '#802020', lichKing: '#c0c0e0',
        shadowThief: '#404060', wraith: '#8080c0', corruptedBeast: '#804040',
        thornedHorror: '#408040', witchDoctor: '#606040', treantProtector: '#608040',
        plagueShaman: '#804080', hellhound: '#c04020', shadowDemon: '#402080',
        infernalImp: '#c06020', abyssalCultist: '#6040a0', flamespawn: '#e08020',
        dreadKnight: '#8080a0', archdemon: '#a02020', eldritchTreant: '#408060',
    };
    const c = colors[enemy.sprite] || '#808080';
    ctx.fillStyle = c;
    if (enemy.isBoss) {
        // Boss: larger, more complex shape
        ctx.beginPath();
        ctx.moveTo(0, -35);
        ctx.lineTo(-25, -15);
        ctx.lineTo(-20, 15);
        ctx.lineTo(0, 25);
        ctx.lineTo(20, 15);
        ctx.lineTo(25, -15);
        ctx.closePath();
        ctx.fill();
        ctx.strokeStyle = '#fff3';
        ctx.lineWidth = 2;
        ctx.stroke();
        // Eyes
        ctx.fillStyle = '#e04040';
        ctx.beginPath();
        ctx.arc(-8, -5, 4, 0, Math.PI * 2);
        ctx.arc(8, -5, 4, 0, Math.PI * 2);
        ctx.fill();
    }
    else if (enemy.isMinion) {
        ctx.beginPath();
        ctx.arc(0, 0, 12, 0, Math.PI * 2);
        ctx.fill();
    }
    else {
        // Regular enemy
        ctx.beginPath();
        ctx.arc(0, 0, 20, 0, Math.PI * 2);
        ctx.fill();
        // Eyes
        ctx.fillStyle = '#e04040';
        ctx.beginPath();
        ctx.arc(-6, -3, 3, 0, Math.PI * 2);
        ctx.arc(6, -3, 3, 0, Math.PI * 2);
        ctx.fill();
    }
    ctx.restore();
}
function drawIntentBar(x, y, w, intent) {
    const icon = {
        attack: '⚔', defend: '🛡', buff: '▲', debuff: '▼',
        multiHit: '⚔⚔', summon: '★', drain: '♥',
    }[intent.type] || '?';
    const color = {
        attack: COLORS.enemyAttack, defend: COLORS.enemyDefend, buff: COLORS.enemyBuff,
        debuff: COLORS.enemyDebuff, multiHit: COLORS.enemyMulti, summon: COLORS.enemySummon,
        drain: COLORS.enemyDrain,
    }[intent.type] || '#808080';
    drawText(icon, x, y, 14, color, 'left');
    drawText(intent.text, x + 22, y + 2, 10, color, 'left');
}
function drawDebuffIcon(x, y, d) {
    const icons = { weak: 'W', vulnerable: 'V', bleed: 'B', burn: '🔥', poison: 'P' };
    const color = d.type === 'weak' ? '#a08040' : d.type === 'vulnerable' ? '#a040c0' :
        d.type === 'bleed' ? '#e04040' : d.type === 'burn' ? '#e08040' : '#40c040';
    drawRoundRect(x, y, 20, 18, 3, '#1a1a2e', color);
    drawCenteredText(icons[d.type] || '?', x + 10, y + 2, 9, color);
    drawCenteredText(`${d.amount}`, x + 10, y + 11, 8, COLORS.textDim);
}
function drawBuffIcon(x, y, b) {
    const icons = { strength: 'S', dexterity: 'D', intangible: 'I', regeneration: 'R', energy_next: 'E', block_next: 'B', energy: 'E' };
    const color = '#40c040';
    drawRoundRect(x, y, 20, 18, 3, '#1a2a1a', color);
    drawCenteredText(icons[b.type] || '?', x + 10, y + 2, 9, color);
    if (b.type === 'intangible' && b.turnsRemaining) {
        drawCenteredText(`${b.turnsRemaining}`, x + 10, y + 11, 8, COLORS.textDim);
    }
}
function renderRestScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Campfire', W / 2, 40, 28, '#e0a040', true);
    drawCenteredText('Choose an action:', W / 2, 80, 16, '#c0c0c0');
    const actions = [
        { id: 'rest', label: 'Rest', desc: `Heal ${Math.floor(state.player.maxHp * 0.25)} HP` },
        { id: 'forge', label: 'Forge', desc: 'Upgrade a card' },
        { id: 'meditate', label: 'Meditate', desc: 'Remove a card from your deck' },
    ];
    actions.forEach((a, i) => {
        const x = W / 2 - 220 + i * 160;
        const y = 140;
        const hover = getHoverState() === `rest_${a.id}`;
        drawRoundRect(x, y, 140, 80, 8, hover ? '#2a2a5e' : '#1a1a3e', '#4a4a6e');
        drawCenteredText(a.label, x + 70, y + 20, 16, '#c0c0c0', true);
        drawCenteredText(a.desc, x + 70, y + 45, 10, '#8a8a9a');
    });
    // If forge/meditate, show card list
    if (state.restAction === 'forge') {
        drawCenteredText('Select a card to upgrade:', W / 2, 260, 14, '#c0c0c0');
        renderCardList(state.player.deck, 20, 300, 100, 120, 'Forge', 'forge');
    }
    else if (state.restAction === 'meditate') {
        drawCenteredText('Select a card to remove:', W / 2, 260, 14, '#c0c0c0');
        renderCardList(state.player.deck, 20, 300, 100, 120, 'Meditate', 'meditate');
    }
}
function renderCardList(deck, startX, startY, w, h, _title, action) {
    // Simple grid of cards for selection
    drawRect(startX, startY, W - startX * 2, H - startY - 40, '#0e0e1a', '#2a2a3e');
    const cols = Math.floor((W - startX * 2) / (w + 4));
    deck.slice(0, 20).forEach((card, i) => {
        const col = i % cols;
        const row = Math.floor(i / cols);
        const x = startX + col * (w + 4);
        const y = startY + row * (h + 4);
        const hover = getHoverState() === `${action}_card_${i}`;
        const ci = { ...card, instanceId: `deck_${i}` };
        drawCard(ci, x, y, w, h, hover, false);
    });
}
function renderShopScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Merchant\'s Stall', W / 2, 20, 24, '#e0a040', true);
    drawCenteredText(`Gold: ${state.player.gold}`, W / 2, 55, 16, COLORS.gold);
    // Cards
    drawCenteredText('Cards', W / 2, 90, 14, '#c0c0c0');
    state.shopItems.cards.forEach((item, i) => {
        const x = W / 2 - 200 + i * 140;
        const y = 110;
        const canBuy = state.player.gold >= item.price;
        const hover = getHoverState() === `shop_card_${i}`;
        drawCard({ ...item.card, instanceId: `shop_card_${i}` }, x, y, 110, 120, hover, canBuy);
        drawCenteredText(`${item.price}g`, x + 55, y + 130, 12, COLORS.gold);
    });
    // Relics
    drawCenteredText('Relics', W / 2, 280, 14, '#c0c0c0');
    state.shopItems.relics.forEach((item, i) => {
        const x = W / 2 - 100 + i * 120;
        const y = 310;
        const canBuy = state.player.gold >= item.price;
        const hover = getHoverState() === `shop_relic_${i}`;
        drawRoundRect(x, y, 90, 100, 6, hover ? '#2a2a5e' : '#1a1a3e', canBuy ? '#4a4a6e' : '#3a3a3e');
        drawCenteredText(item.relic.name, x + 45, y + 10, 11, getRarityColor(item.relic.rarity), true);
        drawCenteredText(item.relic.description, x + 45, y + 30, 9, '#8a8a9a');
        drawCenteredText(`${item.price}g`, x + 45, y + 75, 12, COLORS.gold);
    });
    // Potions
    drawCenteredText('Potions', W / 2, 450, 14, '#c0c0c0');
    state.shopItems.potions.forEach((item, i) => {
        const x = W / 2 - 150 + i * 100;
        const y = 480;
        const canBuy = state.player.gold >= item.price && state.player.potions.length < 3;
        const hover = getHoverState() === `shop_potion_${i}`;
        drawRoundRect(x, y, 75, 60, 6, hover ? '#2a2a5e' : '#1a1a3e', canBuy ? '#4a4a6e' : '#3a3a3e');
        drawCenteredText('🧪', x + 37, y + 5, 16, '#a0a0c0');
        drawCenteredText(item.potion.name, x + 37, y + 25, 9, '#a0a0c0');
        drawCenteredText(`${item.price}g`, x + 37, y + 45, 11, COLORS.gold);
    });
    // Remove card button
    if (state.shopRemoveAvailable) {
        const rx = W / 2 - 60;
        const ry = 570;
        const hover = getHoverState() === 'shop_remove';
        drawRoundRect(rx, ry, 120, 35, 6, hover ? '#2a2a5e' : '#1a1a3e', '#4a4a6e');
        drawCenteredText('Remove Card (75g)', rx + 60, ry + 10, 12, '#c0c0c0');
    }
    // Leave button
    const lx = W - 100;
    const ly = H - 40;
    const hover = getHoverState() === 'shop_leave';
    drawRoundRect(lx, ly, 80, 30, 6, hover ? '#3a2020' : '#2a1a1a', '#804040');
    drawCenteredText('Leave', lx + 40, ly + 8, 12, '#c0a0a0');
}
function renderEventScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    if (!state.currentEvent)
        return;
    const ev = state.currentEvent;
    drawCenteredText(ev.title, W / 2, 60, 24, '#a060c0', true);
    // Event text
    drawRect(W / 2 - 250, 110, 500, 80, '#1a1a2e', '#3a3a5e');
    drawCenteredText(ev.description, W / 2, 125, 14, '#c0c0c0');
    ev.choices.forEach((choice, i) => {
        const cy = 220 + i * 60;
        const hover = getHoverState() === `event_${i}`;
        drawRoundRect(W / 2 - 200, cy, 400, 45, 6, hover ? '#2a2a5e' : '#1a1a3e', hover ? '#a060c0' : '#3a3a5e');
        drawText(choice.text, W / 2 - 185, cy + 15, 13, hover ? '#e0e0e0' : '#c0c0c0');
    });
}
function renderCardRewardScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Choose a Card', W / 2, 40, 22, '#c0c0c0', true);
    const cards = state.pendingRewards.cardChoices;
    if (cards.length > 0) {
        const totalW = cards.length * 140 + (cards.length - 1) * 20;
        const startX = W / 2 - totalW / 2;
        cards.forEach((card, i) => {
            const x = startX + i * 160;
            const y = 120;
            const hover = getHoverState() === `reward_${i}`;
            drawCard({ ...card, instanceId: `reward_${i}` }, x, y, 120, 160, hover, false);
        });
    }
    // Skip button
    const lx = W / 2 - 60;
    const ly = 350;
    const hover = getHoverState() === 'reward_skip';
    drawRoundRect(lx, ly, 120, 35, 6, hover ? '#3a2020' : '#2a1a1a', '#804040');
    drawCenteredText('Skip', lx + 60, ly + 10, 13, '#c0a0a0');
}
function renderGameOverScreen(state) {
    drawRect(0, 0, W, H, '#1a0a0a');
    drawCenteredText('YOU HAVE FALLEN', W / 2, H * 0.3, 40, '#e04040', true);
    drawCenteredText(`Act ${state.act}, Floor ${state.floor}`, W / 2, H * 0.3 + 50, 16, '#8a8a9a');
    drawCenteredText(`Gold: ${state.player.gold} | Cards: ${state.player.deck.length}`, W / 2, H * 0.3 + 75, 14, '#6a6a7a');
    const hover = getHoverState() === 'restart';
    const btnX = W / 2 - 100;
    const btnY = H * 0.6;
    drawRoundRect(btnX, btnY, 200, 45, 8, hover ? '#3a3a5e' : '#2a2a4e', hover ? '#e04040' : '#4a4a6e');
    drawCenteredText('TRY AGAIN', W / 2, btnY + 15, 16, hover ? '#e0e0e0' : '#c0c0c0', true);
}
function renderVictoryScreen(state) {
    drawRect(0, 0, W, H, '#0a1a0a');
    drawCenteredText('VICTORY', W / 2, H * 0.25, 48, '#e0c040', true);
    drawCenteredText('The Abyss has been conquered...', W / 2, H * 0.25 + 60, 18, '#a0c080');
    drawCenteredText(`Gold: ${state.player.gold} | Cards: ${state.player.deck.length} | Relics: ${state.player.relics.length}`, W / 2, H * 0.25 + 100, 14, '#8a8a9a');
    const hover = getHoverState() === 'restart';
    const btnX = W / 2 - 100;
    const btnY = H * 0.5;
    drawRoundRect(btnX, btnY, 200, 45, 8, hover ? '#3a3a5e' : '#2a2a4e', hover ? '#e0c040' : '#4a4a6e');
    drawCenteredText('PLAY AGAIN', W / 2, btnY + 15, 16, hover ? '#e0e0e0' : '#c0c0c0', true);
}
function renderBossRewardScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Boss Relic Rewards', W / 2, 40, 22, '#e0c040', true);
    drawCenteredText('Choose one:', W / 2, 75, 14, '#c0c0c0');
    state.bossRelicChoices.forEach((relic, i) => {
        const x = W / 2 - 240 + i * 170;
        const y = 110;
        const hover = getHoverState() === `bossrelic_${i}`;
        drawRoundRect(x, y, 150, 160, 8, hover ? '#2a2a5e' : '#1a1a3e', '#e0c040');
        drawCenteredText(relic.name, x + 75, y + 15, 13, '#e0c040', true);
        drawCenteredText(relic.description, x + 75, y + 40, 10, '#8a8a9a');
    });
    // Skip
    const hover = getHoverState() === 'bossrelic_skip';
    const btnX = W / 2 - 50;
    const btnY = 310;
    drawRoundRect(btnX, btnY, 100, 30, 6, hover ? '#3a2020' : '#2a1a1a', '#804040');
    drawCenteredText('Skip', btnX + 50, btnY + 8, 12, '#c0a0a0');
}
function renderPotionPickScreen(state) {
    drawRect(0, 0, W, H, COLORS.bg);
    drawCenteredText('Potion Found!', W / 2, 60, 22, '#a060c0', true);
    const potion = state.pendingRewards.potionChoice;
    if (potion) {
        const potions = {
            bloodVial: { id: 'bloodVial', name: 'Blood Vial', description: 'Heal 15 HP.' },
            firebomb: { id: 'firebomb', name: 'Firebomb', description: 'Deal 10 damage to ALL enemies.' },
            dexTonic: { id: 'dexTonic', name: 'Dex Tonic', description: 'Gain 3 Dexterity for 1 turn.' },
            energyElixir: { id: 'energyElixir', name: 'Energy Elixir', description: 'Gain 2 Energy this turn.' },
            cursedBrew: { id: 'cursedBrew', name: 'Cursed Brew', description: 'Apply 3 Vulnerable to all enemies.' },
            bleedingFlask: { id: 'bleedingFlask', name: 'Bleeding Flask', description: 'Apply 6 Bleed to an enemy.' },
            impInBottle: { id: 'impInBottle', name: 'Imp-in-a-Bottle', description: 'Add Imp Claw+ to hand.' },
            smokeBomb: { id: 'smokeBomb', name: 'Smoke Bomb', description: 'Escape from combat, no rewards.' },
        };
        const p = potions[potion];
        if (p) {
            drawRoundRect(W / 2 - 120, 120, 240, 100, 8, '#1a1a3e', '#a060c0');
            drawCenteredText('🧪', W / 2, 135, 30, '#a0a0c0');
            drawCenteredText(p.name, W / 2, 175, 16, '#c0c0c0', true);
            drawCenteredText(p.description, W / 2, 200, 12, '#8a8a9a');
        }
        if (state.player.potions.length < 3) {
            const hover = getHoverState() === 'potion_accept';
            drawRoundRect(W / 2 - 80, 250, 160, 40, 6, hover ? '#2a2a5e' : '#1a1a3e', '#a060c0');
            drawCenteredText('Accept', W / 2, 265, 14, '#c0c0c0');
        }
        else {
            drawCenteredText('Potion slots full!', W / 2, 260, 14, '#e04040');
        }
    }
}
// Central render function
function renderScreen(state) {
    switch (state.screen) {
        case 'title':
            renderTitleScreen(state);
            break;
        case 'classSelect':
            renderClassSelect(state);
            break;
        case 'map':
            renderMapScreen(state);
            break;
        case 'combat':
            renderCombatScreen(state);
            break;
        case 'rest':
            renderRestScreen(state);
            break;
        case 'shop':
            renderShopScreen(state);
            break;
        case 'event':
            renderEventScreen(state);
            break;
        case 'cardReward':
            renderCardRewardScreen(state);
            break;
        case 'gameOver':
            renderGameOverScreen(state);
            break;
        case 'victory':
            renderVictoryScreen(state);
            break;
        case 'bossReward':
            renderBossRewardScreen(state);
            break;
        case 'potionPick':
            renderPotionPickScreen(state);
            break;
    }
}
function getW() { return W; }
function getH() { return H; }
// Hover state
let hoverState = '';
function setHoverState(s) { hoverState = s; }
function getHoverState() { return hoverState; }
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"907c5aeb-5f26-4bda-a3c5-4084188525b2","tokensIn":4656619,"tokensOut":96607,"tokensTotal":4753226,"cost":0,"turns":59,"toolCalls":58,"failedToolCalls":2,"timestamp":"2026-08-02T09:06:13.699Z"} -->