← Shadows results

Shadows

Gemma 4 31b · full

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 44,918 bytes · SHA-256 12e2af2e0ae9
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shadows of the Abyss</title>
    <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Crimson+Text:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-color: #0a0a0c;
            --panel-color: #1a1a1e;
            --accent-color: #8b0000;
            --text-color: #d4d4d8;
            --highlight-color: #f5f5f5;
            --gold-color: #ffd700;
            --energy-color: #4dabff;
            --block-color: #aab;
            --hp-color: #e53935;
            --rarity-common: #fff;
            --rarity-uncommon: #4caf50;
            --rarity-rare: #ff9800;
        }

        * { box-sizing: border-box; }
        body {
            margin: 0;
            padding: 0;
            background-color: var(--bg-color);
            color: var(--text-color);
            font-family: 'Crimson Text', serif;
            overflow: hidden;
            user-select: none;
        }

        h1, h2, h3, .cinzel {
            font-family: 'Cinzel', serif;
            text-transform: uppercase;
        }

        #game-container {
            width: 100vw;
            height: 100vh;
            position: relative;
            display: flex;
            flex-direction: column;
        }

        .screen {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: none;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 20px;
            background: radial-gradient(circle, #1a1a2e 0%, #0a0a0c 100%);
        }

        .screen.active { display: flex; }

        /* UI Components */
        .btn {
            background: var(--panel-color);
            color: var(--text-color);
            border: 2px solid var(--accent-color);
            padding: 10px 20px;
            font-family: 'Cinzel', serif;
            cursor: pointer;
            transition: all 0.2s;
            margin: 10px;
            font-size: 1.1rem;
        }
        .btn:hover {
            background: var(--accent-color);
            color: white;
        }
        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        /* Class Selection */
        .class-grid {
            display: flex;
            gap: 20px;
            margin-bottom: 40px;
        }
        .class-card {
            background: var(--panel-color);
            border: 3px solid #444;
            padding: 20px;
            width: 200px;
            text-align: center;
            cursor: pointer;
            transition: transform 0.2s, border-color 0.2s;
        }
        .class-card:hover {
            transform: translateY(-10px);
            border-color: var(--accent-color);
        }
        .class-card h3 { margin-top: 0; }

        /* Map Screen */
        #map-canvas {
            background: rgba(0,0,0,0.5);
            border: 2px solid #333;
            cursor: pointer;
        }
        .map-overlay {
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0,0,0,0.8);
            padding: 15px;
            border: 1px solid var(--accent-color);
            font-family: 'Cinzel', serif;
        }

        /* Combat Screen */
        #combat-area {
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            align-items: center;
            padding: 20px;
            position: relative;
        }
        .entity-row {
            display: flex;
            justify-content: center;
            gap: 50px;
            width: 100%;
            margin: 40px 0;
        }
        .entity {
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
            width: 150px;
        }
        .entity-sprite {
            width: 100px;
            height: 100px;
            background: #333;
            border-radius: 10px;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 3rem;
            box-shadow: 0 0 20px rgba(0,0,0,0.5);
            transition: transform 0.2s;
        }
        .entity.attacking .entity-sprite { transform: scale(1.1) translateY(-20px); }

        .stat-bar {
            width: 100%;
            height: 20px;
            background: #222;
            border: 1px solid #555;
            position: relative;
            margin: 5px 0;
        }
        .stat-fill {
            height: 100%;
            transition: width 0.3s ease-out;
        }
        .hp-fill { background: var(--hp-color); }
        .block-fill { background: var(--block-color); width: 0%; }
        .stat-text {
            position: absolute;
            width: 100%;
            text-align: center;
            font-size: 0.8rem;
            line-height: 20px;
            color: white;
            font-weight: bold;
            text-shadow: 1px 1px 2px black;
        }

        .intent-icon {
            position: absolute;
            top: -40px;
            background: rgba(0,0,0,0.7);
            padding: 5px 10px;
            border-radius: 5px;
            font-weight: bold;
            white-space: nowrap;
            font-family: 'Cinzel', serif;
            color: #ffbb00;
        }

        .status-container {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }
        .status-pill {
            font-size: 0.7rem;
            padding: 2px 5px;
            background: #444;
            border-radius: 3px;
            border: 1px solid #666;
        }

        /* Hand and Cards */
        #player-controls {
            width: 100%;
            height: 250px;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 15px;
        }
        #hand {
            display: flex;
            justify-content: center;
            gap: 10px;
            height: 180px;
            perspective: 1000px;
        }
        .card {
            width: 120px;
            height: 170px;
            background: #2a2a2e;
            border: 2px solid #555;
            border-radius: 10px;
            padding: 10px;
            display: flex;
            flex-direction: column;
            cursor: pointer;
            transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s;
            position: relative;
            user-select: none;
        }
        .card:hover {
            transform: translateY(-30px) scale(1.1);
            z-index: 10;
            border-color: white;
            box-shadow: 0 10px 20px rgba(0,0,0,0.5);
        }
        .card.playable { border-color: var(--energy-color); }
        .card.unplayable { opacity: 0.7; filter: grayscale(0.5); cursor: not-allowed; }
        
        .card-cost {
            position: absolute;
            top: -10px;
            left: -10px;
            width: 25px;
            height: 25px;
            background: var(--energy-color);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            color: white;
            border: 2px solid white;
        }
        .card-name {
            font-family: 'Cinzel', serif;
            font-size: 0.9rem;
            text-align: center;
            margin-bottom: 10px;
            border-bottom: 1px solid #555;
            padding-bottom: 5px;
        }
        .card-desc {
            font-size: 0.8rem;
            text-align: center;
            flex-grow: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            font-style: italic;
        }
        .card-rarity-common { border-color: var(--rarity-common); }
        .card-rarity-uncommon { border-color: var(--rarity-uncommon); }
        .card-rarity-rare { border-color: var(--rarity-rare); }

        #combat-info {
            display: flex;
            gap: 30px;
            font-family: 'Cinzel', serif;
            font-size: 1.2rem;
        }
        .info-item {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        /* Rewards Screen */
        .reward-options {
            display: flex;
            gap: 20px;
            margin-top: 30px;
        }

        /* Event/Shop Modal */
        .overlay-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.85);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 100;
        }
        .modal-content {
            background: var(--panel-color);
            border: 2px solid var(--accent-color);
            padding: 30px;
            max-width: 600px;
            text-align: center;
            border-radius: 10px;
        }
        .modal-text {
            font-size: 1.3rem;
            margin-bottom: 20px;
            line-height: 1.5;
        }
        .shop-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 15px;
            margin-bottom: 20px;
        }
        .shop-item {
            background: #222;
            border: 1px solid #444;
            padding: 10px;
            cursor: pointer;
        }
        .shop-item:hover { border-color: var(--gold-color); }

        /* Animations */
        @keyframes damage-shake {
            0% { transform: translateX(0); }
            25% { transform: translateX(-5px); }
            50% { transform: translateX(5px); }
            75% { transform: translateX(-5px); }
            100% { transform: translateX(0); }
        }
        .shake { animation: damage-shake 0.2s; }
        
        .floating-text {
            position: absolute;
            color: white;
            font-weight: bold;
            font-size: 1.5rem;
            pointer-events: none;
            animation: float-up 1s forwards;
            z-index: 100;
        }
        @keyframes float-up {
            0% { opacity: 1; transform: translateY(0); }
            100% { opacity: 0; transform: translateY(-50px); }
        }
    </style>
