← Shadows results

Shadows

Gemma 4 31b · composer

Initial view of Shadows

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 33,788 bytes · SHA-256 a3023054feb6
<!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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.min.js"></script>
<style>
/* ── css: layout ── */

body {
    margin: 0;
    padding: 0;
    background: #0a0a0a;
    color: #e0e0e0;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    overflow: hidden;
    user-select: none;
}

#game-container {
    width: 100vw;
    height: 100vh;
    position: relative;
}

.screen {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    transition: opacity 0.3s ease;
}

.hidden {
    display: none !important;
    opacity: 0;
}

h1 {
    font-size: 4rem;
    color: #8b0000;
    text-shadow: 0 0 20px rgba(139, 0, 0, 0.5);
    margin-bottom: 1rem;
}

.class-selection {
    display: flex;
    gap: 20px;
    margin-top: 2rem;
}

.class-card {
    background: #1a1a1a;
    border: 2px solid #333;
    padding: 2rem;
    width: 250px;
    text-align: center;
    cursor: pointer;
    transition: transform 0.2s, border-color 0.2s;
    border-radius: 10px;
}

.class-card:hover {
    transform: translateY(-10px);
    border-color: #8b0000;
}

.class-card button {
    background: #8b0000;
    color: white;
    border: none;
    padding: 10px 20px;
    cursor: pointer;
    font-size: 1.1rem;
    margin-top: 1rem;
}

#map-header {
    position: absolute;
    top: 20px;
    width: 90%;
    display: flex;
    justify-content: space-between;
    font-size: 1.5rem;
    font-weight: bold;
    pointer-events: none;
}

#map-canvas-container {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

#map-canvas {
    background: #111;
    box-shadow: 0 0 50px rgba(0,0,0,0.8);
}

#combat-hud {
    position: absolute;
    top: 0;
    width: 100%;
    height: 60%;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0 50px;
}

.stat-bar {
    width: 200px;
    height: 24px;
    background: #333;
    border: 2px solid #555;
    position: relative;
    overflow: hidden;
}

.bar-fill {
    height: 100%;
    background: #c00;
    width: 100%;
    transition: width 0.3s ease;
}

#player-hp-text {
    position: absolute;
    width: 100%;
    text-align: center;
    font-weight: bold;
    font-size: 14px;
    line-height: 24px;
    color: white;
    text-shadow: 1px 1px 2px black;
}

.block-value {
    font-size: 2rem;
    color: #4a9eff;
    font-weight: bold;
    text-align: center;
}

#player-energy {
    font-size: 1.5rem;
    text-align: center;
}

#enemy-container {
    display: flex;
    gap: 30px;
}

.enemy-unit {
    text-align: center;
    width: 180px;
}

.enemy-hp-bar {
    width: 100%;
    height: 15px;
    background: #333;
    border: 1px solid #777;
    margin-bottom: 5px;
}

.enemy-hp-fill {
    height: 100%;
    background: #a00;
    transition: width 0.3s ease;
}

.enemy-intent {
    background: #222;
    border: 1px solid #444;
    padding: 5px;
    margin-bottom: 10px;
    font-size: 0.9rem;
    min-height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#combat-area {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 40%;
    background: linear-gradient(to top, #000, transparent);
    display: flex;
    justify-content: center;
    align-items: center;
    padding-bottom: 50px;
}

#hand {
    display: flex;
    gap: 10px;
    justify-content: center;
    align-items: flex-end;
}

.card {
    width: 140px;
    height: 200px;
    background: #2a2a2a;
    border: 2px solid #666;
    border-radius: 8px;
    padding: 10px;
    cursor: pointer;
    transition: transform 0.2s, box-shadow 0.2s;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    text-align: center;
    font-size: 0.85rem;
}

.card:hover {
    transform: translateY(-20px) scale(1.05);
    box-shadow: 0 10px 20px rgba(0,0,0,0.5);
}

.card.unplayable {
    opacity: 0.6;
    cursor: not-allowed;
    filter: grayscale(1);
}

.card-cost {
    position: absolute;
    top: -10px;
    left: -10px;
    width: 30px;
    height: 30px;
    background: #4a9eff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    border: 2px solid white;
}

.card-title {
    font-weight: bold;
    font-size: 1rem;
    margin-bottom: 10px;
}

