🎮ArcadeLab

זירת החיסולים

by MysticFlare49
1165 lines44.3 KB
▶ Play
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>זירת החיסולים</title>
<style>
:root{
  --bg:#10131f; --panel:#1b2032; --panel2:#242b47; --line:#323a5e;
  --gold:#ffc93c; --gold-d:#c69413;
  --green:#35d07f; --green-d:#1e8f57;
  --red:#ff5b5b; --cyan:#3ec7f0; --violet:#a78bfa;
  --text:#f4f6ff; --dim:#9aa3c7;
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;
  user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;}
html,body{height:100%;overflow:hidden;overscroll-behavior:none;}
body{position:fixed;inset:0;width:100%;background:var(--bg);color:var(--text);
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Heebo",Arial,sans-serif;
  touch-action:none;}
button{font-family:inherit;cursor:pointer;color:var(--text);}
input{user-select:text;-webkit-user-select:text;}

/* ---------- screens ---------- */
.screen{position:absolute;inset:0;display:none;flex-direction:column;}
.screen.active{display:flex;}
.hidden{display:none !important;}

/* ---------- home ---------- */
#home{padding:calc(14px + env(safe-area-inset-top)) 18px calc(20px + env(safe-area-inset-bottom));align-items:center;}
.top-row{width:100%;display:flex;justify-content:center;position:relative;}
#soundBtn{position:absolute;left:0;top:0;}
.icon-btn{width:44px;height:44px;border-radius:14px;background:var(--panel);
  border:2px solid var(--line);font-size:1.2rem;display:flex;align-items:center;justify-content:center;}
#trophyPill{display:flex;align-items:center;gap:10px;background:var(--panel);
  border:2px solid var(--line);border-radius:999px;padding:10px 20px;
  font-size:1.05rem;font-weight:800;}