</head>
<body>

<div id="game-container">
    <!-- Start Screen -->
    <div id="screen-start" class="screen active">
        <h1 style="font-size: 3rem; color: var(--accent-color); margin-bottom: 50px;">Shadows of the Abyss</h1>
        <p>Choose your cursed archetype to begin your descent.</p>
        <div class="class-grid">
            <div class="class-card" onclick="game.selectClass('Blood Reaper')">
                <h3>Blood Reaper</h3>
                <p>Sacrifice vitality for devastating power.</p>
                <small>Specialty: HP Loss / Life Steal / Bleed</small>
            </div>
            <div class="class-card" onclick="game.selectClass('Shadowmancer')">
                <h3>Shadowmancer</h3>
                <p>Manipulate the void and weaken your foes.</p>
                <small>Specialty: Debuffs / Intangible / Draw</small>
            </div>
            <div class="class-card" onclick="game.selectClass('Hellforged')">
                <h3>Hellforged</h3>
                <p>Wield the fires of the pit to incinerate all.</p>
                <small>Specialty: Burn / Summoning / Energy</small>
            </div>
        </div>
    </div>

    <!-- Map Screen -->
    <div id="screen-map" class="screen">
        <div class="map-overlay">
            <div>Act: <span id="map-act">1</span> - <span id="map-act-name">The Crypts</span></div>
            <div>Gold: <span id="map-gold">0</span>g</div>
            <div>HP: <span id="map-hp">0</span> / <span id="map-max-hp">0</span></div>
        </div>
        <canvas id="map-canvas"></canvas>
    </div>

    <!-- Combat Screen -->
    <div id="screen-combat" class="screen">
        <div id="combat-area">
            <div class="entity-row" id="enemy-row"></div>
            
            <div class="entity-row">
                <div class="entity" id="player-entity">
                    <div class="entity-sprite">🛡️</div>
                    <div class="stat-bar"><div id="player-hp-fill" class="stat-fill hp-fill"></div><div id="player-hp-text" class="stat-text">70/70</div></div>
                    <div class="stat-bar"><div id="player-block-fill" class="stat-fill block-fill"></div><div id="player-block-text" class="stat-text">0</div></div>
                    <div class="status-container" id="player-statuses"></div>
                    <div style="margin-top: 10px; font-weight: bold;">The Fallen</div>
                </div>
            </div>

            <div id="player-controls">
                <div id="combat-info">
                    <div class="info-item">⚡ <span id="player-energy">3</span> / 3</div>
                    <div class="info-item">🎴 <span id="deck-count">0</span> in deck</div>
                </div>
                <div id="hand"></div>
                <button class="btn" onclick="game.endTurn()">End Turn</button>
            </div>
        </div>
    </div>

    <!-- Reward Screen -->
    <div id="screen-reward" class="screen">
        <h2 id="reward-title">Choose a Card</h2>
        <div class="reward-options" id="reward-cards"></div>
        <button class="btn" onclick="game.skipReward()">Skip Reward</button>
    </div>

    <!-- GameOver/Victory Screen -->
    <div id="screen-end" class="screen">
        <h1 id="end-title">Game Over</h1>
        <p id="end-msg"></p>
        <button class="btn" onclick="location.reload()">Try Again</button>
    </div>