.card-desc {
    font-style: italic;
    font-size: 0.8rem;
    flex-grow: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

#end-turn-btn {
    position: absolute;
    right: 50px;
    bottom: 80px;
    padding: 15px 30px;
    background: #444;
    color: white;
    border: 2px solid #888;
    cursor: pointer;
    font-size: 1.2rem;
}

#end-turn-btn:hover {
    background: #666;
}

#reward-cards, #shop-items {
    display: flex;
    gap: 20px;
    margin: 2rem;
    justify-content: center;
}

.reward-option {
    background: #222;
    border: 2px solid #444;
    padding: 1rem;
    text-align: center;
    cursor: pointer;
    width: 160px;
    transition: border-color 0.2s;
}

.reward-option:hover {
    border-color: #8b0000;
}

#event-content {
    background: #1a1a1a;
    padding: 2rem;
    border: 2px solid #333;
    max-width: 600px;
    text-align: center;
    border-radius: 15px;
}

#event-choices {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 2rem;
}

.choice-btn {
    padding: 10px;
    background: #333;
    color: white;
    border: 1px solid #555;
    cursor: pointer;
}

.choice-btn:hover {
    background: #444;
}

#shop-header {
    display: flex;
    gap: 20px;
    align-items: center;
    margin-bottom: 1rem;
}