#trophyPill:active{transform:scale(.97);}
.tp-cup{font-size:1.2rem;}
#trophyCount{color:var(--gold);}
.tp-div{width:2px;height:18px;background:var(--line);border-radius:2px;}
.tp-name{color:var(--dim);max-width:34vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

.home-mid{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;text-align:center;width:100%;}
.title-emojis{font-size:2.3rem;display:flex;gap:16px;}
.title-emojis span{display:inline-block;animation:bob 2.4s ease-in-out infinite;}
.title-emojis span:nth-child(2){animation-delay:.35s;}
.title-emojis span:nth-child(3){animation-delay:.7s;}
@keyframes bob{50%{transform:translateY(-9px);}}
.game-title{font-size:clamp(2.7rem,14vw,4.4rem);font-weight:900;line-height:1.02;
  letter-spacing:-1px;color:var(--gold);
  text-shadow:0 5px 0 rgba(0,0,0,.55),0 14px 40px rgba(255,201,60,.16);}
.subtitle{color:var(--dim);font-weight:700;}
.selected-info{background:var(--panel);border:2px solid var(--line);padding:9px 18px;border-radius:14px;font-weight:700;}
.selected-info b{color:var(--gold);}
.next-unlock{color:var(--gold);font-size:.92rem;font-weight:800;min-height:1.2em;}

.home-bottom{width:100%;display:flex;gap:12px;}
.btn{border:none;border-radius:18px;font-weight:900;transition:transform .06s;}
.btn:active{transform:translateY(4px);border-bottom-width:2px !important;}
.btn-green{background:var(--green);border-bottom:6px solid var(--green-d);color:#06281a;
  font-size:1.5rem;padding:18px 10px;flex:2;}
.btn-panel{background:var(--panel2);border-bottom:6px solid #10141f;color:var(--text);
  font-size:1.1rem;padding:18px 10px;flex:1;}

/* ---------- characters ---------- */
#chars{padding:calc(12px + env(safe-area-inset-top)) 16px 0;}
.chars-head{display:flex;align-items:center;gap:12px;padding-bottom:12px;}
.chars-head h2{flex:1;font-size:1.5rem;font-weight:900;}
.chars-trophy{font-weight:800;color:var(--gold);background:var(--panel);
  border:2px solid var(--line);border-radius:999px;padding:8px 14px;}
.char-list{flex:1;overflow-y:auto;touch-action:pan-y;display:flex;flex-direction:column;gap:12px;
  padding-bottom:calc(20px + env(safe-area-inset-bottom));}
.char-card{display:flex;gap:14px;background:var(--panel);border:2.5px solid var(--line);
  border-radius:20px;padding:14px;text-align:right;width:100%;align-items:center;}
.char-card.selected{border-color:var(--gold);box-shadow:0 0 0 3px rgba(255,201,60,.16);}
.char-card.locked{opacity:.8;}
.char-card.locked .cc-emoji{filter:grayscale(.7);}
.cc-emoji{font-size:2.8rem;width:76px;height:76px;display:flex;align-items:center;justify-content:center;
  background:#141a2e;border-radius:18px;border:2.5px solid var(--cc,#555);flex:0 0 auto;}
.cc-body{flex:1;min-width:0;}
.cc-name{font-size:1.25rem;font-weight:900;color:var(--cc,#fff);display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.cc-badge{font-size:.68rem;background:var(--gold);color:#221a00;padding:3px 9px;border-radius:99px;font-weight:900;}
.cc-line{color:var(--dim);font-size:.85rem;margin-top:3px;font-weight:600;}
.cc-lock{margin-top:9px;color:var(--gold);font-weight:800;font-size:.9rem;}
.cc-prog{height:9px;background:#000;border-radius:5px;margin-top:6px;overflow:hidden;}
.cc-prog div{height:100%;background:var(--gold);border-radius:5px;}
.cc-progtxt{font-size:.75rem;color:var(--dim);margin-top:4px;font-weight:700;}
.shake{animation:shk .35s;}
@keyframes shk{25%{transform:translateX(-6px);}50%{transform:translateX(6px);}75%{transform:translateX(-4px);}}

/* ---------- game ---------- */
#game{background:#141827;}
#cv{position:absolute;inset:0;}
#hud{position:absolute;top:calc(8px + env(safe-area-inset-top));left:0;right:0;
  display:flex;justify-content:center;gap:7px;pointer-events:none;z-index:5;padding:0 54px;}
.chip{background:rgba(18,22,40,.88);border:1.5px solid var(--line);border-radius:12px;
  padding:5px 9px;display:flex;align-items:center;gap:6px;}
.chip.me{border-color:var(--gold);}
.chip.dead{opacity:.45;filter:grayscale(1);}
.chip-e{font-size:1rem;}
.chip-n{font-size:.72rem;font-weight:800;max-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.chip-bar{width:42px;height:6px;background:#000;border-radius:3px;overflow:hidden;}
.chip-bar div{height:100%;width:100%;background:var(--green);border-radius:3px;}
#exitBtn{position:absolute;top:calc(8px + env(safe-area-inset-top));left:10px;
  width:38px;height:38px;border-radius:12px;background:rgba(18,22,40,.88);
  border:1.5px solid var(--line);color:var(--dim);font-size:1rem;font-weight:900;z-index:6;}

#joyZone{position:absolute;left:0;bottom:0;width:56%;height:72%;touch-action:none;z-index:4;}
#joyBase{position:absolute;width:118px;height:118px;border-radius:50%;
  background:rgba(255,255,255,.05);border:2px solid rgba(255,255,255,.13);
  left:26px;bottom:calc(40px + env(safe-area-inset-bottom));pointer-events:none;}
#joyBase.on{background:rgba(255,255,255,.09);}
#joyKnob{position:absolute;width:56px;height:56px;border-radius:50%;
  background:rgba(255,255,255,.26);border:2px solid rgba(255,255,255,.4);
  left:50%;top:50%;transform:translate(-50%,-50%);}

#fireBtn{position:absolute;right:20px;bottom:calc(32px + env(safe-area-inset-bottom));
  width:94px;height:94px;border-radius:50%;font-size:2.4rem;z-index:4;
  background:linear-gradient(180deg,#ff6b5e,#e04338);border:none;
  border-bottom:6px solid #a52c24;box-shadow:0 8px 20px rgba(0,0,0,.45);touch-action:none;}
#fireBtn.press{transform:scale(.92);border-bottom-width:3px;}
#powerBtn{position:absolute;right:34px;bottom:calc(148px + env(safe-area-inset-bottom));
  width:74px;height:74px;border-radius:50%;padding:6px;border:none;z-index:4;touch-action:none;
  background:conic-gradient(var(--gold) var(--pct,0deg), #262c49 var(--pct,0deg));}
#powerBtn .pin{width:100%;height:100%;border-radius:50%;background:#141a2e;
  display:flex;align-items:center;justify-content:center;font-size:1.7rem;opacity:.5;}
#powerBtn.ready .pin{opacity:1;}
#powerBtn.ready{animation:ppulse 1s infinite;box-shadow:0 0 24px rgba(255,201,60,.6);}
@keyframes ppulse{50%{transform:scale(1.09);}}

#countWrap{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:14px;pointer-events:none;z-index:8;}
#cdNum{font-size:min(28vw,130px);font-weight:900;color:var(--gold);
  text-shadow:0 6px 0 rgba(0,0,0,.5);line-height:1;}
#cdNum.pop{animation:cdpop .5s;}
@keyframes cdpop{0%{transform:scale(1.7);opacity:.2;}100%{transform:scale(1);opacity:1;}}
#cdHint{background:rgba(18,22,40,.88);border:1.5px solid var(--line);border-radius:14px;
  padding:9px 16px;font-weight:800;color:var(--dim);font-size:.95rem;}

/* ---------- overlay / modal ---------- */
.overlay{position:absolute;inset:0;background:rgba(8,10,20,.74);display:flex;
  align-items:center;justify-content:center;z-index:20;padding:20px;}
.overlay-card{background:var(--panel);border:2.5px solid var(--line);border-radius:24px;
  padding:26px 22px;width:min(370px,92vw);display:flex;flex-direction:column;gap:13px;
  text-align:center;position:relative;z-index:2;}
.overlay-card .btn{padding:15px 10px;font-size:1.2rem;}
.ov-emoji{font-size:4.2rem;line-height:1;animation:cdpop .5s;}
#ovTitle{font-size:1.65rem;font-weight:900;color:var(--gold);}
#ovSub{color:var(--text);font-weight:600;line-height:1.55;}
.ov-total{color:var(--dim);font-size:.9rem;}
.ov-unlock{background:rgba(255,201,60,.12);border:1.5px solid var(--gold);
  border-radius:14px;padding:11px;color:var(--gold);font-weight:800;line-height:1.5;}
#confetti{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:1;}
#confetti span{position:absolute;top:-36px;animation:fall linear forwards;}
@keyframes fall{to{transform:translateY(112vh) rotate(560deg);opacity:.75;}}
#nameInput{font-size:1.2rem;font-weight:800;padding:14px;border-radius:14px;
  border:2px solid var(--line);background:#12172a;color:var(--text);text-align:center;outline:none;width:100%;}
#nameInput:focus{border-color:var(--gold);}
</style>
</head>
<body>

<!-- ============ HOME ============ -->
<div id="home" class="screen active">
  <div class="top-row">
    <button id="soundBtn" class="icon-btn">🔊</button>
    <button id="trophyPill">
      <span class="tp-cup">🏆</span><span id="trophyCount">0</span>
      <span class="tp-div"></span>
      <span id="playerNameLabel" class="tp-name">בחרו שם ✏️</span>
    </button>
  </div>
  <div class="home-mid">
    <div class="title-emojis"><span>🥷</span><span>🤖</span><span>👺</span></div>
    <h1 class="game-title">זירת<br>החיסולים</h1>
    <p class="subtitle">3 לוחמים. זירה אחת. רק אחד שורד.</p>
    <div id="selectedInfo" class="selected-info"></div>
    <div id="nextUnlock" class="next-unlock"></div>
  </div>
  <div class="home-bottom">
    <button id="startBtn" class="btn btn-green">⚔️ התחלה</button>
    <button id="charsBtn" class="btn btn-panel">🎭 דמויות</button>
  </div>
</div>

<!-- ============ CHARACTERS ============ -->
<div id="chars" class="screen">
  <div class="chars-head">
    <button id="backBtn" class="icon-btn">→</button>
    <h2>הדמויות</h2>
    <div class="chars-trophy">🏆 <span id="trophyCount2">0</span></div>
  </div>
  <div id="charList" class="char-list"></div>
</div>

<!-- ============ GAME ============ -->
<div id="game" class="screen">
  <canvas id="cv"></canvas>
  <div id="hud"></div>
  <button id="exitBtn">✕</button>
  <div id="joyZone">
    <div id="joyBase"><div id="joyKnob"></div></div>
  </div>
  <button id="powerBtn"><span class="pin"><span id="powerIcon">💥</span></span></button>
  <button id="fireBtn">👊</button>
  <div id="countWrap" class="hidden">
    <div id="cdNum"></div>
    <div id="cdHint"></div>
  </div>
  <div id="overlay" class="overlay hidden">
    <div id="confetti"></div>
    <div class="overlay-card">
      <div id="ovEmoji" class="ov-emoji">🏆</div>
      <h2 id="ovTitle"></h2>
      <div id="ovSub"></div>
      <div id="ovUnlock" class="ov-unlock hidden"></div>
      <button id="againBtn" class="btn btn-green">⚔️ עוד קרב!</button>
      <button id="homeBtn" class="btn btn-panel">🏠 דף הבית</button>
    </div>
  </div>
</div>

<!-- ============ NAME MODAL ============ -->
<div id="nameModal" class="overlay hidden">
  <div class="overlay-card">
    <h2 style="font-weight:900;">✏️ מה השם שלכם?</h2>
    <input id="nameInput" maxlength="14" placeholder="השם שלכם...">
    <button id="saveNameBtn" class="btn btn-green">שמירה ✓</button>
    <button id="cancelNameBtn" class="btn btn-panel">ביטול</button>
  </div>
</div>

<script>
'use strict';
/* ================= helpers ================= */
const $=id=>document.getElementById(id);
const clamp=(v,a,b)=>v<a?a:(v>b?b:v);
const rand=(a,b)=>a+Math.random()*(b-a);
const fmt=n=>n.toLocaleString();
const distF=(a,b)=>Math.hypot(b.x-a.x,b.y-a.y);
const angTo=(a,b)=>Math.atan2(b.y-a.y,b.x-a.x);
function shuffle(arr){for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[arr[i],arr[j]]=[arr[j],arr[i]];}return arr;}
function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}