</div>

<!-- Modals -->
<div id="modal-overlay" class="overlay-modal">
    <div class="modal-content">
        <h2 id="modal-title">Event</h2>
        <div id="modal-text" class="modal-text"></div>
        <div id="modal-options"></div>
    </div>
</div>

<script>
/** GAME DATA & CONFIG **/
const DATA = {
    CLASSES: {
        'Blood Reaper': {
            startingDeck: ['Strike', 'Strike', 'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend', 'Defend', 'Sanguine Strike'],
            pool: [
                { name: 'Sanguine Strike', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(8); p.heal(2); }, upgrade: (p, e) => { e.takeDamage(10); p.heal(3); } },
                { name: 'Hemorrhage', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(5); e.addStatus('Bleed', 3); }, upgrade: (p, e) => { e.takeDamage(6); e.addStatus('Bleed', 4); } },
                { name: 'Bloodletting', type: 'Attack', rarity: 'Uncommon', cost: 1, effect: (p, e) => { p.takeDamage(3); e.takeDamage(12); }, upgrade: (p, e) => { p.takeDamage(2); e.takeDamage(15); } },
                { name: 'Crimson Eruption', type: 'Attack', rarity: 'Rare', cost: 2, effect: (p, enemies) => { enemies.forEach(en => en.takeDamage(8)); p.takeDamage(2); }, upgrade: (p, enemies) => { enemies.forEach(en => en.takeDamage(10)); p.takeDamage(2); }, allEnemies: true },
                { name: 'Crimson Shield', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.gainBlock(6); p.takeDamage(2); }, upgrade: (p) => { p.gainBlock(8); p.takeDamage(2); } },
                { name: 'Blood Ward', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.gainBlock(3); p.addStatus('Regeneration', 1); }, upgrade: (p) => { p.gainBlock(4); p.addStatus('Regeneration', 2); } },
                { name: 'Sacrificial Barrier', type: 'Skill', rarity: 'Uncommon', cost: 2, effect: (p) => { p.gainBlock(10); p.takeDamage(5); }, upgrade: (p) => { p.gainBlock(13); p.takeDamage(5); } },
                { name: 'Vital Surge', type: 'Skill', rarity: 'Rare', cost: 0, effect: (p) => { const missing = p.maxHp - p.hp; p.gainBlock(missing); }, upgrade: (p) => { const missing = p.maxHp - p.hp; p.gainBlock(missing + 3); }, exhaust: true },
                { name: 'Life Tap', type: 'Skill', rarity: 'Common', cost: 0, effect: (p) => { p.draw(2); p.takeDamage(3); }, upgrade: (p) => { p.draw(3); p.takeDamage(3); } },
                { name: 'Rend Flesh', type: 'Skill', rarity: 'Uncommon', cost: 1, effect: (p, enemies) => { enemies.forEach(en => en.addStatus('Bleed', 5)); p.takeDamage(2); }, upgrade: (p, enemies) => { enemies.forEach(en => en.addStatus('Bleed', 7)); p.takeDamage(2); }, allEnemies: true },
                { name: 'Soul Siphon', type: 'Power', rarity: 'Uncommon', cost: 2, effect: (p) => { p.powers.push('Soul Siphon'); }, upgrade: (p) => { p.powers.push('Soul Siphon'); }, permanent: true, costUpgrade: 1 },
                { name: 'Blood Pact', type: 'Power', rarity: 'Rare', cost: 1, effect: (p) => { p.powers.push('Blood Pact'); }, upgrade: (p) => { p.powers.push('Blood Pact'); }, permanent: true },
            ]
        },
        'Shadowmancer': {
            startingDeck: ['Strike', 'Strike', 'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend', 'Defend', 'Shadow Bolt'],
            pool: [
                { name: 'Shadow Bolt', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(6); e.addStatus('Weak', 1); }, upgrade: (p, e) => { e.takeDamage(8); e.addStatus('Weak', 2); } },
                { name: 'Grasp of Despair', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(4); e.addStatus('Vulnerable', 1); }, upgrade: (p, e) => { e.takeDamage(5); e.addStatus('Vulnerable', 2); } },
                { name: 'Nightmare Lash', type: 'Attack', rarity: 'Uncommon', cost: 2, effect: (p, e) => { let dmg = 7; if(e.hasStatus('Weak')) dmg += 7; e.takeDamage(dmg); }, upgrade: (p, e) => { let dmg = 9; if(e.hasStatus('Weak')) dmg += 9; e.takeDamage(dmg); } },
                { name: 'Eternal Darkness', type: 'Attack', rarity: 'Rare', cost: 2, effect: (p, enemies) => { for(let i=0; i<3; i++) { const target = enemies[Math.floor(Math.random()*enemies.length)]; target.takeDamage(3); } }, upgrade: (p, enemies) => { for(let i=0; i<3; i++) { const target = enemies[Math.floor(Math.random()*enemies.length)]; target.takeDamage(4); } }, allEnemies: true },
                { name: 'Cloak of Shadows', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.gainBlock(5); p.draw(1); }, upgrade: (p) => { p.gainBlock(7); p.draw(1); } },
                { name: 'Haunting Mist', type: 'Skill', rarity: 'Common', cost: 1, effect: (p, enemies) => { p.gainBlock(7); enemies.forEach(en => en.addStatus('Weak', 1)); }, upgrade: (p, enemies) => { p.gainBlock(9); enemies.forEach(en => en.addStatus('Weak', 1)); }, allEnemies: true },
                { name: 'Phantom Armor', type: 'Skill', rarity: 'Uncommon', cost: 1, effect: (p) => { p.gainBlock(8); p.deferredBuffs.push({type: 'Dexterity', value: 2, trigger: 'nextTurn'}); }, upgrade: (p) => { p.gainBlock(10); p.deferredBuffs.push({type: 'Dexterity', value: 3, trigger: 'nextTurn'}); } },
                { name: 'Whispering Shroud', type: 'Skill', rarity: 'Rare', cost: 1, effect: (p) => { p.gainBlock(4); p.deferredBuffs.push({type: 'Block', value: 4, trigger: 'nextTurn'}); }, upgrade: (p) => { p.gainBlock(5); p.deferredBuffs.push({type: 'Block', value: 5, trigger: 'nextTurn'}); } },
                { name: 'Shadowform', type: 'Skill', rarity: 'Uncommon', cost: 2, effect: (p) => { p.addStatus('Intangible', 2); }, upgrade: (p) => { p.addStatus('Intangible', 3); }, exhaust: true },
                { name: 'Curse of Misfortune', type: 'Skill', rarity: 'Common', cost: 1, effect: (p, e) => { e.tempStatMod('Strength', -3); e.addStatus('Vulnerable', 1); }, upgrade: (p, e) => { e.tempStatMod('Strength', -4); e.addStatus('Vulnerable', 2); } },
                { name: 'Dark Ritual', type: 'Skill', rarity: 'Uncommon', cost: 0, effect: (p) => { p.gainEnergy(2); p.addToDeck('Dazed'); }, upgrade: (p) => { p.gainEnergy(2); p.addToDeck('Dazed'); } },
                { name: 'Umbral Mastery', type: 'Power', rarity: 'Rare', cost: 2, effect: (p) => { p.powers.push('Umbral Mastery'); }, upgrade: (p) => { p.powers.push('Umbral Mastery'); }, permanent: true },
            ]
        },
        'Hellforged': {
            startingDeck: ['Strike', 'Strike', 'Strike', 'Strike', 'Strike', 'Defend', 'Defend', 'Defend', 'Defend', 'Firebolt'],
            pool: [
                { name: 'Firebolt', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(7); e.addStatus('Burn', 2); }, upgrade: (p, e) => { e.takeDamage(9); e.addStatus('Burn', 3); } },
                { name: 'Hellfire Blast', type: 'Attack', rarity: 'Uncommon', cost: 2, effect: (p, enemies) => { enemies.forEach(en => en.takeDamage(9)); }, upgrade: (p, enemies) => { enemies.forEach(en => en.takeDamage(12)); }, allEnemies: true },
                { name: 'Searing Strike', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(6); p.deferredBuffs.push({type: 'Energy', value: 1, trigger: 'nextTurn'}); }, upgrade: (p, e) => { e.takeDamage(8); p.deferredBuffs.push({type: 'Energy', value: 1, trigger: 'nextTurn'}); } },
                { name: 'Immolate', type: 'Attack', rarity: 'Rare', cost: 2, effect: (p, e) => { e.takeDamage(12); }, upgrade: (p, e) => { e.takeDamage(16); }, exhaust: true },
                { name: 'Obsidian Skin', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.gainBlock(7); }, upgrade: (p) => { p.gainBlock(10); } },
                { name: 'Flame Ward', type: 'Skill', rarity: 'Common', cost: 1, effect: (p, enemies) => { p.gainBlock(5); const e = enemies[Math.floor(Math.random()*enemies.length)]; e.addStatus('Burn', 2); }, upgrade: (p, enemies) => { p.gainBlock(6); const e = enemies[Math.floor(Math.random()*enemies.length)]; e.addStatus('Burn', 3); }, allEnemies: true },
                { name: 'Demonic Resilience', type: 'Skill', rarity: 'Uncommon', cost: 1, effect: (p) => { p.gainBlock(4); p.heal(2); }, upgrade: (p) => { p.gainBlock(5); p.heal(3); } },
                { name: 'Brimstone Shield', type: 'Skill', rarity: 'Rare', cost: 1, effect: (p) => { p.gainBlock(3); p.tempStatMod('Strength', 2); }, upgrade: (p) => { p.gainBlock(4); p.tempStatMod('Strength', 3); } },
                { name: 'Summon Imp', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.addToHand('Imp Claw'); }, upgrade: (p) => { p.addToHand('Imp Claw+'); }, exhaust: true },
                { name: 'Infernal Pact', type: 'Skill', rarity: 'Uncommon', cost: 0, effect: (p) => { p.takeDamage(2); p.gainEnergy(2); p.addToHand('Imp Claw'); }, upgrade: (p) => { p.takeDamage(1); p.gainEnergy(2); p.addToHand('Imp Claw+'); } },
                { name: 'Hellfire Aura', type: 'Power', rarity: 'Rare', cost: 2, effect: (p) => { p.powers.push('Hellfire Aura'); }, upgrade: (p) => { p.powers.push('Hellfire Aura'); }, permanent: true },
                { name: 'Soul Burn', type: 'Skill', rarity: 'Uncommon', cost: 1, effect: (p, e) => { e.addStatus('Burn', 8); p.draw(1); }, upgrade: (p, e) => { e.addStatus('Burn', 10); p.draw(1); } },
            ]
        }
    },
    BASICS: {
        'Strike': { name: 'Strike', type: 'Attack', rarity: 'Basic', cost: 1, effect: (p, e) => { e.takeDamage(6); }, upgrade: (p, e) => { e.takeDamage(8); } },
        'Defend': { name: 'Defend', type: 'Skill', rarity: 'Basic', cost: 1, effect: (p) => { p.gainBlock(5); }, upgrade: (p) => { p.gainBlock(8); } },
    },
    COLORLESS: [
        { name: 'Flash of Insight', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.draw(2); }, upgrade: (p) => { p.draw(3); } },
        { name: 'Iron Will', type: 'Skill', rarity: 'Common', cost: 1, effect: (p) => { p.gainBlock(8); }, upgrade: (p) => { p.gainBlock(11); } },
        { name: 'Quick Slash', type: 'Attack', rarity: 'Common', cost: 1, effect: (p, e) => { e.takeDamage(7); }, upgrade: (p, e) => { e.takeDamage(9); } },
        { name: 'Potent Brew', type: 'Skill', rarity: 'Uncommon', cost: 0, effect: (p) => { p.gainEnergy(1); }, upgrade: (p) => { p.gainEnergy(2); }, exhaust: true },
    ],
    CURSES: {
        'Wound': { name: 'Wound', type: 'Curse', cost: 0, effect: () => {}, unplayable: true, ethereal: true, exhaust: true },
        'Doubt': { name: 'Doubt', type: 'Curse', cost: 0, effect: (p) => { p.takeDamage(1); }, unplayable: true },
        'Dazed': { name: 'Dazed', type: 'Status', cost: 0, effect: () => {}, unplayable: true, ethereal: true, exhaust: true },
    },
    TOKENS: {
        'Imp Claw': { name: 'Imp Claw', type: 'Attack', cost: 0, effect: (p, e) => { e.takeDamage(4); }, exhaust: true },
        'Imp Claw+': { name: 'Imp Claw+', type: 'Attack', cost: 0, effect: (p, e) => { e.takeDamage(6); }, exhaust: true },
    },
    ACTS: [
        {
            name: 'The Crypts',
            enemies: {
                'Skeleton Warrior': { hp: 22, sprite: '💀', pattern: [ {type: 'Attack', value: 6}, {type: 'Attack', value: 6}, {type: 'Defend', value: 8} ] },
                'Zombie Brute': { hp: 28, sprite: '🧟', pattern: [ {type: 'Attack', value: 8}, {type: 'Debuff', status: 'Weak', value: 2}, {type: 'Attack', value: 8} ] },
                'Dark Cultist': { hp: 20, sprite: '🧙', pattern: [ {type: 'Buff', stat: 'Strength', value: 3}, {type: 'Attack', value: 5}, {type: 'Attack', value: 5} ] },
                'Rat Swarm': { hp: 15, sprite: '🐀', pattern: [ {type: 'Multi', count: 2, value: 3}, {type: 'Multi', count: 2, value: 3} ] },
                'Tomb Guardian': { hp: 42, sprite: '🛡️', pattern: [ {type: 'Combo', attack: 6, block: 8}, {type: 'Multi', count: 3, value: 4}, {type: 'Attack', value: 12} ] },
                'Blood Witch': { hp: 36, sprite: '🩸', pattern: [ {type: 'Drain', attack: 5, heal: 4}, {type: 'Debuff', status: 'Vulnerable', value: 1, status2: 'Weak', value2: 1}, {type: 'Drain', attack: 7, heal: 4} ] },
                'Lich King': { hp: 65, sprite: '👑', isBoss: true }
            }
        }
        // Acts 2 and 3 would follow similarly. For the MVP, we'll iterate on Act 1.
    ]
};

