← Deckbuilder results

Deckbuilder

ox alpha · 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 32,009 bytes · SHA-256 66e49a12efc2
<!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; --gold-dim:#8a6f1c; --parch:#e8d9b0;
  --bg0:#0a0a10; --bg1:#14121c; --bg2:#1e1b2a;
  --atk:#e25822; --def:#5b8fc9; --oth:#9a6fd0;
  --hp:#a4243b; --en:#3fa7a0;
}
*{box-sizing:border-box;margin:0;padding:0;user-select:none}
body{
  background:radial-gradient(ellipse at 50% -10%, #241f33 0%, var(--bg0) 70%);
  color:#d8d2c4;font-family:Georgia,'Times New Roman',serif;
  min-height:100vh;overflow-x:hidden;
}
h1,h2,h3{font-variant:small-caps;letter-spacing:2px}
button{font-family:inherit;cursor:pointer}
.screen{display:none;min-height:100vh;flex-direction:column;align-items:center;padding:12px}
.screen.active{display:flex}

.btn{
  background:linear-gradient(180deg,#3a3a42,#1c1c22);
  color:var(--parch);border:1px solid #55505e;border-radius:6px;
  padding:10px 22px;font-size:15px;font-variant:small-caps;letter-spacing:2px;
  box-shadow:inset 0 1px 0 #6a6474, 0 3px 8px #000;
  transition:all .15s;
}
.btn:hover:not(:disabled){border-color:var(--gold);color:#fff;box-shadow:inset 0 1px 0 #6a6474,0 0 12px rgba(201,162,39,.35)}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn.small{padding:5px 12px;font-size:13px}

#menu{justify-content:center;gap:26px;text-align:center}
#menu h1{font-size:52px;color:var(--parch);text-shadow:0 0 24px rgba(201,162,39,.4),0 2px 0 #000}
#menu .sub{color:#8f8898;font-style:italic;margin-top:-14px}
.classes{display:flex;gap:22px;flex-wrap:wrap;justify-content:center;margin-top:10px}
.classCard{
  width:230px;background:linear-gradient(180deg,#1b1826,#100e18);
  border:1px solid #3c3750;border-radius:10px;padding:18px;cursor:pointer;
  transition:all .2s;position:relative;overflow:hidden;
}
.classCard:hover{transform:translateY(-6px);border-color:var(--gold);box-shadow:0 8px 30px rgba(0,0,0,.7),0 0 20px rgba(201,162,39,.15)}
.classCard h2{font-size:22px;margin:10px 0 4px}
.classCard .theme{font-size:12.5px;color:#9a93a6;font-style:italic;min-height:34px}
.classCard .stats{font-size:13px;margin-top:8px;color:#c8c2b4}
.classCard svg{display:block;margin:0 auto;filter:drop-shadow(0 4px 10px #000)}
.cBastion h2{color:#9db8dc}.cWither h2{color:#d98a45}.cShroud h2{color:#b48fe0}

#topbar{
  width:100%;max-width:1180px;display:flex;align-items:center;justify-content:space-between;
  background:linear-gradient(180deg,#221e2e,#15121e);border:1px solid #3c3750;border-radius:8px;
  padding:8px 18px;margin-bottom:10px;box-shadow:0 4px 14px rgba(0,0,0,.5);
}
#topbar .stat{font-size:16px;display:flex;align-items:center;gap:7px}
#topbar .stat b{color:var(--gold);font-size:19px}
#topbar .sep{width:1px;height:24px;background:#3c3750}
#floorLabel{color:var(--parch);font-variant:small-caps;letter-spacing:3px;font-size:18px}

#combat{max-width:1180px;width:100%}
#battlefield{display:flex;width:100%;gap:14px;flex:1;min-height:0}
#handPanel{width:250px;flex-shrink:0;display:flex;flex-direction:column;gap:8px}
#handPanel h3{color:#8f8898;font-size:14px;border-bottom:1px solid #3c3750;padding-bottom:4px}
#hand{display:flex;flex-direction:column;gap:6px;overflow-y:auto;max-height:62vh;padding:4px 2px}
#centerCol{flex:1;display:flex;flex-direction:column;align-items:center;gap:10px}
#heroZone{display:flex;align-items:center;gap:16px;background:rgba(20,17,30,.6);border:1px solid #3c3750;border-radius:10px;padding:10px 18px;width:100%;justify-content:center}
#heroZone .portrait{font-size:44px;filter:drop-shadow(0 0 8px rgba(201,162,39,.3))}
.barWrap{width:220px;height:16px;background:#0c0a12;border:1px solid #443f52;border-radius:8px;overflow:hidden;position:relative}
.barFill{height:100%;transition:width .35s;border-radius:7px}
.hpFill{background:linear-gradient(180deg,#d0455e,#7c1a2c)}
.barText{position:absolute;inset:0;text-align:center;font-size:11.5px;line-height:15px;color:#fff;text-shadow:0 1px 2px #000}
#heroBuffs{font-size:12px;color:#9a93a6;min-height:16px}
#enemyRow{display:flex;gap:18px;justify-content:center;flex-wrap:wrap;flex:1;align-items:center}
.enemy{
  width:170px;background:linear-gradient(180deg,#1c1626,#0f0c16);border:1px solid #4a3a5c;border-radius:12px;
  padding:12px;text-align:center;cursor:pointer;transition:all .2s;position:relative;
}
.enemy:hover{border-color:#8a6fb0}
.enemy.targeted{border-color:var(--atk);box-shadow:0 0 18px rgba(226,88,34,.4)}
.enemy.dead{opacity:.25;pointer-events:none;filter:grayscale(1)}
.enemy .sprite{font-size:52px;line-height:1;transition:transform .15s}
.enemy.shake .sprite{animation:shake .35s}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-7px)}50%{transform:translateX(6px)}75%{transform:translateX(-4px)}}
.enemy .ename{font-variant:small-caps;letter-spacing:1px;font-size:15px;margin:4px 0 6px;color:#e0d8c8}
.enemy .estat{font-size:12px;color:#9a93a6}
.enemy .statusRow{font-size:11px;min-height:15px;margin-top:4px}
.st-burn{color:#e8a33d}.st-poison{color:#7fc76a}.st-stun{color:#e0d060}.st-silence{color:#b48fe0}
.enemy .intent{font-size:11.5px;color:#d0906a;margin-top:3px;font-style:italic}
#rightCol{width:190px;flex-shrink:0;display:flex;flex-direction:column;gap:10px;align-items:center}
#energyMeter{width:100%;background:linear-gradient(180deg,#1c2a2a,#101a1a);border:1px solid #2e5a56;border-radius:10px;padding:12px;text-align:center}
#energyMeter .big{font-size:34px;color:var(--en);text-shadow:0 0 12px rgba(63,167,160,.5)}
#energyMeter .lbl{font-size:12px;color:#7ba7a3;font-variant:small-caps;letter-spacing:2px}
#deckInfo{font-size:12.5px;color:#8f8898;text-align:center;line-height:1.7}
#controls{width:100%;max-width:1180px;display:flex;gap:12px;justify-content:center;margin-top:10px;padding:10px;background:rgba(20,17,30,.5);border:1px solid #3c3750;border-radius:8px}
#log{width:100%;max-width:1180px;height:64px;overflow-y:auto;font-size:12.5px;color:#9a93a6;background:rgba(10,8,16,.6);border:1px solid #2c2838;border-radius:8px;padding:6px 12px;margin-top:8px;line-height:1.55}
#log .l-good{color:#8fce7f}#log .l-bad{color:#e07a6a}#log .l-gold{color:var(--gold)}#log .l-sys{color:#7a7490;font-style:italic}

.card{
  width:230px;height:74px;border-radius:8px;position:relative;cursor:pointer;
  transition:transform .15s, box-shadow .15s;flex-shrink:0;
}
.card:hover{transform:translateX(6px) scale(1.03);z-index:5}
.card svg{position:absolute;inset:0;width:100%;height:100%;border-radius:8px}
.card .cardInner{position:absolute;inset:0;display:flex;flex-direction:column;padding:7px 8px 6px 52px;pointer-events:none}
.card .cname{font-size:13.5px;font-variant:small-caps;letter-spacing:1px;color:#f2e8ce;text-shadow:0 1px 2px #000;line-height:1.1}
.card .ctext{font-size:10.5px;color:#cfc6ae;line-height:1.25;margin-top:2px;flex:1}
.card .ccost{position:absolute;top:4px;right:7px;font-size:14px;color:var(--en);font-weight:bold;text-shadow:0 0 5px rgba(63,167,160,.8)}
.card .cstats{position:absolute;bottom:4px;right:7px;font-size:12px;font-weight:bold}
.card.unplayable{filter:grayscale(.7) brightness(.6)}
.card.unplayable:hover{transform:none}
.card.tA{box-shadow:0 0 10px rgba(226,88,34,.35),0 3px 8px #000}
.card.tD{box-shadow:0 0 10px rgba(91,143,201,.35),0 3px 8px #000}
.card.tU{box-shadow:0 0 10px rgba(154,111,208,.35),0 3px 8px #000}

#shop{gap:16px}
#shop h2{color:var(--parch);font-size:30px;text-shadow:0 0 16px rgba(201,162,39,.3)}
#shopGold{font-size:19px;color:var(--gold)}
#shopGrid{display:flex;flex-wrap:wrap;gap:16px;justify-content:center;max-width:1050px}
.shopItem{display:flex;flex-direction:column;align-items:center;gap:8px;background:rgba(20,17,30,.6);border:1px solid #3c3750;border-radius:10px;padding:12px}
.shopItem .price{font-size:15px;color:var(--gold)}
.shopItem.sold{opacity:.35;pointer-events:none}
#shopInv{max-width:1050px;width:100%;margin-top:4px}
#shopInv h3{color:#8f8898;font-size:14px;margin-bottom:6px}
#invList{font-size:13px;color:#b8b2a4;line-height:1.7;column-count:3;column-gap:30px}

#endScreen{justify-content:center;gap:20px;text-align:center}
#endScreen h1{font-size:46px}
#endScreen p{color:#9a93a6;font-style:italic;max-width:520px}
.victory h1{color:var(--gold);text-shadow:0 0 30px rgba(201,162,39,.5)}
.defeat h1{color:#a4243b;text-shadow:0 0 30px rgba(164,36,59,.5)}

.float{
  position:absolute;font-size:20px;font-weight:bold;pointer-events:none;z-index:50;
  text-shadow:0 2px 4px #000;animation:floatUp 1s ease-out forwards;
}
@keyframes floatUp{0%{opacity:0;transform:translateY(6px)}15%{opacity:1}100%{opacity:0;transform:translateY(-38px)}}
::-webkit-scrollbar{width:8px}::-webkit-scrollbar-thumb{background:#3c3750;border-radius:4px}
</style>
</head>
<body>

<div id="menu" class="screen active">
  <h1>Shadows of Aethelgard</h1>
  <p class="sub">The Void consumes the land. Choose your hero.</p>
  <div class="classes" id="classSelect"></div>
</div>

<div id="combat" class="screen">
  <div id="topbar">
    <div class="stat">💰 <b id="hudGold">0</b></div>
    <div class="sep"></div>
    <div id="floorLabel">Floor 1 — Room 1</div>
    <div class="sep"></div>
    <div class="stat">❤️ <b id="hudHp">30</b>/<span id="hudMaxHp">30</span></div>
  </div>
  <div id="battlefield">
    <div id="handPanel">
      <h3>Hand (<span id="handCount">0</span>/<span id="handMax">4</span>)</h3>
      <div id="hand"></div>
    </div>
    <div id="centerCol">
      <div id="heroZone">
        <div class="portrait" id="heroPortrait">🛡️</div>
        <div>
          <div class="barWrap"><div class="barFill hpFill" id="heroHpBar"></div><div class="barText" id="heroHpText"></div></div>
          <div id="heroBuffs"></div>
        </div>
      </div>
      <div id="enemyRow"></div>
    </div>
    <div id="rightCol">
      <div id="energyMeter">
        <div class="big" id="energyText">3/3</div>
        <div class="lbl">Energy</div>
      </div>
      <div id="deckInfo"></div>
    </div>
  </div>
  <div id="controls">
    <button class="btn" id="btnEndTurn">End Turn</button>
  </div>
  <div id="log"></div>
</div>

<div id="shop" class="screen">
  <h2>The Wandering Merchant</h2>
  <div id="shopGold"></div>
  <div id="shopGrid"></div>
  <div id="shopInv">
    <h3>Your Deck</h3>
    <div id="invList"></div>
  </div>
  <button class="btn" id="btnLeaveShop">Continue Descent</button>
</div>

<div id="endScreen" class="screen">
  <h1 id="endTitle"></h1>
  <p id="endText"></p>
  <button class="btn" id="btnRestart">Return to Main Menu</button>
</div>

<script>
'use strict';
/* ================= DATA ================= */
const CLASSES = {
  bastion:{name:'The Bastion',theme:'Immovable object. High defense, durability and area strikes.',hp:35,en:3,portrait:'🛡️',col:'#7d93b5'},
  wither:{name:'The Wither',theme:'Rot and burn. Afflictions, fire, and lingering death.',hp:28,en:4,portrait:'🔥',col:'#c96a2e'},
  shroud:{name:'The Shroud',theme:'Shadow and speed. Assassination, evasion, energy tricks.',hp:26,en:4,portrait:'🗡️',col:'#9a6fd0'}
};
const CLS_PREFIX={bastion:'BA',wither:'WI',shroud:'SH'};
const CARDS = {
  'BA-A01':{n:'Slam',t:'A',c:2,a:3,d:0,x:'Deal 3 damage. If you have 30+ HP, draw 1 card.'},
  'BA-A02':{n:'Earthquake',t:'A',c:3,a:4,d:0,x:'Deal 4 damage to ALL enemies.'},
  'BA-A03':{n:'Stone Fist',t:'A',c:1,a:2,d:0,x:'Deal 2 damage.'},
  'BA-A04':{n:'Boulder',t:'A',c:4,a:6,d:0,x:'Deal 6 damage.'},
  'BA-D01':{n:'Shield Wall',t:'D',c:0,a:0,d:4,x:'Gain 4 Defense for this turn.'},
  'BA-D02':{n:'Iron Skin',t:'D',c:2,a:0,d:5,x:'Gain 5 Defense permanently. Discard 1 card.'},
  'BA-D03':{n:'Stand Firm',t:'D',c:1,a:0,d:2,x:'Gain 2 Defense.'},
  'BA-D04':{n:'Taunt',t:'D',c:2,a:0,d:3,x:'Gain 3 Defense. Stun 1 enemy for 1 turn.'},
  'BA-U01':{n:'Search',t:'U',c:2,a:0,d:0,x:'Draw 1 card (hand max 5).'},
  'BA-U02':{n:'Repair',t:'U',c:3,a:0,d:0,x:'Heal 6 HP.'},
  'BA-U03':{n:'Rally',t:'U',c:1,a:0,d:0,x:'Draw 1 card. Gain 1 Energy.'},
  'BA-U04':{n:'Loot',t:'U',c:2,a:0,d:0,x:'Gain 5 Gold.'},
  'WI-A01':{n:'Fireball',t:'A',c:2,a:3,d:0,x:'Deal 3 damage. Apply 1 Burn.'},
  'WI-A02':{n:'Inferno',t:'A',c:3,a:4,d:0,x:'Deal 4 damage. Apply 3 Burn.'},
  'WI-A03':{n:'Ember',t:'A',c:1,a:2,d:0,x:'Deal 2 damage. Apply 1 Burn (1 dmg/turn).'},
  'WI-A04':{n:'Pyre',t:'A',c:4,a:5,d:0,x:'Deal 5 damage. All enemies take 1 dmg next turn.'},
  'WI-D01':{n:'Scorch Shield',t:'D',c:2,a:0,d:3,x:'Gain 3 Defense.'},
  'WI-D02':{n:'Heat Resist',t:'D',c:1,a:0,d:1,x:'Gain 1 Defense. Gain 1 Energy.'},
  'WI-D03':{n:'Smoke',t:'D',c:2,a:0,d:4,x:'Gain 4 Defense.'},
  'WI-D04':{n:'Endure',t:'D',c:3,a:0,d:2,x:'Gain 2 Defense. Heal 2 HP.'},
  'WI-U01':{n:'Poison',t:'U',c:2,a:0,d:0,x:'Apply 1 Poison (1 dmg/turn) to target.'},
  'WI-U02':{n:'Burn',t:'U',c:3,a:3,d:0,x:'Deal 3 damage + 2 Burn to ALL enemies.'},
  'WI-U03':{n:'Slow',t:'U',c:1,a:0,d:0,x:'Draw 1 card. Stun target 1 turn.'},
  'WI-U04':{n:'Wither',t:'U',c:2,a:0,d:0,x:'Heal 5 HP. Reduce target Attack by 1.'},
  'SH-A01':{n:'Backstab',t:'A',c:2,a:4,d:0,x:'Deal 4 damage. +1 if target has no Defense.'},
  'SH-A02':{n:'Shadow Bolt',t:'A',c:3,a:5,d:0,x:'Deal 5 damage. Ignores enemy Defense.'},
  'SH-A03':{n:'Silence',t:'A',c:1,a:3,d:0,x:'Deal 3 damage. Target cannot act next turn.'},
  'SH-A04':{n:'Vengeance',t:'A',c:3,a:6,d:0,x:'Deal 6 damage. Gain 2 Energy.'},
  'SH-D01':{n:'Blur',t:'D',c:3,a:0,d:2,x:'Gain 2 Defense. Ignore the next attack.'},
  'SH-D02':{n:'Step',t:'D',c:2,a:0,d:3,x:'Gain 3 Defense. Gain 1 Energy.'},
  'SH-D03':{n:'Evasion',t:'D',c:1,a:0,d:1,x:'Gain 1 Defense. Skip the next enemy action.'},
  'SH-D04':{n:'Phantom',t:'D',c:2,a:0,d:4,x:'Gain 4 Defense.'},
  'SH-U01':{n:'Dash',t:'U',c:1,a:0,d:0,x:'Gain 1 Energy. Skip the next enemy attack.'},
  'SH-U02':{n:'Teleport',t:'U',c:2,a:2,d:0,x:'Deal 2 damage to ALL enemies.'},
  'SH-U03':{n:'Hide',t:'U',c:2,a:0,d:1,x:'Gain 1 Defense. Discard 1 random card, draw 2.'},
  'SH-U04':{n:'Assassinate',t:'U',c:3,a:8,d:0,x:'Deal 8 damage. Cannot be blocked.'}
};
const ALL_IDS = Object.keys(CARDS);
const TYPE_NAME = {A:'Attack',D:'Defense',U:'Other'};
const ENEMY_KINDS = [
  {names:['Goblin','Cave Rat','Snarl Pup'],icons:['👹','🐀','🐺']},
  {names:['Vampire Knight','Ogre','Ghoul'],icons:['🧛','👹','🧟']},
  {names:['Void Warrior','Cultist','Hollow One'],icons:['👾','🕯️','👤']}
];
const BOSSES = [
  {name:'The Gatekeeper',icon:'🗿',hp:80,dmg:15,mec:'Blocks 5 dmg/turn while above 20 HP'},
  {name:'The Corrupted',icon:'👁️',hp:80,dmg:12,mec:'Deals 20 dmg if your Energy < 3'},
  {name:'The Void Lord',icon:'🌑',hp:40,dmg:14,mec:'Regenerates 10 HP per turn'}
];

/* ================= SVG CARD ART ================= */
const CLS_SVG = {
  bastion:{f1:'#5a6d8a',f2:'#232a38',gem:'#9db8dc',orn:'#8fa3c2',back:['#1e2430','#39445a'],motif:'shield'},
  wither:{f1:'#8a5528',f2:'#2a1c10',gem:'#e0954a',orn:'#c98a4a',back:['#241608','#4a3014'],motif:'flame'},
  shroud:{f1:'#4d3a6e',f2:'#170f22',gem:'#b48fe0',orn:'#9a7cc0',back:['#150e20','#33234a'],motif:'moon'}
};
function motifSVG(m,c){
  if(m==='shield')return `<path d="M50 18 L74 27 V48 Q74 68 50 80 Q26 68 26 48 V27 Z" fill="none" stroke="${c}" stroke-width="2.5"/><path d="M50 28 L64 33 V48 Q64 61 50 70 Q36 61 36 48 V33 Z" fill="${c}" opacity=".25"/><path d="M50 30 V68 M42 42 H58" stroke="${c}" stroke-width="2"/>`;
  if(m==='flame')return `<path d="M50 16 Q66 32 62 46 Q72 44 72 56 Q72 72 50 80 Q28 72 28 56 Q28 44 38 46 Q34 32 50 16 Z" fill="none" stroke="${c}" stroke-width="2.5"/><path d="M50 40 Q57 50 54 58 Q59 58 58 65 Q56 73 50 75 Q44 73 43 65 Q42 56 47 52 Q45 46 50 40Z" fill="${c}" opacity=".35"/>`;
  return `<circle cx="50" cy="48" r="24" fill="none" stroke="${c}" stroke-width="2.5"/><path d="M62 30 A24 24 0 1 0 62 66 A19 19 0 1 1 62 30 Z" fill="${c}" opacity=".4"/><circle cx="42" cy="40" r="2" fill="${c}"/><circle cx="38" cy="52" r="1.5" fill="${c}"/>`;
}
/* Unique frame per deck type (class), with art placeholder */
function cardFrame(cls,type){
  const s=CLS_SVG[cls];
  const tCol=type==='A'?'#e25822':type==='D'?'#5b8fc9':'#9a6fd0';
  const uid=cls+type;
  return `<svg viewBox="0 0 230 74" preserveAspectRatio="none">
    <defs><linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="${s.f2}"/><stop offset="1" stop-color="#0c0a10"/></linearGradient>
      <linearGradient id="b${uid}" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0" stop-color="${s.f1}"/><stop offset=".5" stop-color="${s.f2}"/><stop offset="1" stop-color="${s.f1}"/></linearGradient>
    </defs>
    <rect width="230" height="74" rx="8" fill="url(#g${uid})"/>
    <rect x="1.5" y="1.5" width="227" height="71" rx="7" fill="none" stroke="url(#b${uid})" stroke-width="2.5"/>
    <rect x="4" y="4" width="222" height="66" rx="5" fill="none" stroke="${s.orn}" stroke-width=".7" opacity=".55"/>
    <rect x="9" y="9" width="36" height="56" rx="4" fill="#000" stroke="${tCol}" stroke-width="1.4"/>
    <rect x="11" y="11" width="32" height="52" fill="#141018"/>
    <g transform="translate(27,32) scale(.42)">${motifSVG(s.motif,s.gem)}</g>
    <text x="27" y="63" font-size="6" fill="${s.orn}" text-anchor="middle" opacity=".8">ART</text>
    <circle cx="196" cy="14" r="8" fill="#0c0a12" stroke="${s.gem}" stroke-width="1.5"/>
    <rect x="120" y="60" width="100" height="10" rx="3" fill="${s.f1}" opacity=".25"/>
  </svg>`;
}
function cardBack(cls){
  const s=CLS_SVG[cls];
  return `<svg viewBox="0 0 230 74" preserveAspectRatio="none">
    <defs><radialGradient id="rb${cls}" cx=".5" cy=".5" r=".8">
      <stop offset="0" stop-color="${s.back[1]}"/><stop offset="1" stop-color="${s.back[0]}"/></radialGradient></defs>
    <rect width="230" height="74" rx="8" fill="url(#rb${cls})"/>
    <rect x="1.5" y="1.5" width="227" height="71" rx="7" fill="none" stroke="${s.gem}" stroke-width="2"/>
    <rect x="6" y="6" width="218" height="62" rx="4" fill="none" stroke="${s.orn}" stroke-width=".8" stroke-dasharray="5 4" opacity=".6"/>
    <g opacity=".5">${Array.from({length:9},(_,i)=>`<circle cx="${20+i*24}" cy="${i%2?22:52}" r="1.6" fill="${s.gem}"/>`).join('')}</g>
    <g transform="translate(115,37)" opacity=".85">${motifSVG(s.motif,s.gem)}</g>
    <text x="115" y="70" font-size="6" fill="${s.orn}" text-anchor="middle" letter-spacing="3" opacity=".7">AETHELGARD</text>
  </svg>`;
}
function cardHTML(id,cls){
  const c=CARDS[id];
  const stats = c.a>0?`⚔ ${c.a}`:(c.d>0?`🛡 ${c.d}`:'');
  return `<div class="card t${c.t}" data-id="${id}">
    ${cardFrame(cls,c.t)}
    <div class="cardInner">
      <div class="cname">${c.n}</div>
      <div class="ctext">${c.x}</div>
    </div>
    <div class="ccost">◆${c.c}</div>
    <div class="cstats">${stats}</div>
  </div>`;
}

/* ================= STATE ================= */
let G=null;
const $=id=>document.getElementById(id);
function log(msg,cls=''){const d=document.createElement('div');if(cls)d.className='l-'+cls;d.innerHTML=msg;$('log').appendChild(d);$('log').scrollTop=1e9;}
function show(id){document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));$(id).classList.add('active');}
const shuffle=a=>{for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;};

function newGame(cls){
  const c=CLASSES[cls];
  G={cls,hp:c.hp,maxHp:c.hp,maxEn:c.en,en:c.en,handSize:4,gold:0,
     block:0,permBlock:0,
     deck:ALL_IDS.filter(i=>i.slice(0,2)===CLS_PREFIX[cls]),
     draw:[],discard:[],hand:[],inv:{},floor:1,room:0,enemies:[],target:0,
     ignoreNext:false,skipEnemy:false,pyreDot:false};
  G.deck.forEach(i=>G.inv[i]=(G.inv[i]||0)+1);
  log('You descend into Aethelgard as <b>'+c.name+'</b>.','sys');
  startRoom();
}

/* ================= ROOMS / ENEMIES ================= */
function makeEnemy(floor,isBoss){
  if(isBoss){const b=BOSSES[floor-1];return {name:b.name,icon:b.icon,hp:b.hp,maxHp:b.hp,dmg:b.dmg,def:0,burn:0,poison:0,stun:0,boss:true,mec:b.mec};}
  const k=ENEMY_KINDS[floor-1];const idx=Math.floor(Math.random()*k.names.length);
  const hp=[20,30,40][floor-1],dmg=[3,5,8][floor-1];
  return {name:k.names[idx],icon:k.icons[idx],hp,maxHp:hp,dmg,def:0,burn:0,poison:0,stun:0};
}
function startRoom(){
  G.en=G.maxEn;
  G.hand=[];G.draw=shuffle([...G.deck]);G.discard=[];
  G.block=G.permBlock;G.ignoreNext=false;G.skipEnemy=false;G.pyreDot=false;
  const isBoss=G.room===3;
  const n=isBoss?1:(Math.random()<.4||G.floor>1?2:1);
  G.enemies=Array.from({length:n},()=>makeEnemy(G.floor,isBoss));
  G.target=0;
  $('floorLabel').textContent=`Floor ${G.floor} — ${isBoss?'⚠ BOSS':'Room '+(G.room+1)}`;
  $('log').innerHTML='';
  log(isBoss?'A Boss bars your way!':'Enemies emerge from the shadows…','sys');
  show('combat');
  drawUp(); render();
}
function drawOne(){
  if(!G.draw.length){if(!G.discard.length)return;G.draw=shuffle(G.discard.splice(0));log('Discard pile reshuffled.','sys');}
  G.hand.push(G.draw.pop());
}
function drawUp(){while(G.hand.length<G.handSize)drawOne();}

/* ================= RENDER ================= */
function render(){
  $('hudGold').textContent=G.gold;
  $('hudHp').textContent=G.hp;$('hudMaxHp').textContent=G.maxHp;
  $('heroPortrait').textContent=CLASSES[G.cls].portrait;
  $('heroHpBar').style.width=(100*Math.max(0,G.hp)/G.maxHp)+'%';
  $('heroHpText').textContent=`${CLASSES[G.cls].name} — HP ${G.hp}/${G.maxHp} · 🛡 ${G.block}${G.permBlock?` (+${G.permBlock} perm)`:''}`;
  $('heroBuffs').textContent=[G.ignoreNext?'✨ Ignore next attack':'',G.skipEnemy?'💨 Skip next enemy action':''].filter(Boolean).join(' · ');
  $('energyText').textContent=`${G.en}/${G.maxEn}`;
  $('handCount').textContent=G.hand.length;$('handMax').textContent=Math.min(5,G.handSize);
  $('deckInfo').innerHTML=`Draw pile: ${G.draw.length}<br>Discard: ${G.discard.length}<br>Deck: ${G.deck.length} cards`;
  $('hand').innerHTML=G.hand.map((id,i)=>{
    const playable=CARDS[id].c<=G.en;
    return cardHTML(id,G.cls).replace('class="card',(playable?'class="card':'class="unplayable card'));
  }).join('');
  const cards=document.querySelectorAll('#hand .card');
  cards.forEach((el,idx)=>{el.dataset.i=idx;el.onclick=()=>playCard(idx);});
  $('enemyRow').innerHTML=G.enemies.map((e,i)=>e.hp<=0?`<div class="enemy dead"><div class="sprite">💀</div><div class="ename">${e.name}</div><div class="estat">slain</div></div>`:
    `<div class="enemy ${i===G.target?'targeted':''}" data-e="${i}">
      <div class="sprite">${e.icon}</div><div class="ename">${e.name}</div>
      <div class="barWrap"><div class="barFill hpFill" style="width:${100*e.hp/e.maxHp}%"></div><div class="barText">${e.hp}/${e.maxHp}</div></div>
      <div class="estat">⚔ ${e.dmg}${e.def?' · 🛡 '+e.def:''}</div>
      <div class="statusRow">${e.burn?`<span class="st-burn">🔥${e.burn}</span> `:''}${e.poison?`<span class="st-poison">☠${e.poison}</span> `:''}${e.stun?`<span class="st-stun">💫Stunned</span>`:''}</div>
      <div class="intent">${e.boss?('✦ '+e.mec):'Intends to attack for '+e.dmg}</div>
    </div>`).join('');
  document.querySelectorAll('.enemy:not(.dead)').forEach(el=>el.onclick=()=>{G.target=+el.dataset.e;render();});
}
function floatText(txt,color){
  const el=$('enemyRow');
  const f=document.createElement('div');f.className='float';f.textContent=txt;f.style.color=color;
  f.style.left=(35+Math.random()*30)+'%';f.style.top='30%';
  el.appendChild(f);setTimeout(()=>f.remove(),1000);
}

/* ================= CARD PLAY ================= */
function aliveEnemies(){return G.enemies.filter(e=>e.hp>0);}
function hitEnemy(e,dmg,pierce){
  let d=dmg;
  if(!pierce&&e.def>0){const abs=Math.min(e.def,d);e.def-=abs;d-=abs;}
  e.hp-=d;if(e.hp<0)e.hp=0;return d;
}
function checkDeaths(){
  let killed=0,boss=false;
  G.enemies.forEach(e=>{if(e.hp<=0&&!e.counted){e.counted=true;killed++;if(e.boss)boss=true;}});
  if(killed){const g=killed*(boss?15:5);G.gold+=g;log(`${killed} enemy slain! +${g} Gold`,'gold');floatText('SLAIN','#c9a227');}
  return aliveEnemies().length===0;
}
function playCard(i){
  if(!aliveEnemies().length)return;
  const id=G.hand[i],c=CARDS[id];
  if(c.c>G.en)return log('Not enough Energy.','bad');
  G.hand.splice(i,1);G.discard.push(id);G.en-=c.c;
  let t=G.enemies[G.target].hp>0?G.enemies[G.target]:aliveEnemies()[0];
  const pierce=(id==='SH-A02'||id==='SH-U04');
  switch(id){
    case 'BA-A01':hitEnemy(t,3);log(`Slam hits ${t.name} for 3.`);if(G.hp>=30){drawOne();log('Drew a card (HP ≥ 30).','good');}break;
    case 'BA-A02':case 'SH-U02':aliveEnemies().forEach(e=>hitEnemy(e,c.a));log(`Hits ALL enemies for ${c.a}.`);break;
    case 'BA-A03':case 'BA-A04':hitEnemy(t,c.a);log(`${c.n} hits ${t.name} for ${c.a}.`);break;
    case 'BA-D01':case 'BA-D03':case 'WI-D01':case 'WI-D03':case 'WI-D04':case 'SH-D01':case 'SH-D02':case 'SH-D03':case 'SH-D04':
      G.block+=c.d;log(`Gain ${c.d} Defense.`,'good');
      if(id==='WI-D04'){G.hp=Math.min(G.maxHp,G.hp+2);log('Healed 2 HP.','good');}
      if(id==='BA-D04'){t.stun+=1;log('Enemy stunned!','good');}
      if(id==='SH-D01'){G.ignoreNext=true;log('Next attack will be ignored.','good');}
      if(id==='SH-D03'||id==='SH-D02'&&false){}
      if(id==='SH-D03'){G.skipEnemy=true;log('Next enemy action will be skipped.','good');}
      break;
    case 'BA-D02':G.permBlock+=5;G.block+=5;if(G.hand.length){G.discard.push(G.hand.splice(Math.floor(Math.random()*G.hand.length),1)[0]);}log('Iron Skin: +5 permanent Defense, discarded 1 card.','good');break;
    case 'BA-U01':drawOne();log('Search: drew a card.');break;
    case 'BA-U02':G.hp=Math.min(G.maxHp,G.hp+6);log('Repair: healed 6 HP.','good');break;
    case 'BA-U03':drawOne();G.en++;log('Rally: +1 card, +1 Energy.','good');break;
    case 'BA-U04':G.gold+=5;log('Loot: +5 Gold.','gold');break;
    case 'WI-A01':hitEnemy(t,3);t.burn+=1;log('Fireball: 3 dmg, 1 Burn.');break;
    case 'WI-A02':hitEnemy(t,4);t.burn+=3;log('Inferno: 4 dmg, 3 Burn.');break;
    case 'WI-A03':hitEnemy(t,2);t.burn+=1;log('Ember: 2 dmg, 1 Burn.');break;
    case 'WI-A04':hitEnemy(t,5);G.pyreDot=true;log('Pyre: enemies scorch next turn.');break;
    case 'WI-D02':G.block+=1;G.en++;log('+1 Defense, +1 Energy.','good');break;
    case 'WI-U01':t.poison+=1;log('Poison applied.','good');break;
    case 'WI-U02':aliveEnemies().forEach(e=>{hitEnemy(e,3);e.burn+=2;});log('Burn: 3 dmg + 2 Burn to all.');break;
    case 'WI-U03':drawOne();t.stun+=1;log('Slow: target stunned.');break;
    case 'WI-U04':G.hp=Math.min(G.maxHp,G.hp+5);t.dmg=Math.max(0,t.dmg-1);log('Wither: heal 5, enemy Attack -1.','good');break;
    case 'SH-A01':{const d=4+(t.def<=0?1:0);hitEnemy(t,d);log(`Backstab: ${d} dmg.`);}break;
    case 'SH-A02':hitEnemy(t,5,true);log('Shadow Bolt pierces Defense: 5 dmg.');break;
    case 'SH-A03':hitEnemy(t,3);t.stun+=1;log('Silence: 3 dmg, target silenced.');break;
    case 'SH-A04':hitEnemy(t,6);G.en=Math.min(G.maxEn,G.en+2);log('Vengeance: 6 dmg, +2 Energy.','good');break;
    case 'SH-U01':G.en++;G.skipEnemy=true;log('Dash: +1 Energy, next enemy attack dodged.','good');break;
    case 'SH-U03':G.block+=1;if(G.hand.length)G.discard.push(G.hand.splice(Math.floor(Math.random()*G.hand.length),1)[0]);drawOne();drawOne();log('Hide: +1 Def, discard 1, draw 2.');break;
    case 'SH-U04':hitEnemy(t,8,true);log('Assassinate: unblockable 8 dmg!');break;
  }
  if(checkDeaths()){render();setTimeout(roomClear,700);return;}
  render();
}

/* ================= ENEMY TURN ================= */
function endTurn(){
  $('btnEndTurn').disabled=true;
  aliveEnemies().forEach(e=>{
    const dot=e.burn+e.poison;
    if(dot){e.hp=Math.max(0,e.hp-dot);log(`${e.name} suffers ${dot} DoT damage.`,'good');}
    if(e.burn)e.burn--; if(e.poison)e.poison--;
  });
  if(checkDeaths()){render();setTimeout(roomClear,700);return;}
  setTimeout(()=>{
    if(G.skipEnemy){log('You slip away — enemy actions skipped!','good');G.skipEnemy=false;}
    else{
      aliveEnemies().forEach(e=>{
        if(e.stun>0){e.stun--;log(`${e.name} is stunned and cannot act.`,'good');return;}
        let dmg=e.dmg;
        if(e.boss){
          if(G.floor===1)e.def=(e.hp>=20)?5:0;
          if(G.floor===2&&G.en<3){dmg=20;log('The Corrupted senses your weakness!','bad');}
          if(G.floor===3){e.hp=Math.min(e.maxHp,e.hp+10);log('The Void Lord regenerates 10 HP.','bad');}
        }
        if(G.pyreDot)e.hp=Math.max(0,e.hp-1);
        if(G.ignoreNext){G.ignoreNext=false;log('The attack passes through you!','good');return;}
        const blocked=Math.min(G.block,dmg);G.block-=blocked;const taken=dmg-blocked;
        G.hp-=taken;
        log(`${e.name} attacks for ${dmg} (${blocked} blocked, ${taken} damage).`,taken?'bad':'good');
      });
      G.pyreDot=false;
      if(checkDeaths()){render();setTimeout(roomClear,700);return;}
      if(G.hp<=0){gameOver(false);return;}
    }
    G.en=G.maxEn;G.block=G.permBlock;drawUp();
    log('— Your turn —','sys');
    $('btnEndTurn').disabled=false;
    render();
  },500);
  render();
}

/* ================= PROGRESSION / SHOP ================= */
function roomClear(){
  log('Room cleared!','gold');
  if(G.room===3){
    if(G.floor===3){gameOver(true);return;}
    G.floor++;G.room=0;log(`Floor ${G.floor} awaits…`,'sys');
  } else G.room++;
  openShop();
}
let offers=[];
function priceOf(id){return Math.min(50,(CARDS[id].c+3)+(G.inv[id]||0)*2+((G.inv[id]||0)>=3?4:0));}
function openShop(){
  offers=shuffle([...ALL_IDS]).slice(0,6).map(id=>({id,sold:false}));
  renderShop();show('shop');
}
function renderShop(){
  $('shopGold').textContent=`💰 Gold: ${G.gold}`;
  $('shopGrid').innerHTML=offers.map((o,i)=>{
    const p=priceOf(o.id),maxed=(G.inv[o.id]||0)>=5;
    return `<div class="shopItem ${o.sold||maxed?'sold':''}">
      <div style="position:relative;width:230px;height:74px">${cardHTML(o.id,G.cls)}</div>
      <div class="price">${maxed?'MAX (5 copies)':`💰 ${p}`}</div>
      <button class="btn small" data-b="${i}" ${(o.sold||maxed||G.gold<p)?'disabled':''}>Buy</button>
    </div>`;
  }).join('');
  document.querySelectorAll('[data-b]').forEach(b=>b.onclick=()=>buy(+b.dataset.b));
  const counts={};G.deck.forEach(id=>counts[id]=(counts[id]||0)+1);
  $('invList').innerHTML=Object.entries(counts).map(([id,n])=>`${n}× <b>${CARDS[id].n}</b> <span style="color:#777">(${TYPE_NAME[CARDS[id].t]})</span>`).join('<br>');
}
function buy(i){
  const o=offers[i],p=priceOf(o.id);
  if(o.sold||G.gold<p||(G.inv[o.id]||0)>=5)return;
  G.gold-=p;o.sold=true;
  G.inv[o.id]=(G.inv[o.id]||0)+1;G.deck.push(o.id);
  log(`Bought <b>${CARDS[o.id].n}</b> for ${p} gold.`,'gold');
  renderShop();
}
$('btnLeaveShop').onclick=()=>startRoom();
$('btnEndTurn').onclick=endTurn;

/* ================= END / MENU ================= */
function gameOver(win){
  show('endScreen');
  $('endScreen').className='screen active '+(win?'victory':'defeat');
  $('endTitle').textContent=win?'Victory Portal Reached':'Consumed by the Void';
  $('endText').textContent=win
    ?'The Void Lord falls. Light returns to Aethelgard — for now.'
    :'Your shadow joins the countless lost in the dark halls of Aethelgard.';
}
$('btnRestart').onclick=()=>show('menu');

function buildMenu(){
  $('classSelect').innerHTML=Object.entries(CLASSES).map(([k,c])=>{
    return `<div class="classCard c${k}" data-cls="${k}">
      <div style="position:relative;width:230px;height:74px">${cardBack(k)}</div>
      <h2>${c.name}</h2><div class="theme">${c.theme}</div>
      <div class="stats">❤️ ${c.hp} HP · ◆ ${c.en} Energy<br>Starter deck of 12 unique cards</div>
    </div>`;
  }).join('');
  document.querySelectorAll('.classCard').forEach(el=>el.onclick=()=>newGame(el.dataset.cls));
}
buildMenu();
</script>
</body>
</html>
<!-- agent-meta {"model":"stealth/ox-alpha","provider":"openrouter","persona":"full","sessionId":"0131549c-6dff-4f83-8476-9a78fe3e5c2a","tokensIn":1025843,"tokensOut":32049,"tokensTotal":1057892,"cost":0,"turns":30,"toolCalls":30,"failedToolCalls":0,"timestamp":"2026-08-21T19:11:26.365Z"} -->