/* ================= save ================= */
const SAVE_KEY='zirat-hahisulim-save';
const save={name:'',trophies:0,selected:'thief',sound:true};
async function loadSave(){
  if(!window.storage)return;
  try{
    const r=await window.storage.get(SAVE_KEY);
    if(r&&r.value){const d=JSON.parse(r.value);
      if(typeof d.name==='string')save.name=d.name;
      if(typeof d.trophies==='number')save.trophies=d.trophies;
      if(typeof d.selected==='string')save.selected=d.selected;
      if(typeof d.sound==='boolean')save.sound=d.sound;
    }
  }catch(e){}
}
let saveTimer=null;
function persist(){
  if(!window.storage)return;
  clearTimeout(saveTimer);
  saveTimer=setTimeout(async()=>{
    try{await window.storage.set(SAVE_KEY,JSON.stringify(save));}catch(e){}
  },250);
}

/* ================= characters ================= */
const CHARS=[
 {id:'thief',emoji:'🥷',name:'הגנב',cost:0,color:'#a78bfa',type:'melee',
  dmg:200,rate:3.5,spd:1,atkEmoji:'👊',
  power:{emoji:'🦵',name:'בעיטת־על',charge:10},
  lines:['👊 בוקס מקרוב — 200 נזק','🦵 מכת כוח: בעיטה — 750 נזק','⏱️ נטענת ב־10 שניות']},
 {id:'robot',emoji:'🤖',name:'הרובוט',cost:1000,color:'#3ec7f0',type:'shoot',
  dmg:100,rate:7,spd:1,atkEmoji:'🔩',projEmoji:'🔩',
  power:{emoji:'⚡',name:'לייזר־ענק',charge:14},
  lines:['🔩 יורה ברגים — 100 נזק','⚡ מכת כוח: לייזר על כל המגרש — 500 לכל היריבים','⏱️ נטענת ב־14 שניות']},
 {id:'samurai',emoji:'👺',name:'הסמוראי',cost:2000,color:'#ff5b5b',type:'shoot',
  dmg:100,rate:7,spd:1.3,atkEmoji:'🔪',projEmoji:'🔪',
  power:{emoji:'⚔️',name:'חרב־החיסול',charge:20},
  lines:['🔪 יורה סכינים — 100 נזק','💨 הכי מהיר בזירה!','⚔️ מכת כוח: חרב — חיסול מיידי מקרוב','⏱️ נטענת ב־20 שניות']},
];
const charById=id=>CHARS.find(c=>c.id===id)||CHARS[0];
const unlocked=c=>save.trophies>=c.cost;
const BOT_NAMES=['צל','רעם','עקרב','ברזל','קוברה','פנתר','טורנדו','ניצוץ'];

/* ================= sound ================= */
let actx=null;
document.addEventListener('pointerdown',()=>{
  if(!actx){try{actx=new (window.AudioContext||window.webkitAudioContext)();}catch(e){}}
  else if(actx.state==='suspended'){actx.resume().catch(()=>{});}
},true);
function tone(freq,dur,type,vol,slideTo){
  if(!save.sound||!actx)return;
  try{
    const o=actx.createOscillator(),g=actx.createGain(),t=actx.currentTime;
    o.type=type||'square';
    o.frequency.setValueAtTime(freq,t);
    if(slideTo)o.frequency.exponentialRampToValueAtTime(Math.max(20,slideTo),t+dur);
    g.gain.setValueAtTime(vol||.12,t);
    g.gain.exponentialRampToValueAtTime(.0001,t+dur);
    o.connect(g);g.connect(actx.destination);
    o.start(t);o.stop(t+dur+.03);
  }catch(e){}
}
function sfx(n){
  switch(n){
    case 'shoot':tone(640,.05,'square',.04,220);break;
    case 'shoot2':tone(480,.05,'square',.025,180);break;
    case 'punch':tone(150,.08,'triangle',.2,70);break;
    case 'whiff':tone(320,.05,'sine',.035,140);break;
    case 'hit':tone(230,.06,'square',.1,100);break;
    case 'hurt':tone(170,.11,'sawtooth',.13,70);break;
    case 'boom':tone(120,.32,'sawtooth',.22,35);tone(65,.4,'square',.16,28);break;
    case 'kick':tone(95,.26,'sawtooth',.28,32);break;
    case 'sword':tone(950,.14,'square',.16,110);break;
    case 'laser':tone(310,.6,'sawtooth',.2,55);break;
    case 'ready':tone(880,.09,'sine',.14);setTimeout(()=>tone(1175,.13,'sine',.14),95);break;
    case 'select':tone(660,.08,'sine',.12,760);break;
    case 'deny':tone(190,.12,'square',.1,120);break;
    case 'count':tone(440,.09,'sine',.12);break;
    case 'go':tone(523,.12,'square',.14);setTimeout(()=>tone(784,.2,'square',.14),120);break;
    case 'win':[523,659,784,1046].forEach((f,i)=>setTimeout(()=>tone(f,.18,'square',.14),i*130));break;
    case 'lose':[392,311,262].forEach((f,i)=>setTimeout(()=>tone(f,.22,'sine',.13),i*160));break;
  }
}

/* ================= screens / home UI ================= */
function show(id){
  document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
  $(id).classList.add('active');
}
function refreshHome(){
  $('trophyCount').textContent=fmt(save.trophies);
  $('trophyCount2').textContent=fmt(save.trophies);
  $('playerNameLabel').textContent=save.name?save.name:'בחרו שם ✏️';
  const c=charById(save.selected);
  $('selectedInfo').innerHTML='משחקים בתור: '+c.emoji+' <b>'+esc(c.name)+'</b>';
  const next=CHARS.find(x=>!unlocked(x));
  $('nextUnlock').textContent=next
    ?('עוד '+fmt(next.cost-save.trophies)+' גביעים ו'+next.name+' שלכם! '+next.emoji)
    :'כל הדמויות פתוחות! 🎉';
  $('soundBtn').textContent=save.sound?'🔊':'🔇';
}
function renderChars(){
  const list=$('charList');list.innerHTML='';
  CHARS.forEach(c=>{
    const un=unlocked(c);
    const card=document.createElement('button');
    card.className='char-card'+(un?'':' locked')+(save.selected===c.id?' selected':'');
    card.style.setProperty('--cc',c.color);
    let html='<div class="cc-emoji">'+(un?c.emoji:'🔒')+'</div><div class="cc-body">';
    html+='<div class="cc-name">'+esc(c.name)+(save.selected===c.id?' <span class="cc-badge">נבחר ✓</span>':'')+'</div>';
    html+=c.lines.map(l=>'<div class="cc-line">'+l+'</div>').join('');
    if(!un){
      const pct=Math.min(100,Math.round(save.trophies/c.cost*100));
      html+='<div class="cc-lock">🔒 נפתח ב־'+fmt(c.cost)+' גביעים'
        +'<div class="cc-prog"><div style="width:'+pct+'%"></div></div>'
        +'<div class="cc-progtxt">'+fmt(save.trophies)+' / '+fmt(c.cost)+'</div></div>';
    }
    html+='</div>';
    card.innerHTML=html;
    card.addEventListener('click',()=>{
      if(un){save.selected=c.id;persist();renderChars();refreshHome();sfx('select');}
      else{card.classList.remove('shake');void card.offsetWidth;card.classList.add('shake');sfx('deny');}
    });
    list.appendChild(card);
  });
}
$('startBtn').addEventListener('click',()=>startMatch());
$('charsBtn').addEventListener('click',()=>{renderChars();show('chars');});
$('backBtn').addEventListener('click',()=>{refreshHome();show('home');});
$('soundBtn').addEventListener('click',()=>{save.sound=!save.sound;persist();refreshHome();if(save.sound)sfx('select');});