/** GAME ENGINE **/
class Game {
    constructor() {
        this.state = 'START';
        this.player = null;
        this.currentAct = 0;
        this.currentFloor = 1;
        this.map = [];
        this.combat = null;
    }

    selectClass(className) {
        const classData = DATA.CLASSES[className];
        this.player = new Player(className, classData);
        this.state = 'MAP';
        this.currentAct = 0;
        this.currentFloor = 1;
        this.generateMap();
        this.showScreen('screen-map');
        this.updateMapUI();
    }

    generateMap() {
        this.map = [];
        const layout = [
            { type: 'START' }, // 1
            { type: 'Enemy', branch: true }, // 2
            { type: 'Enemy' }, // 3
            { type: 'Event' }, // 4
            { type: 'Shop' }, // 5
            { type: 'Enemy' }, // 6
            { type: 'Rest' }, // 7
            { type: 'Enemy', branch: true }, // 8
            { type: 'Elite', branch: true }, // 9
            { type: 'Event' }, // 10
            { type: 'Enemy' }, // 11
            { type: 'Elite', branch: true }, // 12
            { type: 'Enemy' }, // 13
            { type: 'Rest' }, // 14
            { type: 'Boss' } // 15
        ];
        
        for(let i=0; i<layout.length; i++) {
            this.map.push({ ...layout[i], floor: i + 1 });
        }
        this.drawMap();
    }