.rest-options {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.rest-options button {
    padding: 20px;
    font-size: 1.2rem;
    background: #333;
    color: white;
    border: 2px solid #555;
    cursor: pointer;
}
</style>
</head>
<body>
<div id="game-container">
    <div id="ui-layer">
        <div id="screen-start" class="screen">
            <h1>Shadows of the Abyss</h1>
            <p>Choose your cursed archetype</p>
            <div class="class-selection">
                <div class="class-card" id="class-blood">
                    <h2>Blood Reaper</h2>
                    <p>Sacrifice HP for power and life steal.</p>
                    <button onclick="game.selectClass('blood')">Select</button>
                </div>
                <div class="class-card" id="class-shadow">
                    <h2>Shadowmancer</h2>
                    <p>Manipulate shadows, debuffs, and intangibility.</p>
                    <button onclick="game.selectClass('shadow')">Select</button>
                </div>
                <div class="class-card" id="class-hell">
                    <h2>Hellforged</h2>
                    <p>Wield fire, burn enemies, and summon imps.</p>
                    <button onclick="game.selectClass('hell')">Select</button>
                </div>
            </div>
        </div>

        <div id="screen-map" class="screen hidden">
            <div id="map-header">
                <span id="map-act">Act 1: The Crypts</span>
                <span id="map-gold">Gold: 99</span>
            </div>
            <div id="map-canvas-container">
                <canvas id="map-canvas"></canvas>
            </div>
            <div id="map-controls">
                <p id="map-node-info">Select a node to proceed</p>
            </div>
        </div>

        <div id="screen-combat" class="screen hidden">
            <div id="combat-hud">
                <div id="player-stats">
                    <div class="stat-bar">
                        <div id="player-hp-fill" class="bar-fill"></div>
                        <span id="player-hp-text">70 / 70</span>
                    </div>
                    <div id="player-block" class="block-value">0</div>
                    <div id="player-energy">Energy: 3 / 3</div>
                </div>
                <div id="enemy-side">
                    <div id="enemy-container"></div>
                </div>
            </div>
            <div id="combat-area">
                <div id="hand"></div>
                <button id="end-turn-btn">End Turn</button>
            </div>
        </div>

        <div id="screen-reward" class="screen hidden">
            <h2 id="reward-title">Victory!</h2>
            <div id="reward-cards"></div>
            <div id="reward-potions"></div>
            <div id="reward-relics"></div>
            <button id="reward-next" class="hidden">Continue</button>
        </div>

        <div id="screen-event" class="screen hidden">
            <div id="event-content">
                <h2 id="event-title">Event</h2>
                <p id="event-text"></p>
                <div id="event-choices"></div>
            </div>
        </div>

        <div id="screen-shop" class="screen hidden">
            <h2>The Cursed Shop</h2>
            <div id="shop-header">
                <span id="shop-gold">Gold: 99</span>
                <button id="shop-remove-card">Remove Card (75g)</button>
            </div>
            <div id="shop-items"></div>
            <button id="shop-exit">Leave Shop</button>
        </div>

        <div id="screen-rest" class="screen hidden">
            <h2>Rest Site</h2>
            <div class="rest-options">
                <button onclick="game.restAction('heal')">Heal (25% Max HP)</button>
                <button onclick="game.restAction('upgrade')">Forge (Upgrade Card)</button>
                <button onclick="game.restAction('remove')">Meditate (Remove Card)</button>
            </div>
        </div>

        <div id="screen-gameover" class="screen hidden">
            <h1>YOU DIED</h1>
            <p>Your soul is lost to the abyss.</p>
            <button onclick="location.reload()">Try Again</button>
        </div>

        <div id="screen-victory" class="screen hidden">
            <h1>VICTORY</h1>
            <p>You have emerged from the Abyssal Spire!</p>
            <button onclick="location.reload()">Play Again</button>
        </div>
    </div>
</div>
<script>
'use strict';

// ── const: CARD_DATA ──
const CARD_DATA = {
    basic: {
        Strike: { cost: 1, type: 'Attack', desc: 'Deal 6 damage', upgrade: { desc: 'Deal 8 damage' } },
        Defend: { cost: 1, type: 'Skill', desc: 'Gain 5 Block', upgrade: { desc: 'Gain 8 Block' } },
    },
    blood: {
        SanguineStrike: { cost: 1, type: 'Attack', desc: 'Deal 8 damage. Heal 2 HP.', upgrade: { desc: 'Deal 10 damage. Heal 3 HP.' } },
        Hemorrhage: { cost: 1, type: 'Attack', desc: 'Deal 5 damage. Apply 3 Bleed.', upgrade: { desc: 'Deal 6 damage. Apply 4 Bleed.' } },
        Bloodletting: { cost: 1, type: 'Attack', desc: 'Lose 3 HP. Deal 12 damage.', upgrade: { desc: 'Lose 2 HP. Deal 15 damage.' } },
        CrimsonEruption: { cost: 2, type: 'Attack', desc: 'Deal 8 damage to ALL. Lose 2 HP.', upgrade: { desc: 'Deal 10 damage to ALL. Lose 2 HP.' } },
        CrimsonShield: { cost: 1, type: 'Skill', desc: 'Gain 6 Block. Lose 2 HP.', upgrade: { desc: 'Gain 8 Block. Lose 2 HP.' } },
        BloodWard: { cost: 1, type: 'Skill', desc: 'Gain 3 Block. Gain 1 Regen.', upgrade: { desc: 'Gain 4 Block. Gain 2 Regen.' } },
        SacrificialBarrier: { cost: 2, type: 'Skill', desc: 'Gain 10 Block. Lose 5 HP.', upgrade: { desc: 'Gain 13 Block. Lose 5 HP.' } },
        VitalSurge: { cost: 0, type: 'Skill', desc: 'Gain Block equal to missing HP. Exhaust.', upgrade: { desc: 'Gain Block equal to missing HP + 3. Exhaust.' } },
        LifeTap: { cost: 0, type: 'Skill', desc: 'Draw 2 cards. Lose 3 HP.', upgrade: { desc: 'Draw 3 cards. Lose 3 HP.' } },
        RendFlesh: { cost: 1, type: 'Skill', desc: 'Apply 5 Bleed to ALL. Lose 2 HP.', upgrade: { desc: 'Apply 7 Bleed to ALL. Lose 2 HP.' } },
        SoulSiphon: { cost: 2, type: 'Power', desc: 'Start of turn: Gain 1 Energy, lose 1 HP.', upgrade: { cost: 1, desc: 'Start of turn: Gain 1 Energy, lose 1 HP.' } },
        BloodPact: { cost: 1, type: 'Power', desc: 'Losing HP from cards gains 1 Strength.', upgrade: { desc: 'Losing HP from cards gains 2 Strength.' } },
    },
    shadow: {
        ShadowBolt: { cost: 1, type: 'Attack', desc: 'Deal 6 damage. Apply 1 Weak.', upgrade: { desc: 'Deal 8 damage. Apply 2 Weak.' } },
        GraspOfDespair: { cost: 1, type: 'Attack', desc: 'Deal 4 damage. Apply 1 Vulnerable.', upgrade: { desc: 'Deal 5 damage. Apply 2 Vulnerable.' } },
        NightmareLash: { cost: 2, type: 'Attack', desc: 'Deal 7 damage. +7 if Weak.', upgrade: { desc: 'Deal 9 damage. +9 if Weak.' } },
        EternalDarkness: { cost: 2, type: 'Attack', desc: 'Deal 3 damage 3 times to random.', upgrade: { desc: 'Deal 4 damage 3 times to random.' } },
        CloakOfShadows: { cost: 1, type: 'Skill', desc: 'Gain 5 Block. Draw 1.', upgrade: { desc: 'Gain 7 Block. Draw 1.' } },
        HauntingMist: { cost: 1, type: 'Skill', desc: 'Gain 7 Block. All enemies Weak 1.', upgrade: { desc: 'Gain 9 Block. All enemies Weak 1.' } },
        PhantomArmor: { cost: 1, type: 'Skill', desc: 'Gain 8 Block. Next turn +2 Dex.', upgrade: { desc: 'Gain 10 Block. Next turn +3 Dex.' } },
        WhisperingShroud: { cost: 1, type: 'Skill', desc: 'Gain 4 Block. Next turn +4 Block.', upgrade: { desc: 'Gain 5 Block. Next turn +5 Block.' } },
        Shadowform: { cost: 2, type: 'Skill', desc: 'Gain 2 Intangible. Exhaust.', upgrade: { desc: 'Gain 3 Intangible. Exhaust.' } },
        CurseOfMisfortune: { cost: 1, type: 'Skill', desc: 'Enemy loses 3 Str this turn. 1 Vuln.', upgrade: { desc: 'Enemy loses 4 Str this turn. 2 Vuln.' } },
        DarkRitual: { cost: 0, type: 'Skill', desc: 'Gain 2 Energy. Shuffle Dazed into Draw.', upgrade: { desc: 'Gain 2 Energy. Shuffle Dazed into Discard.' } },
        UmbralMastery: { cost: 2, type: 'Power', desc: 'Start of turn: All enemies Weak 1.', upgrade: { desc: 'Start of turn: All enemies Weak 2.' } },
    },
    hell: {
        Firebolt: { cost: 1, type: 'Attack', desc: 'Deal 7 damage. Apply 2 Burn.', upgrade: { desc: 'Deal 9 damage. Apply 3 Burn.' } },
        HellfireBlast: { cost: 2, type: 'Attack', desc: 'Deal 9 damage to ALL.', upgrade: { desc: 'Deal 12 damage to ALL.' } },
        SearingStrike: { cost: 1, type: 'Attack', desc: 'Deal 6 damage. +1 Energy next turn.', upgrade: { desc: 'Deal 8 damage. +1 Energy next turn.' } },
        Immolate: { cost: 2, type: 'Attack', desc: 'Deal 12 damage. Exhaust.', upgrade: { desc: 'Deal 16 damage. Exhaust.' } },
        ObsidianSkin: { cost: 1, type: 'Skill', desc: 'Gain 7 Block.', upgrade: { desc: 'Gain 10 Block.' } },
        FlameWard: { cost: 1, type: 'Skill', desc: 'Gain 5 Block. 2 Burn to random.', upgrade: { desc: 'Gain 6 Block. 3 Burn to random.' } },
        DemonicResilience: { cost: 1, type: 'Skill', desc: 'Gain 4 Block. Heal 2 HP.', upgrade: { desc: 'Gain 5 Block. Heal 3 HP.' } },
        BrimstoneShield: { cost: 1, type: 'Skill', desc: 'Gain 3 Block. +2 Str this turn.', upgrade: { desc: 'Gain 4 Block. +3 Str this turn.' } },
        SummonImp: { cost: 1, type: 'Skill', desc: 'Add Imp Claw to hand. Exhaust.', upgrade: { desc: 'Add Imp Claw+ to hand. Exhaust.' } },
        InfernalPact: { cost: 0, type: 'Skill', desc: 'Lose 2 HP. Gain 2 Energy. Add Imp Claw.', upgrade: { desc: 'Lose 1 HP. Gain 2 Energy. Add Imp Claw+.' } },
        HellfireAura: { cost: 2, type: 'Power', desc: 'Start of turn: 3 damage to ALL.', upgrade: { desc: 'Start of turn: 5 damage to ALL.' } },
        SoulBurn: { cost: 1, type: 'Skill', desc: 'Apply 8 Burn to enemy. Draw 1.', upgrade: { desc: 'Apply 10 Burn to enemy. Draw 1.' } },
    },
    colorless: {
        FlashOfInsight: { cost: 1, type: 'Skill', desc: 'Draw 2 cards.', upgrade: { desc: 'Draw 3 cards.' } },
        IronWill: { cost: 1, type: 'Skill', desc: 'Gain 8 Block.', upgrade: { desc: 'Gain 11 Block.' } },
        QuickSlash: { cost: 1, type: 'Attack', desc: 'Deal 7 damage.', upgrade: { desc: 'Deal 9 damage.' } },
        PotentBrew: { cost: 0, type: 'Skill', desc: 'Gain 1 Energy. Exhaust.', upgrade: { desc: 'Gain 2 Energy. Exhaust.' } },
    },
    status: {
        Wound: { cost: 1, type: 'Curse', desc: 'Unplayable. Ethereal. Exhaust.' },
        Dazed: { cost: 1, type: 'Status', desc: 'Unplayable. Ethereal. Exhaust.' },
        Doubt: { cost: 1, type: 'Curse', desc: 'Unplayable. Lose 1 HP at end of turn.' },
        ImpClaw: { cost: 0, type: 'Attack', desc: 'Deal 4 damage. Exhaust.' },
        ImpClawPlus: { cost: 0, type: 'Attack', desc: 'Deal 6 damage. Exhaust.' },
    }
};

// ── const: ENEMY_DATA ──
const ENEMY_DATA = {
    act1: {
        Skeleton: { hp: 22, pattern: [
            { type: 'attack', val: 6, desc: 'Attack 6' },
            { type: 'attack', val: 6, desc: 'Attack 6' },
            { type: 'defend', val: 8, desc: 'Defend 8' }
        ]},
        ZombieBrute: { hp: 28, pattern: [
            { type: 'attack', val: 8, desc: 'Attack 8' },
            { type: 'debuff', val: 2, effect: 'weak', desc: 'Apply 2 Weak' },
            { type: 'attack', val: 8, desc: 'Attack 8' }
        ]},
        DarkCultist: { hp: 20, pattern: [
            { type: 'buff', val: 3, effect: 'strength', desc: 'Gain 3 Strength' },
            { type: 'attack', val: 5, desc: 'Attack 5' },
            { type: 'attack', val: 5, desc: 'Attack 5' }
        ]},
        RatSwarm: { hp: 15, pattern: [
            { type: 'multi', val: 3, hits: 2, desc: 'Attack 3 x 2' },
            { type: 'multi', val: 3, hits: 2, desc: 'Attack 3 x 2' }
        ]},
        TombGuardian: { hp: 42, pattern: [
            { type: 'special', val: 6, block: 8, desc: 'Attack 6, Gain 8 Block' },
            { type: 'multi', val: 4, hits: 3, desc: 'Attack 4 x 3' },
            { type: 'attack', val: 12, desc: 'Attack 12' }
        ]},
        BloodWitch: { hp: 36, pattern: [
            { type: 'special', val: 5, heal: 4, desc: 'Attack 5, Heal 4' },
            { type: 'debuff', val: 1, effects: ['weak', 'vuln'], desc: 'Apply Weak & Vuln' },
            { type: 'special', val: 7, heal: 4, desc: 'Attack 7, Heal 4' }
        ]},
        LichKing: { hp: 65, pattern: [
            { type: 'summon', enemy: 'Skeleton', desc: 'Summon Skeleton' },
            { type: 'attack', val: 8, desc: 'Attack 8' },
            { type: 'defend', val: 12, desc: 'Defend 12' }
        ], phase2: { hp: 30, strength: 2, pattern: [
            { type: 'attack', val: 10, desc: 'Attack 10' },
            { type: 'attack', val: 10, desc: 'Attack 10' },
            { type: 'defend', val: 15, desc: 'Defend 15' }
        ]}}
    },
    // Act 2 and 3 will be similar mappings
};

// ── function: resolveCardEffect ──
function resolveCardEffect(game, card, targetIndex = 0) {
    const info = game.getCardInfo(card);
    const player = game.player;
    const combat = game.combat;
    const target = combat.enemies[targetIndex];

    // Basic parsing of description for simple numbers
    const getVal = (txt) => {
        const match = txt.match(/\d+/);
        return match ? parseInt(match[0]) : 0;
    };

    // This is a simplified effect engine
    if (card.id === 'Strike') {
        game.damageEnemy(getVal(info.desc), targetIndex);
    } else if (card.id === 'Defend') {
        player.block += getVal(info.desc);
    } else if (card.id === 'SanguineStrike') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        player.hp = Math.min(player.maxHp, player.hp + 2);
    } else if (card.id === 'Hemorrhage') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        // Add bleed effect logic later
    } else if (card.id === 'Bloodletting') {
        player.hp -= 3;
        game.damageEnemy(getVal(info.desc), targetIndex);
    } else if (card.id === 'CrimsonEruption') {
        const dmg = getVal(info.desc);
        combat.enemies.forEach((_, idx) => game.damageEnemy(dmg, idx));
        player.hp -= 2;
    } else if (card.id === 'CrimsonShield') {
        player.block += getVal(info.desc);
        player.hp -= 2;
    } else if (card.id === 'BloodWard') {
        player.block += getVal(info.desc);
        // Regen logic
    } else if (card.id === 'SacrificialBarrier') {
        player.block += getVal(info.desc);
        player.hp -= 5;
    } else if (card.id === 'VitalSurge') {
        const missing = player.maxHp - player.hp;
        player.block += missing;
    } else if (card.id === 'LifeTap') {
        game.drawCards(2);
        player.hp -= 3;
    } else if (card.id === 'RendFlesh') {
        combat.enemies.forEach((_, idx) => {
            // apply bleed logic
        });
        player.hp -= 2;
    } else if (card.id === 'ShadowBolt') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        // Weakness logic
    } else if (card.id === 'GraspOfDespair') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        // Vuln logic
    } else if (card.id === 'CloakOfShadows') {
        player.block += getVal(info.desc);
        game.drawCards(1);
    } else if (card.id === 'HauntingMist') {
        player.block += getVal(info.desc);
        combat.enemies.forEach(en => { /* Weak */ });
    } else if (card.id === 'Firebolt') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        // Burn logic
    } else if (card.id === 'HellfireBlast') {
        const dmg = getVal(info.desc);
        combat.enemies.forEach((_, idx) => game.damageEnemy(dmg, idx));
    } else if (card.id === 'SearingStrike') {
        game.damageEnemy(getVal(info.desc), targetIndex);
        // Energy next turn logic
    } else if (card.id === 'Immolate') {
        game.damageEnemy(getVal(info.desc), targetIndex);
    } else if (card.id === 'ObsidianSkin') {
        player.block += getVal(info.desc);
    } else if (card.id === 'FlameWard') {
        player.block += getVal(info.desc);
        // random burn enemy
    } else if (card.id === 'DemonicResilience') {
        player.block += getVal(info.desc);
        player.hp = Math.min(player.maxHp, player.hp + 2);
    } else if (card.id === 'BrimstoneShield') {
        player.block += getVal(info.desc);
        player.strength += 2;
    } else if (card.id === 'SummonImp') {
        game.player.hand.push({ id: 'ImpClaw', upgraded: false });
    } else if (card.id === 'InfernalPact') {
        player.hp -= 2;
        player.energy += 2;
        game.player.hand.push({ id: 'ImpClaw', upgraded: false });
    } else if (card.id === 'SoulBurn') {
        // apply burn
        game.drawCards(1);
    } else if (card.id === 'ImpClaw') {
        game.damageEnemy(4, targetIndex);
    } else if (card.id === 'ImpClawPlus') {
        game.damageEnemy(6, targetIndex);
    }
}