/* name modal */
$('trophyPill').addEventListener('click',()=>{
  $('nameInput').value=save.name;
  $('nameModal').classList.remove('hidden');
  setTimeout(()=>$('nameInput').focus(),60);
});
function saveName(){
  save.name=$('nameInput').value.trim().slice(0,14);
  persist();refreshHome();
  $('nameModal').classList.add('hidden');
  sfx('select');
}
$('saveNameBtn').addEventListener('click',saveName);
$('nameInput').addEventListener('keydown',e=>{if(e.key==='Enter')saveName();});
$('cancelNameBtn').addEventListener('click',()=>$('nameModal').classList.add('hidden'));

/* ================= canvas / arena ================= */
const cv=$('cv'),ctx=cv.getContext('2d');
let W=0,H=0,S=1,dpr=1;
const HUD_SAFE=58;
let walls=[];
const WALL_DEFS=[
 {fx:.50,fy:.40,len:.36,horiz:true},
 {fx:.50,fy:.66,len:.20,horiz:true},
 {fx:.16,fy:.55,len:.20,horiz:false},
 {fx:.84,fy:.55,len:.20,horiz:false},
 {fx:.25,fy:.24,len:.16,horiz:true},
 {fx:.75,fy:.24,len:.16,horiz:true},
];
function buildWalls(){
  const th=26*S;
  walls=WALL_DEFS.map(d=>{
    const L=d.horiz?d.len*W:d.len*H;
    const w=d.horiz?L:th, h=d.horiz?th:L;
    return {x:d.fx*W-w/2,y:d.fy*H-h/2,w:w,h:h};
  });
}
function resizeCanvas(){
  dpr=Math.min(window.devicePixelRatio||1,2);
  const w=window.innerWidth,h=window.innerHeight;
  if(G.running&&W>0&&H>0){
    const rx=w/W,ry=h/H;
    G.fighters.forEach(f=>{f.x*=rx;f.y*=ry;});
    G.projs.forEach(p=>{p.x*=rx;p.y*=ry;});
  }
  W=w;H=h;
  cv.width=Math.round(W*dpr);cv.height=Math.round(H*dpr);
  cv.style.width=W+'px';cv.style.height=H+'px';
  ctx.setTransform(dpr,0,0,dpr,0,0);
  S=clamp(Math.min(W,H)/480,0.8,1.7);
  buildWalls();
}
window.addEventListener('resize',resizeCanvas);

/* geometry */
function hitWall(x,y,r){
  for(const w of walls){
    const cx=clamp(x,w.x,w.x+w.w),cy=clamp(y,w.y,w.y+w.h);
    const dx=x-cx,dy=y-cy;
    if(dx*dx+dy*dy<r*r)return true;
  }
  return false;
}
function seg(ax,ay,bx,by,cx,cy,dx,dy){
  const d1=(dx-cx)*(ay-cy)-(dy-cy)*(ax-cx);
  const d2=(dx-cx)*(by-cy)-(dy-cy)*(bx-cx);
  const d3=(bx-ax)*(cy-ay)-(by-ay)*(cx-ax);
  const d4=(bx-ax)*(dy-ay)-(by-ay)*(dx-ax);
  return ((d1>0&&d2<0)||(d1<0&&d2>0))&&((d3>0&&d4<0)||(d3<0&&d4>0));
}
function losClear(x1,y1,x2,y2){
  for(const w of walls){
    const x=w.x,y=w.y,X=w.x+w.w,Y=w.y+w.h;
    if(seg(x1,y1,x2,y2,x,y,X,y)||seg(x1,y1,x2,y2,X,y,X,Y)||
       seg(x1,y1,x2,y2,X,Y,x,Y)||seg(x1,y1,x2,y2,x,Y,x,y))return false;
  }
  return true;
}

/* ================= game state ================= */
const G={running:false,state:'idle',fighters:[],projs:[],fx:[],time:0,
  countdown:0,cdLabel:'',shake:0,laser:0,laserOwner:null,endTimer:0,place:0,newUnlocks:[]};
let firing=false,fireJust=false;
let joyVec={x:0,y:0};
const keys={};

function makeFighter(def,x,y,isPlayer,name){
  return {def:def,x:x,y:y,r:22*S,hp:1000,maxHp:1000,alive:true,isPlayer:isPlayer,
    name:name,kx:0,ky:0,aim:isPlayer?-Math.PI/2:Math.PI/2,
    charge:0,atkCd:0,flash:0,dash:null,readySfx:false,chipDead:false,
    ai:isPlayer?null:{target:null,retarget:0,strafe:Math.random()<.5?1:-1,
      strafeT:rand(.7,1.5),ideal:rand(170,290),powerT:rand(.3,1.2)}};
}
function enemiesAlive(f){return G.fighters.filter(e=>e!==f&&e.alive);}
function clampF(f){f.x=clamp(f.x,f.r+4,W-f.r-4);f.y=clamp(f.y,f.r+HUD_SAFE,H-f.r-4);}
function ejectFromWalls(f){
  let tries=0;
  while(hitWall(f.x,f.y,f.r+2)&&tries++<60){
    const a=Math.atan2(H/2-f.y,W/2-f.x);
    f.x+=Math.cos(a)*10;f.y+=Math.sin(a)*10;
  }
  clampF(f);
}

function startMatch(){
  if(!unlocked(charById(save.selected)))save.selected='thief';
  resizeCanvas();
  const pdef=charById(save.selected);
  const pool=shuffle(CHARS.slice());
  const names=shuffle(BOT_NAMES.slice());
  G.fighters=[
    makeFighter(pdef,W*.5,H*.78,true,save.name||'אני'),
    makeFighter(pool[0],W*.18,H*.15,false,names[0]),
    makeFighter(pool[1],W*.82,H*.15,false,names[1]),
  ];
  G.fighters.forEach(ejectFromWalls);
  G.projs=[];G.fx=[];G.time=0;G.shake=0;G.laser=0;G.laserOwner=null;
  G.state='count';G.countdown=2.85;G.cdLabel='';G.endTimer=0;G.place=0;G.newUnlocks=[];
  G.running=true;firing=false;fireJust=false;joyVec={x:0,y:0};
  setupHUD();
  $('fireBtn').textContent=pdef.atkEmoji;
  $('powerIcon').textContent=pdef.power.emoji;
  $('cdHint').textContent='🕹️ תזוזה  •  '+pdef.atkEmoji+' '+(pdef.type==='melee'?'מכות':'ירי')+'  •  '+pdef.power.emoji+' מכת כוח';
  $('countWrap').classList.remove('hidden');
  $('overlay').classList.add('hidden');
  $('powerBtn').classList.remove('ready');
  $('powerBtn').style.setProperty('--pct','0deg');
  updateHUD();
  show('game');
}

