← Deckbuilder results

Deckbuilder

Qwen 3.6 27b MTP · typescript

Initial view of Deckbuilder

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 86,974 bytes · SHA-256 e198818f5ca3
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shadows of Aethelgard</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0f; font-family: 'Georgia', serif; color: #d4c5a9; }

#game-container { width: 100vw; height: 100vh; position: relative; overflow: hidden; }
.screen { display: none; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; position: absolute; top: 0; left: 0; z-index: 1; }
.screen.active { display: flex; }

.overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 100; display: flex; align-items: center; justify-content: center; }
.overlay.hidden { display: none; }

/* Class Select */
#screen-class-select { background: radial-gradient(ellipse at center, #1a1520 0%, #0a0a0f 70%); }
#screen-class-select h1 { font-size: 3em; color: #c9a84c; text-shadow: 0 0 20px rgba(201,168,76,0.5), 0 2px 4px rgba(0,0,0,0.8); margin-bottom: 0.2em; letter-spacing: 3px; }
.subtitle { font-size: 1.1em; color: #8a7e6b; margin-bottom: 2em; font-style: italic; }

#class-cards { display: flex; gap: 2em; flex-wrap: wrap; justify-content: center; padding: 0 1em; }
.class-card { width: 240px; height: 360px; border-radius: 12px; cursor: pointer; transition: transform 0.3s, box-shadow 0.3s; position: relative; overflow: hidden; display: flex; flex-direction: column; align-items: center; padding: 1em; }
.class-card:hover { transform: translateY(-8px) scale(1.05); }
.class-card .class-name { font-size: 1.4em; margin-top: 0.5em; text-shadow: 0 2px 4px rgba(0,0,0,0.8); }
.class-card .class-theme { font-style: italic; color: #aaa; margin-bottom: 0.5em; font-size: 0.9em; }
.class-card .class-stats { font-size: 0.85em; color: #bbb; text-align: center; line-height: 1.6; }

/* Combat Screen */
#screen-combat { background: radial-gradient(ellipse at bottom, #1a1215 0%, #0d0b10 50%, #08070e 100%); justify-content: flex-start; padding-top: 0; }

#combat-hud-top { width: 100%; display: flex; justify-content: space-between; padding: 8px 20px; background: linear-gradient(180deg, rgba(30,25,20,0.9) 0%, transparent 100%); font-size: 0.9em; z-index: 10; }
#combat-hud-top span { padding: 4px 12px; border-radius: 4px; background: rgba(0,0,0,0.5); border: 1px solid #3a3020; }

#combat-arena { width: 100%; flex: 1; display: flex; justify-content: space-between; align-items: center; padding: 0 60px; position: relative; min-height: 0; }
#enemy-area { display: flex; flex-direction: column; align-items: center; gap: 8px; }
#player-area { width: 100%; display: flex; justify-content: center; padding-bottom: 10px; }

#enemy-sprite { width: 120px; height: 140px; border-radius: 8px; position: relative; transition: transform 0.1s; }
#enemy-sprite.hit { animation: enemyHit 0.3s ease-out; }
@keyframes enemyHit { 0% { filter: brightness(3); transform: scale(1.1); } 100% { filter: none; transform: scale(1); } }

#enemy-info, #player-info { text-align: center; width: 200px; }
#enemy-name { font-size: 1em; color: #c9a84c; margin-bottom: 4px; }
#enemy-hp-bar, #player-hp-bar { width: 100%; height: 16px; background: #2a1515; border-radius: 8px; overflow: hidden; border: 1px solid #4a3030; position: relative; }
#enemy-hp-fill, #player-hp-fill { height: 100%; background: linear-gradient(90deg, #8b2500, #cc3300); transition: width 0.4s ease-out; border-radius: 8px; }
#player-hp-fill { background: linear-gradient(90deg, #1a6b1a, #2ecc40); }

#enemy-statuses, #player-statuses { display: flex; gap: 4px; justify-content: center; margin-top: 4px; min-height: 20px; flex-wrap: wrap; }
.status-badge { font-size: 0.7em; padding: 2px 6px; border-radius: 3px; background: rgba(0,0,0,0.6); border: 1px solid #555; }

#player-defense { color: #4a9eff; font-weight: bold; margin-top: 2px; }
#hud-energy { color: #ffcc00; font-size: 1em; padding: 4px 12px; border-radius: 4px; background: rgba(0,0,0,0.5); border: 1px solid #3a3020; }

/* Hand Area */
#combat-hand-area { width: 100%; min-height: 200px; display: flex; justify-content: center; align-items: flex-end; padding-bottom: 8px; position: relative; z-index: 5; }
#hand-container { display: flex; gap: -30px; justify-content: center; align-items: flex-end; perspective: 800px; min-height: 170px; padding: 0 20px; }

/* Cards */
.game-card { width: 140px; height: 195px; border-radius: 8px; cursor: pointer; transition: transform 0.2s, box-shadow 0.2s, margin-top 0.2s; position: relative; flex-shrink: 0; user-select: none; }
.game-card:hover { transform: translateY(-30px) scale(1.08); z-index: 20; box-shadow: 0 8px 25px rgba(0,0,0,0.6); margin-top: -20px; }
.game-card.playable { filter: drop-shadow(0 0 4px rgba(255,200,0,0.3)); }
.game-card.unplayable { opacity: 0.5; cursor: not-allowed; }
.game-card.unplayable:hover { transform: none; box-shadow: none; margin-top: 0; }

/* Card Detail Overlay */
#card-detail-content { background: #1a1520; border: 2px solid #c9a84c; border-radius: 12px; padding: 2em; max-width: 360px; text-align: center; }
#card-detail-content h3 { color: #c9a84c; margin-bottom: 0.5em; font-size: 1.3em; }
#card-detail-content p { line-height: 1.6; color: #bbb; }

/* Combat Controls */
#combat-controls { width: 100%; display: flex; justify-content: center; align-items: center; gap: 20px; padding: 8px 20px 12px; background: linear-gradient(0deg, rgba(30,25,20,0.9) 0%, transparent 100%); }
#btn-end-turn { padding: 10px 30px; font-size: 1em; font-family: 'Georgia', serif; background: linear-gradient(180deg, #4a3520, #2a1a10); color: #d4c5a9; border: 2px solid #6a5030; border-radius: 6px; cursor: pointer; transition: all 0.2s; letter-spacing: 1px; }
#btn-end-turn:hover { background: linear-gradient(180deg, #5a4530, #3a2a20); box-shadow: 0 0 15px rgba(201,168,76,0.3); border-color: #c9a84c; }
#btn-end-turn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Shop Screen */
#screen-shop { background: radial-gradient(ellipse at center, #1a1520 0%, #0a0a0f 70%); padding: 2em; }
#screen-shop h2 { color: #c9a84c; font-size: 2em; margin-bottom: 0.3em; text-shadow: 0 0 15px rgba(201,168,76,0.4); }
#shop-gold-display { color: #ffd700; font-size: 1.2em; margin-bottom: 1.5em; }
#shop-cards { display: flex; gap: 1.5em; flex-wrap: wrap; justify-content: center; max-width: 900px; margin-bottom: 2em; }
.shop-card-wrapper { text-align: center; }
.shop-card-price { color: #ffd700; font-size: 0.85em; margin-top: 6px; }
.shop-card-count { color: #888; font-size: 0.75em; }

/* Buttons */
button { padding: 12px 30px; font-size: 1em; font-family: 'Georgia', serif; background: linear-gradient(180deg, #4a3520, #2a1a10); color: #d4c5a9; border: 2px solid #6a5030; border-radius: 6px; cursor: pointer; transition: all 0.2s; letter-spacing: 1px; }
button:hover { background: linear-gradient(180deg, #5a4530, #3a2a20); box-shadow: 0 0 15px rgba(201,168,76,0.3); border-color: #c9a84c; }

/* Boss Intro */
#screen-boss-intro { background: radial-gradient(ellipse at center, #2a0a0a 0%, #0a0a0f 70%); }
#screen-boss-intro h2 { color: #cc3300; font-size: 2.5em; text-shadow: 0 0 30px rgba(204,51,0,0.6); margin-bottom: 0.5em; }
#boss-desc { color: #aa8866; font-style: italic; margin-bottom: 2em; max-width: 500px; text-align: center; line-height: 1.6; }

/* Game Over */
#screen-game-over { background: radial-gradient(ellipse at center, #1a0a0a 0%, #0a0a0f 70%); }
#screen-game-over h2 { font-size: 3em; margin-bottom: 0.5em; text-shadow: 0 0 30px rgba(204,51,0,0.6); }
#game-over-text { color: #aa8866; margin-bottom: 2em; font-size: 1.1em; }

/* Damage/Heal Float */
.float-text { position: absolute; pointer-events: none; font-weight: bold; font-size: 1.4em; animation: floatUp 1s ease-out forwards; z-index: 50; text-shadow: 0 2px 4px rgba(0,0,0,0.8); }
@keyframes floatUp { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-60px) scale(0.7); } }

/* Card play animation */
.card-play-anim { position: absolute; z-index: 30; pointer-events: none; transition: all 0.4s ease-in-out; }

/* Turn indicator */
#turn-indicator { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 2em; color: #c9a84c; text-shadow: 0 0 20px rgba(201,168,76,0.6); opacity: 0; pointer-events: none; z-index: 40; transition: opacity 0.3s; }
#turn-indicator.show { opacity: 1; animation: fadeOut 1s ease-out forwards; }
@keyframes fadeOut { 0% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }

/* Room transition */
#room-transition { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #0a0a0f; z-index: 200; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.5s; }
#room-transition.active { opacity: 1; pointer-events: all; }
#room-transition h2 { color: #c9a84c; font-size: 2em; text-shadow: 0 0 20px rgba(201,168,76,0.5); }

/* Card type colors */
.card-type-attack { border-color: #cc3300 !important; }
.card-type-defense { border-color: #4a9eff !important; }
.card-type-other { border-color: #aa44ff !important; }
/* ── css: ambient ── */
/* Ambient particles */
#particles-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; overflow: hidden; }
.particle { position: absolute; border-radius: 50%; opacity: 0.3; animation: particleFloat linear infinite; }
@keyframes particleFloat {
  0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
  10% { opacity: 0.4; }
  90% { opacity: 0.4; }
  100% { transform: translateY(-20px) rotate(360deg); opacity: 0; }
}

/* Vignette overlay */
#vignette { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.6) 100%); }

/* Combat arena atmosphere */
#combat-arena::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(180deg, transparent 60%, rgba(20,10,30,0.5) 100%); pointer-events: none; }

/* Card hover glow effects */
.game-card.playable:hover { box-shadow: 0 0 20px rgba(201,168,76,0.4), 0 8px 25px rgba(0,0,0,0.6); }
.card-type-attack.playable:hover { box-shadow: 0 0 20px rgba(204,51,0,0.5), 0 8px 25px rgba(0,0,0,0.6); }
.card-type-defense.playable:hover { box-shadow: 0 0 20px rgba(74,158,255,0.5), 0 8px 25px rgba(0,0,0,0.6); }
.card-type-other.playable:hover { box-shadow: 0 0 20px rgba(170,68,255,0.5), 0 8px 25px rgba(0,0,0,0.6); }

/* Enemy idle animation */
#enemy-sprite { animation: enemyIdle 3s ease-in-out infinite; }
@keyframes enemyIdle {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

/* Player area subtle glow */
#player-area::before { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 200px; height: 40px; background: radial-gradient(ellipse, rgba(100,200,100,0.1) 0%, transparent 70%); pointer-events: none; }

/* Room transition overlay */
#room-transition { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #0a0a0f; z-index: 200; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.5s ease-in-out; }
#room-transition.active { opacity: 1; pointer-events: all; }
#room-transition h2 { color: #c9a84c; font-size: 2em; text-shadow: 0 0 20px rgba(201,168,76,0.5); animation: pulseGlow 2s ease-in-out infinite; }
@keyframes pulseGlow { 0%, 100% { text-shadow: 0 0 20px rgba(201,168,76,0.3); } 50% { text-shadow: 0 0 40px rgba(201,168,76,0.7); } }

/* Shop ambient */
#screen-shop::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse at center bottom, rgba(201,168,76,0.05) 0%, transparent 60%); pointer-events: none; }

/* Card play flash */
.card-play-flash { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); pointer-events: none; animation: flashFade 0.3s ease-out forwards; z-index: 25; }
@keyframes flashFade { from { opacity: 1; } to { opacity: 0; } }

/* Energy pips */
.energy-pip { display: inline-block; width: 14px; height: 14px; border-radius: 50%; margin-right: 3px; transition: all 0.2s; }
.energy-pip.filled { background: radial-gradient(circle, #ffdd44, #cc9900); box-shadow: 0 0 6px rgba(255,221,68,0.5); }
.energy-pip.empty { background: #3a3020; border: 1px solid #5a4a30; }

/* HP bar glow */
#player-hp-fill::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 50%; background: linear-gradient(180deg, rgba(255,255,255,0.2) 0%, transparent 100%); border-radius: 8px 8px 0 0; }
#enemy-hp-fill::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 50%; background: linear-gradient(180deg, rgba(255,255,255,0.15) 0%, transparent 100%); border-radius: 8px 8px 0 0; }

/* Scrollbar styling */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0a0a0f; }
::-webkit-scrollbar-thumb { background: #3a3020; border-radius: 3px; }
</style>
</head>
<body>
<div id="game-container"><div id="particles-container"></div>
<div id="vignette"></div>
<div id="room-transition"><h2></h2></div>
  <div id="screen-class-select" class="screen active">
    <h1>Shadows of Aethelgard</h1>
    <p class="subtitle">Choose your champion to descend into the Void</p>
    <div id="class-cards"></div>
  </div>
  <div id="screen-combat" class="screen">
    <div id="combat-hud-top">
      <span id="hud-floor"></span>
      <span id="hud-room"></span>
      <span id="hud-gold"></span>
    </div>
    <div id="combat-arena">
      <div id="enemy-area">
        <div id="enemy-sprite"></div>
        <div id="enemy-info">
          <div id="enemy-name"></div>
          <div id="enemy-hp-bar"><div id="enemy-hp-fill"></div></div>
          <span id="enemy-hp-text"></span>
          <span id="enemy-statuses"></span>
        </div>
      </div>
      <div id="player-area">
        <div id="player-info">
          <span id="player-hp-text"></span>
          <div id="player-hp-bar"><div id="player-hp-fill"></div></div>
          <span id="player-defense"></span>
          <span id="player-statuses"></span>
        </div>
      </div>
    </div>
    <div id="combat-hand-area">
      <div id="hand-container"></div>
    </div>
    <div id="combat-controls">
      <button id="btn-end-turn">End Turn</button>
      <span id="hud-energy"></span>
    </div>
  </div>
  <div id="screen-shop" class="screen">
    <h2>The Merchant's Tent</h2>
    <p id="shop-gold-display"></p>
    <div id="shop-cards"></div>
    <button id="btn-leave-shop">Leave Shop</button>
  </div>
  <div id="screen-boss-intro" class="screen">
    <h2 id="boss-name"></h2>
    <p id="boss-desc"></p>
    <button id="btn-enter-combat">Enter Combat</button>
  </div>
  <div id="screen-game-over" class="screen">
    <h2 id="game-over-title"></h2>
    <p id="game-over-text"></p>
    <button id="btn-restart">Restart</button>
  </div>
  <div id="card-detail-overlay" class="overlay hidden">
    <div id="card-detail-content"></div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./ui":"src/ui.ts"},"src/cards.ts":{"./types":"src/types.ts"},"src/enemies.ts":{"./types":"src/types.ts"},"src/card-svg.ts":{"./types":"src/types.ts"},"src/game-state.ts":{"./types":"src/types.ts","./cards":"src/cards.ts","./enemies":"src/enemies.ts"},"src/combat.ts":{"./types":"src/types.ts","./game-state":"src/game-state.ts"},"src/ui.ts":{"./types":"src/types.ts","./game-state":"src/game-state.ts","./cards":"src/cards.ts","./combat":"src/combat.ts","./card-svg":"src/card-svg.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";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const UI = __importStar(require("./ui"));
// Initialize the game
UI.cacheElements();
UI.renderClassSelect();
// Wire up end turn button
const btn = document.getElementById('btn-end-turn');
if (btn) {
    btn.addEventListener('click', () => UI.onEndTurn());
}
// Keyboard shortcut: Space/Enter to end turn
document.addEventListener('keydown', (e) => {
    if (e.key === ' ' || e.key === 'Enter') {
        const gs = UI.getGameState();
        if (gs && gs.combatActive && gs.turnPhase === 'player') {
            UI.onEndTurn();
        }
    }
});
console.log('%c Shadows of Aethelgard ', 'background: #1a1520; color: #c9a84c; font-size: 16px; padding: 4px 8px; border-radius: 4px;');
};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
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.CLASS_INFO = exports.CLASS_STARTER_DECKS = exports.ALL_CARDS = void 0;
exports.createCardInstance = createCardInstance;
exports.getCardById = getCardById;
exports.getCardsByClass = getCardsByClass;
exports.ALL_CARDS = [
    // === BASTION (12 cards) ===
    { id: 'BA-A01', name: 'Slam', type: 'Attack', cost: 2, attack: 3, defense: 0, ability: 'If you have ≥30 HP, draw 1 card.', description: 'Deals 3 damage. If you have 30+ HP, draw 1 card.', classId: 'bastion' },
    { id: 'BA-A02', name: 'Earthquake', type: 'Attack', cost: 3, attack: 4, defense: 0, ability: 'Hits all enemies.', description: 'Deals 4 damage to the enemy. (AoE in multi-enemy combat.)', classId: 'bastion' },
    { id: 'BA-A03', name: 'Stone Fist', type: 'Attack', cost: 1, attack: 2, defense: 0, ability: '', description: 'Deals 2 damage.', classId: 'bastion' },
    { id: 'BA-A04', name: 'Boulder', type: 'Attack', cost: 4, attack: 6, defense: 0, ability: '', description: 'Deals 6 damage. Costs 4 Energy.', classId: 'bastion' },
    { id: 'BA-D01', name: 'Shield Wall', type: 'Defense', cost: 0, attack: 0, defense: 4, ability: '', description: 'Gain 4 Defense for this turn.', classId: 'bastion' },
    { id: 'BA-D02', name: 'Iron Skin', type: 'Defense', cost: 2, attack: 0, defense: 5, ability: 'Permanent. Discard 1 card.', description: 'Gain 5 Defense permanently (base). Discard 1 card.', classId: 'bastion' },
    { id: 'BA-D03', name: 'Stand Firm', type: 'Defense', cost: 1, attack: 0, defense: 2, ability: '', description: 'Gain 2 Defense for this turn.', classId: 'bastion' },
    { id: 'BA-D04', name: 'Taunt', type: 'Defense', cost: 2, attack: 0, defense: 3, ability: 'Stun enemy 1 turn.', description: 'Gain 3 Defense. Stun the enemy for 1 turn.', classId: 'bastion' },
    { id: 'BA-U01', name: 'Search', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Draw 1. Discard if >5.', description: 'Draw 1 card. If hand > 5 cards, discard down to 5.', classId: 'bastion' },
    { id: 'BA-U02', name: 'Repair', type: 'Other', cost: 3, attack: 0, defense: 0, ability: 'Heal 6 HP.', description: 'Restore 6 HP.', classId: 'bastion' },
    { id: 'BA-U03', name: 'Rally', type: 'Other', cost: 1, attack: 0, defense: 0, ability: 'Draw 1. Gain 1 Energy.', description: 'Draw 1 card and gain 1 Energy.', classId: 'bastion' },
    { id: 'BA-U04', name: 'Loot', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Gain 5 Gold.', description: 'Gain 5 Gold.', classId: 'bastion' },
    // === WITHER (12 cards) ===
    { id: 'WI-A01', name: 'Fireball', type: 'Attack', cost: 2, attack: 3, defense: 0, ability: 'Apply 1 Burn.', description: 'Deals 3 damage. Apply 1 Burn to target.', classId: 'wither' },
    { id: 'WI-A02', name: 'Inferno', type: 'Attack', cost: 3, attack: 4, defense: 0, ability: 'Apply 3 Burn.', description: 'Deals 4 damage. Applies 3 Burn to target.', classId: 'wither' },
    { id: 'WI-A03', name: 'Ember', type: 'Attack', cost: 1, attack: 2, defense: 0, ability: 'Apply 1 Burn/turn.', description: 'Deals 2 damage. Deals 1 damage every turn to target (Burn).', classId: 'wither' },
    { id: 'WI-A04', name: 'Pyre', type: 'Attack', cost: 4, attack: 5, defense: 0, ability: 'All enemies take 1 dmg next turn.', description: 'Deals 5 damage. All enemies take 1 extra damage next turn.', classId: 'wither' },
    { id: 'WI-D01', name: 'Scorch Shield', type: 'Defense', cost: 2, attack: 0, defense: 3, ability: '', description: 'Gain 3 Defense for this turn.', classId: 'wither' },
    { id: 'WI-D02', name: 'Heat Resist', type: 'Defense', cost: 1, attack: 0, defense: 1, ability: 'Gain 1 Energy.', description: 'Gain 1 Defense. Gain 1 Energy.', classId: 'wither' },
    { id: 'WI-D03', name: 'Smoke', type: 'Defense', cost: 2, attack: 0, defense: 4, ability: '', description: 'Gain 4 Defense for this turn.', classId: 'wither' },
    { id: 'WI-D04', name: 'Endure', type: 'Defense', cost: 3, attack: 0, defense: 2, ability: 'Heal 2 HP.', description: 'Gain 2 Defense. Heal 2 HP.', classId: 'wither' },
    { id: 'WI-U01', name: 'Poison', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Apply 1 Poison.', description: 'Apply 1 Poison to target (1 dmg/turn).', classId: 'wither' },
    { id: 'WI-U02', name: 'Burn Wave', type: 'Other', cost: 3, attack: 0, defense: 0, ability: '3 dmg + 2 Burn all enemies.', description: 'Deal 3 damage and apply 2 Burn to the enemy.', classId: 'wither' },
    { id: 'WI-U03', name: 'Slow', type: 'Other', cost: 1, attack: 0, defense: 0, ability: 'Draw 1. Stun target.', description: 'Draw 1 card. Target is stunned next turn.', classId: 'wither' },
    { id: 'WI-U04', name: 'Wither', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Heal 5 HP.', description: 'Restore 5 HP.', classId: 'wither' },
    // === SHROUD (12 cards) ===
    { id: 'SH-A01', name: 'Backstab', type: 'Attack', cost: 2, attack: 4, defense: 0, ability: '+1 dmg if target has no Defense.', description: 'Deals 4 damage. +1 Damage if target has no Defense.', classId: 'shroud' },
    { id: 'SH-A02', name: 'Shadow Bolt', type: 'Attack', cost: 3, attack: 5, defense: 0, ability: 'Cannot be blocked.', description: 'Deals 5 damage. Target cannot block this damage.', classId: 'shroud' },
    { id: 'SH-A03', name: 'Silence', type: 'Attack', cost: 1, attack: 3, defense: 0, ability: 'Stun target 1 turn.', description: 'Deals 3 damage. Target is stunned for 1 turn.', classId: 'shroud' },
    { id: 'SH-A04', name: 'Vengeance', type: 'Attack', cost: 3, attack: 6, defense: 0, ability: 'Gain 2 Energy.', description: 'Deals 6 damage. Gain 2 Energy after playing.', classId: 'shroud' },
    { id: 'SH-D01', name: 'Blur', type: 'Defense', cost: 3, attack: 0, defense: 2, ability: '', description: 'Gain 2 Defense for this turn.', classId: 'shroud' },
    { id: 'SH-D02', name: 'Step', type: 'Defense', cost: 2, attack: 0, defense: 3, ability: 'Gain 1 Energy.', description: 'Gain 3 Defense. Gain 1 Energy.', classId: 'shroud' },
    { id: 'SH-D03', name: 'Evasion', type: 'Defense', cost: 1, attack: 0, defense: 1, ability: '', description: 'Gain 1 Defense for this turn.', classId: 'shroud' },
    { id: 'SH-D04', name: 'Phantom', type: 'Defense', cost: 2, attack: 0, defense: 4, ability: '', description: 'Gain 4 Defense. Ghost form for 1 turn.', classId: 'shroud' },
    { id: 'SH-U01', name: 'Dash', type: 'Other', cost: 1, attack: 0, defense: 0, ability: 'Gain 1 Energy.', description: 'Gain 1 Energy.', classId: 'shroud' },
    { id: 'SH-U02', name: 'Teleport', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Deal 2 damage.', description: 'Deal 2 damage to the enemy.', classId: 'shroud' },
    { id: 'SH-U03', name: 'Hide', type: 'Other', cost: 2, attack: 0, defense: 0, ability: 'Gain 1 Def. Discard 1. Draw 2.', description: 'Gain 1 Defense. Discard 1 card. Draw 2 cards.', classId: 'shroud' },
    { id: 'SH-U04', name: 'Assassinate', type: 'Other', cost: 3, attack: 0, defense: 0, ability: 'Deal 8 unblockable damage.', description: 'Deal 8 damage. Cannot be blocked this turn.', classId: 'shroud' },
];
exports.CLASS_STARTER_DECKS = {
    bastion: ['BA-A01', 'BA-A03', 'BA-A03', 'BA-D01', 'BA-D01', 'BA-D03', 'BA-U02', 'BA-U03', 'BA-U04', 'BA-A02'],
    wither: ['WI-A01', 'WI-A03', 'WI-A03', 'WI-D01', 'WI-D02', 'WI-U01', 'WI-U02', 'WI-U03', 'WI-U04', 'WI-A02'],
    shroud: ['SH-A01', 'SH-A03', 'SH-A03', 'SH-D01', 'SH-D03', 'SH-U01', 'SH-U02', 'SH-U03', 'SH-U04', 'SH-A02'],
};
exports.CLASS_INFO = {
    bastion: { name: 'The Bastion', theme: 'Immovable Object', hp: 35, energy: 3, handSize: 4 },
    wither: { name: 'The Wither', theme: 'Rot and Burn', hp: 30, energy: 3, handSize: 4 },
    shroud: { name: 'The Shroud', theme: 'Shadow and Speed', hp: 28, energy: 3, handSize: 4 },
};
let uidCounter = 0;
function createCardInstance(def) {
    return { def, uid: `card_${def.id}_${uidCounter++}` };
}
function getCardById(id) {
    return exports.ALL_CARDS.find(c => c.id === id);
}
function getCardsByClass(classId) {
    return exports.ALL_CARDS.filter(c => c.classId === classId);
}
};

// ── module: src/enemies.ts ──
__mods["src/enemies.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BOSS_DESCRIPTIONS = exports.BOSS_TYPES = exports.BOSS_DEFS = exports.FLOOR_ENEMIES = void 0;
exports.getRandomEnemy = getRandomEnemy;
exports.FLOOR_ENEMIES = {
    1: [
        { name: 'Void Goblin', hp: 20, damage: 3, spriteColor: '#4a7a3a', spriteShape: 'goblin' },
        { name: 'Shadow Rat', hp: 15, damage: 2, spriteColor: '#6a5a4a', spriteShape: 'goblin' },
        { name: 'Dark Imp', hp: 18, damage: 3, spriteColor: '#7a3a3a', spriteShape: 'goblin' },
    ],
    2: [
        { name: 'Vampire Knight', hp: 30, damage: 5, spriteColor: '#6a2a4a', spriteShape: 'knight' },
        { name: 'Void Ogre', hp: 35, damage: 6, spriteColor: '#4a4a6a', spriteShape: 'knight' },
        { name: 'Cursed Warrior', hp: 28, damage: 5, spriteColor: '#5a3a5a', spriteShape: 'knight' },
    ],
    3: [
        { name: 'Void Warrior', hp: 40, damage: 8, spriteColor: '#3a2a6a', spriteShape: 'warrior' },
        { name: 'Dark Cultist', hp: 35, damage: 7, spriteColor: '#5a2a5a', spriteShape: 'warrior' },
        { name: 'Abyssal Knight', hp: 45, damage: 9, spriteColor: '#2a3a6a', spriteShape: 'warrior' },
    ],
};
exports.BOSS_DEFS = {
    1: { name: 'The Gatekeeper', hp: 80, damage: 15, spriteColor: '#8b7355', spriteShape: 'boss' },
    2: { name: 'The Corrupted', hp: 80, damage: 15, spriteColor: '#6a1a3a', spriteShape: 'boss' },
    3: { name: 'The Void Lord', hp: 80, damage: 15, spriteColor: '#2a1a4a', spriteShape: 'boss' },
};
exports.BOSS_TYPES = {
    1: 'gatekeeper',
    2: 'corrupted',
    3: 'voidlord',
};
exports.BOSS_DESCRIPTIONS = {
    1: '"Blocks 5 damage per turn until HP drops below 20. An immovable force guarding the descent."',
    2: '"Deals devastating 20 damage if you have less than 3 Energy. A corrupted knight consumed by darkness."',
    3: '"Regenerates 10 HP per turn and cannot be blocked. The final horror that consumes all of Aethelgard."',
};
function getRandomEnemy(floor) {
    const enemies = exports.FLOOR_ENEMIES[floor] || exports.FLOOR_ENEMIES[1];
    return enemies[Math.floor(Math.random() * enemies.length)];
}
};

// ── module: src/card-svg.ts ──
__mods["src/card-svg.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCardSVG = createCardSVG;
exports.createCardBackSVG = createCardBackSVG;
exports.createEnemySVG = createEnemySVG;
const CLASS_COLORS = {
    bastion: { primary: '#c9a84c', secondary: '#8b7355', accent: '#d4af37', glow: 'rgba(201,168,76,0.4)' },
    wither: { primary: '#cc3300', secondary: '#8b2500', accent: '#ff6600', glow: 'rgba(204,51,0,0.4)' },
    shroud: { primary: '#7a3abf', secondary: '#4a1a7a', accent: '#aa44ff', glow: 'rgba(122,58,191,0.4)' },
};
const TYPE_COLORS = {
    Attack: '#cc3300',
    Defense: '#4a9eff',
    Other: '#aa44ff',
};
function createCardSVG(cardDef, faceUp = true) {
    const colors = CLASS_COLORS[cardDef.classId];
    if (!faceUp)
        return createCardBackSVG(cardDef.classId);
    const typeColor = TYPE_COLORS[cardDef.type] || '#888';
    const statsText = cardDef.attack > 0 ? `⚔${cardDef.attack}` : (cardDef.defense > 0 ? `🛡${cardDef.defense}` : '');
    return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="195" viewBox="0 0 140 195">
    <defs>
      <linearGradient id="bg_${cardDef.id}" x1="0%" y1="0%" x2="100%" y2="100%">
        <stop offset="0%" style="stop-color:#1a1520"/>
        <stop offset="100%" style="stop-color:#0d0b10"/>
      </linearGradient>
      <filter id="glow_${cardDef.id}">
        <feGaussianBlur stdDeviation="2" result="blur"/>
        <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
      </filter>
    </defs>
    <!-- Card background -->
    <rect x="1" y="1" width="138" height="193" rx="8" fill="url(#bg_${cardDef.id})"/>
    <!-- Outer border with class color -->
    <rect x="2" y="2" width="136" height="191" rx="7" fill="none" stroke="${colors.primary}" stroke-width="2.5" filter="url(#glow_${cardDef.id})"/>
    <!-- Inner decorative border -->
    <rect x="6" y="6" width="128" height="183" rx="4" fill="none" stroke="${colors.secondary}" stroke-width="0.7" opacity="0.5"/>
    
    ${getClassFrameDecoration(cardDef.classId, colors)}
    
    <!-- Cost badge -->
    <circle cx="22" cy="22" r="13" fill="#1a1520" stroke="${colors.primary}" stroke-width="1.5"/>
    <text x="22" y="27" text-anchor="middle" font-size="14" font-weight="bold" fill="${colors.accent}" font-family="Georgia">${cardDef.cost}</text>
    
    <!-- Card name -->
    <text x="70" y="28" text-anchor="middle" font-size="11" font-weight="bold" fill="${colors.primary}" font-family="Georgia">${cardDef.name}</text>
    
    <!-- Type label -->
    <rect x="50" y="34" width="40" height="12" rx="3" fill="${typeColor}" opacity="0.3"/>
    <text x="70" y="43" text-anchor="middle" font-size="8" fill="${typeColor}" font-family="Georgia">${cardDef.type}</text>
    
    <!-- Card image placeholder area -->
    <rect x="12" y="52" width="116" height="70" rx="4" fill="#0a0810" stroke="${colors.secondary}" stroke-width="0.5"/>
    ${getClassImagePlaceholder(cardDef.classId, colors)}
    
    <!-- Stats -->
    <text x="70" y="132" text-anchor="middle" font-size="16" font-weight="bold" fill="${typeColor}" font-family="Georgia">${statsText}</text>
    
    <!-- Divider line -->
    <line x1="15" y1="140" x2="125" y2="140" stroke="${colors.secondary}" stroke-width="0.5" opacity="0.6"/>
    
    <!-- Ability text -->
    <text x="70" y="152" text-anchor="middle" font-size="7.5" fill="#bbb" font-family="Georgia">${cardDef.description.length > 40 ? cardDef.description.substring(0, 38) + '...' : cardDef.description}</text>
    <text x="70" y="162" text-anchor="middle" font-size="7.5" fill="#999" font-family="Georgia">${cardDef.description.length > 40 ? cardDef.description.substring(38) : ''}</text>
    
    <!-- Bottom decorative element -->
    <text x="70" y="182" text-anchor="middle" font-size="6" fill="${colors.secondary}" opacity="0.5">${cardDef.id}</text>
  </svg>`;
}
function getClassFrameDecoration(classId, colors) {
    switch (classId) {
        case 'bastion':
            return `
        <!-- Bastion: Shield corner ornaments -->
        <path d="M6,12 L14,6 L20,12" fill="none" stroke="${colors.primary}" stroke-width="1.5"/>
        <path d="M134,12 L126,6 L120,12" fill="none" stroke="${colors.primary}" stroke-width="1.5"/>
        <!-- Shield motif top center -->
        <polygon points="70,8 74,12 74,18 70,22 66,18 66,12" fill="none" stroke="${colors.primary}" stroke-width="0.8"/>
      `;
        case 'wither':
            return `
        <!-- Wither: Flame corner ornaments -->
        <path d="M6,14 Q10,8 14,14 Q12,10 10,14" fill="${colors.primary}" opacity="0.5"/>
        <path d="M134,14 Q130,8 126,14 Q128,10 130,14" fill="${colors.primary}" opacity="0.5"/>
        <!-- Flame motif top center -->
        <circle cx="70" cy="12" r="3" fill="${colors.primary}" opacity="0.3"/>
      `;
        case 'shroud':
            return `
        <!-- Shroud: Shadow corner ornaments -->
        <path d="M6,14 L10,8 L14,14 Z" fill="${colors.primary}" opacity="0.3"/>
        <path d="M134,14 L130,8 L126,14 Z" fill="${colors.primary}" opacity="0.3"/>
        <!-- Eye motif top center -->
        <ellipse cx="70" cy="12" rx="5" ry="3" fill="none" stroke="${colors.primary}" stroke-width="0.8"/>
        <circle cx="70" cy="12" r="1.5" fill="${colors.primary}" opacity="0.6"/>
      `;
    }
}
function getClassImagePlaceholder(classId, colors) {
    switch (classId) {
        case 'bastion':
            return `
        <!-- Bastion: Shield emblem -->
        <polygon points="70,62 85,72 85,90 70,100 55,90 55,72" fill="none" stroke="${colors.primary}" stroke-width="1.5" opacity="0.4"/>
        <polygon points="70,68 80,75 80,88 70,95 60,88 60,75" fill="${colors.secondary}" opacity="0.2"/>
        <line x1="70" y1="68" x2="70" y2="95" stroke="${colors.primary}" stroke-width="0.8" opacity="0.3"/>
      `;
        case 'wither':
            return `
        <!-- Wither: Flame/skull emblem -->
        <circle cx="70" cy="85" r="12" fill="none" stroke="${colors.primary}" stroke-width="1.5" opacity="0.4"/>
        <path d="M62,90 Q65,75 70,82 Q75,75 78,90" fill="${colors.accent}" opacity="0.3"/>
        <circle cx="66" cy="83" r="2" fill="${colors.primary}" opacity="0.4"/>
        <circle cx="74" cy="83" r="2" fill="${colors.primary}" opacity="0.4"/>
      `;
        case 'shroud':
            return `
        <!-- Shroud: Shadow/eye emblem -->
        <ellipse cx="70" cy="85" rx="16" ry="10" fill="none" stroke="${colors.primary}" stroke-width="1.5" opacity="0.4"/>
        <circle cx="70" cy="85" r="6" fill="${colors.secondary}" opacity="0.3"/>
        <circle cx="70" cy="85" r="3" fill="${colors.primary}" opacity="0.5"/>
        <!-- Shadow wisps -->
        <path d="M48,85 Q52,78 56,85" fill="none" stroke="${colors.primary}" stroke-width="0.8" opacity="0.3"/>
        <path d="M84,85 Q88,92 92,85" fill="none" stroke="${colors.primary}" stroke-width="0.8" opacity="0.3"/>
      `;
    }
}
function createCardBackSVG(classId) {
    const colors = CLASS_COLORS[classId];
    switch (classId) {
        case 'bastion':
            return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="195" viewBox="0 0 140 195">
        <rect x="1" y="1" width="138" height="193" rx="8" fill="#1a1520"/>
        <rect x="2" y="2" width="136" height="191" rx="7" fill="none" stroke="${colors.primary}" stroke-width="2.5"/>
        <!-- Bastion card back: Shield pattern -->
        <polygon points="70,25 110,45 110,100 70,130 30,100 30,45" fill="none" stroke="${colors.primary}" stroke-width="2"/>
        <polygon points="70,35 100,50 100,95 70,118 40,95 40,50" fill="${colors.secondary}" opacity="0.15"/>
        <!-- Cross pattern -->
        <line x1="70" y1="35" x2="70" y2="118" stroke="${colors.primary}" stroke-width="1" opacity="0.4"/>
        <line x1="40" y1="76" x2="100" y2="76" stroke="${colors.primary}" stroke-width="1" opacity="0.4"/>
        <!-- Corner ornaments -->
        <circle cx="35" cy="35" r="4" fill="none" stroke="${colors.secondary}" stroke-width="0.8"/>
        <circle cx="105" cy="35" r="4" fill="none" stroke="${colors.secondary}" stroke-width="0.8"/>
        <circle cx="35" cy="160" r="4" fill="none" stroke="${colors.secondary}" stroke-width="0.8"/>
        <circle cx="105" cy="160" r="4" fill="none" stroke="${colors.secondary}" stroke-width="0.8"/>
      </svg>`;
        case 'wither':
            return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="195" viewBox="0 0 140 195">
        <rect x="1" y="1" width="138" height="193" rx="8" fill="#1a1520"/>
        <rect x="2" y="2" width="136" height="191" rx="7" fill="none" stroke="${colors.primary}" stroke-width="2.5"/>
        <!-- Wither card back: Flame pattern -->
        <path d="M70,30 Q85,50 80,70 Q90,65 85,80 Q95,75 88,95 Q100,90 90,110 Q80,120 70,130" fill="none" stroke="${colors.primary}" stroke-width="2"/>
        <path d="M70,30 Q55,50 60,70 Q50,65 55,80 Q45,75 52,95 Q40,90 50,110 Q60,120 70,130" fill="none" stroke="${colors.primary}" stroke-width="2"/>
        <!-- Inner flame glow -->
        <path d="M70,45 Q80,60 76,75 Q82,72 78,85 Q86,82 80,98 Q88,95 82,108" fill="${colors.accent}" opacity="0.1"/>
        <path d="M70,45 Q60,60 64,75 Q58,72 62,85 Q54,82 60,98 Q52,95 58,108" fill="${colors.accent}" opacity="0.1"/>
        <!-- Ember dots -->
        <circle cx="70" cy="35" r="2" fill="${colors.accent}" opacity="0.4"/>
        <circle cx="60" cy="40" r="1.5" fill="${colors.primary}" opacity="0.3"/>
        <circle cx="80" cy="40" r="1.5" fill="${colors.primary}" opacity="0.3"/>
      </svg>`;
        case 'shroud':
            return `<svg xmlns="http://www.w3.org/2000/svg" width="140" height="195" viewBox="0 0 140 195">
        <rect x="1" y="1" width="138" height="193" rx="8" fill="#1a1520"/>
        <rect x="2" y="2" width="136" height="191" rx="7" fill="none" stroke="${colors.primary}" stroke-width="2.5"/>
        <!-- Shroud card back: Shadow eye pattern -->
        <ellipse cx="70" cy="80" rx="35" ry="22" fill="none" stroke="${colors.primary}" stroke-width="2"/>
        <circle cx="70" cy="80" r="14" fill="${colors.secondary}" opacity="0.2"/>
        <circle cx="70" cy="80" r="8" fill="${colors.primary}" opacity="0.3"/>
        <circle cx="70" cy="80" r="4" fill="#0a0810"/>
        <!-- Shadow wisps -->
        <path d="M35,80 Q25,65 30,50" fill="none" stroke="${colors.primary}" stroke-width="1.5" opacity="0.3"/>
        <path d="M105,80 Q115,95 110,110" fill="none" stroke="${colors.primary}" stroke-width="1.5" opacity="0.3"/>
        <path d="M42,62 Q35,55 38,45" fill="none" stroke="${colors.secondary}" stroke-width="1" opacity="0.2"/>
        <path d="M98,98 Q105,105 102,115" fill="none" stroke="${colors.secondary}" stroke-width="1" opacity="0.2"/>
      </svg>`;
    }
}
function createEnemySVG(enemyDef, isBoss) {
    const scale = isBoss ? 1.3 : 1;
    const w = Math.round(80 * scale);
    const h = Math.round(100 * scale);
    let bodyPath = '';
    switch (enemyDef.spriteShape) {
        case 'goblin':
            bodyPath = `<ellipse cx="${w / 2}" cy="${h * 0.65}" rx="${w * 0.35}" ry="${h * 0.3}" fill="${enemyDef.spriteColor}"/>
        <circle cx="${w / 2}" cy="${h * 0.35}" r="${w * 0.22}" fill="${enemyDef.spriteColor}"/>
        <polygon points="${w * 0.3},${h * 0.28} ${w * 0.4},${h * 0.15} ${w * 0.45},${h * 0.3}" fill="${enemyDef.spriteColor}"/>
        <polygon points="${w * 0.7},${h * 0.28} ${w * 0.6},${h * 0.15} ${w * 0.55},${h * 0.3}" fill="${enemyDef.spriteColor}"/>`;
            break;
        case 'knight':
            bodyPath = `<rect x="${w * 0.25}" y="${h * 0.4}" width="${w * 0.5}" height="${h * 0.45}" rx="8" fill="${enemyDef.spriteColor}"/>
        <circle cx="${w / 2}" cy="${h * 0.3}" r="${w * 0.25}" fill="${enemyDef.spriteColor}"/>
        <rect x="${w * 0.15}" y="${h * 0.45}" width="${w * 0.15}" height="${h * 0.3}" rx="4" fill="${enemyDef.spriteColor}"/>
        <rect x="${w * 0.7}" y="${h * 0.45}" width="${w * 0.15}" height="${h * 0.3}" rx="4" fill="${enemyDef.spriteColor}"/>`;
            break;
        case 'warrior':
            bodyPath = `<polygon points="${w / 2},${h * 0.15} ${w * 0.85},${h * 0.9} ${w * 0.15},${h * 0.9}" fill="${enemyDef.spriteColor}"/>
        <circle cx="${w / 2}" cy="${h * 0.3}" r="${w * 0.2}" fill="${enemyDef.spriteColor}"/>`;
            break;
        case 'boss':
            bodyPath = `<rect x="${w * 0.2}" y="${h * 0.35}" width="${w * 0.6}" height="${h * 0.5}" rx="10" fill="${enemyDef.spriteColor}"/>
        <circle cx="${w / 2}" cy="${h * 0.25}" r="${w * 0.28}" fill="${enemyDef.spriteColor}"/>
        <rect x="${w * 0.1}" y="${h * 0.4}" width="${w * 0.18}" height="${h * 0.35}" rx="6" fill="${enemyDef.spriteColor}"/>
        <rect x="${w * 0.72}" y="${h * 0.4}" width="${w * 0.18}" height="${h * 0.35}" rx="6" fill="${enemyDef.spriteColor}"/>`;
            break;
    }
    const eyeColor = isBoss ? '#ff3300' : '#ffcc00';
    return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
    <defs>
      <filter id="enemyGlow"><feGaussianBlur stdDeviation="3" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
    </defs>
    <g filter="url(#enemyGlow)">
      ${bodyPath}
      <!-- Eyes -->
      <circle cx="${w * 0.4}" cy="${h * 0.32}" r="${isBoss ? 5 : 3}" fill="${eyeColor}"/>
      <circle cx="${w * 0.6}" cy="${h * 0.32}" r="${isBoss ? 5 : 3}" fill="${eyeColor}"/>
    </g>
    ${isBoss ? `<text x="${w / 2}" y="${h + 14}" text-anchor="middle" font-size="8" fill="#cc3300" font-family="Georgia">★ BOSS ★</text>` : ''}
  </svg>`;
}
};

// ── module: src/game-state.ts ──
__mods["src/game-state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.createEnemyState = createEnemyState;
exports.createInitialState = createInitialState;
exports.shuffle = shuffle;
exports.drawCards = drawCards;
exports.discardHand = discardHand;
exports.applyEnemyStatusEffects = applyEnemyStatusEffects;
exports.applyPlayerStatusEffects = applyPlayerStatusEffects;
exports.tickStatusEffects = tickStatusEffects;
exports.getBossDamage = getBossDamage;
exports.applyBossStartTurn = applyBossStartTurn;
exports.canPlayCard = canPlayCard;
exports.getGoldReward = getGoldReward;
const cards_1 = require("./cards");
const enemies_1 = require("./enemies");
function cloneStatus(s) {
    return { burn: s.burn, poison: s.poison, stun: s.stun, silence: s.silence };
}
function createPlayer(classId) {
    const info = cards_1.CLASS_INFO[classId];
    const starterIds = cards_1.CLASS_STARTER_DECKS[classId];
    // Build inventory count
    const inventory = {};
    for (const id of starterIds) {
        inventory[id] = (inventory[id] || 0) + 1;
    }
    // Create deck instances
    const deck = [];
    for (const id of starterIds) {
        const def = (0, cards_1.getCardById)(id);
        if (def)
            deck.push((0, cards_1.createCardInstance)(def));
    }
    return {
        classId,
        hp: info.hp,
        maxHp: info.hp,
        energy: info.energy,
        maxEnergy: info.energy,
        handSize: info.handSize,
        gold: 0,
        defense: 0,
        status: { burn: 0, poison: 0, stun: 0, silence: 0 },
        deck,
        discardPile: [],
        inventory,
    };
}
function createEnemyState(floor, isBoss) {
    const def = isBoss ? enemies_1.BOSS_DEFS[floor] : (0, enemies_1.getRandomEnemy)(floor);
    return {
        def,
        currentHp: def.hp,
        maxHp: def.hp,
        defense: 0,
        status: { burn: 0, poison: 0, stun: 0, silence: 0 },
        isBoss,
        bossType: isBoss ? enemies_1.BOSS_TYPES[floor] : undefined,
    };
}
function createInitialState(classId) {
    return {
        player: createPlayer(classId),
        enemy: null,
        hand: [],
        floor: 1,
        room: 0, // 0 = before first room
        isBossRoom: false,
        turnPhase: 'player',
        combatActive: false,
        gameOver: false,
    };
}
// Shuffle array in place (Fisher-Yates)
function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
}
function drawCards(state, count) {
    // Move cards from discard to deck if needed
    if (state.player.deck.length === 0 && state.player.discardPile.length > 0) {
        state.player.deck.push(...state.player.discardPile);
        state.player.discardPile = [];
        shuffle(state.player.deck);
    }
    for (let i = 0; i < count; i++) {
        if (state.player.deck.length === 0)
            break;
        const spaceLeft = state.player.handSize - state.hand.length;
        if (spaceLeft <= 0)
            break;
        const card = state.player.deck.pop();
        state.hand.push(card);
    }
}
function discardHand(state) {
    state.player.discardPile.push(...state.hand);
    state.hand = [];
}
// Apply status effects at start of enemy turn (burn/poison damage to enemy)
function applyEnemyStatusEffects(enemy) {
    let totalDmg = 0;
    if (enemy.status.burn > 0) {
        totalDmg += enemy.status.burn;
        enemy.currentHp -= enemy.status.burn;
        enemy.status.burn--;
    }
    if (enemy.status.poison > 0) {
        totalDmg += enemy.status.poison;
        enemy.currentHp -= enemy.status.poison;
        enemy.status.poison--;
    }
    return totalDmg;
}
// Apply status effects at start of player turn (burn/poison damage to player)
function applyPlayerStatusEffects(player) {
    let totalDmg = 0;
    if (player.status.burn > 0) {
        totalDmg += player.status.burn;
        player.hp -= player.status.burn;
        player.status.burn--;
    }
    if (player.status.poison > 0) {
        totalDmg += player.status.poison;
        player.hp -= player.status.poison;
        player.status.poison--;
    }
    return totalDmg;
}
// Decrease stun/silence counters at end of turn
function tickStatusEffects(status) {
    if (status.stun > 0)
        status.stun--;
    if (status.silence > 0)
        status.silence--;
}
// Boss-specific behavior during enemy action
function getBossDamage(enemy, playerEnergy) {
    switch (enemy.bossType) {
        case 'gatekeeper':
            // Normal damage
            return enemy.def.damage;
        case 'corrupted':
            // Deals 20 if player has < 3 energy
            return playerEnergy < 3 ? 20 : enemy.def.damage;
        case 'voidlord':
            // Normal damage, but also regenerates
            return enemy.def.damage;
        default:
            return enemy.def.damage;
    }
}
// Boss-specific behavior at start of their turn
function applyBossStartTurn(enemy) {
    switch (enemy.bossType) {
        case 'gatekeeper':
            // Blocks 5 damage per turn until HP < 20
            if (enemy.currentHp >= 20) {
                enemy.defense = Math.max(enemy.defense, 5);
            }
            break;
        case 'voidlord':
            // Regenerates 10 HP per turn
            enemy.currentHp = Math.min(enemy.maxHp + 40, enemy.currentHp + 10);
            if (enemy.currentHp > enemy.maxHp) {
                enemy.maxHp = enemy.currentHp;
            }
            break;
        case 'corrupted':
            // No special start-of-turn behavior
            break;
    }
}
// Check if a card can be played
function canPlayCard(card, player) {
    return card.def.cost <= player.energy;
}
// Get gold reward for killing an enemy
function getGoldReward(enemy) {
    return enemy.isBoss ? 15 : 5;
}
};

// ── module: src/combat.ts ──
__mods["src/combat.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.playCard = playCard;
exports.executeEnemyTurn = executeEnemyTurn;
exports.startCombat = startCombat;
exports.endPlayerTurn = endPlayerTurn;
exports.handleCardSideEffects = handleCardSideEffects;
const GS = __importStar(require("./game-state"));
// Play a card and resolve its effects
function playCard(card, player, enemy) {
    const logs = [];
    // Deduct energy
    if (card.def.cost > player.energy)
        return [{ type: 'message', text: 'Not enough energy!' }];
    player.energy -= card.def.cost;
    switch (card.def.type) {
        case 'Attack':
            resolveAttackCard(card, player, enemy, logs);
            break;
        case 'Defense':
            resolveDefenseCard(card, player, enemy, logs);
            break;
        case 'Other':
            resolveOtherCard(card, player, enemy, logs);
            break;
    }
    return logs;
}
function resolveAttackCard(card, _player, enemy, logs) {
    let damage = card.def.attack;
    let unblockable = false;
    // Special abilities per card
    switch (card.def.id) {
        case 'BA-A01': // Slam: If player has >=30 HP, draw 1
            break; // Draw handled in UI after combat resolution
        case 'SH-A01': // Backstab: +1 if target has no defense
            if (enemy.defense <= 0)
                damage += 1;
            break;
        case 'SH-A02': // Shadow Bolt: Cannot be blocked
            unblockable = true;
            break;
        case 'SH-A03': // Silence: Stun target
            enemy.status.stun = Math.max(enemy.status.stun, 1);
            logs.push({ type: 'status', text: 'Stunned!', target: 'enemy' });
            break;
        case 'WI-A01': // Fireball: Apply 1 Burn
            enemy.status.burn += 1;
            logs.push({ type: 'status', text: '+1 Burn', target: 'enemy' });
            break;
        case 'WI-A02': // Inferno: Apply 3 Burn
            enemy.status.burn += 3;
            logs.push({ type: 'status', text: '+3 Burn', target: 'enemy' });
            break;
        case 'WI-A03': // Ember: Apply 1 Burn/turn (same as burn)
            enemy.status.burn += 1;
            logs.push({ type: 'status', text: '+1 Burn', target: 'enemy' });
            break;
        case 'WI-A04': // Pyre: All enemies take 1 extra next turn (simplified to +2 burn)
            enemy.status.burn += 2;
            logs.push({ type: 'status', text: '+2 Burn', target: 'enemy' });
            break;
    }
    // Apply damage
    if (!unblockable && enemy.defense > 0) {
        const blocked = Math.min(damage, enemy.defense);
        enemy.defense -= blocked;
        damage -= blocked;
        if (blocked > 0)
            logs.push({ type: 'defense', text: `${blocked} Blocked`, target: 'enemy' });
    }
    if (damage > 0) {
        enemy.currentHp -= damage;
        logs.push({ type: 'damage', text: `-${damage}`, target: 'enemy' });
    }
    else if (!unblockable) {
        logs.push({ type: 'message', text: 'Fully blocked!' });
    }
    else {
        // Unblockable still deals full damage
        enemy.currentHp -= card.def.attack;
        logs.push({ type: 'damage', text: `-${card.def.attack} (unblockable)`, target: 'enemy' });
    }
    if (enemy.currentHp < 0)
        enemy.currentHp = 0;
}
function resolveDefenseCard(card, player, _enemy, logs) {
    switch (card.def.id) {
        case 'BA-D02': // Iron Skin: Permanent defense + discard 1 (handled by UI)
            player.maxHp += card.def.defense; // Simplified: permanent = increase max HP as buffer
            player.hp += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Permanent`, target: 'player' });
            logs.push({ type: 'discard', text: 'Discarded 1 card', target: 'player' });
            break;
        case 'BA-D04': // Taunt: Stun enemy
            player.defense += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Def`, target: 'player' });
            // Stun handled in UI
            break;
        case 'WI-D04': // Endure: Heal 2 HP
            player.defense += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Def`, target: 'player' });
            player.hp = Math.min(player.maxHp, player.hp + 2);
            logs.push({ type: 'heal', text: '+2 HP', target: 'player' });
            break;
        case 'WI-D02': // Heat Resist: Gain 1 Energy
            player.defense += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Def`, target: 'player' });
            player.energy += 1;
            logs.push({ type: 'energy', text: '+1 Energy', target: 'player' });
            break;
        case 'SH-D02': // Step: Gain 1 Energy
            player.defense += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Def`, target: 'player' });
            player.energy += 1;
            logs.push({ type: 'energy', text: '+1 Energy', target: 'player' });
            break;
        default:
            // Standard defense card
            player.defense += card.def.defense;
            logs.push({ type: 'defense', text: `+${card.def.defense} Def`, target: 'player' });
    }
}
function resolveOtherCard(card, player, enemy, logs) {
    switch (card.def.id) {
        case 'BA-U01': // Search: Draw 1, discard if >5
            logs.push({ type: 'draw', text: '+1 Draw', target: 'player' });
            break;
        case 'BA-U02': // Repair: Heal 6 HP
            player.hp = Math.min(player.maxHp, player.hp + 6);
            logs.push({ type: 'heal', text: '+6 HP', target: 'player' });
            break;
        case 'BA-U03': // Rally: Draw 1, Gain 1 Energy
            logs.push({ type: 'draw', text: '+1 Draw', target: 'player' });
            player.energy += 1;
            logs.push({ type: 'energy', text: '+1 Energy', target: 'player' });
            break;
        case 'BA-U04': // Loot: Gain 5 Gold
            player.gold += 5;
            logs.push({ type: 'gold', text: '+5 Gold', target: 'player' });
            break;
        case 'WI-U01': // Poison: Apply 1 Poison
            enemy.status.poison += 1;
            logs.push({ type: 'status', text: '+1 Poison', target: 'enemy' });
            break;
        case 'WI-U02': // Burn Wave: 3 dmg + 2 Burn
            let dmg = 3;
            if (enemy.defense > 0) {
                const blocked = Math.min(dmg, enemy.defense);
                enemy.defense -= blocked;
                dmg -= blocked;
            }
            if (dmg > 0) {
                enemy.currentHp -= dmg;
                logs.push({ type: 'damage', text: `-${dmg}`, target: 'enemy' });
            }
            enemy.status.burn += 2;
            logs.push({ type: 'status', text: '+2 Burn', target: 'enemy' });
            break;
        case 'WI-U03': // Slow: Draw 1, Stun target
            logs.push({ type: 'draw', text: '+1 Draw', target: 'player' });
            enemy.status.stun = Math.max(enemy.status.stun, 1);
            logs.push({ type: 'status', text: 'Stunned!', target: 'enemy' });
            break;
        case 'WI-U04': // Wither: Heal 5 HP
            player.hp = Math.min(player.maxHp, player.hp + 5);
            logs.push({ type: 'heal', text: '+5 HP', target: 'player' });
            break;
        case 'SH-U01': // Dash: Gain 1 Energy
            player.energy += 1;
            logs.push({ type: 'energy', text: '+1 Energy', target: 'player' });
            break;
        case 'SH-U02': // Teleport: Deal 2 damage
            let tDmg = 2;
            if (enemy.defense > 0) {
                const blocked = Math.min(tDmg, enemy.defense);
                enemy.defense -= blocked;
                tDmg -= blocked;
            }
            if (tDmg > 0) {
                enemy.currentHp -= tDmg;
                logs.push({ type: 'damage', text: `-${tDmg}`, target: 'enemy' });
            }
            break;
        case 'SH-U03': // Hide: Gain 1 Def, Discard 1, Draw 2
            player.defense += 1;
            logs.push({ type: 'defense', text: '+1 Def', target: 'player' });
            logs.push({ type: 'discard', text: 'Discarded 1 card', target: 'player' });
            logs.push({ type: 'draw', text: '+2 Draw', target: 'player' });
            break;
        case 'SH-U04': // Assassinate: Deal 8 unblockable damage
            enemy.currentHp -= 8;
            logs.push({ type: 'damage', text: '-8 (unblockable)', target: 'enemy' });
            break;
        default:
            logs.push({ type: 'message', text: 'Card played.' });
    }
    if (enemy.currentHp < 0)
        enemy.currentHp = 0;
}
// Execute the enemy's turn
function executeEnemyTurn(player, enemy) {
    const logs = [];
    // Check stun
    if (enemy.status.stun > 0) {
        GS.tickStatusEffects(enemy.status);
        logs.push({ type: 'message', text: `${enemy.def.name} is stunned!` });
        return logs;
    }
    // Boss start-of-turn effects
    GS.applyBossStartTurn(enemy);
    // Calculate damage
    let damage = GS.getBossDamage(enemy, player.energy);
    // Apply damage to player (defense absorbs first)
    if (player.defense > 0) {
        const blocked = Math.min(damage, player.defense);
        player.defense -= blocked;
        damage -= blocked;
        logs.push({ type: 'defense', text: `${blocked} Blocked`, target: 'player' });
    }
    if (damage > 0) {
        player.hp -= damage;
        logs.push({ type: 'damage', text: `-${damage}`, target: 'player' });
    }
    else {
        logs.push({ type: 'message', text: 'Attack fully blocked!' });
    }
    if (player.hp < 0)
        player.hp = 0;
    // Tick enemy status effects
    GS.tickStatusEffects(enemy.status);
    return logs;
}
// Start a new combat encounter
function startCombat(state) {
    state.enemy = GS.createEnemyState(state.floor, state.isBossRoom);
    state.combatActive = true;
    state.turnPhase = 'player';
    // Clear hand from previous combat
    state.hand = [];
    // Reshuffle discard into deck if needed
    if (state.player.deck.length === 0 && state.player.discardPile.length > 0) {
        state.player.deck.push(...state.player.discardPile);
        state.player.discardPile = [];
    }
    // Shuffle and draw starting hand
    GS.shuffle(state.player.deck);
    const drawCount = Math.min(state.player.handSize, state.player.deck.length);
    for (let i = 0; i < drawCount; i++) {
        if (state.player.deck.length > 0) {
            state.hand.push(state.player.deck.pop());
        }
    }
    // Reset per-turn values
    state.player.defense = 0;
    state.player.energy = state.player.maxEnergy;
}
// End player turn, start enemy turn
function endPlayerTurn(state) {
    const result = { logs: [], enemyDead: false, playerDead: false };
    // Apply burn/poison to enemy from previous turns
    const statusDmg = GS.applyEnemyStatusEffects(state.enemy);
    if (statusDmg > 0) {
        result.logs.push({ type: 'damage', text: `-${statusDmg} Status`, target: 'enemy' });
    }
    // Check enemy death from status damage
    if (state.enemy.currentHp <= 0) {
        state.enemy.currentHp = 0;
        const gold = GS.getGoldReward(state.enemy);
        state.player.gold += gold;
        result.logs.push({ type: 'gold', text: `+${gold} Gold`, target: 'player' });
        result.enemyDead = true;
        return result;
    }
    // Enemy turn
    const enemyLogs = executeEnemyTurn(state.player, state.enemy);
    result.logs.push(...enemyLogs);
    // Check player death
    if (state.player.hp <= 0) {
        state.player.hp = 0;
        result.playerDead = true;
        return result;
    }
    // Tick player status effects
    GS.tickStatusEffects(state.player.status);
    // Start new player turn
    state.turnPhase = 'player';
    state.player.defense = 0; // Defense expires at end of turn
    state.player.energy = Math.min(state.player.maxEnergy, state.player.energy + 1);
    // Discard hand and draw new cards
    GS.discardHand(state);
    // Reshuffle discard pile into deck if needed
    if (state.player.deck.length === 0 && state.player.discardPile.length > 0) {
        state.player.deck.push(...state.player.discardPile);
        state.player.discardPile = [];
        GS.shuffle(state.player.deck);
    }
    const drawCount = Math.min(state.player.handSize, state.player.deck.length);
    for (let i = 0; i < drawCount; i++) {
        if (state.player.deck.length > 0) {
            state.hand.push(state.player.deck.pop());
        }
    }
    return result;
}
// Handle special card effects that need game state access
function handleCardSideEffects(card, player, enemy) {
    const logs = [];
    switch (card.def.id) {
        case 'BA-A01': // Slam: If >=30 HP, draw 1
            if (player.hp >= 30) {
                logs.push({ type: 'draw', text: '+1 Draw (Slam)', target: 'player' });
            }
            break;
        case 'BA-D04': // Taunt: Stun enemy
            enemy.status.stun = Math.max(enemy.status.stun, 1);
            logs.push({ type: 'status', text: 'Enemy Stunned!', target: 'enemy' });
            break;
        case 'SH-A04': // Vengeance: Gain 2 Energy (already handled in defense)
            player.energy += 2;
            logs.push({ type: 'energy', text: '+2 Energy', target: 'player' });
            break;
    }
    return logs;
}
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.cacheElements = cacheElements;
exports.renderClassSelect = renderClassSelect;
exports.enterNextRoom = enterNextRoom;
exports.renderCombat = renderCombat;
exports.onEndTurn = onEndTurn;
exports.renderShop = renderShop;
exports.getGameState = getGameState;
exports.setEndTurnHandler = setEndTurnHandler;
const GS = __importStar(require("./game-state"));
const Cards = __importStar(require("./cards"));
const Combat = __importStar(require("./combat"));
const SVG = __importStar(require("./card-svg"));
// DOM element references
let elements = {};
function cacheElements() {
    const ids = [
        'screen-class-select', 'screen-combat', 'screen-shop', 'screen-boss-intro',
        'screen-game-over', 'class-cards', 'hand-container', 'enemy-sprite',
        'enemy-name', 'enemy-hp-fill', 'enemy-hp-text', 'enemy-statuses',
        'player-hp-text', 'player-hp-fill', 'player-defense', 'player-statuses',
        'hud-floor', 'hud-room', 'hud-gold', 'hud-energy', 'btn-end-turn',
        'shop-cards', 'shop-gold-display', 'btn-leave-shop',
        'boss-name', 'boss-desc', 'btn-enter-combat',
        'game-over-title', 'game-over-text', 'btn-restart',
        'card-detail-overlay', 'card-detail-content',
    ];
    for (const id of ids) {
        elements[id] = document.getElementById(id);
    }
    // Create ambient particles
    createParticles();
}
function createParticles() {
    const container = document.getElementById('particles-container');
    if (!container)
        return;
    const colors = ['rgba(201,168,76,0.3)', 'rgba(150,130,100,0.2)', 'rgba(100,80,60,0.2)'];
    for (let i = 0; i < 20; i++) {
        const particle = document.createElement('div');
        particle.className = 'particle';
        const size = Math.random() * 3 + 1;
        particle.style.width = `${size}px`;
        particle.style.height = `${size}px`;
        particle.style.left = `${Math.random() * 100}%`;
        particle.style.background = colors[Math.floor(Math.random() * colors.length)];
        particle.style.animationDuration = `${Math.random() * 15 + 10}s`;
        particle.style.animationDelay = `${Math.random() * 10}s`;
        container.appendChild(particle);
    }
}
function showScreen(name) {
    document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
    const el = elements[name];
    if (el)
        el.classList.add('active');
}
// ===== CLASS SELECT SCREEN =====
function renderClassSelect() {
    showScreen('screen-class-select');
    const container = elements['class-cards'];
    container.innerHTML = '';
    const classes = [
        { id: 'bastion', name: 'The Bastion', theme: 'Immovable Object', hp: 35, energy: 3 },
        { id: 'wither', name: 'The Wither', theme: 'Rot and Burn', hp: 30, energy: 3 },
        { id: 'shroud', name: 'The Shroud', theme: 'Shadow and Speed', hp: 28, energy: 3 },
    ];
    const classColors = {
        bastion: '#c9a84c', wither: '#cc3300', shroud: '#7a3abf',
    };
    for (const cls of classes) {
        const card = document.createElement('div');
        card.className = 'class-card';
        card.style.border = `2px solid ${classColors[cls.id]}`;
        card.style.background = `linear-gradient(180deg, rgba(${hexToRgb(classColors[cls.id])},0.15) 0%, #1a1520 60%)`;
        // Show a sample card back for visual flair
        const sampleCardDef = { id: 'SAMPLE', name: cls.name, type: 'Attack', cost: 1, attack: 3, defense: 0, ability: '', description: '', classId: cls.id };
        const svgStr = SVG.createCardBackSVG(cls.id);
        card.innerHTML = `
      <div style="width:120px;height:165px;margin-bottom:8px;transform:scale(0.7);">${svgStr}</div>
      <div class="class-name" style="color:${classColors[cls.id]}">${cls.name}</div>
      <div class="class-theme">"${cls.theme}"</div>
      <div class="class-stats">HP: ${cls.hp} | Energy: ${cls.energy}<br/>Hand Size: 4<br/>Deck: 10 cards</div>
    `;
        card.addEventListener('click', () => {
            startGame(cls.id);
        });
        container.appendChild(card);
    }
}
function hexToRgb(hex) {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return `${r},${g},${b}`;
}
// ===== GAME START =====
let gameState = null;
let drawQueue = 0; // Cards to draw after current play
function startGame(classId) {
    gameState = GS.createInitialState(classId);
    enterNextRoom();
}
// ===== ROOM MANAGEMENT =====
let firstRoom = true;
function enterNextRoom() {
    if (!gameState)
        return;
    gameState.room++;
    // Boss room at the end of each floor (room 9 per floor)
    const roomsPerFloor = 9;
    if (gameState.room % roomsPerFloor === 0) {
        gameState.isBossRoom = true;
        showBossIntro();
    }
    else {
        startCombatEncounter();
    }
    firstRoom = false;
}
function showBossIntro() {
    if (!gameState)
        return;
    showScreen('screen-boss-intro');
    const bossNames = ['', 'The Gatekeeper', 'The Corrupted', 'The Void Lord'];
    const bossDescs = [
        '',
        '"Blocks 5 damage per turn until HP drops below 20. An immovable force guarding the descent."',
        '"Deals devastating 20 damage if you have less than 3 Energy. A corrupted knight consumed by darkness."',
        '"Regenerates 10 HP per turn and cannot be blocked. The final horror that consumes all of Aethelgard."',
    ];
    const nameEl = elements['boss-name'];
    const descEl = elements['boss-desc'];
    if (nameEl)
        nameEl.textContent = `Floor ${gameState.floor} Boss: ${bossNames[gameState.floor]}`;
    if (descEl)
        descEl.textContent = bossDescs[gameState.floor];
    const btn = elements['btn-enter-combat'];
    if (btn) {
        btn.onclick = () => startCombatEncounter();
    }
}
function startCombatEncounter() {
    if (!gameState)
        return;
    const gs = gameState; // capture for callback
    // Skip transition on first room for faster startup
    if (firstRoom) {
        Combat.startCombat(gs);
        showScreen('screen-combat');
        renderCombat();
        return;
    }
    // Show room transition
    const isBoss = gs.isBossRoom;
    const roomsPerFloor = 9;
    const roomInFloor = ((gs.room - 1) % roomsPerFloor) + 1;
    showRoomTransition(isBoss ? `⚔ Floor ${gs.floor} Boss ⚔` : `Floor ${gs.floor} — Room ${roomInFloor}`, () => {
        Combat.startCombat(gs);
        showScreen('screen-combat');
        renderCombat();
    });
}
function showRoomTransition(text, callback) {
    const overlay = document.getElementById('room-transition');
    if (!overlay)
        return;
    const h2 = overlay.querySelector('h2');
    if (h2)
        h2.textContent = text;
    overlay.classList.add('active');
    setTimeout(() => {
        overlay.classList.remove('active');
        setTimeout(callback, 150);
    }, 600);
}
// ===== COMBAT RENDERING =====
function renderCombat() {
    if (!gameState || !gameState.enemy)
        return;
    const p = gameState.player;
    const e = gameState.enemy;
    // HUD top
    setText('hud-floor', `Floor ${gameState.floor}`);
    setText('hud-room', `Room ${gameState.room}`);
    setText('hud-gold', `💰 ${p.gold}`);
    // Energy pips
    let energyHTML = '<span style="color:#cc9900;margin-right:4px">⚡</span>';
    for (let i = 0; i < p.maxEnergy; i++) {
        const filled = i < p.energy;
        energyHTML += `<span class="energy-pip ${filled ? 'filled' : 'empty'}"></span>`;
    }
    setHTML('hud-energy', energyHTML);
    // Enemy
    const enemySvg = SVG.createEnemySVG(e.def, e.isBoss);
    setHTML('enemy-sprite', enemySvg);
    setText('enemy-name', `${e.def.name}${e.isBoss ? ' ★' : ''}`);
    const hpPercent = Math.max(0, (e.currentHp / e.maxHp) * 100);
    setStyle('enemy-hp-fill', 'width', `${hpPercent}%`);
    setText('enemy-hp-text', `${e.currentHp}/${e.maxHp} HP`);
    // Enemy statuses
    renderStatuses('enemy-statuses', e.status, e.defense > 0 ? [`🛡${e.defense}`] : []);
    // Player
    const playerHpPercent = Math.max(0, (p.hp / p.maxHp) * 100);
    setStyle('player-hp-fill', 'width', `${playerHpPercent}%`);
    setText('player-hp-text', `❤️ ${p.hp}/${p.maxHp}`);
    setText('player-defense', p.defense > 0 ? `🛡 ${p.defense} Defense` : '');
    renderStatuses('player-statuses', p.status, []);
    // Hand
    renderHand();
    // End turn button state
    const btn = elements['btn-end-turn'];
    if (btn) {
        btn.disabled = gameState.turnPhase !== 'player';
    }
}
function renderStatuses(containerId, status, extras) {
    const container = elements[containerId];
    if (!container)
        return;
    let html = '';
    if (status.burn > 0)
        html += `<span class="status-badge" style="border-color:#cc6600;color:#ff9944">🔥 Burn ${status.burn}</span>`;
    if (status.poison > 0)
        html += `<span class="status-badge" style="border-color:#44aa00;color:#88dd44">☠ Poison ${status.poison}</span>`;
    if (status.stun > 0)
        html += `<span class="status-badge" style="border-color:#ffcc00;color:#ffee66">💫 Stunned</span>`;
    if (status.silence > 0)
        html += `<span class="status-badge" style="border-color:#aa44ff;color:#cc88ff">🔇 Silenced</span>`;
    for (const ex of extras) {
        html += `<span class="status-badge">${ex}</span>`;
    }
    container.innerHTML = html;
}
function renderHand() {
    if (!gameState)
        return;
    const container = elements['hand-container'];
    if (!container)
        return;
    container.innerHTML = '';
    for (let i = 0; i < gameState.hand.length; i++) {
        const card = gameState.hand[i];
        const playable = GS.canPlayCard(card, gameState.player);
        const div = document.createElement('div');
        div.className = `game-card ${playable ? 'playable' : 'unplayable'} card-type-${card.def.type.toLowerCase()}`;
        div.innerHTML = SVG.createCardSVG(card.def);
        // Stagger cards slightly
        const offset = (i - gameState.hand.length / 2) * 15;
        div.style.marginLeft = `${offset}px`;
        if (playable) {
            div.addEventListener('click', () => playCardFromHand(i));
        }
        container.appendChild(div);
    }
}
// ===== CARD PLAYING =====
function playCardFromHand(index) {
    if (!gameState || gameState.turnPhase !== 'player')
        return;
    const card = gameState.hand[index];
    if (!GS.canPlayCard(card, gameState.player))
        return;
    // Remove from hand and add to discard pile (played cards go to discard)
    gameState.hand.splice(index, 1);
    gameState.player.discardPile.push(card);
    // Play the card
    const logs = Combat.playCard(card, gameState.player, gameState.enemy);
    // Handle side effects (draws, discards)
    const sideEffects = Combat.handleCardSideEffects(card, gameState.player, gameState.enemy);
    // Process draws from side effects
    for (const log of [...logs, ...sideEffects]) {
        if (log.type === 'draw') {
            const count = parseInt(log.text.match(/\+(\d+)/)?.[1] || '0');
            drawQueue += count;
        }
        if (log.type === 'discard') {
            // Will discard from hand after card play resolves
            if (gameState.hand.length > 0) {
                const discarded = gameState.hand.pop();
                gameState.player.discardPile.push(discarded);
            }
        }
    }
    // Process draws
    for (let i = 0; i < drawQueue; i++) {
        if (gameState.player.deck.length > 0 && gameState.hand.length < gameState.player.handSize + 2) {
            const drawn = gameState.player.deck.pop();
            gameState.hand.push(drawn);
        }
        else if (gameState.player.deck.length === 0 && gameState.player.discardPile.length > 0) {
            GS.shuffle(gameState.player.discardPile);
            gameState.player.deck.push(...gameState.player.discardPile);
            gameState.player.discardPile = [];
            if (gameState.player.deck.length > 0 && gameState.hand.length < gameState.player.handSize + 2) {
                const drawn = gameState.player.deck.pop();
                gameState.hand.push(drawn);
            }
        }
    }
    drawQueue = 0;
    // Show card play flash effect
    const arena = document.getElementById('combat-arena');
    if (arena) {
        const flash = document.createElement('div');
        flash.className = 'card-play-flash';
        arena.appendChild(flash);
        setTimeout(() => flash.remove(), 300);
    }
    // Show float text for damage/heal
    showCombatFeedback(logs, sideEffects);
    // Animate enemy hit if damage dealt
    const hasDamage = logs.some(l => l.type === 'damage' && l.target === 'enemy');
    if (hasDamage) {
        const spriteEl = elements['enemy-sprite'];
        if (spriteEl) {
            spriteEl.classList.add('hit');
            setTimeout(() => spriteEl.classList.remove('hit'), 300);
        }
    }
    // Check enemy death
    if (gameState.enemy.currentHp <= 0) {
        gameState.enemy.currentHp = 0;
        const gold = GS.getGoldReward(gameState.enemy);
        gameState.player.gold += gold;
        setTimeout(() => {
            showScreen('screen-shop');
            renderShop();
        }, 600);
        return;
    }
    // Check player death (shouldn't happen from card play, but just in case)
    if (gameState.player.hp <= 0) {
        gameState.gameOver = true;
        setTimeout(() => showGameOver(false), 500);
        return;
    }
    renderCombat();
}
function showCombatFeedback(logs, sideEffects) {
    const allLogs = [...logs, ...sideEffects];
    const arena = document.getElementById('combat-arena');
    if (!arena)
        return;
    for (const log of allLogs) {
        if (log.type === 'damage' || log.type === 'heal') {
            const floatEl = document.createElement('div');
            floatEl.className = 'float-text';
            floatEl.textContent = log.text;
            if (log.target === 'enemy') {
                floatEl.style.color = '#ff4444';
                floatEl.style.left = '50%';
                floatEl.style.top = '30%';
            }
            else {
                floatEl.style.color = log.type === 'heal' ? '#44ff44' : '#ff4444';
                floatEl.style.left = '50%';
                floatEl.style.bottom = '20%';
            }
            arena.appendChild(floatEl);
            setTimeout(() => floatEl.remove(), 1000);
        }
    }
}
// ===== END TURN =====
function onEndTurn() {
    if (!gameState || gameState.turnPhase !== 'player')
        return;
    const gs = gameState; // capture for async callbacks
    // Show turn indicator
    showTurnIndicator('Enemy Turn');
    const result = Combat.endPlayerTurn(gs);
    renderCombat();
    // Process results
    setTimeout(() => {
        if (result.enemyDead) {
            setTimeout(() => {
                showScreen('screen-shop');
                renderShop();
            }, 500);
        }
        else if (result.playerDead) {
            gs.gameOver = true;
            setTimeout(() => showGameOver(false), 500);
        }
        else {
            // Show player turn indicator after enemy action
            setTimeout(() => showTurnIndicator('Your Turn'), 800);
        }
    }, 300);
}
function showTurnIndicator(text) {
    let indicator = document.getElementById('turn-indicator');
    if (!indicator) {
        indicator = document.createElement('div');
        indicator.id = 'turn-indicator';
        document.getElementById('game-container')?.appendChild(indicator);
    }
    indicator.textContent = text;
    indicator.classList.remove('show');
    void indicator.offsetWidth; // Force reflow
    indicator.classList.add('show');
}
// ===== SHOP =====
function renderShop() {
    if (!gameState)
        return;
    const p = gameState.player;
    // Gold display with heal option
    const goldEl = elements['shop-gold-display'];
    if (goldEl) {
        const healCost = Math.max(1, Math.floor((p.maxHp - p.hp) * 2));
        const canHeal = p.gold >= healCost && p.hp < p.maxHp;
        goldEl.innerHTML = `Gold: ${p.gold} &nbsp;|&nbsp; ` +
            `<button id="btn-heal" style="padding:4px 12px;font-size:0.85em;" ${canHeal ? '' : 'disabled'}>❤️ Heal (${healCost}g)</button>`;
        const healBtn = document.getElementById('btn-heal');
        if (healBtn && canHeal) {
            healBtn.onclick = () => {
                p.gold -= healCost;
                p.hp = Math.min(p.maxHp, p.hp + Math.floor(healCost / 2));
                renderShop();
            };
        }
    }
    const container = elements['shop-cards'];
    if (!container)
        return;
    container.innerHTML = '';
    // Generate shop items - random cards from player's class
    const availableCards = generateShopItems(p.classId, p.inventory);
    for (const item of availableCards) {
        const wrapper = document.createElement('div');
        wrapper.className = 'shop-card-wrapper';
        const canAfford = p.gold >= item.cost;
        const maxOwned = item.ownedCount >= 5;
        const cardDiv = document.createElement('div');
        cardDiv.className = `game-card ${canAfford && !maxOwned ? 'playable' : 'unplayable'}`;
        cardDiv.innerHTML = SVG.createCardSVG(item.cardDef);
        if (!canAfford || maxOwned) {
            cardDiv.style.opacity = '0.5';
        }
        const priceEl = document.createElement('div');
        priceEl.className = 'shop-card-price';
        priceEl.textContent = maxOwned ? 'MAX' : `💰 ${item.cost}`;
        const countEl = document.createElement('div');
        countEl.className = 'shop-card-count';
        countEl.textContent = `Owned: ${item.ownedCount}/5`;
        wrapper.appendChild(cardDiv);
        wrapper.appendChild(priceEl);
        wrapper.appendChild(countEl);
        if (canAfford && !maxOwned) {
            cardDiv.addEventListener('click', () => buyCard(item));
        }
        container.appendChild(wrapper);
    }
    const leaveBtn = elements['btn-leave-shop'];
    if (leaveBtn) {
        leaveBtn.onclick = () => leaveShop();
    }
}
function buyCard(item) {
    if (!gameState)
        return;
    if (gameState.player.gold < item.cost || item.ownedCount >= 5)
        return;
    gameState.player.gold -= item.cost;
    const instance = Cards.createCardInstance(item.cardDef);
    gameState.player.deck.push(instance);
    gameState.player.inventory[item.cardDef.id] = (gameState.player.inventory[item.cardDef.id] || 0) + 1;
    renderShop();
}
function leaveShop() {
    if (!gameState)
        return;
    const roomsPerFloor = 9;
    // Check if we just defeated a boss (room is multiple of 9)
    if (gameState.room % roomsPerFloor === 0 && gameState.floor < 3) {
        // Move to next floor - start fresh room count
        gameState.floor++;
        gameState.isBossRoom = false;
        enterNextRoom();
    }
    else if (gameState.room % roomsPerFloor === 0 && gameState.floor === 3) {
        // Victory! Defeated Floor 3 boss
        showGameOver(true);
    }
    else {
        // Normal room progression
        enterNextRoom();
    }
}
// ===== GAME OVER =====
function showGameOver(victory) {
    const gs = gameState; // capture before potential null issues
    showScreen('screen-game-over');
    const titleEl = elements['game-over-title'];
    const textEl = elements['game-over-text'];
    if (victory) {
        titleEl.textContent = 'Victory!';
        titleEl.style.color = '#c9a84c';
        textEl.textContent = 'You have defeated the Void Lord and saved Aethelgard!';
    }
    else {
        titleEl.textContent = 'Defeated...';
        titleEl.style.color = '#cc3300';
        textEl.textContent = `The Void consumes you on Floor ${gs?.floor ?? '?'}, Room ${gs?.room ?? '?'}.`;
    }
    const btn = elements['btn-restart'];
    if (btn) {
        btn.onclick = () => renderClassSelect();
    }
}
// ===== SHOP GENERATION =====
function generateShopItems(classId, inventory) {
    const classCards = Cards.getCardsByClass(classId);
    // Pick 4-6 random cards for the shop
    const shuffled = [...classCards];
    GS.shuffle(shuffled);
    const selected = shuffled.slice(0, 5);
    return selected.map(cardDef => {
        const ownedCount = inventory[cardDef.id] || 0;
        let cost;
        if (ownedCount >= 3) {
            cost = 10 + (ownedCount - 2) * 5; // Duplication penalty
        }
        else {
            cost = cardDef.cost + ownedCount; // Base cost scales with ownership
        }
        cost = Math.min(cost, 50); // Cap at 50
        return { cardDef, cost, ownedCount };
    });
}
// ===== UTILITY FUNCTIONS =====
function setText(id, text) {
    const el = elements[id];
    if (el)
        el.textContent = text;
}
function setHTML(id, html) {
    const el = elements[id];
    if (el)
        el.innerHTML = html;
}
function setStyle(id, prop, value) {
    const el = elements[id];
    if (el)
        el.style.setProperty(prop, value);
}
// Export for main.ts to call
function getGameState() {
    return gameState;
}
function setEndTurnHandler(handler) {
    const btn = elements['btn-end-turn'];
    if (btn)
        btn.onclick = handler;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"d7b3f5f8-e130-44db-930f-af1f837722c8","tokensIn":4699497,"tokensOut":56768,"tokensTotal":4756265,"turns":85,"toolCalls":84,"failedToolCalls":2,"timestamp":"2026-07-30T19:42:56.515Z"} -->