// ── main ──

class Game {
    constructor() {
        this.state = 'START';
        this.player = {
            hp: 70,
            maxHp: 70,
            gold: 99,
            energy: 3,
            maxEnergy: 3,
            strength: 0,
            dexterity: 0,
            block: 0,
            deck: [],
            hand: [],
            drawPile: [],
            discardPile: [],
            relics: [],
            potions: [],
            class: null,
            act: 1,
            floor: 1,
            currentPos: { x: 2, y: 0 }
        };
        this.combat = null;
        this.init();
    }

    init() {
        this.updateUI();
    }

    selectClass(cls) {
        this.player.class = cls;
        this.setupStartingDeck(cls);
        this.state = 'MAP';
        this.renderMap();
        this.updateUI();
    }

    setupStartingDeck(cls) {
        const basic = CARD_DATA.basic;
        const classPool = CARD_DATA[cls];
        
        for (let i = 0; i < 5; i++) this.player.deck.push({ id: 'Strike', upgraded: false });
        for (let i = 0; i < 4; i++) this.player.deck.push({ id: 'Defend', upgraded: false });
        
        const sigCard = Object.keys(classPool)[0];
        this.player.deck.push({ id: sigCard, upgraded: false });
        
        this.shuffle(this.player.deck);
    }

    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]];
        }
    }

    updateUI() {
        document.querySelectorAll('.screen').forEach(s => s.classList.add('hidden'));
        
        if (this.state === 'START') {
            document.getElementById('screen-start').classList.remove('hidden');
        } else if (this.state === 'MAP') {
            document.getElementById('screen-map').classList.remove('hidden');
            document.getElementById('map-gold').innerText = `Gold: ${this.player.gold}`;
            document.getElementById('map-act').innerText = `Act ${this.player.act}: The Crypts`;
        } else if (this.state === 'COMBAT') {
            document.getElementById('screen-combat').classList.remove('hidden');
            this.updateCombatHUD();
        } else if (this.state === 'REWARD') {
            document.getElementById('screen-reward').classList.remove('hidden');
        } else if (this.state === 'EVENT') {
            document.getElementById('screen-event').classList.remove('hidden');
        } else if (this.state === 'SHOP') {
            document.getElementById('screen-shop').classList.remove('hidden');
        } else if (this.state === 'REST') {
            document.getElementById('screen-rest').classList.remove('hidden');
        } else if (this.state === 'GAMEOVER') {
            document.getElementById('screen-gameover').classList.remove('hidden');
        } else if (this.state === 'VICTORY') {
            document.getElementById('screen-victory').classList.remove('hidden');
        }
    }

    renderMap() {
        const canvas = document.getElementById('map-canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 800;
        canvas.height = 600;

        ctx.fillStyle = '#111';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const nodes = [];
        for (let y = 0; y < 15; y++) {
            nodes.push({ x: 400, y: 550 - (y * 35), type: 'Enemy' });
        }

        ctx.strokeStyle = '#444';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(400, 550);
        ctx.lineTo(400, 550 - (14 * 35));
        ctx.stroke();

        nodes.forEach((node, i) => {
            ctx.fillStyle = (i === this.player.floor) ? '#8b0000' : '#444';
            ctx.beginPath();
            ctx.arc(node.x, node.y, 10, 0, Math.PI * 2);
            ctx.fill();
            
            if (i === this.player.floor) {
                ctx.fillStyle = 'white';
                ctx.fillText('Current Floor', node.x + 15, node.y + 5);
            }
        });

        canvas.onclick = (e) => {
            if (this.state !== 'MAP') return;
            this.startCombat();
        };
    }

    startCombat() {
        this.state = 'COMBAT';
        this.combat = {
            turn: 1,
            enemies: [
                { name: 'Skeleton Warrior', hp: 22, maxHp: 22, block: 0, pattern: ENEMY_DATA.act1.Skeleton.pattern, turnIndex: 0 }
            ],
            playerBlock: 0
        };
        
        this.player.drawPile = [...this.player.deck];
        this.shuffle(this.player.drawPile);
        this.player.discardPile = [];
        this.player.energy = this.player.maxEnergy;
        
        this.startPlayerTurn();
        this.updateUI();
    }

    startPlayerTurn() {
        this.player.block = 0;
        this.player.energy = this.player.maxEnergy;
        this.drawCards(5);
        this.updateCombatHUD();
        this.renderHand();
    }

    drawCards(count) {
        for (let i = 0; i < count; i++) {
            if (this.player.drawPile.length === 0) {
                if (this.player.discardPile.length === 0) break;
                this.player.drawPile = [...this.player.discardPile];
                this.player.discardPile = [];
                this.shuffle(this.player.drawPile);
            }
            this.player.hand.push(this.player.drawPile.pop());
        }
    }

    updateCombatHUD() {
        document.getElementById('player-hp-fill').style.width = `${(this.player.hp / this.player.maxHp) * 100}%`;
        document.getElementById('player-hp-text').innerText = `${this.player.hp} / ${this.player.maxHp}`;
        document.getElementById('player-block').innerText = this.player.block;
        document.getElementById('player-energy').innerText = `Energy: ${this.player.energy} / ${this.player.maxEnergy}`;
        
        const enemyCont = document.getElementById('enemy-container');
        enemyCont.innerHTML = '';
        this.combat.enemies.forEach(en => {
            const el = document.createElement('div');
            el.className = 'enemy-unit';
            const intent = en.pattern[en.turnIndex % en.pattern.length];
            el.innerHTML = `
                <div class="enemy-intent">${intent.desc}</div>
                <div class="enemy-hp-bar"><div class="enemy-hp-fill" style="width:${(en.hp / en.maxHp)*100}%"></div></div>
                <div style="font-weight:bold">${en.name}</div>
                <div style="color:#4a9eff">Block: ${en.block}</div>
                <div>HP: ${en.hp}/${en.maxHp}</div>
            `;
            enemyCont.appendChild(el);
        });
    }

    renderHand() {
        const handEl = document.getElementById('hand');
        handEl.innerHTML = '';
        this.player.hand.forEach((card, idx) => {
            const cardInfo = this.getCardInfo(card);
            const el = document.createElement('div');
            el.className = 'card';
            if (cardInfo.cost > this.player.energy) el.classList.add('unplayable');
            
            el.innerHTML = `
                <div class="card-cost">${cardInfo.cost}</div>
                <div class="card-title">${card.id}${card.upgraded ? '+' : ''}</div>
                <div class="card-desc">${cardInfo.desc}</div>
            `;
            
            el.onclick = () => this.playCard(idx);
            handEl.appendChild(el);
        });
    }

    getCardInfo(card) {
        let data = CARD_DATA.basic[card.id] || CARD_DATA[this.player.class][card.id] || CARD_DATA.colorless[card.id] || CARD_DATA.status[card.id];
        if (card.upgraded && data.upgrade) {
            return { ...data, ...data.upgrade };
        }
        return data;
    }

    playCard(idx) {
        const card = this.player.hand[idx];
        const info = this.getCardInfo(card);
        if (info.cost > this.player.energy) return;
        
        this.player.energy -= info.cost;
        resolveCardEffect(this, card);
        
        this.player.discardPile.push(this.player.hand.splice(idx, 1)[0]);
        this.updateCombatHUD();
        this.renderHand();
    }

    damageEnemy(amount, targetIdx = 0) {
        const target = this.combat.enemies[targetIdx];
        if (!target) return;
        let rem = amount;
        if (target.block > 0) {
            const absorbed = Math.min(target.block, rem);
            target.block -= absorbed;
            rem -= absorbed;
        }
        target.hp -= rem;
        if (target.hp <= 0) {
            target.hp = 0;
            // Check if all enemies dead
            if (this.combat.enemies.every(e => e.hp <= 0)) {
                this.endCombat(true);
            }
        }
    }

    endCombat(victory) {
        if (victory) {
            this.state = 'REWARD';
            document.getElementById('reward-title').innerText = 'Victory!';
            document.getElementById('reward-next').classList.remove('hidden');
            document.getElementById('reward-next').onclick = () => {
                this.player.floor++;
                this.state = 'MAP';
                this.renderMap();
                this.updateUI();
            };
        } else {
            this.state = 'GAMEOVER';
        }
        this.updateUI();
    }
}