function setupHUD(){
  const hud=$('hud');hud.innerHTML='';
  G.fighters.forEach(f=>{
    const chip=document.createElement('div');
    chip.className='chip'+(f.isPlayer?' me':'');
    chip.innerHTML='<span class="chip-e">'+f.def.emoji+'</span>'
      +'<span class="chip-n">'+esc(f.name)+'</span>'
      +'<div class="chip-bar"><div></div></div>';
    f.chip=chip;f.chipBar=chip.querySelector('.chip-bar div');
    hud.appendChild(chip);
  });
}
function updateHUD(){
  for(const f of G.fighters){
    if(!f.chipBar)continue;
    const pct=clamp(f.hp/f.maxHp,0,1);
    f.chipBar.style.width=(pct*100)+'%';
    f.chipBar.style.background=pct>.5?'#35d07f':(pct>.25?'#ffc93c':'#ff5b5b');
    if(!f.alive&&!f.chipDead){
      f.chipDead=true;f.chip.classList.add('dead');
      f.chip.querySelector('.chip-e').textContent='💀';
    }
  }
}
function updatePowerBtn(){
  const f=G.fighters[0];if(!f)return;
  const pct=clamp(f.charge/f.def.power.charge,0,1);
  $('powerBtn').style.setProperty('--pct',(pct*360)+'deg');
  const ready=pct>=1&&f.alive&&G.state==='play';
  $('powerBtn').classList.toggle('ready',ready);
  if(!ready)f.readySfx=false;
  else if(!f.readySfx){f.readySfx=true;sfx('ready');}
}

/* ================= input ================= */
const joyZone=$('joyZone'),joyBase=$('joyBase'),joyKnob=$('joyKnob');
let joyId=null,joyOrigin={x:0,y:0};
const JOY_R=52;
function moveKnob(dx,dy){joyKnob.style.transform='translate(calc(-50% + '+dx+'px), calc(-50% + '+dy+'px))';}
function resetJoyPos(){
  joyBase.style.left='26px';joyBase.style.top='auto';
  joyBase.style.bottom='calc(40px + env(safe-area-inset-bottom))';
}
joyZone.addEventListener('pointerdown',e=>{
  if(joyId!==null)return;
  joyId=e.pointerId;
  try{joyZone.setPointerCapture(joyId);}catch(err){}
  joyOrigin={x:e.clientX,y:e.clientY};
  const zr=joyZone.getBoundingClientRect();
  joyBase.style.left=(e.clientX-zr.left-59)+'px';
  joyBase.style.bottom='auto';
  joyBase.style.top=(e.clientY-zr.top-59)+'px';
  joyBase.classList.add('on');
  joyVec={x:0,y:0};moveKnob(0,0);
  e.preventDefault();
});
joyZone.addEventListener('pointermove',e=>{
  if(e.pointerId!==joyId)return;
  let dx=e.clientX-joyOrigin.x,dy=e.clientY-joyOrigin.y;
  const d=Math.hypot(dx,dy);
  if(d>JOY_R){dx*=JOY_R/d;dy*=JOY_R/d;}
  joyVec={x:dx/JOY_R,y:dy/JOY_R};
  moveKnob(dx,dy);
});
function endJoy(e){
  if(e.pointerId!==joyId)return;
  joyId=null;joyVec={x:0,y:0};moveKnob(0,0);
  joyBase.classList.remove('on');resetJoyPos();
}
joyZone.addEventListener('pointerup',endJoy);
joyZone.addEventListener('pointercancel',endJoy);

const fireBtn=$('fireBtn');
fireBtn.addEventListener('pointerdown',e=>{
  try{fireBtn.setPointerCapture(e.pointerId);}catch(err){}
  firing=true;fireJust=true;
  fireBtn.classList.add('press');
  e.preventDefault();
});
function endFire(){firing=false;fireBtn.classList.remove('press');}
fireBtn.addEventListener('pointerup',endFire);
fireBtn.addEventListener('pointercancel',endFire);

$('powerBtn').addEventListener('pointerdown',e=>{tryPlayerPower();e.preventDefault();});
$('exitBtn').addEventListener('click',()=>{
  G.running=false;G.state='idle';
  $('countWrap').classList.add('hidden');
  $('overlay').classList.add('hidden');
  refreshHome();show('home');
});

window.addEventListener('keydown',e=>{
  keys[e.key.toLowerCase()]=true;
  if(e.key===' '){keys.space=true;fireJust=true;e.preventDefault();}
  if((e.key==='e'||e.key==='E'||e.key==='Enter')&&G.state==='play')tryPlayerPower();
});
window.addEventListener('keyup',e=>{
  keys[e.key.toLowerCase()]=false;
  if(e.key===' ')keys.space=false;
});
document.addEventListener('contextmenu',e=>e.preventDefault());
document.addEventListener('touchmove',e=>{
  if(e.target&&e.target.closest&&e.target.closest('#game'))e.preventDefault();
},{passive:false});

/* ================= combat ================= */
const BASE_SPEED=195;
function moveFighter(f,dx,dy,dt){
  const sp=BASE_SPEED*S*f.def.spd;
  const nx=f.x+(dx*sp+f.kx)*dt;
  const ny=f.y+(dy*sp+f.ky)*dt;
  const dec=Math.pow(0.002,dt);
  f.kx*=dec;f.ky*=dec;
  if(!hitWall(nx,f.y,f.r))f.x=nx;
  if(!hitWall(f.x,ny,f.r))f.y=ny;
  clampF(f);
}
function knock(t,from,power){
  const a=angTo(from,t);
  t.kx+=Math.cos(a)*power;t.ky+=Math.sin(a)*power;
}
function autoTarget(f){
  let best=null,bd=1e9,anyBest=null,anyD=1e9;
  for(const e of enemiesAlive(f)){
    const d=distF(f,e);
    if(d<anyD){anyD=d;anyBest=e;}
    if(d<bd&&losClear(f.x,f.y,e.x,e.y)){bd=d;best=e;}
  }
  return best||anyBest;
}
function shootProj(f,angle){
  const sp=470*S;
  G.projs.push({x:f.x+Math.cos(angle)*(f.r+10),y:f.y+Math.sin(angle)*(f.r+10),
    vx:Math.cos(angle)*sp,vy:Math.sin(angle)*sp,r:8*S,
    dmg:f.def.dmg,owner:f,emoji:f.def.projEmoji,
    rot:Math.random()*6.28,spin:(Math.random()<.5?-1:1)*rand(9,14)});
  sfx(f.isPlayer?'shoot':'shoot2');
}
function meleeSwing(f){
  let target=null,bd=1e9;
  for(const e of enemiesAlive(f)){
    const d=distF(f,e);
    if(d<f.r+e.r+34*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){target=e;bd=d;}
  }
  if(target){
    f.aim=angTo(f,target);
    fxEmoji(target.x,target.y,'👊',30*S,.28);
    fxRing(target.x,target.y,f.def.color,8,26*S,.25);
    knock(target,f,260*S);
    sfx('punch');
    hurt(target,f.def.dmg,f);
  }else{
    const wx=f.x+Math.cos(f.aim)*(f.r+22*S),wy=f.y+Math.sin(f.aim)*(f.r+22*S);
    fxEmoji(wx,wy,'💨',20*S,.2);
    if(f.isPlayer)sfx('whiff');
  }
}
function damageNoEnd(f,dmg,src,silent){
  if(!f.alive||G.state!=='play')return false;
  f.hp-=dmg;f.flash=.16;
  const kill=f.hp<=0;
  if(!silent)fxDmg(f.x,f.y-f.r-30*S,String(dmg),
    kill?'#ffd75e':(f.isPlayer?'#ff9b9b':'#ffe9a8'),dmg>=500);
  if(f.isPlayer)sfx('hurt');else if(src&&src.isPlayer)sfx('hit');
  if(kill){
    f.hp=0;f.alive=false;f.dash=null;
    fxEmoji(f.x,f.y,'💥',54*S,.5);
    fxRing(f.x,f.y,'#ff8a5c',10,70*S,.45);
    fxSpark(f.x,f.y,'#ffce7a');
    addShake(9);sfx('boom');
    if(src&&src.isPlayer){try{if(navigator.vibrate)navigator.vibrate(35);}catch(e){}}
  }
  return kill;
}
function hurt(f,dmg,src){damageNoEnd(f,dmg,src,false);checkEnd();}
function checkEnd(){
  if(G.state!=='play')return;
  const alive=G.fighters.filter(x=>x.alive);
  const p=G.fighters[0];
  if(!p.alive){G.place=alive.length+1;beginEnd();}
  else if(alive.length===1){G.place=1;beginEnd();}
}
function beginEnd(){
  G.state='ending';
  G.endTimer=G.place===1?0.8:1.1;
  firing=false;
  G.newUnlocks=[];
  if(G.place===1){
    const before=save.trophies;
    save.trophies+=10;persist();
    G.newUnlocks=CHARS.filter(c=>before<c.cost&&save.trophies>=c.cost);
  }
}
function addShake(m){G.shake=Math.max(G.shake,m);}