    drawMap() {
        const canvas = document.getElementById('map-canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 600;
        canvas.height = 800;
        
        ctx.clearRect(0,0,600,800);
        
        const nodes = this.map;
        const col = 300;
        const rowSpacing = 50;
        const startY = 750;

        // Draw paths
        ctx.strokeStyle = '#555';
        ctx.lineWidth = 2;
        ctx.beginPath();
        for(let i=0; i < nodes.length - 1; i++) {
            ctx.moveTo(col, startY - i * rowSpacing);
            ctx.lineTo(col, startY - (i+1) * rowSpacing);
        }
        ctx.stroke();

        // Draw nodes
        nodes.forEach((node, i) => {
            const x = col;
            const y = startY - i * rowSpacing;
            
            // Highlight the current floor and previous floors
            ctx.fillStyle = (node.floor <= this.currentFloor) ? varColor('--accent-color') : '#333';
            ctx.beginPath();
            ctx.arc(x, y, 10, 0, Math.PI*2);
            ctx.fill();
            
            // Edge highlight for the floor the player is currently ON
            if(node.floor === this.currentFloor) {
                ctx.strokeStyle = 'white';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.arc(x, y, 13, 0, Math.PI*2);
                ctx.stroke();
            }

            ctx.fillStyle = 'white';
            ctx.font = '12px Cinzel';
            ctx.textAlign = 'center';
            ctx.fillText(node.type, x + 20, y + 5);
        });
    }

    updateMapUI() {
        document.getElementById('map-act').innerText = this.currentAct + 1;
        document.getElementById('map-act-name').innerText = DATA.ACTS[0].name;
        document.getElementById('map-gold').innerText = this.player.gold;
        document.getElementById('map-hp').innerText = this.player.hp;
        document.getElementById('map-max-hp').innerText = this.player.maxHp;
    }

    handleMapClick(e) {
        const rect = document.getElementById('map-canvas').getBoundingClientRect();
        const y = e.clientY - rect.top;
        const rowSpacing = 50;
        const startY = 750;
        
        const floorIdx = Math.round((startY - y) / rowSpacing);
        if(floorIdx === this.currentFloor) {
            this.enterNode(this.map[floorIdx]);
        }
    }

    enterNode(node) {
        this.currentFloor = node.floor;
        if(node.type === 'Enemy' || node.type === 'Elite' || node.type === 'Boss') {
            this.startCombat(node.type);
        } else if(node.type === 'Shop') {
            this.openShop();
        } else if(node.type === 'Rest') {
            this.openRest();
        } else if(node.type === 'Event') {
            this.openEvent();
        } else if(node.type === 'START') {
            // do nothing
        }
    }

    startCombat(type) {
        this.state = 'COMBAT';
        this.combat = new Combat(this.player, type);
        this.showScreen('screen-combat');
        this.combat.init();
        this.combat.updateUI();
    }

    endTurn() {
        this.combat.endPlayerTurn();
    }

    showScreen(id) {
        document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
        document.getElementById(id).classList.add('active');
    }

    skipReward() {
        this.state = 'MAP';
        this.showScreen('screen-map');
        this.updateMapUI();
    }

    // Modal functions
    openModal(title, text, options) {
        document.getElementById('modal-title').innerText = title;
        document.getElementById('modal-text').innerText = text;
        const opts = document.getElementById('modal-options');
        opts.innerHTML = '';
        options.forEach(opt => {
            const b = document.createElement('button');
            b.className = 'btn';
            b.innerText = opt.text;
            b.onclick = () => {
                opt.action();
                document.getElementById('modal-overlay').style.display = 'none';
            };
            opts.appendChild(b);
        });
        document.getElementById('modal-overlay').style.display = 'flex';
    }

    openShop() {
        this.openModal('The Dark Merchant', 'Trade your gold for forbidden power.', [
            { text: 'Browse Wares', action: () => {
                // simplified shop for now
                this.player.gold -= 50;
                this.player.addRandomCard();
                this.updateMapUI();
            }},
            { text: 'Leave', action: () => {} }
        ]);
    }

    openRest() {
        this.openModal('Rest Site', 'A moment of respite in the abyss.', [
            { text: 'Heal 17 HP', action: () => { this.player.heal(17); this.updateMapUI(); }},
            { text: 'Leave', action: () => {} }
        ]);
    }

    openEvent() {
        this.openModal('Strange Occurrence', 'You find a blood-stained altar.', [
            { text: 'Offer 15 HP (Get Relic)', action: () => { this.player.takeDamage(15); this.updateMapUI(); }},
            { text: 'Leave', action: () => {} }
        ]);
    }
}

class Player {
    constructor(className, classData) {
        this.className = className;
        this.maxHp = 70;
        this.hp = 70;
        this.gold = 99;
        this.energy = 3;
        this.maxEnergy = 3;
        this.strength = 0;
        this.dexterity = 0;
        this.block = 0;
        this.statuses = {}; // name: value
        this.powers = [];
        this.deferredBuffs = [];
        this.relics = [];
        this.potions = [];
        
        this.deck = [];
        this.hand = [];
        this.drawPile = [];
        this.discardPile = [];
        
        // Initial deck
        classData.startingDeck.forEach(cardName => {
            this.deck.push(this.createCard(cardName));
        });
    }

