🎮ArcadeLab

AETERNA — O Ciclo do Criador

by SonicLion32
2885 lines183.4 KB
▶ Play
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETERNA — O Ciclo do Criador</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>👑</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700;900&family=Nunito:wght@400;600;700;800&family=Press+Start+2P&display=swap" rel="stylesheet">
<script src="https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js"></script>
<style>
:root{--gold:#d8a94e;--gold2:#f2d38a;--line:#6b5731;--ink:#eadfc3;--dim:#a89877}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%;overflow:hidden;background:#0c120d;color:var(--ink);font-family:Nunito,system-ui,sans-serif}
button{font-family:inherit;cursor:pointer}
[hidden]{display:none!important}
::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#150f09}::-webkit-scrollbar-thumb{background:#5c4a28;border-radius:4px}
.panel{background:linear-gradient(#2c2418,#1d170f);border:1px solid var(--line);box-shadow:0 0 0 1px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.06),inset 0 0 0 1px rgba(216,169,78,.12),0 14px 34px rgba(0,0,0,.6);clip-path:polygon(10px 0,calc(100% - 10px) 0,100% 10px,100% calc(100% - 10px),calc(100% - 10px) 100%,10px 100%,0 calc(100% - 10px),0 10px)}
.btn{font-family:Cinzel,serif;font-weight:700;letter-spacing:.05em;color:var(--gold2);background:linear-gradient(#3a2f1c,#241c10);border:1px solid #7a6234;padding:9px 16px;font-size:13px;transition:transform .12s,box-shadow .12s}
.btn:hover{transform:translateY(-1px);box-shadow:0 0 14px rgba(216,169,78,.35)}
.btn:active{transform:translateY(1px)}
.btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
.btn.cta{background:linear-gradient(#e9c26a,#b8862f);color:#2a1c06;border-color:#f6dd9a;font-size:17px;padding:14px 26px;text-shadow:0 1px 0 rgba(255,255,255,.35);animation:pg 2.2s infinite}
.btn.ghost{background:rgba(0,0,0,.3)}
.btn.sm{padding:5px 10px;font-size:11px}
@keyframes pg{0%,100%{box-shadow:0 0 10px rgba(233,194,106,.25)}50%{box-shadow:0 0 26px rgba(233,194,106,.6)}}
#lobbyScr{position:fixed;inset:0;z-index:75;display:grid;place-items:center;text-align:center;padding:20px;background:radial-gradient(900px 500px at 50% 20%,#241c08 0%,transparent 60%),radial-gradient(700px 500px at 50% 90%,#0e1a14 0%,transparent 60%),#05070a;overflow:auto}
#lobbyScr::before,#lobbyScr::after{content:'';position:fixed;left:0;right:0;height:60px;background:#000;z-index:2}
#lobbyScr::before{top:0}#lobbyScr::after{bottom:0}
.lobby-box{max-width:540px;width:100%;padding:30px;position:relative;z-index:3}
.lobby-box h2{font-family:Cinzel,serif;font-weight:900;font-size:36px;color:var(--gold2);letter-spacing:.14em;margin-bottom:6px;text-shadow:0 2px 0 #5c4415,0 0 30px rgba(216,169,78,.4)}
.lobby-box small{color:var(--dim);font-size:11px;display:block;margin-bottom:22px;letter-spacing:.12em}
.lobby-tabs{display:flex;gap:6px;margin-bottom:16px}
.lobby-tab{flex:1;padding:10px;font-family:Cinzel,serif;font-weight:700;font-size:12px;color:var(--dim);background:rgba(0,0,0,.35);border:1px solid var(--line);letter-spacing:.05em}
.lobby-tab.sel{color:#2a1c06;background:linear-gradient(#e9c26a,#c89a44);border-color:#f6dd9a}
.lobby-field{margin-bottom:10px;text-align:left}
.lobby-field label{font-family:Cinzel,serif;font-size:10px;letter-spacing:.12em;color:var(--dim);display:block;margin-bottom:6px;font-weight:800}
.lobby-field input{width:100%;background:rgba(0,0,0,.5);border:1px solid var(--line);color:var(--gold2);padding:12px 14px;font-size:16px;font-weight:700;font-family:'Press Start 2P',monospace;letter-spacing:.15em;outline:none;text-align:center}
.lobby-field input:focus{border-color:var(--gold);box-shadow:0 0 12px rgba(216,169,78,.3)}
.lobby-info{font-size:12px;color:var(--dim);line-height:1.6;margin:12px 0;padding:10px;background:rgba(0,0,0,.3);border:1px solid rgba(107,87,49,.3);text-align:left}
.lobby-info b{color:var(--gold2)}
#lobbyStatus{font-size:13px;font-weight:800;margin:14px 0;min-height:22px;color:#9fe8ff;transition:color .2s}
#lobbyStatus.err{color:#ff8a7a}
#lobbyStatus.ok{color:#8fe08f}
#lobbyStatus.warn{color:#ffb3a7}
#introScr{position:fixed;inset:0;z-index:70;display:grid;place-items:center;text-align:center;padding:90px 20px;background:radial-gradient(900px 500px at 50% 20%,#241c08 0%,transparent 60%),radial-gradient(700px 500px at 50% 90%,#0e1a14 0%,transparent 60%),#05070a}
#introScr::before,#introScr::after{content:'';position:fixed;left:0;right:0;height:70px;background:#000;z-index:2}
#introScr::before{top:0}#introScr::after{bottom:0}
#introTitle{font-family:Cinzel,serif;font-weight:900;font-size:clamp(40px,6vw,68px);color:var(--gold2);letter-spacing:.18em;text-shadow:0 2px 0 #5c4415,0 0 44px rgba(216,169,78,.45);margin-bottom:8px;animation:tIn 2s ease}
@keyframes tIn{from{opacity:0;letter-spacing:.5em}}
#introSub{font-size:12px;letter-spacing:.34em;color:var(--dim);margin-bottom:34px}
#storyTxt{max-width:660px;min-height:120px;font-size:17px;line-height:1.8;color:#e8dcc0;animation:ein .8s ease}
#storyDots{margin:22px 0;letter-spacing:8px;color:#5c4a28;font-size:10px}
#storyDots b{color:var(--gold)}
#createScr{position:fixed;inset:0;z-index:50;overflow:auto;background:radial-gradient(1100px 600px at 75% 15%,#1c2c1e 0%,transparent 60%),radial-gradient(900px 700px at 15% 85%,#14231c 0%,transparent 55%),#0b100c}
#bgfx{position:absolute;inset:0;width:100%;height:100%}
.c-wrap{position:relative;display:flex;gap:40px;max-width:1240px;margin:0 auto;padding:40px;align-items:flex-start;min-height:100%}
.c-left{flex:1.15;min-width:340px}
.pill{display:inline-flex;gap:8px;align-items:center;font-size:12px;font-weight:700;color:#e8c98a;background:rgba(40,30,10,.7);border:1px solid #5c4a28;padding:5px 12px;border-radius:20px;letter-spacing:.04em}
h1.title{font-family:Cinzel,serif;font-weight:900;font-size:clamp(40px,5vw,66px);line-height:.95;color:var(--gold2);letter-spacing:.12em;margin:14px 0 8px;text-shadow:0 2px 0 #5c4415,0 0 34px rgba(216,169,78,.35)}
.c-tag{color:var(--dim);font-size:14px;max-width:480px;margin-bottom:18px}
.classes{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;max-height:340px;overflow-y:auto;padding-right:4px}
.cls-row{display:flex;gap:12px;align-items:center;padding:8px 12px;border:1px solid rgba(107,87,49,.4);background:rgba(0,0,0,.28);transition:transform .15s,border-color .15s,background .15s;text-align:left;color:var(--ink)}
.cls-row:hover{transform:translateX(5px);border-color:var(--gold)}
.cls-row.sel{border-color:var(--gold);background:linear-gradient(90deg,rgba(216,169,78,.16),rgba(0,0,0,.2))}
.cls-ico{width:42px;height:42px;flex:none;display:grid;place-items:center;font-size:20px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#4a3b22,#1c150a);border:1px solid var(--line)}
.cls-row b{font-family:Cinzel,serif;font-size:15px;display:block;color:var(--gold2)}
.cls-row span{font-size:11.5px;color:var(--dim)}
.field-lbl{font-family:Cinzel,serif;font-size:11px;letter-spacing:.15em;color:var(--dim);margin:12px 0 6px}
.name-row{display:flex;gap:8px;margin-bottom:14px}
#heroName{flex:1;background:rgba(0,0,0,.4);border:1px solid var(--line);color:var(--ink);padding:11px 14px;font-size:15px;font-weight:600;outline:none}
#heroName:focus{border-color:var(--gold)}
#dice{width:46px;font-size:18px;background:rgba(0,0,0,.4);border:1px solid var(--line);color:var(--ink);transition:transform .3s}
#dice:hover{transform:rotate(180deg);border-color:var(--gold)}
.keys-hint{margin-top:14px;font-size:12px;color:#7d6f52}
.c-right{flex:1;min-width:360px;position:sticky;top:40px}
.preview{padding:16px}
#pvCanvas{display:block;margin:0 auto;background:radial-gradient(circle at 50% 62%,#243826,#101a12 75%);border:1px solid rgba(107,87,49,.5)}
#pvDesc{font-size:12.5px;color:var(--dim);text-align:center;min-height:36px;margin:8px 0 10px}
.pv-stat{display:flex;align-items:center;gap:10px;margin:5px 0;font-size:10px;font-weight:800;letter-spacing:.1em;color:var(--dim)}
.pv-stat em{width:70px;font-style:normal}
.pv-bar{flex:1;height:7px;background:#120d08;border:1px solid #000}
.pv-bar i{display:block;height:100%;width:20%;background:linear-gradient(90deg,#8a6a2c,var(--gold2));transition:width .45s}
.cust{margin-top:12px;border-top:1px solid var(--line);padding-top:10px}
.cust-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;align-items:center}
.cust-row small{width:100%;font-size:10px;letter-spacing:.12em;color:var(--dim);font-weight:800}
.sw{width:26px;height:26px;border-radius:50%;border:2px solid rgba(0,0,0,.5);transition:transform .12s}
.sw:hover{transform:scale(1.15)}
.sw.sel{border-color:var(--gold2);box-shadow:0 0 8px rgba(242,211,138,.7)}
.hbtn{font-size:11px;font-weight:700;color:var(--ink);background:rgba(0,0,0,.35);border:1px solid var(--line);padding:4px 10px}
.hbtn.sel{border-color:var(--gold);color:var(--gold2)}
#gameScr{position:fixed;inset:0}
#world{position:absolute;inset:0;cursor:crosshair;background:#122030}
#flash{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .45s;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(200,30,20,.55) 100%)}
#flash.on{opacity:1;transition:opacity .05s}
#vign{position:absolute;inset:0;pointer-events:none;z-index:2;background:radial-gradient(ellipse at center,transparent 58%,rgba(0,0,0,.5))}
#hud{position:absolute;inset:0;pointer-events:none;z-index:5}
#hud>*{pointer-events:auto}
#pframe{position:absolute;top:14px;left:14px;display:flex;gap:12px;padding:10px 16px 12px 12px;align-items:center;min-width:300px}
#pPortrait{width:56px;height:56px;border-radius:50%;display:grid;place-items:center;font-size:26px;flex:none;background:radial-gradient(circle at 35% 30%,#4a3b22,#17110a);border:2px solid var(--gold);box-shadow:0 0 12px rgba(216,169,78,.35)}
.pf-name{font-family:Cinzel,serif;font-weight:700;font-size:15px;color:var(--gold2)}
.pf-lvl{font-family:'Press Start 2P',monospace;font-size:8px;color:#ffd76a;float:right;margin-top:4px}
.bar{position:relative;height:15px;background:#120d08;border:1px solid #000;margin-top:4px;overflow:hidden}
.bar i{display:block;height:100%;transition:width .18s}
.bar.hp i{background:linear-gradient(#ff7a6d,#8f1f1f)}
.bar.mp i{background:linear-gradient(#7db6ff,#214f9e)}
.bar.xp{height:6px}.bar.xp i{background:linear-gradient(#ffe08a,#a9781f)}
.bar b{position:absolute;inset:0;display:grid;place-items:center;font-size:10px;font-weight:800;color:#fff;text-shadow:0 1px 2px #000;z-index:1}
#bossBar{position:absolute;top:14px;left:50%;transform:translateX(-50%);width:440px;padding:8px 18px;text-align:center}
#bossName{font-family:Cinzel,serif;font-weight:900;font-size:15px;color:#ffb347;letter-spacing:.08em}
#bossBar .bar{height:12px;margin-top:5px}#bossBar .bar i{background:linear-gradient(#ffb347,#a33210)}
#tgt{position:absolute;top:82px;left:50%;transform:translateX(-50%);padding:6px 18px;text-align:center;min-width:220px}
#tgtName{font-family:Cinzel,serif;font-weight:700;color:#ffb3a7;font-size:13px}
#tgtLvl{font-size:10px;color:var(--dim)}
#tgt .bar{margin-top:4px;height:10px}#tgt .bar i{background:linear-gradient(#ff8a5e,#a33210)}
#bossIntro{position:absolute;top:38%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none!important;z-index:9;opacity:0;transition:opacity .5s}
#bossIntro.show{opacity:1}
#bossIntro b{font-family:Cinzel,serif;font-weight:900;font-size:44px;color:#ffb347;letter-spacing:.14em;text-shadow:0 0 40px rgba(255,140,40,.6),0 4px 0 #4a1c08;display:block}
#bossIntro small{font-size:13px;letter-spacing:.3em;color:var(--dim)}
#zoneName{position:absolute;top:110px;left:50%;transform:translateX(-50%);text-align:center;pointer-events:none!important}
#zoneName b{font-family:Cinzel,serif;font-size:16px;color:var(--gold2);text-shadow:0 2px 6px #000;letter-spacing:.06em}
#zoneName small{display:block;font-size:11px;color:var(--dim);text-shadow:0 1px 3px #000}
#announce{position:absolute;top:150px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;gap:6px;align-items:center;pointer-events:none!important;z-index:8}
.ann{font-family:Cinzel,serif;font-weight:700;font-size:14px;color:var(--gold2);background:rgba(20,14,6,.92);border:1px solid var(--line);padding:7px 22px;animation:annIn .4s cubic-bezier(.2,1.4,.4,1),annOut .6s 4.6s forwards;white-space:nowrap}
@keyframes annIn{from{opacity:0;transform:translateY(-16px) scale(.9)}}
@keyframes annOut{to{opacity:0;transform:translateY(-10px)}}
#mmWrap{position:absolute;top:14px;right:14px;padding:10px;text-align:center}
#mmBox{position:relative;width:150px;height:150px;border:1px solid var(--line);background:#000}
#mm,#dots{position:absolute;inset:0;width:100%;height:100%;image-rendering:pixelated}
#mmFrame{position:absolute;border:1px solid rgba(255,255,255,.55);pointer-events:none}
#progBox{margin-top:7px;font-size:10.5px;font-weight:700;color:#e8c98a;max-width:150px;line-height:1.4}
#onlineLbl{margin-top:6px;font-size:10.5px;font-weight:800;color:#9fe8ff}
#buffs{position:absolute;top:104px;left:14px;display:flex;gap:6px}
.buff{width:34px;height:34px;background:rgba(20,15,7,.85);border:1px solid var(--gold);display:grid;place-items:center;font-size:16px;position:relative}
.buff s{position:absolute;bottom:-14px;left:0;right:0;text-align:center;font-size:9px;color:var(--gold2);text-decoration:none;font-weight:800}
#menu{position:absolute;right:14px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:8px}
#menu .btn{width:52px;height:52px;font-size:20px;padding:0;display:grid;place-items:center}
#chat{position:absolute;left:14px;bottom:238px;width:340px}
#chatLog{height:110px;overflow-y:auto;padding:8px 10px;font-size:12px;line-height:1.45;background:rgba(10,8,4,.55);scrollbar-width:thin}
#chatIn{width:100%;margin-top:4px;background:rgba(10,8,4,.75);border:1px solid var(--line);color:var(--ink);padding:7px 10px;font-size:13px;outline:none}
#chatIn:focus{border-color:var(--gold)}
#logbox{position:absolute;left:14px;bottom:96px;width:340px}
#logLog{height:110px;overflow-y:auto;padding:8px 10px;font-size:12px;line-height:1.45;background:rgba(10,8,4,.55);scrollbar-width:thin}
#logLog .e,#chatLog .e{animation:ein .25s ease}
@keyframes ein{from{opacity:0;transform:translateX(-8px)}}
#logLog .sys,#chatLog .sys{color:var(--gold2);font-style:italic}
#logLog .log{color:#8d8268;font-size:11.5px}
#logLog b,#chatLog b{font-weight:800}
#resLbl{position:absolute;left:50%;bottom:110px;transform:translateX(-50%);font-size:11px;font-weight:700;color:#cfe0d0;background:rgba(15,11,5,.75);border:1px solid rgba(107,87,49,.5);padding:2px 12px;pointer-events:none!important}
#goldLbl{position:absolute;left:50%;bottom:80px;transform:translateX(-50%);font-family:Cinzel,serif;font-weight:700;font-size:15px;color:var(--gold2);background:rgba(15,11,5,.85);border:1px solid var(--line);padding:3px 16px;pointer-events:none!important}
#hotbar{position:absolute;left:50%;bottom:14px;transform:translateX(-50%);display:flex;gap:6px}
.slot{position:relative;width:56px;height:56px;background:linear-gradient(#2c2418,#17110a);border:1px solid var(--line);display:grid;place-items:center;font-size:24px;transition:transform .12s,border-color .12s,box-shadow .12s;user-select:none}
.slot:hover{transform:translateY(-3px);border-color:var(--gold);box-shadow:0 6px 14px rgba(0,0,0,.5),0 0 10px rgba(216,169,78,.25)}
.slot .key{position:absolute;top:2px;left:4px;font-family:'Press Start 2P',monospace;font-size:7px;color:var(--dim)}
.slot .qty{position:absolute;bottom:1px;right:4px;font-size:10px;font-weight:800;color:var(--gold2)}
.slot .cd{position:absolute;left:0;bottom:0;width:100%;height:0;background:rgba(0,0,0,.72);pointer-events:none}
.slot .lock{position:absolute;inset:0;display:grid;place-items:center;font-size:16px;pointer-events:none}
.slot.nomana{filter:saturate(.4) brightness(.7)}
.win{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20;min-width:520px;max-width:700px;animation:winIn .22s cubic-bezier(.2,1.3,.4,1);border:1px solid #8a6c34!important;box-shadow:0 0 0 3px rgba(20,14,6,.9),0 0 0 4px rgba(216,169,78,.4),0 26px 60px rgba(0,0,0,.75)!important}
@keyframes winIn{from{opacity:0;transform:translate(-50%,-46%) scale(.94)}}
.win-h{display:flex;justify-content:space-between;align-items:center;padding:13px 18px;border-bottom:1px solid var(--line);background:linear-gradient(90deg,rgba(216,169,78,.16),rgba(216,169,78,.02) 55%,rgba(216,169,78,.16));position:relative}
.win-h::after{content:'◆ ◆ ◆';position:absolute;left:50%;transform:translateX(-50%);bottom:-9px;font-size:8px;color:var(--gold);letter-spacing:6px;text-shadow:0 0 6px rgba(0,0,0,.9)}
.win-h b{font-family:Cinzel,serif;font-size:16px;color:var(--gold2);letter-spacing:.1em}
.win-x{background:none;border:1px solid var(--line);color:var(--dim);width:28px;height:28px;font-size:13px}
.win-x:hover{color:#fff;border-color:var(--gold)}
.win-b{padding:18px;max-height:62vh;overflow-y:auto}
.wtabs{display:flex;gap:5px;margin-bottom:14px;flex-wrap:wrap}
.wtab{flex:1;font-family:Cinzel,serif;font-weight:700;font-size:11px;letter-spacing:.05em;padding:8px 6px;color:var(--dim);background:rgba(0,0,0,.35);border:1px solid var(--line);transition:all .15s;min-width:80px}
.wtab:hover{color:var(--ink)}
.wtab.sel{color:#2a1c06;background:linear-gradient(#e9c26a,#c89a44);border-color:#f6dd9a}
.eqrow{display:flex;gap:10px;align-items:center;margin-bottom:8px;font-size:13px}
.eqrow .slot{width:44px;height:44px;font-size:19px;pointer-events:none}
.stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin:14px 0;font-size:12px}
.stat-grid div{background:rgba(0,0,0,.3);border:1px solid rgba(107,87,49,.4);padding:6px 10px}
.stat-grid b{color:var(--gold2)}
#invGrid{display:grid;grid-template-columns:repeat(6,52px);gap:6px;justify-content:center}
#invGrid .slot{width:52px;height:52px;cursor:pointer}
.slot.rar2{border-color:#4d8fe0}.slot.rar3{border-color:#a45ee0;box-shadow:0 0 8px rgba(164,94,224,.35)}.slot.rar4{border-color:#f2d38a;box-shadow:0 0 10px rgba(242,211,138,.5)}
.inv-empty{color:#6d6047;font-size:12px;text-align:center;grid-column:1/-1;padding:14px}
.tree-wrap{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.tree-col{display:flex;flex-direction:column;gap:8px;padding:8px;background:rgba(0,0,0,.25);border:1px solid rgba(107,87,49,.35)}
.tree-col small{font-family:Cinzel,serif;font-size:9px;letter-spacing:.15em;color:var(--gold);text-align:center}
.tnode{padding:7px;border:1px solid var(--line);background:linear-gradient(#241d12,#17110a);text-align:left;color:var(--ink);transition:all .15s;font-size:11px}
.tnode b{font-size:11px;color:var(--ink);display:block}
.tnode small{font-size:9.5px;color:var(--dim);display:block;line-height:1.3}
.tnode .lvl{font-size:8px;color:#b0765e;font-weight:800}
.tnode.on{border-color:var(--gold);background:linear-gradient(#3a2f16,#241c0c);box-shadow:0 0 10px rgba(216,169,78,.3)}
.tnode.on b{color:var(--gold2)}
.tnode:not(:disabled):hover{transform:translateY(-2px);border-color:var(--gold)}
.tnode:disabled{opacity:.55;cursor:not-allowed}
.pet-card,.ally-card{display:flex;gap:12px;align-items:center;padding:10px;border:1px solid rgba(107,87,49,.45);background:rgba(0,0,0,.25);margin-bottom:8px}
.pet-card .pi,.ally-card .pi{width:46px;height:46px;display:grid;place-items:center;font-size:26px;background:radial-gradient(circle at 35% 30%,#4a3b22,#1c150a);border:1px solid var(--line);border-radius:50%}
.pet-card .sn,.ally-card .sn{flex:1}.pet-card .sn b,.ally-card .sn b{font-size:13px;color:var(--gold2)}.pet-card .sn small,.ally-card .sn small{display:block;color:var(--dim);font-size:11px}
.pet-card .bar{margin-top:4px;height:8px}
.act{font-family:Cinzel,serif;font-weight:700;font-size:10px;padding:6px 9px;border:1px solid var(--line);background:rgba(0,0,0,.35);color:var(--dim);margin-left:4px}
.act.on{border-color:var(--gold);color:var(--gold2);background:rgba(216,169,78,.12)}
.rombar{height:6px;background:#120d08;border:1px solid #000;margin-top:4px}
.rombar i{display:block;height:100%;background:linear-gradient(#ff8ab0,#c04a6a)}
.shop-row{display:flex;gap:10px;align-items:center;padding:8px;border-bottom:1px solid rgba(107,87,49,.3)}
.shop-row .si{font-size:22px;width:34px;text-align:center}
.shop-row .sn{flex:1}.shop-row .sn b{font-size:13px}.shop-row .sn small{display:block;color:var(--dim);font-size:11px}
.shop-row .btn{padding:6px 12px;font-size:11px}
.trav-row{display:flex;gap:10px;align-items:center;padding:9px;border-bottom:1px solid rgba(107,87,49,.3)}
.trav-row .tt{width:26px;height:26px;border-radius:50%;border:2px solid rgba(0,0,0,.5);flex:none}
.trav-row .sn{flex:1}.trav-row .sn b{font-size:13px}.trav-row .sn small{display:block;color:var(--dim);font-size:11px}
.trav-row .lk{font-size:11px;color:#b0765e;font-weight:700;max-width:150px;text-align:right}
.trav-row.cur .sn b{color:#8fe08f}
.qitem{border:1px solid rgba(107,87,49,.5);background:rgba(0,0,0,.25);padding:10px 12px;margin-bottom:8px}
.qitem b{font-family:Cinzel,serif;font-size:13px;color:var(--gold2)}
.qitem small{display:block;color:var(--dim);font-size:11.5px;margin-top:3px}
.qitem .qp{font-weight:800;font-size:12px;margin-top:4px;color:#8fe08f}
.dlg-npc{display:flex;gap:14px;align-items:center;margin-bottom:12px}
.dlg-npc .slot{width:54px;height:54px;font-size:26px;pointer-events:none}
.dlg-npc b{font-family:Cinzel,serif;font-size:16px;color:var(--gold2)}
.dlg-txt{font-size:13.5px;line-height:1.55;margin-bottom:14px}
.dlg-txt .qq{border-left:3px solid var(--gold);padding:6px 10px;background:rgba(216,169,78,.08);margin-bottom:10px}
.dlg-btns{display:flex;gap:8px;flex-wrap:wrap}
.help-tbl{width:100%;font-size:13px;border-collapse:collapse}
.help-tbl td{padding:5px 4px;border-bottom:1px solid rgba(107,87,49,.25)}
.help-tbl td:first-child{font-family:'Press Start 2P',monospace;font-size:8px;color:var(--gold);width:110px}
#deathScr,#endScr{position:absolute;inset:0;z-index:30;display:grid;place-items:center;text-align:center;animation:ein .8s;padding:20px}
#deathScr{background:radial-gradient(circle at 50% 45%,rgba(70,8,8,.88),rgba(5,2,2,.96))}
#deathScr h2{font-family:Cinzel,serif;font-weight:900;font-size:48px;color:#ff6b5e;text-shadow:0 0 30px rgba(255,60,40,.5)}
#endScr{background:radial-gradient(circle at 50% 40%,rgba(90,70,20,.92),rgba(8,6,2,.97))}
#endScr h2{font-family:Cinzel,serif;font-weight:900;font-size:44px;color:var(--gold2);text-shadow:0 0 40px rgba(255,220,120,.6)}
#deathScr p,#endScr p{color:var(--dim);margin:14px auto 24px;max-width:520px;line-height:1.6}
#tip{position:fixed;z-index:80;max-width:240px;background:#efe0b8;color:#2c2212;border:1px solid #8a6c33;padding:8px 11px;font-size:12px;line-height:1.4;box-shadow:0 8px 20px rgba(0,0,0,.6);pointer-events:none}
#tip b{font-family:Cinzel,serif}
#tip .tp{color:#7a5a1f;font-weight:800}
#tip .rar3c{color:#7a2ee0}#tip .rar4c{color:#9a6a10}
@media (max-width:960px){.c-wrap{flex-direction:column}.c-right{position:static;width:100%}#logbox,#chat{width:260px}}
</style>
</head>
<body>

<div id="lobbyScr">
  <div class="lobby-box panel">
    <h2>AETERNA</h2>
    <small>MULTIPLAYER PEER-TO-PEER · SEM SERVIDOR</small>
    <div class="lobby-tabs">
      <button class="lobby-tab sel" id="ltHost">🌐 CRIAR SALA</button>
      <button class="lobby-tab" id="ltJoin">🔗 ENTRAR EM SALA</button>
      <button class="lobby-tab" id="ltSolo">🎮 JOGAR SOLO</button>
    </div>
    <div id="lobbyFields">
      <div id="lobbyIdBox"></div>
    </div>
    <div id="lobbyStatus"></div>
    <button class="btn cta" id="btnLobbyGo" style="width:100%;margin-top:8px">Continuar ▸</button>
  </div>
</div>

<div id="introScr" hidden>
  <div style="position:relative;z-index:3">
    <div id="introTitle">AETERNA</div>
    <div id="introSub">O CICLO DO CRIADOR</div>
    <div id="storyTxt"></div>
    <div id="storyDots"></div>
    <button class="btn cta" id="btnStoryNext">Continuar ▸</button>
    <button class="btn ghost" id="btnStorySkip" style="margin-left:10px">Pular abertura</button>
    <button class="btn ghost" id="btnIntroBack" style="margin-left:10px">← Lobby</button>
  </div>
</div>

<div id="createScr" hidden>
  <canvas id="bgfx"></canvas>
  <div class="c-wrap">
    <div class="c-left">
      <div class="pill">♻ <span id="cycleLbl">Ciclo 1</span> — aquele que matar o Criador tomará o Trono</div>
      <h1 class="title">AETERNA</h1>
      <p class="c-tag">Um mundo-ilha sonhado por mil deuses esquecidos. Dez coroas guardadas por feras. Três asas no céu. E um Trono que sussurra o seu nome.</p>
      <div class="field-lbl">ESCOLHA SUA CLASSE (8)</div>
      <div class="classes" id="classList"></div>
      <div class="field-lbl">NOME DO HERÓI</div>
      <div class="name-row">
        <input id="heroName" maxlength="14" placeholder="Digite seu nome..." autocomplete="off">
        <button id="dice" title="Nome aleatório">🎲</button>
      </div>
      <button id="btnPlay" class="btn cta">⚔ &nbsp;Iniciar Jornada</button>
      <button id="btnContinue" class="btn ghost" style="margin-top:10px;width:100%" hidden></button>
      <p class="keys-hint">WASD mover · Clique atacar/colher · E interagir · 1–6 habilidades · Enter chat · I herói</p>
    </div>
    <div class="c-right">
      <div class="preview panel">
        <canvas id="pvCanvas" width="320" height="280"></canvas>
        <div id="pvDesc"></div>
        <div id="pvStats"></div>
        <div class="cust">
          <div class="cust-row"><small>PELE</small><span id="skinRow"></span></div>
          <div class="cust-row"><small>PENTEADO</small><span id="hairRow"></span></div>
          <div class="cust-row"><small>COR DO CABELO</small><span id="hairColRow"></span></div>
          <div class="cust-row"><small>ROUPA</small><span id="colRow"></span></div>
          <div class="cust-row"><small>DETALHE</small><span id="col2Row"></span></div>
        </div>
      </div>
    </div>
  </div>
</div>

<div id="gameScr" hidden>
  <canvas id="world"></canvas>
  <div id="vign"></div><div id="flash"></div>
  <div id="hud">
    <div id="pframe" class="panel">
      <div id="pPortrait">⚔️</div>
      <div style="flex:1">
        <div><span class="pf-name" id="pName">—</span><span class="pf-lvl" id="pLvl">Nv 1</span></div>
        <div class="bar hp"><i id="hpFill"></i><b id="hpTxt"></b></div>
        <div class="bar mp"><i id="mpFill"></i><b id="mpTxt"></b></div>
        <div class="bar xp"><i id="xpFill"></i></div>
      </div>
    </div>
    <div id="buffs"></div>
    <div id="bossBar" class="panel" hidden><span id="bossName"></span><div class="bar"><i id="bossFill"></i></div></div>
    <div id="tgt" class="panel" hidden><span id="tgtName"></span> <span id="tgtLvl"></span><div class="bar"><i id="tgtFill"></i></div></div>
    <div id="bossIntro"><b></b><small>GUARDIÃO DA ILHA</small></div>
    <div id="zoneName"><b></b><small></small></div>
    <div id="announce"></div>
    <div id="mmWrap" class="panel">
      <div id="mmBox"><canvas id="mm" width="64" height="64"></canvas><canvas id="dots" width="150" height="150"></canvas><div id="mmFrame"></div></div>
      <div id="progBox">—</div>
      <div id="onlineLbl">🟡 solo</div>
    </div>
    <div id="menu">
      <button class="btn" id="btnInv" title="Herói (I)">🎒</button>
      <button class="btn" id="btnQuest" title="Missões (Q)">📜</button>
      <button class="btn" id="btnHelp" title="Ajuda (H)">❓</button>
      <button class="btn" id="btnSnd" title="Som">🔊</button>
      <button class="btn" id="btnReset" title="Apagar tudo">↺</button>
    </div>
    <div id="chat"><div class="panel"><div id="chatLog"></div></div><input id="chatIn" maxlength="90" placeholder="Enter para conversar..." autocomplete="off"></div>
    <div id="logbox"><div class="panel"><div id="logLog"></div></div></div>
    <div id="resLbl"></div>
    <div id="goldLbl">🪙 0</div>
    <div id="hotbar"></div>
  </div>

  <div class="win panel" id="winInv" hidden>
    <div class="win-h"><b>HERÓI</b><button class="win-x">✕</button></div>
    <div class="win-b">
      <div class="wtabs">
        <button class="wtab sel" id="tabEq">⚔ EQUIPAR</button>
        <button class="wtab" id="tabTree">🌳 ÁRVORE</button>
        <button class="wtab" id="tabPets">🐾 PETS</button>
        <button class="wtab" id="tabAlly">🤝 ALIADOS</button>
        <button class="wtab" id="tabForge">⚒️ FORJA</button>
      </div>
      <div id="invTabEq">
        <div class="eqrow"><div class="slot" id="eqWpn">🗡️</div><span>Arma — <b id="eqWpnN" style="color:var(--gold2)"></b><br><small style="color:var(--dim)">Arco/cajado = distância.</small></span></div>
        <div class="eqrow"><div class="slot" id="eqArm">🛡️</div><span>Armadura — <b id="eqArmN" style="color:var(--gold2)"></b></span></div>
        <div class="stat-grid" id="statLines"></div>
        <div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap">
          <button class="btn sm" id="btnUseHp">🧪 Vida (<span id="cntHp">0</span>)</button>
          <button class="btn sm" id="btnUseMp">🔮 Mana (<span id="cntMp">0</span>)</button>
        </div>
        <div id="invGrid"></div>
      </div>
      <div id="invTabTree" hidden>
        <div style="margin-bottom:10px;font-size:13px">Pontos: <b id="tpLbl" style="color:var(--gold2)">0</b></div>
        <div class="tree-wrap" id="treeGrid"></div>
      </div>
      <div id="invTabPets" hidden>
        <div style="margin-bottom:10px;font-size:12.5px;color:var(--dim)">Adote filhotes com E. Um ativo por vez.</div>
        <div id="petList"></div>
      </div>
      <div id="invTabAlly" hidden>
        <div style="margin-bottom:10px;font-size:12.5px;color:var(--dim)">Aliados têm sinergia de classe. Romance dá +5% de dano.</div>
        <div id="allyList"></div>
      </div>
      <div id="invTabForge" hidden>
        <div id="forgeRes" style="margin-bottom:10px;font-size:13px"></div>
        <div style="margin-bottom:10px;font-size:12px;color:var(--dim)">Árvores e pedras podem ser quebradas NA MÃO.</div>
        <div id="forgeList"></div>
      </div>
    </div>
  </div>
  <div class="win panel" id="winQuest" hidden>
    <div class="win-h"><b>DIÁRIO DE MISSÕES</b><button class="win-x">✕</button></div>
    <div class="win-b" id="questList"></div>
  </div>
  <div class="win panel" id="winShop" hidden>
    <div class="win-h"><b><span id="shopTitle">EMPÓRIO</span> — 🪙 <span id="shopGold">0</span></b><button class="win-x">✕</button></div>
    <div class="win-b">
      <div class="wtabs"><button class="wtab sel" id="tabBuy">COMPRAR</button><button class="wtab" id="tabSell">VENDER</button></div>
      <div id="shopList"></div>
    </div>
  </div>
  <div class="win panel" id="dlg" hidden>
    <div class="win-h"><b id="dlgTitle">—</b><button class="win-x">✕</button></div>
    <div class="win-b"><div class="dlg-npc"><div class="slot" id="dlgIco">🧙</div><b id="dlgName"></b></div>
    <div class="dlg-txt" id="dlgBody"></div><div class="dlg-btns" id="dlgBtns"></div></div>
  </div>
  <div class="win panel" id="winTravel" hidden>
    <div class="win-h"><b>⛵ CAIS — PARA ONDE NAVEGAR?</b><button class="win-x">✕</button></div>
    <div class="win-b" id="travelList"></div>
  </div>
  <div class="win panel" id="winHelp" hidden>
    <div class="win-h"><b>GUIA DO HERÓI</b><button class="win-x">✕</button></div>
    <div class="win-b"><table class="help-tbl">
      <tr><td>W A S D</td><td>Mover</td></tr>
      <tr><td>CLIQUE</td><td>Atacar · andar · quebrar árvores/pedras</td></tr>
      <tr><td>ESPAÇO</td><td>Ataque básico (arco/cajado = distância)</td></tr>
      <tr><td>1–6</td><td>Habilidades</td></tr>
      <tr><td>R · T</td><td>Poção de vida · mana</td></tr>
      <tr><td>ENTER</td><td>Chat com outros heróis (multiplayer)</td></tr>
      <tr><td>E</td><td>Interagir: NPCs, cais, caverna, mina, portal, trono, filhotes</td></tr>
      <tr><td>I · Q · C</td><td>Herói · Missões · Forja</td></tr>
    </table>
    <p style="margin-top:12px;font-size:12px;color:var(--dim);line-height:1.7">🌐 <b>Multiplayer:</b> veja outros heróis e converse. Seu mundo é só seu.<br>🎯 barco → 10 chefes → Portal → Éter → Trono.</p></div>
  </div>
  <div id="deathScr" hidden>
    <div><h2>VOCÊ CAIU EM BATALHA</h2><p id="deathTxt">A maré o devolve à costa. (-10 🪙)</p>
    <button class="btn cta" id="btnRespawn">☀ Renascer</button></div>
  </div>
  <div id="endScr" hidden>
    <div><h2 id="endTitle">O TRONO É SEU</h2><p id="endTxt"></p>
    <button class="btn cta" id="btnCycle">♻ Sentar no Trono — iniciar novo ciclo</button></div>
  </div>
  <div id="tip" hidden></div>
</div>

<script>
'use strict';
const TAU=Math.PI*2,T=40,ZOOM=1.34;
const R=(a,b)=>a+Math.random()*(b-a);
const RI=(a,b)=>Math.floor(R(a,b+1));
const chance=c=>Math.random()<c;
const clamp=(v,a,b)=>v<a?a:v>b?b:v;
const dist=(ax,ay,bx,by)=>Math.hypot(ax-bx,ay-by);
const pick=a=>a[Math.floor(Math.random()*a.length)];
const $=id=>document.getElementById(id);
function h2(x,y){const n=Math.sin(x*127.1+y*311.7)*43758.5453;return n-Math.floor(n);}
function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
function genRoomId(){return Math.random().toString(36).slice(2,6).toUpperCase()+'-'+Math.random().toString(36).slice(2,6).toUpperCase();}

let AC=null,muted=false;
function initAudio(){if(!AC){try{AC=new (window.AudioContext||window.webkitAudioContext)();}catch(e){}}if(AC&&AC.state==='suspended')AC.resume();}
function tone(f,d,type,v,slide,dl){if(!AC||muted)return;type=type||'square';v=v||.14;slide=slide||0;dl=dl||0;
  const t0=AC.currentTime+dl,o=AC.createOscillator(),g=AC.createGain();o.type=type;o.frequency.setValueAtTime(f,t0);
  if(slide)o.frequency.exponentialRampToValueAtTime(Math.max(30,f+slide),t0+d);
  g.gain.setValueAtTime(v,t0);g.gain.exponentialRampToValueAtTime(.001,t0+d);
  o.connect(g).connect(AC.destination);o.start(t0);o.stop(t0+d+.02);}
const sfx={swing:()=>tone(180,.07,'sawtooth',.05,-100),hit:()=>tone(110,.1,'square',.16,-70),
  crit:()=>{tone(240,.12,'square',.18,-160);tone(480,.1,'square',.08,0,.03);},
  hurt:()=>tone(85,.2,'sawtooth',.2,-45),coin:()=>{tone(880,.08,'triangle',.14);tone(1320,.12,'triangle',.12,0,.08);},
  potion:()=>tone(280,.22,'sine',.16,320),lvl:()=>[523,659,784,1046].forEach((f,i)=>tone(f,.16,'triangle',.16,0,i*.09)),
  boom:()=>tone(60,.35,'sawtooth',.24,-30),ui:()=>tone(700,.04,'square',.05),err:()=>tone(140,.12,'square',.1,-60),
  chop:()=>tone(300,.06,'square',.1,-120),mine:()=>tone(520,.05,'square',.08,-200),
  die:()=>tone(220,.6,'sawtooth',.18,-180),tame:()=>[400,600,800].forEach((f,i)=>tone(f,.12,'sine',.12,100,i*.07)),
  portal:()=>[300,450,600,900].forEach((f,i)=>tone(f,.3,'sine',.1,120,i*.08))};

const CLASSES={
 guerreiro:{label:'Guerreiro',emoji:'⚔️',tag:'Força bruta no corpo a corpo',desc:'Muralha de aço.',hp:150,mp:45,atk:10,def:5,range:58,spd:180,cd:.7,ranged:false,proj:null,crit:0,stats:{VIDA:90,ATAQUE:75,DEFESA:85,ALCANCE:20}},
 mago:{label:'Mago',emoji:'🪄',tag:'Devastação arcana à distância',desc:'Frágil, letal.',hp:95,mp:130,atk:13,def:2,range:250,spd:165,cd:.85,ranged:true,proj:'bolt',crit:0,stats:{VIDA:40,ATAQUE:95,DEFESA:30,ALCANCE:90}},
 arqueiro:{label:'Arqueiro',emoji:'🏹',tag:'Precisão e mobilidade',desc:'Rápido, certeiro.',hp:115,mp:75,atk:11,def:3,range:265,spd:195,cd:.7,ranged:true,proj:'arrow',crit:.05,stats:{VIDA:60,ATAQUE:80,DEFESA:42,ALCANCE:96}},
 paladino:{label:'Paladino',emoji:'🛡️',tag:'Tanque sagrado com cura',desc:'Fé inabalável.',hp:175,mp:70,atk:9,def:7,range:58,spd:170,cd:.8,ranged:false,proj:null,crit:0,stats:{VIDA:100,ATAQUE:62,DEFESA:100,ALCANCE:20}},
 assassino:{label:'Assassino',emoji:'🗡️',tag:'Velocidade e críticos',desc:'Uma sombra.',hp:105,mp:60,atk:12,def:3,range:52,spd:220,cd:.48,ranged:false,proj:null,crit:.15,stats:{VIDA:50,ATAQUE:85,DEFESA:35,ALCANCE:16}},
 druida:{label:'Druida',emoji:'🌿',tag:'Magia natural com cura',desc:'Voz da floresta.',hp:110,mp:110,atk:11,def:3,range:235,spd:175,cd:.85,ranged:true,proj:'nature',crit:0,stats:{VIDA:58,ATAQUE:78,DEFESA:38,ALCANCE:85}},
 necromante:{label:'Necromante',emoji:'💀',tag:'Drena vida das trevas',desc:'Cada golpe rouba vida.',hp:100,mp:120,atk:12,def:2,range:245,spd:165,cd:.85,ranged:true,proj:'shadow',crit:0,stats:{VIDA:45,ATAQUE:88,DEFESA:28,ALCANCE:88}},
 mendigo:{label:'Mendigo',emoji:'🥣',tag:'Nada a perder, tudo a ganhar',desc:'Sem arma. +30% XP/ouro.',hp:125,mp:55,atk:6,def:0,range:50,spd:190,cd:.7,ranged:false,proj:null,crit:.05,xpMult:1.3,goldMult:1.3,stats:{VIDA:55,ATAQUE:30,DEFESA:8,ALCANCE:14}},
};
const ITEMS={
 php:{n:'Poção de Vida',icon:'🧪',type:'potion',price:25,desc:'Restaura 45% da vida.'},
 pmp:{n:'Poção de Mana',icon:'🔮',type:'potion',price:20,desc:'Restaura 55% do mana.'},
 gift:{n:'Presente',icon:'🎁',type:'gift',price:80,desc:'Dê a um Aliado (+6 ❤).'},
 axe:{n:'Machado',icon:'🪓',type:'tool',price:60,desc:'Árvores caem em 3 golpes (na mão: 5).'},
 pick:{n:'Picareta',icon:'⛏️',type:'tool',price:60,desc:'Pedras em 3 golpes (na mão: 6).'},
 a1:{n:'Túnica Simples',icon:'👕',type:'armor',def:2,price:0,tier:1},
 a2:{n:'Armadura de Couro',icon:'🦺',type:'armor',def:6,price:120,rar:2,tier:2},
 a3:{n:'Cota de Malha',icon:'⛓️',type:'armor',def:12,price:400,rar:3,tier:3},
 a4:{n:'Armadura Real',icon:'🛡️',type:'armor',def:20,price:1200,rar:4,tier:4},
};
const WK={e:{vk:'sword',names:['Espada Gasta','Espada de Ferro','Lâmina de Aço','Lâmina do Alvorecer']},m:{vk:'axe',names:['Machado Torto','Machado de Ferro','Machado de Guerra','Machado Titã']},h:{vk:'hammer',names:['Maça Torta','Maça de Ferro','Martelo da Aurora','Martelo Celeste']},a:{vk:'bow',names:['Arco Curto','Arco de Caça','Arco Élfico','Arco Tempestade']},c:{vk:'staff',names:['Galho Arcano','Cajado de Cristal','Cajado Rúnico','Cajado do Vazio']},d:{vk:'dagger',names:['Adaga Enferrujada','Adagas Gêmeas','Presas Noturnas','Presas do Vazio']}};
const ATK_T=[4,10,18,30],WPN_P=[15,150,500,1500];
Object.keys(WK).forEach(k=>{WK[k].names.forEach((nm,t)=>{ITEMS['w_'+k+(t+1)]={n:nm,icon:{sword:'🗡️',axe:'🪓',hammer:'🔨',bow:'🏹',staff:'🪄',dagger:'🗡️'}[WK[k].vk],type:'weapon',vk:WK[k].vk,atk:ATK_T[t],price:WPN_P[t],rar:t>=3?4:t>=2?3:2};});});
const BSWPN={
 bw1:{n:'Presa do Alfa',icon:'🐺',vk:'dagger',atk:16,crit:.12,price:600,d:'crítico'},
 bw2:{n:'Coração de Carvalho',icon:'🌳',vk:'staff',atk:18,leech:.12,price:650,d:'roubo de vida'},
 bw3:{n:'Punho do Rei',icon:'👑',vk:'hammer',atk:20,crit:.10,price:700,d:'crítico'},
 bw4:{n:'Brasa Eterna',icon:'🔥',vk:'sword',atk:24,leech:.12,price:800,d:'roubo de vida'},
 bw5:{n:'Sombra Uivante',icon:'🌘',vk:'dagger',atk:27,crit:.18,price:900,d:'crítico'},
 bw6:{n:'Lâmina Glacial',icon:'❄️',vk:'sword',atk:30,spd:.12,price:1000,d:'velocidade'},
 bw7:{n:'Presa da Matriarca',icon:'🐍',vk:'axe',atk:33,leech:.18,price:1100,d:'roubo de vida'},
 bw8:{n:'Olho do Arauto',icon:'👁️',vk:'staff',atk:36,crit:.15,price:1200,d:'crítico'},
 bw9:{n:'Lâmina da Coroa',icon:'⚜️',vk:'sword',atk:42,crit:.10,leech:.10,price:1500,d:'crítico e roubo'},
 bw10:{n:'Pena Caída',icon:'🪶',vk:'bow',atk:46,spd:.15,price:1600,d:'velocidade'},
 bw11:{n:'Martelo do Julgamento',icon:'⚖️',vk:'hammer',atk:50,crit:.20,price:1800,d:'crítico'},
 bw12:{n:'Lança do Vigia',icon:'🔆',vk:'axe',atk:55,leech:.20,price:2000,d:'roubo de vida'},
 bw13:{n:'Fragmento do Trono',icon:'🌌',vk:'sword',atk:66,crit:.15,leech:.15,price:3000,d:'tudo'},
};
Object.keys(BSWPN).forEach(k=>{const b=BSWPN[k];ITEMS[k]={n:b.n,icon:b.icon,type:'weapon',vk:b.vk,atk:b.atk,crit:b.crit||0,leech:b.leech||0,spd:b.spd||0,price:b.price,rar:4,boss:1,desc:'Arma de chefe · '+(b.d||'')};});
const IXWPN={ix1:['Garra da Névoa','🌫️','dagger',12],ix2:['Folha Rubra','🍂','axe',15],ix3:['Eco de Pedra','🪨','hammer',18],ix4:['Brasa Moura','🕯️','staff',21],ix5:['Uivo Umbral','🌒','dagger',24],ix6:['Dente de Gelo','🧊','sword',27],ix7:['Escama Verde','🐉','axe',30],ix8:['Sussurro Abissal','🌊','staff',34],ix9:['Insígnia Real','🎖️','sword',38],ix11:['Pena de Prata','🪶','bow',42],ix12:['Mármore Vivo','🏛️','hammer',47],ix13:['Aurora','🌅','sword',52]};
Object.keys(IXWPN).forEach(k=>{const x=IXWPN[k];ITEMS[k]={n:x[0],icon:x[1],type:'weapon',vk:x[2],atk:x[3],crit:.06,price:200+x[3]*14,rar:3,desc:'Exclusiva desta ilha'};});
const SKILLS={
 guerreiro:[{n:'Golpe Heroico',i:'💥',mp:12,cd:5,type:'strike',mult:2.2,lvl:1,d:'Golpe com 220% de dano.'},{n:'Redemoinho',i:'🌪️',mp:20,cd:10,type:'nova',mult:1.5,lvl:2,d:'Giro que atinge todos ao redor.'},{n:'Grito de Guerra',i:'📯',mp:15,cd:14,type:'buffAtk',pct:.5,lvl:4,d:'+50% de dano por 8s.'},{n:'Investida',i:'🐂',mp:12,cd:8,type:'dash',lvl:7,d:'Avança até o alvo.'},{n:'Execução',i:'⚰️',mp:26,cd:12,type:'strike',mult:3.5,lvl:11,d:'350% de dano.'},{n:'Fúria Berserker',i:'🔥',mp:30,cd:24,type:'buffAtk',pct:1,dur:10,lvl:16,d:'+100% de dano por 10s.'}],
 mago:[{n:'Dardo Arcano',i:'✨',mp:8,cd:2,type:'proj',mult:1.4,lvl:1,d:'Disparo rápido.'},{n:'Bola de Fogo',i:'🔥',mp:14,cd:4,type:'proj',mult:2.0,lvl:2,d:'Esfera flamejante.'},{n:'Nova Congelante',i:'❄️',mp:22,cd:11,type:'nova',mult:1.3,slow:1,lvl:4,d:'Gelo ao redor.'},{n:'Translocação',i:'🌀',mp:14,cd:8,type:'dash',lvl:7,d:'Teleporta até o alvo.'},{n:'Explosão Arcana',i:'🌌',mp:28,cd:13,type:'aoe',mult:2.6,lvl:11,d:'Detonação massiva.'},{n:'Meteoro',i:'☄️',mp:40,cd:18,type:'aoe',mult:3.8,lvl:16,d:'O céu desaba.'}],
 arqueiro:[{n:'Tiro Rápido',i:'💨',mp:7,cd:1.5,type:'proj',mult:1.3,lvl:1,d:'Disparo veloz.'},{n:'Tiro Certeiro',i:'🎯',mp:12,cd:5,type:'proj',mult:1.8,pierce:1,critB:.5,lvl:2,d:'Perfurante.'},{n:'Chuva de Flechas',i:'☄️',mp:22,cd:11,type:'aoe',mult:1.5,lvl:4,d:'Saraivada.'},{n:'Rolamento',i:'🤸',mp:10,cd:7,type:'dash',lvl:7,d:'Rola até o alvo.'},{n:'Foco Aguçado',i:'👁️',mp:16,cd:15,type:'buffCrit',lvl:11,d:'+35% crítico.'},{n:'Tempestade Perfurante',i:'🌠',mp:34,cd:17,type:'aoe',mult:2.6,lvl:16,d:'Mil flechas.'}],
 paladino:[{n:'Martelo Sagrado',i:'🔨',mp:12,cd:5,type:'strike',mult:1.9,lvl:1,d:'Golpe divino.'},{n:'Luz Curativa',i:'✨',mp:18,cd:11,type:'heal',heal:.35,lvl:2,d:'Restaura 35%.'},{n:'Onda Sagrada',i:'🌟',mp:22,cd:11,type:'nova',mult:1.4,lvl:4,d:'Onda de luz.'},{n:'Escudo da Fé',i:'🛡️',mp:16,cd:15,type:'guard',lvl:7,d:'-45% de dano.'},{n:'Julgamento',i:'⚖️',mp:26,cd:12,type:'strike',mult:3.0,lvl:11,d:'300% de dano.'},{n:'Santuário',i:'⛪',mp:36,cd:20,type:'heal',heal:.6,lvl:16,d:'60% da vida.'}],
 assassino:[{n:'Punhalada',i:'🗡️',mp:8,cd:2,type:'strike',mult:1.6,critB:.2,lvl:1,d:'Rápida.'},{n:'Passo Sombrio',i:'🌑',mp:12,cd:7,type:'dash',lvl:2,d:'Avança.'},{n:'Punhalada Fatal',i:'🩸',mp:16,cd:6,type:'strike',mult:2.4,critB:.4,lvl:4,d:'Profunda.'},{n:'Véu Sombrio',i:'🌫️',mp:16,cd:15,type:'buffCrit',lvl:7,d:'+35% crítico.'},{n:'Sede de Sangue',i:'🦇',mp:24,cd:12,type:'nova',mult:1.4,leech:.6,lvl:11,d:'Corte em área.'},{n:'Dança da Morte',i:'💃',mp:34,cd:17,type:'nova',mult:2.2,lvl:16,d:'Cem cortes.'}],
 druida:[{n:'Lança de Espinhos',i:'🌵',mp:10,cd:3,type:'proj',mult:1.6,lvl:1,d:'Dardo.'},{n:'Florescer',i:'🌸',mp:16,cd:11,type:'heal',heal:.3,lvl:2,d:'Restaura 30%.'},{n:'Raízes Famintas',i:'🌿',mp:20,cd:11,type:'nova',mult:1.4,slow:1,lvl:4,d:'Prende.'},{n:'Forma Selvagem',i:'🐾',mp:12,cd:8,type:'dash',lvl:7,d:'Salta.'},{n:'Tempestade Verde',i:'🍃',mp:26,cd:13,type:'aoe',mult:2.0,lvl:11,d:'Fúria.'},{n:'Renascimento',i:'🌺',mp:36,cd:20,type:'heal',heal:.7,lvl:16,d:'70% da vida.'}],
 necromante:[{n:'Seta Sombria',i:'🌑',mp:10,cd:3,type:'proj',mult:1.5,leech:.25,lvl:1,d:'Drena 25%.'},{n:'Drenar Vida',i:'🩸',mp:16,cd:6,type:'proj',mult:1.9,leech:.45,lvl:2,d:'Drena 45%.'},{n:'Explosão Mortal',i:'☠️',mp:24,cd:12,type:'aoe',mult:1.8,lvl:4,d:'Detonação.'},{n:'Passo do Vazio',i:'🕳️',mp:14,cd:8,type:'dash',lvl:7,d:'Desliza.'},{n:'Manto do Vazio',i:'🕸️',mp:18,cd:15,type:'buffAtk',pct:.5,lvl:11,d:'+50% dano.'},{n:'Colheita de Almas',i:'⚰️',mp:34,cd:17,type:'nova',mult:2.4,leech:.4,lvl:16,d:'Ceifa.'}],
 mendigo:[{n:'Soco Inglês',i:'👊',mp:8,cd:3,type:'strike',mult:1.9,lvl:1,d:'Na marra.'},{n:'Pedrada',i:'🪨',mp:10,cd:5,type:'proj',mult:1.6,lvl:2,d:'Pedra.'},{n:'Malandragem',i:'🎲',mp:14,cd:14,type:'buffCrit',lvl:4,d:'+35% crítico.'},{n:'Emboscada',i:'🏃',mp:12,cd:8,type:'dash',lvl:7,d:'Avança.'},{n:'Fúria do Povo',i:'🍞',mp:24,cd:12,type:'nova',mult:1.9,lvl:11,d:'Revolta.'},{n:'Golpe do Destino',i:'🌟',mp:34,cd:17,type:'strike',mult:4.2,lvl:16,d:'420%.'}],
};
const BRNAMES=['FORÇA','TÉCNICA','VITALIDADE','ESPÍRITO','DESTINO','SOMBRA','FORTUNA','MAESTRIA'];
const TREETPL=[];
const TBR=[
 [{n:'Braços de Ferro',d:'+10% de dano',eff:{atk:.10}},{n:'Golpe Treinado',d:'Habilidade 1: +30%',eff:{s0:.30}},{n:'Músculos de Aço',d:'+15% de dano',eff:{atk:.15}},{n:'Cataclismo',d:'Habilidade 1: +60%',eff:{s0:.60}}],
 [{n:'Reflexos',d:'Habilidade 2: +30%',eff:{s1:.30}},{n:'Ritmo',d:'-10% de recarga',eff:{cdr:.10}},{n:'Fluxo',d:'Habilidade 2: +60%',eff:{s1:.60}},{n:'Tempo Perfeito',d:'-15% de recarga',eff:{cdr:.15}}],
 [{n:'Vigor',d:'+12% de vida',eff:{hp:.12}},{n:'Pele Grossa',d:'+5 de defesa',eff:{def:5}},{n:'Sangue Quente',d:'+20% de vida',eff:{hp:.20}},{n:'Muralha',d:'+10 de defesa',eff:{def:10}}],
 [{n:'Olho do Matador',d:'+5% de crítico',eff:{crit:.05}},{n:'Feitiço Forte',d:'Habilidade 3: +30%',eff:{s2:.30}},{n:'Instinto',d:'+8% de crítico',eff:{crit:.08}},{n:'Feitiço Maior',d:'Habilidade 3: +60%',eff:{s2:.60}}],
 [{n:'Passo Leve',d:'+8% velocidade',eff:{spd:.08}},{n:'Sorte no Saque',d:'+15% de ouro',eff:{gold:.15}},{n:'Corrida',d:'+12% velocidade',eff:{spd:.12}},{n:'Aprendiz Veloz',d:'+20% de XP',eff:{xp:.20}}],
 [{n:'Vampirismo I',d:'Rouba 4% do dano',eff:{leech:.04}},{n:'Frieza',d:'+6% de crítico',eff:{crit:.06}},{n:'Vampirismo II',d:'Rouba 8% do dano',eff:{leech:.08}},{n:'Sede Eterna',d:'+12% de dano',eff:{atk:.12}}],
 [{n:'Prosperidade',d:'+10% de XP',eff:{xp:.10}},{n:'Tesouro',d:'+20% de ouro',eff:{gold:.20}},{n:'Resiliência',d:'+15% de vida',eff:{hp:.15}},{n:'Bênção',d:'+8 de defesa',eff:{def:8}}],
 [{n:'Arte da Guerra I',d:'Habilidade 4: +40%',eff:{s3:.40}},{n:'Arte da Guerra II',d:'Habilidade 5: +40%',eff:{s4:.40}},{n:'Lenda',d:'+15% de dano',eff:{atk:.15}},{n:'Obra-Prima',d:'Habilidade 6: +60%',eff:{s5:.60}}],
];
const TLVL=[2,5,9,14];
TBR.forEach((br,bi)=>br.forEach((nd,ti)=>TREETPL.push({id:'t'+(bi*4+ti),b:bi,t:ti,i:['⚔️','🌀','❤️','✨','🍀','🌑','🪙','👑'][bi],n:nd.n,d:nd.d,lvl:TLVL[ti],eff:nd.eff})));
const PETKINDS={
 cat:{n:'Gato',icon:'🐈',buff:'crit',vals:[.04,.07,.12],d:'+crítico'},
 wolf:{n:'Lobo',icon:'🐺',buff:'dmg',vals:[.06,.10,.16],d:'+dano'},
 rabbit:{n:'Coelho',icon:'🐇',buff:'spd',vals:[.08,.13,.20],d:'+velocidade'},
 eagle:{n:'Águia',icon:'🦅',buff:'xp',vals:[.15,.25,.40],d:'+XP ganho'},
 bear:{n:'Urso',icon:'🐻',buff:'def',vals:[.15,.28,.45],d:'+defesa'},
 fox:{n:'Raposa',icon:'🦊',buff:'gold',vals:[.15,.28,.45],d:'+ouro'},
 snake:{n:'Cobra',icon:'🐍',buff:'leech',vals:[.03,.05,.09],d:'+roubo de vida'},
};
const STAGEN=['Filhote','Jovem','Adulto'];
const ISLEPETS=[['cat','rabbit'],['wolf','fox'],['rabbit','snake'],['fox','cat'],['snake','bear'],['wolf','bear'],['rabbit','eagle'],['eagle','fox'],['snake','eagle'],['bear','wolf'],[],['eagle','snake'],['eagle','fox'],['eagle','cat'],[]];
const HELPERS=[
 {id:1,isl:1,name:'Kara, a Caçadora',cls:'arqueiro',lvl:5,syn:'arqueiro',col:'#6a8a5a',col2:'#3c5232',hair:'#c8a04a'},
 {id:2,isl:2,name:'Bruno Lenhador',cls:'guerreiro',lvl:7,syn:'guerreiro',col:'#8a5a3a',col2:'#52321e',hair:'#2e2419'},
 {id:3,isl:3,name:'Pedra, o Mineiro',cls:'paladino',lvl:9,syn:'paladino',col:'#7d7466',col2:'#4c463c',hair:'#5a5248'},
 {id:4,isl:4,name:'Ashara, a Pira',cls:'mago',lvl:11,syn:'mago',col:'#8a4a3a',col2:'#52261e',hair:'#e08a4a'},
 {id:5,isl:5,name:'Nyx, a Sombria',cls:'assassino',lvl:13,syn:'assassino',col:'#3c3c58',col2:'#1e1e30',hair:'#2c2c44'},
 {id:6,isl:6,name:'Ymir do Gelo',cls:'druida',lvl:15,syn:'druida',col:'#7a9ab0',col2:'#44607a',hair:'#e8f0f4'},
 {id:7,isl:7,name:'Selka Domadora',cls:'druida',lvl:17,syn:'druida',col:'#5a7a4a',col2:'#324c28',hair:'#8a5a3a'},
 {id:8,isl:8,name:'Vipera',cls:'necromante',lvl:20,syn:'necromante',col:'#4a3a6a',col2:'#28203c',hair:'#6a4a8a'},
 {id:9,isl:9,name:'Sir Aldric',cls:'guerreiro',lvl:23,syn:'guerreiro',col:'#8a6a3a',col2:'#523e1e',hair:'#d8d2c0'},
 {id:10,isl:11,name:'Lumen',cls:'mago',lvl:26,syn:'mago',col:'#c8d8f0',col2:'#8a9ab8',hair:'#f0e8d0'},
 {id:11,isl:12,name:'Justícia',cls:'paladino',lvl:29,syn:'paladino',col:'#e8e4da',col2:'#a8a296',hair:'#f0ead8'},
 {id:12,isl:13,name:'Aurora',cls:'arqueiro',lvl:32,syn:'arqueiro',col:'#ffe8c8',col2:'#c8a878',hair:'#ffd88a'},
];
const ROMSTAGES=['Desconhecido','Amigo','Próximo','Apaixonado','AMOR ❤'];
const ROMLINES=[
 ['“Hmm? Poucos chegam até aqui.”','“Você luta bem.”','“Não confio em forasteiros.”'],
 ['“Que bom te ver.”','“Se sobreviver, me conta.”','“Você é diferente.”'],
 ['“Guardei um lugar pra você.”','“Quando você some, a ilha fica quieta.”','“Já lutei ao lado de muitos.”'],
 ['“Me leva com você?”','“Meu coração bate mais forte.”','“Não é só gratidão.”'],
 ['“Onde você for, eu vou.”','“Meu amor é sua armadura.”','“Até o Trono.”'],
];
const RESN={wood:'🪵 Madeira',stone:'🪨 Pedra',iron:'🔩 Ferro',mithril:'💠 Mithril',crystal:'🔮 Cristal'};
const RECIPES=[
 {out:'axe',cost:{wood:5},d:'Derruba árvores'},
 {out:'pick',cost:{wood:5,stone:2},d:'Quebra pedras e minério'},
 {out:'a2',cost:{wood:6,stone:3},d:'Armadura de Couro'},
 {out:'a3',cost:{iron:10},d:'Cota de Malha'},
 {out:'a4',cost:{mithril:8,crystal:3},d:'Armadura Real'},
];
Object.keys(WK).forEach(k=>{RECIPES.push({out:'w_'+k+'2',cost:{wood:6,stone:4},d:WK[k].names[1]});RECIPES.push({out:'w_'+k+'3',cost:{iron:12},d:WK[k].names[2]});});
const PAL=[
 {grass:['#5d9c46','#568f42','#63a44b'],sea:'#2f6f92',sand:'#d8c07a',tree:0},
 {grass:['#6f8f6a','#668762','#7a9a74'],sea:'#3a5f74',sand:'#b7b7a0',tree:0},
 {grass:['#a8853f','#9c7a38','#b8944a'],sea:'#2f6f92',sand:'#d8b06a',tree:1},
 {grass:['#8f8a70','#87816a','#9a947c'],sea:'#31586e',sand:'#a89a7a',tree:0},
 {grass:['#6e5a4a','#665244','#7a6452'],sea:'#3a4a5a',sand:'#8a7a66',tree:0},
 {grass:['#3f5a4a','#385244','#476454'],sea:'#1f3a4a',sand:'#5a6a62',tree:2},
 {grass:['#aebec8','#a2b2be','#bccad2'],sea:'#3a6a8a',sand:'#e0e8ee',tree:0},
 {grass:['#8a6a52','#816248','#94725a'],sea:'#2f5a6e',sand:'#b0926a',tree:0},
 {grass:['#3a3a48','#34343f','#434352'],sea:'#141e30',sand:'#4a4a58',tree:2},
 {grass:['#4a3a56','#443450','#523f60'],sea:'#0e1424',sand:'#5c4a68',tree:2},
 {grass:['#6a5a9a','#625290','#7464a8'],sea:'#141030',sand:'#8a7ac0',tree:0},
 {grass:['#dde8f2','#d2ddec','#e8f0f8'],sea:'#9fc8ea',sand:'#ffffff',tree:0,sky:1},
 {grass:['#d2def0','#c6d4ea','#dee8f6'],sea:'#8fb8e4',sand:'#f4f8ff',tree:0,sky:1},
 {grass:['#e6def2','#dcd2ea','#efe8f8'],sea:'#a8a0d8',sand:'#ffffff',tree:0,sky:1},
 {grass:['#3a3226','#342c20','#423a2c'],sea:'#0a0810',sand:'#5c5236',tree:0,dark:1},
];
const CAVEPAL={grass:['#4a4250','#443c4a','#50485a'],sea:'#191520',sand:'#3a3440',tree:0};
const ISLES=[
 {name:'Ilha de Valdória',sub:'A ilha principal',size:96,radF:.44,hint:'Estaleiro: 250 ouros.',
  mobs:[{v:'slime',n:'Slime',lvl:1,c:'#6fce5c',cnt:12},{v:'wolf',n:'Lobo',lvl:3,c:'#8a8f98',cnt:8},{v:'wildcat',n:'Gato',lvl:2,c:'#b08a5a',cnt:4},{v:'bandit',n:'Salteador',lvl:3,c:'#6a5a44',cnt:3},{v:'boar',n:'Javali',lvl:3,c:'#8a6a4a',cnt:4}],boss:null,rec:'Nv 1+'},
 {name:'Brumal',sub:'Névoa e uivos',size:66,radF:.40,hint:'Alfa Sangrento.',
  mobs:[{v:'wolf',n:'Lobo da Névoa',lvl:4,c:'#7a8a96',cnt:7},{v:'bat',n:'Morcego',lvl:5,c:'#5a5470',cnt:6},{v:'bandit',n:'Salteador',lvl:5,c:'#5a4a56',cnt:3},{v:'spider',n:'Aranha',lvl:5,c:'#4a4a52',cnt:4},{v:'boar',n:'Javali',lvl:6,c:'#6a6a72',cnt:3}],boss:{v:'wolf',n:'Alfa Sangrento',lvl:8,c:'#6b4646',bs:'alphawolf',arts:['charge','howl','summon','frenzy','nova','slam'],drop:'bw1'},rec:'Nv 4+'},
 {name:'Folimbar',sub:'Outono eterno',size:66,radF:.40,hint:'Carvalho Errante.',
  mobs:[{v:'slime',n:'Slime Âmbar',lvl:5,c:'#d8a03a',cnt:6},{v:'boar',n:'Javali',lvl:6,c:'#a05a3a',cnt:6},{v:'myconid',n:'Micônico',lvl:6,c:'#c87a5a',cnt:5},{v:'wildcat',n:'Gato',lvl:5,c:'#8a6a4a',cnt:3},{v:'spider',n:'Viúva',lvl:7,c:'#8a4a2a',cnt:3}],boss:{v:'golem',n:'Carvalho Errante',lvl:10,c:'#7a5c38',bs:'treant',arts:['slam','nova','summon','heal','spikes','charge'],drop:'bw2'},rec:'Nv 6+'},
 {name:'Cavernosa',sub:'Ecos de pedra',size:68,radF:.40,hint:'Rei de Pedra.',
  mobs:[{v:'bat',n:'Morcego',lvl:7,c:'#4c4658',cnt:6},{v:'spider',n:'Aranha',lvl:7,c:'#4a3a3a',cnt:5},{v:'golem',n:'Golem',lvl:8,c:'#7d7466',cnt:5},{v:'skeleton',n:'Esqueleto',lvl:8,c:'#e8e4da',cnt:4},{v:'myconid',n:'Micônico',lvl:8,c:'#b8a890',cnt:3}],boss:{v:'golem',n:'Rei de Pedra',lvl:12,c:'#8f8574',bs:'stoneking',arts:['slam','volley','leap','spikes','summon','frenzy'],drop:'bw3'},rec:'Nv 8+'},
 {name:'Cinzenta',sub:'Cinzas como neve',size:68,radF:.40,hint:'Senhor das Cinzas.',
  mobs:[{v:'spirit',n:'Espectro',lvl:9,c:'#c8b8a8',cnt:6},{v:'golem',n:'Golem',lvl:10,c:'#4c4844',cnt:5},{v:'cultist',n:'Cultista',lvl:10,c:'#8a4a3a',cnt:4},{v:'elemental',n:'Elemental',lvl:10,c:'#e08a4a',cnt:3},{v:'voidling',n:'Rastejante',lvl:10,c:'#3a3234',cnt:3}],boss:{v:'dragon',n:'Senhor das Cinzas',lvl:14,c:'#8a5a4a',bs:'ashlord',arts:['volley','charge','nova','spikes','summon','frenzy'],drop:'bw4'},rec:'Nv 10+'},
 {name:'Umbral',sub:'Floresta da sombra',size:70,radF:.40,hint:'Lobo Primordial.',
  mobs:[{v:'spirit',n:'Espectro',lvl:11,c:'#7a8ac8',cnt:6},{v:'wolf',n:'Lobo',lvl:12,c:'#3c3c4c',cnt:6},{v:'cultist',n:'Cultista',lvl:12,c:'#3c3c58',cnt:4},{v:'wildcat',n:'Pantera',lvl:11,c:'#2c2c34',cnt:3},{v:'spider',n:'Tecelã',lvl:12,c:'#2c2c3c',cnt:3}],boss:{v:'wolf',n:'Lobo Primordial',lvl:16,c:'#2c2c3e',bs:'primalwolf',arts:['charge','leap','howl','nova','frenzy','summon'],drop:'bw5'},rec:'Nv 12+'},
 {name:'Gélida',sub:'Inverno eterno',size:70,radF:.40,hint:'Colosso de Gelo.',
  mobs:[{v:'golem',n:'Golem',lvl:13,c:'#9fc8dc',cnt:6},{v:'elemental',n:'Elemental',lvl:13,c:'#b8e0f0',cnt:5},{v:'bat',n:'Morcego',lvl:13,c:'#6a8a9c',cnt:5},{v:'wolf',n:'Lobo',lvl:13,c:'#d8e4ec',cnt:4},{v:'skeleton',n:'Esqueleto',lvl:14,c:'#c8d8e4',cnt:3}],boss:{v:'golem',n:'Colosso de Gelo',lvl:18,c:'#b8dcec',bs:'icecolossus',arts:['slam','leap','volley','nova','spikes','heal'],drop:'bw6'},rec:'Nv 14+'},
 {name:'Serpentina',sub:'Ninhos de dragão',size:72,radF:.40,hint:'Matriarca.',
  mobs:[{v:'dragon',n:'Filhote de Dragão',lvl:14,c:'#6a8a4a',cnt:6},{v:'spirit',n:'Espírito',lvl:15,c:'#e89a4a',cnt:5},{v:'skeleton',n:'Esqueleto',lvl:15,c:'#d8d2c0',cnt:4},{v:'elemental',n:'Elemental',lvl:15,c:'#e8a05a',cnt:3},{v:'wildcat',n:'Pantera',lvl:14,c:'#5a7a4a',cnt:3}],boss:{v:'dragon',n:'Matriarca',lvl:21,c:'#4a7a5a',bs:'matriarch',arts:['volley','charge','spikes','nova','summon','frenzy'],drop:'bw7'},rec:'Nv 16+'},
 {name:'Abismo',sub:'Mar sem cor',size:72,radF:.40,hint:'Arauto do Vazio.',
  mobs:[{v:'spirit',n:'Espectro',lvl:16,c:'#4a6a8a',cnt:6},{v:'voidling',n:'Rastejante',lvl:16,c:'#3a2a4c',cnt:5},{v:'golem',n:'Golem',lvl:16,c:'#3a3a4c',cnt:5},{v:'cultist',n:'Profeta',lvl:17,c:'#4a3a6a',cnt:3},{v:'bat',n:'Morcego',lvl:16,c:'#2c2c44',cnt:4}],boss:{v:'spirit',n:'Arauto do Vazio',lvl:24,c:'#8a6ac8',bs:'herald',arts:['volley','spikes','nova','leap','summon','heal'],drop:'bw8'},rec:'Nv 19+'},
 {name:'Coroa Final',sub:'Último degrau',size:74,radF:.40,hint:'Guardião.',
  mobs:[{v:'dragon',n:'Dragão',lvl:18,c:'#8a6a3a',cnt:6},{v:'seraph',n:'Serafim',lvl:18,c:'#e8d8a0',cnt:4},{v:'bandit',n:'Desertor',lvl:18,c:'#8a6a3a',cnt:3},{v:'golem',n:'Golem',lvl:19,c:'#c8a84a',cnt:3},{v:'elemental',n:'Elemental',lvl:18,c:'#e0c06a',cnt:3}],boss:{v:'dragon',n:'Guardião',lvl:28,c:'#c8a83a',bs:'crownkeeper',arts:['charge','slam','volley','leap','frenzy','spikes'],drop:'bw9'},rec:'Nv 22+'},
 {name:'Marco do Portal',sub:'Véu entre mundos',size:42,radF:.38,hint:'Portal ao Éter.',mobs:[],boss:null,rec:'—'},
 {name:'Éter',sub:'Primeira ilha do céu',size:64,radF:.40,sky:1,hint:'Serafim Caído.',
  mobs:[{v:'spirit',n:'Espírito',lvl:22,c:'#cfe0ff',cnt:6},{v:'bat',n:'Morcego',lvl:22,c:'#aab8d8',cnt:5},{v:'seraph',n:'Serafim',lvl:22,c:'#e8e0c8',cnt:4},{v:'elemental',n:'Elemental',lvl:22,c:'#f0e8d0',cnt:3},{v:'voidling',n:'Mácula',lvl:23,c:'#6a6a8a',cnt:3}],boss:{v:'spirit',n:'Serafim Caído',lvl:30,c:'#ffd88a',bs:'seraphboss',arts:['volley','nova','leap','spikes','heal','summon'],drop:'bw10'},rec:'Nv 24+'},
 {name:'Zênite',sub:'Estátuas vivas',size:64,radF:.40,sky:1,hint:'Arcanjo de Mármore.',
  mobs:[{v:'golem',n:'Golem',lvl:24,c:'#e8e4da',cnt:6},{v:'spirit',n:'Vigia',lvl:25,c:'#bfe8e0',cnt:5},{v:'seraph',n:'Serafim',lvl:25,c:'#ffe8c8',cnt:4},{v:'elemental',n:'Elemental',lvl:25,c:'#dfe8f0',cnt:3},{v:'skeleton',n:'Relíquia',lvl:25,c:'#f0ead8',cnt:3}],boss:{v:'golem',n:'Arcanjo',lvl:33,c:'#f2ead8',bs:'marblearch',arts:['slam','volley','leap','nova','frenzy','spikes'],drop:'bw11'},rec:'Nv 27+'},
 {name:'Borda do Céu',sub:'Último chão',size:66,radF:.40,sky:1,hint:'Vigia do Trono.',
  mobs:[{v:'dragon',n:'Dragão',lvl:26,c:'#cfe8f8',cnt:6},{v:'spirit',n:'Alma',lvl:27,c:'#fff0b8',cnt:5},{v:'seraph',n:'Serafim',lvl:27,c:'#ffe8c8',cnt:4},{v:'elemental',n:'Elemental',lvl:27,c:'#ffe0a0',cnt:3},{v:'voidling',n:'Sombra',lvl:26,c:'#8a8aa0',cnt:3}],boss:{v:'dragon',n:'Vigia',lvl:36,c:'#f0e0a0',bs:'vigil',arts:['volley','spikes','charge','nova','leap','frenzy'],drop:'bw12'},rec:'Nv 30+'},
 {name:'Trono do Criador',sub:'O fim',size:48,radF:.38,dark:1,hint:'',mobs:[],boss:null,final:1,rec:'???'},
];
const AMBK=[['butterfly','butterfly','firefly'],['firefly','leaf'],['leaf','leaf','butterfly'],['firefly','firefly'],['ember','ember'],['firefly','firefly'],['snow','snow'],['ember','butterfly'],['firefly','ember'],['petal','firefly'],['petal','firefly'],['petal','petal','firefly'],['petal','petal','firefly'],['petal','firefly'],['ember','ember']];
const GENLINES=['Quem mata o Criador senta no Trono.','Vi um barco fantasma.','Compre poções antes do chefe.','Meu primo foi pra próxima ilha.','A fogueira afasta sombras.','A mina tem minério bom.','Cada chefe guarda uma arma.','Adotei um filhote.'];
const VILNAMES=['Náufrago Tião','Viajante Iris','Ermitã Nara','Caçador Bolso','Andarilha Lune','Velho Otto','Batedora Kess','Sentinela Aya','Peregrino Sol','Curandeira Mira'];
function flines(id){const arr=[ISLES[id].hint||'Que os ventos o ajudem.'];for(let k=0;k<3;k++)arr.push(pick(GENLINES));return arr;}
const QUESTS=[];
QUESTS.push({id:'q0a',isl:0,role:'guide',name:'Praga Verde',kind:'kill',mob:'slime',need:8,xp:70,gold:50,txt:'Derrote 8 Slimes.'});
QUESTS.push({id:'q0b',isl:0,role:'shop',name:'Uivos',kind:'kill',mob:'wolf',need:5,xp:90,gold:70,txt:'Derrote 5 Lobos.'});
QUESTS.push({id:'q0c',isl:0,role:'fish',name:'Roubadores',kind:'kill',mob:'bandit',need:3,xp:120,gold:90,txt:'Derrote 3 Salteadores.'});
QUESTS.push({id:'q0d',isl:0,role:'kid',name:'Aposta',kind:'gold',need:120,xp:90,gold:0,item:'php',iq:3,txt:'Traga 120 de ouro.'});
QUESTS.push({id:'q0e',isl:0,role:'villager',name:'Gatos',kind:'kill',mob:'wildcat',need:3,xp:80,gold:60,txt:'Afaste 3 Gatos.'});
for(let i=1;i<=9;i++){
  QUESTS.push({id:'q'+i+'f',isl:i,role:'flavor',name:'Limpeza em '+ISLES[i].name,kind:'kill',mob:ISLES[i].mobs[0].v,need:6,xp:60+i*40,gold:50+i*25,txt:'Derrote 6 de '+ISLES[i].mobs[0].n+'.'});
  QUESTS.push({id:'q'+i+'m',isl:i,role:'shop',name:'Contrato',kind:'kill',mob:'any',need:8,xp:80+i*45,gold:80+i*30,txt:'Derrote 8 criaturas.'});
}
for(let i=11;i<=13;i++){
  QUESTS.push({id:'q'+i+'f',isl:i,role:'flavor',name:'Paz nos Céus',kind:'kill',mob:ISLES[i].mobs[0].v,need:8,xp:200+(i-11)*120,gold:200+(i-11)*80,txt:'Derrote 8 '+ISLES[i].mobs[0].n+'.'});
}
function questFor(npcRole,isl){for(const q of QUESTS)if(q.role===npcRole&&q.isl===isl)return q;return null;}
function shopStock(isl){
  const arr=['php','pmp','gift'];
  const at=isl<=1?2:isl<=4?3:4;
  arr.push('a'+Math.min(at,4));if(at>=3)arr.push('a2');
  const wt=isl===0?1:isl<=3?2:isl<=6?3:4;
  Object.keys(WK).forEach(k=>{const t=Math.min(wt,3);arr.push('w_'+k+t);if(t>=2)arr.push('w_'+k+(t-1));});
  if(isl>=1&&isl<=9)arr.push('ix'+isl);
  if(isl>=11&&isl<=13)arr.push('ix'+isl);
  return arr;
}

let state='lobby',time=0,dayT=.3,cineT=0;
let ZN=null,zones={};
let p=null,keys={},cam={x:0,y:0},shake=0;
let projs=[],parts=[],ftexts=[],fx=[],birds=[];
let prog={boat:false,kills:{},godSlain:false};
let meta={cycle:1,prevName:'',prevCls:'guerreiro',prevAp:null};
let hbSlots=[],hbBuilt=false;

/* ================= MULTIPLAYER P2P CORRIGIDO ================= */
let NET={on:false,peer:null,conn:null,isHost:false,roomId:null,players:{},posT:0};
function zoneKey(){return ZN?(ZN.isCave?((ZN.isMine?'m':'c')+ZN.parent):String(ZN.id)):'0';}
function netStatus(msg,cls){const el=$('onlineLbl');if(el){el.textContent=msg;el.style.color='';}}
function netSetup(mode,roomId){
  // MODO SOLO: não inicia PeerJS, evita erros
  if(mode==='solo'){
    NET.on=false;NET.peer=null;NET.conn=null;
    netStatus('🟡 solo');
    console.log('[NET] Modo solo ativado');
    return;
  }
  // Cria/entra em sala
  NET.roomId=roomId;
  NET.isHost=(mode==='host');
  const prefix='aeterna-v7-';
  const peerId=NET.isHost?(prefix+roomId):null;
  
  console.log('[NET] Iniciando modo:',mode,'roomId:',roomId,'peerId:',peerId);
  netStatus(NET.isHost?('🟡 criando '+roomId+'...'):('🟡 entrando em '+roomId+'...'));
  
  // Verifica se PeerJS carregou
  if(typeof Peer==='undefined'){
    console.warn('[NET] PeerJS não carregou, jogando solo');
    netStatus('⚠ multiplayer indisponível');
    return;
  }
  
  try{
    NET.peer=new Peer(peerId,{
      debug:0,
      config:{iceServers:[
        {urls:'stun:stun.l.google.com:19302'},
        {urls:'stun:global.stun.twilio.com:3478'},
        {urls:'stun:stun1.l.google.com:19302'}
      ]}
    });
  }catch(e){
    console.error('[NET] Falha ao criar peer:',e);
    netStatus('⚠ erro de conexão');
    NET.on=false;
    return;
  }
  
  NET.peer.on('open',id=>{
    console.log('[NET] Peer aberto:',id);
    if(NET.isHost){
      NET.on=true;
      netStatus('🟢 sala '+roomId,'#8fe08f');
    }else{
      netStatus('🟡 conectando...','#9fe8ff');
    }
  });
  
  NET.peer.on('connection',conn=>{
    console.log('[NET] Conexão recebida');
    if(NET.conn){try{conn.close();}catch(e){}return;}
    NET.conn=conn;
    setupConn(conn);
  });
  
  NET.peer.on('error',err=>{
    console.warn('[NET] Erro:',err.type,err);
    if(err.type==='unavailable'){
      netStatus('❌ ID em uso','#ff8a7a');
    }else if(err.type==='peer-unavailable'){
      netStatus('❌ sala não encontrada','#ff8a7a');
    }else if(err.type==='network'||err.type==='server-error'){
      netStatus('⚠ rede instável','#ffb3a7');
    }else{
      netStatus('⚠ erro: '+err.type,'#ffb3a7');
    }
  });
  
  NET.peer.on('disconnected',()=>{
    console.log('[NET] Desconectado do broker');
    netStatus('🟡 reconectando...','#ffb3a7');
    try{if(NET.peer&&!NET.peer.destroyed)NET.peer.reconnect();}catch(e){}
  });
  
  // Se for cliente, conecta ao host
  if(!NET.isHost){
    setTimeout(()=>{
      if(!NET.peer||NET.peer.destroyed)return;
      try{
        console.log('[NET] Conectando a:',prefix+roomId);
        const conn=NET.peer.connect(prefix+roomId,{reliable:true});
        NET.conn=conn;
        setupConn(conn);
      }catch(e){
        console.error('[NET] Falha ao conectar:',e);
        netStatus('❌ conexão falhou','#ff8a7a');
      }
    },900);
  }
}
function setupConn(conn){
  if(!conn)return;
  conn.on('open',()=>{
    NET.on=true;
    netStatus('🟢 conectado! ('+NET.roomId+')','#8fe08f');
    console.log('[NET] Conectado!');
    chatSys('🌐 <b>Conectado!</b> Cada um tem seu próprio mundo.');
    if(p)netSend({t:'join',name:p.name,cls:p.cls,ap:p.ap,lvl:p.lvl,z:zoneKey()});
  });
  conn.on('data',d=>{
    let m;try{m=typeof d==='string'?JSON.parse(d):d;}catch(e){return;}
    netMsg(m);
  });
  conn.on('error',err=>{
    console.warn('[NET] Erro na conexão:',err);
  });
  conn.on('close',()=>{
    NET.on=false;NET.conn=null;
    netStatus('🟡 desconectado','#ffb3a7');
    chatSys('🌐 Conexão perdida.');
  });
}
function netSend(o){try{if(NET.on&&NET.conn&&NET.conn.open)NET.conn.send(JSON.stringify(o));}catch(e){}}
function netMsg(m){
  if(!m||!m.t)return;
  if(m.t==='join')chatSys('🌐 <b>'+esc(m.n||'?')+'</b> entrou');
  else if(m.t==='chat')chatSay(m.n||'?',m.txt||'');
  else if(m.t==='event')chatSys(m.txt||'');
  else if(m.t==='pos'){
    const id='peer';
    let o=NET.players[id];
    if(!o){o=NET.players[id]={x:m.x,y:m.y,tx:m.x,ty:m.y,wk:0};}
    o.tx=m.x;o.ty=m.y;o.z=String(m.z);o.l=m.lvl;o.n=m.n;o.c=m.cls;o.ap=m.ap;
  }
}
function chatSay(n,txt){const log=$('chatLog');if(!log)return;const d=document.createElement('div');d.className='e';d.innerHTML='<b style="color:#9fe8ff">'+esc(n)+':</b> '+esc(txt);log.appendChild(d);while(log.children.length>50)log.removeChild(log.firstChild);log.scrollTop=log.scrollHeight;}
function chatSys(h){const log=$('chatLog');if(!log)return;const d=document.createElement('div');d.className='e sys';d.innerHTML=h;log.appendChild(d);while(log.children.length>50)log.removeChild(log.firstChild);log.scrollTop=log.scrollHeight;}
function chatMe(txt){const log=$('chatLog');if(!log||!p)return;const d=document.createElement('div');d.className='e';d.innerHTML='<b style="color:#ffd76a">'+esc(p.name)+':</b> '+esc(txt);log.appendChild(d);while(log.children.length>50)log.removeChild(log.firstChild);log.scrollTop=log.scrollHeight;}

const SKEY='aeterna_hero_v7',MKEY='aeterna_meta_v3',PKEY='aeterna_prog_v3';
function saveHero(){if(!p||state!=='play')return;try{localStorage.setItem(SKEY,JSON.stringify({v:7,name:p.name,cls:p.cls,lvl:p.lvl,xp:p.xp,gold:p.gold,inv:p.inv,wpn:p.wpn,arm:p.arm,ap:p.ap,island:ZN&&!ZN.isCave?ZN.id:(ZN?ZN.parent:0),tp:p.tp,tree:p.tree,pets:p.pets,petIdx:p.petIdx,allies:p.allies,allyIdx:p.allyIdx,rom:p.rom,qs:p.qs,res:p.res}));}catch(e){}}
function saveProg(){try{localStorage.setItem(PKEY,JSON.stringify(prog));}catch(e){}}
function saveMeta(){try{localStorage.setItem(MKEY,JSON.stringify(meta));}catch(e){}}
function loadJSON(k){try{return JSON.parse(localStorage.getItem(k));}catch(e){return null;}}

function activePet(){if(p.petIdx===null||p.petIdx<0||!p.pets[p.petIdx])return null;return p.pets[p.petIdx];}
function petStage(pt){return pt.lvl>=10?2:pt.lvl>=5?1:0;}
function petBuffVal(k){const pt=activePet();if(!pt)return 0;const pk=PETKINDS[pt.kind];if(pk.buff!==k)return 0;return pk.vals[petStage(pt)];}
function petNeed(l){return 40+l*30;}
function activeAlly(){if(p.allyIdx===null||p.allyIdx<0)return null;const id=p.allies[p.allyIdx];return HELPERS.find(h=>h.id===id)||null;}
function romStage(v){return v>=70?4:v>=45?3:v>=25?2:v>=10?1:0;}
function romVal(id){return p.rom[id]||0;}
function lvFactor(ml){const d=p.lvl-ml;if(d<=4)return 1;return Math.max(.25,1-(d-4)*.12);}

function findFreeZ(z,x0,y0,x1,y1){for(let i=0;i<90;i++){const x=RI(x0,x1)*T+T/2,y=RI(y0,y1)*T+T/2;if(!blockedZ(z,x,y,10))return{x,y};}return{x:z.cx*T,y:z.cy*T};}
function blockedZ(z,px,py,r){r=r||9;const S=z.S;
  const at=(x,y)=>{const tx=Math.floor(x/T),ty=Math.floor(y/T);if(tx<0||ty<0||tx>=S||ty>=S)return 1;return z.map[ty][tx];};
  const bad=t=>t===1||t===5||t===6||t===9;
  if(bad(at(px-r,py-r))||bad(at(px+r,py-r))||bad(at(px-r,py+r))||bad(at(px+r,py+r)))return true;
  for(const b of z.buildings){if(px+r>b.x*T&&px-r<(b.x+b.w)*T&&py+r>b.y*T&&py-r<(b.y+b.h)*T)return true;}
  return false;}
function genZone(id){
  const cfg=ISLES[id],S=cfg.size,pal=PAL[id],seed=id*77+13;
  const z={id,cfg,S,pal,map:[],shade:[],decor:[],buildings:[],npcs:[],mobs:[],critters:[],campfires:[],hits:{},amb:[],ambKinds:AMBK[id]||['butterfly'],cx:S>>1,cy:S>>1};
  const rad=S*cfg.radF;
  for(let y=0;y<S;y++){z.map.push([]);z.shade.push([]);
    for(let x=0;x<S;x++){
      const nz=(h2(x*.14+seed,y*.14+seed)-.5)*S*.22;
      const d=Math.hypot(x-z.cx,y-z.cy)+nz;
      let t=1;if(d<rad-2)t=2;if(d<rad-4)t=0;
      z.map[y][x]=t;z.shade[y][x]=Math.random();
    }}
  const seaSeen=Array.from({length:S},()=>Array(S).fill(false));
  const qq=[];
  for(let x=0;x<S;x++){
    if(z.map[0][x]===1){seaSeen[0][x]=true;qq.push(x,0);}
    if(z.map[S-1][x]===1){seaSeen[S-1][x]=true;qq.push(x,S-1);}
  }
  for(let y=0;y<S;y++){
    if(z.map[y][0]===1){seaSeen[y][0]=true;qq.push(0,y);}
    if(z.map[y][S-1]===1){seaSeen[y][S-1]=true;qq.push(S-1,y);}
  }
  let qi=0;
  while(qi<qq.length){const x=qq[qi++],y=qq[qi++];
    const nb=[[1,0],[-1,0],[0,1],[0,-1]];
    for(let k=0;k<4;k++){const nx=x+nb[k][0],ny=y+nb[k][1];
      if(nx>=0&&ny>=0&&nx<S&&ny<S&&!seaSeen[ny][nx]&&z.map[ny][nx]===1){seaSeen[ny][nx]=true;qq.push(nx,ny);}}}
  for(let y=0;y<S;y++)for(let x=0;x<S;x++)if(z.map[y][x]===1&&!seaSeen[y][x])z.map[y][x]=0;
  const cx=z.cx,cy=z.cy;
  const clearRect=(x0,y0,x1,y1)=>{for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++){if(!z.map[y])continue;const t=z.map[y][x];if(t===5||t===6)z.map[y][x]=0;}};
  const forceClear=(x0,y0,x1,y1)=>{for(let y=y0;y<=y1;y++)for(let x=x0;x<=x1;x++){if(!z.map[y])continue;const t=z.map[y][x];if(t===1||t===5||t===6)z.map[y][x]=0;}};
  const carve=(x,y)=>{if(z.map[y]&&(z.map[y][x]===0||z.map[y][x]===1||z.map[y][x]===2||z.map[y][x]===5||z.map[y][x]===6))z.map[y][x]=3;};
  const mkNpc=(name,ico,x,y,role,lines)=>{const n={name,ico,x,y,home:{x,y},role,wt:R(1,3),wx:x,wy:y,lines:lines||null};
    forceClear(Math.floor(x/T)-1,Math.floor(y/T)-1,Math.floor(x/T)+1,Math.floor(y/T)+1);
    z.npcs.push(n);return n;};
  const addBuilding=(b)=>{z.buildings.push(b);forceClear(b.x-1,b.y-1,b.x+b.w,b.y+b.h);};
  const dockX=cx;const shoreY=Math.floor(cy+rad)-3;
  for(let y=shoreY;y<=Math.min(S-2,shoreY+6);y++){z.map[y][dockX]=3;z.map[y][dockX+1]=3;}
  z.dock={x:dockX*T+T,y:(Math.min(S-2,shoreY+6))*T+T/2};
  const topY=Math.max(6,Math.floor(cy-rad)+7);
  for(let y=topY;y<=Math.min(S-2,shoreY+6);y++){carve(cx-1,y);carve(cx,y);}
  const dens=pal.sky?.02:(id===9?.08:.045);
  for(let y=2;y<S-2;y++)for(let x=2;x<S-2;x++){
    if(z.map[y][x]!==0)continue;
    if(Math.abs(x-dockX)<3&&y>topY)continue;
    if(id===0&&Math.abs(x-cx)<9&&Math.abs(y-(cy+4))<7)continue;
    const r=Math.random();
    if(r<dens)z.map[y][x]=5;
    else if(r<dens+.015)z.map[y][x]=6;
  }
  if(id===0){
    for(let y=cy+1;y<=cy+7;y++)for(let x=cx-6;x<=cx+6;x++)z.map[y][x]=4;
    addBuilding({x:cx-6,y:cy+1,w:3,h:2,kind:'house'});addBuilding({x:cx+3,y:cy+1,w:3,h:2,kind:'house'});
    addBuilding({x:cx-6,y:cy+6,w:3,h:2,kind:'house'});addBuilding({x:cx+3,y:cy+6,w:3,h:2,kind:'house'});
    addBuilding({x:cx+1,y:cy+4,w:2,h:1,kind:'stall'});
    addBuilding({x:dockX-5,y:shoreY-2,w:2,h:1,kind:'house'});addBuilding({x:dockX+3,y:shoreY-2,w:2,h:1,kind:'house'});
    clearRect(cx-8,cy-2,cx+8,cy+9);
    z.campfires.push({x:(cx-3)*T,y:(cy+3)*T});
    mkNpc('Mercadora Mira','🛍️',(cx-2)*T,(cy+5)*T+10,'shop');
    mkNpc('Ancelmo, o Ancião','🧙',(cx+2)*T,(cy+2)*T+10,'guide');
    mkNpc('Estaleiro do Cais','⚓',dockX*T-T,(shoreY-2)*T,'boat');
    mkNpc('Pescador Caio','🎣',(dockX+2)*T,(shoreY-1)*T,'fish',flines(0));
    mkNpc('Aldeã Bela','🧺',(cx-4)*T,(cy+3)*T,'villager',flines(0));
    mkNpc('Garoto Tico','🪁',(cx+5)*T,(cy+7)*T,'kid',['Slimes dão ouro!','Sonho ser capitão.','Aposto 120 de ouro!']);
    z.spawn={x:cx*T,y:(cy+8)*T};
  }else{
    z.spawn={x:cx*T,y:(shoreY-2)*T};
    if(id>0&&id<10){
      clearRect(cx-7,shoreY-6,cx+7,shoreY-2);
      if(id<=4){addBuilding({x:cx-6,y:shoreY-5,w:2,h:1,kind:'house'});addBuilding({x:cx+4,y:shoreY-5,w:2,h:1,kind:'house'});}
      else{addBuilding({x:cx-6,y:shoreY-5,w:2,h:1,kind:'tent'});addBuilding({x:cx+4,y:shoreY-5,w:2,h:1,kind:'tent'});}
      z.campfires.push({x:(cx+1)*T,y:(shoreY-3)*T});
      mkNpc('Mercador Errante','🧳',(cx+4)*T,(shoreY-3)*T,'shop');
      mkNpc(pick(VILNAMES),'🧑‍🌾',(cx-4)*T,(shoreY-3)*T,'flavor',flines(id));
    }
    if(id>=11&&id<=13){
      z.campfires.push({x:(cx+1)*T,y:(shoreY-4)*T});
      mkNpc('Alma Errante','🕊️',(cx+3)*T,(shoreY-3)*T,'flavor',flines(id));
    }
  }
  clearRect(cx-7,topY-4,cx+7,topY+4);
  z.arena={x:cx*T,y:topY*T};
  if(id<10){const cpt=findFreeZ(z,Math.floor(cx+rad*.45),Math.max(4,Math.floor(cy-rad*.6)),Math.min(S-4,Math.floor(cx+rad*.8)),Math.floor(cy-rad*.2));
    z.caveDoor={x:cpt.x,y:cpt.y};
    const mpt=findFreeZ(z,Math.floor(cx-rad*.7),Math.max(4,Math.floor(cy-rad*.6)),Math.floor(cx-rad*.2),Math.floor(cy-rad*.2));
    z.mineDoor={x:mpt.x,y:mpt.y};}
  if(id===10){for(let y=cy-2;y<=cy+2;y++)for(let x=cx-2;x<=cx+2;x++)z.map[y][x]=4;z.portal={x:cx*T,y:cy*T};}
  if(id===14){for(let y=topY-3;y<=topY+3;y++)for(let x=cx-5;x<=cx+5;x++)z.map[y][x]=4;z.throne={x:cx*T,y:(topY-2)*T};}
  if(cfg.mobs)cfg.mobs.forEach(mdef=>{for(let i=0;i<mdef.cnt;i++){
    const pt=findFreeZ(z,4,4,S-4,S-10);
    z.mobs.push(makeMob(mdef.v,mdef.n,mdef.lvl,mdef.c,pt.x,pt.y,1,false));}});
  if(id>=1&&id<=9){const pv2=cfg.mobs[0].lvl>=10?'skeleton':'bandit',pn=pv2==='skeleton'?'Esqueleto':'Patrulheiro';
    for(let g2=0;g2<2;g2++){const py=R(topY+8,shoreY-8)*T,pc=pick(['#5a4a3a','#4a4a5a','#6a5a44']);
      for(let k=0;k<2;k++)z.mobs.push(makeMob(pv2,pn,cfg.mobs[0].lvl+1,pc,cx*T+R(-60,60),py+R(-40,40),1,false,true));}}
  if(cfg.boss){const b=cfg.boss;const bm=makeMob(b.v,b.n,b.lvl,b.c,z.arena.x,z.arena.y,2.1,true);bm.bs=b.bs;bm.arts=b.arts;bm.drop=b.drop;z.mobs.push(bm);}
  if(id===14){const nm=meta.cycle>1?meta.prevName:'O Criador';
    const lvl=38+meta.cycle*3;
    const fb=makeMob(meta.cycle>1?'darkhero':'god',nm,lvl,'#f2d38a',z.arena.x,z.arena.y,2.2,true);
    fb.final=true;fb.bs=meta.cycle>1?'darkhero':'god';
    fb.arts=['volley','slam','charge','nova','leap','spikes','summon','frenzy'];
    z.mobs.push(fb);}
  (ISLEPETS[id]||[]).forEach(k=>{const pt=findFreeZ(z,6,6,S-6,S-6);
    z.critters.push({kind:k,x:pt.x,y:pt.y,home:{x:pt.x,y:pt.y},wt:0,wx:pt.x,wy:pt.y,dir:1});});
  for(let i=0;i<S*S/40;i++){const x=RI(2,S-3),y=RI(2,S-3);if(z.map[y][x]!==0)continue;
    const k=pal.sky?'star':(pal.dark?'peb':pick(['flower','tuft','peb','mush']));
    z.decor.push({x:x*T+R(4,T-4),y:y*T+R(4,T-4),k,s:Math.random()});}
  return z;
}
function genCave(pid,mine){
  const cfg=ISLES[pid],S=42;
  const z={id:pid,parent:pid,isCave:true,isMine:!!mine,cfg,S,pal:CAVEPAL,map:[],shade:[],decor:[],buildings:[],npcs:[],mobs:[],critters:[],campfires:[],hits:{},amb:[],ambKinds:['firefly'],cx:S>>1,cy:S>>1};
  const rad=S*.40;
  for(let y=0;y<S;y++){z.map.push([]);z.shade.push([]);
    for(let x=0;x<S;x++){
      const nz=(h2(x*.2+pid*31,y*.2)-.5)*6;
      z.map[y][x]=(Math.hypot(x-z.cx,y-z.cy)+nz<rad)?8:6;
      z.shade[y][x]=Math.random();
    }}
  if(mine){let placed=0;
    while(placed<24){const x=RI(4,S-5),y=RI(4,S-5);
      if(z.map[y][x]===8){z.map[y][x]=9;placed++;}}}
  z.spawn={x:z.cx*T,y:(z.cy+rad*.7)*T};
  z.exit={x:z.cx*T,y:(z.cy+rad*.75)*T};
  const baseLvl=(cfg.mobs&&cfg.mobs[0])?cfg.mobs[0].lvl+2:4;
  const mix=mine?['golem','skeleton','elemental']:['skeleton','myconid'].concat(cfg.mobs?cfg.mobs.map(m=>m.v):[]);
  for(let i=0;i<(mine?12:16);i++){
    const pt=findFreeZ(z,Math.floor(z.cx-rad*.7),Math.floor(z.cy-rad*.7),Math.floor(z.cx+rad*.7),Math.floor(z.cy+rad*.7));
    const v=pick(mix);
    z.mobs.push(makeMob(v,{skeleton:'Esqueleto',myconid:'Micônico',golem:'Guardião',elemental:'Elemental'}[v]||'Criatura',baseLvl+RI(0,1),PAL[pid].grass[2],pt.x,pt.y,1,false));
  }
  for(let i=0;i<40;i++){const x=RI(2,S-3),y=RI(2,S-3);if(z.map[y][x]!==8)continue;
    z.decor.push({x:x*T+R(4,T-4),y:y*T+R(4,T-4),k:mine?pick(['crystal','crystal','peb']):pick(['crystal','peb','mush']),s:Math.random()});}
  return z;
}
function makeMob(v,n,lvl,c,x,y,big,boss,patrol){
  const hp=Math.round((30+lvl*16+lvl*lvl*.7)*(boss?6:1));
  const spd=v==='wolf'?130:v==='bat'?140:v==='spirit'?90:v==='dragon'?95:v==='bandit'?115:v==='boar'?120:v==='wildcat'?135:v==='spider'?110:60+lvl*2;
  return {v,n,lvl,c,x,y,big:big||1,boss:!!boss,r:12+(big||1)*5,hp,maxhp:hp,
    atk:Math.round((8+lvl*3.8)*(boss?1.25:1)),def:Math.round(lvl*.5),xp:Math.round(lvl*16+20),
    g:[Math.round(lvl*2.2)+2,Math.round(lvl*3.4)+4],spd:Math.min(spd,150),
    aggro:boss?280:160,home:{x,y},homeR:patrol?160:0,state:'idle',wt:R(1,3),wx:x,wy:y,atkCd:0,artCd:R(3,6),lastArt:'',spCd:R(4,7),
    dead:false,seed:R(0,9),hitT:0,slowT:0,dir:1,respawn:0,spawnPt:{x,y},introDone:false,
    ranged:(v==='cultist'||v==='seraph')};
}
function enterZone(key){
  let z;
  if(typeof key==='string'&&(key[0]==='c'||key[0]==='m')){const pid=parseInt(key.slice(1));if(!zones[key])zones[key]=genCave(pid,key[0]==='m');z=zones[key];}
  else{if(!zones[key])zones[key]=genZone(key);z=zones[key];}
  ZN=z;
  if(p){p.x=z.spawn.x;p.y=z.spawn.y;p.moveTo=null;p.target=null;}
  projs=[];parts=[];ftexts=[];fx=[];birds=[];
  buildMinimap();
  $('zoneName').querySelector('b').textContent=z.isCave?(z.isMine?'Mina de '+z.cfg.name:'Caverna de '+z.cfg.name):z.cfg.name;
  $('zoneName').querySelector('small').textContent=z.isCave?(z.isMine?'Minere com a Picareta':'Fonte de farm'):z.cfg.sub+' · '+z.cfg.rec;
  logSay('📍 <b>'+(z.isCave?(z.isMine?'Mina de ':'Caverna de ')+z.cfg.name:z.cfg.name)+'</b>');
  const vw=world.width/ZOOM,vh=world.height/ZOOM,lim=z.S*T;
  cam.x=lim>vw?clamp(p.x-vw/2,0,lim-vw):(lim-vw)/2;
  cam.y=lim>vh?clamp(p.y-vh/2,0,lim-vh):(lim-vh)/2;
  saveHero();
}
function isUnlocked(id){
  if(id===0)return true;
  if(id===1)return prog.boat;
  if(id<=9)return prog.boat&&!!prog.kills['i'+(id-1)];
  if(id===10)return !!prog.kills['i9'];
  if(id===11)return !!prog.kills['i9'];
  if(id===12)return !!prog.kills['s1'];
  if(id===13)return !!prog.kills['s2'];
  if(id===14)return !!prog.kills['s3'];
  return false;
}
function lockText(id){
  if(id===1)return 'Compre o barco (250 🪙)';
  if(id<=9)return 'Chefe de '+ISLES[id-1].name;
  if(id===10||id===11)return 'Guardião da Coroa';
  if(id===12)return 'Serafim Caído';
  if(id===13)return 'Arcanjo';
  if(id===14)return 'Vigia do Trono';
  return 'Bloqueada';
}
function oreKeyFor(id){return id<=3?'iron':id<=6?'iron':id<=9?'mithril':'crystal';}
function mainStage(){
  if(!prog.boat)return '1. Barco no Estaleiro (250 🪙)';
  for(let i=1;i<=9;i++)if(!prog.kills['i'+i])return '2. Chefe: '+ISLES[i].name;
  if(!prog.kills.s1)return '3. Portal → Serafim';
  if(!prog.kills.s2)return '3. Arcanjo';
  if(!prog.kills.s3)return '3. Vigia';
  if(!prog.godSlain)return '4. Trono: '+(meta.cycle>1?meta.prevName:'Criador');
  return '✦ Ciclo completo.';
}

const STARTWPN={guerreiro:'w_e1',mago:'w_c1',arqueiro:'w_a1',paladino:'w_h1',assassino:'w_d1',druida:'w_c1',necromante:'w_c1',mendigo:null};
function newPlayer(name,cls,ap){
  const mend=cls==='mendigo';
  p={name,cls,ap,x:0,y:0,dir:1,walk:0,lvl:1,xp:0,gold:mend?0:20,inv:mend?[]:[{id:'php',q:3}],
    wpn:STARTWPN[cls]||null,arm:null,target:null,moveTo:null,atkCd:0,atkT:0,
    cds:[0,0,0,0,0,0],buffs:{},dead:false,lastHit:-9,hp:0,mp:0,tp:0,tree:{},pets:[],petIdx:-1,
    allies:[],allyIdx:-1,rom:{},talkCd:{},petX:0,petY:0,petCd:0,allyX:0,allyY:0,allyCd:0,qs:{},
    res:{wood:0,stone:0,iron:0,mithril:0,crystal:0}};
  calcStats();p.hp=p.maxhp;p.mp=p.maxmp;
}
function treeSum(key){let s=0;for(const id in p.tree)if(p.tree[id]){const n=TREETPL.find(t=>t.id===id);if(n&&n.eff[key])s+=n.eff[key];}return s;}
function calcStats(){
  const c=CLASSES[p.cls],L=p.lvl;
  p.maxhp=Math.round(c.hp*(1+(L-1)*.15)*(1+treeSum('hp')));
  p.maxmp=Math.round(c.mp*(1+(L-1)*.10));
  p.atk=Math.round(c.atk*(1+(L-1)*.12)*(1+treeSum('atk')))+(ITEMS[p.wpn]?ITEMS[p.wpn].atk:0);
  p.def=c.def+Math.floor(L/2)+treeSum('def')+(ITEMS[p.arm]?ITEMS[p.arm].def:0);
  const wvk=p.wpn?ITEMS[p.wpn].vk:null;
  p.range=wvk==='bow'?260:wvk==='staff'?235:(wvk?58:(c.ranged?c.range:58));
  p.spd=c.spd*(1+((ITEMS[p.wpn]&&ITEMS[p.wpn].spd)||0))*(1+treeSum('spd'))*(1+petBuffVal('spd'));
  p.hp=Math.min(p.hp||p.maxhp,p.maxhp);
  p.mp=Math.min(p.mp||p.maxmp,p.maxmp);
}
const lvlNeed=l=>Math.round(80*Math.pow(l,1.6));
function gainXP(v0){
  const c=CLASSES[p.cls];
  const v=Math.round(v0*(c.xpMult||1)*(1+petBuffVal('xp'))*(1+treeSum('xp')));
  ftext(p.x,p.y-40,'+'+v+' XP','#ffe08a',14);
  p.xp+=v;
  while(p.xp>=lvlNeed(p.lvl)){
    p.xp-=lvlNeed(p.lvl);p.lvl++;p.tp++;
    calcStats();p.hp=p.maxhp;p.mp=p.maxmp;
    fx.push({k:'ring',x:p.x,y:p.y,col:'#ffd76a',maxR:80,t:0,dur:.8});
    burst(p.x,p.y-14,26,'#ffd76a');sfx.lvl();
    sysSay('🎉 Nível <b>'+p.lvl+'</b>!');
    announce('⬆ Nível '+p.lvl+'!');
    netSend({t:'event',txt:'⬆ '+p.name+' → nível '+p.lvl+'!'});
  }
}

function critChance(){return .10+CLASSES[p.cls].crit+treeSum('crit')+petBuffVal('crit')+((ITEMS[p.wpn]&&ITEMS[p.wpn].crit)||0)+((p.buffs.crit&&p.buffs.crit>time)?.35:0);}
function atkBuff(){let m=1;if(p.buffs.atk&&p.buffs.atk>time)m*=1+(p.buffs.atkPct||.5);const a=activeAlly();if(a&&romVal(a.id)>=70)m*=1.05;return m;}
function playerRaw(mult){mult=mult||1;return p.atk*mult*(1+petBuffVal('dmg'))*atkBuff()*R(.85,1.15);}
function inRange(m){return dist(p.x,p.y,m.x,m.y)<=p.range+m.r;}
function validTarget(){
  if(p.target&&!p.target.dead)return p.target;
  let best=null,bd=1e9;
  for(const m of ZN.mobs)if(!m.dead){const d=dist(p.x,p.y,m.x,m.y);if(d<bd){bd=d;best=m;}}
  return best;
}
function doBasic(){
  if(p.dead||p.atkCd>0)return;
  const t=validTarget();if(!t)return;
  p.target=t;
  if(!inRange(t)){p.moveTo={x:t.x,y:t.y};return;}
  p.moveTo=null;p.atkCd=CLASSES[p.cls].cd;p.atkT=.26;p.dir=t.x>p.x?1:-1;sfx.swing();
  const wvk=p.wpn?ITEMS[p.wpn].vk:(CLASSES[p.cls].ranged?'staff':'fist');
  if(wvk==='bow')projFire(p.x,p.y-16,t,playerRaw(),'arrow',{});
  else if(wvk==='staff')projFire(p.x,p.y-16,t,playerRaw(),CLASSES[p.cls].proj||'bolt',{});
  else hitMob(t,playerRaw(),{});
}
function projFire(x,y,t,dmg,kind,opts){
  const a=Math.atan2(t.y-y,t.x-x),sp=kind==='fire'?360:460;
  projs.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,dmg,kind,life:1.1,from:'p',pierce:opts.pierce,leech:opts.leech||0,hit:new Set()});
}
function hitMob(m,raw,o){
  o=o||{};
  const crit=o.crit||chance(critChance()+(o.critB||0));
  let d=Math.max(1,Math.round(raw-m.def*.6));if(crit)d=Math.round(d*1.7);
  m.hp-=d;m.hitT=.16;
  ftext(m.x+R(-6,6),m.y-m.r-18,String(d),crit?'#ffd76a':'#ffffff',crit?18:14);
  if(crit)sfx.crit();else sfx.hit();
  burst(m.x,m.y-10,4,m.c);
  let leech=o.leech||0;
  if(o.src==='p'){leech=Math.max(leech,treeSum('leech')+petBuffVal('leech')+((ITEMS[p.wpn]&&ITEMS[p.wpn].leech)||0));}
  if(leech&&o.src==='p'){const h=Math.round(d*leech);p.hp=Math.min(p.maxhp,p.hp+h);ftext(p.x,p.y-46,'+'+h,'#7ef28a',12);}
  if(m.hp<=0)killMob(m,'p');
}
function killMob(m,src){
  m.dead=true;
  burst(m.x,m.y-10,16,m.c);
  if(src!=='p')return;
  const c=CLASSES[p.cls],f=lvFactor(m.lvl);
  const g=Math.round(RI(m.g[0],m.g[1])*(c.goldMult||1)*(1+petBuffVal('gold'))*(1+treeSum('gold'))*f);
  p.gold+=g;sfx.coin();
  ftext(m.x,m.y-m.r-34,'+'+g+' 🪙','#ffd76a',12);
  gainXP(Math.round(m.xp*f));
  logSay('Derrotou <b>'+m.n+'</b> (+'+Math.round(m.xp*f)+' XP, +'+g+' 🪙)');
  if(chance(.15))dropItem(chance(.5)?'php':'pmp');
  if(chance(.04))dropItem('gift');
  if(m.boss&&m.drop)dropItem(m.drop);
  const pt=activePet();
  if(pt){pt.xp+=Math.round(m.xp*.4);
    while(pt.xp>=petNeed(pt.lvl)){pt.xp-=petNeed(pt.lvl);const oldSt=petStage(pt);pt.lvl++;
      if(petStage(pt)!==oldSt){announce('🐾 '+PETKINDS[pt.kind].n+' → '+STAGEN[petStage(pt)]+'!');sfx.tame();}
      else sysSay('🐾 '+PETKINDS[pt.kind].n+' → Nv '+pt.lvl);}}
  questKill(m);
  if(m.boss){
    sfx.boom();shake=8;
    announce('☠ '+m.n+' derrotado!');
    netSend({t:'event',txt:'☠ '+p.name+' derrotou '+m.n+'!'});
    if(m.final){prog.godSlain=true;saveProg();announce('🌌 O Criador tombou.');sysSay('👑 Trono (E).');}
    else if(ZN.id>=1&&ZN.id<=9){prog.kills['i'+ZN.id]=true;saveProg();
      if(ZN.id===9)announce('🌀 Portal!');
      else announce('⛵ '+ISLES[ZN.id+1].name+'!');}
    else if(ZN.id>=11&&ZN.id<=13){prog.kills['s'+(ZN.id-10)]=true;saveProg();
      if(ZN.id<13)announce('✨ '+ISLES[ZN.id+1].name+'!');
      else announce('👑 Trono aberto!');}
  }
  if(!m.boss)m.respawn=ZN.isCave?R(5,8):R(9,15);
}
function damagePlayer(raw){
  if(p.dead)return;
  const effDef=p.def*(1+petBuffVal('def'));
  let d=Math.max(1,Math.round(raw*(1-effDef/(effDef+70))));
  if(!p.arm)d=Math.round(d*1.35);
  if(p.buffs.guard&&p.buffs.guard>time)d=Math.round(d*.55);
  p.hp-=d;p.lastHit=time;
  ftext(p.x+R(-6,6),p.y-48,'-'+d,'#ff6b5e',15);
  shake=Math.min(shake+4,12);
  $('flash').classList.add('on');setTimeout(()=>$('flash').classList.remove('on'),90);
  sfx.hurt();
  if(p.hp<=0){p.hp=0;p.dead=true;p.target=null;sfx.die();$('deathScr').hidden=false;}
}
function respawnPlayer(){
  p.dead=false;p.hp=p.maxhp;p.mp=p.maxmp;p.gold=Math.max(0,p.gold-10);
  p.x=ZN.spawn.x;p.y=ZN.spawn.y;p.target=null;p.moveTo=null;
  $('deathScr').hidden=true;saveHero();
}
function usePotion(id){
  id=id||'php';
  const slot=p.inv.find(s=>s.id===id);
  if(!slot){ftext(p.x,p.y-44,'Sem poções!','#ff6b5e',13);sfx.err();return;}
  if(id==='php'&&p.hp>=p.maxhp){ftext(p.x,p.y-44,'Vida cheia','#9fd8a5',12);return;}
  if(id==='pmp'&&p.mp>=p.maxmp){ftext(p.x,p.y-44,'Mana cheia','#9fd8a5',12);return;}
  slot.q--;if(slot.q<=0)p.inv.splice(p.inv.indexOf(slot),1);
  if(id==='php'){const h=Math.round(p.maxhp*.45);p.hp=Math.min(p.maxhp,p.hp+h);ftext(p.x,p.y-44,'+'+h,'#7ef28a',16);burst(p.x,p.y-16,10,'#7ef28a');}
  else{const m2=Math.round(p.maxmp*.55);p.mp=Math.min(p.maxmp,p.mp+m2);ftext(p.x,p.y-44,'+'+m2,'#8fc6ff',14);}
  sfx.potion();refreshInv();
}
function useSkill(i){
  if(p.dead)return;
  const s=SKILLS[p.cls][i];
  if(p.lvl<s.lvl){ftext(p.x,p.y-44,'Nível '+s.lvl,'#ffb3a7',13);sfx.err();return;}
  if(p.cds[i]>0)return;
  if(p.mp<s.mp){ftext(p.x,p.y-44,'Mana insuficiente','#8fc6ff',13);sfx.err();return;}
  const t=validTarget();
  p.mp-=s.mp;p.cds[i]=s.cd*(1-Math.min(.35,treeSum('cdr')));p.atkT=.26;sfx.swing();
  const smult=s.mult?(s.mult*(1+treeSum('s'+i))):0;
  if(s.type==='strike'){
    if(t&&dist(p.x,p.y,t.x,t.y)<=p.range+t.r+14){p.dir=t.x>p.x?1:-1;
      hitMob(t,playerRaw(smult),{critB:s.critB||0,src:'p'});
      fx.push({k:'ring',x:t.x,y:t.y,col:'#ffb35e',maxR:40,t:0,dur:.35});}
    else ftext(p.x,p.y-44,'Sem alvo','#ffb3a7',13);
  }else if(s.type==='proj'){
    if(t&&dist(p.x,p.y,t.x,t.y)<380){p.dir=t.x>p.x?1:-1;
      projFire(p.x,p.y-16,t,playerRaw(smult),(p.cls==='mago'&&i===1)?'fire':(CLASSES[p.cls].proj||'rock'),{pierce:s.pierce,leech:s.leech});}
    else ftext(p.x,p.y-44,'Sem alvo','#ffb3a7',13);
  }else if(s.type==='nova'){
    fx.push({k:'ring',x:p.x,y:p.y,col:s.slow?'#9fd8ff':'#e8d8b0',maxR:130,t:0,dur:.6});sfx.boom();
    let tot=0;
    ZN.mobs.forEach(m=>{if(!m.dead&&dist(p.x,p.y,m.x,m.y)<130+m.r){hitMob(m,playerRaw(smult),{leech:s.leech,src:'p'});if(s.slow)m.slowT=time+3;tot++;}});
    if(s.leech){const h=Math.round(tot*p.atk*smult*s.leech*.5);if(h>0){p.hp=Math.min(p.maxhp,p.hp+h);ftext(p.x,p.y-52,'+'+h,'#7ef28a',14);}}
  }else if(s.type==='aoe'){
    const ax=t?t.x:p.x+(p.dir*140),ay=t?t.y:p.y;
    fx.push({k:'ring',x:ax,y:ay,col:'#c9a2ff',maxR:110,t:0,dur:.55});
    burst(ax,ay-10,18,'#c9a2ff');sfx.boom();
    ZN.mobs.forEach(m=>{if(!m.dead&&dist(ax,ay,m.x,m.y)<110+m.r)hitMob(m,playerRaw(smult),{src:'p'});});
  }else if(s.type==='heal'){
    const h=Math.round(p.maxhp*s.heal);p.hp=Math.min(p.maxhp,p.hp+h);
    ftext(p.x,p.y-52,'+'+h,'#7ef28a',18);burst(p.x,p.y-16,14,'#7ef28a');
    fx.push({k:'ring',x:p.x,y:p.y,col:'#7ef28a',maxR:60,t:0,dur:.5});sfx.potion();
  }else if(s.type==='buffAtk'){
    p.buffs.atk=time+(s.dur||8);p.buffs.atkPct=s.pct||.5;
    fx.push({k:'ring',x:p.x,y:p.y,col:'#ff8a5e',maxR:60,t:0,dur:.5});sfx.boom();
    ftext(p.x,p.y-52,s.n.toUpperCase()+'!','#ff9d6a',14);
  }else if(s.type==='buffCrit'){
    p.buffs.atk=time+8;p.buffs.atkPct=.2;p.buffs.crit=time+8;
    fx.push({k:'ring',x:p.x,y:p.y,col:'#d7e8a2',maxR:55,t:0,dur:.5});
    ftext(p.x,p.y-52,s.n.toUpperCase()+'!','#d7e8a2',14);
  }else if(s.type==='guard'){
    p.buffs.guard=time+8;
    fx.push({k:'ring',x:p.x,y:p.y,col:'#9fd8ff',maxR:50,t:0,dur:.5});
    ftext(p.x,p.y-52,'ESCUDO DA FÉ','#9fd8ff',14);
  }else if(s.type==='dash'){
    let dx=p.dir*130,dy=0;
    if(t){const a=Math.atan2(t.y-p.y,t.x-p.x);const dd=Math.min(dist(p.x,p.y,t.x,t.y)-t.r-20,160);dx=Math.cos(a)*dd;dy=Math.sin(a)*dd;}
    for(let s2=0;s2<6;s2++){const nx=p.x+dx/6,ny=p.y+dy/6;if(blockedZ(ZN,nx,ny,9))break;p.x=nx;p.y=ny;}
    p.buffs.crit=time+4;fx.push({k:'ring',x:p.x,y:p.y,col:'#8a7ac8',maxR:40,t:0,dur:.3});sfx.boom();
  }
}
function updPet(dt){
  const pt=activePet();
  if(!pt||p.dead)return;
  const tx=p.x-p.dir*26,ty=p.y+18;
  p.petX+=(tx-p.petX)*Math.min(1,dt*3);
  p.petY+=(ty-p.petY)*Math.min(1,dt*3);
  p.petCd-=dt;
  const t=p.target;
  if(t&&!t.dead&&p.petCd<=0&&dist(p.petX,p.petY,t.x,t.y)<170){
    p.petCd=.8;
    const st=petStage(pt);
    const d=Math.round((4+pt.lvl*2.5)*(st===2?1.6:st===1?1.2:1));
    t.hp-=d;t.hitT=.12;
    ftext(t.x+R(-6,6),t.y-t.r-10,String(d),'#a8e0ff',11);
    burst(t.x,t.y-8,2,'#a8e0ff');
    if(t.hp<=0)killMob(t,'p');
  }
}
function updAlly(dt){
  const a=activeAlly();
  if(!a||p.dead)return;
  const tx=p.x+p.dir*26,ty=p.y+20;
  p.allyX+=(tx-p.allyX)*Math.min(1,dt*3);
  p.allyY+=(ty-p.allyY)*Math.min(1,dt*3);
  p.allyCd-=dt;
  const t=p.target;
  if(t&&!t.dead&&p.allyCd<=0&&dist(p.allyX,p.allyY,t.x,t.y)<220){
    p.allyCd=.7;
    let d=(6+a.lvl*2.4);
    if(a.syn===p.cls)d*=1.5;
    if(romVal(a.id)>=70)d*=1.5;
    d=Math.round(d*R(.9,1.1));
    t.hp-=d;t.hitT=.12;
    ftext(t.x+R(-8,8),t.y-t.r-14,String(d),'#ffe08a',13);
    burst(t.x,t.y-8,3,a.col);
    if(t.hp<=0)killMob(t,'p');
  }
}

function countItem(id){const s=p.inv.find(s=>s.id===id);return s?s.q:0;}
function addInv(id,q){
  const it=ITEMS[id];
  if(it.type==='potion'||it.type==='gift'){const s=p.inv.find(s=>s.id===id);if(s){s.q+=q;return true;}}
  if(p.inv.length>=24)return false;
  p.inv.push({id,q:q||1});return true;
}
function dropItem(id){
  if(addInv(id,1)){sysSay('🎁 <b>'+ITEMS[id].icon+' '+ITEMS[id].n+'</b>');refreshInv();}
  else{p.gold+=25;sysSay('Inv cheio! +25 🪙.');}
}
function useInv(idx){
  const s=p.inv[idx];if(!s)return;const it=ITEMS[s.id];
  if(it.type==='potion')usePotion(s.id);
  else if(it.type==='gift'){ftext(p.x,p.y-44,'Dê em ALIADOS','#ffb3a7',12);return;}
  else if(it.type==='weapon'){const old=p.wpn;p.wpn=s.id;p.inv.splice(idx,1);if(old)addInv(old,1);calcStats();sysSay('Equipou <b>'+it.icon+' '+it.n+'</b>');sfx.ui();}
  else if(it.type==='armor'){const old=p.arm;p.arm=s.id;p.inv.splice(idx,1);if(old)addInv(old,1);calcStats();sysSay('Equipou <b>'+it.icon+' '+it.n+'</b>');sfx.ui();}
  refreshInv();
}
function itemTip(id){
  const it=ITEMS[id];
  let ex=[];
  if(it.atk)ex.push('+'+it.atk+' ATQ');if(it.def)ex.push('+'+it.def+' DEF');
  if(it.crit)ex.push('+'+Math.round(it.crit*100)+'% crítico');if(it.leech)ex.push('rouba '+Math.round(it.leech*100)+'%');
  if(it.spd)ex.push('+'+Math.round(it.spd*100)+'% vel');
  const rc=it.rar===4?'rar4c':it.rar===3?'rar3c':'';
  return '<b class="'+rc+'">'+it.icon+' '+it.n+'</b><br>'+(it.desc||ex.join(' · '));
}
function refreshInv(){
  if(!p)return;
  const g=$('invGrid');g.innerHTML='';
  if(!p.inv.length)g.innerHTML='<div class="inv-empty">Inventário vazio</div>';
  p.inv.forEach((s,i)=>{
    const it=ITEMS[s.id],d=document.createElement('div');
    d.className='slot'+(it.rar===2?' rar2':it.rar===3?' rar3':it.rar===4?' rar4':'');
    d.innerHTML=it.icon+(s.q>1?'<span class="qty">'+s.q+'</span>':'');
    d.onclick=()=>useInv(i);
    d.onmousemove=e=>showTip(e,itemTip(s.id));
    d.onmouseleave=hideTip;
    g.appendChild(d);
  });
  const w=p.wpn?ITEMS[p.wpn]:null,a=p.arm?ITEMS[p.arm]:null;
  $('eqWpn').textContent=w?w.icon:'✊';
  $('eqWpnN').textContent=w?w.n+' (+'+w.atk+' ATQ)':'Punhos nus';
  $('eqArm').textContent=a?a.icon:'—';
  $('eqArmN').innerHTML=a?a.n+' (+'+a.def+' DEF)':'<span style="color:#ff8a7a">SEM ARMADURA (+35% dano)</span>';
  $('statLines').innerHTML='<div>⚔ ATQ <b>'+p.atk+'</b></div><div>🛡 DEF <b>'+p.def+'</b></div><div>🎯 CRIT <b>'+Math.round(critChance()*100)+'%</b></div>'+
    '<div>❤ VIDA <b>'+p.maxhp+'</b></div><div>🔷 MANA <b>'+p.maxmp+'</b></div><div>🐾 PET <b>'+(activePet()?PETKINDS[activePet().kind].n:'—')+'</b></div>';
  $('cntHp').textContent=countItem('php');$('cntMp').textContent=countItem('pmp');
  renderTree();renderPets();renderAllies();renderForge();
}
function renderTree(){
  $('tpLbl').textContent=p.tp;
  const g=$('treeGrid');g.innerHTML='';
  for(let b=0;b<8;b++){
    const col=document.createElement('div');col.className='tree-col';
    col.innerHTML='<small>'+BRNAMES[b]+'</small>';
    for(let t=0;t<4;t++){
      const n=TREETPL.find(x=>x.b===b&&x.t===t);
      const prevOk=t===0||!!p.tree[TREETPL.find(x=>x.b===b&&x.t===(t-1)).id];
      const on=!!p.tree[n.id];
      const can=!on&&p.tp>0&&prevOk&&p.lvl>=n.lvl;
      const btn=document.createElement('button');btn.className='tnode'+(on?' on':'');
      btn.disabled=!can&&!on;
      btn.innerHTML='<b>'+n.i+' '+n.n+'</b><small>'+n.d+'</small><span class="lvl">'+(on?'✔':'Nv '+n.lvl)+'</span>';
      btn.onclick=()=>{if(can){p.tp--;p.tree[n.id]=true;calcStats();sfx.lvl();sysSay('🌳 <b>'+n.n+'</b>');refreshInv();saveHero();}};
      col.appendChild(btn);
    }
    g.appendChild(col);
  }
}
function renderPets(){
  const l=$('petList');l.innerHTML='';
  if(!p.pets.length)l.innerHTML='<div class="inv-empty">Adote filhotes com E.</div>';
  p.pets.forEach((pt,i)=>{
    const pk=PETKINDS[pt.kind],st=petStage(pt);
    const card=document.createElement('div');card.className='pet-card';
    card.innerHTML='<div class="pi">'+pk.icon+'</div><div class="sn"><b>'+pk.n+' · '+STAGEN[st]+' (Nv '+pt.lvl+')</b>'+
      '<small>'+pk.d+' +'+Math.round(pk.vals[st]*100)+'%</small>'+
      '<div class="bar xp"><i style="width:'+(pt.xp/petNeed(pt.lvl)*100)+'%"></i></div></div>';
    const b=document.createElement('button');b.className='act'+(p.petIdx===i?' on':'');
    b.textContent=p.petIdx===i?'✔ ATIVO':'ATIVAR';
    b.onclick=()=>{p.petIdx=i;p.petX=p.x;p.petY=p.y+20;sfx.tame();refreshInv();saveHero();};
    card.appendChild(b);l.appendChild(card);
  });
}
function renderAllies(){
  const l=$('allyList');l.innerHTML='';
  if(!p.allies.length)l.innerHTML='<div class="inv-empty">Complete missões locais.</div>';
  p.allies.forEach((id)=>{
    const h=HELPERS.find(x=>x.id===id);if(!h)return;
    const idx=p.allies.indexOf(id);
    const rv=romVal(id),rs=romStage(rv);
    const syn=h.syn===p.cls;
    let d=(6+h.lvl*2.4);if(syn)d*=1.5;if(rs>=4)d*=1.5;
    const card=document.createElement('div');card.className='ally-card';
    card.innerHTML='<div class="pi">'+CLASSES[h.cls].emoji+'</div><div class="sn"><b>'+h.name+' (Nv '+h.lvl+')</b>'+
      '<small>Sinergia: <b style="color:'+(syn?'#8fe08f':'var(--dim)')+'">'+CLASSES[h.syn].label+(syn?' ✔':'')+'</b> · Dano ~'+Math.round(d)+'</small>'+
      '<small style="color:#ff8ab0">'+ROMSTAGES[rs]+' ('+rv+'/70 ❤)</small>'+
      '<div class="rombar"><i style="width:'+Math.min(100,rv/70*100)+'%"></i></div></div>';
    const bt=document.createElement('button');bt.className='act'+(p.allyIdx===idx?' on':'');
    bt.textContent=p.allyIdx===idx?'✔ ATIVO':'INVOCAR';
    bt.onclick=()=>{p.allyIdx=idx;p.allyX=p.x;p.allyY=p.y+20;sfx.ui();refreshInv();saveHero();};
    const bc=document.createElement('button');bc.className='act';bc.textContent='💬';
    bc.onclick=()=>{
      if((p.talkCd[id]||0)>Date.now()){sysSay('💬 Espere…');return;}
      p.talkCd[id]=Date.now()+10000;
      const oldSt=romStage(p.rom[id]||0);
      p.rom[id]=(p.rom[id]||0)+1;
      sysSay('💬 '+h.name+': '+pick(ROMLINES[romStage(p.rom[id])]));
      if(oldSt!==romStage(p.rom[id])){announce('💗 '+h.name+' → '+ROMSTAGES[romStage(p.rom[id])]);sfx.tame();}
      refreshInv();saveHero();};
    const bg2=document.createElement('button');bg2.className='act';bg2.textContent='🎁';
    bg2.onclick=()=>{
      if(countItem('gift')<1){sysSay('🎁 Compre na loja.');sfx.err();return;}
      const s=p.inv.find(x=>x.id==='gift');s.q--;if(s.q<=0)p.inv.splice(p.inv.indexOf(s),1);
      const oldSt=romStage(p.rom[id]||0);
      p.rom[id]=(p.rom[id]||0)+6;
      sysSay('🎁 '+h.name+' adorou! (+6 ❤)');
      if(oldSt!==romStage(p.rom[id])){announce('💗 '+h.name+' → '+ROMSTAGES[romStage(p.rom[id])]);sfx.tame();}
      refreshInv();saveHero();};
    card.appendChild(bt);card.appendChild(bc);card.appendChild(bg2);l.appendChild(card);
  });
}
function renderForge(){
  $('forgeRes').innerHTML=Object.keys(p.res).map(k=>RESN[k].split(' ')[0]+' <b>'+p.res[k]+'</b> '+RESN[k].split(' ')[1]).join(' · ');
  const l=$('forgeList');l.innerHTML='';
  RECIPES.forEach(rc=>{
    const it=ITEMS[rc.out];
    const can=Object.keys(rc.cost).every(k=>p.res[k]>=rc.cost[k]);
    const row=document.createElement('div');row.className='shop-row';
    row.innerHTML='<div class="si">'+it.icon+'</div><div class="sn"><b>'+it.n+'</b><small>'+rc.d+' · '+Object.keys(rc.cost).map(k=>RESN[k].split(' ')[0]+rc.cost[k]).join(' ')+'</small></div>';
    const b=document.createElement('button');b.className='btn';b.textContent='⚒ Forjar';
    b.disabled=!can;
    b.onclick=()=>{Object.keys(rc.cost).forEach(k=>p.res[k]-=rc.cost[k]);
      if(!addInv(rc.out,1)){sysSay('Inv cheio!');return;}
      sfx.mine();sysSay('⚒ <b>'+it.icon+' '+it.n+'</b>!');renderForge();refreshInv();saveHero();};
    row.appendChild(b);l.appendChild(row);
  });
}
let shopMode='buy';
function openShop(){renderShop(shopMode);showWin('winShop');}
function renderShop(mode){
  shopMode=mode;
  $('shopGold').textContent=p.gold;
  const isl=ZN.isCave?ZN.parent:ZN.id;
  $('shopTitle').textContent=isl===0?'EMPÓRIO DA MIRA':'EMPÓRIO · '+ISLES[isl].name.toUpperCase();
  $('tabBuy').classList.toggle('sel',mode==='buy');
  $('tabSell').classList.toggle('sel',mode==='sell');
  const l=$('shopList');l.innerHTML='';
  if(mode==='buy'){
    shopStock(isl).forEach(id=>{
      const it=ITEMS[id];
      const row=document.createElement('div');row.className='shop-row';
      row.innerHTML='<div class="si">'+it.icon+'</div><div class="sn"><b>'+it.n+'</b><small>'+(it.atk?'+'+it.atk+' ATQ':it.def?'+'+it.def+' DEF':it.desc)+'</small></div>';
      const b=document.createElement('button');b.className='btn';b.textContent='🪙 '+it.price;
      b.disabled=p.gold<it.price;
      b.onclick=()=>{if(p.gold<it.price)return sfx.err();
        if(!addInv(id,1)){sysSay('Inv cheio!');return;}
        p.gold-=it.price;sfx.coin();logSay('Comprou <b>'+it.icon+' '+it.n+'</b>');renderShop('buy');refreshInv();};
      row.appendChild(b);l.appendChild(row);
    });
  }else{
    if(!p.inv.length)l.innerHTML='<div class="inv-empty">Nada para vender.</div>';
    p.inv.forEach((s,i)=>{
      const it=ITEMS[s.id],sp=Math.max(1,Math.floor((it.price||10)/2));
      const row=document.createElement('div');row.className='shop-row';
      row.innerHTML='<div class="si">'+it.icon+'</div><div class="sn"><b>'+it.n+'</b>'+(s.q>1?' x'+s.q:'')+'</div>';
      const b=document.createElement('button');b.className='btn';b.textContent='Vender 🪙 '+sp;
      b.onclick=()=>{s.q--;if(s.q<=0)p.inv.splice(i,1);p.gold+=sp;sfx.coin();renderShop('sell');refreshInv();};
      row.appendChild(b);l.appendChild(row);
    });
  }
}
function showTip(e,html){const t=$('tip');t.innerHTML=html;t.hidden=false;
  t.style.left=Math.min(e.clientX+16,innerWidth-260)+'px';t.style.top=Math.min(e.clientY+14,innerHeight-90)+'px';}
function hideTip(){$('tip').hidden=true;}

function questState(q){const s=p.qs[q.id];if(s&&s.d)return 'done';if(s)return s.p>=q.need?'ready':'active';return 'avail';}
function questKill(m){
  for(const q of QUESTS){
    const s=p.qs[q.id];if(!s||s.d||q.kind!=='kill')continue;
    if(s.p>=q.need)continue;
    const match=q.mob==='any'?(ZN.id===q.isl&&!ZN.isCave):(m.v===q.mob);
    if(match){s.p++;
      if(s.p>=q.need)sysSay('📜 <b>'+q.name+'</b> completo!');
      else if(s.p%2===0)logSay('📜 '+q.name+': '+s.p+'/'+q.need);}
  }
}
function renderQuestLog(){
  const l=$('questList');let h='',any=false;
  for(const q of QUESTS){const s=p.qs[q.id];if(!s)continue;any=true;
    const st=s.d?'✅ Concluída':(s.p>=q.need?'❗ Pronta':(q.kind==='gold'?'Leve '+q.need+' 🪙':s.p+'/'+q.need));
    h+='<div class="qitem"><b>'+q.name+'</b> <span style="color:var(--dim)">· '+ISLES[q.isl].name+'</span><small>'+q.txt+'</small><div class="qp">'+st+'</div></div>';}
  if(!any)h='<div class="inv-empty">Nenhuma missão ativa.</div>';
  l.innerHTML=h;
}

function npcMarker(n){
  const q=questFor(n.role,ZN.isCave?ZN.parent:ZN.id);
  if(q){const st=questState(q);if(st==='avail')return '!';if(st==='ready')return '?';}
  return null;
}
function interact(){
  if(p.dead||!ZN)return;
  let bc=null,bd2=60;
  for(const c of ZN.critters){const d=dist(p.x,p.y,c.x,c.y);if(d<bd2){bd2=d;bc=c;}}
  if(bc){
    if(p.pets.some(x=>x.kind===bc.kind)){ftext(p.x,p.y-44,'Já tem '+PETKINDS[bc.kind].n.toLowerCase(),'#a89877',12);return;}
    ZN.critters.splice(ZN.critters.indexOf(bc),1);
    p.pets.push({kind:bc.kind,lvl:1,xp:0});
    if(p.petIdx<0){p.petIdx=0;p.petX=p.x;p.petY=p.y+20;}
    sfx.tame();announce('🐾 '+PETKINDS[bc.kind].n+' adotado!');
    saveHero();return;
  }
  let best=null,bd=80;
  for(const n of ZN.npcs){const d=dist(p.x,p.y,n.x,n.y);if(d<bd){bd=d;best=n;}}
  if(best){sfx.ui();
    if(best.role==='shop'){logSay('🛒 '+best.name);openShop();return;}
    openNpcDlg(best);return;}
  if(ZN.exit&&dist(p.x,p.y,ZN.exit.x,ZN.exit.y)<90){enterZone(ZN.parent);return;}
  if(ZN.mineDoor&&dist(p.x,p.y,ZN.mineDoor.x,ZN.mineDoor.y)<90){sfx.ui();enterZone('m'+ZN.id);announce('⛏️ Mina!');return;}
  if(ZN.caveDoor&&dist(p.x,p.y,ZN.caveDoor.x,ZN.caveDoor.y)<90){sfx.ui();enterZone('c'+ZN.id);announce('🕳️ Caverna!');return;}
  if(ZN.portal&&dist(p.x,p.y,ZN.portal.x,ZN.portal.y)<90){
    if(isUnlocked(11)){sfx.portal();enterZone(11);announce('✨ Éter!');}
    else ftext(p.x,p.y-44,'Portal adormecido','#c8b8ff',13);
    return;
  }
  if(ZN.throne&&dist(p.x,p.y,ZN.throne.x,ZN.throne.y)<100){
    if(prog.godSlain)openEnding();
    else ftext(p.x,p.y-44,'Trono ocupado','#ffd76a',13);
    return;
  }
  if(ZN.dock&&dist(p.x,p.y,ZN.dock.x,ZN.dock.y)<110){
    if(!prog.boat){
      ftext(p.x,p.y-44,'Compre barco no Estaleiro','#ffb3a7',13);
      sfx.err();return;
    }
    sfx.ui();openTravel();return;
  }
  ftext(p.x,p.y-44,'Nada para interagir','#a89877',12);
}
function openNpcDlg(n){
  const isl=ZN.isCave?ZN.parent:ZN.id;
  $('dlgTitle').textContent=n.name;$('dlgName').textContent=n.name;$('dlgIco').textContent=n.ico;$('dlgBtns').innerHTML='';
  let body='';
  const q=questFor(n.role,isl);
  if(q){
    const st=questState(q);
    if(st==='avail'){
      body+='<div class="qq">📜 <b>'+q.name+'</b><br>'+q.txt+'<br><span style="color:var(--gold2)">'+q.xp+' XP'+(q.gold?' · '+q.gold+' 🪙':'')+'</span></div>';
      addDlgBtn('Aceitar',()=>{p.qs[q.id]={p:0,d:false};sfx.ui();sysSay('📜 <b>'+q.name+'</b>');hideWins();});
    }else if(st==='active'){
      body+='<div class="qq">📜 <b>'+q.name+'</b><br>'+(q.kind==='gold'?'Junte '+q.need+' 🪙':p.qs[q.id].p+'/'+q.need)+'</div>';
    }else if(st==='ready'){
      body+='<div class="qq">📜 <b>'+q.name+'</b> pronta!</div>';
      addDlgBtn('🎁 Entregar',()=>{
        if(q.kind==='gold'){if(p.gold<q.need){sfx.err();return;}p.gold-=q.need;}
        p.qs[q.id].d=true;
        gainXP(q.xp);if(q.gold)p.gold+=q.gold;
        if(q.item)for(let k=0;k<q.iq;k++)addInv(q.item,1);
        const hh=HELPERS.find(x=>x.isl===q.isl&&q.id.endsWith('f'));
        if(hh&&!p.allies.includes(hh.id)){p.allies.push(hh.id);announce('🤝 '+hh.name+'!');sysSay('🤝 <b>'+hh.name+'</b> em ALIADOS');}
        sfx.coin();sysSay('✔ <b>'+q.name+'</b>!');refreshInv();hideWins();});
    }
  }
  if(n.role==='boat'){
    if(prog.boat)body+='“Seu barco está no cais.”';
    else{body+='“Barco: <b>250 🪙</b>.”';
      const b=addDlgBtn('⛵ Comprar (250 🪙)',()=>{
        if(p.gold<250){sfx.err();return;}
        p.gold-=250;prog.boat=true;saveProg();sfx.coin();
        announce('⛵ Barco!');hideWins();});
      if(p.gold<250)b.disabled=true;}
    $('dlgBody').innerHTML=body;
    if(prog.boat)addDlgBtn('Ir ao cais',()=>{hideWins();openTravel();});
    addDlgBtn('Sair',hideWins);}
  else if(n.role==='guide'){
    body+='“'+pick(['Derrote chefes para abrir rotas.','Quem mata o Criador senta no Trono.','Quebre árvores na mão.','Cada ilha tem um aliado.','Use ID para jogar com amigo.'])+'”';
    $('dlgBody').innerHTML=body;addDlgBtn('Obrigado',hideWins);}
  else{
    body+='“'+pick(n.lines||['Bem-vindo.'])+'”';
    $('dlgBody').innerHTML=body;
    addDlgBtn('Conte mais',()=>{$('dlgBody').innerHTML=body.replace(/“.*?”/,'“'+pick(n.lines||['Hmm.'])+'”');sfx.ui();});
    addDlgBtn('Sair',hideWins);}
  showWin('dlg');
}
function addDlgBtn(txt,fn){const b=document.createElement('button');b.className='btn';b.textContent=txt;b.onclick=fn;$('dlgBtns').appendChild(b);return b;}
function openTravel(){
  const l=$('travelList');l.innerHTML='';
  ISLES.forEach((isl,i)=>{
    const un=isUnlocked(i),cur=ZN.id===i;
    const row=document.createElement('div');row.className='trav-row'+(cur?' cur':'');
    row.innerHTML='<div class="tt" style="background:'+PAL[i].grass[0]+'"></div><div class="sn"><b>'+isl.name+(cur?' (aqui)':'')+'</b><small>'+isl.sub+' · '+isl.rec+'</small></div>';
    if(cur){const s2=document.createElement('span');s2.className='lk';s2.textContent='Você está aqui';row.appendChild(s2);}
    else if(un){const b=document.createElement('button');b.className='btn';b.textContent='⛵ Zarpar';
      b.onclick=()=>{hideWins();enterZone(i);announce('⛵ '+isl.name);};row.appendChild(b);}
    else{const s2=document.createElement('span');s2.className='lk';s2.textContent='🔒 '+lockText(i);row.appendChild(s2);}
    l.appendChild(row);
  });
  showWin('winTravel');
}
function openEnding(){
  const bossName=meta.cycle>1?meta.prevName:'O Criador';
  $('endTitle').textContent='O TRONO É SEU, '+p.name.toUpperCase();
  $('endTxt').innerHTML='Com '+bossName+' derrotado. Você se senta.<br><br>No próximo ciclo, <b>você</b> será o deus. ♻ Ciclo '+(meta.cycle+1);
  $('endScr').hidden=false;
}
$('btnCycle').onclick=()=>{
  meta={cycle:meta.cycle+1,prevName:p.name,prevCls:p.cls,prevAp:Object.assign({},p.ap)};
  saveMeta();
  try{localStorage.removeItem(SKEY);localStorage.removeItem(PKEY);}catch(e){}
  location.reload();
};

function tryHarvest(wx,wy){
  const tx=Math.floor(wx/T),ty=Math.floor(wy/T);
  if(tx<0||ty<0||tx>=ZN.S||ty>=ZN.S)return false;
  const t=ZN.map[ty][tx];
  const px=tx*T+T/2,py=ty*T+T/2;
  if(dist(p.x,p.y,px,py)>110)return false;
  const key=tx+','+ty;
  const hasAxe=countItem('axe')>0,hasPick=countItem('pick')>0;
  if(t===5){
    const need=hasAxe?3:5;
    ZN.hits[key]=(ZN.hits[key]||0)+1;
    sfx.chop();burst(px,py-20,5,'#4c9650');
    ftext(px,py-34,Math.min(ZN.hits[key],need)+'/'+need+(hasAxe?' 🪓':' ✊'),'#c8e0a0',12);
    if(ZN.hits[key]>=need){ZN.map[ty][tx]=0;const w=hasAxe?RI(3,5):RI(2,3);p.res.wood+=w;
      ftext(px,py-20,'+'+w+' 🪵','#c8e0a0',13);
      sysSay('🪵 +'+w+(hasAxe?'.':' (na mão!)'));
      renderForge();saveHero();}
    return true;
  }
  if(t===6&&!ZN.isCave){
    const need=hasPick?3:6;
    ZN.hits[key]=(ZN.hits[key]||0)+1;sfx.mine();
    burst(px,py-8,5,'#9a9284');
    ftext(px,py-20,Math.min(ZN.hits[key],need)+'/'+need+(hasPick?' ⛏️':' ✊'),'#cfd0d4',12);
    if(ZN.hits[key]>=need){ZN.map[ty][tx]=0;const st=hasPick?RI(2,3):RI(1,2);p.res.stone+=st;
      ftext(px,py-12,'+'+st+' 🪨','#cfd0d4',13);
      if(hasPick&&ZN.id>0&&chance(.3)){const ok=oreKeyFor(ZN.id),o=RI(1,2);p.res[ok]+=o;ftext(px,py-30,'+'+o+' '+RESN[ok].split(' ')[0],'#ffd76a',13);}
      sysSay('⛏️ +'+st);
      renderForge();saveHero();}
    return true;
  }
  if(t===9){
    if(!hasPick){ftext(p.x,p.y-44,'Precisa de ⛏️','#ffb3a7',12);return true;}
    ZN.hits[key]=(ZN.hits[key]||0)+1;sfx.mine();
    burst(px,py-8,6,'#8fd8e0');
    if(ZN.hits[key]>=3){ZN.map[ty][tx]=8;
      const ok=oreKeyFor(ZN.id),o=RI(1,2);p.res[ok]+=o;const st=RI(1,2);p.res.stone+=st;
      ftext(px,py-16,'+'+o+' '+RESN[ok].split(' ')[0],'#ffd76a',14);
      sysSay('⛏️ +'+o+' '+RESN[ok].split(' ')[1]);
      renderForge();saveHero();}
    return true;
  }
  return false;
}

function eproj(m,x,y,a,spd,dmg,col){projs.push({x,y,vx:Math.cos(a)*spd,vy:Math.sin(a)*spd,dmg,kind:'foe',life:1.4,from:'m',col:col||m.c,hit:new Set()});}
function dmgZone(cx2,cy2,rad,dmg){if(!p.dead&&dist(cx2,cy2,p.x,p.y)<rad)damagePlayer(dmg);}
function doArt(m,art,P){
  if(m.boss)fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:70,t:0,dur:.4});
  switch(art){
    case 'slam':fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:140*P,t:0,dur:.5});sfx.boom();if(m.boss)shake=Math.max(shake,7);dmgZone(m.x,m.y,150*P,m.atk*1.6);break;
    case 'volley':{const a0=Math.atan2(p.y-m.y,p.x-m.x);for(let k=-2;k<=2;k++)eproj(m,m.x,m.y-m.r*.5,a0+k*.22,300,m.atk*.8);sfx.boom();}break;
    case 'charge':{const a=Math.atan2(p.y-m.y,p.x-m.x);const st=m.boss?12:7;
      for(let s2=0;s2<st;s2++){const nx=m.x+Math.cos(a)*18,ny=m.y+Math.sin(a)*18;if(blockedZ(ZN,nx,ny,m.r*.6))break;m.x=nx;m.y=ny;}
      m.dir=Math.cos(a)>0?1:-1;
      if(dist(m.x,m.y,p.x,p.y)<m.r+26)damagePlayer(m.atk*1.4);sfx.boom();}break;
    case 'nova':fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:190*P,t:0,dur:.6});sfx.boom();dmgZone(m.x,m.y,190*P,m.atk*1.3);break;
    case 'summon':{if(ZN.cfg.mobs&&ZN.cfg.mobs.length){const md2=ZN.cfg.mobs[0];
      for(let k=0;k<(m.boss?3:2);k++){const a=R(0,TAU),nx=m.x+Math.cos(a)*70,ny=m.y+Math.sin(a)*70;
        ZN.mobs.push(makeMob(md2.v,md2.n,md2.lvl,md2.c,nx,ny,1,false));}}
      burst(m.x,m.y-10,10,m.c);}break;
    case 'leap':{m.x=clamp(p.x+R(-50,50),20,ZN.S*T-20);m.y=clamp(p.y+R(-50,50),20,ZN.S*T-20);
      fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:120*P,t:0,dur:.45});sfx.boom();shake=Math.max(shake,6);dmgZone(m.x,m.y,120*P,m.atk*1.5);}break;
    case 'heal':{const h=Math.round(m.maxhp*(m.boss?.08:.2));m.hp=Math.min(m.maxhp,m.hp+h);
      ftext(m.x,m.y-m.r-20,'+'+h,'#7ef28a',14);burst(m.x,m.y-10,8,'#7ef28a');}break;
    case 'frenzy':m.frenzyT=time+6;burst(m.x,m.y-10,12,'#ff5544');ftext(m.x,m.y-m.r-24,'FÚRIA!','#ff5544',16);sfx.boom();break;
    case 'howl':m.frenzyT=time+5;ftext(m.x,m.y-m.r-24,'AUUUU!','#cfe8ff',14);break;
    case 'spikes':{for(let k=0;k<8;k++)eproj(m,m.x,m.y-m.r*.5,k*TAU/8,260,m.atk*.7);sfx.boom();}break;
    case 'hop':{const a=Math.atan2(p.y-m.y,p.x-m.x);for(let s2=0;s2<4;s2++){const nx=m.x+Math.cos(a)*16,ny=m.y+Math.sin(a)*16;if(blockedZ(ZN,nx,ny,m.r*.6))break;m.x=nx;m.y=ny;}
      if(dist(m.x,m.y,p.x,p.y)<m.r+22)damagePlayer(m.atk*1.2);}break;
    case 'goonova':fx.push({k:'ring',x:m.x,y:m.y,col:'#8fd8a0',maxR:90,t:0,dur:.5});dmgZone(m.x,m.y,90,m.atk*.9);break;
    case 'lunge':doArt(m,'hop',0);break;
    case 'screech':fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:80,t:0,dur:.4});dmgZone(m.x,m.y,80,m.atk*.8);break;
    case 'dive':doArt(m,'charge',0);break;
    case 'hurl':eproj(m,m.x,m.y-m.r,Math.atan2(p.y-m.y,p.x-m.x),280,m.atk);break;
    case 'bolt':eproj(m,m.x,m.y-16,Math.atan2(p.y-m.y,p.x-m.x),330,m.atk);break;
    case 'snova':fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:100,t:0,dur:.5});dmgZone(m.x,m.y,100,m.atk);break;
    case 'firebolt':eproj(m,m.x,m.y-16,Math.atan2(p.y-m.y,p.x-m.x),320,m.atk,'#ff9d4a');break;
    case 'wingnova':fx.push({k:'ring',x:m.x,y:m.y,col:'#ffb35e',maxR:110,t:0,dur:.5});dmgZone(m.x,m.y,110,m.atk*1.1);break;
    case 'spin':fx.push({k:'ring',x:m.x,y:m.y,col:'#e8d8b0',maxR:85,t:0,dur:.4});dmgZone(m.x,m.y,85,m.atk*1.1);break;
    case 'throw':eproj(m,m.x,m.y-16,Math.atan2(p.y-m.y,p.x-m.x),300,m.atk*.9,'#c8b89a');break;
    case 'volley3':{const a0=Math.atan2(p.y-m.y,p.x-m.x);for(let k=-1;k<=1;k++)eproj(m,m.x,m.y-16,a0+k*.25,310,m.atk*.75);}break;
    case 'hnova':fx.push({k:'ring',x:m.x,y:m.y,col:'#fff0b8',maxR:110,t:0,dur:.5});dmgZone(m.x,m.y,110,m.atk);break;
    case 'spore':fx.push({k:'ring',x:m.x,y:m.y,col:'#b8e08a',maxR:90,t:0,dur:.5});dmgZone(m.x,m.y,90,m.atk*.8);break;
    case 'bash':if(dist(m.x,m.y,p.x,p.y)<m.r+26)damagePlayer(m.atk*1.3);break;
    case 'web':eproj(m,m.x,m.y-10,Math.atan2(p.y-m.y,p.x-m.x),260,m.atk*.8,'#d8d8e8');break;
    case 'pounce':doArt(m,'hop',0);break;
    case 'shards':{const a0=Math.atan2(p.y-m.y,p.x-m.x);for(let k=-1;k<=1;k++)eproj(m,m.x,m.y-14,a0+k*.3,300,m.atk*.7);}break;
    case 'enova':fx.push({k:'ring',x:m.x,y:m.y,col:m.c,maxR:100,t:0,dur:.5});dmgZone(m.x,m.y,100,m.atk);break;
    case 'blink':{m.x=clamp(p.x+R(-60,60),20,ZN.S*T-20);m.y=clamp(p.y+R(-60,60),20,ZN.S*T-20);
      if(dist(m.x,m.y,p.x,p.y)<m.r+24)damagePlayer(m.atk*1.2);}break;
    case 'fury':if(dist(m.x,m.y,p.x,p.y)<m.r+28)damagePlayer(m.atk*1.6);break;
    case 'tusk':doArt(m,'charge',0);break;
    case 'charge2':doArt(m,'charge',0);break;
  }
}
const MOBARTS={
 slime:['hop','goonova'],wolf:['lunge','howl'],bat:['screech','dive'],boar:['charge2','tusk'],
 golem:['slam','hurl'],spirit:['bolt','snova'],dragon:['firebolt','wingnova'],
 bandit:['spin','throw'],skeleton:['spin','throw'],cultist:['bolt','volley3'],seraph:['bolt','hnova'],
 myconid:['spore','bash'],spider:['web','pounce'],elemental:['shards','enova'],voidling:['bolt','blink'],wildcat:['pounce','fury'],
};

function tryMove(e,dx,dy){
  if(dx&&!blockedZ(ZN,e.x+dx,e.y,9))e.x+=dx;
  if(dy&&!blockedZ(ZN,e.x,e.y+dy,9))e.y+=dy;
  const lim=ZN.S*T;
  e.x=clamp(e.x,12,lim-12);e.y=clamp(e.y,12,lim-12);
}
function updPlayer(dt){
  if(p.dead)return;
  let dx=((keys['d']||keys['arrowright'])?1:0)-((keys['a']||keys['arrowleft'])?1:0);
  let dy=((keys['s']||keys['arrowdown'])?1:0)-((keys['w']||keys['arrowup'])?1:0);
  p.atkCd=Math.max(0,p.atkCd-dt);p.atkT=Math.max(0,p.atkT-dt);
  p.cds=p.cds.map(c=>Math.max(0,c-dt));
  if(dx||dy){p.moveTo=null;const l=Math.hypot(dx,dy);dx/=l;dy/=l;
    tryMove(p,dx*p.spd*dt,dy*p.spd*dt);
    if(dx)p.dir=dx>0?1:-1;
    p.walk+=dt*2.2;
  }else if(p.moveTo){
    if(p.target&&!p.target.dead&&inRange(p.target)){p.moveTo=null;doBasic();}
    else{const d=dist(p.x,p.y,p.moveTo.x,p.moveTo.y);
      if(d<6)p.moveTo=null;
      else{const a=Math.atan2(p.moveTo.y-p.y,p.moveTo.x-p.x);
        tryMove(p,Math.cos(a)*p.spd*dt,Math.sin(a)*p.spd*dt);
        p.dir=Math.cos(a)>0?1:-1;p.walk+=dt*2.2;}}
  }
  p.mp=Math.min(p.maxmp,p.mp+(2+p.lvl*.25)*dt);
  if(time-p.lastHit>5)p.hp=Math.min(p.maxhp,p.hp+(1+p.lvl*.3)*dt);
  if(p.target&&p.target.dead)p.target=null;
}
function bossIntro(m){
  cineT=2.8;
  const bi=$('bossIntro');
  bi.querySelector('b').textContent=m.n;
  bi.classList.add('show');
  setTimeout(()=>bi.classList.remove('show'),2600);
  sfx.boom();
}
function updMobs(dt){
  for(const m of ZN.mobs){
    if(m.dead){
      if(!m.boss){m.respawn-=dt;
        if(m.respawn<=0){Object.assign(m,{hp:m.maxhp,dead:false,x:m.spawnPt.x,y:m.spawnPt.y,state:'idle',slowT:0,hitT:0});}}
      continue;
    }
    m.hitT=Math.max(0,m.hitT-dt);m.atkCd=Math.max(0,m.atkCd-dt);
    const frenz=m.frenzyT&&m.frenzyT>time;
    const spdMult=(frenz?1.5:1)*((m.slowT>time)?.45:1);
    const atkMult=frenz?1.4:1;
    if(m.boss){m.artCd-=dt;
      if(m.artCd<=0&&m.state==='chase'&&!p.dead&&dist(m.x,m.y,p.x,p.y)<420){
        const arts=m.arts||['slam'];
        let a=pick(arts);if(a===m.lastArt)a=pick(arts);
        m.lastArt=a;doArt(m,a,1);
        m.artCd=R(4,6.5);}}
    else{m.spCd-=dt;
      if(m.spCd<=0&&m.state==='chase'&&!p.dead&&dist(m.x,m.y,p.x,p.y)<260){
        const arts=MOBARTS[m.v]||['hop'];
        doArt(m,pick(arts),.7);
        m.spCd=R(5,8);}}
    if(!p.dead&&dist(m.x,m.y,p.x,p.y)<m.aggro){m.state='chase';
      if(m.boss&&!m.introDone){m.introDone=true;bossIntro(m);}}
    const dHome=dist(m.x,m.y,m.home.x,m.home.y);
    if(p.dead||dHome>480)m.state=dHome>16?'return':'idle';
    if(m.state==='chase'&&!p.dead){
      const d=dist(m.x,m.y,p.x,p.y);
      if(m.ranged&&d<280){
        if(m.atkCd<=0){m.atkCd=frenz?1.0:1.6;m.dir=p.x>m.x?1:-1;
          eproj(m,m.x,m.y-20,Math.atan2(p.y-m.y,p.x-m.x),320,m.atk*atkMult);
          tone(500,.1,'sine',.06,-200);}}
      else if(d>m.r+16){const a=Math.atan2(p.y-m.y,p.x-m.x);
        tryMove(m,Math.cos(a)*m.spd*spdMult*dt,Math.sin(a)*m.spd*spdMult*dt);
        m.dir=Math.cos(a)>0?1:-1;}
      else if(m.atkCd<=0){m.atkCd=(m.boss?1.4:1.05)/(frenz?1.6:1);
        damagePlayer(m.atk*atkMult*R(.85,1.15));
        if(m.boss){shake=Math.max(shake,8);sfx.boom();fx.push({k:'ring',x:p.x,y:p.y,col:'#c8a86a',maxR:46,t:0,dur:.4});}}
    }else if(m.state==='return'){
      const d=dist(m.x,m.y,m.home.x,m.home.y);
      if(d>10){const a=Math.atan2(m.home.y-m.y,m.home.x-m.x);
        tryMove(m,Math.cos(a)*m.spd*1.6*dt,Math.sin(a)*m.spd*1.6*dt);}
      else m.state='idle';
      m.hp=Math.min(m.maxhp,m.hp+m.maxhp*.15*dt);
      m.frenzyT=0;
    }else{
      m.wt-=dt;
      const hr=m.homeR||60;
      if(m.wt<=0){m.wt=R(2,5);m.wx=m.home.x+R(-hr,hr);m.wy=m.home.y+R(-hr,hr);}
      const d=dist(m.x,m.y,m.wx,m.wy);
      if(d>8){const a=Math.atan2(m.wy-m.y,m.wx-m.x);
        tryMove(m,Math.cos(a)*m.spd*.4*dt,Math.sin(a)*m.spd*.4*dt);
        m.dir=Math.cos(a)>0?1:-1;}
    }
  }
}
function updNpcs(dt){
  for(const n of ZN.npcs){
    n.wt-=dt;
    if(n.wt<=0){n.wt=R(2,5);n.wx=n.home.x+R(-36,36);n.wy=n.home.y+R(-22,22);}
    const d=dist(n.x,n.y,n.wx,n.wy);
    if(d>5){const a=Math.atan2(n.wy-n.y,n.wx-n.x);
      const nx=n.x+Math.cos(a)*24*dt,ny=n.y+Math.sin(a)*24*dt;
      if(!blockedZ(ZN,nx,ny,7)){n.x=nx;n.y=ny;}}
  }
  for(const c of ZN.critters){
    c.wt-=dt;
    if(c.wt<=0){c.wt=R(1.5,4);c.wx=c.home.x+R(-50,50);c.wy=c.home.y+R(-40,40);}
    const d=dist(c.x,c.y,c.wx,c.wy);
    if(d>5){const a=Math.atan2(c.wy-c.y,c.wx-c.x);
      const nx=c.x+Math.cos(a)*40*dt,ny=c.y+Math.sin(a)*40*dt;
      if(!blockedZ(ZN,nx,ny,6)){c.x=nx;c.y=ny;c.dir=Math.cos(a)>0?1:-1;}}
  }
}
function updProjs(dt){
  for(let i=projs.length-1;i>=0;i--){
    const pr=projs[i];pr.x+=pr.vx*dt;pr.y+=pr.vy*dt;pr.life-=dt;
    if(pr.kind==='fire'&&chance(.5))parts.push({x:pr.x,y:pr.y,vx:R(-20,20),vy:R(-20,20),life:.3,col:'#ff9d4a',size:2.4,g:0});
    let hit=false;
    if(pr.from==='p'){
      for(const m of ZN.mobs){
        if(m.dead||pr.hit.has(m))continue;
        if(dist(pr.x,pr.y,m.x,m.y-m.r*.4)<m.r+10){
          pr.hit.add(m);hitMob(m,pr.dmg,{leech:pr.leech,src:'p'});
          if(pr.kind==='fire'){burst(pr.x,pr.y,10,'#ff9d4a');sfx.boom();}
          if(!pr.pierce){hit=true;break;}
        }
      }
    }else{
      if(!p.dead&&dist(pr.x,pr.y,p.x,p.y-14)<18){damagePlayer(pr.dmg);burst(pr.x,pr.y,6,pr.col||'#b88ae0');hit=true;}
    }
    if(hit||pr.life<=0||blockedZ(ZN,pr.x,pr.y,3))projs.splice(i,1);
  }
}
function updFX(dt){
  for(let i=parts.length-1;i>=0;i--){const q=parts[i];q.x+=q.vx*dt;q.y+=q.vy*dt;q.vy+=(q.g===undefined?60:q.g)*dt;q.life-=dt;if(q.life<=0)parts.splice(i,1);}
  for(let i=ftexts.length-1;i>=0;i--){const q=ftexts[i];q.y-=28*dt;q.life-=dt;if(q.life<=0)ftexts.splice(i,1);}
  for(let i=fx.length-1;i>=0;i--){fx[i].t+=dt;if(fx[i].t>=fx[i].dur)fx.splice(i,1);}
}
function updAmbient(dt){
  if(!ZN)return;
  const vw=world.width/ZOOM,vh=world.height/ZOOM;
  if(ZN.amb.length<26&&chance(.35)){
    let k=pick(ZN.ambKinds);
    if(k==='firefly'&&dayDark()<.2&&ZN.id<10&&!ZN.isCave)k='butterfly';
    const a={k,x:cam.x+R(-30,vw+60),y:cam.y+R(-20,vh+40),t:R(0,6),s:R(0,6),life:R(5,9)};
    if(k==='butterfly')a.c=pick(['#f2e6a0','#ffd0e0','#cfe8ff','#ffb35e']);
    else if(k==='leaf')a.c=pick(['#c87a3a','#b8662e','#d89a4a']);
    else if(k==='petal')a.c=pick(['#ffd0e0','#f0c0d8','#ffe0ea']);
    else if(k==='ember')a.c=ZN.id===14?'#ffd76a':'#ff9d4a';
    ZN.amb.push(a);
  }
  for(let i=ZN.amb.length-1;i>=0;i--){const a=ZN.amb[i];a.t+=dt;a.life-=dt;
    if(a.k==='butterfly'){a.x+=(Math.sin(a.t*2+a.s)*24+12)*dt;a.y+=Math.cos(a.t*3+a.s)*20*dt;}
    else if(a.k==='leaf'||a.k==='petal'){a.x+=(Math.sin(a.t*1.5+a.s)*22-10)*dt;a.y+=(26+a.s*8)*dt;}
    else if(a.k==='snow'){a.x+=Math.sin(a.t+a.s)*12*dt;a.y+=(22+a.s*8)*dt;}
    else if(a.k==='ember'){a.y-=(32+a.s*22)*dt;a.x+=Math.sin(a.t*3+a.s)*10*dt;}
    else if(a.k==='firefly'){a.x+=Math.sin(a.t*1.2+a.s)*16*dt;a.y+=Math.cos(a.t*1.7+a.s)*13*dt;}
    if(a.life<=0||a.y>cam.y+vh+60)ZN.amb.splice(i,1);}
  for(const cf of ZN.campfires)if(chance(dt*5))parts.push({x:cf.x+R(-4,4),y:cf.y-12,vx:R(-8,8),vy:R(-46,-26),life:R(.4,.8),col:'#ffb347',size:R(1.4,2.6),g:-20});
  if(!ZN.isCave&&ZN.id<10&&birds.length===0&&chance(dt*.1)){
    const n=RI(3,5),y=cam.y+R(30,vh*.4),x=cam.x-60,vx=R(70,110);
    for(let k=0;k<n;k++)birds.push({x:x-k*26,y:y+R(-18,18),vx,ph:R(0,6)});
  }
  for(let i=birds.length-1;i>=0;i--){const b=birds[i];b.x+=b.vx*dt;if(b.x>cam.x+world.width/ZOOM+80)birds.splice(i,1);}
}
function burst(x,y,n,col){for(let i=0;i<n;i++){const a=R(0,TAU),v=R(30,140);parts.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v-40,life:R(.3,.7),col,size:R(1.5,3.6),g:140});}}
function ftext(x,y,txt,col,size){ftexts.push({x,y,txt,col,size:size||13,life:1});}
let saveT=8;
function update(dt){
  dayT=(dayT+dt/240)%1;
  shake=Math.max(0,shake-30*dt);
  if(cineT>0)cineT-=dt;
  updPlayer(dt);updPet(dt);updAlly(dt);updMobs(dt);updNpcs(dt);updProjs(dt);updFX(dt);updAmbient(dt);
  NET.posT-=dt;
  if(NET.on&&NET.posT<=0&&p){NET.posT=.12;netSend({t:'pos',x:Math.round(p.x),y:Math.round(p.y),z:zoneKey(),l:p.lvl,n:p.name,cls:p.cls,ap:p.ap});}
  for(const k in NET.players){const o=NET.players[k];
    o.x+=((o.tx||o.x)-o.x)*Math.min(1,dt*8);
    o.y+=((o.ty||o.y)-o.y)*Math.min(1,dt*8);
    if(Math.abs(o.x-(o.tx||o.x))>2||Math.abs(o.y-(o.ty||o.y))>2)o.wk=(o.wk||0)+dt*2.2;}
  const vw=world.width/ZOOM,vh=world.height/ZOOM,lim=ZN.S*T;
  const tx2=lim>vw?clamp(p.x-vw/2,0,lim-vw):(lim-vw)/2;
  const ty2=lim>vh?clamp(p.y-vh/2,0,lim-vh):(lim-vh)/2;
  cam.x+=(tx2-cam.x)*Math.min(1,dt*6);
  cam.y+=(ty2-cam.y)*Math.min(1,dt*6);
  saveT-=dt;if(saveT<=0){saveHero();saveT=8;}
}

const world=$('world'),wg=world.getContext('2d');
function rr(g,x,y,w,h,r){g.beginPath();g.moveTo(x+r,y);g.arcTo(x+w,y,x+w,y+h,r);g.arcTo(x+w,y+h,x,y+h,r);g.arcTo(x,y+h,x,y,r);g.arcTo(x,y,x+w,y,r);g.closePath();}
function plate(g,x,y,txt,col){g.font='700 12px Cinzel,serif';g.textAlign='center';g.lineWidth=3;g.strokeStyle='rgba(0,0,0,.85)';g.strokeText(txt,x,y);g.fillStyle=col;g.fillText(txt,x,y);}
function hpbar(g,x,y,w,pc){g.fillStyle='rgba(0,0,0,.65)';g.fillRect(x-w/2,y,w,5);g.fillStyle='#e2483d';g.fillRect(x-w/2,y,w*clamp(pc,0,1),5);}
function lvlCol(m){if(!p)return '#ffb3a7';if(m.lvl<p.lvl-4)return '#9a9a9a';if(m.lvl>p.lvl+3)return '#ff6b5e';return '#ffb3a7';}
function drawHero(g,x,y,o){
  const t=time,s=o.scale||1;
  g.save();g.translate(x,y);
  g.fillStyle='rgba(0,0,0,.3)';g.beginPath();g.ellipse(0,2,13*s,5.5*s,0,0,TAU);g.fill();
  g.scale((o.dir||1)*s,s);
  const moving=o.moving,bob=moving?Math.sin(o.walk*TAU)*1.6:Math.sin(t*2.2)*.8;
  const lo=moving?Math.sin(o.walk*TAU)*3:0;
  g.translate(0,bob*.4);
  g.fillStyle='#2b2620';
  g.fillRect(-5+lo*.6,-7,3.6,7);g.fillRect(1.4-lo*.6,-7,3.6,7);
  g.fillStyle=o.col;rr(g,-7,-21,14,15,4);g.fill();
  g.fillStyle='rgba(0,0,0,.22)';g.fillRect(-7,-9,14,2.5);
  g.fillStyle=o.col2;g.fillRect(-7,-21,14,3.5);
  const armT=o.armT||1;
  if(armT>=2){g.fillStyle=armT===4?'#c8a83a':armT===3?'#9aa0aa':'#8a6a44';rr(g,-6,-19,12,10,3);g.fill();
    if(armT>=3){g.beginPath();g.arc(-7,-17,3,0,TAU);g.arc(7,-17,3,0,TAU);g.fill();}
    if(armT>=4){g.strokeStyle='#f2d38a';g.lineWidth=1.2;rr(g,-6.5,-19.5,13,11,3);g.stroke();}}
  g.fillStyle=o.col2;rr(g,-9,-18,3.4,8,1.5);g.fill();
  if(o.cls==='guerreiro'||o.cls==='paladino'){g.fillStyle='#7a5438';g.beginPath();g.arc(-9,-12,4.6,0,TAU);g.fill();g.fillStyle='#d8a94e';g.beginPath();g.arc(-9,-12,1.6,0,TAU);g.fill();}
  g.fillStyle=o.skin||'#f0c8a0';g.beginPath();g.arc(0,-27,6.6,0,TAU);g.fill();
  const hs=o.hairStyle||0,hc=o.hairCol||'#5a3a22';
  g.fillStyle=hc;
  if(hs===3){g.fillRect(-1.6,-35,3.2,7);}
  else{g.beginPath();g.arc(0,-28.5,6.7,Math.PI,0);g.fill();g.fillRect(-6.7,-29,3,4);}
  if(hs===1){g.fillRect(-7.4,-28,3,10);g.fillRect(4.4,-28,3,10);}
  if(hs===2){g.beginPath();g.arc(0,-35,3,0,TAU);g.fill();}
  g.fillStyle='#222';g.beginPath();g.arc(3,-27,1,0,TAU);g.fill();
  const a=o.atkT>0?-.7+Math.sin((1-o.atkT/.26)*Math.PI)*1.9:.35;
  g.save();g.translate(7,-14);g.rotate(a);
  const sw=o.wvis||'sword';
  if(sw==='sword'){g.fillStyle='#7a5438';g.fillRect(-1.5,-1,3,7);g.fillStyle='#d8a94e';g.fillRect(-4.5,-2,9,2.4);g.fillStyle='#e8edf4';rr(g,-1.6,-17,3.2,15,1.4);g.fill();}
  else if(sw==='axe'){g.fillStyle='#7a5438';g.fillRect(-1.4,-2,2.8,10);g.fillStyle='#b8bec8';g.beginPath();g.moveTo(-1,-14);g.quadraticCurveTo(-9,-12,-8,-4);g.lineTo(-1,-6);g.closePath();g.fill();g.fillRect(-1,-15,5,4);}
  else if(sw==='staff'){g.fillStyle='#7a5438';g.fillRect(-1.4,-19,2.8,26);g.fillStyle=o.orb||'#8fd8ff';g.beginPath();g.arc(0,-20,4,0,TAU);g.fill();g.fillStyle='rgba(255,255,255,.85)';g.beginPath();g.arc(-1.2,-21.2,1.4,0,TAU);g.fill();}
  else if(sw==='bow'){g.strokeStyle='#7a5438';g.lineWidth=2.6;g.beginPath();g.arc(0,0,10,-1.15,1.15);g.stroke();g.strokeStyle='#e8e8e8';g.lineWidth=1;g.beginPath();g.moveTo(Math.cos(-1.15)*10,Math.sin(-1.15)*10);g.lineTo(Math.cos(1.15)*10,Math.sin(1.15)*10);g.stroke();}
  else if(sw==='hammer'){g.fillStyle='#7a5438';g.fillRect(-1.5,-1,3,9);g.fillStyle='#b8bec8';rr(g,-6,-12,12,9,2);g.fill();}
  else if(sw==='dagger'){g.fillStyle='#3a3a3a';g.fillRect(-1.2,0,2.4,5);g.fillStyle='#dfe6ee';rr(g,-1.4,-10,2.8,10,1.2);g.fill();}
  g.restore();
  g.restore();
}
function drawSlime(g,m){
  const sq=1+Math.sin(time*6+m.seed)*.12,hit=m.hitT>0,s=m.big*.9;
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(m.x,m.y+2,13*s,5*s,0,0,TAU);g.fill();
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.ellipse(m.x,m.y-8*s,12*s*sq,10*s*sq,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':'rgba(0,0,0,.35)';g.lineWidth=1.6;g.stroke();
  g.fillStyle='rgba(255,255,255,.4)';g.beginPath();g.ellipse(m.x-4*s,m.y-13*s,3*s,2*s,-.5,0,TAU);g.fill();
  g.fillStyle='#1e2b18';g.beginPath();g.arc(m.x-3.5*s,m.y-9*s,1.8*s,0,TAU);g.arc(m.x+4*s,m.y-9*s,1.8*s,0,TAU);g.fill();
}
function drawWolf(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,mv=m.state!=='idle'?Math.sin(t*14+m.seed)*2.4:0;
  g.save();g.translate(m.x,m.y);g.scale((m.dir||1)*s,s);
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(0,2,16,5,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':'rgba(0,0,0,.4)';g.lineWidth=3.2;g.lineCap='round';
  [-9,-3,4,9].forEach((lx,i)=>{g.beginPath();g.moveTo(lx,-6);g.lineTo(lx+(i%2?mv:-mv),2);g.stroke();});
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.ellipse(0,-10,14,7.5,0,0,TAU);g.fill();
  g.beginPath();g.arc(12,-14,6.4,0,TAU);g.fill();
  g.beginPath();g.moveTo(9,-19);g.lineTo(11,-25);g.lineTo(14,-19);g.closePath();g.fill();
  g.fillStyle='rgba(0,0,0,.45)';g.beginPath();g.moveTo(14,-14);g.lineTo(21,-12);g.lineTo(14,-10);g.closePath();g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=3.2;g.beginPath();g.moveTo(-13,-12);g.quadraticCurveTo(-19,-17,-17,-22);g.stroke();
  g.fillStyle='#ffcf4d';g.beginPath();g.arc(13,-15,1.5,0,TAU);g.fill();
  g.restore();
}
function drawBat(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,flap=Math.sin(t*12+m.seed)*.7,fo=Math.sin(t*3+m.seed)*3;
  g.save();g.translate(m.x,m.y-10+fo);g.scale((m.dir||1)*s,s);
  g.fillStyle=hit?'#fff':m.c;
  g.save();g.rotate(-flap);g.beginPath();g.moveTo(-3,-2);g.lineTo(-17,-9);g.lineTo(-13,1);g.closePath();g.fill();g.restore();
  g.save();g.rotate(flap);g.beginPath();g.moveTo(3,-2);g.lineTo(17,-9);g.lineTo(13,1);g.closePath();g.fill();g.restore();
  g.beginPath();g.arc(0,0,6,0,TAU);g.fill();
  g.fillStyle=hit?'#fff':'#ff5544';g.beginPath();g.arc(2,-1,1.2,0,TAU);g.arc(-2,-1,1.2,0,TAU);g.fill();
  g.restore();
}
function drawGolem(g,m){
  const t=time,hit=m.hitT>0,s=m.big,bob=Math.sin(t*2+m.seed)*1.2*s;
  g.save();g.translate(m.x,m.y);
  g.fillStyle='rgba(0,0,0,.32)';g.beginPath();g.ellipse(0,3,17*s,6*s,0,0,TAU);g.fill();
  g.translate(0,bob);
  const c=hit?'#fff':m.c;
  g.fillStyle=c;
  rr(g,-15*s,-14*s,7*s,14*s,3);g.fill();rr(g,8*s,-14*s,7*s,14*s,3);g.fill();
  rr(g,-9*s,-17*s,18*s,18*s,4);g.fill();
  rr(g,-6.5*s,-26*s,13*s,10*s,3);g.fill();
  g.strokeStyle='rgba(0,0,0,.3)';g.lineWidth=1.2*s;
  g.beginPath();g.moveTo(-4*s,-10*s);g.lineTo(1*s,-6*s);g.moveTo(3*s,-14*s);g.lineTo(6*s,-10*s);g.stroke();
  g.fillStyle=m.boss?'#ffb347':'#ffdf8a';
  g.beginPath();g.arc(-3*s,-21*s,1.8*s,0,TAU);g.arc(3*s,-21*s,1.8*s,0,TAU);g.fill();
  g.restore();
}
function drawSpirit(g,m){
  const t=time,hit=m.hitT>0,s=m.big,fo=Math.sin(t*3+m.seed)*4;
  g.save();g.translate(m.x,m.y-14+fo);
  g.globalAlpha=.3;g.fillStyle=m.c;g.beginPath();g.arc(0,0,16*s,0,TAU);g.fill();
  g.globalAlpha=hit?.6:.92;
  g.beginPath();g.arc(0,-3*s,9*s,Math.PI,0);
  g.quadraticCurveTo(9*s,8*s,0,13*s);
  g.quadraticCurveTo(-9*s,8*s,-9*s,-3*s);
  g.fillStyle=hit?'#fff':m.c;g.fill();
  g.globalAlpha=1;
  g.fillStyle='#1c1c2c';g.beginPath();g.arc(-3*s,-4*s,1.7*s,0,TAU);g.arc(3*s,-4*s,1.7*s,0,TAU);g.fill();
  g.restore();
}
function drawDragon(g,m){
  const t=time,hit=m.hitT>0,s=m.big,flap=Math.sin(t*4+m.seed)*.35;
  g.save();g.translate(m.x,m.y);g.scale((m.dir||1)*s,s);
  g.fillStyle='rgba(0,0,0,.32)';g.beginPath();g.ellipse(0,4,22,7,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=4;g.lineCap='round';
  g.beginPath();g.moveTo(-14,-8);g.quadraticCurveTo(-26,-12,-30,-20);g.stroke();
  g.fillStyle=hit?'#fff':m.c;
  g.save();g.translate(-2,-14);g.rotate(-flap);g.beginPath();g.moveTo(0,0);g.lineTo(-16,-14);g.lineTo(-6,2);g.closePath();g.fill();g.restore();
  g.save();g.translate(2,-14);g.rotate(flap);g.beginPath();g.moveTo(0,0);g.lineTo(14,-16);g.lineTo(8,2);g.closePath();g.fill();g.restore();
  g.beginPath();g.ellipse(0,-8,16,9,0,0,TAU);g.fill();
  g.fillStyle='rgba(255,255,255,.25)';g.beginPath();g.ellipse(0,-4,12,4.5,0,0,TAU);g.fill();
  g.fillStyle=hit?'#fff':m.c;
  g.beginPath();g.arc(15,-16,6.5,0,TAU);g.fill();
  g.beginPath();g.moveTo(19,-17);g.lineTo(27,-14);g.lineTo(19,-12);g.closePath();g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=2;
  g.beginPath();g.moveTo(12,-21);g.lineTo(10,-26);g.moveTo(16,-22);g.lineTo(15,-27);g.stroke();
  g.fillStyle='#ffcf4d';g.beginPath();g.arc(16,-17,1.6,0,TAU);g.fill();
  g.restore();
}
function drawBoar(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,mv=m.state!=='idle'?Math.sin(t*12+m.seed)*2:0;
  g.save();g.translate(m.x,m.y);g.scale((m.dir||1)*s,s);
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(0,2,15,5,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':'rgba(0,0,0,.4)';g.lineWidth=3.4;
  [-8,-3,4,9].forEach((lx,i)=>{g.beginPath();g.moveTo(lx,-6);g.lineTo(lx+(i%2?mv:-mv),2);g.stroke();});
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.ellipse(0,-9,14,8.5,0,0,TAU);g.fill();
  g.beginPath();g.arc(13,-10,6,0,TAU);g.fill();
  g.fillStyle='#efe8d8';g.beginPath();g.moveTo(15,-7);g.quadraticCurveTo(19,-8,18,-12);g.lineTo(16,-9);g.closePath();g.fill();
  g.fillStyle='#2c2218';g.beginPath();g.arc(16,-11,1.2,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=2;g.beginPath();g.moveTo(-12,-14);g.lineTo(-15,-17);g.moveTo(-8,-15);g.lineTo(-10,-18);g.stroke();
  g.restore();
}
function drawMyconid(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,bo=Math.sin(t*3+m.seed)*1.5;
  g.save();g.translate(m.x,m.y+bo);
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(0,2,10*s,4*s,0,0,TAU);g.fill();
  g.fillStyle=hit?'#fff':'#e0d0b0';rr(g,-4*s,-12*s,8*s,12*s,3*s);g.fill();
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.arc(0,-13*s,10*s,Math.PI,0);g.closePath();g.fill();
  g.fillStyle='rgba(255,255,255,.5)';g.beginPath();g.arc(-4*s,-16*s,2*s,0,TAU);g.arc(4*s,-18*s,1.6*s,0,TAU);g.fill();
  g.fillStyle='#2c2218';g.beginPath();g.arc(-2.4*s,-8*s,1.3*s,0,TAU);g.arc(2.4*s,-8*s,1.3*s,0,TAU);g.fill();
  g.restore();
}
function drawSpider(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.85;
  g.save();g.translate(m.x,m.y);g.scale((m.dir||1)*s,s);
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(0,2,12,4,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':'rgba(0,0,0,.55)';g.lineWidth=2;
  for(let k=0;k<4;k++){const w=Math.sin(t*10+k)*2;
    g.beginPath();g.moveTo(-2,-8);g.lineTo(-12-k*2,-4+w+k*2);g.stroke();
    g.beginPath();g.moveTo(2,-8);g.lineTo(12+k*2,-4-w+k*2);g.stroke();}
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.arc(-3,-8,6,0,TAU);g.fill();g.beginPath();g.arc(5,-9,4.5,0,TAU);g.fill();
  g.fillStyle='#ff5544';g.beginPath();g.arc(6.5,-10,1,0,TAU);g.arc(8.5,-9,1,0,TAU);g.fill();
  g.restore();
}
function drawElemental(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,fo=Math.sin(t*2.5+m.seed)*3;
  g.save();g.translate(m.x,m.y-8+fo);
  g.globalAlpha=.35;g.fillStyle=m.c;g.beginPath();g.arc(0,-6,15*s,0,TAU);g.fill();g.globalAlpha=1;
  g.fillStyle=hit?'#fff':m.c;
  g.beginPath();g.moveTo(0,-22*s);g.lineTo(8*s,-6*s);g.lineTo(0,4*s);g.lineTo(-8*s,-6*s);g.closePath();g.fill();
  g.strokeStyle='rgba(255,255,255,.6)';g.lineWidth=1.4;g.stroke();
  g.beginPath();g.moveTo(-10*s,-8*s);g.lineTo(-5*s,-14*s);g.lineTo(-4*s,-4*s);g.closePath();g.fill();
  g.beginPath();g.moveTo(10*s,-8*s);g.lineTo(5*s,-14*s);g.lineTo(4*s,-4*s);g.closePath();g.fill();
  g.fillStyle='#1c2c3c';g.beginPath();g.arc(-2.4*s,-9*s,1.4*s,0,TAU);g.arc(2.4*s,-9*s,1.4*s,0,TAU);g.fill();
  g.restore();
}
function drawVoidling(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.9,fo=Math.sin(t*3+m.seed)*3;
  g.save();g.translate(m.x,m.y-6+fo);
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.arc(0,-6*s,9*s,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=2.6;
  for(let k=0;k<5;k++){const a=t*2+k*TAU/5;
    g.beginPath();g.moveTo(Math.cos(a)*8*s,-6*s+Math.sin(a)*8*s);g.lineTo(Math.cos(a)*15*s,-6*s+Math.sin(a)*15*s);g.stroke();}
  g.fillStyle='#e8d24a';g.beginPath();g.arc(0,-6*s,3.6*s,0,TAU);g.fill();
  g.fillStyle='#1c1c2c';g.beginPath();g.arc(0,-6*s,1.5*s,0,TAU);g.fill();
  g.restore();
}
function drawWildcat(g,m){
  const t=time,hit=m.hitT>0,s=m.big*.8,mv=m.state!=='idle'?Math.sin(t*14+m.seed)*2:0;
  g.save();g.translate(m.x,m.y);g.scale((m.dir||1)*s,s);
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(0,2,13,4,0,0,TAU);g.fill();
  g.strokeStyle=hit?'#fff':'rgba(0,0,0,.4)';g.lineWidth=2.6;g.lineCap='round';
  [-7,-3,3,7].forEach((lx,i)=>{g.beginPath();g.moveTo(lx,-5);g.lineTo(lx+(i%2?mv:-mv),2);g.stroke();});
  g.fillStyle=hit?'#fff':m.c;g.beginPath();g.ellipse(0,-8,12,5.5,0,0,TAU);g.fill();
  g.beginPath();g.arc(10,-12,4.8,0,TAU);g.fill();
  g.beginPath();g.moveTo(7.5,-16);g.lineTo(8.5,-20);g.lineTo(10.5,-16.5);g.closePath();g.fill();
  g.beginPath();g.moveTo(11.5,-16.5);g.lineTo(13,-20);g.lineTo(14,-16);g.closePath();g.fill();
  g.strokeStyle=hit?'#fff':m.c;g.lineWidth=2.4;g.beginPath();g.moveTo(-11,-9);g.quadraticCurveTo(-16,-14,-14,-18);g.stroke();
  g.fillStyle='#e8d24a';g.beginPath();g.arc(11.5,-12.5,1.2,0,TAU);g.fill();
  g.restore();
}
function drawDarkHero(g,m){
  const s=m.big;
  g.save();g.translate(m.x,m.y);
  g.globalAlpha=.35;g.fillStyle='#f2d38a';g.beginPath();g.arc(0,-24*s,26*s+Math.sin(time*2)*3,0,TAU);g.fill();g.globalAlpha=1;
  g.restore();
  const ap=meta.prevAp||{skin:'#f0c8a0',hairCol:'#2e2419',hairStyle:0,col:'#3a3226',col2:'#171310'};
  const pc=CLASSES[meta.prevCls]||CLASSES.guerreiro;
  const wvk=pc.ranged?'staff':'sword';
  drawHero(g,m.x,m.y,{cls:meta.prevCls,wvis:wvk,orb:'#ffd76a',scale:s,dir:m.dir||1,walk:m.state!=='idle'?time*2.2:0,atkT:m.atkCd>.9?.2:0,moving:m.state!=='idle',skin:ap.skin,hairCol:'#c8b89a',hairStyle:ap.hairStyle,col:'#2c2c34',col2:'#d8a94e',armT:4});
  g.save();g.translate(m.x,m.y-58*s);g.fillStyle='#ffd76a';
  g.beginPath();g.moveTo(-8,0);g.lineTo(-8,-6);g.lineTo(-4,-2);g.lineTo(0,-8);g.lineTo(4,-2);g.lineTo(8,-6);g.lineTo(8,0);g.closePath();g.fill();
  g.restore();
}
function drawGod(g,m){
  const t=time,s=m.big,fo=Math.sin(t*1.6)*4;
  g.save();g.translate(m.x,m.y-10+fo);
  g.globalAlpha=.3;g.fillStyle='#ffe9b0';g.beginPath();g.arc(0,-18*s,(30+Math.sin(t*3)*4)*s,0,TAU);g.fill();g.globalAlpha=1;
  g.strokeStyle='rgba(255,220,130,.7)';g.lineWidth=2;
  for(let k=0;k<3;k++){g.beginPath();g.arc(0,-16*s,(22+k*8)*s,t*(k%2?-.4:.4)+k*2,t*(k%2?-.4:.4)+k*2+2);g.stroke();}
  g.fillStyle=m.hitT>0?'#fff':'#f2ead8';
  g.beginPath();g.moveTo(-11*s,6*s);g.lineTo(-7*s,-24*s);g.lineTo(7*s,-24*s);g.lineTo(11*s,6*s);g.closePath();g.fill();
  g.fillStyle='#f0c8a0';g.beginPath();g.arc(0,-30*s,7*s,0,TAU);g.fill();
  g.fillStyle='#ffd76a';
  g.beginPath();g.moveTo(-7*s,-36*s);g.lineTo(-7*s,-42*s);g.lineTo(-3*s,-38*s);g.lineTo(0,-44*s);g.lineTo(3*s,-38*s);g.lineTo(7*s,-42*s);g.lineTo(7*s,-36*s);g.closePath();g.fill();
  g.fillStyle='#2c2212';g.beginPath();g.arc(-2.5*s,-30*s,1.2*s,0,TAU);g.arc(2.5*s,-30*s,1.2*s,0,TAU);g.fill();
  g.restore();
}
function drawWings(g,m,c,n){
  const t=time,s=m.big,flap=Math.sin(t*3+m.seed)*.3;
  g.save();g.translate(m.x,m.y-m.r*.7);
  g.fillStyle=c;
  for(let k=0;k<n;k++){
    g.save();g.rotate(-.6-flap-k*.22);g.beginPath();g.ellipse(-m.r*1.1,0,m.r*1.2,m.r*.28,0,0,TAU);g.fill();g.restore();
    g.save();g.rotate(.6+flap+k*.22);g.beginPath();g.ellipse(m.r*1.1,0,m.r*1.2,m.r*.28,0,0,TAU);g.fill();g.restore();
  }
  g.restore();
}
function drawHalo(g,m,c){
  g.save();g.translate(m.x,m.y-m.r-40);g.strokeStyle=c;g.lineWidth=3;
  g.beginPath();g.ellipse(0,0,20,6,0,0,TAU);g.stroke();g.restore();
}
function drawBossDeco(g,m){
  const t=time;
  switch(m.bs){
    case 'alphawolf':g.fillStyle='#c8483a';for(let k=0;k<5;k++){g.save();g.translate(m.x-10+k*6,m.y-m.r-6);g.rotate(-.3+k*.15);g.beginPath();g.moveTo(0,0);g.lineTo(3,-12);g.lineTo(6,0);g.closePath();g.fill();g.restore();}break;
    case 'primalwolf':g.globalAlpha=.3;g.fillStyle='#8a6ac8';g.beginPath();g.arc(m.x,m.y-m.r*.6,m.r+14+Math.sin(t*3)*4,0,TAU);g.fill();g.globalAlpha=1;break;
    case 'treant':g.fillStyle='#2f6b34';g.beginPath();g.arc(m.x,m.y-m.r-18,20,0,TAU);g.arc(m.x-14,m.y-m.r-8,12,0,TAU);g.arc(m.x+14,m.y-m.r-8,12,0,TAU);g.fill();break;
    case 'stoneking':g.save();g.translate(m.x,m.y-m.r-26);g.fillStyle='#ffd76a';g.beginPath();g.moveTo(-12,0);g.lineTo(-12,-10);g.lineTo(-6,-4);g.lineTo(0,-14);g.lineTo(6,-4);g.lineTo(12,-10);g.lineTo(12,0);g.closePath();g.fill();g.restore();break;
    case 'ashlord':if(chance(.3))parts.push({x:m.x+R(-20,20),y:m.y-m.r,vx:R(-10,10),vy:R(-40,-20),life:.5,col:'#ff9d4a',size:2,g:-10});break;
    case 'icecolossus':g.fillStyle='#dff0fa';for(let k=0;k<4;k++){g.save();g.translate(m.x-15+k*10,m.y-m.r-4);g.beginPath();g.moveTo(0,0);g.lineTo(3,-14);g.lineTo(6,0);g.closePath();g.fill();g.restore();}break;
    case 'matriarch':g.fillStyle='#3a6a4a';for(let k=0;k<3;k++){g.beginPath();g.ellipse(m.x-6+k*6,m.y-m.r-8,6,10,-.3+k*.3,0,TAU);g.fill();}break;
    case 'herald':g.strokeStyle='#6a4a9a';g.lineWidth=4;g.lineCap='round';for(let k=0;k<6;k++){const a=t*1.5+k*TAU/6;
      g.beginPath();g.moveTo(m.x,m.y-m.r*.5);g.quadraticCurveTo(m.x+Math.cos(a)*30,m.y-m.r*.5+Math.sin(a)*30,m.x+Math.cos(a)*48,m.y-m.r*.5+Math.sin(a)*48+Math.sin(t*4+k)*8);g.stroke();}break;
    case 'crownkeeper':drawHalo(g,m,'rgba(255,215,106,.9)');break;
    case 'seraphboss':drawWings(g,m,'rgba(255,230,170,.85)',3);break;
    case 'marblearch':drawWings(g,m,'rgba(240,235,220,.9)',2);drawHalo(g,m,'rgba(255,240,200,.9)');break;
    case 'vigil':g.strokeStyle='rgba(255,240,180,.5)';g.lineWidth=3;for(let k=0;k<6;k++){const a=t+k*TAU/6;
      g.beginPath();g.moveTo(m.x+Math.cos(a)*m.r,m.y-m.r*.6+Math.sin(a)*m.r);g.lineTo(m.x+Math.cos(a)*(m.r+26),m.y-m.r*.6+Math.sin(a)*(m.r+26));g.stroke();}break;
  }
}
function drawMobE(g,m){
  if(m.v==='slime')drawSlime(g,m);
  else if(m.v==='wolf')drawWolf(g,m);
  else if(m.v==='bat')drawBat(g,m);
  else if(m.v==='golem')drawGolem(g,m);
  else if(m.v==='spirit')drawSpirit(g,m);
  else if(m.v==='dragon')drawDragon(g,m);
  else if(m.v==='god')drawGod(g,m);
  else if(m.v==='darkhero')drawDarkHero(g,m);
  else if(m.v==='bandit'){drawHero(g,m.x,m.y,{cls:'assassino',wvis:'dagger',scale:1.25,dir:m.dir||1,walk:m.state!=='idle'?time*2.2:0,atkT:(m.state==='chase'&&m.atkCd>.8)?.2:0,moving:m.state!=='idle',col:m.c,col2:'#241f1a',skin:'#d9a066',hairCol:'#1e1a14',hairStyle:0,armT:2});}
  else if(m.v==='skeleton'){drawHero(g,m.x,m.y,{cls:'guerreiro',wvis:'sword',scale:1.2,dir:m.dir||1,walk:m.state!=='idle'?time*2.2:0,atkT:(m.state==='chase'&&m.atkCd>.8)?.2:0,moving:m.state!=='idle',col:'#8a8a80',col2:'#5c5c54',skin:'#e8e4da',hairCol:'#e8e4da',hairStyle:3,armT:1});}
  else if(m.v==='cultist'){drawHero(g,m.x,m.y,{cls:'necromante',wvis:'staff',orb:'#c86ae0',scale:1.25,dir:m.dir||1,walk:m.state!=='idle'?time*2.2:0,atkT:m.atkCd>1.3?.2:0,moving:m.state!=='idle',col:m.c,col2:'#1c1420',skin:'#d9a066',hairCol:'#2c2234',hairStyle:1,armT:1});}
  else if(m.v==='seraph'){drawWings(g,m,'rgba(255,250,230,.9)',2);drawHero(g,m.x,m.y,{cls:'paladino',wvis:'hammer',scale:1.25,dir:m.dir||1,walk:m.state!=='idle'?time*2.2:0,atkT:m.atkCd>1.3?.2:0,moving:m.state!=='idle',col:m.c,col2:'#c8b070',skin:'#f0d8b8',hairCol:'#f0e8d0',hairStyle:2,armT:3});}
  else if(m.v==='boar')drawBoar(g,m);
  else if(m.v==='myconid')drawMyconid(g,m);
  else if(m.v==='spider')drawSpider(g,m);
  else if(m.v==='elemental')drawElemental(g,m);
  else if(m.v==='voidling')drawVoidling(g,m);
  else if(m.v==='wildcat')drawWildcat(g,m);
  if(m.boss&&m.bs)drawBossDeco(g,m);
  if(m.frenzyT&&m.frenzyT>time){g.globalAlpha=.35+.15*Math.sin(time*8);g.strokeStyle='#ff5544';g.lineWidth=3;
    g.beginPath();g.arc(m.x,m.y-m.r*.5,m.r+10,0,TAU);g.stroke();g.globalAlpha=1;}
  if(m.boss){g.save();g.translate(m.x,m.y-m.r-34*m.big/2);g.fillStyle='#ffd76a';
    g.beginPath();g.moveTo(-7,0);g.lineTo(-7,-5);g.lineTo(-3,-1.5);g.lineTo(0,-7);g.lineTo(3,-1.5);g.lineTo(7,-5);g.lineTo(7,0);g.closePath();g.fill();g.restore();}
}
function drawPetKind(g,kind,x,y,stage,dir){
  const s=(stage===2?1.25:stage===1?1.0:.75);
  g.save();g.translate(x,y);g.scale(dir||1,1);
  g.fillStyle='rgba(0,0,0,.22)';g.beginPath();g.ellipse(0,2,9*s,3*s,0,0,TAU);g.fill();
  const t=time;
  if(kind==='wolf'){
    g.fillStyle='#8a94a6';g.beginPath();g.ellipse(0,-5*s,8*s,4.5*s,0,0,TAU);g.fill();
    g.beginPath();g.arc(7*s,-7*s,3.6*s,0,TAU);g.fill();
    g.beginPath();g.moveTo(5*s,-10*s);g.lineTo(6*s,-13*s);g.lineTo(8*s,-10*s);g.closePath();g.fill();
    g.fillStyle='#ffcf4d';g.beginPath();g.arc(8*s,-7.5*s,.9*s,0,TAU);g.fill();
  }else if(kind==='cat'){
    g.fillStyle='#c8a06a';g.beginPath();g.ellipse(0,-4*s,7*s,4*s,0,0,TAU);g.fill();
    g.beginPath();g.arc(6*s,-7*s,3.2*s,0,TAU);g.fill();
    g.beginPath();g.moveTo(4*s,-9.5*s);g.lineTo(4.8*s,-12.5*s);g.lineTo(6.4*s,-10*s);g.closePath();g.fill();
    g.beginPath();g.moveTo(7*s,-10*s);g.lineTo(8.4*s,-12.5*s);g.lineTo(9*s,-9.5*s);g.closePath();g.fill();
    g.strokeStyle='#c8a06a';g.lineWidth=2*s;g.beginPath();g.moveTo(-6*s,-4*s);g.quadraticCurveTo(-10*s,-8*s+Math.sin(t*6)*2,-9*s,-11*s);g.stroke();
  }else if(kind==='rabbit'){
    g.fillStyle='#e0dcd4';g.beginPath();g.ellipse(0,-4*s,6*s,5*s,0,0,TAU);g.fill();
    g.beginPath();g.arc(4*s,-8*s,3*s,0,TAU);g.fill();
    g.beginPath();g.ellipse(3*s,-13*s,1.3*s,3.6*s,-.2,0,TAU);g.ellipse(5.5*s,-13*s,1.3*s,3.6*s,.2,0,TAU);g.fill();
    g.fillStyle='#2c2218';g.beginPath();g.arc(5*s,-8.3*s,.8*s,0,TAU);g.fill();
  }else if(kind==='bear'){
    g.fillStyle='#7a5a3a';g.beginPath();g.ellipse(0,-6*s,9*s,6*s,0,0,TAU);g.fill();
    g.beginPath();g.arc(8*s,-9*s,4*s,0,TAU);g.fill();
    g.beginPath();g.arc(6*s,-13*s,1.6*s,0,TAU);g.arc(10*s,-13*s,1.6*s,0,TAU);g.fill();
    g.fillStyle='#2c2218';g.beginPath();g.arc(9*s,-9.5*s,.9*s,0,TAU);g.fill();
  }else if(kind==='fox'){
    g.fillStyle='#d87a3a';g.beginPath();g.ellipse(0,-5*s,8*s,4.2*s,0,0,TAU);g.fill();
    g.beginPath();g.arc(7*s,-8*s,3.4*s,0,TAU);g.fill();
    g.beginPath();g.moveTo(5*s,-11*s);g.lineTo(5.8*s,-14*s);g.lineTo(7.6*s,-11*s);g.closePath();g.fill();
    g.beginPath();g.moveTo(8*s,-11*s);g.lineTo(9.6*s,-14*s);g.lineTo(10.4*s,-10.5*s);g.closePath();g.fill();
    g.strokeStyle='#d87a3a';g.lineWidth=2.6*s;g.beginPath();g.moveTo(-7*s,-5*s);g.quadraticCurveTo(-12*s,-9*s+Math.sin(t*6)*2,-11*s,-12*s);g.stroke();
    g.fillStyle='#fff';g.beginPath();g.arc(-11*s,-12*s,1.4*s,0,TAU);g.fill();
  }else if(kind==='snake'){
    g.strokeStyle='#5a8a4a';g.lineWidth=3*s;g.lineCap='round';
    g.beginPath();g.moveTo(-9*s,0);
    for(let k=0;k<5;k++)g.quadraticCurveTo(-9*s+k*4*s+2*s,Math.sin(t*5+k)*3*s,-9*s+(k+1)*4*s,0);
    g.stroke();
    g.fillStyle='#5a8a4a';g.beginPath();g.arc(11*s,0,2.4*s,0,TAU);g.fill();
  }
  g.restore();
}
function drawPetE(g){
  const pt=activePet();
  if(!pt||p.dead)return;
  if(pt.kind==='eagle'){
    const t=time,ex=p.x+Math.sin(t*1.4)*26,ey=p.y-56+Math.sin(t*2.4)*6;
    const s=petStage(pt)===2?1.25:petStage(pt)===1?1:.75;
    g.save();g.translate(ex,ey);g.scale(p.dir||1,1);
    g.fillStyle='#8a6a44';g.beginPath();g.ellipse(0,0,6*s,3.4*s,0,0,TAU);g.fill();
    const f=Math.sin(t*9)*4*s;
    g.strokeStyle='#6a4c2e';g.lineWidth=2.4*s;g.lineCap='round';
    g.beginPath();g.moveTo(-2*s,-1);g.quadraticCurveTo(-8*s,-4-f,-13*s,-2-f);g.stroke();
    g.beginPath();g.moveTo(2*s,-1);g.quadraticCurveTo(8*s,-4-f,13*s,-2-f);g.stroke();
    g.fillStyle='#e8d8b0';g.beginPath();g.arc(5*s,-1.4*s,2*s,0,TAU);g.fill();
    g.restore();
    return;
  }
  drawPetKind(g,pt.kind,p.petX,p.petY,petStage(pt),p.dir);
}
function drawAllyE(g){
  const a=activeAlly();
  if(!a||p.dead)return;
  const cls=CLASSES[a.cls];
  const wvk=cls.ranged?'staff':(a.cls==='assassino'?'dagger':a.cls==='paladino'?'hammer':'sword');
  drawHero(g,p.allyX,p.allyY,{cls:a.cls,wvis:wvk,orb:'#fff',scale:1.3,dir:p.dir,walk:time*2,atkT:p.allyCd>.55?.2:0,moving:true,skin:'#f0c8a0',hairCol:a.hair,col:a.col,col2:a.col2,armT:3});
  const rv=romVal(a.id);
  if(rv>=70){g.font='12px serif';g.textAlign='center';g.fillText('❤',p.allyX,p.allyY-52);}
  plate(g,p.allyX,p.allyY-44,a.name.split(',')[0],'#cfe8ff');
}
function drawCritterE(g,c){
  drawPetKind(g,c.kind,c.x,c.y,0,c.dir);
  plate(g,c.x,c.y-24,PETKINDS[c.kind].n+' selvagem','#cfe8ff');
  if(p&&!p.dead&&dist(p.x,p.y,c.x,c.y)<70){
    g.fillStyle='rgba(0,0,0,.7)';rr(g,c.x-7,c.y-42,14,13,3);g.fill();g.strokeStyle='#d8a94e';g.stroke();
    g.fillStyle='#ffd76a';g.font='700 9px Nunito';g.textAlign='center';g.fillText('E',c.x,c.y-32);}
}
function drawNpcE(g,n){
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(n.x,n.y+1,11,4.5,0,0,TAU);g.fill();
  g.fillStyle=n.role==='shop'?'#c07a3a':n.role==='boat'?'#3a6ea8':n.role==='guide'?'#7a68b0':'#6a8a5a';
  g.beginPath();g.moveTo(n.x-9,n.y);g.lineTo(n.x-6,n.y-22);g.lineTo(n.x+6,n.y-22);g.lineTo(n.x+9,n.y);g.closePath();g.fill();
  g.fillStyle='#f0c8a0';g.beginPath();g.arc(n.x,n.y-27,6.4,0,TAU);g.fill();
  g.font='15px serif';g.textAlign='center';g.fillText(n.ico,n.x,n.y-38);
  plate(g,n.x,n.y-52,n.name,'#ffe9b0');
  const mk=npcMarker(n);
  if(mk){const by=n.y-66+Math.sin(time*3)*3;
    g.font='900 17px Cinzel,serif';g.textAlign='center';g.lineWidth=3.4;g.strokeStyle='rgba(0,0,0,.85)';g.strokeText(mk,n.x,by);
    g.fillStyle=mk==='!'?'#ffd24d':'#7ef28a';g.fillText(mk,n.x,by);}
  if(p&&!p.dead&&dist(p.x,p.y,n.x,n.y)<80){
    g.fillStyle='rgba(0,0,0,.7)';rr(g,n.x-7,n.y-84,14,13,3);g.fill();g.strokeStyle='#d8a94e';g.lineWidth=1;g.stroke();
    g.fillStyle='#ffd76a';g.font='700 9px Nunito';g.textAlign='center';g.fillText('E',n.x,n.y-74);}
}
function drawTree(g,tx,ty,seed,variant){
  const px=tx*T+T/2,py=ty*T+T-8,sw=Math.sin(time*1.3+seed*9)*2.2;
  g.fillStyle='rgba(0,0,0,.25)';g.beginPath();g.ellipse(px+4,py+3,14,5,0,0,TAU);g.fill();
  g.fillStyle=variant===1?'#8a5a34':variant===2?'#3c3c44':'#6e4a2c';
  rr(g,px-3.5,py-18,7,19,2);g.fill();
  const gr=variant===1?['#8a5c2a','#a06c32','#b87c3c']:variant===2?['#242438','#2c2c44','#36364e']:['#2f6b34','#3d8040','#4c9650'];
  g.fillStyle=gr[0];g.beginPath();g.arc(px+sw,py-30,15,0,TAU);g.fill();
  g.fillStyle=gr[1];g.beginPath();g.arc(px-8+sw,py-22,10,0,TAU);g.arc(px+9+sw,py-23,10,0,TAU);g.fill();
  g.fillStyle=gr[2];g.beginPath();g.arc(px-2+sw,py-35,8,0,TAU);g.fill();
}
function drawRock(g,tx,ty,seed,dark){
  const px=tx*T+T/2,py=ty*T+T-12;
  g.fillStyle='rgba(0,0,0,.25)';g.beginPath();g.ellipse(px+3,py+8,15,5,0,0,TAU);g.fill();
  g.fillStyle=dark?'#3a3a44':(seed>.5?'#847b6d':'#77705f');
  g.beginPath();g.moveTo(px-14,py+7);g.lineTo(px-10,py-7);g.lineTo(px-1,py-13);g.lineTo(px+10,py-7);g.lineTo(px+14,py+7);g.closePath();g.fill();
  g.fillStyle='rgba(255,255,255,.14)';g.beginPath();g.moveTo(px-10,py-7);g.lineTo(px-1,py-13);g.lineTo(px+4,py-7);g.closePath();g.fill();
}
function drawOre(g,tx,ty,seed){
  const px=tx*T+T/2,py=ty*T+T-12;
  g.fillStyle='rgba(0,0,0,.3)';g.beginPath();g.ellipse(px+3,py+8,15,5,0,0,TAU);g.fill();
  g.fillStyle='#5c564e';
  g.beginPath();g.moveTo(px-14,py+7);g.lineTo(px-10,py-7);g.lineTo(px-1,py-13);g.lineTo(px+10,py-7);g.lineTo(px+14,py+7);g.closePath();g.fill();
  const ok=oreKeyFor(ZN.parent||ZN.id);
  const cc={iron:'#c89a6a',mithril:'#8fd8e0',crystal:'#c8a2ff'}[ok]||'#c89a6a';
  const gl=.5+.4*Math.sin(time*3+seed*9);
  g.fillStyle=cc;g.globalAlpha=gl;
  g.beginPath();g.arc(px-5,py-2,2.4,0,TAU);g.arc(px+4,py+2,2,0,TAU);g.arc(px+1,py-7,1.8,0,TAU);g.fill();
  g.globalAlpha=1;
}
function drawHouse(g,b,dark){
  const px=b.x*T,py=b.y*T,w=b.w*T,h=b.h*T;
  if(b.kind==='stall'){
    g.fillStyle='rgba(0,0,0,.25)';g.fillRect(px-2,py+h-4,w+4,6);
    g.fillStyle='#7a5438';g.fillRect(px+3,py+4,3,h-4);g.fillRect(px+w-6,py+4,3,h-4);
    g.fillStyle='#c8a06a';g.fillRect(px+6,py+h-14,w-12,10);
    for(let i=0;i<4;i++){g.fillStyle=i%2?'#c8484a':'#efe0b8';g.fillRect(px+i*(w/4),py,w/4,10);}
    return;
  }
  if(b.kind==='tent'){
    g.fillStyle='rgba(0,0,0,.25)';g.fillRect(px-2,py+h-3,w+4,5);
    g.fillStyle='#9a7a52';g.beginPath();g.moveTo(px-2,py+h);g.lineTo(px+w/2,py+2);g.lineTo(px+w+2,py+h);g.closePath();g.fill();
    g.strokeStyle='#5e4426';g.lineWidth=1.5;g.stroke();
    g.fillStyle='#3a2c18';g.beginPath();g.moveTo(px+w/2-6,py+h);g.lineTo(px+w/2,py+h-12);g.lineTo(px+w/2+6,py+h);g.closePath();g.fill();
    return;
  }
  g.fillStyle='rgba(0,0,0,.3)';g.fillRect(px,py+h-2,w+8,8);
  g.fillStyle='#c9b088';g.fillRect(px+2,py+h-26,w-4,24);
  g.strokeStyle='#6e4a2c';g.lineWidth=2;g.strokeRect(px+2,py+h-26,w-4,24);
  g.fillStyle='#4a3222';g.fillRect(px+w/2-7,py+h-18,14,16);
  [[px+8,py+h-20],[px+w-15,py+h-20]].forEach(wp=>{g.fillStyle=dark>.25?'#ffd98a':'#3a3f4a';g.fillRect(wp[0],wp[1],8,8);});
  g.fillStyle='#8a4a3a';g.beginPath();g.moveTo(px-4,py+h-24);g.lineTo(px+w/2,py-2);g.lineTo(px+w+4,py+h-24);g.closePath();g.fill();
  g.strokeStyle='#5e2f24';g.lineWidth=1.6;g.stroke();
}
function drawCampfire(g,x,y){
  g.fillStyle='rgba(0,0,0,.28)';g.beginPath();g.ellipse(x,y+3,12,4,0,0,TAU);g.fill();
  g.save();g.translate(x,y);
  g.strokeStyle='#5a3c24';g.lineWidth=4;g.lineCap='round';
  g.beginPath();g.moveTo(-8,2);g.lineTo(8,-2);g.moveTo(-8,-2);g.lineTo(8,2);g.stroke();
  const f=Math.sin(time*9+x)*1.5;
  g.fillStyle='#e25822';g.beginPath();g.moveTo(-5,0);g.quadraticCurveTo(-4,-10-f,0,-14-f);g.quadraticCurveTo(4,-10-f,5,0);g.closePath();g.fill();
  g.fillStyle='#ffb347';g.beginPath();g.moveTo(-2.6,0);g.quadraticCurveTo(-2,-6-f*.7,0,-8.5-f*.7);g.quadraticCurveTo(2,-6-f*.7,2.6,0);g.closePath();g.fill();
  g.restore();
}
function drawCaveDoor(g,x,y,mine){
  g.fillStyle='rgba(0,0,0,.3)';g.beginPath();g.ellipse(x,y+4,26,8,0,0,TAU);g.fill();
  g.fillStyle=mine?'#5c5046':'#4c4650';g.beginPath();g.moveTo(x-24,y+4);g.lineTo(x-18,y-26);g.lineTo(x-4,y-36);g.lineTo(x+12,y-28);g.lineTo(x+22,y+4);g.closePath();g.fill();
  g.fillStyle='#12101a';g.beginPath();g.ellipse(x,y-2,12,16,0,Math.PI,0);g.closePath();g.fill();
  if(mine){g.strokeStyle='#7a5438';g.lineWidth=3;g.beginPath();g.moveTo(x-16,y+2);g.lineTo(x-13,y-20);g.moveTo(x+14,y+2);g.lineTo(x+11,y-20);g.stroke();}
  plate(g,x,y-44,mine?'Mina':'Caverna',mine?'#ffd76a':'#c8b8ff');
  if(p&&!p.dead&&dist(p.x,p.y,x,y)<90){
    g.fillStyle='rgba(0,0,0,.7)';rr(g,x-7,y-62,14,13,3);g.fill();g.strokeStyle='#d8a94e';g.stroke();
    g.fillStyle='#ffd76a';g.font='700 9px Nunito';g.textAlign='center';g.fillText('E',x,y-52);}
}
function drawExitDoor(g,x,y){
  g.save();g.translate(x,y);
  g.strokeStyle='rgba(200,220,255,'+(.6+.3*Math.sin(time*3))+')';g.lineWidth=3;
  g.beginPath();g.ellipse(0,-16,16,24,0,0,TAU);g.stroke();
  g.fillStyle='rgba(160,200,255,.25)';g.beginPath();g.ellipse(0,-16,13,21,0,0,TAU);g.fill();
  g.restore();
  plate(g,x,y-48,'Saída','#9fd8ff');
}
function dayDark(){if(!ZN||ZN.isCave||ZN.id>=10)return 0;return Math.max(0,Math.sin(dayT*TAU-Math.PI/2))*.9;}
let mmTimer=0;
function buildMinimap(){
  const mm=$('mm');mm.width=ZN.S;mm.height=ZN.S;
  const g=mm.getContext('2d'),pal=ZN.pal;
  for(let y=0;y<ZN.S;y++)for(let x=0;x<ZN.S;x++){
    const t=ZN.map[y][x];
    g.fillStyle=t===1?pal.sea:t===2?pal.sand:t===4?'#8f887a':t===3?'#8a6a44':t===5?'#2c5a2f':t===9?'#c8a2ff':t===6?(ZN.isCave?'#191520':'#6e685e'):t===8?'#4a4250':pal.grass[0];
    g.fillRect(x,y,1,1);
  }
}
function renderMinimapDots(){
  if((mmTimer++)%2)return;
  const g=$('dots').getContext('2d');g.clearRect(0,0,150,150);
  const s=150/(ZN.S*T);
  g.fillStyle='#ff5544';for(const m of ZN.mobs)if(!m.dead)g.fillRect(m.x*s-1,m.y*s-1,m.boss?5:2,m.boss?5:2);
  g.fillStyle='#ffe08a';for(const n of ZN.npcs)g.fillRect(n.x*s-1.5,n.y*s-1.5,3,3);
  g.fillStyle='#8fe0ff';for(const c of ZN.critters)g.fillRect(c.x*s-1.5,c.y*s-1.5,3,3);
  g.fillStyle='#9fe8ff';for(const k in NET.players){const o=NET.players[k];if(o.z===zoneKey())g.fillRect(o.x*s-1.5,o.y*s-1.5,3,3);}
  if(ZN.dock){g.fillStyle='#c8a06a';g.fillRect(ZN.dock.x*s-2,ZN.dock.y*s-2,4,4);}
  if(ZN.caveDoor){g.fillStyle='#b88ae0';g.fillRect(ZN.caveDoor.x*s-2,ZN.caveDoor.y*s-2,4,4);}
  if(ZN.mineDoor){g.fillStyle='#ffd76a';g.fillRect(ZN.mineDoor.x*s-2,ZN.mineDoor.y*s-2,4,4);}
  if(ZN.exit){g.fillStyle='#9fd8ff';g.fillRect(ZN.exit.x*s-2,ZN.exit.y*s-2,4,4);}
  g.fillStyle='#fff';g.beginPath();g.arc(p.x*s,p.y*s,2.8,0,TAU);g.fill();
  const f=$('mmFrame'),vw=world.width/ZOOM,vh=world.height/ZOOM;
  f.style.left=clamp(cam.x*s,0,148)+'px';f.style.top=clamp(cam.y*s,0,148)+'px';
  f.style.width=Math.min(150,vw*s)+'px';f.style.height=Math.min(150,vh*s)+'px';
}
function drawAmbient(g){
  for(const a of ZN.amb){
    const al=clamp(a.life/1.5,0,1);g.globalAlpha=al;
    if(a.k==='butterfly'){g.fillStyle=a.c;const f=Math.sin(time*16+a.s)*2.6;
      g.beginPath();g.ellipse(a.x-2.2-f*.5,a.y,2.6,1.7,-.5,0,TAU);g.ellipse(a.x+2.2+f*.5,a.y,2.6,1.7,.5,0,TAU);g.fill();}
    else if(a.k==='leaf'||a.k==='petal'){g.fillStyle=a.c;g.save();g.translate(a.x,a.y);g.rotate(a.t*2+a.s);
      g.beginPath();g.ellipse(0,0,3,1.6,0,0,TAU);g.fill();g.restore();}
    else if(a.k==='snow'){g.fillStyle='rgba(255,255,255,.92)';g.beginPath();g.arc(a.x,a.y,1.7,0,TAU);g.fill();}
    else if(a.k==='ember'){g.fillStyle=a.c;g.fillRect(a.x,a.y,2.2,2.2);}
    else if(a.k==='firefly'){const gl=.35+.55*Math.sin(time*3+a.s*7);g.fillStyle='rgba(200,255,120,'+clamp(gl,0,1)+')';g.beginPath();g.arc(a.x,a.y,1.9,0,TAU);g.fill();}
  }
  g.globalAlpha=1;
}
function drawBirds(g){
  g.strokeStyle='rgba(30,34,40,.8)';g.lineWidth=2;g.lineCap='round';
  for(const b of birds){const f=Math.sin(time*10+b.ph)*3.4;
    g.beginPath();g.moveTo(b.x-6,b.y+f);g.quadraticCurveTo(b.x-2,b.y-2,b.x,b.y);g.quadraticCurveTo(b.x+2,b.y-2,b.x+6,b.y+f);g.stroke();}
}
function renderGame(){
  const g=wg,W=world.width,H=world.height;
  g.setTransform(1,0,0,1,0,0);g.clearRect(0,0,W,H);
  const shx=shake>0?R(-shake,shake):0,shy=shake>0?R(-shake,shake):0;
  g.setTransform(ZOOM,0,0,ZOOM,shx*ZOOM,shy*ZOOM);
  g.save();g.translate(-cam.x,-cam.y);
  const vw=W/ZOOM,vh=H/ZOOM;
  const x0=Math.max(0,Math.floor(cam.x/T)-1),x1=Math.min(ZN.S-1,Math.ceil((cam.x+vw)/T)+1);
  const y0=Math.max(0,Math.floor(cam.y/T)-1),y1=Math.min(ZN.S-1,Math.ceil((cam.y+vh)/T)+1);
  const pal=ZN.pal;
  for(let ty=y0;ty<=y1;ty++)for(let tx=x0;tx<=x1;tx++){
    const t=ZN.map[ty][tx],px=tx*T,py=ty*T,n=ZN.shade[ty][tx];
    if(t===1){g.fillStyle=pal.sea;g.fillRect(px,py,T,T);
      if(((tx*3+ty*7+Math.floor(time*2.5))%6)===0){g.fillStyle=pal.sky?'rgba(255,255,255,.35)':'rgba(255,255,255,.16)';g.fillRect(px+4,py+10+Math.sin(time+tx)*4,16,2);}
      continue;}
    g.fillStyle=t===2?pal.sand:t===3?'#8a6a44':t===4?'#8f887a':t===8?(n>.8?pal.grass[2]:n>.2?pal.grass[0]:pal.grass[1]):(n>.8?pal.grass[2]:n>.2?pal.grass[0]:pal.grass[1]);
    g.fillRect(px,py,T,T);
    if(t===4){g.strokeStyle='rgba(0,0,0,.15)';g.strokeRect(px+.5,py+.5,T,T);}
    if(t===3){g.fillStyle='rgba(0,0,0,.2)';for(let k=0;k<5;k++)g.fillRect(px,py+k*8,T,1.6);}
    if(t===0&&!pal.sky&&n>.78){const sw2=Math.sin(time*2+tx*1.3+ty*.8)*1.6;
      g.strokeStyle='rgba(30,70,28,.55)';g.lineWidth=1.4;
      g.beginPath();g.moveTo(px+9,py+T-5);g.lineTo(px+9+sw2,py+T-13);g.moveTo(px+16,py+T-4);g.lineTo(px+16+sw2,py+T-11);g.moveTo(px+25,py+T-6);g.lineTo(px+25+sw2,py+T-14);g.stroke();}
  }
  for(const d of ZN.decor){
    if(d.x<cam.x-T||d.x>cam.x+vw+T||d.y<cam.y-T||d.y>cam.y+vh+T)continue;
    const px=d.x,py=d.y;
    if(d.k==='flower'){g.fillStyle=d.s>.5?'#e86a6a':'#e8d24a';g.beginPath();g.arc(px,py,2.2,0,TAU);g.fill();g.fillStyle='#3d8040';g.fillRect(px-.8,py,1.6,4.5);}
    else if(d.k==='tuft'){g.strokeStyle='#3f7a35';g.lineWidth=1.2;g.beginPath();g.moveTo(px-3,py+3);g.lineTo(px-1,py-3);g.moveTo(px,py+3);g.lineTo(px+1,py-4);g.moveTo(px+3,py+3);g.lineTo(px+2,py-2);g.stroke();}
    else if(d.k==='peb'){g.fillStyle='#9a9284';g.beginPath();g.arc(px,py,2.4,0,TAU);g.fill();}
    else if(d.k==='mush'){g.fillStyle='#d8484a';g.beginPath();g.arc(px,py,2.6,Math.PI,0);g.fill();g.fillStyle='#e8e0c8';g.fillRect(px-1,py,2,3);}
    else if(d.k==='star'){g.fillStyle='rgba(255,255,255,'+(.4+.4*Math.sin(time*2+d.s*9))+')';g.beginPath();g.arc(px,py,1.6,0,TAU);g.fill();}
    else if(d.k==='crystal'){g.fillStyle='rgba(140,220,230,'+(.5+.3*Math.sin(time*2+d.s*7))+')';g.beginPath();g.moveTo(px,py);g.lineTo(px+3,py-8);g.lineTo(px+6,py);g.closePath();g.fill();}
  }
  const dark=dayDark();
  const dl=[];
  for(let ty=y0;ty<=y1;ty++)for(let tx=x0;tx<=x1;tx++){
    const t=ZN.map[ty][tx];
    if(t===5)dl.push({y:ty*T+T-8,f:()=>drawTree(g,tx,ty,ZN.shade[ty][tx],pal.tree)});
    else if(t===6)dl.push({y:ty*T+T-10,f:()=>drawRock(g,tx,ty,ZN.shade[ty][tx],pal.dark)});
    else if(t===9)dl.push({y:ty*T+T-10,f:()=>drawOre(g,tx,ty,ZN.shade[ty][tx])});
  }
  for(const b of ZN.buildings)dl.push({y:(b.y+b.h)*T,f:()=>drawHouse(g,b,dark)});
  for(const n of ZN.npcs)dl.push({y:n.y,f:()=>drawNpcE(g,n)});
  for(const c of ZN.critters)dl.push({y:c.y,f:()=>drawCritterE(g,c)});
  for(const cf of ZN.campfires)dl.push({y:cf.y,f:()=>drawCampfire(g,cf.x,cf.y)});
  if(ZN.mineDoor)dl.push({y:ZN.mineDoor.y,f:()=>drawCaveDoor(g,ZN.mineDoor.x,ZN.mineDoor.y,true)});
  if(ZN.caveDoor)dl.push({y:ZN.caveDoor.y,f:()=>drawCaveDoor(g,ZN.caveDoor.x,ZN.caveDoor.y,false)});
  if(ZN.exit)dl.push({y:ZN.exit.y,f:()=>drawExitDoor(g,ZN.exit.x,ZN.exit.y)});
  if(ZN.dock&&prog.boat)dl.push({y:ZN.dock.y,f:()=>{
    const bx=ZN.dock.x,by=ZN.dock.y+8;
    g.fillStyle='#7a5438';g.beginPath();g.moveTo(bx-20,by);g.quadraticCurveTo(bx,by+14,bx+20,by);g.lineTo(bx+14,by-6);g.lineTo(bx-14,by-6);g.closePath();g.fill();
    g.strokeStyle='#5a3c24';g.lineWidth=2;g.beginPath();g.moveTo(bx,by-6);g.lineTo(bx,by-26);g.stroke();
    g.fillStyle='#efe0b8';g.beginPath();g.moveTo(bx,by-26);g.lineTo(bx+12,by-14);g.lineTo(bx,by-12);g.closePath();g.fill();}});
  if(ZN.portal)dl.push({y:ZN.portal.y,f:()=>{
    const px2=ZN.portal.x,py2=ZN.portal.y;
    g.save();g.translate(px2,py2-20);
    g.strokeStyle='rgba(200,180,255,.9)';g.lineWidth=3;
    for(let k=0;k<2;k++){g.beginPath();g.ellipse(0,0,26+k*8,38+k*8,Math.sin(time+k)*.2,0,TAU);g.stroke();}
    g.fillStyle='rgba(160,130,255,'+(.35+.15*Math.sin(time*3))+')';g.beginPath();g.ellipse(0,0,20,32,0,0,TAU);g.fill();
    g.restore();
    plate(g,px2,py2-70,'Portal do Éter','#c8b8ff');}});
  if(ZN.throne)dl.push({y:ZN.throne.y,f:()=>{
    const px2=ZN.throne.x,py2=ZN.throne.y;
    g.fillStyle='#c8a83a';rr(g,px2-16,py2-40,32,42,4);g.fill();
    g.fillStyle='#8a6a1a';rr(g,px2-12,py2-14,24,16,3);g.fill();
    g.fillStyle='#f2d38a';g.beginPath();g.moveTo(px2-16,py2-40);g.lineTo(px2-16,py2-52);g.lineTo(px2-8,py2-44);g.lineTo(px2,py2-56);g.lineTo(px2+8,py2-44);g.lineTo(px2+16,py2-52);g.lineTo(px2+16,py2-40);g.closePath();g.fill();
    if(prog.godSlain){g.globalAlpha=.4+.2*Math.sin(time*3);g.fillStyle='#ffe9b0';g.beginPath();g.arc(px2,py2-24,40,0,TAU);g.fill();g.globalAlpha=1;}
    plate(g,px2,py2-64,'O Trono do Criador','#ffd76a');}});
  for(const m of ZN.mobs)if(!m.dead&&m.x>cam.x-140&&m.x<cam.x+vw+140&&m.y>cam.y-140&&m.y<cam.y+vh+140)
    dl.push({y:m.y,f:()=>{
      if(p.target===m){g.strokeStyle='#ffd24d';g.lineWidth=2;g.setLineDash([6,4]);g.lineDashOffset=-time*24;
        g.beginPath();g.ellipse(m.x,m.y+3,m.r+10,(m.r+10)*.4,0,0,TAU);g.stroke();g.setLineDash([]);}
      drawMobE(g,m);
      const by=m.y-m.r-(m.boss?52:28);
      plate(g,m.x,by,m.n+'  Nv '+m.lvl,m.boss?'#ffb347':lvlCol(m));
      if(m.hp<m.maxhp)hpbar(g,m.x,by+6,m.boss?70:36,m.hp/m.maxhp);
      if(m.slowT>time){g.font='11px serif';g.textAlign='center';g.fillText('❄️',m.x+m.r+8,m.y-m.r);}}});
  for(const k in NET.players){const o=NET.players[k];
    if(o.z!==zoneKey())continue;
    if(o.x<cam.x-140||o.x>cam.x+vw+140)continue;
    dl.push({y:o.y,f:(oo=>()=>{
      const cls=CLASSES[oo.c]||CLASSES.guerreiro;
      const wvis=cls.ranged?'staff':'sword';
      const moving=Math.abs(oo.x-(oo.tx||oo.x))>2||Math.abs(oo.y-(oo.ty||oo.y))>2;
      drawHero(g,oo.x,oo.y,{cls:oo.c,wvis,orb:'#fff',walk:oo.wk||0,dir:(oo.tx||oo.x)>=oo.x?1:-1,atkT:0,moving,scale:1.45,
        skin:(oo.ap&&oo.ap.skin)||'#f0c8a0',hairCol:(oo.ap&&oo.ap.hairCol)||'#5a3a22',hairStyle:(oo.ap&&oo.ap.hairStyle)||0,
        col:(oo.ap&&oo.ap.col)||'#7d8291',col2:(oo.ap&&oo.ap.col2)||'#4c5258',armT:1});
      plate(g,oo.x,oo.y-54,(oo.n||'?')+' · Nv '+(oo.l||1),'#9fe8ff');
    })(o)});
  }
  dl.push({y:p.petY||0,f:()=>drawPetE(g)});
  dl.push({y:p.allyY||0,f:()=>drawAllyE(g)});
  if(!p.dead)dl.push({y:p.y,f:()=>{
    const c=CLASSES[p.cls];
    const moving=!!(keys['w']||keys['a']||keys['s']||keys['d']||keys['arrowup']||keys['arrowdown']||keys['arrowleft']||keys['arrowright']||p.moveTo);
    const orb=p.cls==='mago'?'#8fd8ff':p.cls==='druida'?'#8fe08a':p.cls==='necromante'?'#b88ae0':'#fff';
    const wvis=p.wpn?ITEMS[p.wpn].vk:(c.ranged?'staff':'fist');
    const armT=p.arm?ITEMS[p.arm].tier:1;
    drawHero(g,p.x,p.y,{cls:p.cls,wvis,orb,walk:p.walk,dir:p.dir,atkT:p.atkT,moving,scale:1.5,skin:p.ap.skin,hairCol:p.ap.hairCol,hairStyle:p.ap.hairStyle,col:p.ap.col,col2:p.ap.col2,armT});
    plate(g,p.x,p.y-58,p.name,'#ffd76a');}});
  dl.sort((a,b)=>a.y-b.y);
  for(const d of dl)d.f();
  for(const pr of projs){
    if(pr.kind==='foe'){g.fillStyle=pr.col||'#b88ae0';g.beginPath();g.arc(pr.x,pr.y,5,0,TAU);g.fill();
      g.fillStyle='rgba(255,255,255,.7)';g.beginPath();g.arc(pr.x,pr.y,2,0,TAU);g.fill();continue;}
    const a=Math.atan2(pr.vy,pr.vx);
    if(pr.kind==='arrow'||pr.kind==='nature'){g.save();g.translate(pr.x,pr.y);g.rotate(a);
      g.strokeStyle=pr.kind==='nature'?'#8fe08a':'#c8a06a';g.lineWidth=2.4;g.beginPath();g.moveTo(-9,0);g.lineTo(7,0);g.stroke();
      g.fillStyle=pr.kind==='nature'?'#c8f0c0':'#dfe6ee';g.beginPath();g.moveTo(10,0);g.lineTo(4,-3);g.lineTo(4,3);g.closePath();g.fill();g.restore();}
    else if(pr.kind==='rock'){g.fillStyle='#9a9284';g.beginPath();g.arc(pr.x,pr.y,5,0,TAU);g.fill();g.strokeStyle='#5c564c';g.stroke();}
    else{const cc={fire:'#ff9d4a',bolt:'#8fd8ff',shadow:'#b88ae0'}[pr.kind]||'#fff';
      g.fillStyle=cc;g.beginPath();g.arc(pr.x,pr.y,pr.kind==='fire'?7:5,0,TAU);g.fill();
      g.fillStyle='rgba(255,255,255,.85)';g.beginPath();g.arc(pr.x,pr.y,2.2,0,TAU);g.fill();}
  }
  drawAmbient(g);
  drawBirds(g);
  for(const e of fx){
    const prg=e.t/e.dur;
    if(e.k==='ring'){g.strokeStyle=e.col;g.globalAlpha=1-prg;g.lineWidth=3.5;g.beginPath();g.arc(e.x,e.y,e.maxR*(1-Math.pow(1-prg,2)),0,TAU);g.stroke();g.globalAlpha=1;}
  }
  for(const q of parts){g.globalAlpha=clamp(q.life*2.5,0,1);g.fillStyle=q.col;g.fillRect(q.x-q.size/2,q.y-q.size/2,q.size,q.size);}
  g.globalAlpha=1;
  for(const q of ftexts){g.globalAlpha=clamp(q.life*1.6,0,1);g.font='800 '+q.size+'px Cinzel,serif';g.textAlign='center';
    g.lineWidth=3;g.strokeStyle='rgba(0,0,0,.8)';g.strokeText(q.txt,q.x,q.y);g.fillStyle=q.col;g.fillText(q.txt,q.x,q.y);}
  g.globalAlpha=1;
  g.globalCompositeOperation='lighter';
  const lg=g.createRadialGradient(p.x-140,p.y-180,40,p.x,p.y,560);
  lg.addColorStop(0,'rgba(255,236,190,.06)');lg.addColorStop(1,'rgba(255,236,190,0)');
  g.fillStyle=lg;g.fillRect(cam.x,cam.y,vw,vh);
  g.globalCompositeOperation='source-over';
  if(ZN.isCave){
    g.fillStyle='rgba(6,6,14,.4)';g.fillRect(cam.x,cam.y,vw,vh);
    g.globalCompositeOperation='lighter';
    const gr=g.createRadialGradient(p.x,p.y-10,10,p.x,p.y-10,180);
    gr.addColorStop(0,'rgba(255,225,160,.18)');gr.addColorStop(1,'rgba(255,225,160,0)');
    g.fillStyle=gr;g.beginPath();g.arc(p.x,p.y-10,180,0,TAU);g.fill();
    g.globalCompositeOperation='source-over';
  }
  if(dark>0.02){g.fillStyle='rgba(8,14,44,'+(dark*.5)+')';g.fillRect(cam.x,cam.y,vw,vh);
    g.globalCompositeOperation='lighter';
    for(const cf of ZN.campfires){
      if(cf.x<cam.x-140||cf.x>cam.x+vw+140||cf.y<cam.y-140||cf.y>cam.y+vh+140)continue;
      const fl=.2+.06*Math.sin(time*7+cf.x);
      const gr=g.createRadialGradient(cf.x,cf.y-8,4,cf.x,cf.y-8,120);
      gr.addColorStop(0,'rgba(255,170,70,'+(fl+dark*.12)+')');gr.addColorStop(1,'rgba(255,170,70,0)');
      g.fillStyle=gr;g.beginPath();g.arc(cf.x,cf.y-8,120,0,TAU);g.fill();}
    g.globalCompositeOperation='source-over';}
  if(ZN.id>=10&&ZN.id<=13&&!ZN.isCave){g.fillStyle='rgba(255,255,255,'+(.04+.02*Math.sin(time))+')';g.fillRect(cam.x,cam.y,vw,vh);}
  if(ZN.id===14&&!ZN.isCave){g.fillStyle='rgba(40,10,10,.22)';g.fillRect(cam.x,cam.y,vw,vh);}
  g.restore();
  g.setTransform(1,0,0,1,0,0);
  if(cineT>0){const bh=Math.min(1,H*.09);
    g.fillStyle='#000';g.fillRect(0,0,W,bh);g.fillRect(0,H-bh,W,bh);}
  renderMinimapDots();
}

function buildHotbar(){
  const hb=$('hotbar');hb.innerHTML='';hbSlots=[];
  const mk=(key,icon,tip)=>{const d=document.createElement('div');d.className='slot';
    d.innerHTML='<span class="key">'+key+'</span>'+icon+'<div class="cd"></div><span class="lock" hidden>🔒</span>';
    d.onmousemove=e=>showTip(e,tip);d.onmouseleave=hideTip;hb.appendChild(d);hbSlots.push(d);return d;};
  const c=CLASSES[p.cls];
  mk('SPC',c.emoji,'<b>Ataque básico</b><br>Segue a arma equipada.<br><span class="tp">Espaço/clique</span>').onclick=doBasic;
  SKILLS[p.cls].forEach((s,i)=>mk(String(i+1),s.i,'<b>'+s.i+' '+s.n+'</b><br>'+s.d+'<br><span class="tp">'+s.mp+' mana · '+s.cd+'s · nível '+s.lvl+'</span>').onclick=()=>useSkill(i));
  const pot=mk('R','🧪','<b>Poção de Vida</b> (R)<br>Mana: T.');
  pot.insertAdjacentHTML('beforeend','<span class="qty"></span>');
  pot.onclick=()=>usePotion('php');
  hbBuilt=true;
}
function updateHUD(){
  if(!p)return;
  $('hpFill').style.width=(p.hp/p.maxhp*100)+'%';$('hpTxt').textContent=Math.ceil(p.hp)+' / '+p.maxhp;
  $('mpFill').style.width=(p.mp/p.maxmp*100)+'%';$('mpTxt').textContent=Math.floor(p.mp)+' / '+p.maxmp;
  $('xpFill').style.width=(p.xp/lvlNeed(p.lvl)*100)+'%';
  $('pLvl').textContent='Nv '+p.lvl;$('goldLbl').textContent='🪙 '+p.gold;
  $('resLbl').innerHTML='🪵 '+p.res.wood+' · 🪨 '+p.res.stone+' · 🔩 '+p.res.iron+' · 💠 '+p.res.mithril+' · 🔮 '+p.res.crystal;
  $('progBox').innerHTML='🎯 OBJETIVO<br>'+mainStage();
  if(!hbBuilt)buildHotbar();
  const cds=[clamp(p.atkCd/CLASSES[p.cls].cd,0,1)];
  for(let i=0;i<6;i++)cds.push(clamp(p.cds[i]/SKILLS[p.cls][i].cd,0,1));
  cds.push(0);
  hbSlots.forEach((s,i)=>{
    s.querySelector('.cd').style.height=(cds[i]*100)+'%';
    const lk=s.querySelector('.lock');
    if(i>=1&&i<=6){const sk=SKILLS[p.cls][i-1];const locked=p.lvl<sk.lvl;
      if(lk)lk.hidden=!locked;
      if(locked)s.querySelector('.cd').style.height='100%';
      else s.classList.toggle('nomana',p.mp<sk.mp);}
    if(i===7){const q=s.querySelector('.qty');if(q)q.textContent=countItem('php')||'';}
  });
  const bb=$('bossBar');
  let boss=null;
  if(p.target&&!p.target.dead&&p.target.boss)boss=p.target;
  if(!boss)for(const m of ZN.mobs)if(m.boss&&!m.dead&&m.state==='chase'){boss=m;break;}
  if(boss){bb.hidden=false;$('bossName').textContent='👑 '+boss.n+' · Nv '+boss.lvl;
    $('bossFill').style.width=(boss.hp/boss.maxhp*100)+'%';}
  else bb.hidden=true;
  const tg=$('tgt');
  if(p.target&&!p.target.dead){tg.hidden=false;$('tgtName').textContent=p.target.n;
    $('tgtLvl').textContent='Nv '+p.target.lvl+(p.target.boss?' · CHEFE':'');
    $('tgtFill').style.width=(p.target.hp/p.target.maxhp*100)+'%';}
  else tg.hidden=true;
  const bf=$('buffs');let bh='';
  if(p.buffs.atk&&p.buffs.atk>time)bh+='<div class="buff">📯<s>'+Math.ceil(p.buffs.atk-time)+'s</s></div>';
  if(p.buffs.crit&&p.buffs.crit>time)bh+='<div class="buff">👁️<s>'+Math.ceil(p.buffs.crit-time)+'s</s></div>';
  if(p.buffs.guard&&p.buffs.guard>time)bh+='<div class="buff">🛡️<s>'+Math.ceil(p.buffs.guard-time)+'s</s></div>';
  const a=activeAlly();if(a)bh+='<div class="buff" title="'+a.name+'">🤝</div>';
  if(bf.innerHTML!==bh)bf.innerHTML=bh;
}
function sysSay(h){const log=$('logLog');if(!log)return;const d=document.createElement('div');d.className='e sys';d.innerHTML=h;log.appendChild(d);while(log.children.length>40)log.removeChild(log.firstChild);log.scrollTop=log.scrollHeight;}
function logSay(h){const log=$('logLog');if(!log)return;const d=document.createElement('div');d.className='e log';d.innerHTML=h;log.appendChild(d);while(log.children.length>40)log.removeChild(log.firstChild);log.scrollTop=log.scrollHeight;}
function announce(txt){const a=document.createElement('div');a.className='ann';a.textContent=txt;
  const box=$('announce');if(box)box.appendChild(a);setTimeout(()=>{try{a.remove();}catch(e){}},5300);}

/* ================= LOBBY CORRIGIDO ================= */
const STORY=[
 'A câmera desce das estrelas.<br><br><b>AETERNA</b> — um sonho antigo.',
 'Dizem que este mundo já teve <b>mil criadores</b>.',
 'Cada um deles começou como você: na praia de Valdória.',
 'As <b>dez ilhas</b>. Um Trono que sussurra: <i>"suba"</i>.',
 'Quem destrona um deus, herda o Trono.',
 'A história se repete. Até alguém decidir <b>quebrá-la</b>.',
 'As ondas se afastam.<br><br><b style="color:#f2d38a">Luz, herói.</b>',
];
let storyIdx=0;
function renderStory(){
  $('storyTxt').innerHTML=STORY[storyIdx];
  $('storyTxt').style.animation='none';void $('storyTxt').offsetWidth;$('storyTxt').style.animation='ein .8s ease';
  let d='';for(let k=0;k<STORY.length;k++)d+=(k===storyIdx?'<b>●</b>':'○')+' ';
  $('storyDots').innerHTML=d;
  $('btnStoryNext').textContent=storyIdx===STORY.length-1?'Criar herói ▸':'Continuar ▸';
}

let lobbyMode='host';
let lobbyRoom='';

function readRoomIdFromInput(){
  const inp=$('roomIdIn');
  if(!inp)return '';
  const v=inp.value.toUpperCase().replace(/[^A-Z0-9\-]/g,'').slice(0,9);
  inp.value=v;
  return v;
}

function setLobbyMode(m){
  lobbyMode=m;
  document.querySelectorAll('.lobby-tab').forEach(t=>t.classList.remove('sel'));
  if(m==='host')$('ltHost').classList.add('sel');
  else if(m==='join')$('ltJoin').classList.add('sel');
  else $('ltSolo').classList.add('sel');
  
  const box=$('lobbyIdBox');
  $('lobbyStatus').textContent='';
  $('lobbyStatus').className='';
  lobbyRoom='';
  
  if(m==='host'){
    lobbyRoom=genRoomId();
    box.innerHTML='<div class="lobby-field"><label>ID DA SALA (clique para copiar)</label>'+
      '<input id="roomIdShow" readonly value="'+lobbyRoom+'" style="cursor:pointer;text-align:center;letter-spacing:.2em"></div>'+
      '<div class="lobby-info" style="margin-top:10px"><b>Como funciona:</b><br>'+
      '1. Copie o ID acima<br>'+
      '2. Envie para seu amigo<br>'+
      '3. Ele abre o jogo e escolhe "ENTRAR EM SALA"<br>'+
      '4. Ele cola o ID e clica Continuar<br>'+
      '5. Vocês se veem no mesmo mundo!</div>';
    $('roomIdShow').onclick=()=>{
      const inp=$('roomIdShow');
      inp.select();
      try{
        if(navigator.clipboard&&navigator.clipboard.writeText){
          navigator.clipboard.writeText(lobbyRoom).then(()=>{
            $('lobbyStatus').textContent='✔ ID copiado! Mande para seu amigo.';
            $('lobbyStatus').className='ok';
          }).catch(()=>{
            try{document.execCommand('copy');$('lobbyStatus').textContent='✔ copiado!';$('lobbyStatus').className='ok';}catch(e2){}
          });
        }else{
          document.execCommand('copy');
          $('lobbyStatus').textContent='✔ copiado!';
          $('lobbyStatus').className='ok';
        }
      }catch(e){
        $('lobbyStatus').textContent='ID: '+lobbyRoom;
        $('lobbyStatus').className='';
      }
    };
    $('lobbyStatus').textContent='🟢 Sala pronta: '+lobbyRoom;
    $('lobbyStatus').className='ok';
  }else if(m==='join'){
    box.innerHTML='<div class="lobby-field"><label>ID DA SALA DO AMIGO</label>'+
      '<input id="roomIdIn" placeholder="ABCD-1234" maxlength="9" autocomplete="off" spellcheck="false"></div>'+
      '<div class="lobby-info" style="margin-top:10px">Cole aqui o ID que seu amigo te mandou.<br>'+
      '<small style="color:#a89877">Pressione Enter para continuar.</small></div>';
    setTimeout(()=>{
      const inp=$('roomIdIn');
      if(inp){
        inp.focus();
        inp.addEventListener('input',()=>{
          const v=readRoomIdFromInput();
          lobbyRoom=v;
          if(v.length>=5){
            $('lobbyStatus').textContent='✔ ID válido: '+v;
            $('lobbyStatus').className='ok';
          }else if(v.length>0){
            $('lobbyStatus').textContent='⏳ digitando... ('+v.length+'/5 caracteres)';
            $('lobbyStatus').className='';
          }else{
            $('lobbyStatus').textContent='';
            $('lobbyStatus').className='';
          }
        });
        inp.addEventListener('keydown',e=>{
          if(e.key==='Enter'){
            e.preventDefault();
            $('btnLobbyGo').click();
          }
        });
      }
    },50);
  }else{
    // modo solo
    box.innerHTML='<div class="lobby-info"><b>Modo Solo:</b> você joga sozinho.<br>'+
      'Seu progresso é salvo no navegador.<br>'+
      'Sem multiplayer, sem complicação.</div>';
    $('lobbyStatus').textContent='🟡 Você entrará em modo solo.';
    $('lobbyStatus').className='';
  }
}

$('ltHost').onclick=()=>setLobbyMode('host');
$('ltJoin').onclick=()=>setLobbyMode('join');
$('ltSolo').onclick=()=>setLobbyMode('solo');

$('btnLobbyGo').onclick=()=>{
  // Lê direto do input no momento do clique (corrige bug do closure)
  if(lobbyMode==='join'){
    const v=readRoomIdFromInput();
    lobbyRoom=v;
    if(!v||v.length<5){
      $('lobbyStatus').textContent='❌ ID inválido! Precisa de pelo menos 5 caracteres.';
      $('lobbyStatus').className='err';
      const inp=$('roomIdIn');if(inp)inp.focus();
      return;
    }
  }
  
  // Avança para a história
  $('lobbyScr').hidden=true;
  $('introScr').hidden=false;
  state='intro';
  storyIdx=0;
  renderStory();
  
  // Inicia rede conforme modo escolhido
  netSetup(lobbyMode,lobbyRoom);
};

$('btnIntroBack').onclick=()=>{
  $('introScr').hidden=true;
  $('lobbyScr').hidden=false;
  state='lobby';
  // Limpa conexão se existir
  if(NET.peer){
    try{NET.peer.destroy();}catch(e){}
    NET.peer=null;NET.conn=null;NET.on=false;
  }
};

function closeIntro(){$('introScr').hidden=true;$('createScr').hidden=false;state='create';}
$('btnStoryNext').onclick=()=>{initAudio();sfx.ui();if(storyIdx<STORY.length-1){storyIdx++;renderStory();}else closeIntro();};
$('btnStorySkip').onclick=()=>{initAudio();closeIntro();};

let selCls='guerreiro',embers=[];
const apSel={skin:'#f0c8a0',hairCol:'#5a3a22',hairStyle:0,col:'#c8503c',col2:'#8a2f22'};
const SKINS=['#f6d7b8','#f0c8a0','#d9a066','#b07a4a','#8a5a34','#5c3a22'];
const HAIRCOLS=['#2e2419','#5a3a22','#8a6a3a','#c8a04a','#d8d8e0','#c8484a','#4a7ac8','#6a4ac8','#4ac87a'];
const HAIRSTYLES=['Curto','Longo','Coque','Moicano'];
const COLS=['#c8503c','#6a52c8','#4a7a3a','#3a6ea8','#c8a03a','#7d8291','#a84a7a','#3f3f46'];
const COLS2=['#8a2f22','#41307e','#2e5224','#274b78','#7a5a1a','#4c5258','#722c4c','#1f1f24'];
function swRow(el,opts,cur,cb){
  el.innerHTML='';
  opts.forEach(o=>{const b=document.createElement('button');b.className='sw'+(o===cur?' sel':'');b.style.background=o;
    b.onclick=()=>{cb(o);el.querySelectorAll('.sw').forEach(x=>x.classList.remove('sel'));b.classList.add('sel');initAudio();sfx.ui();};
    el.appendChild(b);});
}
function buildCust(){
  swRow($('skinRow'),SKINS,apSel.skin,v=>apSel.skin=v);
  swRow($('hairColRow'),HAIRCOLS,apSel.hairCol,v=>apSel.hairCol=v);
  swRow($('colRow'),COLS,apSel.col,v=>apSel.col=v);
  swRow($('col2Row'),COLS2,apSel.col2,v=>apSel.col2=v);
  const hr=$('hairRow');hr.innerHTML='';
  HAIRSTYLES.forEach((h,i)=>{const b=document.createElement('button');b.className='hbtn'+(i===apSel.hairStyle?' sel':'');b.textContent=h;
    b.onclick=()=>{apSel.hairStyle=i;hr.querySelectorAll('.hbtn').forEach(x=>x.classList.remove('sel'));b.classList.add('sel');initAudio();sfx.ui();};
    hr.appendChild(b);});
}
function buildCreate(){
  $('cycleLbl').textContent='Ciclo '+meta.cycle+(meta.cycle>1?' · chefe final: '+meta.prevName:'');
  const list=$('classList');
  list.innerHTML='';
  for(const key in CLASSES){const c=CLASSES[key];
    const b=document.createElement('button');b.className='cls-row'+(key===selCls?' sel':'');
    b.innerHTML='<div class="cls-ico">'+c.emoji+'</div><div><b>'+c.label+'</b><span>'+c.tag+'</span></div>';
    b.onclick=()=>{selCls=key;initAudio();sfx.ui();
      document.querySelectorAll('.cls-row').forEach(r=>r.classList.remove('sel'));b.classList.add('sel');updPreview();};
    list.appendChild(b);}
  $('pvStats').innerHTML=['VIDA','ATAQUE','DEFESA','ALCANCE'].map(k=>'<div class="pv-stat"><em>'+k+'</em><div class="pv-bar"><i id="pv-'+k+'"></i></div></div>').join('');
  buildCust();updPreview();
  const hs=loadJSON(SKEY);
  if(hs&&hs.v===7){const bc=$('btnContinue');bc.hidden=false;bc.textContent='↻ Continuar como '+hs.name+' — Nv '+hs.lvl;
    bc.onclick=()=>{initAudio();continueGame(hs);};}
  $('dice').onclick=()=>{initAudio();sfx.ui();$('heroName').value=pick(['Valthor','Kael','Bruna','Aria','Thorgar','Lyra','Fenrik','Isolde','Draven','Mirael','Corvo','Aldric','Selene','Boran','Nyx','Oren'])+(chance(.5)?RI(2,99):'');};
  $('btnPlay').onclick=()=>{initAudio();
    const old=loadJSON(PKEY);
    if(old&&(old.boat||Object.keys(old.kills||{}).length)){
      if(!confirm('Iniciar NOVA jornada reinicia o progresso do mundo.\n\nCiclos mantidos. Continuar?'))return;
      try{localStorage.removeItem(PKEY);localStorage.removeItem(SKEY);}catch(e){}
      prog={boat:false,kills:{},godSlain:false};
    }
    const name=($('heroName').value.trim()||'Aventureiro').slice(0,14);
    startGame(name,selCls,Object.assign({},apSel));};
}
function updPreview(){
  const c=CLASSES[selCls];
  $('pvDesc').textContent=c.desc;
  for(const k in c.stats){const el=$('pv-'+k);if(el)el.style.width=c.stats[k]+'%';}
}
const pv=$('pvCanvas'),pgc=pv.getContext('2d'),bg=$('bgfx'),bgg=bg.getContext('2d');
function renderCreate(dt){
  bg.width=bg.clientWidth;bg.height=bg.clientHeight;
  bgg.fillStyle='#0b100c';bgg.fillRect(0,0,bg.width,bg.height);
  if(embers.length<70&&chance(.4))embers.push({x:R(0,bg.width),y:bg.height+10,vx:R(-8,8),vy:R(-46,-16),life:R(4,9),size:R(1,3.2),col:chance(.7)?'#e8a94e':'#8fd8a0'});
  for(let i=embers.length-1;i>=0;i--){const e=embers[i];e.x+=e.vx*dt;e.y+=e.vy*dt;e.life-=dt;
    if(e.life<=0||e.y<-10){embers.splice(i,1);continue;}
    bgg.globalAlpha=clamp(e.life/3,0,.9);bgg.fillStyle=e.col;bgg.beginPath();bgg.arc(e.x,e.y,e.size,0,TAU);bgg.fill();}
  bgg.globalAlpha=1;
  pgc.clearRect(0,0,320,280);
  pgc.fillStyle='rgba(0,0,0,.35)';pgc.beginPath();pgc.ellipse(160,238,60,14,0,0,TAU);pgc.fill();
  pgc.fillStyle='#3a3f36';pgc.beginPath();pgc.ellipse(160,232,54,12,0,0,TAU);pgc.fill();
  pgc.fillStyle='#4c5245';pgc.beginPath();pgc.ellipse(160,229,54,12,0,0,TAU);pgc.fill();
  const c=CLASSES[selCls];
  const swing=(Math.sin(time*.9)>.92)?((Math.sin(time*.9)-.92)/.08)*.26:0;
  const orb=selCls==='mago'?'#8fd8ff':selCls==='druida'?'#8fe08a':selCls==='necromante'?'#b88ae0':'#fff';
  const wvis=c.ranged?'staff':'sword';
  drawHero(pgc,160,224,{cls:selCls,wvis,orb,walk:0,dir:1,atkT:swing,moving:false,scale:5,skin:apSel.skin,hairCol:apSel.hairCol,hairStyle:apSel.hairStyle,col:apSel.col,col2:apSel.col2,armT:1});
}

function startGame(name,cls,ap){
  newPlayer(name,cls,ap);
  prog=loadJSON(PKEY)||{boat:false,kills:{},godSlain:false};
  $('createScr').hidden=true;$('gameScr').hidden=false;
  $('pPortrait').textContent=CLASSES[cls].emoji;$('pName').textContent=name;
  state='play';
  enterZone(0);
  buildHotbar();refreshInv();
  if(NET.on)netSend({t:'join',name:p.name,cls:p.cls,ap:p.ap,lvl:p.lvl,z:zoneKey()});
  sysSay('⚔ Bem-vindo, <b>'+name+'</b>.');
  if(cls==='mendigo')sysSay('🥣 Mendigo: +30% XP/ouro.');
  logSay('🎯 Ancelmo (E) · Quebre árvores na mão · Barco no Estaleiro');
  if(meta.cycle>1)sysSay('♻ Ciclo '+meta.cycle+': <b>'+meta.prevName+'</b> te espera.');
  announce(meta.cycle>1?'♻ Ciclo '+meta.cycle:'⚔ A jornada começa');
  saveHero();
}
function continueGame(hs){
  prog=loadJSON(PKEY)||{boat:false,kills:{},godSlain:false};
  newPlayer(hs.name,hs.cls,hs.ap);
  p.lvl=hs.lvl;p.xp=hs.xp;p.gold=hs.gold;p.inv=hs.inv||[{id:'php',q:3}];p.wpn=hs.wpn===undefined?null:hs.wpn;p.arm=hs.arm;
  p.tp=hs.tp||0;p.tree=hs.tree||{};p.pets=hs.pets||[];p.petIdx=hs.petIdx===undefined?-1:hs.petIdx;
  p.allies=hs.allies||[];p.allyIdx=hs.allyIdx===undefined?-1:hs.allyIdx;p.rom=hs.rom||{};p.qs=hs.qs||{};
  p.res=hs.res||{wood:0,stone:0,iron:0,mithril:0,crystal:0};
  calcStats();p.hp=p.maxhp;p.mp=p.maxmp;
  if(activePet()){p.petX=p.x;p.petY=p.y+20;}
  if(activeAlly()){p.allyX=p.x;p.allyY=p.y+20;}
  $('createScr').hidden=true;$('gameScr').hidden=false;
  $('pPortrait').textContent=CLASSES[hs.cls].emoji;$('pName').textContent=hs.name;
  state='play';
  enterZone(hs.island||0);
  buildHotbar();refreshInv();
  if(NET.on)netSend({t:'join',name:p.name,cls:p.cls,ap:p.ap,lvl:p.lvl,z:zoneKey()});
  sysSay('↻ Retomado: <b>'+hs.name+'</b>');
}

addEventListener('keydown',e=>{
  if(state!=='play')return;
  if(document.activeElement===$('chatIn'))return;
  const k=e.key.toLowerCase();
  if(k==='enter'){$('chatIn').focus();e.preventDefault();return;}
  if([' ','arrowup','arrowdown','arrowleft','arrowright'].indexOf(k)>=0)e.preventDefault();
  keys[k]=true;
  if(p.dead)return;
  if(k===' ')doBasic();
  if(k>='1'&&k<='6')useSkill(parseInt(k)-1);
  if(k==='r')usePotion('php');
  if(k==='t')usePotion('pmp');
  if(k==='e'){hideWins();interact();}
  if(k==='i'){refreshInv();toggleWin('winInv');}
  if(k==='q'){renderQuestLog();toggleWin('winQuest');}
  if(k==='c'){refreshInv();setInvTab('forge');showWin('winInv');}
  if(k==='h')toggleWin('winHelp');
  if(k==='escape'){hideWins();p.target=null;}
});
addEventListener('keyup',e=>{keys[e.key.toLowerCase()]=false;});
$('chatIn').addEventListener('keydown',e=>{
  e.stopPropagation();
  if(e.key==='Enter'){const v=$('chatIn').value.trim();
    if(v){chatMe(v);netSend({t:'chat',txt:v});}
    $('chatIn').value='';$('chatIn').blur();}
  if(e.key==='Escape')$('chatIn').blur();
});
world.addEventListener('mousedown',e=>{
  if(state!=='play'||p.dead)return;
  initAudio();
  const wx=cam.x+e.clientX/ZOOM,wy=cam.y+e.clientY/ZOOM;
  let best=null,bd=30;
  for(const m of ZN.mobs)if(!m.dead){const d=dist(wx,wy,m.x,m.y-m.r*.4);if(d<bd+m.r*.5){bd=d;best=m;}}
  if(best){p.target=best;doBasic();return;}
  if(tryHarvest(wx,wy))return;
  p.target=null;p.moveTo={x:wx,y:clamp(wy,16,ZN.S*T-16)};
});
$('btnRespawn').onclick=()=>{sfx.ui();respawnPlayer();};
$('btnInv').onclick=()=>{sfx.ui();refreshInv();toggleWin('winInv');};
$('btnQuest').onclick=()=>{sfx.ui();renderQuestLog();toggleWin('winQuest');};
$('btnHelp').onclick=()=>{sfx.ui();toggleWin('winHelp');};
$('btnSnd').onclick=function(){muted=!muted;this.textContent=muted?'🔇':'🔊';if(!muted){initAudio();sfx.ui();}};
$('btnReset').onclick=()=>{if(confirm('Apagar TODO o progresso (incluindo ciclos)?')){
  try{localStorage.removeItem(SKEY);localStorage.removeItem(PKEY);localStorage.removeItem(MKEY);}catch(e){}
  location.reload();}};
document.querySelectorAll('.win-x').forEach(b=>b.onclick=hideWins);
function setInvTab(tab){
  $('invTabEq').hidden=tab!=='eq';$('invTabTree').hidden=tab!=='tree';$('invTabPets').hidden=tab!=='pets';$('invTabAlly').hidden=tab!=='ally';$('invTabForge').hidden=tab!=='forge';
  $('tabEq').classList.toggle('sel',tab==='eq');$('tabTree').classList.toggle('sel',tab==='tree');$('tabPets').classList.toggle('sel',tab==='pets');$('tabAlly').classList.toggle('sel',tab==='ally');$('tabForge').classList.toggle('sel',tab==='forge');
  if(tab==='tree')renderTree();if(tab==='pets')renderPets();if(tab==='ally')renderAllies();if(tab==='forge')renderForge();
}
$('tabEq').onclick=()=>setInvTab('eq');
$('tabTree').onclick=()=>setInvTab('tree');
$('tabPets').onclick=()=>setInvTab('pets');
$('tabAlly').onclick=()=>setInvTab('ally');
$('tabForge').onclick=()=>setInvTab('forge');
$('tabBuy').onclick=()=>renderShop('buy');
$('tabSell').onclick=()=>renderShop('sell');
$('btnUseHp').onclick=()=>usePotion('php');
$('btnUseMp').onclick=()=>usePotion('pmp');
function showWin(id){hideWins();$(id).hidden=false;}
function hideWins(){['winInv','winShop','dlg','winTravel','winHelp','winQuest'].forEach(id=>{const el=$(id);if(el)el.hidden=true;});hideTip();}
function toggleWin(id){const w=$(id);if(!w)return;if(w.hidden){if(id==='winInv')refreshInv();showWin(id);}else hideWins();}
addEventListener('beforeunload',()=>{saveHero();saveProg();});

function resize(){world.width=innerWidth;world.height=innerHeight;}
addEventListener('resize',resize);resize();
meta=loadJSON(MKEY)||{cycle:1,prevName:'',prevCls:'guerreiro',prevAp:null};
buildCreate();renderStory();setLobbyMode('host');
let last=performance.now();
function frame(now){
  const dt=Math.min(.05,(now-last)/1000);last=now;time+=dt;
  if(state==='create')renderCreate(dt);
  else if(state==='play'&&ZN&&p){update(dt);renderGame();updateHUD();}
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>
</body>
</html>

Game Source: AETERNA — O Ciclo do Criador

Creator: SonicLion32

Libraries: none

Complexity: complex (2885 lines, 183.4 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: aeterna-o-ciclo-do-criador-soniclion32" to link back to the original. Then publish at arcadelab.ai/publish.