/* powers */
function usePower(f){
  const id=f.def.id;
  if(id==='robot'){
    f.charge=0;G.laser=1.0;G.laserOwner=f;
    addShake(13);sfx('laser');
    fxMsg(f.x,f.y-f.r-40*S,'⚡ לייזר־ענק! ⚡');
    for(const e of enemiesAlive(f))damageNoEnd(e,500,f,false);
    checkEnd();
    return true;
  }
  if(id==='thief'){
    let t=null,bd=1e9;
    for(const e of enemiesAlive(f)){
      const d=distF(f,e);
      if(d<f.r+e.r+60*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){t=e;bd=d;}
    }
    if(!t)return false;
    f.charge=0;f.aim=angTo(f,t);
    fxEmoji(t.x,t.y,'🦵',44*S,.4);
    fxRing(t.x,t.y,'#ffc93c',10,60*S,.35);
    addShake(9);sfx('kick');
    knock(t,f,900*S);
    hurt(t,750,f);
    return true;
  }
  if(id==='samurai'){
    let best=null,bd=1e9;
    for(const e of enemiesAlive(f)){
      const d=distF(f,e);
      if(d<300*S&&d<bd&&losClear(f.x,f.y,e.x,e.y)){best=e;bd=d;}
    }
    if(!best)return false;
    f.charge=0;
    f.dash={a:angTo(f,best),t:0,dur:.22,killed:[]};
    sfx('sword');
    return true;
  }
  return false;
}
function tryPlayerPower(){
  const f=G.fighters[0];
  if(!f||G.state!=='play'||!f.alive||f.dash)return;
  if(f.charge<f.def.power.charge){
    const pb=$('powerBtn');
    pb.classList.remove('shake');void pb.offsetWidth;pb.classList.add('shake');
    sfx('deny');return;
  }
  const ok=usePower(f);
  if(!ok){fxMsg(f.x,f.y-f.r-40*S,'אין יריב קרוב!');sfx('deny');}
}
function dashStep(f,dt){
  const d=f.dash;d.t+=dt;
  const sp=1500*S;
  const nx=f.x+Math.cos(d.a)*sp*dt,ny=f.y+Math.sin(d.a)*sp*dt;
  if(hitWall(nx,ny,f.r)||nx<f.r||nx>W-f.r||ny<f.r+HUD_SAFE||ny>H-f.r){
    d.t=d.dur;
  }else{f.x=nx;f.y=ny;}
  fxGhost(f.x,f.y,f.r,f.def.color);
  for(const e of enemiesAlive(f)){
    if(d.killed.indexOf(e)>=0)continue;
    if(distF(f,e)<f.r+e.r+10*S){
      d.killed.push(e);
      fxEmoji(e.x,e.y,'⚔️',46*S,.45);
      fxMsg(e.x,e.y-44*S,'חיסול!');
      fxRing(e.x,e.y,'#ffffff',6,70*S,.4);
      addShake(10);
      damageNoEnd(e,999999,f,true);
    }
  }
  checkEnd();
  if(f.dash&&d.t>=d.dur)f.dash=null;
}

/* player + bots */
function playerControl(f,dt){
  let mx=joyVec.x,my=joyVec.y;
  mx+=(keys['arrowright']||keys['d']?1:0)-(keys['arrowleft']||keys['a']?1:0);
  my+=(keys['arrowdown']||keys['s']?1:0)-(keys['arrowup']||keys['w']?1:0);
  const m=Math.hypot(mx,my);
  if(m>1){mx/=m;my/=m;}
  moveFighter(f,mx,my,dt);
  const t=autoTarget(f);
  if(t)f.aim=angTo(f,t);
  else if(m>0.1)f.aim=Math.atan2(my,mx);
  f.atkCd-=dt;
  if(fireJust){f.atkCd=Math.min(f.atkCd,0);fireJust=false;}
  if((firing||keys.space)&&f.atkCd<=0){
    if(f.def.type==='shoot')shootProj(f,f.aim);
    else meleeSwing(f);
    f.atkCd=1/f.def.rate;
  }
}
function botThink(f,dt){
  const ai=f.ai;
  ai.retarget-=dt;
  if(ai.retarget<=0||!ai.target||!ai.target.alive){
    ai.target=autoTarget(f);
    ai.retarget=rand(.5,1.1);
  }
  const t=ai.target;
  if(!t){moveFighter(f,0,0,dt);return;}
  const dx=t.x-f.x,dy=t.y-f.y;
  const d=Math.hypot(dx,dy)||1;
  const ux=dx/d,uy=dy/d;
  ai.strafeT-=dt;
  if(ai.strafeT<=0){ai.strafe*=-1;ai.strafeT=rand(.7,1.5);}
  let mx=0,my=0;
  if(f.def.type==='melee'){
    mx=ux;my=uy;
    if(d<f.r+t.r+28*S){mx*=.15;my*=.15;}
    mx+=-uy*ai.strafe*.25;my+=ux*ai.strafe*.25;
  }else{
    const ideal=ai.ideal*S;
    if(d>ideal+70*S){mx=ux;my=uy;}
    else if(d<ideal-70*S){mx=-ux;my=-uy;}
    mx+=-uy*ai.strafe*.8;my+=ux*ai.strafe*.8;
  }
  const mm=Math.hypot(mx,my);
  if(mm>1){mx/=mm;my/=mm;}
  const px=f.x+mx*46*S,py=f.y+my*46*S;
  if(hitWall(px,py,f.r)){
    const a=Math.atan2(my,mx);
    const offs=[1.2,-1.2,2.2,-2.2];
    for(let i=0;i<offs.length;i++){
      const nx2=f.x+Math.cos(a+offs[i])*46*S,ny2=f.y+Math.sin(a+offs[i])*46*S;
      if(!hitWall(nx2,ny2,f.r)){mx=Math.cos(a+offs[i]);my=Math.sin(a+offs[i]);break;}
    }
  }
  f.aim=Math.atan2(dy,dx);
  moveFighter(f,mx,my,dt);
  f.atkCd-=dt;
  if(f.def.type==='shoot'){
    if(f.atkCd<=0&&d<560*S&&losClear(f.x,f.y,t.x,t.y)){
      shootProj(f,f.aim+rand(-.13,.13));
      f.atkCd=rand(.32,.5);
    }
  }else{
    if(f.atkCd<=0&&d<f.r+t.r+36*S){
      meleeSwing(f);
      f.atkCd=.5;
    }
  }
  if(f.charge>=f.def.power.charge){
    ai.powerT-=dt;
    if(ai.powerT<=0){
      const used=usePower(f);
      ai.powerT=used?.6:.25;
    }
  }
}
function separate(){
  const fs=G.fighters.filter(f=>f.alive);
  for(let i=0;i<fs.length;i++)for(let j=i+1;j<fs.length;j++){
    const a=fs[i],b=fs[j];
    let dx=b.x-a.x,dy=b.y-a.y;
    let d=Math.hypot(dx,dy);
    const min=a.r+b.r;
    if(d<min){
      if(d<0.01){dx=1;dy=0;d=1;}
      const push=(min-d)/2,ux=dx/d,uy=dy/d;
      a.x-=ux*push;a.y-=uy*push;
      b.x+=ux*push;b.y+=uy*push;
      clampF(a);clampF(b);
      if(hitWall(a.x,a.y,a.r))ejectFromWalls(a);
      if(hitWall(b.x,b.y,b.r))ejectFromWalls(b);
    }
  }
}
function updateProjs(dt){
  for(let i=G.projs.length-1;i>=0;i--){
    const p=G.projs[i];
    p.x+=p.vx*dt;p.y+=p.vy*dt;p.rot+=p.spin*dt;
    if(p.x<-40||p.x>W+40||p.y<-40||p.y>H+40){G.projs.splice(i,1);continue;}
    if(hitWall(p.x,p.y,p.r)){fxSpark(p.x,p.y,'#c9d2ff');G.projs.splice(i,1);continue;}
    let hitF=false;
    for(const f of G.fighters){
      if(!f.alive||f===p.owner)continue;
      const dx=f.x-p.x,dy=f.y-p.y;
      const rr=(f.r+p.r);
      if(dx*dx+dy*dy<rr*rr){
        fxSpark(p.x,p.y,p.owner.def.color);
        hurt(f,p.dmg,p.owner);
        hitF=true;break;
      }
    }
    if(hitF)G.projs.splice(i,1);
  }
}