    createCard(name) {
        let cardDef = DATA.BASICS[name] || DATA.CLASSES[this.className]?.pool.find(c => c.name === name) || DATA.CURSES[name] || DATA.TOKENS[name];
        if(!cardDef) return null;
        return { ...cardDef, upgraded: false, id: Math.random().toString(36).substr(2, 9) };
    }

    addRandomCard() {
        const pool = DATA.CLASSES[this.className].pool;
        const card = this.createCard(pool[Math.floor(Math.random()*pool.length)].name);
        this.deck.push(card);
    }

    addToDeck(name) {
        this.drawPile.push(this.createCard(name));
    }

    addToHand(name) {
        this.hand.push(this.createCard(name));
    }

    draw(count) {
        for(let i=0; i<count; i++) {
            if(this.drawPile.length === 0) {
                if(this.discardPile.length === 0) break;
                this.drawPile = [...this.discardPile].sort(() => Math.random() - 0.5);
                this.discardPile = [];
            }
            this.hand.push(this.drawPile.pop());
        }
    }

    gainBlock(val) {
        const bonus = this.statuses['Dexterity'] || 0;
        this.block += (val + bonus);
    }

    takeDamage(val) {
        if(this.statuses['Intangible']) {
            val = 1;
        }
        this.hp -= val;
        if(this.hp < 0) this.hp = 0;
    }

