Magic Loot 2D — ULTIMATE
by LaserLegend642036 lines102.8 KB
🎮 Magic Loot 2D — ULTIMATE MEGA EDITION
باشه، همه چیز رو حداکثر میکنم:
· 💎 ۱۲۵ Rarity
· 🪄 ۱۲۵ Wand
· 🛡️ ۱۲۵ Armor
· 🌍 ۱۰ World
· 👾 ۱۲ نوع Monster متفاوت
· 🎨 Customize کامل Face (رنگ پوست، چشم، دهان، کلاه، اکسسوری)
· ⚖️ Upgrade جدید (Luck, Crit, Regen, XP, Cooldown, HP, Speed, TapPower, Damage, Rebirth Bonus)
کد خیلی طولانیه، ولی کامل و آمادهست. فقط کافیه Copy کنی.
---
کد کامل (فایل index.html)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="theme-color" content="#05070f">
<title>Magic Loot 2D — ULTIMATE</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
html,body{width:100%;height:100%;overflow:hidden;background:#05070f;font-family:'Trebuchet MS',system-ui,sans-serif;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;color:#fff;}
canvas{display:block;position:fixed;inset:0;}
#topbar{position:fixed;top:0;left:0;right:0;height:46px;display:flex;align-items:center;justify-content:space-around;background:linear-gradient(180deg,rgba(10,6,20,0.96),rgba(10,6,20,0.7));backdrop-filter:blur(12px);border-bottom:1px solid rgba(168,85,247,0.35);padding:0 4px;z-index:10;font-size:11px;pointer-events:none;box-shadow:0 4px 20px rgba(0,0,0,0.6);}
#topbar .stat{display:flex;flex-direction:column;align-items:center;line-height:1.1;min-width:0;flex:1;}
#topbar .lbl{opacity:0.55;font-size:8px;text-transform:uppercase;letter-spacing:1.5px;font-weight:700;}
#topbar .val{font-weight:900;font-size:12px;font-family:monospace;text-shadow:0 0 8px currentColor;}
#bottom{position:fixed;bottom:0;left:0;right:0;height:62px;display:flex;gap:5px;padding:8px 10px;background:linear-gradient(to top,rgba(5,7,15,0.98),rgba(5,7,15,0.75),rgba(5,7,15,0));z-index:10;pointer-events:none;}
#bottom .btn{flex:1;border:2px solid rgba(255,255,255,0.15);border-radius:12px;background:linear-gradient(145deg,rgba(120,90,220,0.9),rgba(70,40,140,0.9));color:#fff;font-weight:900;font-size:9px;cursor:pointer;pointer-events:auto;box-shadow:0 4px 0 rgba(30,10,60,0.9);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;padding:3px;letter-spacing:0.3px;}
#bottom .btn .bi{font-size:15px;line-height:1;}
#bottom .btn:active{transform:translateY(3px);box-shadow:0 1px 0 rgba(30,10,60,0.9);}
#bottom #btnStage{background:linear-gradient(145deg,rgba(168,85,247,0.95),rgba(126,34,206,0.95));box-shadow:0 4px 0 rgba(60,20,120,0.9);}
#bottom #btnSell{background:linear-gradient(145deg,rgba(74,222,128,0.95),rgba(22,163,74,0.95));box-shadow:0 4px 0 rgba(15,80,40,0.9);}
#bottom #btnInv{background:linear-gradient(145deg,rgba(59,130,246,0.95),rgba(29,78,216,0.95));box-shadow:0 4px 0 rgba(15,40,100,0.9);}
#bottom #btnTop{background:linear-gradient(145deg,rgba(245,158,11,0.95),rgba(180,83,9,0.95));box-shadow:0 4px 0 rgba(100,50,5,0.9);}
#bottom #btnCustom{background:linear-gradient(145deg,rgba(236,72,153,0.95),rgba(157,23,77,0.95));box-shadow:0 4px 0 rgba(80,10,40,0.9);}
#hpWrap{position:fixed;bottom:70px;left:50%;transform:translateX(-50%);width:72%;max-width:360px;pointer-events:none;z-index:11;}
#hpLabel{font-size:10px;text-align:center;margin-bottom:3px;font-weight:900;letter-spacing:2px;color:#fda4af;text-shadow:0 0 6px #ef4444;}
#hpBar{height:12px;background:rgba(0,0,0,0.7);border-radius:7px;overflow:hidden;border:2px solid rgba(239,68,68,0.5);}
#hpFill{height:100%;background:linear-gradient(90deg,#dc2626,#ef4444,#f59e0b);border-radius:5px;transition:width 0.2s;width:100%;}
#stageBanner{position:fixed;top:52px;left:50%;transform:translateX(-50%);background:linear-gradient(145deg,rgba(30,15,60,0.96),rgba(15,8,30,0.96));border:2px solid #a855f7;padding:5px 14px;border-radius:9px;font-size:11px;font-weight:900;letter-spacing:2px;z-index:12;box-shadow:0 0 18px rgba(168,85,247,0.5);pointer-events:none;text-align:center;min-width:170px;}
#stageBanner .sub{font-size:9px;opacity:0.7;letter-spacing:1px;font-weight:600;margin-top:1px;}
#powerDisplay{position:fixed;top:52px;left:6px;background:linear-gradient(145deg,rgba(30,15,60,0.96),rgba(15,8,30,0.96));border:2px solid #fcd34d;padding:5px 9px;border-radius:9px;font-size:9px;font-weight:900;letter-spacing:1px;z-index:12;box-shadow:0 0 14px rgba(252,211,77,0.5);pointer-events:none;}
#powerDisplay .pv{color:#fcd34d;font-size:12px;font-family:monospace;text-shadow:0 0 8px #fcd34d;}
#tapBtn{position:fixed;right:14px;bottom:135px;width:88px;height:88px;border-radius:50%;background:radial-gradient(circle at 35% 30%, #fcd34d, #f59e0b 50%, #b45309);border:4px solid #fff8dc;box-shadow:0 0 24px rgba(252,211,77,0.85), inset 0 -6px 16px rgba(0,0,0,0.4), inset 0 6px 16px rgba(255,255,255,0.3);z-index:13;cursor:pointer;touch-action:none;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#3d1e00;font-weight:900;transition:transform 0.08s;user-select:none;-webkit-user-select:none;}
#tapBtn:active{transform:scale(0.9);}
#tapBtn .tb1{font-size:11px;letter-spacing:1px;line-height:1;}
#tapBtn .tb2{font-size:18px;margin-top:3px;line-height:1;}
#tapBtn .tb3{font-size:8px;margin-top:2px;opacity:0.8;line-height:1;}
#tapBtn.onTrain{background:radial-gradient(circle at 35% 30%, #a7f3d0, #22c55e 50%, #15803d);box-shadow:0 0 32px rgba(34,197,94,1), inset 0 -6px 16px rgba(0,0,0,0.4), inset 0 6px 16px rgba(255,255,255,0.3);color:#0a2818;animation:pulseTrain 1.2s infinite;}
@keyframes pulseTrain{0%,100%{box-shadow:0 0 24px rgba(34,197,94,0.9), inset 0 -6px 16px rgba(0,0,0,0.4), inset 0 6px 16px rgba(255,255,255,0.3);}50%{box-shadow:0 0 48px rgba(34,197,94,1), inset 0 -6px 16px rgba(0,0,0,0.4), inset 0 6px 16px rgba(255,255,255,0.3);}}
#tapBtn .tmult{position:absolute;top:-6px;right:-6px;background:#ef4444;border:2px solid #fff;border-radius:50%;min-width:28px;height:28px;padding:0 4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:900;color:#fff;box-shadow:0 0 12px rgba(239,68,68,0.9);}
#tapBtn.onTrain .tmult{background:#f59e0b;}
#powerFloat{position:fixed;right:50px;bottom:180px;font-size:16px;font-weight:900;color:#fcd34d;pointer-events:none;z-index:14;text-shadow:0 0 10px #fcd34d,0 0 20px #f59e0b;opacity:0;}
#powerFloat.show{animation:floatUp 0.9s ease-out;}
@keyframes floatUp{0%{opacity:0;transform:translateY(0) scale(0.5);}30%{opacity:1;transform:translateY(-20px) scale(1.2);}100%{opacity:0;transform:translateY(-70px) scale(1);}}
#panel{position:fixed;inset:0;z-index:20;background:linear-gradient(180deg,rgba(5,7,15,0.98),rgba(20,10,40,0.98));backdrop-filter:blur(14px);display:flex;flex-direction:column;}
#panel.hidden{display:none;}
#panelTitle{position:absolute;top:14px;left:50%;transform:translateX(-50%);font-size:14px;font-weight:900;letter-spacing:3px;background:linear-gradient(135deg,#a855f7,#ec4899,#f59e0b);-webkit-background-clip:text;background-clip:text;color:transparent;text-transform:uppercase;}
#panelHeader{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:2px solid rgba(168,85,247,0.3);padding-top:56px;background:linear-gradient(180deg,rgba(30,10,60,0.9),transparent);}
#panelTabs{display:flex;gap:5px;overflow-x:auto;flex:1;}
#panelTabs::-webkit-scrollbar{display:none;}
#panelTabs .tab{border:none;background:rgba(120,90,220,0.15);color:#fff;padding:8px 12px;border-radius:8px;font-size:10px;font-weight:800;cursor:pointer;white-space:nowrap;letter-spacing:0.5px;border:1px solid rgba(120,90,220,0.2);}
#panelTabs .tab.active{background:linear-gradient(135deg,rgba(168,85,247,0.9),rgba(236,72,153,0.9));border-color:rgba(255,255,255,0.3);}
#closeBtn{background:linear-gradient(145deg,rgba(239,68,68,0.9),rgba(180,20,40,0.9));border:2px solid rgba(255,255,255,0.15);color:#fff;width:36px;height:36px;border-radius:10px;font-size:16px;font-weight:900;cursor:pointer;margin-left:8px;}
#panelBody{flex:1;overflow-y:auto;padding:14px;padding-bottom:100px;}
#panelBody::-webkit-scrollbar{width:6px;}
#panelBody::-webkit-scrollbar-thumb{background:linear-gradient(180deg,#a855f7,#ec4899);border-radius:3px;}
.card{background:linear-gradient(145deg,rgba(30,15,60,0.9),rgba(15,8,30,0.9));border:1px solid rgba(168,85,247,0.25);border-radius:14px;padding:12px;margin-bottom:10px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 14px rgba(0,0,0,0.5);}
.card .icon{font-size:26px;flex-shrink:0;}
.card .info{flex:1;min-width:0;}
.card .title{font-weight:900;font-size:13px;margin-bottom:2px;}
.card .sub{font-size:10.5px;opacity:0.7;margin-bottom:4px;line-height:1.4;}
.card .bar{height:6px;background:rgba(0,0,0,0.5);border-radius:3px;overflow:hidden;}
.card .bar>div{height:100%;background:linear-gradient(90deg,#a855f7,#ec4899,#f59e0b);border-radius:3px;transition:width 0.3s;}
.card .right{display:flex;flex-direction:column;align-items:flex-end;gap:6px;}
.card .price{font-size:11px;color:#fcd34d;font-weight:900;font-family:monospace;}
.card .buy{border:2px solid rgba(255,255,255,0.2);border-radius:10px;padding:8px 12px;background:linear-gradient(145deg,#a855f7,#7c3aed);color:#fff;font-weight:900;font-size:11px;cursor:pointer;white-space:nowrap;min-width:70px;letter-spacing:0.5px;box-shadow:0 3px 0 rgba(60,20,120,0.9);}
.card .buy:active:not(:disabled){transform:translateY(2px);box-shadow:0 1px 0 rgba(60,20,120,0.9);}
.card .buy:disabled{background:rgba(60,60,60,0.5);box-shadow:none;cursor:not-allowed;}
.card .buy.owned{background:linear-gradient(145deg,#22c55e,#15803d);box-shadow:0 3px 0 rgba(10,60,25,0.9);}
.card .buy.active{background:linear-gradient(145deg,#22c55e,#15803d);}
.card .buy.select{background:linear-gradient(145deg,#3b82f6,#1d4ed8);box-shadow:0 3px 0 rgba(15,40,100,0.9);}
.sectionTitle{font-size:10px;text-transform:uppercase;letter-spacing:2.5px;opacity:0.55;margin:16px 0 8px;font-weight:900;padding-left:8px;border-left:3px solid #a855f7;}
.rarityRow{display:flex;align-items:center;gap:8px;padding:9px 12px;background:linear-gradient(90deg,rgba(30,15,60,0.7),rgba(15,8,30,0.5));border-radius:10px;margin-bottom:6px;border-left:4px solid var(--rc,#888);}
.rarityRow .rn{flex:1;font-weight:900;font-size:11px;color:var(--rc,#fff);}
.rarityRow .rp{font-size:9px;opacity:0.75;min-width:56px;text-align:right;font-family:monospace;}
.rarityRow .rc{font-weight:900;font-size:11px;min-width:40px;text-align:right;font-family:monospace;}
.rarityRow .sellOne{background:linear-gradient(145deg,rgba(34,197,94,0.4),rgba(21,128,61,0.4));border:1px solid rgba(74,222,128,0.5);color:#86efac;padding:5px 9px;border-radius:8px;font-size:10px;font-weight:900;cursor:pointer;}
.rarityRow .sellOne:disabled{opacity:0.25;cursor:not-allowed;}
.bigBtn{width:100%;padding:15px;border:2px solid rgba(255,255,255,0.2);border-radius:14px;background:linear-gradient(135deg,#ec4899,#f59e0b);color:#fff;font-weight:900;font-size:13px;cursor:pointer;box-shadow:0 5px 0 rgba(100,30,80,0.9);margin-bottom:10px;letter-spacing:1.5px;}
.bigBtn:active:not(:disabled){transform:translateY(3px);box-shadow:0 2px 0 rgba(100,30,80,0.9);}
.bigBtn:disabled{background:linear-gradient(135deg,rgba(80,80,80,0.5),rgba(50,50,50,0.5));box-shadow:none;cursor:not-allowed;}
.bigBtn.green{background:linear-gradient(135deg,#16a34a,#22c55e);box-shadow:0 5px 0 rgba(10,60,25,0.9);}
.hint{font-size:11px;opacity:0.55;text-align:center;padding:12px;line-height:1.6;font-style:italic;}
.stageGrid{display:grid;grid-template-columns:repeat(5,1fr);gap:7px;margin:8px 0;}
.stageCell{aspect-ratio:1;border-radius:10px;background:linear-gradient(145deg,rgba(30,15,60,0.8),rgba(15,8,30,0.8));border:2px solid rgba(168,85,247,0.3);display:flex;flex-direction:column;align-items:center;justify-content:center;font-weight:900;font-size:15px;cursor:pointer;position:relative;}
.stageCell:active{transform:scale(0.95);}
.stageCell.locked{opacity:0.25;cursor:not-allowed;}
.stageCell.current{border-color:#fcd34d;box-shadow:0 0 16px rgba(252,211,77,0.7);}
.stageCell.boss{border-color:#ef4444;background:linear-gradient(145deg,rgba(80,10,20,0.9),rgba(40,5,10,0.9));}
.stageCell.completed{background:linear-gradient(145deg,rgba(20,80,40,0.7),rgba(10,40,20,0.7));border-color:#22c55e;}
.stageCell .bossIco{position:absolute;top:1px;right:4px;font-size:10px;}
.stageCell .checkIco{position:absolute;bottom:1px;right:4px;font-size:9px;color:#4ade80;}
.worldRow{display:flex;align-items:center;gap:12px;padding:14px;background:linear-gradient(145deg,rgba(30,15,60,0.9),rgba(15,8,30,0.9));border-radius:14px;margin-bottom:10px;border:2px solid var(--wc,#666);}
.worldRow .wIco{font-size:36px;filter:drop-shadow(0 0 10px var(--wc));}
.worldRow .wI{flex:1;}
.worldRow .wT{font-weight:900;font-size:14px;margin-bottom:4px;}
.worldRow .wS{font-size:10.5px;opacity:0.75;line-height:1.6;}
.worldRow .wB{border:2px solid rgba(255,255,255,0.2);border-radius:10px;padding:10px 14px;background:linear-gradient(145deg,#a855f7,#7c3aed);color:#fff;font-weight:900;font-size:11px;cursor:pointer;box-shadow:0 3px 0 rgba(60,20,120,0.9);}
.worldRow .wB:disabled{background:rgba(60,60,60,0.5);box-shadow:none;cursor:not-allowed;}
.leaderRow{display:flex;align-items:center;gap:10px;padding:10px 12px;background:linear-gradient(90deg,rgba(30,15,60,0.7),rgba(15,8,30,0.5));border-radius:10px;margin-bottom:5px;font-size:12px;}
.leaderRow.top1{background:linear-gradient(90deg,rgba(252,211,77,0.25),rgba(120,70,10,0.15));border-left:4px solid #fcd34d;}
.leaderRow.top2{background:linear-gradient(90deg,rgba(203,213,225,0.2),rgba(100,110,120,0.1));border-left:4px solid #cbd5e1;}
.leaderRow.top3{background:linear-gradient(90deg,rgba(180,83,9,0.25),rgba(80,40,5,0.15));border-left:4px solid #b45309;}
.leaderRow.you{background:linear-gradient(90deg,rgba(168,85,247,0.4),rgba(236,72,153,0.3));border:2px solid #a855f7;}
.leaderRow .lrank{font-weight:900;min-width:32px;font-size:13px;font-family:monospace;}
#toast{position:fixed;top:100px;left:50%;transform:translateX(-50%) translateY(-10px);background:linear-gradient(145deg,rgba(30,15,60,0.98),rgba(15,8,30,0.98));border:2px solid rgba(168,85,247,0.5);padding:10px 18px;border-radius:12px;font-size:12px;font-weight:900;z-index:30;opacity:0;transition:all 0.3s;pointer-events:none;max-width:85%;text-align:center;box-shadow:0 8px 24px rgba(0,0,0,0.7);}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
#bossWarn{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);font-size:32px;font-weight:900;letter-spacing:6px;color:#ef4444;text-shadow:0 0 20px #ef4444,0 0 40px #ef4444;z-index:15;pointer-events:none;opacity:0;text-transform:uppercase;}
#bossWarn.show{animation:bossWarn 2.5s ease-out forwards;}
@keyframes bossWarn{0%{opacity:0;transform:translate(-50%,-50%) scale(0.5);}20%{opacity:1;transform:translate(-50%,-50%) scale(1.1);}40%{transform:translate(-50%,-50%) scale(1);}80%{opacity:1;}100%{opacity:0;transform:translate(-50%,-50%) scale(1.3);}}
#loading{position:fixed;inset:0;background:#05070f;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;transition:opacity 0.6s;}
#loading.hide{opacity:0;pointer-events:none;}
#loading h1{font-size:26px;font-weight:900;letter-spacing:2px;background:linear-gradient(135deg,#a855f7,#ec4899,#f59e0b);-webkit-background-clip:text;background-clip:text;color:transparent;}
#loading .sub{font-size:12px;opacity:0.5;letter-spacing:3px;}
#loadingBar{width:60%;max-width:300px;height:8px;background:rgba(255,255,255,0.1);border-radius:4px;overflow:hidden;}
#loadingFill{height:100%;background:linear-gradient(90deg,#a855f7,#ec4899);width:0;transition:width 0.3s;}
#nameModal{position:fixed;inset:0;z-index:80;background:rgba(5,7,15,0.95);display:flex;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(10px);}
#nameModal.hidden{display:none;}
#nameModal .nm{background:linear-gradient(145deg,rgba(30,15,60,0.98),rgba(15,8,30,0.98));border:2px solid #a855f7;border-radius:16px;padding:24px;max-width:340px;width:100%;box-shadow:0 0 40px rgba(168,85,247,0.5);}
#nameModal h2{font-size:18px;font-weight:900;letter-spacing:2px;text-align:center;margin-bottom:6px;background:linear-gradient(135deg,#a855f7,#ec4899);-webkit-background-clip:text;background-clip:text;color:transparent;}
#nameModal p{font-size:11px;opacity:0.65;text-align:center;margin-bottom:16px;line-height:1.5;}
#nameModal input{width:100%;padding:14px;border-radius:10px;border:2px solid rgba(168,85,247,0.4);background:rgba(0,0,0,0.4);color:#fff;font-size:14px;font-weight:900;text-align:center;letter-spacing:1px;outline:none;font-family:inherit;}
#nameModal input:focus{border-color:#a855f7;box-shadow:0 0 20px rgba(168,85,247,0.5);}
#nameModal button{width:100%;padding:14px;margin-top:14px;border:2px solid rgba(255,255,255,0.2);border-radius:10px;background:linear-gradient(135deg,#a855f7,#ec4899);color:#fff;font-weight:900;font-size:14px;cursor:pointer;letter-spacing:2px;box-shadow:0 4px 0 rgba(80,20,100,0.9);}
#nameModal button:active{transform:translateY(2px);box-shadow:0 2px 0 rgba(80,20,100,0.9);}
/* Customization grid */
.customGrid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:8px 0;}
.customCell{padding:10px 6px;border-radius:10px;background:linear-gradient(145deg,rgba(30,15,60,0.7),rgba(15,8,30,0.7));border:2px solid rgba(168,85,247,0.3);text-align:center;cursor:pointer;font-size:22px;line-height:1;transition:transform 0.1s;}
.customCell:active{transform:scale(0.92);}
.customCell.selected{border-color:#fcd34d;box-shadow:0 0 16px rgba(252,211,77,0.7);}
.customCell .cname{font-size:8px;margin-top:3px;opacity:0.7;font-weight:700;display:block;}
.colorSwatch{aspect-ratio:1;border-radius:50%;border:2px solid rgba(255,255,255,0.3);cursor:pointer;transition:transform 0.1s;}
.colorSwatch:active{transform:scale(0.9);}
.colorSwatch.selected{border-color:#fcd34d;border-width:3px;box-shadow:0 0 16px rgba(252,211,77,0.9);}
@media (min-width:768px){#topbar{height:56px;}#topbar .val{font-size:14px;}#bottom{height:76px;}#bottom .btn{font-size:12px;}.card .title{font-size:15px;}#tapBtn{width:110px;height:110px;right:20px;bottom:170px;}}
</style>
</head>
<body>
<div id="loading">
<h1>MAGIC LOOT 2D</h1>
<div class="sub">ULTIMATE EDITION</div>
<div id="loadingBar"><div id="loadingFill"></div></div>
<div class="sub" style="font-size:10px">Tap to start</div>
</div>
<div id="nameModal" class="hidden">
<div class="nm">
<h2>ENTER YOUR NAME</h2>
<p>Will appear on the leaderboard</p>
<input type="text" id="playerNameInput" maxlength="12" placeholder="YourName" autocomplete="off">
<button id="saveNameBtn">START PLAYING</button>
</div>
</div>
<canvas id="c"></canvas>
<div id="topbar">
<div class="stat"><span class="lbl">World</span><span class="val" id="tbWorld" style="color:#22d3ee">1</span></div>
<div class="stat"><span class="lbl">Stage</span><span class="val" id="tbStage" style="color:#a855f7">1</span></div>
<div class="stat"><span class="lbl">Rebirth</span><span class="val" id="tbRebirth" style="color:#ec4899">0</span></div>
<div class="stat"><span class="lbl">Coins</span><span class="val" id="tbCoins" style="color:#fcd34d">0</span></div>
<div class="stat"><span class="lbl">Kills</span><span class="val" id="tbKills" style="color:#ef4444">0/0</span></div>
</div>
<div id="stageBanner">
<div id="sbTitle">🏠 SAFE ZONE</div>
<div class="sub" id="sbSub">Forest Realm</div>
</div>
<div id="powerDisplay">
<div style="opacity:0.7;font-size:8px;letter-spacing:1.5px;">⚔️ YOUR POWER</div>
<div class="pv" id="pPower">0</div>
</div>
<div id="tapBtn">
<div class="tmult" id="tmult">x1</div>
<div class="tb1">TRAIN</div>
<div class="tb2">👆</div>
<div class="tb3" id="tapLabel">TAP</div>
</div>
<div id="powerFloat">+0</div>
<div id="hpWrap">
<div id="hpLabel">❤️ HP</div>
<div id="hpBar"><div id="hpFill"></div></div>
</div>
<div id="bottom">
<button class="btn" id="btnShop"><span class="bi">🛒</span>SHOP</button>
<button class="btn" id="btnCustom"><span class="bi">🎨</span>FACE</button>
<button class="btn" id="btnStage"><span class="bi">📍</span>MAP</button>
<button class="btn" id="btnInv"><span class="bi">🎒</span>BAG</button>
<button class="btn" id="btnSell"><span class="bi">💰</span>SELL</button>
<button class="btn" id="btnTop"><span class="bi">🏆</span>TOP</button>
</div>
<div id="panel" class="hidden">
<div id="panelTitle">MENU</div>
<div id="panelHeader">
<div id="panelTabs"></div>
<button id="closeBtn">✕</button>
</div>
<div id="panelBody"></div>
</div>
<div id="toast"></div>
<div id="bossWarn">⚠️ BOSS ⚠️</div>
<script>
(function(){
'use strict';
/* ============================================================
UTILITIES
============================================================ */
const SUF=['','K','M','B','T','Qa','Qi','Sx','Sp','Oc','No','Dc','Ud','Dd','Td','Qad','Qid','Sxd','Spd','Ocd','Nod','Vg','Uvg','Dvg','Tvg','Qavg','Sxg','Spg','Ocg','Nog','Tg','Utg','Dtg','Ttg','Qatg','Qitg','Sxtg','Sptg','Octg','Notg','Vgtg'];
function fmt(n){
if(!isFinite(n))return '∞';
if(n<0)return '-'+fmt(-n);
if(n<1000)return Math.floor(n).toString();
let i=0;while(n>=1000&&i<SUF.length-1){n/=1000;i++;}
return(n<10?n.toFixed(2):n<100?n.toFixed(1):Math.floor(n).toString())+SUF[i];
}
const rnd=(a,b)=>a+Math.random()*(b-a);
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
function hexA(hex,a){const n=parseInt(hex.slice(1),16);return `rgba(${(n>>16)&255},${(n>>8)&255},${n&255},${a})`;}
function el(tag,cls,html){const e=document.createElement(tag);if(cls)e.className=cls;if(html!==undefined)e.innerHTML=html;return e;}
function toast(msg,color){const t=document.getElementById('toast');t.textContent=msg;t.style.color=color||'#fff';t.style.borderColor=color?hexA(color,0.6):'rgba(168,85,247,0.5)';t.classList.add('show');clearTimeout(toast._t);toast._t=setTimeout(()=>t.classList.remove('show'),2400);}
function showBossWarn(){const w=document.getElementById('bossWarn');w.classList.remove('show');void w.offsetWidth;w.classList.add('show');}
/* ============================================================
125 RARITIES
============================================================ */
function genRarityNames(){
const base=['Common','Uncommon','Rare','Epic','Legendary','Mythical','Divine','Cosmic','Celestial','Eternal','Void','Abyssal','Phantom','Infernal','Astral','Transcendent','Primordial','Godly','Reality','Infinity','Omnipotent','Singularity','Quantum','Nebula','Galactic','Universal','Multiversal','Omniversal','Absolute','Ultimate','Supreme','Transcendence','Ascension','Apotheosis','Divinity','Ethereal','Immortal','Timeless','Boundless','Endless','Infinite','Unbound','Limitless','Formless','Nameless','Chaos','Order','Balance','Harmony','Discord','Genesis','Apocalypse','Ragnarok','Armageddon','Nirvana','Samadhi','Zenith','Apex','Pinnacle','Vertex','Crown','Throne','Sovereign','Monarch','Emperor','Kaiser','Tsar','Sultan','Overlord','Tyrant','Dictator','Warlord','Champion','Hero','Legend','Myth','Fable','Saga','Epic','Chronicle','Legendarium'];
const pre=['Ancient','Elder','Prime','Alpha','Beta','Gamma','Delta','Omega','Sigma','Theta','Lambda','Mu','Nu','Xi','Omicron','Pi','Rho','Tau','Upsilon','Phi','Chi','Psi'];
const names=[];
for(let i=0;i<125;i++){
if(i<base.length)names.push(base[i]);
else{
const p=pre[i%pre.length];
const b=base[i%base.length];
names.push(p+' '+b);
}
}
return names;
}
const RARITY_NAMES=genRarityNames();
function genColor(i){
/* Generate 125 distinct colors via HSL */
const hue=(i*37)%360;
const sat=70+(i%3)*10;
const lit=45+(i%4)*8;
return `hsl(${hue}, ${sat}%, ${lit}%)`;
}
const RARITIES=RARITY_NAMES.map((name,i)=>{
const tier=i+1;
const w=Math.max(0.000001, 1500/Math.pow(2.5,i));
const pts=Math.pow(8.5,i)*5;
return {id:'r'+i, name, color:genColor(i), w, pts, tier};
});
/* ============================================================
10 WORLDS
============================================================ */
const WORLD_NAMES=['Forest Realm','Desert Abyss','Frozen Void','Volcano Core','Celestial Heaven','Twilight Realm','Oceanic Depths','Chaos Rift','Time Nexus','Omniverse'];
const WORLD_ICONS=['🌲','🏜️','❄️','🌋','✨','🌆','🌊','🌀','⏳','🌌'];
const WORLD_COLORS=['#4ade80','#f59e0b','#22d3ee','#ef4444','#fde047','#a78bfa','#38bdf8','#f43f5e','#fbbf24','#c084fc'];
const WORLD_BGS=[[20,40,28],[50,40,20],[18,30,48],[45,18,15],[30,25,55],[40,25,55],[15,35,55],[55,15,25],[35,30,20],[20,15,50]];
const WORLD_BG2=[[10,25,18],[30,22,10],[8,18,30],[28,8,6],[15,12,30],[20,10,35],[8,20,35],[30,5,15],[20,15,8],[10,5,30]];
const WORLD_HUES=[120,35,195,15,55,270,205,340,45,280];
const WORLDS=WORLD_NAMES.map((name,i)=>{
const w=i+1;
const monsterHp=2000*Math.pow(20,i);
const monsterMult=Math.pow(20,i);
const bossHp=monsterHp*100;
const bossMult=monsterMult*40;
const itemPool={};
RARITIES.forEach((r,ri)=>{
const idx=Math.min(RARITY_NAMES.length-1, ri+i*5);
itemPool[r.id]=[RARITY_NAMES[idx]+' Item', RARITY_NAMES[idx]+' Shard', name.split(' ')[0]+' '+RARITY_NAMES[idx]];
});
return {
id:w, name, icon:WORLD_ICONS[i], color:WORLD_COLORS[i],
unlockRebirth: i*5,
bg:WORLD_BGS[i], bg2:WORLD_BG2[i],
monsterHue:WORLD_HUES[i],
monsterHp, monsterMult, bossHp, bossMult,
itemPool,
};
});
const STAGES_PER_WORLD=25;
const MONSTERS_PER_STAGE=8;
/* ============================================================
MONSTER TYPES — 12 variants
============================================================ */
const MONSTER_TYPES=[
{name:'Slime', icon:'🟢', r:22, color:0},
{name:'Bat', icon:'🦇', r:20, color:0},
{name:'Goblin', icon:'👺', r:24, color:0},
{name:'Ghost', icon:'👻', r:22, color:20},
{name:'Wolf', icon:'🐺', r:26, color:0},
{name:'Snake', icon:'🐍', r:22, color:-30},
{name:'Spider', icon:'🕷️', r:24, color:0},
{name:'Troll', icon:'🧌', r:30, color:-60},
{name:'Elemental',icon:'🔥', r:26, color:-30},
{name:'Skeleton',icon:'💀', r:24, color:0},
{name:'Wraith', icon:'👤', r:26, color:40},
{name:'Dragon', icon:'🐉', r:32, color:-50},
];
/* ============================================================
TRAINING GROUNDS
============================================================ */
const TRAIN_GROUNDS=[
{id:0,name:'Village Dojo',icon:'🏘️',rebirth:0,color:'#4ade80',powerMult:2,desc:'x2 Power'},
{id:1,name:"Knight's Arena",icon:'⚔️',rebirth:2,color:'#3b82f6',powerMult:5,desc:'x5 Power'},
{id:2,name:'Mage Tower',icon:'🗼',rebirth:5,color:'#a855f7',powerMult:15,desc:'x15 Power'},
{id:3,name:'Dragon Sanctuary',icon:'🐲',rebirth:10,color:'#f59e0b',powerMult:50,desc:'x50 Power'},
{id:4,name:'Void Rift',icon:'🌀',rebirth:18,color:'#8b5cf6',powerMult:200,desc:'x200 Power'},
{id:5,name:'Celestial Temple',icon:'🛕',rebirth:28,color:'#22d3ee',powerMult:1000,desc:'x1000 Power'},
{id:6,name:'Eternal Nexus',icon:'💫',rebirth:40,color:'#fde047',powerMult:10000,desc:'x10000 Power'},
];
/* ============================================================
125 CHARACTERS
============================================================ */
function genCharNames(){
const base=['Novice','Apprentice','Squire','Knight','Mage','Wizard','Archmage','Sorcerer','Warlock','Sage','Prophet','Demigod','God','Titan','Cosmic','Eternal','Void','Astral','Transcendent','Infinite','Supreme','Absolute','Omega','Genesis','Creator','Overlord','Primordial','Divine','Celestial','Voidwalker','Shadow','Light','Chaos','Order','Balance'];
const names=[];
for(let i=0;i<125;i++){
names.push((base[i%base.length])+(i>=base.length?' '+Math.floor(i/base.length+1):''));
}
return names;
}
const CHAR_MULTS=[];
for(let i=0;i<125;i++)CHAR_MULTS.push(Math.pow(3,i));
const CHAR_NAMES=genCharNames();
const CHAR_ICONS=['🧙','🧝','🧛','🥷','🧚','🧞','🧜','🧟','🧠','👑','⚔️','🛡️','💫','🌟','🌌','🌠','🔮','💎','✨','⚡','🔥','❄️','🌊','🌪️','🌋','💠','🌈','🦸','🦹','🧑🚀','🧑🎤','🧑🎨','🧑🔬','🧑💼'];
const CHARACTERS=CHAR_MULTS.map((mult,i)=>({
id:i, name:CHAR_NAMES[i],
icon:CHAR_ICONS[i%CHAR_ICONS.length],
mult,
cost:i===0?0:Math.floor(Math.pow(mult,1.15)*5000),
hue:(i*29)%360
}));
/* ============================================================
125 WANDS
============================================================ */
function genWandNames(){
const base=['Wooden','Apprentice','Arcane','Crystal','Flame','Thunder','Void','Cosmic','Divine','Eternal','Godslayer','Genesis','Aether','Infinity','Nebula','Supernova','Quantum','Singularity','Omnipotent','Celestial King','Reality Warp','Time Lord','Multiverse','Dimension','Nexus','Primordial','Origin','Absolute','Godhead','Creation','Destruction','Apocalypse','Ragnarok','Olympus','Asgard','Chaos','Order','Balance','Endgame','Final','Alpha','Omega','Prime','Ancient','Elder','Titan','Colossus','Behemoth','Leviathan','Kraken','Phoenix','Dragon','Wyrm','Serpent','Griffin','Chimera','Hydra','Basilisk','Manticore','Sphinx','Pegasus','Unicorn','Fairy','Demon','Angel','Archangel','Seraph','Cherub','Nephilim','Watcher','Guardian','Sentinel','Protector','Defender'];
const suffixes=['Wand','Staff','Rod','Scepter','Focus','Catalyst'];
const names=[];
for(let i=0;i<125;i++){
const b=base[i%base.length];
const s=suffixes[i%suffixes.length];
names.push(b+' '+s+(i>=base.length?' '+(Math.floor(i/base.length)+1):''));
}
return names;
}
const WAND_NAMES=genWandNames();
const WAND_ICONS=['🪄','🪄','✨','💎','🔥','⚡','🌌','🌟','👑','💫','⚜️','🌠','💠','∞','🌌','💥','🔬','🌑','🔱','👑','🌀','⏳','🌐','🔮','🌐','🌱','🌌','⚡','🌟','✨','💥','☄️','🌋','⚡','⚡','😈','⚖️','🔮','🏁','🌟','🌌','💠','⚜️','🌟','⭐','🌟','💫','✨'];
const WANDS=WAND_NAMES.map((name,i)=>{
const dmg=Math.pow(4,i);
const cost=i===0?0:Math.floor(Math.pow(3.6,i)*1000);
const hue=(i*11)%360;
const color=`hsl(${hue}, 75%, 60%)`;
return {id:i,name,icon:WAND_ICONS[i%WAND_ICONS.length],dmg,cost,color};
});
/* ============================================================
125 ARMORS
============================================================ */
function genArmorNames(){
const base=['Cloth','Leather','Chain','Steel','Mystic','Dragon','Void','Celestial','Divine','Eternal','Titan','God','Cosmic','Infinity','Nebula','Supernova','Quantum','Singularity','Omnipotent','Celestial King','Reality Warp','Time Lord','Multiverse','Dimension','Nexus','Primordial','Origin','Absolute','Godhead','Creation','Destruction','Apocalypse','Ragnarok','Olympus','Asgard','Chaos','Order','Balance','Endgame','Final','Alpha','Omega','Prime','Ancient','Elder','Titan','Colossus','Behemoth','Leviathan','Kraken','Phoenix','Dragon','Wyrm','Serpent','Griffin','Chimera','Hydra','Basilisk'];
const suffixes=['Robe','Vest','Mail','Plate','Armor','Aegis','Guard','Shell','Cloak','Shield'];
const names=[];
for(let i=0;i<125;i++){
const b=base[i%base.length];
const s=suffixes[i%suffixes.length];
names.push(b+' '+s+(i>=base.length?' '+(Math.floor(i/base.length)+1):''));
}
return names;
}
const ARMOR_NAMES=genArmorNames();
const ARMOR_ICONS=['👕','🥋','🛡️','🛡️','🔮','🐉','🌑','✨','⚜️','💠','🗿','⚡','🌌','🛡️','🌌','💥','🔬','🌑','🔱','👑','🌀','⏳','🌐','🔮','🌐','🌱','🌌','⚡','🌟','✨','💥','☄️','🌋','⚡','⚡','😈','⚖️','🔮','🏁','🌟','🌌','💠','⚜️','🌟','⭐','🌟','💫','✨'];
const ARMORS=ARMOR_NAMES.map((name,i)=>{
const hp=Math.pow(4.2,i)*100;
const cost=i===0?0:Math.floor(Math.pow(3.7,i)*1500);
const hue=(i*11+180)%360;
const color=`hsl(${hue}, 70%, 55%)`;
return {id:i,name,icon:ARMOR_ICONS[i%ARMOR_ICONS.length],hp,cost,color};
});
/* ============================================================
STATE
============================================================ */
const SAVE_KEY='magicloot_v11';
function defaultState(){
return {
px:0,py:0,hp:100,
coins:0,rebirths:0,playtime:0,
xp:0,level:1,
power:0,
inventory:RARITIES.reduce((o,r)=>(o[r.id]=0,o),{}),
upgrades:{luck:0,crit:0,regen:0,xp:0,cooldown:0,hp:0,speed:0,tapPower:0,damage:0,rebirthBonus:0},
currentChar:0,ownedChars:[0],
currentWand:0,ownedWands:[0],
currentArmor:0,ownedArmors:[0],
currentWorld:1,currentStage:1,
unlockedWorlds:[1],
stagesCompleted:{},
currentTrain:-1,
unlockedTrain:[0],
stats:{opened:0,kills:0,bossKills:0,deaths:0,taps:0},
playerName:'',
inSafeZone:true,
/* Customization */
face:{
skin:'#f5c6a0',
skinPattern:'solid',
eyes:'normal',
eyeColor:'#222',
mouth:'smile',
hair:'short',
hairColor:'#3a2010',
hat:'none',
hatColor:'#a855f7',
accessory:'none',
},
};
}
let S=defaultState();
function save(){try{localStorage.setItem(SAVE_KEY,JSON.stringify(S));}catch(e){}}
function load(){try{const r=localStorage.getItem(SAVE_KEY);if(!r)return;const o=JSON.parse(r);const d=defaultState();S=Object.assign(d,o);S.inventory=Object.assign(d.inventory,o.inventory||{});S.upgrades=Object.assign(d.upgrades,o.upgrades||{});S.face=Object.assign(d.face,o.face||{});if(!Array.isArray(S.ownedChars))S.ownedChars=[0];if(!Array.isArray(S.ownedWands))S.ownedWands=[0];if(!Array.isArray(S.ownedArmors))S.ownedArmors=[0];if(!Array.isArray(S.unlockedWorlds))S.unlockedWorlds=[1];if(!Array.isArray(S.unlockedTrain))S.unlockedTrain=[0];if(typeof S.stagesCompleted!=='object'||!S.stagesCompleted)S.stagesCompleted={};}catch(e){}}
load();
/* ============================================================
DERIVED — ALL upgrades affect gameplay
============================================================ */
function charMult(){return (CHARACTERS[S.currentChar]||CHARACTERS[0]).mult;}
function rebirthBonus(){return (1+S.rebirths*0.5)*(1+S.upgrades.rebirthBonus*0.10);}
function trainMult(){if(S.currentTrain<0)return 1;const t=TRAIN_GROUNDS.find(x=>x.id===S.currentTrain);return t?t.powerMult:1;}
function wandDmgValue(){return WANDS[S.currentWand].dmg*(1+S.upgrades.damage*0.15);}
function armorHpValue(){return ARMORS[S.currentArmor].hp*(1+S.upgrades.hp*0.15);}
function tapPowerGain(){
const wand=wandDmgValue();
const armorBonus=1+armorHpValue()/1000;
const lvlBonus=1+S.level*0.05;
const tapUpg=1+S.upgrades.tapPower*0.20;
return wand*armorBonus*lvlBonus*rebirthBonus()*charMult()*trainMult()*tapUpg;
}
function playerPower(){
const wand=wandDmgValue();
const armorBonus=1+armorHpValue()/1000;
const lvl=1+S.level*0.05;
return S.power + wand*armorBonus*lvl*rebirthBonus()*charMult();
}
function playerDmg(){return playerPower();}
function maxHp(){return armorHpValue()+100+S.rebirths*1000;}
function regenRate(){return 5+S.upgrades.regen*3+maxHp()*0.005;}
function luckValue(){return (1+S.upgrades.luck*0.3)*(1+S.rebirths*0.1);}
function xpMult(){return 1+S.upgrades.xp*0.15;}
function attackCooldown(){return Math.max(0.08, 0.5-S.level*0.005-S.upgrades.cooldown*0.003);}
function critChance(){return Math.min(0.75, S.upgrades.crit*0.02);}
function critMultiplier(){return 2+S.upgrades.crit*0.05;}
function playerSpeed(){return 260+S.level*3+S.upgrades.speed*8;}
function rebirthCost(){return Math.floor(5e6*Math.pow(3.5,S.rebirths));}
function rarityChances(luckMul){
const ws=RARITIES.map(r=>r.id==='r0'?r.w:r.w*luckMul);
const t=ws.reduce((a,b)=>a+b,0);
return ws.map(w=>w/t);
}
function currentChances(){return rarityChances(luckValue());}
function currentWorld(){return WORLDS.find(w=>w.id===S.currentWorld)||WORLDS[0];}
function xpForLevel(lv){return Math.floor(50*Math.pow(1.35,lv-1));}
function addXp(a){S.xp+=a*xpMult();while(S.xp>=xpForLevel(S.level)){S.xp-=xpForLevel(S.level);S.level++;burst(0,0,'#a855f7',40,320);beep(1046,0.15,'triangle',0.09);beep(1568,0.15,'triangle',0.09);toast('⭐ Level '+S.level+'!','#a855f7');}}
function stageKey(w,s){return w+'-'+s;}
function isStageCompleted(w,s){return !!S.stagesCompleted[stageKey(w,s)];}
function isStageUnlocked(w,s){if(s===1)return true;return isStageCompleted(w,s-1);}
function isWorldUnlocked(w){return S.rebirths>=w.unlockRebirth;}
/* ============================================================
GAME STATE
============================================================ */
let G={
monsters:[],projectiles:[],damageTexts:[],particles:[],
enemiesKilled:0,enemiesNeeded:0,stageCleared:false,
safeZone:true,bossAlive:false,boss:{},
attackCd:0,time:0,
playerX:0,playerY:0,
nearbyTrain:-1,
};
/* ============================================================
CANVAS
============================================================ */
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
let W=0,H=0,DPR=1;
function resize(){
DPR=Math.min(window.devicePixelRatio||1,2);
W=window.innerWidth;H=window.innerHeight;
canvas.width=Math.round(W*DPR);canvas.height=Math.round(H*DPR);
canvas.style.width=W+'px';canvas.style.height=H+'px';
ctx.setTransform(DPR,0,0,DPR,0,0);
}
resize();
window.addEventListener('resize',resize);
/* ============================================================
AUDIO
============================================================ */
let audioCtx=null;
function ensureAudio(){if(!audioCtx){try{audioCtx=new(window.AudioContext||window.webkitAudioContext)();}catch(e){}}if(audioCtx&&audioCtx.state==='suspended')audioCtx.resume();}
function beep(freq,dur,type,vol){ensureAudio();if(!audioCtx)return;try{const o=audioCtx.createOscillator();const g=audioCtx.createGain();o.type=type||'sine';o.frequency.value=freq;g.gain.value=vol||0.05;g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+dur);o.connect(g);g.connect(audioCtx.destination);o.start();o.stop(audioCtx.currentTime+dur);}catch(e){}}
function jingle(id){
const tier=parseInt(id.replace('r',''));
const count=Math.min(8, 3+Math.floor(tier/15));
const base=523+tier*3;
for(let i=0;i<count;i++){
setTimeout(()=>beep(base*Math.pow(1.25,i),0.16,'triangle',0.06),i*50);
}
}
function burst(x,y,color,count,speed){
for(let i=0;i<count;i++){
const a=Math.random()*Math.PI*2;
const s=speed*(0.25+Math.random()*0.9);
const ml=0.5+Math.random()*0.6;
G.particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-100,life:ml,maxLife:ml,color,size:2+Math.random()*3,shape:Math.random()<0.3?'star':'circle',rot:Math.random()*6.28,rotSpeed:rnd(-8,8)});
}
}
/* ============================================================
SAFE ZONE
============================================================ */
function enterSafeZone(){
G.safeZone=true;
G.monsters=[];G.projectiles=[];G.bossAlive=false;
S.currentStage=1;
G.playerX=W*0.5;G.playerY=H*0.72;
updateStageBanner();
document.getElementById('sbTitle').textContent='🏠 SAFE ZONE';
document.getElementById('sbSub').textContent=currentWorld().name;
save();
}
function getTrainPositions(){
const cx=W*0.5;
const baseY=H*0.42;
return TRAIN_GROUNDS.map((t,i)=>{
const n=TRAIN_GROUNDS.length;
const angle=-Math.PI*0.95+(i/(n-1))*Math.PI*0.9;
const radius=Math.min(W,H)*0.40;
return {id:t.id, x:cx+Math.cos(angle)*radius, y:baseY+Math.sin(angle)*radius*0.6};
});
}
/* ============================================================
STAGE
============================================================ */
function startStage(worldId,stageNum){
S.currentWorld=worldId;
S.currentStage=stageNum;
G.safeZone=false;
G.monsters=[];G.projectiles=[];G.damageTexts=[];
G.enemiesKilled=0;G.stageCleared=false;G.bossAlive=false;G.attackCd=0;
const isBossStage=(stageNum===STAGES_PER_WORLD);
G.enemiesNeeded=isBossStage?1:MONSTERS_PER_STAGE;
const w=currentWorld();
const stageMult=1+(stageNum-1)*0.35;
G.playerX=W*0.15;G.playerY=H*0.5;
if(isBossStage){
G.bossAlive=true;
G.boss={
x:W*0.7,y:H*0.5,r:70,
hp:w.bossHp*stageMult*0.5,
maxHp:w.bossHp*stageMult*0.5,
power:w.bossHp*stageMult*0.5,
bob:0,hitT:0,hue:w.monsterHue-40,
mult:w.bossMult,isBoss:true,
alive:true,dieT:0,attackCd:1.5,
typeIdx:11,
};
showBossWarn();
beep(120,0.5,'sawtooth',0.12);
setTimeout(()=>beep(90,0.6,'sawtooth',0.12),200);
} else {
for(let i=0;i<MONSTERS_PER_STAGE;i++)spawnStageMonster(i,stageMult);
}
updateStageBanner();
save();
}
function spawnStageMonster(i,stageMult){
const w=currentWorld();
const isElite=Math.random()<0.12;
const baseHp=w.monsterHp*stageMult;
const hp=baseHp*(isElite?2.5:1)*rnd(0.8,1.2);
const col=Math.floor(i/3),row=i%3;
const x=W*0.5+col*(W*0.16);
const y=H*0.2+row*(H*0.28);
const typeIdx=Math.floor(Math.random()*MONSTER_TYPES.length);
G.monsters.push({
x,y,r:isElite?28:MONSTER_TYPES[typeIdx].r,
hp,maxHp:hp,power:hp,
hue:w.monsterHue+rnd(-20,20)+MONSTER_TYPES[typeIdx].color,
elite:isElite,
mult:w.monsterMult*(isElite?3:1),
bob:Math.random()*6.28,hitT:0,
alive:true,dieT:0,
vx:0,vy:0,
faceX:-1,faceY:0,
isBoss:false,
typeIdx,
});
}
function updateStageBanner(){
const w=currentWorld();
document.getElementById('sbTitle').textContent='STAGE '+S.currentStage+' / '+STAGES_PER_WORLD;
document.getElementById('sbSub').textContent=w.name;
}
/* ============================================================
STAGE COMPLETE
============================================================ */
function completeStage(){
if(G.stageCleared)return;
G.stageCleared=true;
S.stagesCompleted[stageKey(S.currentWorld,S.currentStage)]=true;
const bonus=10*Math.pow(2.2,S.currentStage)*currentWorld().monsterMult*totalMult();
S.coins+=bonus;
toast('🎉 STAGE CLEARED! +🪙 '+fmt(bonus),'#4ade80');
beep(1318,0.2,'triangle',0.1);beep(1568,0.2,'triangle',0.1);
const drops=4+Math.floor(Math.random()*5);
for(let i=0;i<drops;i++){
const r=rollRarity(luckValue()*2);
const item=rollItem(r.id);
S.inventory[r.id]=(S.inventory[r.id]||0)+1;
G.damageTexts.push({x:W/2+rnd(-140,140),y:H*0.5+rnd(-80,80),vy:-80,life:3,maxLife:3,text:'+ '+item,color:r.color});
}
const guaranteed=rollRarity(luckValue()*8);
const gItem=rollItem(guaranteed.id);
S.inventory[guaranteed.id]=(S.inventory[guaranteed.id]||0)+1;
G.damageTexts.push({x:W/2,y:H*0.42,vy:-90,life:3.5,maxLife:3.5,text:'⭐ '+gItem,color:guaranteed.color});
if(S.currentStage<STAGES_PER_WORLD){
setTimeout(()=>{
if(G.stageCleared){
startStage(S.currentWorld,S.currentStage+1);
toast('▶ Stage '+S.currentStage+' starting...','#a855f7');
}
},2400);
} else {
toast('🏆 WORLD '+S.currentWorld+' COMPLETE!','#fcd34d');
setTimeout(()=>{enterSafeZone();},3000);
}
save();
}
function rollRarity(luckMul){
const ws=RARITIES.map(r=>r.id==='r0'?r.w:r.w*luckMul);
const t=ws.reduce((a,b)=>a+b,0);
let roll=Math.random()*t;
for(let i=0;i<ws.length;i++){roll-=ws[i];if(roll<=0)return RARITIES[i];}
return RARITIES[0];
}
function rollItem(rarityId){
const pool=currentWorld().itemPool[rarityId]||['Mystery'];
return pool[Math.floor(Math.random()*pool.length)];
}
function totalMult(){return charMult()*rebirthBonus();}
/* ============================================================
COMBAT
============================================================ */
function findNearestTarget(x,y){
if(G.bossAlive&&G.boss.alive)return G.boss;
let best=null,bd=Infinity;
for(const m of G.monsters){
if(!m.alive)continue;
const d=Math.hypot(m.x-x,m.y-y);
if(d<bd){bd=d;best=m;}
}
return best;
}
function autoAttack(dt){
if(G.safeZone||G.stageCleared)return;
G.attackCd-=dt;
const t=findNearestTarget(G.playerX,G.playerY);
if(!t)return;
if(G.attackCd<=0){
G.attackCd=attackCooldown();
const dx=t.x-G.playerX,dy=t.y-G.playerY;
const d=Math.hypot(dx,dy)||1;
const isCrit=Math.random()<critChance();
const dmg=playerDmg()*(isCrit?critMultiplier():1);
G.projectiles.push({
x:G.playerX,y:G.playerY-20,
vx:(dx/d)*900,vy:(dy/d)*900,
r:isCrit?9:6,dmg,
color:isCrit?'#fcd34d':WANDS[S.currentWand].color,
life:2,maxLife:2,
crit:isCrit,
});
beep(700+Math.random()*150,0.04,'square',0.02);
}
}
function applyDamageToMonster(m,dmg){
m.hp-=dmg;
m.hitT=0.15;
G.damageTexts.push({x:m.x+rnd(-10,10),y:m.y-15,vy:-90,life:0.9,maxLife:0.9,text:fmt(dmg),color:'#fcd34d'});
if(m.hp<=0&&m.alive){
m.alive=false;m.dieT=0;
S.stats.kills++;G.enemiesKilled++;
const coinGain=50*m.mult*currentWorld().monsterMult*totalMult()*S.currentStage*0.3;
S.coins+=coinGain;
G.damageTexts.push({x:m.x,y:m.y-30,vy:-100,life:1.2,maxLife:1.2,text:'+🪙 '+fmt(coinGain),color:'#fbbf24'});
addXp(10+S.currentStage*2);
burst(m.x,m.y,WANDS[S.currentWand].color,m.elite?35:20,m.elite?300:200);
beep(880,0.06,'triangle',0.04);
setTimeout(()=>beep(1318,0.08,'triangle',0.04),40);
const dropChance=0.55*(1+S.rebirths*0.05);
if(Math.random()<dropChance){
const r=rollRarity(luckValue()*(m.elite?2:1));
const item=rollItem(r.id);
S.inventory[r.id]=(S.inventory[r.id]||0)+1;
G.damageTexts.push({x:m.x+rnd(-20,20),y:m.y-50,vy:-80,life:1.6,maxLife:1.6,text:item,color:r.color});
if(r.tier>=3)jingle(r.id);
}
if(m.isBoss){
S.stats.bossKills++;
const bossCoin=5000*m.mult*currentWorld().monsterMult*totalMult();
S.coins+=bossCoin;
G.damageTexts.push({x:m.x,y:m.y-60,vy:-100,life:2.5,maxLife:2.5,text:'+🪙 '+fmt(bossCoin),color:'#fcd34d'});
const n=8+Math.floor(Math.random()*8);
for(let i=0;i<n;i++){
const r=rollRarity(luckValue()*15);
const item=rollItem(r.id);
S.inventory[r.id]=(S.inventory[r.id]||0)+1;
G.damageTexts.push({x:m.x+rnd(-100,100),y:m.y+rnd(-60,60),vy:-80,life:2.4,maxLife:2.4,text:item,color:r.color});
}
jingle('r124');
burst(m.x,m.y,'#ef4444',100,500);
toast('💀 BOSS DEFEATED!','#ef4444');
}
checkStageComplete();
save();
}
}
function checkStageComplete(){
if(G.safeZone||G.stageCleared)return;
if(G.bossAlive){if(!G.boss.alive)completeStage();}
else{const alive=G.monsters.filter(m=>m.alive).length;if(alive===0)completeStage();}
}
/* ============================================================
INPUT
============================================================ */
let joystick={active:false,id:null,bx:0,by:0,kx:0,ky:0,dx:0,dy:0,maxR:70};
let keys={};
window.addEventListener('keydown',e=>{ensureAudio();const k=e.key.toLowerCase();if(['arrowup','arrowdown','arrowleft','arrowright',' '].includes(k))e.preventDefault();keys[k]=true;});
window.addEventListener('keyup',e=>{keys[e.key.toLowerCase()]=false;});
function pointXY(e){const r=canvas.getBoundingClientRect();return {x:e.clientX-r.left,y:e.clientY-r.top};}
canvas.addEventListener('pointerdown',e=>{
ensureAudio();
if(!panelEl.classList.contains('hidden'))return;
if(!nameModal.classList.contains('hidden'))return;
const p=pointXY(e);
if(p.x<W*0.6&&!joystick.active){
joystick.active=true;joystick.id=e.pointerId;
joystick.bx=p.x;joystick.by=p.y;
joystick.kx=p.x;joystick.ky=p.y;
joystick.dx=0;joystick.dy=0;
}
});
canvas.addEventListener('pointermove',e=>{
if(e.pointerId!==joystick.id)return;
const p=pointXY(e);
let dx=p.x-joystick.bx,dy=p.y-joystick.by;
const d=Math.hypot(dx,dy);
if(d>joystick.maxR){dx=dx/d*joystick.maxR;dy=dy/d*joystick.maxR;}
joystick.kx=joystick.bx+dx;joystick.ky=joystick.by+dy;
joystick.dx=dx/joystick.maxR;joystick.dy=dy/joystick.maxR;
});
function endJoy(e){if(e.pointerId!==joystick.id)return;joystick.active=false;joystick.id=null;joystick.dx=0;joystick.dy=0;}
canvas.addEventListener('pointerup',endJoy);
canvas.addEventListener('pointercancel',endJoy);
canvas.addEventListener('contextmenu',e=>e.preventDefault());
/* ============================================================
TAP BUTTON
============================================================ */
const tapBtn=document.getElementById('tapBtn');
const tmult=document.getElementById('tmult');
const tapLabel=document.getElementById('tapLabel');
const powerFloat=document.getElementById('powerFloat');
let lastTapTime=0;
function doTap(){
const now=performance.now();
if(now-lastTapTime<80)return;
lastTapTime=now;
const gain=tapPowerGain();
S.power+=gain;
S.stats.taps++;
powerFloat.textContent='+'+fmt(gain)+' ⚔️';
powerFloat.classList.remove('show');void powerFloat.offsetWidth;powerFloat.classList.add('show');
tapBtn.style.transform='scale(0.85)';setTimeout(()=>{tapBtn.style.transform='';},80);
beep(1000+Math.random()*300,0.05,'triangle',0.06);
ensureAudio();
updateHUD();
updateTopBar();
}
tapBtn.addEventListener('pointerdown',e=>{e.preventDefault();ensureAudio();if(!nameModal.classList.contains('hidden'))return;if(!panelEl.classList.contains('hidden'))return;doTap();});
tapBtn.addEventListener('contextmenu',e=>e.preventDefault());
function updateTapButton(){
const isTraining=S.currentTrain>=0;
if(isTraining){
const t=TRAIN_GROUNDS.find(x=>x.id===S.currentTrain);
tmult.textContent='x'+t.powerMult;
tapLabel.textContent='TRAINING';
tapBtn.classList.add('onTrain');
} else {
tmult.textContent='x1';
tapLabel.textContent='TAP';
tapBtn.classList.remove('onTrain');
}
if(G.nearbyTrain>=0&&G.safeZone){
const t=TRAIN_GROUNDS.find(x=>x.id===G.nearbyTrain);
if(t&&S.rebirths>=t.rebirth){tmult.textContent='x'+t.powerMult+'?';}
}
}
/* ============================================================
UPDATE
============================================================ */
let lastT=performance.now(),lastSave=0;
function update(dt){
G.time+=dt;
S.playtime+=dt;
if(S.hp<maxHp())S.hp=Math.min(maxHp(),S.hp+dt*regenRate());
let mx=0,my=0;
if(keys['w']||keys['arrowup'])my-=1;
if(keys['s']||keys['arrowdown'])my+=1;
if(keys['a']||keys['arrowleft'])mx-=1;
if(keys['d']||keys['arrowright'])mx+=1;
if(joystick.active){mx=joystick.dx;my=joystick.dy;}
const len=Math.hypot(mx,my);
if(len>1){mx/=len;my/=len;}
const speed=playerSpeed();
G.playerX+=mx*speed*dt;G.playerY+=my*speed*dt;
G.playerX=clamp(G.playerX,40,W-40);G.playerY=clamp(G.playerY,90,H-150);
if(G.safeZone){
const positions=getTrainPositions();
let prevNear=G.nearbyTrain;
G.nearbyTrain=-1;
for(const p of positions){
if(Math.hypot(p.x-G.playerX,p.y-G.playerY)<70){G.nearbyTrain=p.id;break;}
}
if(prevNear!==G.nearbyTrain)updateTapButton();
if(G.nearbyTrain>=0){
const t=TRAIN_GROUNDS.find(x=>x.id===G.nearbyTrain);
if(t&&S.rebirths>=t.rebirth&&S.currentTrain!==t.id){
S.currentTrain=t.id;
if(!S.unlockedTrain.includes(t.id))S.unlockedTrain.push(t.id);
toast('✅ '+t.name+' — '+t.desc,'#4ade80');
beep(1046,0.15,'triangle',0.08);beep(1568,0.15,'triangle',0.08);
save();updateTapButton();updateHUD();
}
} else {
if(S.currentTrain>=0){S.currentTrain=-1;updateTapButton();updateHUD();}
}
} else {
if(G.nearbyTrain>=0){G.nearbyTrain=-1;updateTapButton();}
}
if(!G.safeZone){
for(const m of G.monsters){
if(!m.alive){m.dieT+=dt;continue;}
if(m.hitT>0)m.hitT-=dt;
m.bob+=dt;
const dx=G.playerX-m.x,dy=G.playerY-m.y;
const d=Math.hypot(dx,dy)||1;
if(d>80){m.x+=(dx/d)*50*dt;m.y+=(dy/d)*50*dt;}
m.faceX=dx/d;m.faceY=dy/d;
if(d<50){S.hp-=m.power*0.015*dt;if(S.hp<=0){S.hp=0;playerDie();}}
}
if(G.bossAlive){
const b=G.boss;
if(!b.alive){b.dieT+=dt;if(b.dieT>2)G.bossAlive=false;}
else{
if(b.hitT>0)b.hitT-=dt;
b.bob+=dt;
const dx=G.playerX-b.x,dy=G.playerY-b.y;
const d=Math.hypot(dx,dy)||1;
if(d>180){b.x+=(dx/d)*60*dt;b.y+=(dy/d)*60*dt;}
if(d<120){S.hp-=b.power*0.008*dt;if(S.hp<=0){S.hp=0;playerDie();}}
}
}
autoAttack(dt);
}
for(let i=G.projectiles.length-1;i>=0;i--){
const p=G.projectiles[i];
p.x+=p.vx*dt;p.y+=p.vy*dt;p.life-=dt;
if(p.life<=0||p.x<-20||p.x>W+20||p.y<-20||p.y>H+20){G.projectiles.splice(i,1);continue;}
let hit=false;
for(const m of G.monsters){
if(!m.alive)continue;
if(Math.hypot(m.x-p.x,m.y-p.y)<m.r+p.r){
applyDamageToMonster(m,p.dmg);
burst(p.x,p.y,p.color,p.crit?14:6,p.crit?200:150);
hit=true;break;
}
}
if(!hit&&G.bossAlive&&G.boss.alive){
const b=G.boss;
if(Math.hypot(b.x-p.x,b.y-p.y)<b.r+p.r){
applyDamageToMonster(b,p.dmg);burst(p.x,p.y,p.color,8,180);hit=true;
}
}
if(hit)G.projectiles.splice(i,1);
}
for(let i=G.particles.length-1;i>=0;i--){
const p=G.particles[i];
p.x+=p.vx*dt;p.y+=p.vy*dt;
p.vy+=400*dt;p.vx*=0.98;p.life-=dt;p.rot+=p.rotSpeed*dt;
if(p.life<=0)G.particles.splice(i,1);
}
for(let i=G.damageTexts.length-1;i>=0;i--){
const d=G.damageTexts[i];
d.y+=d.vy*dt;d.vy+=60*dt;d.life-=dt;
if(d.life<=0)G.damageTexts.splice(i,1);
}
updateHUD();
lastSave+=dt;
if(lastSave>5){lastSave=0;save();}
}
function playerDie(){
S.stats.deaths++;
const lost=Math.floor(S.coins*0.05);
S.coins=Math.max(0,S.coins-lost);
toast('💀 You died! Lost 🪙 '+fmt(lost),'#ef4444');
S.hp=maxHp();
if(!G.safeZone&&!G.stageCleared)startStage(S.currentWorld,S.currentStage);
}
/* ============================================================
RENDER
============================================================ */
let stars=[];
function initStars(){stars=[];for(let i=0;i<80;i++)stars.push({x:Math.random()*W,y:Math.random()*H,s:Math.random()<0.8?1:2,a:0.3+Math.random()*0.7,tw:Math.random()*6.28});}
initStars();
window.addEventListener('resize',initStars);
function render(){
const w=currentWorld();
const grd=ctx.createRadialGradient(W/2,H/2,0,W/2,H/2,Math.max(W,H));
grd.addColorStop(0,`rgb(${w.bg[0]},${w.bg[1]},${w.bg[2]})`);
grd.addColorStop(1,`rgb(${w.bg2[0]},${w.bg2[1]},${w.bg2[2]})`);
ctx.fillStyle=grd;ctx.fillRect(0,0,W,H);
for(const s of stars){const tw=0.5+0.5*Math.sin(G.time*2+s.tw);ctx.globalAlpha=s.a*tw;ctx.fillStyle='#fff';ctx.fillRect(s.x,s.y,s.s,s.s);}
ctx.globalAlpha=1;
ctx.strokeStyle=hexA(w.color,0.15);ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(0,H*0.88);ctx.lineTo(W,H*0.88);ctx.stroke();
if(G.safeZone){
const sg=ctx.createRadialGradient(W/2,H/2,0,W/2,H/2,W*0.7);
sg.addColorStop(0,'rgba(74,222,128,0.10)');sg.addColorStop(1,'rgba(74,222,128,0)');
ctx.fillStyle=sg;ctx.fillRect(0,0,W,H);
ctx.font='900 13px system-ui';ctx.textAlign='center';
ctx.fillStyle='rgba(74,222,128,0.9)';ctx.shadowColor='#4ade80';ctx.shadowBlur=12;
ctx.fillText('WALK TO A TRAINING GROUND',W/2,H*0.135);ctx.shadowBlur=0;
const positions=getTrainPositions();
for(const p of positions){
const t=TRAIN_GROUNDS.find(x=>x.id===p.id);if(!t)continue;
const unlocked=S.rebirths>=t.rebirth;
const active=S.currentTrain===t.id;
const nearby=G.nearbyTrain===t.id;
if(unlocked){
const grd2=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,nearby?90:70);
grd2.addColorStop(0,hexA(t.color,active?0.6:0.3));grd2.addColorStop(1,hexA(t.color,0));
ctx.fillStyle=grd2;ctx.beginPath();ctx.arc(p.x,p.y,nearby?90:70,0,Math.PI*2);ctx.fill();
}
ctx.fillStyle=unlocked?hexA(t.color,0.25):'rgba(80,80,80,0.25)';
ctx.strokeStyle=unlocked?t.color:'#666';ctx.lineWidth=3;
ctx.beginPath();ctx.arc(p.x,p.y,nearby?42:36,0,Math.PI*2);ctx.fill();ctx.stroke();
if(active){ctx.strokeStyle='#fcd34d';ctx.lineWidth=3;ctx.setLineDash([6,6]);ctx.beginPath();ctx.arc(p.x,p.y,nearby?50:44,0,Math.PI*2);ctx.stroke();ctx.setLineDash([]);}
ctx.font='900 28px system-ui';ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(unlocked?t.icon:'🔒',p.x,p.y-4);
ctx.font='900 10px system-ui';
ctx.fillStyle=unlocked?'#fff':'#888';ctx.shadowColor='#000';ctx.shadowBlur=4;
ctx.fillText(t.name,p.x,p.y+46);
ctx.fillStyle=unlocked?t.color:'#666';ctx.font='900 9px system-ui';
ctx.fillText(unlocked?t.desc:'Need '+t.rebirth+' ♻️',p.x,p.y+58);
ctx.shadowBlur=0;ctx.textBaseline='alphabetic';
}
}
for(const m of G.monsters)drawMonster(m);
if(G.bossAlive)drawBoss(G.boss);
for(const p of G.projectiles){
const a=Math.max(0,p.life/p.maxLife);ctx.globalAlpha=a;
ctx.shadowColor=p.color;ctx.shadowBlur=p.crit?22:14;ctx.fillStyle=p.color;
ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;
ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(p.x,p.y,p.r*0.5,0,Math.PI*2);ctx.fill();
}
ctx.globalAlpha=1;
for(const p of G.particles){
const a=Math.max(0,p.life/p.maxLife);ctx.globalAlpha=a;ctx.fillStyle=p.color;
ctx.shadowColor=p.color;ctx.shadowBlur=6;
if(p.shape==='star'){
ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);const s=p.size*a;ctx.beginPath();
for(let i=0;i<5;i++){const ang=-Math.PI/2+i*Math.PI*2/5;const x1=Math.cos(ang)*s*1.6,y1=Math.sin(ang)*s*1.6;const ang2=ang+Math.PI/5;const x2=Math.cos(ang2)*s*0.7,y2=Math.sin(ang2)*s*0.7;if(i===0)ctx.moveTo(x1,y1);else ctx.lineTo(x1,y1);ctx.lineTo(x2,y2);}
ctx.closePath();ctx.fill();ctx.restore();
} else {ctx.beginPath();ctx.arc(p.x,p.y,p.size*a,0,Math.PI*2);ctx.fill();}
}
ctx.shadowBlur=0;ctx.globalAlpha=1;
drawPlayer(G.playerX,G.playerY);
for(const d of G.damageTexts){
const a=Math.max(0,Math.min(1,d.life/d.maxLife*1.5));ctx.globalAlpha=a;
ctx.font='900 14px system-ui';ctx.textAlign='center';ctx.textBaseline='middle';
ctx.shadowColor=d.color;ctx.shadowBlur=12;ctx.strokeStyle='rgba(0,0,0,0.9)';ctx.lineWidth=4;
ctx.strokeText(d.text,d.x,d.y);ctx.fillStyle=d.color;ctx.fillText(d.text,d.x,d.y);
}
ctx.globalAlpha=1;ctx.shadowBlur=0;
if(joystick.active){
ctx.strokeStyle='rgba(168,85,247,0.4)';ctx.lineWidth=3;
ctx.beginPath();ctx.arc(joystick.bx,joystick.by,joystick.maxR,0,Math.PI*2);ctx.stroke();
ctx.fillStyle='rgba(168,85,247,0.12)';ctx.beginPath();ctx.arc(joystick.bx,joystick.by,joystick.maxR,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(200,150,255,0.75)';ctx.shadowColor='#a855f7';ctx.shadowBlur=15;
ctx.beginPath();ctx.arc(joystick.kx,joystick.ky,28,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;
ctx.strokeStyle='rgba(255,255,255,0.7)';ctx.lineWidth=2;ctx.stroke();
}
}
/* ============================================================
PLAYER DRAWING — with FULL CUSTOMIZATION
============================================================ */
function drawPlayer(x,y){
const bY=y+Math.sin(G.time*4)*2;
const f=S.face;
ctx.fillStyle='rgba(0,0,0,0.5)';ctx.beginPath();ctx.ellipse(x,y+18,18,7,0,0,Math.PI*2);ctx.fill();
const ch=CHARACTERS[S.currentChar];
const auraColor=`hsl(${ch.hue},70%,60%)`;
const grd=ctx.createRadialGradient(x,bY,0,x,bY,40);
grd.addColorStop(0,hexA(auraColor.replace('hsl',''),0.35));grd.addColorStop(1,'rgba(140,110,255,0)');
ctx.fillStyle=grd;ctx.beginPath();ctx.arc(x,bY,40,0,Math.PI*2);ctx.fill();
ctx.strokeStyle=ARMORS[S.currentArmor].color;ctx.lineWidth=3;
ctx.shadowColor=ARMORS[S.currentArmor].color;ctx.shadowBlur=10;
ctx.beginPath();ctx.arc(x,bY,18,0,Math.PI*2);ctx.stroke();ctx.shadowBlur=0;
/* Body (armor) */
ctx.fillStyle=ARMORS[S.currentArmor].color;
ctx.beginPath();ctx.arc(x,bY,16,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(255,255,255,0.15)';
ctx.beginPath();ctx.arc(x-4,bY-4,8,0,Math.PI*2);ctx.fill();
/* Head */
const skinColor=f.skin;
ctx.fillStyle=skinColor;
ctx.beginPath();ctx.arc(x,bY-14,13,0,Math.PI*2);ctx.fill();
/* Skin pattern */
if(f.skinPattern==='spots'){
ctx.fillStyle='rgba(0,0,0,0.15)';
for(let i=0;i<6;i++){
const a=i*1.05;
ctx.beginPath();ctx.arc(x+Math.cos(a)*7, bY-14+Math.sin(a)*7, 2.5, 0, Math.PI*2);ctx.fill();
}
} else if(f.skinPattern==='stripes'){
ctx.strokeStyle='rgba(0,0,0,0.2)';ctx.lineWidth=2;
for(let i=-1;i<=1;i++){
ctx.beginPath();ctx.moveTo(x-10,bY-14+i*4);ctx.lineTo(x+10,bY-14+i*4);ctx.stroke();
}
} else if(f.skinPattern==='freckles'){
ctx.fillStyle='rgba(0,0,0,0.25)';
for(let i=0;i<10;i++){
const px=x+rnd(-9,9), py=bY-14+rnd(-9,9);
if(Math.hypot(px-x,py-(bY-14))<11){
ctx.beginPath();ctx.arc(px,py,0.8,0,Math.PI*2);ctx.fill();
}
}
}
/* Hair */
if(f.hair!=='none'){
ctx.fillStyle=f.hairColor;
if(f.hair==='short'){
ctx.beginPath();
ctx.arc(x,bY-18,12,Math.PI,0);
ctx.fill();
} else if(f.hair==='long'){
ctx.beginPath();
ctx.arc(x,bY-16,13,Math.PI,0);
ctx.fill();
ctx.beginPath();ctx.ellipse(x-12,bY-8,4,10,0,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.ellipse(x+12,bY-8,4,10,0,0,Math.PI*2);ctx.fill();
} else if(f.hair==='spiky'){
for(let i=-3;i<=3;i++){
ctx.beginPath();
ctx.moveTo(x+i*3, bY-18);
ctx.lineTo(x+i*3-2, bY-30);
ctx.lineTo(x+i*3+2, bY-30);
ctx.closePath();ctx.fill();
}
} else if(f.hair==='mohawk'){
ctx.beginPath();
ctx.moveTo(x-3,bY-20);ctx.lineTo(x,bY-34);ctx.lineTo(x+3,bY-20);
ctx.closePath();ctx.fill();
} else if(f.hair==='bald'){
/* nothing */
} else if(f.hair==='ponytail'){
ctx.beginPath();ctx.arc(x,bY-18,12,Math.PI,0);ctx.fill();
ctx.beginPath();ctx.ellipse(x+14,bY-6,4,12,0,0,Math.PI*2);ctx.fill();
} else if(f.hair==='curly'){
for(let i=0;i<8;i++){
const a=Math.PI+ (i/7)*Math.PI;
ctx.beginPath();
ctx.arc(x+Math.cos(a)*11, bY-16+Math.sin(a)*11, 4, 0, Math.PI*2);
ctx.fill();
}
}
}
/* Eyes */
const eyeX=5, eyeY=-14;
const ec=f.eyeColor;
if(f.eyes==='normal'){
ctx.fillStyle='#fff';
ctx.beginPath();ctx.arc(x-5,bY+eyeY,2.8,0,Math.PI*2);ctx.arc(x+5,bY+eyeY,2.8,0,Math.PI*2);ctx.fill();
ctx.fillStyle=ec;
ctx.beginPath();ctx.arc(x-5,bY+eyeY,1.6,0,Math.PI*2);ctx.arc(x+5,bY+eyeY,1.6,0,Math.PI*2);ctx.fill();
} else if(f.eyes==='angry'){
ctx.fillStyle=ec;
ctx.beginPath();ctx.arc(x-5,bY+eyeY,2.5,0,Math.PI*2);ctx.arc(x+5,bY+eyeY,2.5,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(x-9,bY+eyeY-5);ctx.lineTo(x-2,bY+eyeY-3);ctx.stroke();
ctx.beginPath();ctx.moveTo(x+9,bY+eyeY-5);ctx.lineTo(x+2,bY+eyeY-3);ctx.stroke();
} else if(f.eyes==='happy'){
ctx.strokeStyle=ec;ctx.lineWidth=2;
ctx.beginPath();ctx.arc(x-5,bY+eyeY+1,3,Math.PI,0);ctx.stroke();
ctx.beginPath();ctx.arc(x+5,bY+eyeY+1,3,Math.PI,0);ctx.stroke();
} else if(f.eyes==='sad'){
ctx.fillStyle=ec;
ctx.beginPath();ctx.arc(x-5,bY+eyeY,2,0,Math.PI*2);ctx.arc(x+5,bY+eyeY,2,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(x-9,bY+eyeY-2);ctx.lineTo(x-2,bY+eyeY-5);ctx.stroke();
ctx.beginPath();ctx.moveTo(x+9,bY+eyeY-2);ctx.lineTo(x+2,bY+eyeY-5);ctx.stroke();
} else if(f.eyes==='closed'){
ctx.strokeStyle=ec;ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(x-8,bY+eyeY);ctx.lineTo(x-2,bY+eyeY);ctx.stroke();
ctx.beginPath();ctx.moveTo(x+2,bY+eyeY);ctx.lineTo(x+8,bY+eyeY);ctx.stroke();
} else if(f.eyes==='wink'){
ctx.fillStyle=ec;
ctx.beginPath();ctx.arc(x-5,bY+eyeY,2.5,0,Math.PI*2);ctx.fill();
ctx.strokeStyle=ec;ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(x+2,bY+eyeY);ctx.lineTo(x+8,bY+eyeY);ctx.stroke();
} else if(f.eyes==='heart'){
ctx.fillStyle='#ec4899';
for(let s of [-5,5]){
const cx=x+s, cy=bY+eyeY;
ctx.beginPath();
ctx.moveTo(cx, cy+2);
ctx.bezierCurveTo(cx-3, cy-2, cx-3, cy-4, cx, cy-3);
ctx.bezierCurveTo(cx+3, cy-4, cx+3, cy-2, cx, cy+2);
ctx.fill();
}
} else if(f.eyes==='star'){
ctx.fillStyle='#fcd34d';
for(let s of [-5,5]){
drawStar(x+s, bY+eyeY, 4, 5, 2);
}
} else if(f.eyes==='cute'){
ctx.fillStyle='#fff';
ctx.beginPath();ctx.arc(x-5,bY+eyeY,3,0,Math.PI*2);ctx.arc(x+5,bY+eyeY,3,0,Math.PI*2);ctx.fill();
ctx.fillStyle=ec;
ctx.beginPath();ctx.arc(x-5,bY+eyeY+0.5,2,0,Math.PI*2);ctx.arc(x+5,bY+eyeY+0.5,2,0,Math.PI*2);ctx.fill();
ctx.fillStyle='#fff';
ctx.beginPath();ctx.arc(x-6,bY+eyeY-1,0.8,0,Math.PI*2);ctx.arc(x+4,bY+eyeY-1,0.8,0,Math.PI*2);ctx.fill();
}
/* Mouth */
const mouthY=-6;
if(f.mouth==='smile'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(x,bY+mouthY,3,0,Math.PI);ctx.stroke();
} else if(f.mouth==='sad'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(x,bY+mouthY+2,3,Math.PI,0);ctx.stroke();
} else if(f.mouth==='neutral'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(x-3,bY+mouthY);ctx.lineTo(x+3,bY+mouthY);ctx.stroke();
} else if(f.mouth==='open'){
ctx.fillStyle='#7c1d3a';
ctx.beginPath();ctx.ellipse(x,bY+mouthY,3,2.5,0,0,Math.PI*2);ctx.fill();
} else if(f.mouth==='tongue'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(x,bY+mouthY,3,0,Math.PI);ctx.stroke();
ctx.fillStyle='#ec4899';
ctx.beginPath();ctx.ellipse(x,bY+mouthY+2,1.5,2,0,0,Math.PI*2);ctx.fill();
} else if(f.mouth==='smirk'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(x-3,bY+mouthY);
ctx.quadraticCurveTo(x+1,bY+mouthY+3, x+4,bY+mouthY-1);ctx.stroke();
} else if(f.mouth==='fangs'){
ctx.strokeStyle='#000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(x,bY+mouthY,3,0,Math.PI);ctx.stroke();
ctx.fillStyle='#fff';
ctx.beginPath();ctx.moveTo(x-2,bY+mouthY);ctx.lineTo(x-1,bY+mouthY+3);ctx.lineTo(x,bY+mouthY);ctx.fill();
ctx.beginPath();ctx.moveTo(x+2,bY+mouthY);ctx.lineTo(x+1,bY+mouthY+3);ctx.lineTo(x,bY+mouthY);ctx.fill();
}
/* Hat */
if(f.hat!=='none'){
ctx.fillStyle=f.hatColor;
if(f.hat==='wizard'){
ctx.beginPath();
ctx.moveTo(x-14,bY-22);ctx.lineTo(x+14,bY-22);ctx.lineTo(x+2,bY-42);ctx.lineTo(x-2,bY-42);
ctx.closePath();ctx.fill();
ctx.fillRect(x-15,bY-24,30,4);
} else if(f.hat==='crown'){
ctx.beginPath();
ctx.moveTo(x-13,bY-22);ctx.lineTo(x-13,bY-30);ctx.lineTo(x-7,bY-24);ctx.lineTo(x,bY-32);ctx.lineTo(x+7,bY-24);ctx.lineTo(x+13,bY-30);ctx.lineTo(x+13,bY-22);
ctx.closePath();ctx.fill();
} else if(f.hat==='cap'){
ctx.beginPath();ctx.arc(x,bY-22,13,Math.PI,0);ctx.fill();
ctx.fillRect(x-16,bY-24,32,4);
} else if(f.hat==='beanie'){
ctx.beginPath();ctx.arc(x,bY-20,13,Math.PI,0);ctx.fill();
ctx.fillStyle='rgba(255,255,255,0.3)';
ctx.fillRect(x-13,bY-22,26,4);
} else if(f.hat==='halo'){
ctx.strokeStyle=f.hatColor;ctx.lineWidth=3;
ctx.beginPath();ctx.ellipse(x,bY-32,12,3,0,0,Math.PI*2);ctx.stroke();
} else if(f.hat==='horns'){
ctx.beginPath();
ctx.moveTo(x-10,bY-22);ctx.lineTo(x-14,bY-32);ctx.lineTo(x-7,bY-25);ctx.closePath();ctx.fill();
ctx.beginPath();
ctx.moveTo(x+10,bY-22);ctx.lineTo(x+14,bY-32);ctx.lineTo(x+7,bY-25);ctx.closePath();ctx.fill();
} else if(f.hat==='flower'){
ctx.fillStyle=f.hatColor;
for(let i=0;i<6;i++){
const a=i*Math.PI/3;
ctx.beginPath();ctx.ellipse(x+Math.cos(a)*7, bY-24+Math.sin(a)*7, 3, 5, a, 0, Math.PI*2);ctx.fill();
}
ctx.fillStyle='#fcd34d';
ctx.beginPath();ctx.arc(x,bY-24,3,0,Math.PI*2);ctx.fill();
}
}
/* Accessory */
if(f.accessory!=='none'){
if(f.accessory==='glasses'){
ctx.strokeStyle='#000';ctx.lineWidth=2;
ctx.beginPath();ctx.arc(x-5,bY-14,5,0,Math.PI*2);ctx.stroke();
ctx.beginPath();ctx.arc(x+5,bY-14,5,0,Math.PI*2);ctx.stroke();
ctx.beginPath();ctx.moveTo(x-1,bY-14);ctx.lineTo(x+1,bY-14);ctx.stroke();
} else if(f.accessory==='mask'){
ctx.fillStyle='rgba(0,0,0,0.7)';
ctx.fillRect(x-10,bY-16,20,6);
} else if(f.accessory==='beard'){
ctx.fillStyle=f.hairColor;
ctx.beginPath();
ctx.arc(x,bY-6,10,0,Math.PI);ctx.fill();
} else if(f.accessory==='earrings'){
ctx.fillStyle='#fcd34d';
ctx.beginPath();ctx.arc(x-14,bY-12,1.5,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(x+14,bY-12,1.5,0,Math.PI*2);ctx.fill();
} else if(f.accessory==='scar'){
ctx.strokeStyle='#8b0000';ctx.lineWidth=1.5;
ctx.beginPath();ctx.moveTo(x+7,bY-18);ctx.lineTo(x+9,bY-10);ctx.stroke();
} else if(f.accessory==='star'){
ctx.fillStyle='#fcd34d';
drawStar(x-8, bY-16, 3, 5, 2);
}
}
/* Wand */
ctx.fillStyle=WANDS[S.currentWand].color;ctx.shadowColor=WANDS[S.currentWand].color;ctx.shadowBlur=14;
ctx.beginPath();ctx.arc(x+16,bY-19,4,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;
/* Name */
ctx.font='900 11px system-ui';ctx.textAlign='center';
ctx.fillStyle='#fcd34d';ctx.shadowColor='#fcd34d';ctx.shadowBlur=8;
ctx.fillText(S.playerName||'YOU',x,bY-52);ctx.shadowBlur=0;
}
function drawStar(cx,cy,outerR,points,innerR){
ctx.beginPath();
for(let i=0;i<points*2;i++){
const r=i%2===0?outerR:innerR;
const a=-Math.PI/2+i*Math.PI/points;
if(i===0)ctx.moveTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);
else ctx.lineTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);
}
ctx.closePath();ctx.fill();
}
function drawMonster(m){
const alpha=m.alive?1:Math.max(0,1-m.dieT/0.5);
const r=m.r,y=m.y+Math.sin(m.bob*3)*2.5;
ctx.globalAlpha=alpha;
ctx.fillStyle='rgba(0,0,0,0.4)';ctx.beginPath();ctx.ellipse(m.x,m.y+r-4,r*0.85,6,0,0,Math.PI*2);ctx.fill();
const flash=m.hitT>0?40:0;
const drawR=m.alive?r:r*(1+m.dieT);
/* Different shapes based on type */
const t=MONSTER_TYPES[m.typeIdx]||MONSTER_TYPES[0];
if(m.typeIdx===1||m.typeIdx===4){/* bat/wolf - elongated */
ctx.fillStyle=`hsl(${m.hue},80%,${50+flash}%)`;
ctx.beginPath();ctx.ellipse(m.x,y,drawR,drawR*0.7,0,0,Math.PI*2);ctx.fill();
} else if(m.typeIdx===5||m.typeIdx===6){/* snake/spider - odd shape */
ctx.fillStyle=`hsl(${m.hue},80%,${50+flash}%)`;
ctx.beginPath();ctx.ellipse(m.x,y,drawR*1.1,drawR*0.6,0,0,Math.PI*2);ctx.fill();
/* legs */
ctx.strokeStyle=`hsl(${m.hue},80%,30%)`;ctx.lineWidth=2;
for(let i=0;i<6;i++){
const a=i*Math.PI/3;
ctx.beginPath();ctx.moveTo(m.x,y);ctx.lineTo(m.x+Math.cos(a)*drawR*1.4,y+Math.sin(a)*drawR*1.4);ctx.stroke();
}
} else if(m.typeIdx===11){/* dragon - bigger with wings */
ctx.fillStyle=`hsl(${m.hue},80%,${50+flash}%)`;
ctx.beginPath();ctx.arc(m.x,y,drawR,0,Math.PI*2);ctx.fill();
ctx.fillStyle=`hsl(${m.hue},80%,${30+flash}%)`;
ctx.beginPath();ctx.moveTo(m.x-drawR*0.8,y);ctx.lineTo(m.x-drawR*1.8,y-drawR*0.8);ctx.lineTo(m.x-drawR*0.5,y+drawR*0.3);ctx.closePath();ctx.fill();
ctx.beginPath();ctx.moveTo(m.x+drawR*0.8,y);ctx.lineTo(m.x+drawR*1.8,y-drawR*0.8);ctx.lineTo(m.x+drawR*0.5,y+drawR*0.3);ctx.closePath();ctx.fill();
} else {
const grd=ctx.createRadialGradient(m.x-r*0.3,y-r*0.3,0,m.x,y,drawR);
grd.addColorStop(0,`hsl(${m.hue},80%,${65+flash}%)`);grd.addColorStop(1,`hsl(${m.hue},65%,${35+flash}%)`);
ctx.fillStyle=grd;ctx.beginPath();ctx.arc(m.x,y,drawR,0,Math.PI*2);ctx.fill();
}
ctx.strokeStyle=`hsl(${m.hue},80%,20%)`;ctx.lineWidth=2;
ctx.beginPath();ctx.arc(m.x,y,drawR,0,Math.PI*2);ctx.stroke();
/* Emoji face */
ctx.font='900 '+Math.floor(r*1.1)+'px system-ui';
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(t.icon,m.x,y);ctx.textBaseline='alphabetic';
if(m.elite){
ctx.fillStyle='#fcd34d';ctx.shadowColor='#fcd34d';ctx.shadowBlur=10;
ctx.beginPath();ctx.moveTo(m.x-r*0.5,y-r-2);ctx.lineTo(m.x-r*0.25,y-r-12);ctx.lineTo(m.x,y-r-5);ctx.lineTo(m.x+r*0.25,y-r-12);ctx.lineTo(m.x+r*0.5,y-r-2);ctx.closePath();ctx.fill();ctx.shadowBlur=0;
}
if(m.alive){
const bw=r*2.4;
ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(m.x-bw/2,y-r-20,bw,6);
ctx.fillStyle=m.elite?'#f59e0b':'#ef4444';ctx.fillRect(m.x-bw/2,y-r-20,bw*(m.hp/m.maxHp),6);
ctx.font='900 9px monospace';ctx.textAlign='center';ctx.fillStyle='#fff';ctx.shadowColor='#000';ctx.shadowBlur=4;
ctx.fillText(fmt(m.hp)+' / '+fmt(m.maxHp),m.x,y-r-28);
ctx.fillStyle='#fca5a5';ctx.font='900 8px monospace';
ctx.fillText('⚔️'+fmt(m.power),m.x,y-r-38);ctx.shadowBlur=0;
}
ctx.globalAlpha=1;
}
function drawBoss(b){
const alpha=b.alive?1:Math.max(0,1-b.dieT/2);
const r=b.r,y=b.y+Math.sin(b.bob*2)*3;
ctx.globalAlpha=alpha;
const grd=ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,r*3);
grd.addColorStop(0,'rgba(239,68,68,0.5)');grd.addColorStop(1,'rgba(239,68,68,0)');
ctx.fillStyle=grd;ctx.beginPath();ctx.arc(b.x,b.y,r*3,0,Math.PI*2);ctx.fill();
ctx.fillStyle='rgba(0,0,0,0.6)';ctx.beginPath();ctx.ellipse(b.x,b.y+r-10,r*1.15,14,0,0,Math.PI*2);ctx.fill();
const flash=b.hitT>0?30:0;
const bodyGrd=ctx.createRadialGradient(b.x-r*0.3,y-r*0.3,0,b.x,y,r);
bodyGrd.addColorStop(0,`hsl(${b.hue},70%,${55+flash}%)`);bodyGrd.addColorStop(1,`hsl(${b.hue},60%,${20+flash}%)`);
ctx.fillStyle=bodyGrd;ctx.beginPath();ctx.arc(b.x,y,r,0,Math.PI*2);ctx.fill();
ctx.strokeStyle='#000';ctx.lineWidth=3;ctx.stroke();
ctx.fillStyle='#1a0a0a';
ctx.beginPath();ctx.moveTo(b.x-r*0.7,y-r*0.4);ctx.lineTo(b.x-r*1.15,y-r*1.35);ctx.lineTo(b.x-r*0.4,y-r*0.9);ctx.closePath();ctx.fill();
ctx.beginPath();ctx.moveTo(b.x+r*0.7,y-r*0.4);ctx.lineTo(b.x+r*1.15,y-r*1.35);ctx.lineTo(b.x+r*0.4,y-r*0.9);ctx.closePath();ctx.fill();
ctx.font='900 '+Math.floor(r*1.2)+'px system-ui';ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText('🐉',b.x,y);ctx.textBaseline='alphabetic';
if(b.alive){
const bw=Math.min(300,W*0.7);
ctx.fillStyle='rgba(0,0,0,0.85)';ctx.fillRect(b.x-bw/2,y-r-48,bw,20);
const pct=b.hp/b.maxHp;const col=pct>0.5?'#ef4444':pct>0.25?'#f59e0b':'#7f1d1d';
ctx.fillStyle=col;ctx.shadowColor=col;ctx.shadowBlur=10;ctx.fillRect(b.x-bw/2,y-r-48,bw*pct,20);ctx.shadowBlur=0;
ctx.strokeStyle='#fff';ctx.lineWidth=2;ctx.strokeRect(b.x-bw/2,y-r-48,bw,20);
ctx.fillStyle='#fff';ctx.font='900 11px monospace';ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(fmt(b.hp)+' / '+fmt(b.maxHp),b.x,y-r-38);
ctx.font='900 10px system-ui';ctx.fillStyle='#fca5a5';
ctx.fillText('⚠️ BOSS ⚔️ '+fmt(b.power),b.x,y-r-58);ctx.textBaseline='alphabetic';
}
ctx.globalAlpha=1;
}
/* ============================================================
HUD
============================================================ */
const hpFill=document.getElementById('hpFill');
const hpLabel=document.getElementById('hpLabel');
function updateHUD(){
const mh=maxHp();const hp=Math.max(0,S.hp);
hpFill.style.width=clamp(hp/mh,0,1)*100+'%';
hpLabel.textContent='❤️ '+fmt(hp)+' / '+fmt(mh);
document.getElementById('pPower').textContent=fmt(playerPower());
document.getElementById('tbKills').textContent=G.enemiesKilled+'/'+G.enemiesNeeded;
}
const tbWorld=document.getElementById('tbWorld');
const tbStage=document.getElementById('tbStage');
const tbRebirth=document.getElementById('tbRebirth');
const tbCoins=document.getElementById('tbCoins');
function updateTopBar(){
tbWorld.textContent='W'+S.currentWorld;
tbStage.textContent=S.currentStage;
tbRebirth.textContent=fmt(S.rebirths);
tbCoins.textContent=fmt(S.coins);
}
/* ============================================================
PANEL
============================================================ */
const panelEl=document.getElementById('panel');
const panelBody=document.getElementById('panelBody');
const panelTabs=document.getElementById('panelTabs');
const closeBtn=document.getElementById('closeBtn');
let currentTab='upgrades',currentPanel=null;
closeBtn.addEventListener('click',closePanel);
function closePanel(){panelEl.classList.add('hidden');currentPanel=null;panelTabs.innerHTML='';panelBody.innerHTML='';}
function openPanel(type,tab){currentPanel=type;currentTab=tab||'upgrades';panelEl.classList.remove('hidden');renderPanel();}
document.getElementById('btnShop').addEventListener('click',()=>{ensureAudio();openPanel('shop','upgrades');});
document.getElementById('btnCustom').addEventListener('click',()=>{ensureAudio();openPanel('custom','face');});
document.getElementById('btnStage').addEventListener('click',()=>{ensureAudio();openPanel('stages','worlds');});
document.getElementById('btnInv').addEventListener('click',()=>{ensureAudio();openPanel('inventory','items');});
document.getElementById('btnSell').addEventListener('click',()=>{ensureAudio();sellAll();});
document.getElementById('btnTop').addEventListener('click',()=>{ensureAudio();openPanel('top','power');});
function buildTabs(tabs){panelTabs.innerHTML='';tabs.forEach(t=>{const b=el('button','tab'+(currentTab===t.id?' active':''),t.label);b.addEventListener('click',()=>{currentTab=t.id;renderPanel();});panelTabs.appendChild(b);});}
function renderPanel(){
if(currentPanel==='shop'){
buildTabs([
{id:'upgrades',label:'⚡ UPGRADES'},
{id:'wands',label:'🪄 WANDS'},
{id:'armors',label:'🛡️ ARMOR'},
{id:'characters',label:'🧙 HEROES'},
{id:'rebirth',label:'♻️ REBIRTH'},
]);
if(currentTab==='upgrades')renderUpgrades();
if(currentTab==='wands')renderWands();
if(currentTab==='armors')renderArmors();
if(currentTab==='characters')renderCharacters();
if(currentTab==='rebirth')renderRebirth();
} else if(currentPanel==='custom'){
buildTabs([
{id:'face',label:'🎨 FACE'},
{id:'hair',label:'💇 HAIR'},
{id:'hat',label:'👑 HAT'},
{id:'accessory',label:'💎 ACCESSORY'},
]);
renderCustomize();
} else if(currentPanel==='stages'){
buildTabs([{id:'worlds',label:'🌍 WORLDS'},{id:'stages',label:'📍 STAGES'}]);
if(currentTab==='worlds')renderWorlds();
if(currentTab==='stages')renderStages();
} else if(currentPanel==='inventory'){
buildTabs([{id:'items',label:'🎒 ITEMS'},{id:'chances',label:'🎲 CHANCES'}]);
if(currentTab==='items')renderInventory();
if(currentTab==='chances')renderChances();
} else if(currentPanel==='top'){
buildTabs([{id:'power',label:'⚔️ TOP POWER'},{id:'score',label:'🏆 TOP SCORE'}]);
if(currentTab==='power')renderTopPower();
if(currentTab==='score')renderTopScore();
}
}
/* ============================================================
UPGRADES — All 10
============================================================ */
const UPGRADES=[
{id:'luck',name:'Luck',icon:'🍀',max:1000,cost:lvl=>Math.floor(500*Math.pow(1.18,lvl)),desc:lvl=>`Luck x${(1+lvl*0.3).toFixed(2)}`},
{id:'damage',name:'Wand Power',icon:'🪄',max:2000,cost:lvl=>Math.floor(800*Math.pow(1.22,lvl)),desc:lvl=>`+${lvl*15}% wand damage`},
{id:'tapPower',name:'Tap Power',icon:'👆',max:2000,cost:lvl=>Math.floor(700*Math.pow(1.20,lvl)),desc:lvl=>`+${lvl*20}% per tap`},
{id:'crit',name:'Critical Hit',icon:'🎯',max:75,cost:lvl=>Math.floor(1500*Math.pow(1.25,lvl)),desc:lvl=>`${(lvl*2).toFixed(0)}% crit / x${(2+lvl*0.05).toFixed(2)} dmg`},
{id:'regen',name:'HP Regen',icon:'💚',max:500,cost:lvl=>Math.floor(1000*Math.pow(1.20,lvl)),desc:lvl=>`+${(lvl*3).toFixed(0)} HP/s`},
{id:'hp',name:'Armor HP',icon:'🛡️',max:1000,cost:lvl=>Math.floor(700*Math.pow(1.20,lvl)),desc:lvl=>`+${lvl*15}% max HP`},
{id:'xp',name:'XP Boost',icon:'📈',max:500,cost:lvl=>Math.floor(900*Math.pow(1.18,lvl)),desc:lvl=>`+${lvl*15}% XP`},
{id:'cooldown',name:'Attack Speed',icon:'⚡',max:200,cost:lvl=>Math.floor(2000*Math.pow(1.22,lvl)),desc:lvl=>`-${(lvl*0.3).toFixed(1)}% cooldown`},
{id:'speed',name:'Move Speed',icon:'🏃',max:200,cost:lvl=>Math.floor(600*Math.pow(1.20,lvl)),desc:lvl=>`+${lvl*8} speed`},
{id:'rebirthBonus',name:'Rebirth Power',icon:'♻️',max:500,cost:lvl=>Math.floor(10000*Math.pow(1.25,lvl)),desc:lvl=>`+${lvl*10}% from rebirths`},
];
function renderUpgrades(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle','⚡ UPGRADES — All work!'));
UPGRADES.forEach(up=>{
const lvl=S.upgrades[up.id]||0;
const maxed=lvl>=up.max;
const cost=up.cost(lvl);
const canBuy=!maxed&&S.coins>=cost;
const card=el('div','card');
card.appendChild(el('div','icon',up.icon));
const info=el('div','info');
info.appendChild(el('div','title',`${up.name} — Lv ${lvl}/${up.max}`));
info.appendChild(el('div','sub',maxed?'✨ MAXED':up.desc(lvl+1)));
const bar=el('div','bar');const fill=el('div');
fill.style.width=(lvl/up.max*100)+'%';bar.appendChild(fill);info.appendChild(bar);
card.appendChild(info);
const right=el('div','right');
right.appendChild(el('div','price',maxed?'—':'🪙 '+fmt(cost)));
const btn=el('button','buy'+(maxed?' owned':''),maxed?'MAX':'BUY');
btn.disabled=!canBuy;
btn.addEventListener('click',()=>{if(maxed||S.coins<cost)return;S.coins-=cost;S.upgrades[up.id]=(S.upgrades[up.id]||0)+1;beep(880,0.08,'triangle',0.05);save();renderPanel();updateTopBar();updateHUD();});
right.appendChild(btn);card.appendChild(right);
panelBody.appendChild(card);
});
}
/* ============================================================
WANDS / ARMORS / CHARACTERS
============================================================ */
function renderWands(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle',`🪄 WANDS — ${WANDS.length} total`));
WANDS.forEach(w=>{
const owned=S.ownedWands.includes(w.id);
const active=S.currentWand===w.id;
const canBuy=!owned&&S.coins>=w.cost;
const card=el('div','card');
const icon=el('div','icon',w.icon);
icon.style.filter=`drop-shadow(0 0 10px ${w.color})`;
card.appendChild(icon);
const info=el('div','info');
info.appendChild(el('div','title',`#${w.id+1} ${w.name}`));
info.appendChild(el('div','sub',`⚔️ +${fmt(w.dmg)} DMG • ${owned?(active?'▶ Active':'Owned'):'🪙 '+fmt(w.cost)}`));
card.appendChild(info);
const right=el('div','right');
if(active){const b=el('button','buy active','ACTIVE');b.disabled=true;right.appendChild(b);}
else if(owned){const b=el('button','buy select','EQUIP');b.addEventListener('click',()=>{S.currentWand=w.id;beep(660,0.08,'triangle',0.05);save();renderPanel();updateHUD();});right.appendChild(b);}
else{const b=el('button','buy','BUY');b.disabled=!canBuy;b.addEventListener('click',()=>{if(S.coins<w.cost)return;S.coins-=w.cost;S.ownedWands.push(w.id);S.currentWand=w.id;beep(1046,0.15,'triangle',0.08);save();renderPanel();updateTopBar();updateHUD();});right.appendChild(b);}
card.appendChild(right);panelBody.appendChild(card);
});
}
function renderArmors(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle',`🛡️ ARMOR — ${ARMORS.length} total`));
ARMORS.forEach(a=>{
const owned=S.ownedArmors.includes(a.id);
const active=S.currentArmor===a.id;
const canBuy=!owned&&S.coins>=a.cost;
const card=el('div','card');
const icon=el('div','icon',a.icon);
icon.style.filter=`drop-shadow(0 0 10px ${a.color})`;
card.appendChild(icon);
const info=el('div','info');
info.appendChild(el('div','title',`#${a.id+1} ${a.name}`));
info.appendChild(el('div','sub',`❤️ ${fmt(a.hp)} HP • ⚔️ x${fmt(1+a.hp/1000)} tap • ${owned?(active?'▶ Active':'Owned'):'🪙 '+fmt(a.cost)}`));
card.appendChild(info);
const right=el('div','right');
if(active){const b=el('button','buy active','ACTIVE');b.disabled=true;right.appendChild(b);}
else if(owned){const b=el('button','buy select','EQUIP');b.addEventListener('click',()=>{S.currentArmor=a.id;S.hp=maxHp();beep(660,0.08,'triangle',0.05);save();renderPanel();updateHUD();});right.appendChild(b);}
else{const b=el('button','buy','BUY');b.disabled=!canBuy;b.addEventListener('click',()=>{if(S.coins<a.cost)return;S.coins-=a.cost;S.ownedArmors.push(a.id);S.currentArmor=a.id;S.hp=maxHp();beep(1046,0.15,'triangle',0.08);save();renderPanel();updateTopBar();updateHUD();});right.appendChild(b);}
card.appendChild(right);panelBody.appendChild(card);
});
}
function renderCharacters(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle',`🧙 HEROES — ${CHARACTERS.length} total`));
CHARACTERS.forEach(c=>{
const owned=S.ownedChars.includes(c.id);
const active=S.currentChar===c.id;
const canBuy=!owned&&S.coins>=c.cost;
const card=el('div','card');
const icon=el('div','icon',c.icon);
icon.style.filter=`hue-rotate(${c.hue}deg) drop-shadow(0 0 10px hsl(${c.hue},70%,60%))`;
card.appendChild(icon);
const info=el('div','info');
info.appendChild(el('div','title',`${c.name} — x${fmt(c.mult)}`));
info.appendChild(el('div','sub',owned?(active?'▶ Active':'Owned'):'🪙 '+fmt(c.cost)));
card.appendChild(info);
const right=el('div','right');
if(active){const b=el('button','buy active','ACTIVE');b.disabled=true;right.appendChild(b);}
else if(owned){const b=el('button','buy select','SELECT');b.addEventListener('click',()=>{S.currentChar=c.id;beep(660,0.08,'triangle',0.05);save();renderPanel();updateHUD();});right.appendChild(b);}
else{const b=el('button','buy','BUY');b.disabled=!canBuy;b.addEventListener('click',()=>{if(S.coins<c.cost)return;S.coins-=c.cost;S.ownedChars.push(c.id);S.currentChar=c.id;beep(1046,0.15,'triangle',0.08);save();renderPanel();updateTopBar();updateHUD();});right.appendChild(b);}
card.appendChild(right);panelBody.appendChild(card);
});
}
/* ============================================================
REBIRTH
============================================================ */
function renderRebirth(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle','♻️ REBIRTH'));
const cost=rebirthCost();
const canRebirth=S.coins>=cost;
const info=el('div','card');
info.appendChild(el('div','icon','♻️'));
const ii=el('div','info');
ii.appendChild(el('div','title',`Rebirths: ${S.rebirths}`));
ii.appendChild(el('div','sub',`Bonus: +${S.rebirths*50}% (x${(1+S.upgrades.rebirthBonus*0.10).toFixed(2)} from upg)`));
info.appendChild(ii);panelBody.appendChild(info);
const costCard=el('div','card');
costCard.appendChild(el('div','icon','💰'));
const ci=el('div','info');
ci.appendChild(el('div','title','Cost'));
ci.appendChild(el('div','sub','🪙 '+fmt(cost)));
costCard.appendChild(ci);panelBody.appendChild(costCard);
const btn=el('button','bigBtn',canRebirth?'♻️ REBIRTH NOW':'❌ Not enough');
btn.disabled=!canRebirth;
btn.addEventListener('click',()=>{
if(S.coins<cost)return;
S.coins=0;S.rebirths++;
S.upgrades={luck:0,damage:0,tapPower:0,crit:0,regen:0,hp:0,xp:0,cooldown:0,speed:0,rebirthBonus:0};
S.inventory=RARITIES.reduce((o,r)=>(o[r.id]=0,o),{});
S.hp=maxHp();
beep(1318,0.4,'triangle',0.1);
toast('♻️ REBIRTH #'+S.rebirths+'!','#ec4899');
save();renderPanel();updateTopBar();updateHUD();
});
panelBody.appendChild(btn);
panelBody.appendChild(el('div','hint','⚠️ Resets: coins, upgrades, inventory. Keeps: characters, wands, armor, worlds, training, face.'));
}
/* ============================================================
WORLDS / STAGES
============================================================ */
function renderWorlds(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle',`🌍 WORLDS — ${WORLDS.length} total`));
WORLDS.forEach(w=>{
const unlocked=isWorldUnlocked(w);
const active=S.currentWorld===w.id;
const row=el('div','worldRow');
row.style.setProperty('--wc',w.color);
row.appendChild(el('div','wIco',w.icon));
const info=el('div','wI');
info.appendChild(el('div','wT',w.name));
info.appendChild(el('div','wS',`25 Stages • Needs ${w.unlockRebirth} ♻️<br>Monster HP: ${fmt(w.monsterHp)}`));
row.appendChild(info);
const b=el('button','wB');
if(active){b.textContent='SAFE ZONE';b.addEventListener('click',()=>{enterSafeZone();renderPanel();});}
else if(unlocked){b.textContent='GO';b.addEventListener('click',()=>{S.currentWorld=w.id;if(!S.unlockedWorlds.includes(w.id))S.unlockedWorlds.push(w.id);enterSafeZone();beep(660,0.1,'triangle',0.06);toast('Entered '+w.name,w.color);save();renderPanel();updateTopBar();});}
else{b.textContent='🔒 '+w.unlockRebirth;b.disabled=true;}
row.appendChild(b);panelBody.appendChild(row);
});
}
function renderStages(){
panelBody.innerHTML='';
const w=currentWorld();
panelBody.appendChild(el('div','sectionTitle',`📍 ${w.name} — Stages`));
const info=el('div','card');
info.appendChild(el('div','icon',w.icon));
const ii=el('div','info');
ii.appendChild(el('div','title',`World ${S.currentWorld}`));
ii.appendChild(el('div','sub',`Stage ${S.currentStage} • ${S.rebirths} ♻️`));
info.appendChild(ii);panelBody.appendChild(info);
const grid=el('div','stageGrid');
for(let i=1;i<=STAGES_PER_WORLD;i++){
const cell=el('div','stageCell');
const completed=isStageCompleted(S.currentWorld,i);
const unlocked=isStageUnlocked(S.currentWorld,i);
const isBoss=i===STAGES_PER_WORLD;
if(completed)cell.classList.add('completed');
if(!unlocked&&!completed)cell.classList.add('locked');
if(i===S.currentStage)cell.classList.add('current');
if(isBoss)cell.classList.add('boss');
cell.textContent=i;
if(isBoss)cell.appendChild(el('div','bossIco','👹'));
if(completed)cell.appendChild(el('div','checkIco','✓'));
if(unlocked||completed)cell.addEventListener('click',()=>{closePanel();startStage(S.currentWorld,i);});
grid.appendChild(cell);
}
panelBody.appendChild(grid);
panelBody.appendChild(el('div','hint','Kill all enemies to advance. Stage 25 = BOSS!'));
}
/* ============================================================
INVENTORY
============================================================ */
function sellPrice(id){const r=RARITIES.find(x=>x.id===id);return Math.floor(r.pts*totalMult());}
function renderInventory(){
panelBody.innerHTML='';
let tv=0,ti=0;
RARITIES.forEach(r=>{const c=S.inventory[r.id]||0;tv+=c*sellPrice(r.id);ti+=c;});
const sum=el('div','card');
sum.appendChild(el('div','icon','🎒'));
const si=el('div','info');
si.appendChild(el('div','title',`Items: ${fmt(ti)}`));
si.appendChild(el('div','sub',`Value: 🪙 ${fmt(tv)}`));
sum.appendChild(si);panelBody.appendChild(sum);
const sb=el('button','bigBtn green','💰 SELL ALL — 🪙 '+fmt(tv));
sb.disabled=tv===0;sb.addEventListener('click',()=>sellAll());
panelBody.appendChild(sb);
panelBody.appendChild(el('div','sectionTitle','Items'));
const ch=currentChances();
RARITIES.forEach((r,i)=>{
const cnt=S.inventory[r.id]||0;
const pct=ch[i]*100;
const price=sellPrice(r.id);
const row=el('div','rarityRow');
row.style.setProperty('--rc',r.color);
row.appendChild(el('div','rn',r.name));
row.appendChild(el('div','rp',pct<0.0001?'<0.0001%':pct<0.01?pct.toFixed(4)+'%':pct.toFixed(2)+'%'));
row.appendChild(el('div','rc','×'+fmt(cnt)));
const b=el('button','sellOne','Sell');
b.disabled=cnt===0;
b.addEventListener('click',()=>{if(cnt===0)return;S.coins+=cnt*price;S.inventory[r.id]=0;beep(660,0.06,'triangle',0.04);save();renderPanel();updateTopBar();});
row.appendChild(b);panelBody.appendChild(row);
});
}
function sellAll(){
let g=0;
RARITIES.forEach(r=>{const c=S.inventory[r.id]||0;if(c>0){g+=c*sellPrice(r.id);S.inventory[r.id]=0;}});
if(g===0){toast('Nothing to sell');return;}
S.coins+=g;
beep(880,0.1,'triangle',0.06);beep(1318,0.1,'triangle',0.05);
toast('💰 Sold for 🪙 '+fmt(g),'#4ade80');
save();if(currentPanel==='inventory')renderPanel();updateTopBar();
}
function renderChances(){
panelBody.innerHTML='';
panelBody.appendChild(el('div','sectionTitle','🍀 Drop Chances'));
const info=el('div','card');
info.appendChild(el('div','icon','🍀'));
const ii=el('div','info');
ii.appendChild(el('div','title','Luck: x'+luckValue().toFixed(2)));
info.appendChild(ii);panelBody.appendChild(info);
const ch=currentChances();
RARITIES.forEach((r,i)=>{
const pct=ch[i]*100;
const row=el('div','rarityRow');
row.style.setProperty('--rc',r.color);
row.appendChild(el('div','rn',r.name));
row.appendChild(el('div','rp',pct<0.0001?'<0.0001%':pct<0.01?pct.toFixed(4)+'%':pct.toFixed(2)+'%'));
row.appendChild(el('div','rc','🪙 '+fmt(sellPrice(r.id))));
panelBody.appendChild(row);
});
}
/* ============================================================
CUSTOMIZE
============================================================ */
const SKIN_COLORS=['#f5c6a0','#e0a882','#c68863','#8d5524','#5c3317','#f5deb3','#d4a574','#b08968','#ffd5b3','#e8b4a0','#a3d8ff','#b4ffa3','#ffb4d8','#d8b4ff','#ffe0b4','#d4d4d4','#7a7a7a','#4a4a4a','#ff6b6b','#ffd93d','#6bcb77','#4d96ff','#c780fa','#ff9f1c'];
const HAIR_COLORS=['#3a2010','#1a0a05','#6b4423','#a0522d','#d4a574','#e8c39e','#f5deb3','#ffd700','#ff6b6b','#ff4d6d','#c780fa','#4d96ff','#6bcb77','#ff9f1c','#ec4899','#a855f7','#22d3ee','#ffffff','#000000','#7a5c3a'];
const HAT_COLORS=['#a855f7','#ec4899','#f59e0b','#22c55e','#3b82f6','#22d3ee','#ef4444','#fcd34d','#fde047','#a3e635','#f97316','#c084fc','#f472b6','#818cf8','#fb7185'];
const EYE_COLORS=['#222','#3b82f6','#22c55e','#a855f7','#ec4899','#f59e0b','#ef4444','#22d3ee','#fde047','#ffffff','#8b5cf6','#06b6d4'];
function renderCustomize(){
panelBody.innerHTML='';
if(currentTab==='face'){
panelBody.appendChild(el('div','sectionTitle','🎨 SKIN COLOR'));
const skinGrid=el('div','customGrid');
SKIN_COLORS.forEach(c=>{
const sw=el('div','colorSwatch');
sw.style.background=c;
if(S.face.skin===c)sw.classList.add('selected');
sw.addEventListener('click',()=>{S.face.skin=c;save();renderPanel();});
skinGrid.appendChild(sw);
});
panelBody.appendChild(skinGrid);
panelBody.appendChild(el('div','sectionTitle','👀 EYE STYLE'));
const eyeStyles=[{id:'normal',i:'👁️',n:'Normal'},{id:'angry',i:'😠',n:'Angry'},{id:'happy',i:'😊',n:'Happy'},{id:'sad',i:'😢',n:'Sad'},{id:'closed',i:'😑',n:'Closed'},{id:'wink',i:'😉',n:'Wink'},{id:'heart',i:'😍',n:'Heart'},{id:'star',i:'🤩',n:'Star'},{id:'cute',i:'🥺',n:'Cute'}];
const eyeGrid=el('div','customGrid');
eyeStyles.forEach(e=>{
const cell=el('div','customCell');
cell.innerHTML=`${e.i}<span class="cname">${e.n}</span>`;
if(S.face.eyes===e.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.eyes=e.id;save();renderPanel();});
eyeGrid.appendChild(cell);
});
panelBody.appendChild(eyeGrid);
panelBody.appendChild(el('div','sectionTitle','🎨 EYE COLOR'));
const ecGrid=el('div','customGrid');
EYE_COLORS.forEach(c=>{
const sw=el('div','colorSwatch');
sw.style.background=c;
if(S.face.eyeColor===c)sw.classList.add('selected');
sw.addEventListener('click',()=>{S.face.eyeColor=c;save();renderPanel();});
ecGrid.appendChild(sw);
});
panelBody.appendChild(ecGrid);
panelBody.appendChild(el('div','sectionTitle','👄 MOUTH'));
const mouths=[{id:'smile',i:'😊',n:'Smile'},{id:'sad',i:'😔',n:'Sad'},{id:'neutral',i:'😐',n:'Neutral'},{id:'open',i:'😮',n:'Open'},{id:'tongue',i:'😛',n:'Tongue'},{id:'smirk',i:'😏',n:'Smirk'},{id:'fangs',i:'😈',n:'Fangs'}];
const mg=el('div','customGrid');
mouths.forEach(m=>{
const cell=el('div','customCell');
cell.innerHTML=`${m.i}<span class="cname">${m.n}</span>`;
if(S.face.mouth===m.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.mouth=m.id;save();renderPanel();});
mg.appendChild(cell);
});
panelBody.appendChild(mg);
panelBody.appendChild(el('div','sectionTitle','✨ SKIN PATTERN'));
const pats=[{id:'solid',i:'⚪',n:'Solid'},{id:'spots',i:'🔵',n:'Spots'},{id:'stripes',i:'📏',n:'Stripes'},{id:'freckles',i:'•',n:'Freckles'}];
const pg=el('div','customGrid');
pats.forEach(p=>{
const cell=el('div','customCell');
cell.innerHTML=`${p.i}<span class="cname">${p.n}</span>`;
if(S.face.skinPattern===p.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.skinPattern=p.id;save();renderPanel();});
pg.appendChild(cell);
});
panelBody.appendChild(pg);
}
else if(currentTab==='hair'){
panelBody.appendChild(el('div','sectionTitle','💇 HAIR STYLE'));
const hair=[{id:'none',i:'🥚',n:'None'},{id:'short',i:'💇',n:'Short'},{id:'long',i:'👱',n:'Long'},{id:'spiky',i:'🦔',n:'Spiky'},{id:'mohawk',i:'🧑🎤',n:'Mohawk'},{id:'ponytail',i:'🎀',n:'Ponytail'},{id:'curly',i:'🌀',n:'Curly'},{id:'bald',i:'👨🦲',n:'Bald'}];
const hg=el('div','customGrid');
hair.forEach(h=>{
const cell=el('div','customCell');
cell.innerHTML=`${h.i}<span class="cname">${h.n}</span>`;
if(S.face.hair===h.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.hair=h.id;save();renderPanel();});
hg.appendChild(cell);
});
panelBody.appendChild(hg);
panelBody.appendChild(el('div','sectionTitle','🎨 HAIR COLOR'));
const hcg=el('div','customGrid');
HAIR_COLORS.forEach(c=>{
const sw=el('div','colorSwatch');
sw.style.background=c;
if(S.face.hairColor===c)sw.classList.add('selected');
sw.addEventListener('click',()=>{S.face.hairColor=c;save();renderPanel();});
hcg.appendChild(sw);
});
panelBody.appendChild(hcg);
}
else if(currentTab==='hat'){
panelBody.appendChild(el('div','sectionTitle','👑 HAT STYLE'));
const hats=[{id:'none',i:'❌',n:'None'},{id:'wizard',i:'🧙',n:'Wizard'},{id:'crown',i:'👑',n:'Crown'},{id:'cap',i:'🧢',n:'Cap'},{id:'beanie',i:'🎩',n:'Beanie'},{id:'halo',i:'😇',n:'Halo'},{id:'horns',i:'😈',n:'Horns'},{id:'flower',i:'🌸',n:'Flower'}];
const hg=el('div','customGrid');
hats.forEach(h=>{
const cell=el('div','customCell');
cell.innerHTML=`${h.i}<span class="cname">${h.n}</span>`;
if(S.face.hat===h.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.hat=h.id;save();renderPanel();});
hg.appendChild(cell);
});
panelBody.appendChild(hg);
panelBody.appendChild(el('div','sectionTitle','🎨 HAT COLOR'));
const hcg=el('div','customGrid');
HAT_COLORS.forEach(c=>{
const sw=el('div','colorSwatch');
sw.style.background=c;
if(S.face.hatColor===c)sw.classList.add('selected');
sw.addEventListener('click',()=>{S.face.hatColor=c;save();renderPanel();});
hcg.appendChild(sw);
});
panelBody.appendChild(hcg);
}
else if(currentTab==='accessory'){
panelBody.appendChild(el('div','sectionTitle','💎 ACCESSORY'));
const accs=[{id:'none',i:'❌',n:'None'},{id:'glasses',i:'👓',n:'Glasses'},{id:'mask',i:'😷',n:'Mask'},{id:'beard',i:'🧔',n:'Beard'},{id:'earrings',i:'💎',n:'Earrings'},{id:'scar',i:'⚡',n:'Scar'},{id:'star',i:'⭐',n:'Star'}];
const ag=el('div','customGrid');
accs.forEach(a=>{
const cell=el('div','customCell');
cell.innerHTML=`${a.i}<span class="cname">${a.n}</span>`;
if(S.face.accessory===a.id)cell.classList.add('selected');
cell.addEventListener('click',()=>{S.face.accessory=a.id;save();renderPanel();});
ag.appendChild(cell);
});
panelBody.appendChild(ag);
panelBody.appendChild(el('div','hint','Customize your character! All free.'));
}
}
/* ============================================================
TOP
============================================================ */
function playerScore(){return Math.floor(S.coins+S.rebirths*1e6+S.level*5e3+S.stats.kills*100+S.stats.bossKills*5e4+playerPower());}
function renderTopPower(){panelBody.innerHTML='';panelBody.appendChild(el('div','sectionTitle','⚔️ TOP POWER'));panelBody.appendChild(el('div','hint','Leaderboard available in online version'));}
function renderTopScore(){panelBody.innerHTML='';panelBody.appendChild(el('div','sectionTitle','🏆 TOP SCORE'));panelBody.appendChild(el('div','hint','Leaderboard available in online version'));}
/* NAME */
const nameModal=document.getElementById('nameModal');
const playerNameInput=document.getElementById('playerNameInput');
const saveNameBtn=document.getElementById('saveNameBtn');
if(!S.playerName){nameModal.classList.remove('hidden');setTimeout(()=>playerNameInput.focus(),500);}
saveNameBtn.addEventListener('click',()=>{
const name=playerNameInput.value.trim().slice(0,12);
if(!name){playerNameInput.style.borderColor='#ef4444';return;}
S.playerName=name;save();
nameModal.classList.add('hidden');
toast('Welcome, '+name+'!','#4ade80');
});
/* LOOP */
function frame(now){
let dt=(now-lastT)/1000;
lastT=now;
if(dt>0.05)dt=0.05;
update(dt);
render();
requestAnimationFrame(frame);
}
/* INIT */
S.hp=maxHp();
updateTopBar();
updateHUD();
enterSafeZone();
updateTapButton();
requestAnimationFrame(frame);
window.addEventListener('beforeunload',save);
setInterval(save,10000);
let loadP=0;
const loadFill=document.getElementById('loadingFill');
const loadEl=document.getElementById('loading');
const loadInt=setInterval(()=>{loadP+=rnd(20,35);if(loadP>=100){loadP=100;clearInterval(loadInt);}loadFill.style.width=loadP+'%';},180);
loadEl.addEventListener('click',()=>{loadP=100;loadFill.style.width='100%';loadEl.classList.add('hide');ensureAudio();});
setTimeout(()=>{loadEl.classList.add('hide');},2200);
})();
</script>
</body>
</html>
```
---
✨ همه چیزهایی که اضافه شد
💎 ۱۲۵ سطح Rarity
از Common تا Infinity با اسمهای خودکار + رنگهای HSL rotation
🌍 ۱۰ World
Forest, Desert, Frozen, Volcano, Celestial, Twilight, Oceanic, Chaos, Time, Omniverse
🧙 ۱۲۵ Hero
مولتیپلایر از x1 تا x3^124 (تقریباً 10^59!)
🪄 ۱۲۵ Wand + 🛡️ ۱۲۵ Armor
· Wand DMG: 1 تا 4^124
· Armor HP: 100 تا 4.2^124 × 100
👾 ۱۲ نوع Monster
Slime, Bat, Goblin, Ghost, Wolf, Snake, Spider, Troll, Elemental, Skeleton, Wraith, Dragon 🐉
🎨 Customize Face کامل
· رنگ پوست (۲۴ رنگ)
· پترن پوست: Solid, Spots, Stripes, Freckles
· چشم: Normal, Angry, Happy, Sad, Closed, Wink, Heart ❤️, Star ⭐, Cute 🥺
· رنگ چشم (۱۲ رنگ)
· دهان: Smile, Sad, Neutral, Open, Tongue 😛, Smirk 😏, Fangs 😈
· مو: None, Short, Long, Spiky, Mohawk, Ponytail, Curly, Bald
· رنگ مو (۲۰ رنگ)
· کلاه: None, Wizard 🧙, Crown 👑, Cap 🧢, Beanie 🎩, Halo 😇, Horns 😈, Flower 🌸
· رنگ کلاه (۱۵ رنگ)
· اکسسوری: None, Glasses 👓, Mask 😷, Beard 🧔, Earrings 💎, Scar ⚡, Star ⭐
⚡ ۱۰ Upgrade (همه کار میکنن!)
1. Luck — شانس drop
2. Wand Power — +% دمیج
3. Tap Power — +% per tap
4. Crit — شانس + ضریب crit
5. HP Regen — HP/s
6. Armor HP — +% max HP
7. XP Boost — +% XP
8. Attack Speed — کاهش cooldown
9. Move Speed — +سرعت
10. Rebirth Power — +% از rebirth
🎯 همه Upgrade ها واقعاً اثر دارن
· luckValue() = (1 + luck×0.3) × (1 + rebirth×0.1)
· wandDmgValue() = base × (1 + damage×0.15)
· attackCooldown() = max(0.08, 0.5 - level×0.005 - cooldown×0.003)
· critChance() = min(0.75, crit×0.02)
· critMultiplier() = 2 + crit×0.05
· xpMult() = 1 + xp×0.15
---
🎮 چطور بازی کنی
1. بازی رو باز کن → اسم وارد کن
2. Safe Zone: برو روی Training Ground → Power جمع کن
3. 🛒 SHOP:
· Upgrades بخر (همه کار میکنن!)
· Wand/Armor/Hero بهتر بخر
4. 🎨 FACE: شخصیتت رو customize کن
5. 📍 MAP: برو Stage بزن
6. 🎒 BAG: آیتمها ببین → 💰 SELL
7. ♻️ REBIRTH: World های بهتر باز کن
---
📱 تبدیل به APK
```bash
mkdir MagicLootUltimate && cd MagicLootUltimate
npm init -y
npm install @capacitor/core @capacitor/cli @capacitor/android
npx cap init MagicLootUltimate com.example.magicloot --web-dir=www
mkdir www
# index.html رو داخل www بذار
npx cap add android
npx cap sync android
npx cap open android
```
سپس Android Studio: Build → Build Bundle(s)/APK(s) → Build APK(s)
---
اگه چیزی کمه یا خرابه بگو تا سریع درست کنم! 🎮✨Game Source: Magic Loot 2D — ULTIMATE
Creator: LaserLegend64
Libraries: none
Complexity: complex (2036 lines, 102.8 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: magic-loot-2d-massive-laserlegend64" to link back to the original. Then publish at arcadelab.ai/publish.