/* ================= fx ================= */
function addFx(o){o.t=0;G.fx.push(o);}
function fxDmg(x,y,text,color,big){addFx({type:'dmg',x:x,y:y,text:text,color:color,big:!!big,dur:.8});}
function fxMsg(x,y,text){addFx({type:'msg',x:x,y:y,text:text,dur:1.0});}
function fxEmoji(x,y,e,size,dur){addFx({type:'emoji',x:x,y:y,e:e,size:size,dur:dur||.35});}
function fxRing(x,y,color,r0,r1,dur){addFx({type:'ring',x:x,y:y,color:color,r0:r0,r1:r1,dur:dur||.3});}
function fxSpark(x,y,color){addFx({type:'spark',x:x,y:y,color:color,dur:.22});}
function fxGhost(x,y,r,color){addFx({type:'ghost',x:x,y:y,r:r,color:color,dur:.3});}
function updateFx(dt){
  for(let i=G.fx.length-1;i>=0;i--){
    G.fx[i].t+=dt;
    if(G.fx[i].t>=G.fx[i].dur)G.fx.splice(i,1);
  }
}

/* ================= update ================= */
function update(dt){
  G.time+=dt;
  if(G.shake>0)G.shake=Math.max(0,G.shake-34*dt);
  if(G.laser>0)G.laser-=dt;
  updateFx(dt);

  if(G.state==='count'){
    G.countdown-=dt;
    const label=G.countdown>0.6?String(Math.ceil((G.countdown-0.6)/0.75)):'קרב!';
    if(label!==G.cdLabel){
      G.cdLabel=label;
      const cd=$('cdNum');
      cd.textContent=label;
      cd.classList.remove('pop');void cd.offsetWidth;cd.classList.add('pop');
      sfx(label==='קרב!'?'go':'count');
    }
    if(G.countdown<=0){
      G.state='play';
      $('countWrap').classList.add('hidden');
    }
    updateHUD();updatePowerBtn();
    return;
  }
  if(G.state==='ending'||G.state==='over'){
    updateProjs(dt);
    updateHUD();updatePowerBtn();
    if(G.state==='ending'){
      G.endTimer-=dt;
      if(G.endTimer<=0)showOverlay();
    }
    return;
  }
  if(G.state!=='play')return;

  for(const f of G.fighters){
    if(!f.alive)continue;
    f.flash=Math.max(0,f.flash-dt);
    if(f.dash){dashStep(f,dt);continue;}
    f.charge=Math.min(f.def.power.charge,f.charge+dt);
    if(f.isPlayer)playerControl(f,dt);
    else botThink(f,dt);
  }
  separate();
  updateProjs(dt);
  updateHUD();
  updatePowerBtn();
}