    heal(val) {
        this.hp = Math.min(this.maxHp, this.hp + val);
    }

    addStatus(name, val) {
        this.statuses[name] = (this.statuses[name] || 0) + val;
    }

    hasStatus(name) {
        return this.statuses[name] && this.statuses[name] > 0;
    }

    tempStatMod(stat, val) {
        this[stat.toLowerCase()] += val;
    }

    gainEnergy(val) {
        this.energy += val;
    }
}

class Enemy {
    constructor(name, data) {
        this.name = name;
        this.maxHp = data.hp;
        this.hp = data.hp;
        this.sprite = data.sprite;
        this.block = 0;
        this.strength = 0;
        this.statuses = {};
        this.pattern = data.pattern || [];
        this.turnCount = 0;
    }

    takeDamage(val) {
        if(this.block >= val) {
            this.block -= val;
        } else {
            const remaining = val - this.block;
            this.block = 0;
            this.hp -= remaining;
        }
    }

    addStatus(name, val) {
        this.statuses[name] = (this.statuses[name] || 0) + val;
    }

    hasStatus(name) {
        return this.statuses[name] && this.statuses[name] > 0;
    }
}

class Combat {
    constructor(player, type) {
        this.player = player;
        this.type = type;
        this.enemies = [];
        this.turn = 0;
        this.isPlayerTurn = true;
    }

    init() {
        const act = DATA.ACTS[0];
        if(this.type === 'Boss') {
            this.enemies.push(new Enemy('Lich King', act.enemies['Lich King']));
        } else if(this.type === 'Elite') {
            this.enemies.push(new Enemy('Tomb Guardian', act.enemies['Tomb Guardian']));
        } else {
            this.enemies.push(new Enemy('Skeleton Warrior', act.enemies['Skeleton Warrior']));
        }
        
        this.player.drawPile = [...this.player.deck].sort(() => Math.random() - 0.5);
        this.player.discardPile = [];
        this.player.hand = [];
        this.startTurn();
    }

    startTurn() {
        this.turn++;
        this.isPlayerTurn = true;
        this.player.energy = this.player.maxEnergy;
        this.player.block = 0;
        
        // Apply start-of-turn buffs
        if(this.player.statuses['Regeneration']) {
            this.player.heal(this.player.statuses['Regeneration']);
            this.player.statuses['Regeneration']--;
        }

        this.player.draw(5);
        this.updateUI();
    }

    playCard(cardId, targetIdx) {
        if(!this.isPlayerTurn) return;
        
        const cardIdx = this.player.hand.findIndex(c => c.id === cardId);
        const card = this.player.hand[cardIdx];
        
        if(this.player.energy < card.cost) return;
        
        this.player.energy -= card.cost;
        
        const target = this.enemies[targetIdx];
        const effectFn = card.upgraded ? card.upgrade : card.effect;
        
        if(card.allEnemies) {
            effectFn(this.player, this.enemies);
        } else {
            effectFn(this.player, target);
        }
        
        if(card.exhaust) {
            // remove from combat
        } else {
            this.player.discardPile.push(card);
        }
        
        this.player.hand.splice(cardIdx, 1);
        this.updateUI();
        this.checkWinLoss();
    }

    endPlayerTurn() {
        this.isPlayerTurn = false;
        this.player.discardPile.push(...this.player.hand);
        this.player.hand = [];
        this.updateUI();
        this.enemyTurn();
    }

    async enemyTurn() {
        for(const enemy of this.enemies) {
            if(enemy.hp <= 0) continue;
            
            const intent = this.getIntent(enemy);
            this.showIntent(enemy, intent);
            await new Promise(r => setTimeout(r, 600));
            
            this.executeIntent(enemy, intent);
            this.updateUI();
            await new Promise(r => setTimeout(r, 600));
        }
        this.startTurn();
    }

