Shadows
Gemma 4 31b · basic

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 34,955 bytes · SHA-256 15bdacee53a3
<!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@700&family=Crimson+Text:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #0a0a0c;
--card-bg: #1a1a20;
--accent-color: #8b0000;
--text-color: #d1d1d1;
--gold-color: #ffd700;
--energy-color: #4da6ff;
--block-color: #a9a9a9;
--hp-color: #ff4d4d;
}
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Crimson Text', serif;
overflow: hidden;
user-select: none;
}
h1, h2, h3 { font-family: 'Cinzel', serif; color: var(--accent-color); margin: 0; }
#game-container {
position: relative; width: 100vw; height: 100vh;
display: flex; justify-content: center; align-items: center;
}
.screen {
position: absolute; width: 100%; height: 100%;
display: none; flex-direction: column;
align-items: center; justify-content: center;
text-align: center;
}
.screen.active { display: flex; }
/* Class Selection */
.class-grid { display: flex; gap: 20px; margin-top: 40px; }
.class-card {
background: var(--card-bg); border: 2px solid #444;
padding: 20px; width: 200px; cursor: pointer;
transition: 0.3s; border-radius: 10px;
}
.class-card:hover { border-color: var(--accent-color); transform: translateY(-10px); }
/* Map */
#map-canvas {
background: #111; border: 3px solid #333;
cursor: pointer; box-shadow: 0 0 50px rgba(0,0,0,0.8);
}
/* Combat */
#combat-arena {
width: 100%; height: 100%;
background: radial-gradient(circle, #222 0%, #000 100%);
display: flex; flex-direction: column;
justify-content: space-between; padding: 20px; box-sizing: border-box;
}
.battle-top {
display: flex; justify-content: space-around; align-items: center;
height: 60%; width: 100%;
}
.entity {
position: relative; display: flex; flex-direction: column; align-items: center;
transition: transform 0.1s;
}
.entity-sprite {
width: 120px; height: 120px; background: #333;
border-radius: 50%; border: 4px solid var(--accent-color);
margin-bottom: 10px; display: flex; align-items: center; justify-content: center;
font-size: 40px;
}
.status-bar { width: 150px; background: #444; height: 15px; border: 1px solid #000; position: relative; }
.hp-fill { background: var(--hp-color); height: 100%; width: 100%; transition: width 0.3s; }
.block-badge {
position: absolute; top: -20px; right: -20px;
background: var(--block-color); color: #000;
width: 30px; height: 30px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-weight: bold; font-size: 14px; border: 2px solid #000;
}
.intent-icon {
position: absolute; top: -60px; font-size: 24px;
background: rgba(0,0,0,0.6); padding: 5px 10px; border-radius: 5px;
}
.hand {
display: flex; justify-content: center; align-items: flex-end;
gap: 10px; height: 220px; width: 100%;
}
.card {
width: 130px; height: 180px; background: var(--card-bg);
border: 2px solid #555; border-radius: 10px; cursor: pointer;
position: relative; display: flex; flex-direction: column;
padding: 10px; box-sizing: border-box; transition: 0.2s;
color: #eee; text-align: center;
}
.card:hover { transform: translateY(-30px) scale(1.1); border-color: var(--energy-color); }
.card .cost {
position: absolute; top: -10px; left: -10px;
width: 25px; height: 25px; background: var(--energy-color);
color: #000; border-radius: 50%; font-weight: bold; line-height: 25px;
}
.card .name { font-family: 'Cinzel', serif; font-size: 14px; margin-bottom: 10px; color: #fff; }
.card .desc { font-size: 12px; line-height: 1.2; }
.hud {
position: absolute; top: 20px; left: 20px; text-align: left;
font-family: 'Cinzel', serif; font-size: 20px;
}
.btn {
padding: 10px 20px; background: var(--accent-color); color: white;
border: none; cursor: pointer; font-family: 'Cinzel', serif;
border-radius: 5px; transition: 0.2s;
}
.btn:hover { background: #c00; }
.btn:disabled { background: #555; cursor: not-allowed; }
#end-turn-btn {
position: absolute; right: 40px; bottom: 100px;
padding: 20px 30px; font-size: 18px;
}
/* Modal / Reward */
.modal {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.9); display: none;
justify-content: center; align-items: center; z-index: 100;
}
.modal-content {
background: var(--card-bg); padding: 40px; border: 3px solid var(--accent-color);
border-radius: 15px; max-width: 800px; text-align: center;
}
.reward-options { display: flex; gap: 20px; margin: 20px 0; }
/* Shop/Rest */
.shop-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin: 20px 0; }
.shop-item {
background: #222; border: 1px solid #444; padding: 10px;
cursor: pointer; border-radius: 5px;
}
.shop-item:hover { background: #333; border-color: var(--gold-color); }
.log {
position: absolute; left: 20px; bottom: 150px; width: 300px;
max-height: 150px; overflow-y: auto; font-size: 14px;
color: #aaa; pointer-events: none; text-align: left;
}
.effect-badge {
font-size: 10px; background: #444; color: #fff;
padding: 2px 4px; border-radius: 3px; margin: 2px;
display: inline-block;
}
</style>
</head>
<body>
<div id="game-container">
<!-- Main Menu -->
<div id="screen-start" class="screen active">
<h1>SHADOWS OF THE ABYSS</h1>
<p>Select Your Cursed Archetype</p>
<div class="class-grid">
<div class="class-card" onclick="game.selectClass('Blood Reaper')">
<h3>Blood Reaper</h3>
<p>Sacrifice HP for immense power and bleeding wounds.</p>
</div>
<div class="class-card" onclick="game.selectClass('Shadowmancer')">
<h3>Shadowmancer</h3>
<p>Manipulate shadows to weaken foes and vanish from sight.</p>
</div>
<div class="class-card" onclick="game.selectClass('Hellforged')">
<h3>Hellforged</h3>
<p>Wield the fires of hell to burn all in your path.</p>
</div>
</div>
</div>
<!-- Map -->
<div id="screen-map" class="screen">
<h2 id="map-title">The Crypts</h2>
<canvas id="map-canvas" width="600" height="800"></canvas>
<div style="margin-top: 10px;">Gold: <span id="map-gold">0</span> | HP: <span id="map-hp">0/70</span></div>
</div>
<!-- Combat -->
<div id="screen-combat" class="screen">
<div id="combat-arena">
<div class="hud">
<div>Energy: <span id="combat-energy">3/3</span></div>
<div>HP: <span id="combat-hp">70/70</span></div>
<div>Gold: <span id="combat-gold">0</span></div>
</div>
<div class="battle-top" id="battle-entities">
<!-- Player and Enemies injected here -->
</div>
<div class="log" id="combat-log"></div>
<div class="hand" id="combat-hand"></div>
<button id="end-turn-btn" class="btn" onclick="combat.endPlayerTurn()">End Turn</button>
</div>
</div>
<!-- Modal -->
<div id="modal" class="modal">
<div class="modal-content" id="modal-inner"></div>
</div>
</div>
<script>
/**
* DATA & CONFIG
*/
const CARDS_DB = {
Basic: {
Strike: { name: 'Strike', cost: 1, type: 'Attack', desc: 'Deal 6 damage', effect: (t, p) => { t.takeDamage(6 + p.strength); }, upgrade: { desc: 'Deal 8 damage', effect: (t, p) => { t.takeDamage(8 + p.strength); } } },
Defend: { name: 'Defend', cost: 1, type: 'Skill', desc: 'Gain 5 Block', effect: (t, p) => { p.gainBlock(5 + p.dexterity); }, upgrade: { desc: 'Gain 8 Block', effect: (t, p) => { p.gainBlock(8 + p.dexterity); } } },
},
BloodReaper: {
SanguineStrike: { name: 'Sanguine Strike', cost: 1, type: 'Attack', desc: 'Deal 8 dmg. Heal 2 HP.', effect: (t, p) => { t.takeDamage(8 + p.strength); p.heal(2); }, upgrade: { desc: 'Deal 10 dmg. Heal 3 HP.', effect: (t, p) => { t.takeDamage(10 + p.strength); p.heal(3); } } },
Hemorrhage: { name: 'Hemorrhage', cost: 1, type: 'Attack', desc: 'Deal 5 dmg. Apply 3 Bleed.', effect: (t, p) => { t.takeDamage(5 + p.strength); t.applyStatus('Bleed', 3); }, upgrade: { desc: 'Deal 6 dmg. Apply 4 Bleed.', effect: (t, p) => { t.takeDamage(6 + p.strength); t.applyStatus('Bleed', 4); } } },
Bloodletting: { name: 'Bloodletting', cost: 1, type: 'Attack', desc: 'Lose 3 HP. Deal 12 dmg.', effect: (t, p) => { p.takeDamage(3); t.takeDamage(12 + p.strength); }, upgrade: { desc: 'Lose 2 HP. Deal 15 dmg.', effect: (t, p) => { p.takeDamage(2); t.takeDamage(15 + p.strength); } } },
CrimsonEruption: { name: 'Crimson Eruption', cost: 2, type: 'Attack', desc: 'Deal 8 dmg to ALL. Lose 2 HP.', effect: (t, p) => { combat.allEnemies().forEach(e => e.takeDamage(8 + p.strength)); p.takeDamage(2); }, upgrade: { desc: 'Deal 10 dmg to ALL. Lose 2 HP.', effect: (t, p) => { combat.allEnemies().forEach(e => e.takeDamage(10 + p.strength)); p.takeDamage(2); } } },
CrimsonShield: { name: 'Crimson Shield', cost: 1, type: 'Skill', desc: 'Gain 6 Block. Lose 2 HP.', effect: (t, p) => { p.gainBlock(6 + p.dexterity); p.takeDamage(2); }, upgrade: { desc: 'Gain 8 Block. Lose 2 HP.', effect: (t, p) => { p.gainBlock(8 + p.dexterity); p.takeDamage(2); } } },
BloodWard: { name: 'Blood Ward', cost: 1, type: 'Skill', desc: 'Gain 3 Block. Gain 1 Regen.', effect: (t, p) => { p.gainBlock(3 + p.dexterity); p.applyStatus('Regen', 1); }, upgrade: { desc: 'Gain 4 Block. Gain 2 Regen.', effect: (t, p) => { p.gainBlock(4 + p.dexterity); p.applyStatus('Regen', 2); } } },
SacrificialBarrier: { name: 'Sacrificial Barrier', cost: 2, type: 'Skill', desc: 'Gain 10 Block. Lose 5 HP.', effect: (t, p) => { p.gainBlock(10 + p.dexterity); p.takeDamage(5); }, upgrade: { desc: 'Gain 13 Block. Lose 5 HP.', effect: (t, p) => { p.gainBlock(13 + p.dexterity); p.takeDamage(5); } } },
VitalSurge: { name: 'Vital Surge', cost: 1, type: 'Skill', desc: 'Gain block = missing HP. Exhaust.', effect: (t, p) => { p.gainBlock(70 - p.hp); p.exhaustCard(); }, upgrade: { desc: 'Gain block = missing HP + 3. Exhaust.', effect: (t, p) => { p.gainBlock(73 - p.hp); p.exhaustCard(); } } },
LifeTap: { name: 'Life Tap', cost: 0, type: 'Skill', desc: 'Draw 2 cards. Lose 3 HP.', effect: (t, p) => { p.drawCards(2); p.takeDamage(3); }, upgrade: { desc: 'Draw 3 cards. Lose 3 HP.', effect: (t, p) => { p.drawCards(3); p.takeDamage(3); } } },
RendFlesh: { name: 'Rend Flesh', cost: 1, type: 'Skill', desc: 'Apply 5 Bleed to ALL. Lose 2 HP.', effect: (t, p) => { combat.allEnemies().forEach(e => e.applyStatus('Bleed', 5)); p.takeDamage(2); }, upgrade: { desc: 'Apply 7 Bleed to ALL. Lose 2 HP.', effect: (t, p) => { combat.allEnemies().forEach(e => e.applyStatus('Bleed', 7)); p.takeDamage(2); } } },
SoulSiphon: { name: 'Soul Siphon', cost: 2, type: 'Power', desc: 'Start turn: gain 1 Energy, lose 1 HP.', effect: (t, p) => { p.powers.push('SoulSiphon'); }, upgrade: { cost: 1, desc: 'Start turn: gain 1 Energy, lose 1 HP.', effect: (t, p) => { p.powers.push('SoulSiphon'); } } },
BloodPact: { name: 'Blood Pact', cost: 1, type: 'Power', desc: 'Whenever you lose HP from card, gain 1 Str.', effect: (t, p) => { p.powers.push('BloodPact'); }, upgrade: { desc: 'Whenever you lose HP from card, gain 2 Str.', effect: (t, p) => { p.powers.push('BloodPactUpgrade'); } } },
},
Shadowmancer: {
ShadowBolt: { name: 'Shadow Bolt', cost: 1, type: 'Attack', desc: 'Deal 6 dmg. Apply 1 Weak.', effect: (t, p) => { t.takeDamage(6 + p.strength); t.applyStatus('Weak', 1); }, upgrade: { desc: 'Deal 8 dmg. Apply 2 Weak.', effect: (t, p) => { t.takeDamage(8 + p.strength); t.applyStatus('Weak', 2); } } },
GraspOfDespair: { name: 'Grasp of Despair', cost: 1, type: 'Attack', desc: 'Deal 4 dmg. Apply 1 Vuln.', effect: (t, p) => { t.takeDamage(4 + p.strength); t.applyStatus('Vulnerable', 1); }, upgrade: { desc: 'Deal 5 dmg. Apply 2 Vuln.', effect: (t, p) => { t.takeDamage(5 + p.strength); t.applyStatus('Vulnerable', 2); } } },
NightmareLash: { name: 'Nightmare Lash', cost: 2, type: 'Attack', desc: 'Deal 7 dmg. If Weak, +7 dmg.', effect: (t, p) => { let d = 7 + p.strength; if (t.status.Weak > 0) d += 7; t.takeDamage(d); }, upgrade: { desc: 'Deal 9 dmg. If Weak, +9 dmg.', effect: (t, p) => { let d = 9 + p.strength; if (t.status.Weak > 0) d += 9; t.takeDamage(d); } } },
EternalDarkness: { name: 'Eternal Darkness', cost: 2, type: 'Attack', desc: 'Deal 3 dmg to random 3x.', effect: (t, p) => { for(let i=0; i<3; i++) { let target = combat.getRandomEnemy(); target.takeDamage(3 + p.strength); } }, upgrade: { desc: 'Deal 4 dmg to random 3x.', effect: (t, p) => { for(let i=0; i<3; i++) { let target = combat.getRandomEnemy(); target.takeDamage(4 + p.strength); } } } },
CloakOfShadows: { name: 'Cloak of Shadows', cost: 1, type: 'Skill', desc: 'Gain 5 Block. Draw 1.', effect: (t, p) => { p.gainBlock(5 + p.dexterity); p.drawCards(1); }, upgrade: { desc: 'Gain 7 Block. Draw 1.', effect: (t, p) => { p.gainBlock(7 + p.dexterity); p.drawCards(1); } } },
HauntingMist: { name: 'Haunting Mist', cost: 1, type: 'Skill', desc: 'Gain 7 Block. All Weak 1.', effect: (t, p) => { p.gainBlock(7 + p.dexterity); combat.allEnemies().forEach(e => e.applyStatus('Weak', 1)); }, upgrade: { desc: 'Gain 9 Block. All Weak 1.', effect: (t, p) => { p.gainBlock(9 + p.dexterity); combat.allEnemies().forEach(e => e.applyStatus('Weak', 1)); } } },
PhantomArmor: { name: 'Phantom Armor', cost: 1, type: 'Skill', desc: 'Gain 8 Block. Next turn 2 Dex.', effect: (t, p) => { p.gainBlock(8 + p.dexterity); p.nextTurnDex += 2; }, upgrade: { desc: 'Gain 10 Block. Next turn 3 Dex.', effect: (t, p) => { p.gainBlock(10 + p.dexterity); p.nextTurnDex += 3; } } },
WhisperingShroud: { name: 'Whispering Shroud', cost: 1, type: 'Skill', desc: 'Gain 4 Block. Next turn 4 Block.', effect: (t, p) => { p.gainBlock(4 + p.dexterity); p.nextTurnBlock += 4; }, upgrade: { desc: 'Gain 5 Block. Next turn 5 Block.', effect: (t, p) => { p.gainBlock(5 + p.dexterity); p.nextTurnBlock += 5; } } },
Shadowform: { name: 'Shadowform', cost: 2, type: 'Skill', desc: 'Gain 2 Intangible. Exhaust.', effect: (t, p) => { p.applyStatus('Intangible', 2); p.exhaustCard(); }, upgrade: { desc: 'Gain 3 Intangible. Exhaust.', effect: (t, p) => { p.applyStatus('Intangible', 3); p.exhaustCard(); } } },
CurseOfMisfortune: { name: 'Curse of Misfortune', cost: 1, type: 'Skill', desc: 'Enemy -3 Str this turn. 1 Vuln.', effect: (t, p) => { t.strength -= 3; t.applyStatus('Vulnerable', 1); }, upgrade: { desc: 'Enemy -4 Str this turn. 2 Vuln.', effect: (t, p) => { t.strength -= 4; t.applyStatus('Vulnerable', 2); } } },
DarkRitual: { name: 'Dark Ritual', cost: 0, type: 'Skill', desc: 'Gain 2 Energy. Shuffle Dazed.', effect: (t, p) => { p.energy += 2; p.addToDeck('Dazed'); }, upgrade: { desc: 'Gain 2 Energy. Shuffle Dazed.', effect: (t, p) => { p.energy += 2; p.addToDeck('Dazed'); } } },
UmbralMastery: { name: 'Umbral Mastery', cost: 2, type: 'Power', desc: 'Start turn: All enemies Weak 1.', effect: (t, p) => { p.powers.push('UmbralMastery'); }, upgrade: { desc: 'Start turn: All enemies Weak 2.', effect: (t, p) => { p.powers.push('UmbralMasteryUpgrade'); } } },
},
Hellforged: {
Firebolt: { name: 'Firebolt', cost: 1, type: 'Attack', desc: 'Deal 7 dmg. Apply 2 Burn.', effect: (t, p) => { t.takeDamage(7 + p.strength); t.applyStatus('Burn', 2); }, upgrade: { desc: 'Deal 9 dmg. Apply 3 Burn.', effect: (t, p) => { t.takeDamage(9 + p.strength); t.applyStatus('Burn', 3); } } },
HellfireBlast: { name: 'Hellfire Blast', cost: 2, type: 'Attack', desc: 'Deal 9 dmg to ALL.', effect: (t, p) => { combat.allEnemies().forEach(e => e.takeDamage(9 + p.strength)); }, upgrade: { desc: 'Deal 12 dmg to ALL.', effect: (t, p) => { combat.allEnemies().forEach(e => e.takeDamage(12 + p.strength)); } } },
SearingStrike: { name: 'Searing Strike', cost: 1, type: 'Attack', desc: 'Deal 6 dmg. Next turn +1 Energy.', effect: (t, p) => { t.takeDamage(6 + p.strength); p.nextTurnEnergy += 1; }, upgrade: { desc: 'Deal 8 dmg. Next turn +1 Energy.', effect: (t, p) => { t.takeDamage(8 + p.strength); p.nextTurnEnergy += 1; } } },
Immolate: { name: 'Immolate', cost: 2, type: 'Attack', desc: 'Deal 12 dmg. Exhaust.', effect: (t, p) => { t.takeDamage(12 + p.strength); p.exhaustCard(); }, upgrade: { desc: 'Deal 16 dmg. Exhaust.', effect: (t, p) => { t.takeDamage(16 + p.strength); p.exhaustCard(); } } },
ObsidianSkin: { name: 'Obsidian Skin', cost: 1, type: 'Skill', desc: 'Gain 7 Block.', effect: (t, p) => { p.gainBlock(7 + p.dexterity); }, upgrade: { desc: 'Gain 10 Block.', effect: (t, p) => { p.gainBlock(10 + p.dexterity); } } },
FlameWard: { name: 'Flame Ward', cost: 1, type: 'Skill', desc: 'Gain 5 Block. Randomenemy Burn 2.', effect: (t, p) => { p.gainBlock(5 + p.dexterity); combat.getRandomEnemy().applyStatus('Burn', 2); }, upgrade: { desc: 'Gain 6 Block. Randomenemy Burn 3.', effect: (t, p) => { p.gainBlock(6 + p.dexterity); combat.getRandomEnemy().applyStatus('Burn', 3); } } },
DemonicResilience: { name: 'Demonic Resilience', cost: 1, type: 'Skill', desc: 'Gain 4 Block. Heal 2 HP.', effect: (t, p) => { p.gainBlock(4 + p.dexterity); p.heal(2); }, upgrade: { desc: 'Gain 5 Block. Heal 3 HP.', effect: (t, p) => { p.gainBlock(5 + p.dexterity); p.heal(3); } } },
BrimstoneShield: { name: 'Brimstone Shield', cost: 1, type: 'Skill', desc: 'Gain 3 Block. +2 Str this turn.', effect: (t, p) => { p.gainBlock(3 + p.dexterity); p.strength += 2; combat.tempBuffs.push({type:'strength', val: 2, duration: 1}); }, upgrade: { desc: 'Gain 4 Block. +3 Str this turn.', effect: (t, p) => { p.gainBlock(4 + p.dexterity); p.strength += 3; combat.tempBuffs.push({type:'strength', val: 3, duration: 1}); } } },
SummonImp: { name: 'Summon Imp', cost: 1, type: 'Skill', desc: 'Add Imp Claw to hand. Exhaust.', effect: (t, p) => { p.addToHand('ImpClaw'); p.exhaustCard(); }, upgrade: { desc: 'Add Imp Claw+ to hand. Exhaust.', effect: (t, p) => { p.addToHand('ImpClawPlus'); p.exhaustCard(); } } },
InfernalPact: { name: 'Infernal Pact', cost: 0, type: 'Skill', desc: 'Lose 2 HP. Gain 2 Energy. Add Imp Claw.', effect: (t, p) => { p.takeDamage(2); p.energy += 2; p.addToHand('ImpClaw'); }, upgrade: { desc: 'Lose 1 HP. Gain 2 Energy. Add Imp Claw+.', effect: (t, p) => { p.takeDamage(1); p.energy += 2; p.addToHand('ImpClawPlus'); } } },
HellfireAura: { name: 'Hellfire Aura', cost: 2, type: 'Power', desc: 'Start turn: deal 3 dmg to ALL.', effect: (t, p) => { p.powers.push('HellfireAura'); }, upgrade: { desc: 'Start turn: deal 5 dmg to ALL.', effect: (t, p) => { p.powers.push('HellfireAuraUpgrade'); } } },
SoulBurn: { name: 'Soul Burn', cost: 1, type: 'Skill', desc: 'Apply 8 Burn to enemy. Draw 1.', effect: (t, p) => { t.applyStatus('Burn', 8); p.drawCards(1); }, upgrade: { desc: 'Apply 10 Burn to enemy. Draw 1.', effect: (t, p) => { t.applyStatus('Burn', 10); p.drawCards(1); } } },
},
Colorless: {
FlashOfInsight: { name: 'Flash of Insight', cost: 1, type: 'Skill', desc: 'Draw 2 cards.', effect: (t, p) => { p.drawCards(2); }, upgrade: { desc: 'Draw 3 cards.', effect: (t, p) => { p.drawCards(3); } } },
IronWill: { name: 'Iron Will', cost: 1, type: 'Skill', desc: 'Gain 8 Block.', effect: (t, p) => { p.gainBlock(8 + p.dexterity); }, upgrade: { desc: 'Gain 11 Block.', effect: (t, p) => { p.gainBlock(11 + p.dexterity); } } },
QuickSlash: { name: 'Quick Slash', cost: 1, type: 'Attack', desc: 'Deal 7 damage.', effect: (t, p) => { t.takeDamage(7 + p.strength); }, upgrade: { desc: 'Deal 9 damage.', effect: (t, p) => { t.takeDamage(9 + p.strength); } } },
PotentBrew: { name: 'Potent Brew', cost: 0, type: 'Skill', desc: 'Gain 1 Energy. Exhaust.', effect: (t, p) => { p.energy += 1; p.exhaustCard(); }, upgrade: { desc: 'Gain 2 Energy. Exhaust.', effect: (t, p) => { p.energy += 2; p.exhaustCard(); } } },
}
};
const ENEMIES = {
Act1: {
SkeletonWarrior: { name: 'Skeleton Warrior', hp: 22, pattern: [{type:'Attack', val:6}, {type:'Attack', val:6}, {type:'Defend', val:8}], sprite: '💀' },
ZombieBrute: { name: 'Zombie Brute', hp: 28, pattern: [{type:'Attack', val:8}, {type:'Debuff', status:'Weak', val:2}, {type:'Attack', val:8}], sprite: '🧟' },
DarkCultist: { name: 'Dark Cultist', hp: 20, pattern: [{type:'Buff', stat:'strength', val:3}, {type:'Attack', val:5}, {type:'Attack', val:5}], sprite: '🧙' },
RatSwarm: { name: 'Rat Swarm', hp: 15, pattern: [{type:'MultiAttack', count:2, val:3}, {type:'MultiAttack', count:2, val:3}], sprite: '🐀' },
TombGuardian: { name: 'Tomb Guardian', hp: 42, pattern: [{type:'ShieldBash', atk:6, block:8}, {type:'Flurry', count:3, val:4}, {type:'HeavySlam', val:12}], sprite: '🛡️' },
BloodWitch: { name: 'Blood Witch', hp: 36, pattern: [{type:'Drain', atk:5, heal:4}, {type:'Curse', vuln:1, weak:1}, {type:'BloodRitual', atk:7, heal:4}], sprite: '🧛' },
LichKing: { name: 'Lich King', hp: 65, pattern: [{type:'Summon', unit:'SkeletonWarrior'}, {type:'Attack', val:8}, {type:'Defend', val:12}], sprite: '👑' },
},
// Act 2 and 3 defined similarly... simplified for now
};
/**
* GAME LOGIC
*/
class Entity {
constructor(name, hp) {
this.name = name;
this.maxHp = hp;
this.hp = hp;
this.block = 0;
this.strength = 0;
this.dexterity = 0;
this.status = { Weak: 0, Vulnerable: 0, Bleed: 0, Burn: 0, Poison: 0, Regen: 0, Intangible: 0 };
this.powers = [];
}
takeDamage(amt) {
if (this.status.Intangible > 0) amt = 1;
if (this.status.Vulnerable > 0) amt = Math.floor(amt * 1.5);
let remaining = amt;
if (this.block > 0) {
let absorbed = Math.min(this.block, remaining);
this.block -= absorbed;
remaining -= absorbed;
}
this.hp -= remaining;
if (this.hp < 0) this.hp = 0;
combat.log(`${this.name} takes ${amt - (amt - remaining)} damage.`);
}
gainBlock(amt) {
this.block += amt;
}
applyStatus(type, val) {
this.status[type] += val;
combat.log(`${this.name} gains ${val} ${type}.`);
}
heal(amt) {
this.hp = Math.min(this.maxHp, this.hp + amt);
}
}
class Player extends Entity {
constructor(className) {
super(className, 70);
this.className = className;
this.energy = 3;
this.maxEnergy = 3;
this.gold = 99;
this.deck = [];
this.hand = [];
this.drawPile = [];
this.discardPile = [];
this.nextTurnEnergy = 0;
this.nextTurnBlock = 0;
this.nextTurnDex = 0;
this.relics = [];
this.potions = [];
this.exhausted = [];
// Starting Deck
for(let i=0; i<5; i++) this.deck.push({id: 'Strike', upgraded: false});
for(let i=0; i<4; i++) this.deck.push({id: 'Defend', upgraded: false});
const sigMap = { 'Blood Reaper': 'SanguineStrike', 'Shadowmancer': 'ShadowBolt', 'Hellforged': 'Firebolt' };
this.deck.push({id: sigMap[className], upgraded: false});
}
drawCards(count) {
for (let i = 0; i < count; i++) {
if (this.drawPile.length === 0) {
this.drawPile = [...this.discardPile];
this.discardPile = [];
this.shuffle(this.drawPile);
}
if (this.drawPile.length > 0) {
this.hand.push(this.drawPile.pop());
}
}
}
addToDeck(cardId) { this.deck.push({id: cardId, upgraded: false}); }
addToHand(cardId) { this.hand.push({id: cardId, upgraded: false}); }
exhaustCard() {
// Implementation: mark card as exhausted in current combat session
const index = this.hand.findIndex(c => c === this.currentCard);
if (index !== -1) {
const card = this.hand.splice(index, 1)[0];
this.exhausted.push(card);
}
}
shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
}
const game = {
player: null,
act: 1,
floor: 1,
currentScreen: 'screen-start',
selectClass(className) {
this.player = new Player(className);
this.showScreen('screen-map');
this.renderMap();
},
showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(id).classList.add('active');
this.currentScreen = id;
},
renderMap() {
const canvas = document.getElementById('map-canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const nodes = [
{f: 2, t: 'Enemy', x: 200, y: 700}, {f: 2, t: 'Enemy', x: 400, y: 700},
{f: 3, t: 'Enemy', x: 300, y: 600},
{f: 4, t: 'Event', x: 300, y: 500},
{f: 5, t: 'Shop', x: 300, y: 400},
{f: 6, t: 'Enemy', x: 300, y: 300},
{f: 7, t: 'Rest', x: 300, y: 200},
{f: 15, t: 'Boss', x: 300, y: 50}
];
nodes.forEach(n => {
ctx.beginPath();
ctx.arc(n.x, n.y, 20, 0, Math.PI*2);
ctx.fillStyle = n.t === 'Boss' ? 'red' : (n.t === 'Enemy' ? '#555' : '#44a');
ctx.fill();
ctx.fillStyle = 'white';
ctx.fillText(n.t, n.x - 20, n.y - 30);
});
canvas.onclick = (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Simple "click any node" to start combat for demo
this.startCombat('SkeletonWarrior');
};
document.getElementById('map-gold').innerText = this.player.gold;
document.getElementById('map-hp').innerText = `${this.player.hp}/${this.player.maxHp}`;
},
startCombat(enemyKey) {
combat.init(enemyKey);
this.showScreen('screen-combat');
}
};
const combat = {
player: null,
enemies: [],
turn: 'player',
tempBuffs: [],
init(enemyKey) {
this.player = game.player;
this.enemies = [new Entity(ENEMIES.Act1[enemyKey].name, ENEMIES.Act1[enemyKey].hp)];
this.enemies[0].sprite = ENEMIES.Act1[enemyKey].sprite;
this.enemies[0].pattern = ENEMIES.Act1[enemyKey].pattern;
this.enemies[0].patternIdx = 0;
this.player.drawPile = [...this.player.deck];
this.player.shuffle(this.player.drawPile);
this.player.discardPile = [];
this.player.hand = [];
this.player.block = 0;
this.player.energy = this.player.maxEnergy;
this.player.drawCards(5);
this.render();
this.log("Combat Started!");
},
allEnemies() { return this.enemies; },
getRandomEnemy() { return this.enemies[Math.floor(Math.random() * this.enemies.length)]; },
log(msg) {
const l = document.getElementById('combat-log');
l.innerHTML += `<div>${msg}</div>`;
l.scrollTop = l.scrollHeight;
},
playCard(cardIndex) {
if (this.turn !== 'player') return;
const cardObj = this.player.hand[cardIndex];
const cardData = this.getCardData(cardObj);
if (this.player.energy < cardData.cost) {
this.log("Not enough energy!");
return;
}
this.player.energy -= cardData.cost;
this.player.currentCard = cardObj;
const target = this.getRandomEnemy();
cardData.effect(target, this.player);
this.player.discardPile.push(this.player.hand.splice(cardIndex, 1)[0]);
this.render();
},
getCardData(cardObj) {
let pool = CARDS_DB.Basic;
if (game.player.className === 'Blood Reaper') pool = {...CARDS_DB.Basic, ...CARDS_DB.BloodReaper};
if (game.player.className === 'Shadowmancer') pool = {...CARDS_DB.Basic, ...CARDS_DB.Shadowmancer};
if (game.player.className === 'Hellforged') pool = {...CARDS_DB.Basic, ...CARDS_DB.Hellforged};
const data = pool[cardObj.id];
return cardObj.upgraded ? data.upgrade : data;
},
endPlayerTurn() {
this.turn = 'enemy';
this.player.block = 0;
// Discard hand
this.player.discardPile.push(...this.player.hand);
this.player.hand = [];
this.log("Enemy's Turn...");
this.render();
setTimeout(() => this.enemyTurn(), 800);
},
enemyTurn() {
this.enemies.forEach(e => {
if (e.hp <= 0) return;
const act = e.pattern[e.patternIdx];
this.executeEnemyAction(e, act);
e.patternIdx = (e.patternIdx + 1) % e.pattern.length;
});
setTimeout(() => {
this.turn = 'player';
this.startPlayerTurn();
}, 1000);
},
executeEnemyAction(e, act) {
if (act.type === 'Attack') {
this.player.takeDamage(act.val + e.strength);
} else if (act.type === 'Defend') {
e.gainBlock(act.val);
} else if (act.type === 'Debuff') {
this.player.applyStatus(act.status, act.val);
} else if (act.type === 'Buff') {
e[act.stat] += act.val;
} else if (act.type === 'MultiAttack') {
for(let i=0; i<act.count; i++) this.player.takeDamage(act.val + e.strength);
} else if (act.type === 'ShieldBash') {
this.player.takeDamage(act.val + e.strength);
e.gainBlock(act.block);
}
this.render();
},
startPlayerTurn() {
this.player.energy = this.player.maxEnergy + this.player.nextTurnEnergy;
this.player.nextTurnEnergy = 0;
this.player.gainBlock(this.player.nextTurnBlock);
this.player.nextTurnBlock = 0;
this.player.dexterity += this.player.nextTurnDex;
this.player.nextTurnDex = 0;
this.player.drawCards(5);
this.log("Your Turn!");
this.render();
},
render() {
document.getElementById('combat-energy').innerText = `${this.player.energy}/${this.player.maxEnergy}`;
document.getElementById('combat-hp').innerText = `${this.player.hp}/${this.player.maxHp}`;
document.getElementById('combat-gold').innerText = this.player.gold;
const arena = document.getElementById('battle-entities');
arena.innerHTML = '';
// Player
const pDiv = document.createElement('div');
pDiv.className = 'entity';
pDiv.innerHTML = `
<div class="entity-sprite" style="background: #224">👤</div>
<div class="status-bar"><div class="hp-fill" style="width:${(this.player.hp/this.player.maxHp)*100}%"></div></div>
<div style="margin-top:5px">${this.player.name}</div>
${this.player.block > 0 ? `<div class="block-badge">${this.player.block}</div>` : ''}
<div style="font-size:12px">${Object.entries(this.player.status).filter(([k,v])=>v>0).map(([k,v])=>`<span class="effect-badge">${k}:${v}</span>`).join('')}</div>
`;
arena.appendChild(pDiv);
// Enemies
this.enemies.forEach((e, idx) => {
const eDiv = document.createElement('div');
eDiv.className = 'entity';
const intent = e.pattern[e.patternIdx];
const intentText = intent.type === 'Attack' ? `⚔️ ${intent.val}` : (intent.type === 'Defend' ? `🛡️ ${intent.val}` : '❓');
eDiv.innerHTML = `
<div class="intent-icon">${intentText}</div>
<div class="entity-sprite">${e.sprite}</div>
<div class="status-bar"><div class="hp-fill" style="width:${(e.hp/e.maxHp)*100}%"></div></div>
<div style="margin-top:5px">${e.name}</div>
${e.block > 0 ? `<div class="block-badge">${e.block}</div>` : ''}
`;
arena.appendChild(eDiv);
});
const handDiv = document.getElementById('combat-hand');
handDiv.innerHTML = '';
this.player.hand.forEach((card, idx) => {
const data = this.getCardData(card);
const cDiv = document.createElement('div');
cDiv.className = 'card';
cDiv.onclick = () => this.playCard(idx);
cDiv.innerHTML = `
<div class="cost">${data.cost}</div>
<div class="name">${data.name}</div>
<div class="desc">${data.desc}</div>
`;
handDiv.appendChild(cDiv);
});
}
};
// Initial startup
window.onload = () => {
// Setup screens
};
</script>
</body>
</html>
<!-- agent-meta {"model":"gemma-4-31b","provider":"cerebras","persona":"basic","sessionId":"371f27c0-e7d2-41d4-b85e-2e1a47942ca7","tokensIn":8243,"tokensOut":11762,"tokensTotal":20005,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-14T21:56:42.466Z"} -->