/* ================= render ================= */
function circleFill(x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,6.2832);ctx.fill();}
function circleStroke(x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,6.2832);ctx.stroke();}
function rr(x,y,w,h,r){
  ctx.beginPath();
  if(ctx.roundRect)ctx.roundRect(x,y,w,h,r);
  else ctx.rect(x,y,w,h);
}
function drawEmoji(e,x,y,size){
  ctx.font=size+'px "Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif';
  ctx.textAlign='center';ctx.textBaseline='middle';
  ctx.fillText(e,x,y);
}
function render(){
  ctx.clearRect(0,0,W,H);
  ctx.save();
  if(G.shake>0)ctx.translate(rand(-G.shake,G.shake),rand(-G.shake,G.shake));

  ctx.fillStyle='#141827';
  ctx.fillRect(-24,-24,W+48,H+48);
  ctx.strokeStyle='rgba(85,100,170,.13)';ctx.lineWidth=1;
  const g=46*S;
  ctx.beginPath();
  for(let x=g;x<W;x+=g){ctx.moveTo(x,0);ctx.lineTo(x,H);}
  for(let y=g;y<H;y+=g){ctx.moveTo(0,y);ctx.lineTo(W,y);}
  ctx.stroke();

  for(const w of walls){
    ctx.fillStyle='#39415f';rr(w.x,w.y,w.w,w.h,8);ctx.fill();
    ctx.fillStyle='rgba(255,255,255,.10)';rr(w.x,w.y,w.w,4,3);ctx.fill();
    ctx.strokeStyle='rgba(0,0,0,.35)';ctx.lineWidth=2;rr(w.x,w.y,w.w,w.h,8);ctx.stroke();
  }

  for(const f of G.fighters){
    if(!f.alive){
      ctx.globalAlpha=.5;
      drawEmoji('💀',f.x,f.y,f.r*1.2);
      ctx.globalAlpha=1;
    }
  }

  for(const p of G.projs){
    ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot);
    drawEmoji(p.emoji,0,0,16*S);
    ctx.restore();
  }

  for(const f of G.fighters){if(f.alive)drawFighter(f);}
  drawFx();
  if(G.laser>0)drawLaser();
  ctx.restore();
}
function drawFighter(f){
  const c=f.def.color;
  if(f.flash>0){
    ctx.globalAlpha=Math.min(1,f.flash*6);
    ctx.fillStyle='#ffffff';circleFill(f.x,f.y,f.r+6);
    ctx.globalAlpha=1;
  }
  if(f.isPlayer){
    ctx.strokeStyle='rgba(255,215,94,'+(0.3+0.22*Math.sin(G.time*5)).toFixed(3)+')';
    ctx.lineWidth=3;circleStroke(f.x,f.y,f.r+9);
  }
  ctx.fillStyle='#20263e';circleFill(f.x,f.y,f.r);
  ctx.lineWidth=4;ctx.strokeStyle=f.isPlayer?'#ffd75e':c;circleStroke(f.x,f.y,f.r);
  drawEmoji(f.def.emoji,f.x,f.y+1,f.r*1.35);
  const ax=f.x+Math.cos(f.aim)*(f.r+7),ay=f.y+Math.sin(f.aim)*(f.r+7);
  ctx.fillStyle=f.isPlayer?'#ffd75e':c;circleFill(ax,ay,3.5);
  if(!f.isPlayer&&f.charge>=f.def.power.charge&&Math.sin(G.time*10)>0){
    drawEmoji('💥',f.x+f.r*.9,f.y-f.r-9,14*S);
  }
  ctx.font='800 '+Math.round(12*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
  ctx.textAlign='center';ctx.textBaseline='middle';
  ctx.fillStyle=f.isPlayer?'#ffd75e':'#cdd5f7';
  ctx.fillText(f.name,f.x,f.y-f.r-25*S);
  const bw=56*S,bh=7*S,bx=f.x-bw/2,by=f.y-f.r-17*S;
  ctx.fillStyle='rgba(0,0,0,.55)';rr(bx-1,by-1,bw+2,bh+2,4);ctx.fill();
  const pct=clamp(f.hp/f.maxHp,0,1);
  if(pct>0){
    ctx.fillStyle=pct>.5?'#35d07f':(pct>.25?'#ffc93c':'#ff5b5b');
    rr(bx,by,bw*pct,bh,3);ctx.fill();
  }
}
function drawFx(){
  for(const e of G.fx){
    const p=e.t/e.dur;
    if(e.type==='dmg'){
      ctx.globalAlpha=1-p;
      ctx.font='900 '+Math.round((e.big?26:18)*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
      ctx.textAlign='center';ctx.textBaseline='middle';
      const y=e.y-p*44*S;
      ctx.lineWidth=3;ctx.strokeStyle='rgba(0,0,0,.6)';
      ctx.strokeText(e.text,e.x,y);
      ctx.fillStyle=e.color||'#ffe9a8';
      ctx.fillText(e.text,e.x,y);
      ctx.globalAlpha=1;
    }else if(e.type==='msg'){
      ctx.globalAlpha=1-p*p;
      ctx.font='900 '+Math.round(21*S)+'px -apple-system,"Segoe UI",Roboto,sans-serif';
      ctx.textAlign='center';ctx.textBaseline='middle';
      const y=e.y-p*26*S;
      ctx.lineWidth=4;ctx.strokeStyle='rgba(0,0,0,.65)';
      ctx.strokeText(e.text,e.x,y);
      ctx.fillStyle='#ffc93c';
      ctx.fillText(e.text,e.x,y);
      ctx.globalAlpha=1;
    }else if(e.type==='emoji'){
      ctx.globalAlpha=1-p*p;
      const sc=.5+Math.min(1,p*3)*.7;
      drawEmoji(e.e,e.x,e.y,e.size*sc);
      ctx.globalAlpha=1;
    }else if(e.type==='ring'){
      ctx.globalAlpha=(1-p)*.85;
      ctx.strokeStyle=e.color;ctx.lineWidth=Math.max(1,4*(1-p));
      circleStroke(e.x,e.y,e.r0+(e.r1-e.r0)*p);
      ctx.globalAlpha=1;
    }else if(e.type==='spark'){
      ctx.globalAlpha=1-p;
      ctx.fillStyle=e.color||'#fff';
      for(let k=0;k<6;k++){
        const a=k*1.047,r=p*20*S;
        circleFill(e.x+Math.cos(a)*r,e.y+Math.sin(a)*r,2.4*S*(1-p)+.6);
      }
      ctx.globalAlpha=1;
    }else if(e.type==='ghost'){
      ctx.globalAlpha=(1-p)*.3;
      ctx.fillStyle=e.color;
      circleFill(e.x,e.y,e.r*(1-p*.4));
      ctx.globalAlpha=1;
    }
  }
}
function drawLaser(){
  const col=G.laserOwner?G.laserOwner.def.color:'#3ec7f0';
  ctx.globalAlpha=0.14+0.11*Math.abs(Math.sin(G.time*42));
  ctx.fillStyle=col;
  ctx.fillRect(0,0,W,H);
  const n=7;
  for(let i=0;i<n;i++){
    const y=(((i/n)+(1-G.laser)*1.4)%1)*H;
    ctx.globalAlpha=.32;ctx.fillStyle=col;ctx.fillRect(0,y-6,W,14);
    ctx.globalAlpha=.55;ctx.fillStyle='#ffffff';ctx.fillRect(0,y,W,3);
  }
  ctx.globalAlpha=1;
}

/* ================= overlay ================= */
function showOverlay(){
  G.state='over';
  const place=G.place;
  $('ovEmoji').textContent=place===1?'🏆':(place===2?'🥈':'🥉');
  if(place===1){
    $('ovTitle').textContent='מקום ראשון!';
    $('ovSub').innerHTML='+10 גביעים! 🏆<br><span class="ov-total">סה״כ: '+fmt(save.trophies)+' 🏆</span>';
    spawnConfetti();sfx('win');
  }else{
    $('ovTitle').textContent=place===2?'מקום שני — כמעט!':'מקום שלישי';
    $('ovSub').innerHTML='רק המקום הראשון מקבל גביעים.<br>עוד ניסיון? 💪';
    $('confetti').innerHTML='';
    sfx('lose');
  }
  const ou=$('ovUnlock');
  if(G.newUnlocks.length){
    ou.classList.remove('hidden');
    ou.innerHTML=G.newUnlocks.map(c=>'🎉 נפתחה דמות חדשה: '+c.emoji+' <b>'+esc(c.name)+'</b>!').join('<br>');
  }else ou.classList.add('hidden');
  $('overlay').classList.remove('hidden');
}
function spawnConfetti(){
  const box=$('confetti');box.innerHTML='';
  const em=['🎉','✨','⭐','🏆','💛'];
  for(let i=0;i<26;i++){
    const s=document.createElement('span');
    s.textContent=em[i%em.length];
    s.style.left=(Math.random()*100)+'%';
    s.style.animationDelay=(Math.random()*.8)+'s';
    s.style.animationDuration=(2+Math.random()*1.6)+'s';
    s.style.fontSize=(15+Math.random()*20)+'px';
    box.appendChild(s);
  }
}
$('againBtn').addEventListener('click',()=>startMatch());
$('homeBtn').addEventListener('click',()=>{
  G.running=false;G.state='idle';
  $('overlay').classList.add('hidden');
  refreshHome();show('home');
});

/* ================= loop / init ================= */
let last=0;
function loop(ts){
  requestAnimationFrame(loop);
  const dt=Math.min(0.05,(ts-last)/1000||0.016);
  last=ts;
  if(!G.running)return;
  update(dt);
  render();
}
(async function init(){
  await loadSave();
  refreshHome();
  renderChars();
  resizeCanvas();
  resetJoyPos();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>

Game Source: זירת החיסולים

Creator: MysticFlare49

Libraries: none

Complexity: complex (1165 lines, 44.3 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: game-mysticflare49" to link back to the original. Then publish at arcadelab.ai/publish.