window.game = new Game();
document.getElementById('end-turn-btn').onclick = () => {
    const player = window.game.player;
    const combat = window.game.combat;
    
    player.discardPile.push(...player.hand);
    player.hand = [];
    
     combat.enemies.forEach(en => {
        if (en.hp <= 0) return;
        const intent = en.pattern[en.turnIndex % en.pattern.length];
        if (intent.type === 'attack') {
            let dmg = intent.val;
            if (player.block > 0) {
                const absorbed = Math.min(player.block, dmg);
                player.block -= absorbed;
                dmg -= absorbed;
            }
            player.hp -= dmg;
        } else if (intent.type === 'defend') {
            en.block += intent.val;
        } else if (intent.type === 'multi') {
            for (let i = 0; i < intent.hits; i++) {
                let dmg = intent.val;
                if (player.block > 0) {
                    const absorbed = Math.min(player.block, dmg);
                    player.block -= absorbed;
                    dmg -= absorbed;
                }
                player.hp -= dmg;
            }
        }
        en.turnIndex++;
    });
    
    if (player.hp <= 0) {
        window.game.endCombat(false);
    } else {
        window.game.startPlayerTurn();
    }
    window.game.updateUI();
};
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"composer","sessionId":"2659e74c-6657-4534-9ad3-121cc3e5e548","tokensIn":61053,"tokensOut":14241,"tokensTotal":75294,"turns":3,"toolCalls":10,"failedToolCalls":0,"timestamp":"2026-07-14T16:29:47.526Z"} -->