    getIntent(enemy) {
        if(enemy.pattern.length === 0) return {type: 'Attack', value: 10};
        return enemy.pattern[enemy.turnCount % enemy.pattern.length];
    }

    showIntent(enemy, intent) {
        const entityDiv = Array.from(document.querySelectorAll('.entity')).find(div => div.innerText.includes(enemy.name));
        if(!entityDiv) return;
        const icon = entityDiv.querySelector('.intent-icon');
        if(!icon) return;
        
        let text = '';
        switch(intent.type) {
            case 'Attack': text = `⚔️ ${intent.value}`; break;
            case 'Defend': text = `🛡️ ${intent.value}`; break;
            case 'Debuff': text = `🪄 ${intent.status}`; break;
            case 'Multi': text = `⚔️x${intent.count}`; break;
            case 'Combo': text = `⚔️🛡️`; break;
            case 'Drain': text = `🩸`; break;
            default: text = '❓';
        }
        icon.innerText = text;
    }

    executeIntent(enemy, intent) {
        const p = this.player;
        switch(intent.type) {
            case 'Attack':
                p.takeDamage(intent.value + enemy.strength);
                break;
            case 'Defend':
                enemy.block += intent.value;
                break;
            case 'Debuff':
                p.addStatus(intent.status, intent.value);
                break;
            case 'Multi':
                for(let i=0; i<intent.count; i++) p.takeDamage(intent.value);
                break;
            case 'Combo':
                p.takeDamage(intent.attack);
                enemy.block += intent.block;
                break;
            case 'Drain':
                p.takeDamage(intent.attack);
                enemy.heal ? enemy.heal(intent.heal) : enemy.hp += intent.heal;
                break;
        }
        enemy.turnCount++;
    }

    checkWinLoss() {
        if(this.enemies.every(e => e.hp <= 0)) {
            game.showScreen('screen-reward');
            this.player.gold += 20;
            return true;
        }
        if(this.player.hp <= 0) {
            document.getElementById('end-title').innerText = 'You Perished';
            document.getElementById('end-msg').innerText = 'The abyss consumes another soul.';
            game.showScreen('screen-end');
            return true;
        }
        return false;
    }

    updateUI() {
        const playerEntity = document.getElementById('player-entity');
        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-fill').style.width = (this.player.block > 0 ? 100 : 0) + '%';
        document.getElementById('player-block-text').innerText = this.player.block;
        document.getElementById('player-energy').innerText = this.player.energy;
        document.getElementById('deck-count').innerText = this.player.drawPile.length + this.player.discardPile.length;
        
        const statusDiv = document.getElementById('player-statuses');
        statusDiv.innerHTML = '';
        for(let s in this.player.statuses) {
            if(this.player.statuses[s] > 0) {
                const pill = document.createElement('div');
                pill.className = 'status-pill';
                pill.innerText = `${s} ${this.player.statuses[s]}`;
                statusDiv.appendChild(pill);
            }
        }

        const enemyRow = document.getElementById('enemy-row');
        enemyRow.innerHTML = '';
        this.enemies.forEach((e, idx) => {
            const intent = this.getIntent(e);
            let intentText = '❓';
            switch(intent.type) {
                case 'Attack': intentText = `⚔️ ${intent.value}`; break;
                case 'Defend': intentText = `🛡️ ${intent.value}`; break;
                case 'Debuff': intentText = `🪄 ${intent.status}`; break;
                case 'Multi': intentText = `⚔️x${intent.count}`; break;
                case 'Combo': intentText = `⚔️🛡️`; break;
                case 'Drain': intentText = `🩸`; break;
            }

            const div = document.createElement('div');
            div.className = 'entity';
            div.innerHTML = `
                <div class="intent-icon">${intentText}</div>
                <div class="entity-sprite">${e.sprite}</div>
                <div class="stat-bar"><div class="stat-fill hp-fill" style="width:${(e.hp/e.maxHp)*100}%"></div><div class="stat-text">${e.hp}/${e.maxHp}</div></div>
                <div class="stat-bar"><div class="stat-fill block-fill" style="width:${e.block > 0 ? 100 : 0}%"></div><div class="stat-text">${e.block}</div></div>
                <div style="margin-top: 10px; font-weight: bold;">${e.name}</div>
            `;
            enemyRow.appendChild(div);
        });

        const handDiv = document.getElementById('hand');
        handDiv.innerHTML = '';
        this.player.hand.forEach(card => {
            const cDiv = document.createElement('div');
            cDiv.className = `card ${this.player.energy >= card.cost ? 'playable' : 'unplayable'} card-rarity-${card.rarity.toLowerCase()}`;
            cDiv.innerHTML = `
                <div class="card-cost">${card.cost}</div>
                <div class="card-name">${card.name}</div>
                <div class="card-desc">${card.type}</div>
            `;
            cDiv.onclick = () => this.playCard(card.id, 0);
            handDiv.appendChild(cDiv);
        });
    }
}

function varColor(name) {
    return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
}

const game = new Game();

// Map click listener
document.getElementById('map-canvas').onclick = (e) => game.handleMapClick(e);

</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"full","sessionId":"0361acf7-0ce6-4c9f-9926-f57b13c300eb","tokensIn":194800,"tokensOut":16282,"tokensTotal":211082,"turns":7,"toolCalls":6,"failedToolCalls":0,"timestamp":"2026-07-14T16:28:26.499Z"} -->