← Deckbuilder results

Deckbuilder

GLM 5.3 Flash · full

Initial view of Deckbuilder

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 36,581 bytes · SHA-256 ee2ff946e90d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadows of Aethelgard</title>
<style>
:root{
  --gold:#c9a227; --parch:#1a1410; --ink:#e8dcc0;
  --atk:#c0392b; --def:#3b6ea5; --oth:#7d4ba0;
  --bg0:#0a0908; --bg1:#141018; --metal:#2a2d33;
}
*{box-sizing:border-box;margin:0;padding:0;user-select:none}
body{background:radial-gradient(ellipse at 50% -10%, #1c1626 0%, #0a0908 65%);color:var(--ink);
  font-family:'Segoe UI',Georgia,serif;min-height:100vh;overflow-x:hidden}
h1,h2,h3{font-family:Georgia,serif;letter-spacing:2px}
button{font-family:inherit;cursor:pointer}
.etched{background:linear-gradient(180deg,#3a3e46,#22252b);border:1px solid #555;
  color:var(--ink);border-radius:6px;box-shadow:inset 0 1px 0 #666,0 3px 6px #000;
  padding:8px 18px;font-size:14px;letter-spacing:1px;transition:all .15s}
.etched:hover:not(:disabled){filter:brightness(1.25);transform:translateY(-1px)}
.etched:disabled{opacity:.4;cursor:not-allowed}

/* ===== MENU ===== */
#menu{display:flex;flex-direction:column;align-items:center;padding-top:6vh;gap:26px;min-height:100vh}
#menu h1{font-size:52px;color:var(--gold);text-shadow:0 0 24px rgba(201,162,39,.5),0 2px 0 #000}
#menu .sub{color:#8a7f6a;font-style:italic;letter-spacing:4px;margin-top:-16px}
.classes{display:flex;gap:28px;flex-wrap:wrap;justify-content:center}
.classCard{width:250px;background:linear-gradient(170deg,#1c1720,#0d0b10);border:1px solid #3a3348;
  border-radius:10px;padding:20px;text-align:center;cursor:pointer;transition:all .2s;position:relative}
.classCard:hover{transform:translateY(-6px);border-color:var(--gold);box-shadow:0 10px 30px rgba(0,0,0,.8),0 0 18px rgba(201,162,39,.25)}
.classCard svg{width:110px;height:150px;filter:drop-shadow(0 4px 8px #000)}
.classCard h3{margin:10px 0 6px;font-size:20px}
.classCard p{font-size:12.5px;color:#a99f8c;line-height:1.5;font-style:italic}
.classCard .stats{margin-top:10px;font-size:12px;color:#7fb0d8}

/* ===== HUD ===== */
#game{display:none;flex-direction:column;height:100vh}
#topbar{display:flex;justify-content:space-between;align-items:center;padding:8px 18px;
  background:linear-gradient(180deg,#221c2c,#120e18);border-bottom:2px solid #3a3348;box-shadow:0 3px 10px #000}
#topbar .stat{display:flex;align-items:center;gap:8px;font-size:15px}
.pill{background:#0d0b12;border:1px solid #4a4030;border-radius:20px;padding:4px 14px;white-space:nowrap}
.pill b{color:var(--gold)}
#hpwrap{width:220px}
.barOuter{height:16px;background:#0d0b12;border:1px solid #4a4030;border-radius:9px;overflow:hidden;position:relative}
.barFill{height:100%;background:linear-gradient(90deg,#7e1f1f,#c0392b);transition:width .3s}
#hpText{position:absolute;inset:0;text-align:center;font-size:11px;line-height:16px;text-shadow:0 1px 2px #000}

#main{flex:1;display:flex;min-height:0}
#left{width:190px;padding:10px;border-right:1px solid #2a2533;overflow-y:auto}
#log{font-size:11.5px;line-height:1.5;color:#b0a890}
#log div{margin-bottom:4px;padding-bottom:4px;border-bottom:1px dotted #2a2533}
#log .good{color:#8fce8f}#log .bad{color:#e07070}#log .sys{color:#c9a227}

#center{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;position:relative}
#floorBanner{position:absolute;top:8px;font-size:13px;color:#6a6152;letter-spacing:3px}
#enemies{display:flex;gap:34px;align-items:flex-end;flex-wrap:wrap;justify-content:center}
.enemy{width:150px;text-align:center;cursor:pointer;transition:transform .15s;position:relative}
.enemy .sprite{width:110px;height:110px;margin:0 auto;filter:drop-shadow(0 6px 10px #000);transition:transform .15s}
.enemy.targetable .sprite{animation:pulse 1s infinite;cursor:crosshair}
.enemy.targetable{transform:translateY(-4px)}
@keyframes pulse{0%,100%{filter:drop-shadow(0 0 4px #c0392b)}50%{filter:drop-shadow(0 0 16px #ff5030)}}
.enemy.dead{opacity:.15;pointer-events:none;filter:grayscale(1)}
.enemy .ename{font-size:13px;letter-spacing:1px;margin-top:4px}
.enemy .ehp{font-size:11px;color:#c98}
.enemy .statusRow{font-size:10px;min-height:14px}
.stag{display:inline-block;padding:1px 6px;border-radius:8px;margin:0 2px;font-size:10px}
.st-burn{background:#3d2a05;color:#e8b33a}.st-poison{background:#1e3320;color:#7fce7f}
.st-stun{background:#33305a;color:#aab0ff}.st-silence{background:#3a2033;color:#e0a0d0}
.st-block{background:#1e2c3d;color:#7fb0d8}
.enemy .eintent{font-size:11px;color:#d88;margin-top:2px}

#right{width:170px;padding:12px;border-left:1px solid #2a2533;display:flex;flex-direction:column;align-items:center;gap:14px}
#energyOrb{width:84px;height:84px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:24px;font-weight:bold;color:#fff;text-shadow:0 0 8px #000;
  background:radial-gradient(circle at 35% 30%,#5a7fd8,#1a2a6a 70%);border:3px solid #7fa0e0;
  box-shadow:0 0 22px rgba(90,127,216,.6),inset 0 0 14px #000}
#blockBadge{font-size:13px;color:#7fb0d8}
#right .small{font-size:11px;color:#8a7f6a;text-align:center}

#bottom{padding:10px 16px;background:linear-gradient(0deg,#181320,#0e0b14);border-top:2px solid #3a3348;
  display:flex;align-items:center;gap:14px;min-height:190px}
#hand{display:flex;gap:10px;flex:1;overflow-x:auto;padding:6px 4px;min-height:180px;align-items:center}
#hand:empty::after{content:"— hand empty —";color:#4a4438;font-style:italic;margin:auto}
#controls{display:flex;flex-direction:column;gap:10px;width:170px}
#endTurn{font-size:16px;padding:12px}

/* ===== CARD ===== */
.card{width:132px;height:186px;flex:0 0 auto;position:relative;cursor:pointer;transition:transform .15s,box-shadow .15s;
  filter:drop-shadow(0 4px 6px rgba(0,0,0,.7))}
.card:hover{transform:translateY(-14px) scale(1.05);z-index:5}
.card.playable{box-shadow:0 0 0 2px rgba(201,162,39,.0)}
.card.playable::after{content:"";position:absolute;inset:-2px;border-radius:9px;box-shadow:0 0 10px rgba(201,162,39,.55);pointer-events:none}
.card.unplayable{filter:grayscale(.7) brightness(.65)}
.card.selected{transform:translateY(-18px) scale(1.07)}
.card svg.frameSvg{position:absolute;inset:0;width:100%;height:100%}
.card .art{position:absolute;left:14px;top:14px;width:104px;height:74px;display:flex;align-items:center;justify-content:center;
  font-size:9px;color:#5a5344;letter-spacing:2px;font-style:italic}
.card .cname{position:absolute;top:90px;left:0;right:0;text-align:center;font-size:11.5px;font-weight:bold;letter-spacing:.5px;color:#e8dcc0}
.card .ctext{position:absolute;top:106px;left:12px;right:12px;bottom:24px;font-size:9px;line-height:1.35;color:#b0a68e;text-align:center;overflow:hidden}
.card .ccost{position:absolute;top:4px;left:6px;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:14px;font-weight:bold;color:#fff;background:radial-gradient(circle at 35% 30%,#5a7fd8,#1a2a6a);border:2px solid #8fb0f0;box-shadow:0 0 6px #000}
.card .cstat{position:absolute;bottom:4px;font-size:13px;font-weight:bold;width:28px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:6px}
.card .catk{left:6px;background:#3a1512;color:#ff8a70;border:1px solid #7a3020}
.card .cdef{right:6px;background:#12233a;color:#8fc0ff;border:1px solid #2a5a8a}
.card.t-attack .cname{color:#ffb090}.card.t-defense .cname{color:#a8d0ff}.card.t-other .cname{color:#d8b0ff}

/* ===== SHOP / OVERLAYS ===== */
.overlay{position:fixed;inset:0;background:rgba(5,4,8,.9);display:none;align-items:center;justify-content:center;z-index:50;flex-direction:column;gap:18px}
.overlay.show{display:flex}
.overlay h2{color:var(--gold);font-size:32px;text-shadow:0 0 16px rgba(201,162,39,.4)}
#shopCards{display:flex;gap:22px;flex-wrap:wrap;justify-content:center}
.shopSlot{display:flex;flex-direction:column;align-items:center;gap:8px}
.shopSlot .price{font-size:14px;color:var(--gold)}
.shopSlot .price.cant{color:#c05050}
.bigmsg{font-size:22px;color:#c9bfa8;max-width:560px;text-align:center;line-height:1.6}
#toast{position:fixed;bottom:200px;left:50%;transform:translateX(-50%);background:#1c1720;border:1px solid var(--gold);
  padding:8px 22px;border-radius:20px;font-size:13px;color:var(--gold);opacity:0;transition:opacity .3s;pointer-events:none;z-index:99}
#toast.show{opacity:1}
::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background:#3a3348;border-radius:4px}
</style>
</head>
<body>

<!-- ===== SVG DEFS: card frames + backs (one per deck/class) ===== -->
<svg width="0" height="0" style="position:absolute">
<defs>
<!-- BASTION frame: stone/gothic arch, iron-gold -->
<symbol id="frame-bastion" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#1c1712" stroke="#8a6d2f" stroke-width="2"/>
  <rect x="6" y="6" width="120" height="174" rx="6" fill="none" stroke="#5a4a22" stroke-width="1"/>
  <path d="M10 10 h112 v78 h-112 z" fill="#241d15" stroke="#6a5528" stroke-width="1.5"/>
  <path d="M10 88 h112" stroke="#8a6d2f" stroke-width="1.5"/>
  <path d="M14 96 h104 M14 168 h104" stroke="#4a3c1c" stroke-width="1"/>
  <circle cx="66" cy="176" r="5" fill="#8a6d2f"/>
  <path d="M66 12 l6 8 h-12 z" fill="#8a6d2f"/>
  <path d="M4 40 l6 -8 v14 z M128 40 l-6 -8 v14 z" fill="#8a6d2f" opacity=".7"/>
</symbol>
<!-- WITHER frame: rot/organic thorns, sickly green-gold -->
<symbol id="frame-wither" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#141710" stroke="#5a7a2f" stroke-width="2"/>
  <rect x="6" y="6" width="120" height="174" rx="6" fill="none" stroke="#3a4a1c" stroke-width="1"/>
  <path d="M12 12 q54 -6 108 0 v74 q-54 6 -108 0 z" fill="#181c10" stroke="#4a6a22" stroke-width="1.5"/>
  <path d="M12 92 q54 8 108 0" stroke="#5a7a2f" stroke-width="1.5" fill="none"/>
  <path d="M8 100 q10 14 2 30 M124 100 q-10 14 -2 30" stroke="#5a7a2f" stroke-width="1.5" fill="none"/>
  <circle cx="66" cy="176" r="5" fill="#5a7a2f"/>
  <path d="M30 176 q18 -10 36 0 q18 -10 36 0" stroke="#3a4a1c" fill="none" stroke-width="1.5"/>
  <circle cx="20" cy="20" r="2" fill="#5a7a2f"/><circle cx="112" cy="20" r="2" fill="#5a7a2f"/>
</symbol>
<!-- SHROUD frame: sharp angular shadow blades, violet -->
<symbol id="frame-shroud" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#131019" stroke="#6a4a9a" stroke-width="2"/>
  <path d="M8 8 L66 2 L124 8 L124 92 L66 98 L8 92 Z" fill="#1a1424" stroke="#4a3a6a" stroke-width="1.5"/>
  <path d="M8 98 L124 98 L124 172 L66 182 L8 172 Z" fill="#151020" stroke="#4a3a6a" stroke-width="1.5"/>
  <path d="M2 30 L8 34 M130 30 l-6 4 M2 150 l6 -4 M130 150 l-6 -4" stroke="#6a4a9a" stroke-width="1.5"/>
  <path d="M66 104 l8 8 -8 8 -8 -8 z" fill="#6a4a9a"/>
  <path d="M14 178 l6 -6 M118 178 l-6 -6" stroke="#6a4a9a" stroke-width="1.5"/>
</symbol>

<!-- CARD BACKS -->
<symbol id="back-bastion" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#181310" stroke="#8a6d2f" stroke-width="2"/>
  <rect x="10" y="10" width="112" height="166" rx="5" fill="none" stroke="#5a4a22"/>
  <path d="M66 30 L96 60 L66 90 L36 60 Z" fill="none" stroke="#8a6d2f" stroke-width="2"/>
  <path d="M66 44 L82 60 L66 76 L50 60 Z" fill="#8a6d2f" opacity=".6"/>
  <path d="M66 96 v56 M40 124 h52" stroke="#5a4a22" stroke-width="2"/>
  <circle cx="66" cy="124" r="10" fill="none" stroke="#8a6d2f"/>
  <text x="66" y="170" text-anchor="middle" fill="#8a6d2f" font-size="9" letter-spacing="2">AETHELGARD</text>
</symbol>
<symbol id="back-wither" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#101408" stroke="#5a7a2f" stroke-width="2"/>
  <rect x="10" y="10" width="112" height="166" rx="5" fill="none" stroke="#3a4a1c"/>
  <path d="M66 24 q22 26 0 52 q-22 26 0 52 q14 16 0 34" stroke="#5a7a2f" stroke-width="2" fill="none"/>
  <path d="M66 50 q-16 8 -20 24 M66 50 q16 8 20 24 M66 102 q-16 8 -20 24 M66 102 q16 8 20 24" stroke="#4a6a22" fill="none"/>
  <circle cx="66" cy="76" r="6" fill="#5a7a2f" opacity=".7"/>
  <text x="66" y="170" text-anchor="middle" fill="#5a7a2f" font-size="9" letter-spacing="2">AETHELGARD</text>
</symbol>
<symbol id="back-shroud" viewBox="0 0 132 186">
  <rect x="1" y="1" width="130" height="184" rx="9" fill="#0e0a16" stroke="#6a4a9a" stroke-width="2"/>
  <rect x="10" y="10" width="112" height="166" rx="5" fill="none" stroke="#3a2a5a"/>
  <path d="M66 26 L98 93 L66 160 L34 93 Z" fill="none" stroke="#6a4a9a" stroke-width="2"/>
  <path d="M66 50 L84 93 L66 136 L48 93 Z" fill="#6a4a9a" opacity=".45"/>
  <circle cx="66" cy="93" r="5" fill="#a88ad0"/>
  <path d="M20 20 l10 10 M112 20 l-10 10 M20 166 l10 -10 M112 166 l-10 -10" stroke="#6a4a9a"/>
  <text x="66" y="176" text-anchor="middle" fill="#6a4a9a" font-size="9" letter-spacing="2">AETHELGARD</text>
</symbol>
</defs>
</svg>

<!-- ===== MENU ===== -->
<div id="menu">
  <h1>SHADOWS OF AETHELGARD</h1>
  <div class="sub">THE VOID CONSUMES ALL — CHOOSE YOUR HERO</div>
  <div class="classes" id="classPick"></div>
</div>

<!-- ===== GAME ===== -->
<div id="game">
  <div id="topbar">
    <div class="stat">
      <span class="pill">🪙 <b id="uiGold">0</b> Gold</span>
      <span class="pill">⚔ Floor <b id="uiFloor">1</b> · Room <b id="uiRoom">1</b>/4</span>
    </div>
    <div class="stat" id="hpwrap">
      <div style="flex:1">
        <div class="barOuter"><div class="barFill" id="uiHpBar"></div><div id="hpText"></div></div>
      </div>
      <span class="pill" id="uiClassName"></span>
    </div>
  </div>
  <div id="main">
    <div id="left"><h3 style="font-size:13px;color:#8a7f6a;margin-bottom:8px">CHRONICLE</h3><div id="log"></div></div>
    <div id="center">
      <div id="floorBanner"></div>
      <div id="enemies"></div>
      <div id="targetHint" style="font-size:13px;color:var(--gold);min-height:18px"></div>
    </div>
    <div id="right">
      <div id="energyOrb">5/5</div>
      <div class="small">ENERGY</div>
      <div id="blockBadge">🛡 Block: 0</div>
      <button class="etched" id="shopBtn">🛒 Shop</button>
      <div class="small">Shop opens between rooms.<br>1 card/slot · dupes cost more</div>
    </div>
  </div>
  <div id="bottom">
    <div id="hand"></div>
    <div id="controls">
      <button class="etched" id="endTurn">END TURN ▶</button>
      <button class="etched" id="cancelBtn" style="display:none">✖ Cancel Target</button>
      <div class="small" id="turnHint">Play cards, then end your turn.</div>
    </div>
  </div>
</div>

<!-- ===== SHOP ===== -->
<div class="overlay" id="shopOv">
  <h2>THE WANDERING MERCHANT</h2>
  <div class="bigmsg" id="shopGold"></div>
  <div id="shopCards"></div>
  <button class="etched" id="shopContinue">DESCEND ▶</button>
</div>

<!-- ===== END SCREENS ===== -->
<div class="overlay" id="endOv">
  <h2 id="endTitle"></h2>
  <div class="bigmsg" id="endMsg"></div>
  <button class="etched" id="endBtn">RETURN TO MENU</button>
</div>

<div id="toast"></div>

<script>
/* ================= DATA ================= */
const CLASSES = {
  bastion:{name:'The Bastion', hp:35, energy:3, color:'#c9a227', desc:'Immovable object. High defense, block and area damage. Slow but unbreakable.',
    frame:'frame-bastion', back:'back-bastion'},
  wither:{name:'The Wither', hp:25, energy:5, color:'#7fb04a', desc:'Rot and burn. Affliction, damage over time and debuffs. Fragile but cruel.',
    frame:'frame-wither', back:'back-wither'},
  shroud:{name:'The Shroud', hp:28, energy:5, color:'#a88ad0', desc:'Shadow and speed. Assassination, evasion and energy tricks. Strike from darkness.',
    frame:'frame-shroud', back:'back-shroud'}
};

// type: A attack, D defense, U other. tgt: 'one'|'all'|'none'
const CARDS = [
 {id:'BA-A01',cls:'bastion',type:'A',cost:2,atk:3,def:0,name:'Slam',tgt:'one',text:'Deal 3 damage. If you have exactly 30 HP, draw 1 card.'},
 {id:'BA-A02',cls:'bastion',type:'A',cost:3,atk:4,def:0,name:'Earthquake',tgt:'all',text:'Deal 4 damage to all enemies.'},
 {id:'BA-A03',cls:'bastion',type:'A',cost:1,atk:2,def:0,name:'Stone Fist',tgt:'one',text:'Deal 2 damage.'},
 {id:'BA-A04',cls:'bastion',type:'A',cost:4,atk:6,def:0,name:'Boulder',tgt:'one',text:'Deal 6 damage.'},
 {id:'BA-D01',cls:'bastion',type:'D',cost:0,atk:0,def:4,name:'Shield Wall',tgt:null,text:'Gain 4 Defense for this turn.'},
 {id:'BA-D02',cls:'bastion',type:'D',cost:2,atk:0,def:5,name:'Iron Skin',tgt:null,text:'Gain 5 Defense permanently. Discard 1 random card.'},
 {id:'BA-D03',cls:'bastion',type:'D',cost:1,atk:0,def:2,name:'Stand Firm',tgt:null,text:'Gain 2 Defense.'},
 {id:'BA-D04',cls:'bastion',type:'D',cost:2,atk:0,def:3,name:'Taunt',tgt:'one',text:'Gain 3 Defense. Stun 1 enemy for 1 turn.'},
 {id:'BA-U01',cls:'bastion',type:'U',cost:2,atk:0,def:0,name:'Search',tgt:null,text:'Draw 1 card. Discard down to 5 if over.'},
 {id:'BA-U02',cls:'bastion',type:'U',cost:3,atk:0,def:0,name:'Repair',tgt:null,text:'Heal 6 HP.'},
 {id:'BA-U03',cls:'bastion',type:'U',cost:1,atk:0,def:0,name:'Rally',tgt:null,text:'Draw 1 card. Gain 1 Energy.'},
 {id:'BA-U04',cls:'bastion',type:'U',cost:2,atk:0,def:0,name:'Loot',tgt:null,text:'Gain 5 Gold.'},

 {id:'WI-A01',cls:'wither',type:'A',cost:2,atk:3,def:0,name:'Fireball',tgt:'one',text:'Deal 3 damage. Apply 1 Burn.'},
 {id:'WI-A02',cls:'wither',type:'A',cost:3,atk:4,def:0,name:'Inferno',tgt:'one',text:'Deal 4 damage. Apply 3 Burn.'},
 {id:'WI-A03',cls:'wither',type:'A',cost:1,atk:2,def:0,name:'Ember',tgt:'one',text:'Deal 2 damage. Apply 1 Burn.'},
 {id:'WI-A04',cls:'wither',type:'A',cost:4,atk:5,def:0,name:'Pyre',tgt:'one',text:'Deal 5 damage. All enemies take 1 damage next turn.'},
 {id:'WI-D01',cls:'wither',type:'D',cost:2,atk:0,def:3,name:'Scorch Shield',tgt:null,text:'Gain 3 Defense.'},
 {id:'WI-D02',cls:'wither',type:'D',cost:1,atk:0,def:1,name:'Heat Resist',tgt:null,text:'Gain 1 Defense. Gain 1 Energy.'},
 {id:'WI-D03',cls:'wither',type:'D',cost:2,atk:0,def:4,name:'Smoke',tgt:null,text:'Gain 4 Defense.'},
 {id:'WI-D04',cls:'wither',type:'D',cost:3,atk:0,def:2,name:'Endure',tgt:null,text:'Gain 2 Defense. Heal 2 HP.'},
 {id:'WI-U01',cls:'wither',type:'U',cost:2,atk:0,def:0,name:'Poison',tgt:'one',text:'Apply 1 Poison to target.'},
 {id:'WI-U02',cls:'wither',type:'U',cost:3,atk:3,def:0,name:'Wildfire',tgt:'all',text:'Deal 3 damage and apply 2 Burn to all enemies.'},
 {id:'WI-U03',cls:'wither',type:'U',cost:1,atk:0,def:0,name:'Slow',tgt:'one',text:'Draw 1 card. Stun target for 1 turn.'},
 {id:'WI-U04',cls:'wither',type:'U',cost:2,atk:0,def:0,name:'Wither',tgt:'one',text:'Heal 5 HP. Reduce target attack by 1 (min 1).'},

 {id:'SH-A01',cls:'shroud',type:'A',cost:2,atk:4,def:0,name:'Backstab',tgt:'one',text:'Deal 4 damage. +1 damage if target has no Block.'},
 {id:'SH-A02',cls:'shroud',type:'A',cost:3,atk:5,def:0,name:'Shadow Bolt',tgt:'one',text:'Deal 5 damage. Ignores Block.'},
 {id:'SH-A03',cls:'shroud',type:'A',cost:1,atk:3,def:0,name:'Silence',tgt:'one',text:'Deal 3 damage. Silence target (boss specials off) for 1 turn.'},
 {id:'SH-A04',cls:'shroud',type:'A',cost:3,atk:6,def:0,name:'Vengeance',tgt:'one',text:'Deal 6 damage. Gain 2 Energy.'},
 {id:'SH-D01',cls:'shroud',type:'D',cost:3,atk:0,def:2,name:'Blur',tgt:null,text:'Gain 2 Defense. Ignore the next enemy attack.'},
 {id:'SH-D02',cls:'shroud',type:'D',cost:2,atk:0,def:3,name:'Shadow Step',tgt:null,text:'Gain 3 Defense. Gain 1 Energy.'},
 {id:'SH-D03',cls:'shroud',type:'D',cost:1,atk:0,def:1,name:'Evasion',tgt:null,text:'Gain 1 Defense. Skip all enemy actions this turn.'},
 {id:'SH-D04',cls:'shroud',type:'D',cost:2,atk:0,def:4,name:'Phantom',tgt:null,text:'Gain 4 Defense (ghost form for 1 turn).'},
 {id:'SH-U01',cls:'shroud',type:'U',cost:1,atk:0,def:0,name:'Dash',tgt:null,text:'Gain 1 Energy. Skip all enemy attacks this turn.'},
 {id:'SH-U02',cls:'shroud',type:'U',cost:2,atk:2,def:0,name:'Teleport Strike',tgt:'one',text:'Deal 2 damage from the back row.'},
 {id:'SH-U03',cls:'shroud',type:'U',cost:2,atk:0,def:1,name:'Hide',tgt:null,text:'Gain 1 Defense. Discard 1 random card, draw 2.'},
 {id:'SH-U04',cls:'shroud',type:'U',cost:3,atk:8,def:0,name:'Assassinate',tgt:'one',text:'Deal 8 damage. Cannot be blocked.'}
];
const cardById = id => CARDS.find(c=>c.id===id);

const FLOORS = {
  1:{names:['Goblin Cutter','Plague Rat','Goblin Archer'], hp:20, dmg:3, boss:{name:'The Gatekeeper',hp:80,dmg:15}},
  2:{names:['Vampire Knight','Ogre Brute','Vampire Knight'], hp:30, dmg:5, boss:{name:'The Corrupted',hp:80,dmg:15}},
  3:{names:['Void Warrior','Void Cultist','Void Warrior'], hp:40, dmg:8, boss:{name:'The Void Lord',hp:80,dmg:15}}
};
const ENEMY_ART = {
  'Goblin Cutter':'👺','Plague Rat':'🐀','Goblin Archer':'🏹',
  'Vampire Knight':'🧛','Ogre Brute':'👹',
  'Void Warrior':'🛡','Void Cultist':'🕯',
  'The Gatekeeper':'🗿','The Corrupted':'💀','The Void Lord':'👁'
};

/* ================= STATE ================= */
let G = null;
function newGame(clsKey){
  const c = CLASSES[clsKey];
  G = {
    cls:clsKey, hp:c.hp, maxhp:c.hp, energy:c.energy, maxEnergy:5,
    handSize:4, gold:0, block:0, permBlock:0, evade:0, skipEnemy:false,
    deck:[], hand:[], inventory:{}, floor:1, room:1,
    enemies:[], turn:1, over:false, pendingCard:null
  };
  CARDS.filter(x=>x.cls===clsKey).forEach(x=>{
    G.deck.push(x.id); G.inventory[x.id]=1;
  });
  log(`You descend into Aethelgard as ${c.name}.`,'sys');
  startRoom();
}

/* ================= ROOMS / ENEMIES ================= */
function isBossRoom(){ return G.room===4; }
function startRoom(){
  G.turn=1; G.hand=[]; G.block=G.permBlock; G.evade=0; G.skipEnemy=false;
  G.energy = (G.floor===1&&G.room===1) ? CLASSES[G.cls].energy : Math.min(G.maxEnergy,G.energy+1);
  const f = FLOORS[G.floor];
  if(isBossRoom()){
    G.enemies=[mkEnemy(f.boss.name,f.boss.hp,f.boss.dmg,true)];
    log(`⚠ ${f.boss.name} blocks your path!`,'bad');
  } else {
    const n = 1+Math.floor(Math.random()*2)+ (G.room>1?1:0); // 1-3 enemies
    const pool=[...f.names]; G.enemies=[];
    for(let i=0;i<Math.min(n,3);i++){
      const nm=pool.splice(Math.floor(Math.random()*pool.length),1)[0]||f.names[0];
      G.enemies.push(mkEnemy(nm,f.hp,f.dmg,false));
    }
    log(`Room ${G.room}: ${G.enemies.length} enemy approach!`,'sys');
  }
  drawTo(G.handSize);
  render();
}
function mkEnemy(name,hp,dmg,boss){
  return {name,hp,maxhp:hp,dmg,block:0,boss,burn:0,poison:0,stun:0,silence:0,dotNext:0,dmgMod:0};
}

/* ================= CARDS UI ================= */
function cardHTML(c, inShop, price){
  const cl=CLASSES[c.cls];
  const typeGlow = c.type==='A' ? '#c0392b' : c.type==='D' ? '#3b6ea5' : '#7d4ba0';
  const statHtml = (c.type==='A'?`<div class="cstat catk">⚔${c.atk}</div>`:c.type==='D'?`<div class="cstat cdef">🛡${c.def}</div>`:'');
  return `<div class="card t-${c.type==='A'?'attack':c.type==='D'?'defense':'other'}" data-card="${c.id}" ${inShop?`data-shop="1"`:''}>
    <svg class="frameSvg" viewBox="0 0 132 186"><use href="#${cl.frame}"/></svg>
    <div class="art">✦ ART ✦</div>
    <div class="cname">${c.name}</div>
    <div class="ctext">${c.text}</div>
    <div class="ccost">${c.cost}</div>
    ${statHtml}
  </div>`;
}

function render(){
  if(!G) return;
  document.getElementById('uiGold').textContent=G.gold;
  document.getElementById('uiFloor').textContent=G.floor;
  document.getElementById('uiRoom').textContent=G.room;
  document.getElementById('uiClassName').textContent=CLASSES[G.cls].name;
  document.getElementById('uiHpBar').style.width=(100*G.hp/G.maxhp)+'%';
  document.getElementById('hpText').textContent=`❤ ${G.hp} / ${G.maxhp}`;
  document.getElementById('energyOrb').textContent=`${G.energy}/${G.maxEnergy}`;
  document.getElementById('blockBadge').textContent=`🛡 Block: ${G.block}`+(G.permBlock?` (+${G.permBlock} perm)`:'');
  document.getElementById('floorBanner').textContent =
    `FLOOR ${G.floor} — ${isBossRoom()?FLOORS[G.floor].boss.name.toUpperCase():'ROOM '+G.room}`;
  // enemies
  const ec=document.getElementById('enemies'); ec.innerHTML='';
  G.enemies.forEach((e,i)=>{
    const d=document.createElement('div');
    d.className='enemy'+(e.hp<=0?' dead':'')+((G.pendingCard&&e.hp>0)?' targetable':'');
    d.dataset.i=i;
    let st='';
    if(e.burn) st+=`<span class="stag st-burn">🔥${e.burn}</span>`;
    if(e.poison) st+=`<span class="stag st-poison">☠${e.poison}</span>`;
    if(e.stun) st+=`<span class="stag st-stun">💫</span>`;
    if(e.silence) st+=`<span class="stag st-silence">🤐</span>`;
    if(e.block) st+=`<span class="stag st-block">🛡${e.block}</span>`;
    let intent = e.hp>0 ? `⚔ ${e.dmg+e.dmgMod}` : '';
    if(e.boss && !e.silence){
      if(e.name==='The Gatekeeper'&&e.hp>=20) intent+=` 🛡5`;
      if(e.name==='The Void Lord') intent+=` ✚10`;
      if(e.name==='The Corrupted'&&G.energy<3) intent+=` ‼20`;
    }
    d.innerHTML=`<svg class="sprite" viewBox="0 0 100 100"><text x="50" y="66" text-anchor="middle" font-size="60" filter="drop-shadow(0 0 6px #a00)">${ENEMY_ART[e.name]||'👾'}</text></svg>
      <div class="ename">${e.name}</div>
      <div class="barOuter" style="height:10px"><div class="barFill" style="width:${100*Math.max(0,e.hp)/e.maxhp}%"></div></div>
      <div class="ehp">${Math.max(0,e.hp)}/${e.maxhp} HP</div>
      <div class="statusRow">${st}</div><div class="eintent">${intent}</div>`;
    d.onclick=()=>{ if(G.pendingCard && e.hp>0) chooseTarget(i); };
    ec.appendChild(d);
  });
  // hand
  const hc=document.getElementById('hand'); hc.innerHTML='';
  G.hand.forEach((cid,i)=>{
    const c=cardById(cid);
    const el=document.createElement('div');
    el.innerHTML=cardHTML(c,false);
    const cardEl=el.firstElementChild;
    if(c.cost<=G.energy && !G.pendingCard) cardEl.classList.add('playable');
    else if(c.cost>G.energy) cardEl.classList.add('unplayable');
    if(G.pendingCard===i) cardEl.classList.add('selected');
    cardEl.onclick=()=>clickCard(i);
    hc.appendChild(cardEl);
  });
  document.getElementById('endTurn').disabled = !!G.pendingCard || G.over;
  document.getElementById('cancelBtn').style.display = G.pendingCard!==null?'':'none';
  document.getElementById('targetHint').textContent =
    G.pendingCard!==null ? `Choose a target for ${cardById(G.hand[G.pendingCard]).name}` : '';
}

/* ================= PLAY CARDS ================= */
function toast(msg){
  const t=document.getElementById('toast'); t.textContent=msg; t.style.opacity=1;
  clearTimeout(t._h); t._h=setTimeout(()=>t.style.opacity=0,1400);
}
function log(msg,cls){
  const l=document.getElementById('log');
  const d=document.createElement('div'); d.textContent=msg; if(cls)d.className=cls;
  l.prepend(d);
}
function clickCard(i){
  if(G.over||G.pendingCard!==null) return;
  const c=cardById(G.hand[i]);
  if(c.cost>G.energy){ toast('Not enough Energy!'); return; }
  if(c.tgt==='one'){
    const alive=G.enemies.filter(e=>e.hp>0);
    if(!alive.length){ toast('No targets.'); return; }
    if(alive.length===1){ playCard(i, G.enemies.indexOf(alive[0])); }
    else { G.pendingCard=i; render(); }
  } else playCard(i,null);
}
function chooseTarget(i){ playCard(G.pendingCard,i); }
function playCard(handIdx, targetIdx){
  const c=cardById(G.hand[handIdx]);
  if(c.cost>G.energy) return;
  G.energy-=c.cost;
  G.hand.splice(handIdx,1);
  G.pendingCard=null;
  const tgt = targetIdx!=null ? G.enemies[targetIdx] : null;
  log(`You play ${c.name}.`,'sys');
  applyCard(c,tgt);
  drawCheck();
  checkDeaths();
  render();
}
function dealDamage(e,amt,pierce){
  if(!e||e.hp<=0) return;
  let d=amt;
  if(!pierce && e.block>0){ const b=Math.min(e.block,d); e.block-=b; d-=b; }
  e.hp-=d;
  log(`${e.name} takes ${amt} damage${d<amt?` (${amt-d} blocked)`:''}.`,'good');
}
function applyCard(c,tgt){
  const all=()=>G.enemies.filter(e=>e.hp>0);
  switch(c.id){
   // ===== BASTION =====
   case 'BA-A01': dealDamage(tgt,3); if(G.hp===30){drawTo(G.handSize);log('Slam! You draw a card.','good');} break;
   case 'BA-A02': all().forEach(e=>dealDamage(e,4)); break;
   case 'BA-A03': dealDamage(tgt,2); break;
   case 'BA-A04': dealDamage(tgt,6); break;
   case 'BA-D01': G.block+=4; break;
   case 'BA-D02': G.permBlock+=5; G.block+=5; discardRandom(); break;
   case 'BA-D03': G.block+=2; break;
   case 'BA-D04': G.block+=3; if(tgt){tgt.stun+=1;log(`${tgt.name} is stunned!`,'good');} break;
   case 'BA-U01': drawTo(G.handSize+1); while(G.hand.length>5)G.hand.pop(); break;
   case 'BA-U02': heal(6); break;
   case 'BA-U03': drawTo(G.handSize+1); G.energy=Math.min(G.maxEnergy,G.energy+1); break;
   case 'BA-U04': G.gold+=5; toast('+5 Gold'); break;
   // ===== WITHER =====
   case 'WI-A01': dealDamage(tgt,3); tgt.burn+=1; break;
   case 'WI-A02': dealDamage(tgt,4); tgt.burn+=3; break;
   case 'WI-A03': dealDamage(tgt,2); tgt.burn+=1; break;
   case 'WI-A04': dealDamage(tgt,5); all().forEach(e=>e.dotNext+=1); break;
   case 'WI-D01': G.block+=3; break;
   case 'WI-D02': G.block+=1; G.energy=Math.min(G.maxEnergy,G.energy+1); break;
   case 'WI-D03': G.block+=4; break;
   case 'WI-D04': G.block+=2; heal(2); break;
   case 'WI-U01': tgt.poison+=1; log(`${tgt.name} is poisoned.`,'good'); break;
   case 'WI-U02': all().forEach(e=>{dealDamage(e,3);e.burn+=2;}); break;
   case 'WI-U03': drawTo(G.handSize+1); tgt.stun+=1; log(`${tgt.name} is stunned!`,'good'); break;
   case 'WI-U04': heal(5); tgt.dmgMod=Math.max(1-tgt.dmg,tgt.dmgMod-1); log(`${tgt.name}'s attack reduced.`,'good'); break;
   // ===== SHROUD =====
   case 'SH-A01': dealDamage(tgt,4+(tgt.block===0?1:0)); break;
   case 'SH-A02': dealDamage(tgt,5,true); break;
   case 'SH-A03': dealDamage(tgt,3); tgt.silence+=1; log(`${tgt.name} is silenced.`,'good'); break;
   case 'SH-A04': dealDamage(tgt,6); G.energy=Math.min(G.maxEnergy,G.energy+2); break;
   case 'SH-D01': G.block+=2; G.evade+=1; break;
   case 'SH-D02': G.block+=3; G.energy=Math.min(G.maxEnergy,G.energy+1); break;
   case 'SH-D03': G.block+=1; G.skipEnemy=true; log('You melt into shadow — enemies skip their turn!','good'); break;
   case 'SH-D04': G.block+=4; break;
   case 'SH-U01': G.energy=Math.min(G.maxEnergy,G.energy+1); G.skipEnemy=true; log('You dash — enemies skip their turn!','good'); break;
   case 'SH-U02': dealDamage(tgt,2); break;
   case 'SH-U03': G.block+=1; discardRandom(); drawTo(G.handSize+2); break;
   case 'SH-U04': dealDamage(tgt,8,true); break;
  }
}
function heal(n){ G.hp=Math.min(G.maxhp,G.hp+n); log(`You heal ${n} HP.`,'good'); }
function discardRandom(){ if(G.hand.length){ const i=Math.floor(Math.random()*G.hand.length); log(`${cardById(G.hand[i]).name} is discarded.`); G.hand.splice(i,1);} }
function drawTo(n){ while(G.hand.length<n && G.deck.length){ G.hand.push(G.deck[Math.floor(Math.random()*G.deck.length)]); } }
function drawCheck(){}

/* ================= DEATHS / PROGRESSION ================= */
function checkDeaths(){
  G.enemies.forEach(e=>{
    if(e.hp<=0 && !e.counted){
      e.counted=true;
      const g=e.boss?15:5; G.gold+=g;
      log(`${e.name} slain! +${g} Gold.`,'sys');
    }
  });
  if(G.enemies.every(e=>e.hp<=0)) setTimeout(roomCleared,600);
}
function roomCleared(){
  if(G.over) return;
  if(isBossRoom()){
    if(G.floor===3){ endGame(true); return; }
    log(`Floor ${G.floor} cleared! The descent continues...`,'sys');
    G.floor++; G.room=1;
    openShop();
  } else {
    G.room++;
    openShop();
  }
}
function endGame(win){
  G.over=true;
  document.getElementById('endTitle').textContent = win?'VICTORY':'GAME OVER';
  document.getElementById('endMsg').textContent = win
    ? 'The Void Lord falls. Light returns to Aethelgard. The shadows recede... for now.'
    : `You fall on Floor ${G.floor}. The Void claims another hero.`;
  document.getElementById('endOv').classList.add('show');
}

/* ================= ENEMY TURN ================= */
function endTurn(){
  if(G.over||G.pendingCard!==null) return;
  // status ticks on enemies
  G.enemies.forEach(e=>{
    if(e.hp<=0) return;
    if(e.burn){ e.hp-=e.burn; log(`${e.name} burns for ${e.burn}.`,'good'); }
    if(e.poison){ e.hp-=e.poison; log(`${e.name} suffers ${e.poison} poison.`,'good'); }
    if(e.dotNext){ e.hp-=e.dotNext; log(`${e.name} sears for ${e.dotNext}.`,'good'); e.dotNext=0; }
  });
  checkDeaths(); render();
  if(G.enemies.every(e=>e.hp<=0)) return;
  setTimeout(enemyTurn,500);
}
function enemyTurn(){
  if(G.over) return;
  if(!G.skipEnemy){
    G.enemies.forEach(e=>{
      if(e.hp<=0) return;
      // boss specials
      if(e.boss && !e.silence){
        if(e.name==='The Gatekeeper'&&e.hp>=20) e.block=5;
        if(e.name==='The Void Lord'){ e.hp=Math.min(e.maxhp,e.hp+10); log('The Void Lord regenerates 10 HP.','bad'); }
      }
      if(e.stun>0){ e.stun--; log(`${e.name} is stunned and cannot act.`,'good'); return; }
      if(e.silence>0) e.silence--;
      let dmg=e.dmg+e.dmgMod;
      if(e.boss && e.name==='The Corrupted' && G.energy<3){ dmg=20; log('The Corrupted unleashes a devastating burst!','bad'); }
      // player defense
      if(G.evade>0){ G.evade--; log('You evade the attack entirely!','good'); return; }
      const blocked=Math.min(G.block,dmg); G.block-=blocked; const taken=dmg-blocked;
      G.hp-=taken;
      log(`${e.name} hits you for ${dmg}${blocked?` (${blocked} blocked)`:''}.`, taken?'bad':'good');
    });
  } else { G.skipEnemy=false; }
  // end of round
  G.block=G.permBlock;
  if(G.hp<=0){ endGame(false); render(); return; }
  G.turn++;
  G.energy=Math.min(G.maxEnergy,G.energy+1);
  drawTo(G.handSize);
  render();
}

/* ================= SHOP ================= */
let shopStock=[];
function shopPrice(c){
  const owned=G.inventory[c.id]||0;
  return Math.min(50, c.cost*2 + owned*2 + (owned>=3?10-owned*2+4:0));
}
function openShop(){
  const pool=CARDS.filter(c=>(G.inventory[c.id]||0)<5);
  shopStock=[];
  const shuffled=[...pool].sort(()=>Math.random()-.5);
  for(let i=0;i<4 && i<shuffled.length;i++) shopStock.push(shuffled[i]);
  const sc=document.getElementById('shopCards'); sc.innerHTML='';
  shopStock.forEach(c=>{
    const price=shopPrice(c);
    const slot=document.createElement('div'); slot.className='shopSlot';
    slot.innerHTML=cardHTML(c,true)+`<div class="price ${G.gold<price?'cant':''}">🪙 ${price} Gold</div>`;
    const cardEl=slot.querySelector('.card');
    cardEl.onclick=()=>{
      if(G.gold<price){ toast('Not enough Gold!'); return; }
      G.gold-=price;
      G.inventory[c.id]=(G.inventory[c.id]||0)+1;
      G.deck.push(c.id);
      log(`Bought ${c.name} for ${price} Gold.`,'sys');
      openShop(); // refresh (re-roll remaining slots per GDD simplicity)
      render();
    };
    sc.appendChild(slot);
  });
  document.getElementById('shopGold').textContent=`You carry ${G.gold} Gold. Buy cards to strengthen your deck (max 5 copies each).`;
  document.getElementById('shopOv').classList.add('show');
  render();
}
document.getElementById('shopContinue').onclick=()=>{
  document.getElementById('shopOv').classList.remove('show');
  startRoom();
};
document.getElementById('shopBtn').onclick=()=>{ if(!G.over) openShop(); };

/* ================= MENU / INIT ================= */
function buildMenu(){
  const cp=document.getElementById('classPick'); cp.innerHTML='';
  Object.entries(CLASSES).forEach(([k,c])=>{
    const d=document.createElement('div'); d.className='classCard';
    d.innerHTML=`<svg viewBox="0 0 132 186"><use href="#${c.back}"/></svg>
      <h3 style="color:${c.color}">${c.name}</h3><p>${c.desc}</p>
      <div class="stats">❤ ${c.hp} HP · ⚡ ${c.energy} start Energy</div>
      <button class="etched" style="margin-top:12px">DESCEND</button>`;
    d.querySelector('button').onclick=()=>{
      document.getElementById('menu').style.display='none';
      document.getElementById('game').style.display='flex';
      document.getElementById('log').innerHTML='';
      newGame(k);
    };
    cp.appendChild(d);
  });
}
document.getElementById('endTurn').onclick=endTurn;
document.getElementById('cancelBtn').onclick=()=>{ G.pendingCard=null; render(); };
document.getElementById('endBtn').onclick=()=>{
  document.getElementById('endOv').classList.remove('show');
  document.getElementById('game').style.display='none';
  document.getElementById('menu').style.display='flex';
  G=null;
};
buildMenu();
</script>
</body>
</html>
<!-- agent-meta {"model":"z-ai/glm-5.3-flash","provider":"openrouter","persona":"full","sessionId":"9bea930f-5b22-4dff-933d-6f81c503819d","tokensIn":181203,"tokensOut":15723,"tokensTotal":196926,"cost":0.013425389999999999,"turns":9,"toolCalls":8,"failedToolCalls":0,"timestamp":"2026-08-28T03:57:21.322Z"} -->