← Deckbuilder results

Deckbuilder

Ornith 1.5 35B A3B · 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 67,209 bytes · SHA-256 f214bc471d5d
<!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:#c9a24b; --gold-bright:#f2d67a; --ink:#0c0b10;
    --parch:#1a1710; --metal:#2a2a30; --metal-lit:#3d3d46;
    --red:#c0392b; --blue:#3b6ea5; --purple:#7b4ea3; --green:#4a8c5a;
    --burn:#d9a441; --void:#5a3fb0; --hp:#b5443a; --hp2:#e07a5f;
    --mana:#4a86c9; --block:#9db8d0;
  }
  *{box-sizing:border-box;}
  html,body{margin:0; height:100%;}
  body{
    font-family:"Trajan Pro","Times New Roman",Georgia,serif;
    background:
      radial-gradient(1200px 700px at 50% -10%, #241f16 0%, #121009 55%, #080705 100%);
    color:#e8e0cf; overflow:hidden; user-select:none;
  }
  #app{width:100vw; height:100vh; position:relative;}
  .screen{position:absolute; inset:0; display:none; flex-direction:column;}
  .screen.active{display:flex !important;}
  #menu{display:none; align-items:center; justify-content:center; text-align:center;}
  #game{display:none;}
  .screen.active#game{display:grid !important;}
  .title{
    font-size:58px; letter-spacing:8px; margin:0;
    background:linear-gradient(#f2d67a,#c9a24b 40%,#7a5a1e);
    -webkit-background-clip:text; background-clip:text; color:transparent;
    text-shadow:0 2px 18px rgba(201,162,75,.35); font-weight:700;
  }
  .subtitle{color:#a89a7c; letter-spacing:6px; margin:6px 0 34px; font-size:15px;}
  .class-grid{display:flex; gap:26px; flex-wrap:wrap; justify-content:center;}
  .class-card{
    width:250px; padding:22px 20px; border-radius:14px; cursor:pointer;
    background:linear-gradient(180deg,#232019,#15120b);
    border:2px solid #423a26; box-shadow:0 10px 30px rgba(0,0,0,.6), inset 0 0 24px rgba(0,0,0,.5);
    transition:transform .18s, border-color .18s, box-shadow .18s; text-align:left;
  }
  .class-card:hover{transform:translateY(-6px); border-color:var(--gold); box-shadow:0 16px 40px rgba(0,0,0,.7), 0 0 30px rgba(201,162,75,.25);}
  .class-card h3{margin:0 0 4px; font-size:22px; letter-spacing:2px;}
  .class-card .theme{font-size:12px; letter-spacing:3px; text-transform:uppercase; margin-bottom:12px;}
  .bastion .theme{color:#c9d3df;} .wither .theme{color:var(--burn);} .shroud .theme{color:var(--void);}
  .class-card p{font-size:12.5px; line-height:1.5; color:#c3b89e; margin:0 0 8px; font-family:Georgia,serif;}
  .class-card .stats{font-size:12px; color:#e8e0cf; border-top:1px solid #3a3322; padding-top:10px;}
  .class-card .stats span{display:block; color:#a89a7c;}

  /* ---------- TOP BAR ---------- */
  #topbar{
    height:54px; display:flex; align-items:center; justify-content:space-between;
    padding:0 20px; background:linear-gradient(180deg,#26221a,#16130c);
    border-bottom:2px solid #423a26; box-shadow:0 4px 14px rgba(0,0,0,.5); z-index:5;
  }
  .tb-item{display:flex; align-items:center; gap:8px; font-size:15px; letter-spacing:1px;}
  .badge{width:22px;height:22px;border-radius:50%; display:grid; place-items:center; font-size:13px; font-weight:bold;}
  .badge.gold{background:radial-gradient(#f2d67a,#a8791f); color:#2a1c05;}
  .badge.floor{background:radial-gradient(#6b6350,#2b271c); color:var(--gold-bright); border:1px solid var(--gold);}
  .bossbar-wrap{flex:1; max-width:340px; margin:0 18px; display:none;}
  .bossbar-wrap.show{display:block;}
  .boss-label{font-size:11px; letter-spacing:3px; color:var(--red); text-transform:uppercase;}
  .bossbar{height:14px; background:#1a0f0f; border:1px solid #5a2222; border-radius:8px; overflow:hidden; margin-top:3px;}
  .bossbar > i{display:block; height:100%; background:linear-gradient(90deg,#8b2a2a,#e07a5f); width:100%;}

  /* ---------- GAME LAYOUT ---------- */
  #game{flex:1; display:none; grid-template-columns:250px 1fr 200px; gap:12px; padding:12px; min-height:0;}
  .screen.active#game{display:grid;}
  .panel{background:linear-gradient(180deg,#1d1a13,#12100a); border:2px solid #3a3322; border-radius:12px; padding:12px; box-shadow:inset 0 0 26px rgba(0,0,0,.5);}
  .panel h4{margin:0 0 8px; letter-spacing:3px; font-size:12px; color:var(--gold-bright); text-transform:uppercase;}

  /* LEFT: player */
  .stat-row{display:flex; justify-content:space-between; align-items:center; margin:6px 0; font-size:14px;}
  .stat-row b{font-size:17px;}
  .hpcol b{color:var(--hp2);} .manab b{color:var(--mana);} .defb{color:var(--block);}
  .mini-bar{height:7px; background:#120c0c; border-radius:5px; overflow:hidden; margin-top:4px; border:1px solid #000;}
  .mini-bar > i{display:block; height:100%;}
  .energy-orb{width:56px;height:56px;border-radius:50%; margin:8px auto 0;
    background:radial-gradient(#7fb4e8,#2a5f96 60%,#12304d); display:grid; place-items:center;
    font-size:24px; font-weight:bold; color:#08203a; box-shadow:0 0 20px rgba(74,134,201,.6), inset 0 0 12px rgba(0,0,0,.5); border:2px solid #bfe0ff;}
  .statlbl{font-size:10px; letter-spacing:2px; color:#8f8368; text-transform:uppercase;}

  /* CENTER: arena */
  #arena{display:flex; flex-direction:column; align-items:center; justify-content:flex-start; position:relative; overflow:auto;}
  .arena-bg{position:absolute; inset:0; z-index:0; pointer-events:none;
    background:
      radial-gradient(600px 200px at 50% 100%, rgba(120,90,40,.18), transparent 70%),
      linear-gradient(180deg, transparent 55%, rgba(0,0,0,.4));
    border-radius:12px;}
  .enemy-wrap{position:relative; z-index:2; margin-top:10px; text-align:center;}
  .enemy-sprite{width:180px; height:210px; filter:drop-shadow(0 10px 20px rgba(0,0,0,.7)); transition:transform .15s;}
  .enemy-sprite.hurt{animation:shake .3s;} .enemy-sprite.attack{animation:lunge .3s;}
  @keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-10px)}75%{transform:translateX(10px)}}
  @keyframes lunge{0%{transform:translateY(0)}40%{transform:translateY(-24px) scale(1.06)}100%{transform:translateY(0)}}
  .enemy-name{font-size:20px; letter-spacing:3px; margin-top:8px; color:#f0d9a0;}
  .enemy-sub{font-size:11px; letter-spacing:2px; color:#a89a7c; text-transform:uppercase;}
  .hpbar{width:240px; height:16px; background:#1a0f0f; border:2px solid #5a2222; border-radius:9px; overflow:hidden; margin:8px auto 0;}
  .hpbar > i{display:block; height:100%; background:linear-gradient(90deg,#8b2a2a,#e07a5f); transition:width .3s;}
  .hp-txt{font-size:12px; color:#d8b8a0; margin-top:3px;}
  .status-pips{display:flex; gap:6px; justify-content:center; margin-top:8px; min-height:22px;}
  .pip{padding:2px 8px; border-radius:6px; font-size:11px; letter-spacing:1px; border:1px solid #000;}
  .pip.burn{background:linear-gradient(#d9a441,#7a4a10); color:#201200;}
  .pip.poison{background:linear-gradient(#5aa35a,#1e4a24); color:#e0ffe0;}
  .pip.stun{background:linear-gradient(#b8a040,#5a4a10); color:#201800;}
  .targeting-hint{color:var(--gold-bright); font-size:13px; margin-top:10px; letter-spacing:2px; display:none;}
  .targeting-hint.show{display:block; animation:pulse 1s infinite;}
  @keyframes pulse{50%{opacity:.45;}}

  /* message log */
  #log{position:absolute; bottom:10px; left:50%; transform:translateX(-50%); width:min(520px,90%);
    max-height:88px; overflow:hidden; z-index:6; pointer-events:none; text-align:center;}
  #log .line{font-size:12.5px; padding:3px 0; color:#e8e0cf; text-shadow:0 1px 4px #000; animation:fadeUp .3s;}
  #log .line.dmg{color:#ff8a7a;} #log .line.heal{color:#8fe0a0;} #log .line.mana{color:#9dc9f0;}
  #log .line.gold{color:var(--gold-bright);} #log .line.big{font-size:15px; color:#ffd27a;}
  @keyframes fadeUp{from{opacity:0; transform:translateY(6px)}to{opacity:1;}}

  /* HAND */
  #handArea{grid-column:1 / -1; min-height:190px; padding:8px 14px 12px;
    background:linear-gradient(0deg,#17140d,#0e0c07); border-top:2px solid #3a3322;
    display:flex; align-items:center; gap:10px; position:relative;}
  #hand{display:flex; gap:10px; align-items:flex-end; flex:1; min-height:176px; overflow-x:auto; padding:6px; position:relative;}
  .controls{display:flex; flex-direction:column; gap:8px; align-items:center; min-width:120px;}
  .btn{
    cursor:pointer; border:2px solid #5a4e30; border-radius:8px; padding:9px 14px; font-family:inherit;
    letter-spacing:2px; font-size:13px; color:#efe4c8; font-weight:600;
    background:linear-gradient(180deg,#3a342a,#241f16); box-shadow:inset 0 1px 0 #5a5040, 0 3px 8px rgba(0,0,0,.5);
    transition:transform .1s, border-color .1s, box-shadow .1s;
  }
  .btn:hover{border-color:var(--gold-bright); transform:translateY(-2px);} .btn:active{transform:translateY(1px);}
  .btn:disabled{opacity:.4; cursor:not-allowed; transform:none;}
  .btn.play{background:linear-gradient(180deg,#5a4626,#33260f); border-color:var(--gold);}
  .btn.end{background:linear-gradient(180deg,#4a6a3a,#243a1e); border-color:#7ab066;}
  .btn.primary{background:linear-gradient(180deg,#6a4a2a,#3a2610); border-color:var(--gold-bright);}
  .btn.small{padding:6px 10px; font-size:11px;}

  /* CARD */
  .card{
    width:132px; height:186px; border-radius:12px; position:relative; flex-shrink:0; cursor:pointer;
    background-size:cover; background-position:center;
    box-shadow:0 8px 20px rgba(0,0,0,.6); transition:transform .16s, box-shadow .16s;
    border:1px solid rgba(0,0,0,.6);
  }
  .card:hover{transform:translateY(-22px) scale(1.06); box-shadow:0 18px 34px rgba(0,0,0,.75); z-index:4;}
  .card.selected{transform:translateY(-30px) scale(1.08); box-shadow:0 0 0 3px var(--gold-bright),0 18px 34px rgba(0,0,0,.75); z-index:5;}
  .card.unplayable{filter:brightness(.6) saturate(.6);}
  .card .inner{position:absolute; inset:6px; border-radius:8px; display:flex; flex-direction:column; pointer-events:none;}
  .card .cost{position:absolute; top:-8px; left:-8px; width:30px;height:30px; border-radius:50%;
    background:radial-gradient(#7fb4e8,#2a5f96); color:#08203a; display:grid; place-items:center;
    font-weight:bold; font-size:15px; border:2px solid #bfe0ff; box-shadow:0 2px 6px #000; z-index:3;}
  .card .cname{margin-top:14px; text-align:center; font-size:13px; font-weight:700; letter-spacing:1px; color:#fff; text-shadow:0 1px 3px #000;}
  .card .ctype{text-align:center; font-size:8.5px; letter-spacing:2px; color:#d8c9a0; text-transform:uppercase; margin-top:2px;}
  .card .art{flex:1; margin:6px 8px 0; border-radius:6px; background:rgba(0,0,0,.35); border:1px solid rgba(201,162,75,.35);
    display:grid; place-items:center; color:#5a5040; font-size:11px; text-align:center; padding:4px;}
  .card .stats-line{display:flex; justify-content:space-between; padding:4px 8px; font-size:12px; font-weight:bold;}
  .card .atk{color:#ff9a7a; text-shadow:0 1px 2px #000;}
  .card .defv{color:#bcd6ec; text-shadow:0 1px 2px #000;}
  .card .effect{text-align:center; font-size:8.5px; line-height:1.25; color:#efe4c8; padding:0 7px 16px; font-family:Georgia,serif; text-shadow:0 1px 2px #000;}
  .card.glow-attack{box-shadow:0 8px 20px rgba(192,57,43,.5),0 8px 20px rgba(0,0,0,.6);}
  .card.glow-defense{box-shadow:0 8px 20px rgba(59,110,165,.5),0 8px 20px rgba(0,0,0,.6);}
  .card.glow-other{box-shadow:0 8px 20px rgba(123,78,163,.5),0 8px 20px rgba(0,0,0,.6);}

  /* RIGHT */
  .shop-btn{background:linear-gradient(180deg,#6a5426,#3a2c10); border-color:var(--gold);}
  .deck-count{font-size:12px; color:#a89a7c; margin-top:10px;}

  /* MODAL */
  .modal-bg{position:absolute; inset:0; background:rgba(6,5,3,.82); display:none; align-items:center; justify-content:center; z-index:20;}
  .modal-bg.show{display:flex;}
  .modal{width:min(720px,92%); max-height:88vh; overflow:auto; background:linear-gradient(180deg,#232019,#14110b);
    border:2px solid var(--gold); border-radius:14px; padding:24px; box-shadow:0 0 50px rgba(201,162,75,.25);}
  .modal h2{margin:0 0 4px; letter-spacing:4px; color:var(--gold-bright); text-align:center;}
  .modal .sub{text-align:center; color:#a89a7c; letter-spacing:2px; margin-bottom:18px; font-size:13px;}
  .shop-grid{display:flex; gap:16px; flex-wrap:wrap; justify-content:center;}
  .shop-card{width:150px; text-align:center;}
  .shop-card .price{margin-top:6px; color:var(--gold-bright); font-size:14px; letter-spacing:1px;}
  .shop-card .owned{font-size:10px; color:#a89a7c;}
  .rest-options{display:flex; gap:16px; justify-content:center; flex-wrap:wrap; margin-top:10px;}
  .rest-card{width:200px; text-align:center;}
  .center-msg{font-size:20px; text-align:center; line-height:1.6; padding:20px; letter-spacing:1px;}
  .victory h2{color:#f2d67a;} .gameover h2{color:var(--red);}
  .footer-note{margin-top:16px; text-align:center; color:#6f6550; font-size:11px; letter-spacing:1px;}
  .float-dmg{position:absolute; font-size:26px; font-weight:bold; pointer-events:none; z-index:30;
    text-shadow:0 2px 6px #000; animation:floatUp .9s ease-out forwards;}
  @keyframes floatUp{from{opacity:1; transform:translateY(0)}to{opacity:0; transform:translateY(-46px)}}
  .hint-text{color:#8f8368; font-size:12px; text-align:center; letter-spacing:1px; margin-top:4px;}
</style>
</head>
<body>
<div id="app">

  <!-- MENU -->
  <section id="menu" class="screen active">
    <h1 class="title">SHADOWS OF AETHELGARD</h1>
    <div class="subtitle">— DESCEND. BURN THE VOID. RECLAIM THE LAND. —</div>
    <div class="class-grid" id="classGrid"></div>
    <div class="footer-note">Nine rooms. Three floors. Three Void Lords. Choose your champion.</div>
  </section>

  <!-- GAME -->
  <section id="game" class="screen">
    <div id="topbar">
      <div class="tb-item"><span class="badge gold">G</span><span id="goldTxt">0</span></div>
      <div class="tb-item"><span class="badge floor" id="floorTxt">I</span><span id="roomTxt">Room 1</span></div>
      <div class="bossbar-wrap" id="bossbarWrap">
        <div class="boss-label">Boss</div>
        <div class="bossbar"><i id="bossbarFill"></i></div>
      </div>
    </div>

    <div class="panel" id="playerPanel">
      <h4>Champion</h4>
      <div class="statrow stat-row"><span class="statlbl">Health</span><span class="hpcol"><b id="hpTxt">30</b></span></div>
      <div class="mini-bar"><i id="hpBar" style="width:100%;background:linear-gradient(90deg,#b5443a,#e07a5f)"></i></div>
      <div class="statrow stat-row" style="margin-top:10px"><span class="statlbl">Energy</span><span class="manab"><b id="enTxt">5/5</b></span></div>
      <div class="statrow stat-row"><span class="statlbl">Defense</span><span class="defb"><b id="defTxt">0</b></span></div>
      <div class="statrow stat-row"><span class="statlbl">Hand Size</span><span><b id="handSizeTxt">4</b></span></div>
      <div class="energy-orb" id="energyOrb">5</div>
      <div class="deck-count">Deck: <b id="deckCount">0</b> · Discard: <b id="discardCount">0</b></div>
    </div>

    <div class="panel" id="arena">
      <div class="arena-bg"></div>
      <div class="enemy-wrap" id="enemyWrap">
        <div class="enemy-sprite" id="enemySprite"></div>
        <div class="enemy-name" id="enemyName">Enemy</div>
        <div class="enemy-sub" id="enemySub">Floor 1</div>
        <div class="hpbar"><i id="enemyHpBar" style="width:100%"></i></div>
        <div class="hp-txt" id="enemyHpTxt">20 / 20</div>
        <div class="status-pips" id="statusPips"></div>
      </div>
      <div class="targeting-hint" id="targetHint">— Click the enemy to strike —</div>
      <div id="log"></div>
    </div>

    <div class="panel" id="rightPanel">
      <h4>Power</h4>
      <div class="controls" style="align-items:stretch">
        <button class="btn shop-btn" id="shopBtn">◈ SHOP</button>
      </div>
      <h4 style="margin-top:16px">Log</h4>
      <div id="sideLog" style="font-size:11px; color:#a89a7c; max-height:140px; overflow:auto; font-family:Georgia,serif;"></div>
    </div>

    <div id="handArea">
      <div id="hand"></div>
      <div class="controls">
        <button class="btn play" id="playBtn">PLAY</button>
        <button class="btn end" id="endTurnBtn">END TURN</button>
        <div class="hint-text" id="selHint">Select a card</div>
      </div>
    </div>
  </section>

  <!-- MODAL -->
  <div class="modal-bg" id="modalBg">
    <div class="modal" id="modal"></div>
  </div>

</div>
<script>
/* ============================================================
   SHADOWS OF AETHELGARD — single-file roguelite deckbuilder
   ============================================================ */
const $ = (id) => document.getElementById(id);
const rand = (n) => Math.floor(Math.random() * n);
const pick = (a) => a[rand(a.length)];
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const ROM = ['I','II','III','IV','V','VI','VII','VIII','IX','X','XI','XII'];

/* ---------------- CARD DATA ---------------- */
const CARDS = {
  'BA-A01':{name:'Slam',type:'Attack',cost:2,atk:3,def:0,ability:'If you have 35 HP, draw 1 card.',cls:'bastion'},
  'BA-A02':{name:'Earthquake',type:'Attack',cost:3,atk:4,def:0,ability:'All enemies take 4 damage.',cls:'bastion'},
  'BA-A03':{name:'Stone Fist',type:'Attack',cost:1,atk:2,def:0,ability:'Deal 2 damage.',cls:'bastion'},
  'BA-A04':{name:'Boulder',type:'Attack',cost:4,atk:6,def:0,ability:'Deal 6 damage.',cls:'bastion'},
  'BA-D01':{name:'Shield Wall',type:'Defense',cost:0,atk:0,def:4,ability:'Gain 4 Defense.',cls:'bastion'},
  'BA-D02':{name:'Iron Skin',type:'Defense',cost:2,atk:0,def:5,ability:'Gain 5 Defense permanently. Discard 1 card.',cls:'bastion'},
  'BA-D03':{name:'Stand Firm',type:'Defense',cost:1,atk:0,def:2,ability:'Gain 2 Defense.',cls:'bastion'},
  'BA-D04':{name:'Taunt',type:'Defense',cost:2,atk:0,def:3,ability:'Gain 3 Defense. Stun 1 enemy 1 turn.',cls:'bastion'},
  'BA-U01':{name:'Search',type:'Other',cost:2,atk:0,def:0,ability:'Draw 1 card.',cls:'bastion'},
  'BA-U02':{name:'Repair',type:'Other',cost:3,atk:0,def:0,ability:'Heal 6 HP.',cls:'bastion'},
  'BA-U03':{name:'Rally',type:'Other',cost:1,atk:0,def:0,ability:'Draw 1 card. Gain 1 Energy.',cls:'bastion'},
  'BA-U04':{name:'Loot',type:'Other',cost:2,atk:0,def:0,ability:'Gain 5 Gold.',cls:'bastion'},
  'WI-A01':{name:'Fireball',type:'Attack',cost:2,atk:3,def:0,ability:'Apply 1 Burn.',cls:'wither'},
  'WI-A02':{name:'Inferno',type:'Attack',cost:3,atk:4,def:0,ability:'All enemies take 4 damage. Apply 3 Burn.',cls:'wither'},
  'WI-A03':{name:'Ember',type:'Attack',cost:1,atk:2,def:0,ability:'Deals 1 damage every turn.',cls:'wither'},
  'WI-A04':{name:'Pyre',type:'Attack',cost:4,atk:5,def:0,ability:'All enemies take 1 damage next turn.',cls:'wither'},
  'WI-D01':{name:'Scorch Shield',type:'Defense',cost:2,atk:0,def:3,ability:'Gain 3 Defense.',cls:'wither'},
  'WI-D02':{name:'Heat Resist',type:'Defense',cost:1,atk:0,def:1,ability:'Gain 1 Defense. Gain 1 Energy.',cls:'wither'},
  'WI-D03':{name:'Smoke',type:'Defense',cost:2,atk:0,def:4,ability:'Gain 4 Defense.',cls:'wither'},
  'WI-D04':{name:'Endure',type:'Defense',cost:3,atk:0,def:2,ability:'Gain 2 Defense. Heal 2 HP.',cls:'wither'},
  'WI-U01':{name:'Poison',type:'Other',cost:2,atk:0,def:0,ability:'Apply 1 Poison.',cls:'wither'},
  'WI-U02':{name:'Burn',type:'Other',cost:3,atk:0,def:0,ability:'3 damage + 2 Burn to all.',cls:'wither'},
  'WI-U03':{name:'Slow',type:'Other',cost:1,atk:0,def:0,ability:'Draw 1 card. Stun 1 enemy.',cls:'wither'},
  'WI-U04':{name:'Wither',type:'Other',cost:2,atk:0,def:0,ability:'Heal 5 HP. Reduce enemy Attack by 1.',cls:'wither'},
  'SH-A01':{name:'Backstab',type:'Attack',cost:2,atk:4,def:0,ability:'+1 damage if target has no Armor.',cls:'shroud'},
  'SH-A02':{name:'Shadow Bolt',type:'Attack',cost:3,atk:5,def:0,ability:'Target cannot block.',cls:'shroud'},
  'SH-A03':{name:'Silence',type:'Attack',cost:1,atk:3,def:0,ability:'Stun target. It cannot act.',cls:'shroud'},
  'SH-A04':{name:'Vengeance',type:'Attack',cost:3,atk:6,def:0,ability:'Gain 2 Energy.',cls:'shroud'},
  'SH-D01':{name:'Blur',type:'Defense',cost:3,atk:0,def:2,ability:'Ignore the next attack.',cls:'shroud'},
  'SH-D02':{name:'Step',type:'Defense',cost:2,atk:0,def:3,ability:'Gain 3 Defense. Gain 1 Energy.',cls:'shroud'},
  'SH-D03':{name:'Evasion',type:'Defense',cost:1,atk:0,def:1,ability:'Skip enemy attack.',cls:'shroud'},
  'SH-D04':{name:'Phantom',type:'Defense',cost:2,atk:0,def:4,ability:'Ghost form. Ignore next attack.',cls:'shroud'},
  'SH-U01':{name:'Dash',type:'Other',cost:1,atk:0,def:0,ability:'Gain 1 Energy. Skip enemy attack.',cls:'shroud'},
  'SH-U02':{name:'Teleport',type:'Other',cost:2,atk:0,def:0,ability:'Deal 2 damage.',cls:'shroud'},
  'SH-U03':{name:'Hide',type:'Other',cost:2,atk:0,def:0,ability:'Discard 1. Draw 2 cards.',cls:'shroud'},
  'SH-U04':{name:'Assassinate',type:'Other',cost:3,atk:0,def:0,ability:'Deal 8 damage. Unblockable.',cls:'shroud'},
};
const CARD_ORDER = Object.keys(CARDS);

const CLASSES = {
  bastion:{name:'The Bastion',theme:'IMMOVABLE OBJECT',
    desc:'A stalwart of the realm — High Defense, High Block, Low Speed. Durability and crushing area strikes.',
    stats:'Start HP 35 · Start Energy 3',color:'#c9d3df'},
  wither:{name:'The Wither',theme:'ROT AND BURN',
    desc:'Rot and Burn damage with Debuff Control. High AoE that withers enemies in fire and poison.',
    stats:'Start HP 30 · Start Energy 5',color:'#d9a441'},
  shroud:{name:'The Shroud',theme:'SHADOW AND SPEED',
    desc:'High Speed, Evasion and Assassination. Devastating crits and unblockable strikes from the shadows.',
    stats:'Start HP 26 · Start Energy 4',color:'#b79be0'},
};
const START_HP = {bastion:35, wither:30, shroud:26};
const START_ENERGY = {bastion:3, wither:5, shroud:4};
const START_HAND = {bastion:4, wither:4, shroud:4};
const STARTER_DECK = {
  bastion:['BA-A01','BA-A02','BA-A03','BA-A03','BA-D01','BA-D01','BA-D03','BA-U01','BA-U03','BA-U03','BA-U04','BA-U04'],
  wither:['WI-A01','WI-A02','WI-A03','WI-A03','WI-D01','WI-D02','WI-D04','WI-U01','WI-U03','WI-U03','WI-U04','WI-U04'],
  shroud:['SH-A01','SH-A02','SH-A03','SH-A04','SH-D01','SH-D02','SH-D03','SH-U01','SH-U02','SH-U03','SH-U03','SH-U04'],
};

const ENEMY_TYPES = {
  1:[{name:'Goblin',sub:'Forest Raider',sprite:'goblin',hp:20,dmg:3},
     {name:'Giant Rat',sub:'Cellar Pest',sprite:'rat',hp:20,dmg:3}],
  2:[{name:'Vampire Knight',sub:'Bloodsworn',sprite:'vampire',hp:30,dmg:5},
     {name:'Ogre',sub:'Boulder Crusher',sprite:'ogre',hp:30,dmg:5}],
  3:[{name:'Void Warrior',sub:'Hollow Sentinel',sprite:'voidwarrior',hp:40,dmg:8},
     {name:'Cultist',sub:'Void Acolyte',sprite:'cultist',hp:40,dmg:8}],
};
const BOSS = {
  1:{name:'The Gatekeeper',sub:'Keeper of the Gate',sprite:'boss1',hp:80,dmg:15,mechanic:'guard'},
  2:{name:'The Corrupted',sub:'Fount of Rot',sprite:'boss2',hp:80,dmg:15,mechanic:'burst'},
  3:{name:'The Void Lord',sub:'Consumer of Aethelgard',sprite:'boss3',hp:80,dmg:15,mechanic:'regen'},
};

/* ---------------- SVG ART ---------------- */
function enc(s){ return 'data:image/svg+xml,' + encodeURIComponent(s); }

// Per-class card frames (leaves a placeholder image box for the main card image).
function frameSVG(cls){
  if(cls==='wither') return `
  <svg xmlns='http://www.w3.org/2000/svg' width='132' height='186' viewBox='0 0 132 186'>
   <defs><linearGradient id='wG' x1='0' y1='1' x2='1' y2='0'>
    <stop offset='0' stop-color='#3a1a0c'/><stop offset='.5' stop-color='#5a2a12'/><stop offset='1' stop-color='#7a3a14'/></linearGradient></defs>
   <rect x='1.5' y='1.5' width='129' height='183' rx='12' fill='url(#wG)' stroke='#d97b2a' stroke-width='2.5'/>
   <path d='M8 20 L20 8 M112 8 L124 20 M8 166 L20 178 M112 178 L124 166' stroke='#ffb14b' stroke-width='2' opacity='.7'/>
   <path d='M42 170 q8 -22 6 -44 M70 172 q4 -26 8 -50 M94 170 q6 -20 2 -42' stroke='#ff8a3b' stroke-width='1.6' fill='none' opacity='.55'/>
   <rect x='19' y='80' width='94' height='66' rx='5' fill='#141008' stroke='#7a3a14' stroke-width='1'/>
   <text x='66' y='112' font-size='16' fill='#7a3a14' text-anchor='middle' font-family='Georgia'>✦</text>
   <text x='66' y='126' font-size='8.5' fill='#7a3a14' text-anchor='middle' font-family='Georgia'>[ card image ]</text>
  </svg>`;
  if(cls==='shroud') return `
  <svg xmlns='http://www.w3.org/2000/svg' width='132' height='186' viewBox='0 0 132 186'>
   <defs><radialGradient id='sR' cx='.5' cy='.3' r='.85'>
    <stop offset='0' stop-color='#6a54a0'/><stop offset='1' stop-color='#1a1330'/></radialGradient></defs>
   <rect x='1.5' y='1.5' width='129' height='183' rx='12' fill='url(#sR)' stroke='#9b7ad0' stroke-width='2.5'/>
   <path d='M66 12 L74 30 L66 42 L58 30 Z' fill='#9b7ad0' opacity='.85'/>
   <circle cx='66' cy='30' r='20' fill='none' stroke='#c3aef0' stroke-width='1' opacity='.5'/>
   <path d='M66 22 L70 30 L66 36 L62 30 Z' fill='#2a1f44'/>
   <rect x='19' y='80' width='94' height='66' rx='5' fill='#100c1c' stroke='#4a3a6a' stroke-width='1'/>
   <text x='66' y='112' font-size='16' fill='#4a3a6a' text-anchor='middle' font-family='Georgia'>☙</text>
   <text x='66' y='126' font-size='8.5' fill='#4a3a6a' text-anchor='middle' font-family='Georgia'>[ card image ]</text>
  </svg>`;
  // bastion (default)
  return `
  <svg xmlns='http://www.w3.org/2000/svg' width='132' height='186' viewBox='0 0 132 186'>
   <defs><linearGradient id='steelG' x1='0' y1='0' x2='1' y2='1'>
    <stop offset='0' stop-color='#7b818c'/><stop offset='.45' stop-color='#434850'/><stop offset='1' stop-color='#22252a'/></linearGradient>
   <radialGradient id='steelR' cx='.5' cy='.28' r='.85'><stop offset='0' stop-color='#8b919c'/><stop offset='1' stop-color='#2c3036'/></radialGradient></defs>
   <rect x='1.5' y='1.5' width='129' height='183' rx='12' fill='url(#steelR)' stroke='#c9a24b' stroke-width='2.5'/>
   <rect x='8' y='8' width='116' height='170' rx='9' fill='none' stroke='#9aa0aa' stroke-width='1' opacity='.7'/>
   <g fill='#dfe4ea'><circle cx='16' cy='16' r='2.6'/><circle cx='116' cy='16' r='2.6'/><circle cx='16' cy='170' r='2.6'/><circle cx='116' cy='170' r='2.6'/></g>
   <path d='M66 30 L81 36 L81 50 C81 61 73 67 66 71 C59 67 51 61 51 50 L51 36 Z' fill='#3a3f47' stroke='#c9a24b' stroke-width='1.6'/>
   <path d='M66 38 L73 41 L73 50 C73 57 69 60 66 62 C63 60 59 57 59 50 L59 41 Z' fill='#c9a24b' opacity='.85'/>
   <rect x='19' y='80' width='94' height='66' rx='5' fill='#171510' stroke='#5a5240' stroke-width='1'/>
   <text x='66' y='112' font-size='17' fill='#5a5240' text-anchor='middle' font-family='Georgia'>⛊</text>
   <text x='66' y='126' font-size='8.5' fill='#5a5240' text-anchor='middle' font-family='Georgia'>[ card image ]</text>
  </svg>`;
}
function cardBackSVG(cls){
  const acc = {wither:'#d97b2a',shroud:'#9b7ad0',bastion:'#c9a24b'}[cls]||'#c9a24b';
  return `
  <svg xmlns='http://www.w3.org/2000/svg' width='132' height='186' viewBox='0 0 132 186'>
   <defs><linearGradient id='bG' x1='0' y1='0' x2='1' y2='1'>
    <stop offset='0' stop-color='#2a2518'/><stop offset='1' stop-color='#12100a'/></linearGradient></defs>
   <rect x='1.5' y='1.5' width='129' height='183' rx='12' fill='url(#bG)' stroke='${acc}' stroke-width='2.5'/>
   <rect x='10' y='10' width='112' height='166' rx='7' fill='none' stroke='${acc}' stroke-width='1' opacity='.5'/>
   <circle cx='66' cy='66' r='30' fill='none' stroke='${acc}' stroke-width='1.5' opacity='.7'/>
   <path d='M66 40 L74 58 L66 70 L58 58 Z' fill='${acc}' opacity='.85'/>
   <circle cx='66' cy='58' r='7' fill='#12100a' stroke='${acc}' stroke-width='1'/>
   <text x='66' y='120' font-size='11' fill='${acc}' text-anchor='middle' font-family='Georgia' letter-spacing='2'>SHADOWS</text>
   <text x='66' y='136' font-size='8' fill='${acc}' text-anchor='middle' font-family='Georgia' opacity='.7' letter-spacing='2'>OF AETHELGARD</text>
   <g stroke='${acc}' stroke-width='1' opacity='.4'><path d='M20 20 L34 20 M112 20 L98 20 M20 166 L34 166 M112 166 L98 166'/></g>
  </svg>`;
}

// Enemy sprites (low-poly).
function enemySVG(type){
  const S = {
    goblin:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='200' rx='52' ry='9' fill='#000' opacity='.4'/>
     <polygon points='60,190 120,190 112,122 68,122' fill='#4f7a3a'/>
     <polygon points='68,122 112,122 108,98 72,98' fill='#5c8f44'/>
     <polygon points='72,98 108,98 100,70 80,70' fill='#6b9c50'/>
     <polygon points='80,70 100,70 96,58 84,58' fill='#7aaa5c'/>
     <polygon points='72,82 58,72 72,82' fill='#5c8f44'/><polygon points='108,82 122,72 108,82' fill='#5c8f44'/>
     <circle cx='84' cy='86' r='4' fill='#ffcf3b'/><circle cx='96' cy='86' r='4' fill='#ffcf3b'/>
     <polygon points='74,98 106,98 98,104 82,104' fill='#2a3a1a'/>
     <polygon points='68,128 50,158 60,164 74,140' fill='#4f7a3a'/>
     <polygon points='112,128 130,158 120,164 106,140' fill='#4f7a3a'/>
     <ellipse cx='126' cy='160' rx='8' ry='6' fill='#8a7a40'/>
    </svg>`,
    rat:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='190' rx='60' ry='8' fill='#000' opacity='.4'/>
     <ellipse cx='92' cy='150' rx='46' ry='26' fill='#5b4a3d'/>
     <polygon points='52,140 30,132 48,150' fill='#5b4a3d'/>
     <ellipse cx='40' cy='140' rx='15' ry='12' fill='#6b5848'/>
     <circle cx='34' cy='138' r='3' fill='#ffcf3b'/>
     <polygon points='30,132 24,122 38,130' fill='#6b5848'/>
     <path d='M132,150 q20,4 26,-8' stroke='#5b4a3d' stroke-width='5' fill='none'/>
     <polygon points='52,126 62,108 68,126' fill='#6b5848'/>
     <polygon points='60,118 54,108 66,116' fill='#7a6856'/>
     <polygon points='120,156 116,176 128,172' fill='#46382c'/>
     <polygon points='70,172 66,190 78,186' fill='#46382c'/>
     <polygon points='110,172 108,190 120,184' fill='#46382c'/>
    </svg>`,
    vampire:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='200' rx='50' ry='9' fill='#000' opacity='.4'/>
     <polygon points='60,196 120,196 116,120 64,120' fill='#3a3f4b'/>
     <polygon points='64,120 116,120 110,96 70,96' fill='#4a5260'/>
     <path d='M64,100 Q48,142 60,196 L74,196 Q66,142 78,108 Z' fill='#7a1f2b'/>
     <path d='M116,100 Q132,142 120,196 L106,196 Q114,142 102,108 Z' fill='#7a1f2b'/>
     <polygon points='74,96 106,96 100,70 80,70' fill='#565d6b'/>
     <polygon points='80,70 100,70 96,58 84,58' fill='#6b7380'/>
     <rect x='80' y='82' width='20' height='4' fill='#1a1a1a'/>
     <circle cx='86' cy='84' r='2.5' fill='#ff5a5a'/><circle cx='94' cy='84' r='2.5' fill='#ff5a5a'/>
     <polygon points='84,58 96,58 90,44' fill='#c9a24b'/>
     <polygon points='60,128 42,158 52,164 66,138' fill='#4a5260'/>
     <polygon points='120,128 138,158 128,164 114,138' fill='#4a5260'/>
     <rect x='88' y='96' width='4' height='40' fill='#c9a24b'/>
    </svg>`,
    ogre:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='202' rx='60' ry='9' fill='#000' opacity='.4'/>
     <polygon points='52,202 128,202 122,118 58,118' fill='#6b6a5a'/>
     <polygon points='58,118 122,118 116,94 64,94' fill='#7d7c68'/>
     <polygon points='72,94 108,94 102,64 78,64' fill='#7d7c68'/>
     <circle cx='84' cy='78' r='4.5' fill='#ffcf3b'/><circle cx='96' cy='78' r='4.5' fill='#ffcf3b'/>
     <polygon points='76,90 104,90 98,97 82,97' fill='#3a3a2a'/>
     <polygon points='52,126 34,164 48,170 64,138' fill='#6b6a5a'/>
     <polygon points='128,126 146,164 132,170 116,138' fill='#6b6a5a'/>
     <ellipse cx='140' cy='166' rx='11' ry='9' fill='#8a8a76'/>
    </svg>`,
    voidwarrior:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='200' rx='50' ry='9' fill='#000' opacity='.4'/>
     <polygon points='58,200 122,200 116,118 64,118' fill='#3a2f5a'/>
     <polygon points='64,118 116,118 110,94 70,94' fill='#4a3c70'/>
     <polygon points='74,94 106,94 100,68 80,68' fill='#5a4a88'/>
     <circle cx='84' cy='82' r='3' fill='#b088ff'/><circle cx='96' cy='82' r='3' fill='#b088ff'/>
     <polygon points='80,68 100,68 94,54 90,46 86,54' fill='#c9a24b'/>
     <polygon points='58,126 42,158 52,164 66,138' fill='#4a3c70'/>
     <polygon points='122,126 138,158 128,164 114,138' fill='#4a3c70'/>
     <rect x='86' y='120' width='8' height='34' fill='#9b7ad0' opacity='.85'/>
    </svg>`,
    cultist:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='200' rx='46' ry='9' fill='#000' opacity='.4'/>
     <polygon points='60,200 120,200 108,110 72,110' fill='#2c2438'/>
     <polygon points='72,110 108,110 102,90 78,90' fill='#3a3048'/>
     <path d='M78,90 Q90,54 102,90 Q96,72 90,72 Q84,72 78,90 Z' fill='#201a2c'/>
     <circle cx='90' cy='82' r='6' fill='#b088ff'/>
     <circle cx='90' cy='82' r='11' fill='none' stroke='#c3aef0' stroke-width='1' opacity='.5'/>
     <polygon points='60,118 44,150 54,156 66,130' fill='#2c2438'/>
     <polygon points='120,118 136,150 126,156 114,130' fill='#2c2438'/>
     <circle cx='132' cy='150' r='6' fill='#5a3fb0'/>
    </svg>`,
    boss1:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='202' rx='58' ry='9' fill='#000' opacity='.4'/>
     <polygon points='54,202 126,202 120,116 60,116' fill='#4a4238'/>
     <polygon points='60,116 120,116 114,92 66,92' fill='#5c5244'/>
     <polygon points='72,92 108,92 102,64 78,64' fill='#6e6450'/>
     <polygon points='76,64 104,64 100,50 94,58 90,46 86,58 80,50' fill='#c9a24b'/>
     <circle cx='90' cy='48' r='3' fill='#ffcf3b'/>
     <circle cx='86' cy='80' r='3' fill='#ff5a5a'/><circle cx='96' cy='80' r='3' fill='#ff5a5a'/>
     <path d='M80,90 Q90,96 100,90' stroke='#2a241a' stroke-width='3' fill='none'/>
     <path d='M120,118 L136,118 L136,162 Q128,174 120,168 Z' fill='#7a6a40' stroke='#c9a24b' stroke-width='2'/>
     <polygon points='60,124 42,156 52,162 66,134' fill='#5c5244'/>
     <rect x='86' y='120' width='8' height='44' fill='#8a7a40'/>
    </svg>`,
    boss2:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='202' rx='60' ry='9' fill='#000' opacity='.4'/>
     <polygon points='52,202 128,202 120,116 60,116' fill='#4a3a2a'/>
     <polygon points='60,116 120,116 114,92 66,92' fill='#5c4a34'/>
     <path d='M66,92 Q90,60 114,92 Q100,74 90,74 Q80,74 66,92 Z' fill='#3a2c1c'/>
     <polygon points='74,76 80,56 84,74' fill='#2c2016'/><polygon points='90,76 92,50 96,74' fill='#2c2016'/><polygon points='106,76 102,58 100,74' fill='#2c2016'/>
     <circle cx='83' cy='86' r='4' fill='#7ad95a'/><circle cx='97' cy='86' r='4' fill='#7ad95a'/>
     <path d='M82,98 Q90,104 98,98' stroke='#2c2016' stroke-width='3' fill='none'/>
     <polygon points='52,124 34,160 48,166 62,134' fill='#5c4a34'/>
     <polygon points='128,124 146,160 132,166 118,134' fill='#5c4a34'/>
     <ellipse cx='90' cy='150' rx='14' ry='16' fill='#7ad95a' opacity='.35'/>
     <ellipse cx='82' cy='150' rx='6' ry='8' fill='#4a8c3a' opacity='.6'/>
    </svg>`,
    boss3:`<svg xmlns='http://www.w3.org/2000/svg' width='180' height='210' viewBox='0 0 180 210'>
     <ellipse cx='90' cy='202' rx='56' ry='9' fill='#000' opacity='.4'/>
     <path d='M90,120 Q38,142 52,200 Q90,188 128,200 Q142,142 90,120 Z' fill='#241a3a' opacity='.6'/>
     <polygon points='66,120 114,120 108,92 72,92' fill='#2c2244'/>
     <polygon points='76,92 104,92 98,66 82,66' fill='#3a2f5a'/>
     <circle cx='90' cy='78' r='8' fill='#b088ff'/>
     <circle cx='90' cy='78' r='15' fill='none' stroke='#c3aef0' stroke-width='1.5' opacity='.6'/>
     <polygon points='78,60 102,60 96,48 90,56 84,48' fill='#c9a24b'/>
     <polygon points='76,52 80,44 84,52' fill='#f2d67a'/>
     <polygon points='66,100 46,134 56,142 70,118' fill='#3a2f5a'/>
     <polygon points='114,100 134,134 124,142 110,118' fill='#3a2f5a'/>
     <ellipse cx='90' cy='152' rx='17' ry='19' fill='#5a3fb0' opacity='.45'/>
     <ellipse cx='90' cy='152' rx='9' ry='11' fill='#b088ff' opacity='.5'/>
    </svg>`,
  };
  return S[type] || S.goblin;
}

// Hero sprites per class × 3 poses.
function heroSVG(cls, pose){
  const poses = {
    bastion:{
      idle:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='32' ry='6' fill='#000' opacity='.4'/>
       <polygon points='30,120 66,120 62,72 34,72' fill='#4a5260'/>
       <polygon points='34,72 62,72 58,54 38,54' fill='#5c6470'/>
       <polygon points='40,54 56,54 54,38 42,38' fill='#6b7380'/>
       <rect x='44' y='32' width='8' height='8' fill='#c9a24b'/>
       <circle cx='44' cy='46' r='2' fill='#ffcf3b'/><circle cx='52' cy='46' r='2' fill='#ffcf3b'/>
       <path d='M32,74 L22,96 L32,100 L42,80 Z' fill='#8b9099' stroke='#c9a24b' stroke-width='1'/>
       <rect x='64' y='40' width='5' height='38' fill='#c9a24b' stroke='#7a5a1e'/>
       <polygon points='62,40 70,40 66,32' fill='#f2d67a'/>
      </svg>`,
      attack:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='32' ry='6' fill='#000' opacity='.4'/>
       <polygon points='30,120 66,120 62,72 34,72' fill='#4a5260'/>
       <polygon points='34,72 62,72 58,54 38,54' fill='#5c6470'/>
       <polygon points='40,54 56,54 54,38 42,38' fill='#6b7380'/>
       <circle cx='44' cy='46' r='2' fill='#ffcf3b'/><circle cx='52' cy='46' r='2' fill='#ffcf3b'/>
       <path d='M32,74 L24,96 L34,100 L42,80 Z' fill='#8b9099' stroke='#c9a24b' stroke-width='1'/>
       <g transform='rotate(-40 66 48)'><rect x='62' y='24' width='5' height='44' fill='#c9a24b' stroke='#7a5a1e'/><polygon points='60,24 69,24 64,14' fill='#f2d67a'/></g>
      </svg>`,
      hit:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='32' ry='6' fill='#000' opacity='.4'/>
       <polygon points='28,120 66,120 62,72 34,72' fill='#4a3030'/>
       <polygon points='34,72 62,72 58,54 38,54' fill='#5c4a4a'/>
       <polygon points='40,54 56,54 54,38 42,38' fill='#6b5858'/>
       <circle cx='44' cy='46' r='2' fill='#ff5a5a'/><circle cx='52' cy='46' r='2' fill='#ff5a5a'/>
       <path d='M32,74 L24,92 L32,96 L42,78 Z' fill='#8b6060' stroke='#c9a24b' stroke-width='1'/>
       <rect x='64' y='44' width='5' height='34' fill='#c9a24b' stroke='#7a5a1e' transform='rotate(20 66 60)'/>
      </svg>`,
    },
    wither:{
      idle:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='30' ry='6' fill='#000' opacity='.4'/>
       <polygon points='34,120 62,120 58,56 38,56' fill='#5a2a14'/>
       <polygon points='38,56 58,56 54,40 42,40' fill='#6b3418'/>
       <path d='M42,40 Q48,24 54,40 Q50,32 48,32 Q46,32 42,40 Z' fill='#3a1a0c'/>
       <circle cx='44' cy='48' r='3' fill='#ffcf3b'/><circle cx='52' cy='48' r='3' fill='#ff8a3b'/>
       <ellipse cx='48' cy='90' rx='10' ry='12' fill='#ff8a3b' opacity='.4'/>
       <polygon points='34,64 20,88 28,94 40,74' fill='#5a2a14'/>
       <polygon points='62,64 76,86 68,92 56,74' fill='#5a2a14'/>
       <ellipse cx='74' cy='86' rx='6' ry='5' fill='#ffb14b'/>
      </svg>`,
      attack:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='30' ry='6' fill='#000' opacity='.4'/>
       <polygon points='34,120 62,120 58,56 38,56' fill='#5a2a14'/>
       <polygon points='38,56 58,56 54,40 42,40' fill='#6b3418'/>
       <path d='M42,40 Q48,24 54,40 Q50,32 48,32 Q46,32 42,40 Z' fill='#3a1a0c'/>
       <circle cx='44' cy='48' r='3' fill='#ffcf3b'/><circle cx='52' cy='48' r='3' fill='#ff8a3b'/>
       <ellipse cx='48' cy='90' rx='10' ry='12' fill='#ff8a3b' opacity='.4'/>
       <polygon points='34,64 22,86 30,92 42,74' fill='#5a2a14'/>
       <g transform='rotate(-35 70 60)'><ellipse cx='72' cy='52' rx='10' ry='12' fill='#ff8a3b' opacity='.8'/><ellipse cx='72' cy='52' rx='5' ry='7' fill='#ffcf3b'/></g>
      </svg>`,
      hit:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='30' ry='6' fill='#000' opacity='.4'/>
       <polygon points='30,120 62,120 58,56 38,56' fill='#5a2010'/>
       <polygon points='38,56 58,56 54,40 42,40' fill='#6b3016'/>
       <circle cx='44' cy='48' r='3' fill='#ff5a5a'/><circle cx='52' cy='48' r='3' fill='#ff5a5a'/>
       <ellipse cx='48' cy='90' rx='10' ry='12' fill='#ff5a3a' opacity='.5'/>
       <polygon points='34,64 20,86 28,92 40,74' fill='#5a2010'/>
       <polygon points='62,64 78,84 70,90 56,74' fill='#5a2010'/>
      </svg>`,
    },
    shroud:{
      idle:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='28' ry='6' fill='#000' opacity='.4'/>
       <polygon points='36,120 60,120 56,58 40,58' fill='#2c2244'/>
       <polygon points='40,58 56,58 52,42 44,42' fill='#3a2f5a'/>
       <path d='M44,42 Q48,26 54,42 Q50,34 48,34 Q46,34 44,42 Z' fill='#1a1330'/>
       <circle cx='45' cy='50' r='2.5' fill='#b088ff'/><circle cx='51' cy='50' r='2.5' fill='#b088ff'/>
       <ellipse cx='48' cy='88' rx='8' ry='10' fill='#5a3fb0' opacity='.4'/>
       <polygon points='36,66 24,90 32,96 44,76' fill='#2c2244'/>
       <polygon points='60,66 72,90 64,96 52,76' fill='#2c2244'/>
       <rect x='47' y='40' width='2' height='20' fill='#c3aef0' opacity='.7'/>
      </svg>`,
      attack:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='28' ry='6' fill='#000' opacity='.4'/>
       <polygon points='34,120 60,120 56,58 40,58' fill='#2c2244'/>
       <polygon points='40,58 56,58 52,42 44,42' fill='#3a2f5a'/>
       <circle cx='45' cy='50' r='2.5' fill='#b088ff'/><circle cx='51' cy='50' r='2.5' fill='#b088ff'/>
       <polygon points='36,66 24,88 32,94 44,74' fill='#2c2244'/>
       <g transform='rotate(-45 66 56)'><rect x='62' y='32' width='3' height='30' fill='#c3aef0'/><polygon points='61,32 67,32 64,24' fill='#f2d67a'/></g>
      </svg>`,
      hit:`<svg xmlns='http://www.w3.org/2000/svg' width='96' height='128' viewBox='0 0 96 128'>
       <ellipse cx='48' cy='120' rx='28' ry='6' fill='#000' opacity='.4'/>
       <polygon points='30,120 60,120 56,58 40,58' fill='#3a2244'/>
       <polygon points='40,58 56,58 52,42 44,42' fill='#4a3060'/>
       <circle cx='45' cy='50' r='2.5' fill='#ff5a5a'/><circle cx='51' cy='50' r='2.5' fill='#ff5a5a'/>
       <polygon points='36,66 24,88 32,94 44,74' fill='#3a2244'/>
       <polygon points='60,66 74,86 66,92 52,74' fill='#3a2244'/>
      </svg>`,
    },
  };
  return (poses[cls] && poses[cls][pose]) || poses.bastion.idle;
}

/* ---------------- GAME STATE ---------------- */
let G = null;

function newGame(cls){
  const deck = STARTER_DECK[cls].slice();
  const owned = {};
  deck.forEach(id => owned[id] = (owned[id]||0)+1);
  G = {
    cls,
    player:{
      cls, hp:START_HP[cls], maxhp:START_HP[cls],
      energy:START_ENERGY[cls], maxEnergy:START_ENERGY[cls],
      handSize:START_HAND[cls], gold:0,
      deck, owned, discard:[], hand:[],
      shield:0, // permanent defense within a combat
    },
    floor:1, room:1, isBoss:false,
    enemy:null,
    selected:null,
    phase:'idle', // idle | playerTurn | enemyTurn | over
    flags:{ skipEnemy:false, ignoreNext:false, bossTurnSkipped:false },
    busy:false,
  };
}

/* ---------------- UI HELPERS ---------------- */
function showScreen(id){
  document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
  $(id).classList.add('active');
}
function log(msg, cls){
  const box = $('log');
  const d = document.createElement('div');
  d.className = 'line' + (cls? ' '+cls:'');
  d.textContent = msg;
  box.appendChild(d);
  while(box.children.length>5) box.removeChild(box.firstChild);
  const s = $('sideLog');
  const s2 = document.createElement('div');
  s2.textContent = '• ' + msg; s2.style.opacity='.7';
  s.appendChild(s2);
  s.scrollTop = s.scrollHeight;
}
function floatDmg(val, kind){
  const wrap = $('enemyWrap');
  const el = document.createElement('div');
  el.className = 'float-dmg';
  el.textContent = (kind==='player'?'-':'-') + val;
  el.style.color = kind==='player' ? '#ff6a5a' : '#ffe08a';
  el.style.left = (60 + rand(60)) + 'px';
  el.style.top = '40px';
  wrap.appendChild(el);
  setTimeout(()=>el.remove(),900);
}
function glowClass(type){ return type==='Attack'?'glow-attack':type==='Defense'?'glow-defense':'glow-other'; }

function renderCardHTML(card, handIdx){
  const el = document.createElement('div');
  el.className = 'card ' + glowClass(card.type);
  el.style.backgroundImage = `url("${frameSVG(card.cls)}")`;
  el.dataset.idx = handIdx;
  const stat = card.type==='Attack' ? `<span class="atk">⚔ ${card.atk}</span><span></span>`
             : card.type==='Defense' ? `<span></span><span class="defv">🛡 ${card.def}</span>`
             : `<span></span><span></span>`;
  el.innerHTML = `
    <div class="inner">
      <div class="cost">${card.cost}</div>
      <div class="cname">${card.name}</div>
      <div class="ctype">${card.type} · ${CLASSES[card.cls].name.split(' ')[1]||CLASSES[card.cls].name}</div>
      <div class="art">✦</div>
      <div class="stats-line">${stat}</div>
      <div class="effect">${card.ability}</div>
    </div>`;
  el.addEventListener('click', ()=>onCardClick(handIdx));
  return el;
}

function onCardClick(idx){
  if(G.busy || G.phase!=='playerTurn') return;
  const card = G.player.hand[idx];
  if(!card) return;
  const playable = G.player.energy >= card.cost;
  if(G.selected === idx){ G.selected = null; }
  else { G.selected = idx; }
  renderHand();
}

function renderHand(){
  const hand = $('hand');
  hand.innerHTML = '';
  G.player.hand.forEach((card, idx) => {
    const el = renderCardHTML(card, idx);
    const playable = G.player.energy >= card.cost;
    if(!playable) el.classList.add('unplayable');
    if(G.selected === idx) el.classList.add('selected');
    hand.appendChild(el);
  });
  const sel = G.selected != null ? G.player.hand[G.selected] : null;
  $('selHint').textContent = sel ? sel.name : 'Select a card';
  $('playBtn').disabled = !(sel && G.player.energy >= sel.cost);
  $('targetHint').classList.toggle('show', !!(sel && ['Attack','Other'].includes(sel.type) && sel.atk>0));
}

function renderEnemy(){
  const e = G.enemy;
  $('enemySprite').style.backgroundImage = `url("${enc(enemySVG(e.sprite))}")`;
  $('enemyName').textContent = e.name;
  $('enemySub').textContent = (e.boss?'★ ':'' ) + e.sub + ' · Floor ' + G.floor;
  updateEnemyHP();
  renderPips();
}
function updateEnemyHP(){
  const e = G.enemy;
  const pct = clamp(e.hp/e.maxhp*100,0,100);
  $('enemyHpBar').style.width = pct+'%';
  $('enemyHpTxt').textContent = Math.max(0,e.hp) + ' / ' + e.maxhp + (e.guard?(' · 🛡'+e.guard):'');
  if(G.isBoss){ $('bossbarWrap').classList.add('show'); $('bossbarFill').style.width = pct+'%'; }
}
function renderPips(){
  const e = G.enemy;
  const pips = $('statusPips'); pips.innerHTML='';
  if(e.status.burn>0){ const d=document.createElement('div'); d.className='pip burn'; d.textContent='🔥 Burn '+e.status.burn; pips.appendChild(d); }
  if(e.status.poison>0){ const d=document.createElement('div'); d.className='pip poison'; d.textContent='☠ Poison '+e.status.poison; pips.appendChild(d); }
  if(e.status.stun>0){ const d=document.createElement('div'); d.className='pip stun'; d.textContent='⚡ Stun'; pips.appendChild(d); }
}
function renderHUD(){
  $('goldTxt').textContent = G.player.gold;
  $('floorTxt').textContent = ROM[G.floor-1];
  $('roomTxt').textContent = G.isBoss ? 'BOSS' : 'Room ' + G.room;
  const p = G.player;
  $('hpTxt').textContent = Math.max(0,p.hp);
  $('hpBar').style.width = clamp(p.hp/p.maxhp*100,0,100)+'%';
  $('enTxt').textContent = p.energy + '/' + p.maxEnergy;
  $('defTxt').textContent = p.defense + p.shield;
  $('handSizeTxt').textContent = p.handSize;
  $('energyOrb').textContent = p.energy;
  $('deckCount').textContent = p.deck.length;
  $('discardCount').textContent = p.discard.length;
  renderHero();
}
function renderHero(){
  const panel = $('playerPanel');
  let icon = panel.querySelector('.hero-icon');
  if(!icon){
    icon = document.createElement('div');
    icon.className='hero-icon';
    icon.style.cssText='width:96px;height:128px;margin:6px auto 0;background-size:contain;background-repeat:no-repeat;background-position:center;';
    panel.appendChild(icon);
  }
  icon.style.backgroundImage = `url("${enc(heroSVG(G.cls,'idle'))}")`;
}

/* ---------------- COMBAT ---------------- */
function ensureDeck(){
  const p = G.player;
  if(p.deck.length===0 && p.discard.length===0){
    STARTER_DECK[G.cls].forEach(id=>p.deck.push(id));
  }
  if(p.deck.length===0){
    // fallback
    CARD_ORDER.slice(0,4).forEach(id=>p.deck.push(id));
  }
  if(p.discard.length){ p.deck = p.deck.concat(p.discard); p.discard=[]; shuffle(p.deck); }
}
function 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]]; } }

function drawCards(n){
  const p = G.player;
  ensureDeck();
  for(let i=0;i<n;i++){
    if(p.hand.length >= p.handSize + 2) break;
    if(p.deck.length===0) break;
    const id = p.deck.pop();
    p.hand.push(Object.assign({id}, CARDS[id]));
  }
}

function makeEnemy(floor, isBoss){
  let base;
  if(isBoss){ base = BOSS[floor]; }
  else { base = pick(ENEMY_TYPES[floor]); }
  const scale = isBoss?1:(0.9+Math.random()*0.2);
  const hp = Math.round(base.hp*scale);
  const e = {
    name:base.name, sub:base.sub, sprite:base.sprite,
    hp, maxhp:hp, dmg:base.dmg,
    boss:!!isBoss, mechanic:base.mechanic||null,
    defense:0, guard: isBoss&&base.mechanic==='guard'?5:0,
    attackBonus:0,
    status:{burn:0,poison:0,stun:0,silence:0},
  };
  return e;
}

function startCombat(enemy){
  ensureDeck();
  G.enemy = enemy;
  G.player.defense = 0;
  G.player.shield = 0;
  G.player.hand = [];
  G.player.discard = [];
  G.selected = null;
  G.phase = 'playerTurn';
  G.flags = { skipEnemy:false, ignoreNext:false, bossTurnSkipped:false };
  renderEnemy();
  renderHUD();
  beginPlayerTurn();
}

function beginPlayerTurn(){
  const p = G.player;
  if(G.phase==='over') return;
  p.defense = 0;
  p.energy = p.maxEnergy;
  ensureDeck();
  p.hand = [];
  drawCards(p.handSize);
  G.phase = 'playerTurn';
  G.selected = null;
  renderHUD();
  renderHand();
  log(G.isBoss? '★ BOSS: '+G.enemy.name : 'Room '+G.floor+'.'+G.room+' — your turn.', 'big');
}

function resolveStatusAtEnemyStart(){
  const e = G.enemy, p = G.player;
  let total = e.status.burn + e.status.poison;
  if(total>0){
    e.hp -= total;
    log(`DoT deals ${total} (${e.status.burn} Burn / ${e.status.poison} Poison).`, 'dmg');
    floatDmg(total,'damage');
    if(e.hp<=0){ updateEnemyHP(); renderPips(); setTimeout(enemyDefeated,120); return true; }
  }
  if(e.status.stun>0){ e.status.stun--; return true; } // skip attack while stunned
  if(e.status.silence>0) e.status.silence--;
  return false;
}

function enemyTurn(){
  if(G.phase==='over') return;
  G.phase = 'enemyTurn';
  const e = G.enemy, p = G.player;
  // discard hand
  p.discard.push(...p.hand); p.hand = [];
  renderHand();

  // status / stun
  const stunned = resolveStatusAtEnemyStart();
  if(stunned){ updateEnemyHP(); renderPips(); return; }

  // boss mechanics
  if(e.mechanic==='guard'){ e.guard = e.hp>20?5:0; if(e.guard) log(e.name+' braces — blocks 5 damage.', 'mana'); }
  if(e.mechanic==='regen'){ const heal=10; e.hp+=heal; log(e.name+' regenerates '+heal+' HP.', 'heal'); }
  if(e.mechanic==='burst' && p.energy<3){
    log(e.name+' unleashes corruption! (you have <3 Energy)', 'big');
    applyDamageToPlayer(20, {unblockable:true, source:'burst'});
  }

  // skip flags from player cards
  if(G.flags.skipEnemy){ G.flags.skipEnemy=false; log('The enemy cannot act!','mana'); updateEnemyHP(); renderPips(); return; }
  if(G.flags.ignoreNext){ G.flags.ignoreNext=false; log('You blur past the strike!','mana'); updateEnemyHP(); renderPips(); return; }

  // enemy attacks
  enemyAttack();
}

function applyDamageToPlayer(raw, opt){
  opt = opt||{};
  const p = G.player;
  let d = raw;
  if(!opt.unblockable){
    let def = p.defense, sh = p.shield, used=0;
    if(def>0){ const c=Math.min(def,d); d-=c; used+=c; }
    if(d>0 && sh>0){ const c=Math.min(sh,d); d-=c; used+=c; }
    p.defense = def-(def-used<0?0:def-(Math.min(def,raw)));
    p.defense = Math.max(0, def - Math.min(def, raw));
    p.shield = Math.max(0, sh - Math.max(0, raw-(def)));
  }
  p.hp = Math.max(0, p.hp - d);
  if(d>0){ floatDmg(d,'player'); log((opt.source==='burst'?'Corruption ':'Enemy deals ')+d+' damage.','dmg'); }
  renderHero(); renderHUD();
  if(p.hp<=0){ gameOver(); }
}

function enemyAttack(){
  const e = G.enemy, p = G.player;
  e._anim = true;
  $('enemySprite').classList.add('attack');
  setTimeout(()=>$('enemySprite').classList.remove('attack'),320);
  const raw = e.dmg + (e.attackBonus||0);
  log(e.name+' attacks for '+raw+'.');
  applyDamageToPlayer(raw, {});
  renderEnemy();
}

function damageEnemy(amount, opt){
  opt = opt||{};
  const e = G.enemy, p = G.player;
  let d = amount;
  if(!opt.unblockable){
    if(e.guard){ d = Math.max(0, d - e.guard); }
    d = Math.max(0, d - (e.defense||0));
  }
  e.hp -= d;
  e._hurt = true;
  $('enemySprite').classList.add('hurt');
  setTimeout(()=>$('enemySprite').classList.remove('hurt'),320);
  if(d>0){ floatDmg(d,'damage'); log('You deal '+d+' damage.','dmg'); }
  else { log('Attack blocked!'); }
  updateEnemyHP();
  if(e.hp<=0){ setTimeout(enemyDefeated,140); }
  return d;
}

function playCard(idx){
  if(G.busy || G.phase!=='playerTurn') return;
  const card = G.player.hand[idx];
  if(!card) return;
  if(G.player.energy < card.cost){ log('Not enough energy.'); return; }
  G.busy = true;
  G.player.energy -= card.cost;
  G.player.hand.splice(idx,1);
  G.selected = null;
  const cls = card.cls;

  // resolve by type/id
  const e = G.enemy;
  switch(card.type){
    case 'Attack': {
      let atk = card.atk;
      let unblock = false;
      if(card.id==='SH-A01') atk += 1; // backstab: no armor
      if(card.id==='SH-A02') unblock = true; // cannot block
      if(card.id==='SH-U04'){ unblock = true; atk = 8; }
      if(card.id==='BA-A02'){ damageEnemy(atk,{unblock}); log('Earthquake rocks all enemies!'); }
      else if(card.id==='WI-A02'){ damageEnemy(atk,{unblock}); applyBurn(3); }
      else if(card.id==='WI-A04'){ damageEnemy(atk); window.__pyreNext = (window.__pyreNext||0)+1; }
      else {
        damageEnemy(atk, {unblock});
        if(card.id==='WI-A01') applyBurn(1);
        if(card.id==='WI-A03') applyBurn(1);
        if(card.id==='SH-A03'){ applyStun(1); log('Target silenced, cannot act.'); }
      }
      break;
    }
    case 'Defense': {
      const gain = card.def;
      if(card.id==='BA-D02'){ G.player.shield += gain; log('Iron Skin: +'+gain+' permanent Defense.'); pDiscard(); }
      else if(card.id==='BA-D04'){ G.player.defense += gain; applyStun(1); }
      else if(card.id==='WI-D04'){ G.player.defense += gain; healPlayer(2); }
      else if(card.id==='WI-U04'/*handled elsewhere*/){ G.player.defense += gain; }
      else { G.player.defense += gain; }
      if(card.id==='WI-D02'||card.id==='SH-D02'||card.id==='SH-A04'||card.id==='BA-U03') gainEnergy(1 + (card.id==='SH-A04'?1:0) + (card.id==='SH-A04'?0:0));
      if(card.id==='SH-D01'||card.id==='SH-D04') G.flags.ignoreNext = true;
      if(card.id==='SH-D03') G.flags.skipEnemy = true;
      log('Gain '+gain+' Defense.');
      break;
    }
    case 'Other': {
      const cid = card.id;
      if(cid==='BA-A01' && G.player.hp===G.player.maxhp) drawCards(1);
      if(cid==='BA-U01'){ drawCards(1); if(G.player.hand.length>5) pDiscard(); }
      if(cid==='BA-U02') healPlayer(6);
      if(cid==='BA-U03'){ drawCards(1); gainEnergy(1); }
      if(cid==='BA-U04'){ G.player.gold += 5; log('+5 Gold.','gold'); }
      if(cid==='WI-U01') applyPoison(1);
      if(cid==='WI-U02'){ damageEnemy(3); applyBurn(2); }
      if(cid==='WI-U03'){ drawCards(1); applyStun(1); }
      if(cid==='WI-U04'){ healPlayer(5); if(G.enemy.attackBonus>0) G.enemy.attackBonus=Math.max(0,G.enemy.attackBonus-1); else G.enemy.dmg=Math.max(1,G.enemy.dmg-1); log('Enemy Attack reduced.'); }
      if(cid==='SH-A01'){ /*handled in attack*/ }
      if(cid==='SH-U01'){ gainEnergy(1); G.flags.skipEnemy = true; }
      if(cid==='SH-U02'){ damageEnemy(2); }
      if(cid==='SH-U03'){ pDiscard(); drawCards(2); }
      if(cid==='SH-U04'){ damageEnemy(8,{unblock:true}); }
      break;
    }
  }

  renderHUD();
  renderHand();
  renderPips();
  setTimeout(()=>{
    G.busy = false;
    if(G.phase==='playerTurn' && G.player.hand.length===0 && G.player.energy===0){
      // auto offer end turn if out of resources
    }
    if(G.phase==='over') return;
  },120);
}

// simple status helpers
function applyBurn(n){ G.enemy.status.burn += n; log('Target catches fire ('+G.enemy.status.burn+' Burn).','burn'); }
function applyPoison(n){ G.enemy.status.poison += n; log('Target is poisoned ('+G.enemy.status.poison+' Poison).','heal'); }
function applyStun(n){ G.enemy.status.stun += n; }
function healPlayer(n){ G.player.hp = Math.min(G.player.maxhp, G.player.hp+n); log('Healed '+n+' HP.','heal'); renderHero(); }
function gainEnergy(n){ G.player.energy += n; log('+'+n+' Energy.','mana'); }
function pDiscard(){ if(G.player.hand.length){ G.player.discard.push(G.player.hand.shift()); } }

function endTurn(){
  if(G.busy || G.phase!=='playerTurn') return;
  G.busy = true;
  // Pyre extra damage next turn
  const pyre = window.__pyreNext||0;
  if(pyre>0){ window.__pyreNext=0; G.enemy.hp -= pyre; log('Pyre scorches for '+pyre+'.','dmg'); floatDmg(pyre,'damage'); updateEnemyHP(); renderPips(); if(G.enemy.hp<=0){ setTimeout(enemyDefeated,140); G.busy=false; return; } }
  enemyTurn();
  G.busy = false;
}

function enemyDefeated(){
  if(G.phase==='over') return;
  const e = G.enemy;
  log(e.name+' defeated!','big');
  // reward
  const gold = e.boss?15:5;
  G.player.gold += gold;
  log('+'+gold+' Gold.','gold');
  G.phase = 'over';
  setTimeout(()=> showReward(e.boss), 500);
}

/* ---------------- PROGRESSION ---------------- */
function nextRoom(){
  G.room++;
  if(G.room>3){
    // boss of floor
    G.isBoss = true;
    G.room = 1;
    const boss = makeEnemy(G.floor, true);
    startCombat(boss);
    return;
  }
  const enemy = makeEnemy(G.floor, false);
  startCombat(enemy);
}

function showReward(boss){
  const modal = $('modal');
  if(boss && G.floor>=3){
    // WIN
    modal.className = 'modal victory';
    modal.innerHTML = `
      <h2>VICTORY</h2>
      <div class="sub">The Void Lord falls. Aethelgard is reclaimed.</div>
      <div class="center-msg">
        You descended through three floors, shattered nine dark rooms,<br>
        and cast down the last of the Void Lords.<br><br>
        <b style="color:var(--gold-bright)">Gold amassed: ${G.player.gold}</b><br>
        Class: ${CLASSES[G.cls].name}
      </div>
      <div style="text-align:center;margin-top:14px">
        <button class="btn primary" id="menuBtn">RETURN TO THE GATE</button>
      </div>`;
    $('menuBtn').onclick = ()=>{ closeModal(); goMenu(); };
    showModal();
    return;
  }
  // rest screen after boss (floor cleared) or regular room (shop)
  if(boss){
    // floor cleared -> rest + continue
    const nextFloor = G.floor+1;
    modal.className = 'modal';
    modal.innerHTML = `
      <h2>FLOOR ${ROM[G.floor-1]} CLEARED</h2>
      <div class="sub">The ${G.enemy.name} is no more.</div>
      <div class="rest-options">
        <div class="rest-card">
          <div style="width:150px;height:120px;margin:0 auto 8px;background:url("${heroSVG(G.cls,'idle')}") center/contain no-repeat"></div>
          <div style="font-size:13px;margin-bottom:8px">Sanctuary Rest</div>
          <div class="price">Free</div>
          <div class="owned">Full heal + mana</div>
          <button class="btn primary small" id="restBtn" style="margin-top:8px">REST</button>
        </div>
        <div class="rest-card">
          <div style="width:150px;height:120px;margin:0 auto 8px;background:url("${cardBackSVG(G.cls)}") center/contain no-repeat"></div>
          <div style="font-size:13px;margin-bottom:8px">Buy a card</div>
          <div class="price">1g + scaling</div>
          <div class="owned">See shop below</div>
          <button class="btn shop-btn small" id="restShop" style="margin-top:8px">OPEN SHOP</button>
        </div>
      </div>`;
    $('restBtn').onclick = ()=>{ G.player.hp = G.player.maxhp; G.player.energy = G.player.maxEnergy; closeModal(); nextFloor2(); };
    $('restShop').onclick = ()=>{ closeModal(); openShop(false); };
    showModal();
    return;
  }
  // regular room -> shop
  openShop(false);
}

function nextFloor2(){
  G.floor++;
  G.isBoss = false;
  if(G.floor>3){ victory(); return; }
  G.room = 1;
  const enemy = makeEnemy(G.floor, false);
  startCombat(enemy);
}

function victory(){
  const modal = $('modal');
  modal.className='modal victory';
  modal.innerHTML = `
    <h2>THE VOID IS BANISHED</h2>
    <div class="sub">Aethelgard is free.</div>
    <div class="center-msg">You cleared all three floors.<br>The shadows recede. Light returns.</div>
    <div style="text-align:center;margin-top:14px"><button class="btn primary" id="menuBtn2">MENU</button></div>`;
  $('menuBtn2').onclick=()=>{closeModal();goMenu();};
  showModal();
}

/* ---------------- SHOP ---------------- */
function shopOffers(){
  const offers = [];
  const seen = {};
  let guard = 0;
  let tries = 0;
  while(offers.length<3 && tries<200){
    tries++;
    const id = pick(CARD_ORDER);
    if(seen[id]>=3){ guard++; if(guard>40) break; continue; }
    const owned = G.player.owned[id]||0;
    if(owned>=5) continue;
    seen[id]=(seen[id]||0)+1;
    const price = 1 + owned; // cost scaling: Base + count
    offers.push({id, price, owned, maxed:owned>=5});
  }
  return offers;
}

function openShop(fromRest){
  const offers = shopOffers();
  const modal = $('modal');
  modal.className='modal';
  let html = `
    <h2>◈ SHADOW MARKET ◈</h2>
    <div class="sub">Gold: <b id="shopGold" style="color:var(--gold-bright)">${G.player.gold}</b> · Buy cards to grow your deck.</div>
    <div class="shop-grid" id="shopGrid"></div>
    <div style="text-align:center;margin-top:18px;display:flex;gap:10px;justify-content:center">
      <button class="btn primary" id="buyBtn">BUY (selected)</button>
      <button class="btn" id="continueBtn">CONTINUE ▸</button>
    </div>
    <div class="footer-note">Cost scales with copies owned. Max 5 copies per card.</div>`;
  modal.innerHTML = html;
  const grid = $('shopGrid');
  let selOffer = null;
  offers.forEach((o,i)=>{
    const card = CARDS[o.id];
    const el = document.createElement('div');
    el.className='shop-card';
    el.style.cursor='pointer';
    el.style.border = '2px solid #3a3322'; el.style.borderRadius='12px'; el.style.padding='8px';
    el.style.background='rgba(0,0,0,.25)';
    el.innerHTML = `
      <div style="width:120px;height:170px;margin:0 auto;background:url("${frameSVG(card.cls)}") center/contain no-repeat">
        <div style="text-align:center;margin-top:2px;font-size:10px;color:#d8c9a0">${card.name}</div>
      </div>
      <div class="owned">${CLASSES[card.cls].name.split(' ')[1]||''} · ${card.type}</div>
      <div class="price">🪙 ${o.price}g</div>
      <div class="owned">Owned: ${o.owned}/5</div>`;
    el.onclick = ()=>{
      if(o.maxed) return;
      document.querySelectorAll('.shop-card').forEach(c=>c.style.borderColor='#3a3322');
      el.style.borderColor='var(--gold-bright)';
      selOffer = o;
    };
    grid.appendChild(el);
  });
  $('buyBtn').onclick = ()=>{
    if(!selOffer){ return; }
    if(G.player.gold < selOffer.price){ log('Not enough gold.','dmg'); return; }
    if(selOffer.maxed) return;
    G.player.gold -= selOffer.price;
    G.player.owned[selOffer.id] = (G.player.owned[selOffer.id]||0)+1;
    G.player.deck.push(selOffer.id);
    log('Bought '+CARDS[selOffer.id].name+'.','gold');
    // refresh shop
    openShop(true);
  };
  $('continueBtn').onclick = ()=>{
    closeModal();
    if(G.isBoss){ showReward(true); } else { nextRoom(); }
  };
  showModal();
}

/* ---------------- MODAL ---------------- */
function showModal(){ $('modalBg').classList.add('show'); }
function closeModal(){ $('modalBg').classList.remove('show'); }

/* ---------------- GAME OVER / MENU ---------------- */
function gameOver(){
  if(G.phase==='over') return;
  G.phase='over';
  const modal = $('modal');
  modal.className='modal gameover';
  modal.innerHTML = `
    <h2>YOU DIED</h2>
    <div class="sub">The Void claims another champion.</div>
    <div class="center-msg">Fallen on Floor ${ROM[G.floor-1]}, Room ${G.isBoss?'BOSS':G.room}.<br>Gold: ${G.player.gold} — ${CLASSES[G.cls].name}</div>
    <div style="text-align:center;margin-top:14px"><button class="btn primary" id="retryBtn">TRY AGAIN</button></div>`;
  $('retryBtn').onclick = ()=>{ closeModal(); goMenu(); };
  showModal();
}

function goMenu(){
  showScreen('menu');
  buildMenu();
}

function buildMenu(){
  const grid = $('classGrid');
  grid.innerHTML='';
  Object.keys(CLASSES).forEach(cls=>{
    const c = CLASSES[cls];
    const el = document.createElement('div');
    el.className='class-card '+cls;
    el.style.borderColor = c.color+'55';
    el.innerHTML = `
      <h3 style="color:${c.color}">${c.name}</h3>
      <div class="theme">${c.theme}</div>
      <p>${c.desc}</p>
      <div class="stats"><span>${c.stats}</span></div>`;
    el.onclick = ()=> startRun(cls);
    grid.appendChild(el);
  });
}

function startRun(cls){
  newGame(cls);
  showScreen('game');
  $('sideLog').innerHTML='';
  $('log').innerHTML='';
  renderHUD();
  renderHand();
  // intro first room
  const enemy = makeEnemy(1, false);
  startCombat(enemy);
}

/* ---------------- WIRING ---------------- */
function init(){
  buildMenu();
  $('playBtn').onclick = ()=>{
    if(G && G.selected!=null){ playCard(G.selected); }
  };
  $('endTurnBtn').onclick = endTurn;
  $('shopBtn').onclick = ()=>{ if(G) openShop(false); };
  $('enemyWrap').addEventListener('click', ()=>{
    if(G && G.selected!=null && G.phase==='playerTurn'){
      const card = G.player.hand[G.selected];
      if(card && (card.type==='Attack'||(card.type==='Other'&&card.atk>0))) playCard(G.selected);
    }
  });
  document.addEventListener('keydown', (e)=>{
    if(!G) return;
    if(e.code==='Enter'){ endTurn(); }
    if(e.code==='Space'){ e.preventDefault(); if(G.selected!=null) playCard(G.selected); }
    if(e.key>='1'&&e.key<='9'){ const i=parseInt(e.key)-1; if(G.player.hand[i]!=null){ G.selected=(G.selected===i)?null:i; renderHand(); } }
  });
}
init();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/Ornith-1.5-35B-A3B","provider":"brigid","persona":"full","sessionId":"fd6a9e7c-e017-4378-b025-493ca85b11f4","tokensIn":7118655,"tokensOut":128059,"tokensTotal":7246714,"cost":0,"turns":90,"toolCalls":89,"failedToolCalls":1,"timestamp":"2026-08-24T00:56:29.541Z"} -->