Void Bacon+ v3.4

have fun ngas dont do anything stupid

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Void Bacon+ v3.4
// @namespace    VoidBacon
// @version      3.4
// @author       John pork
// @match        *://survev.io/*
// @description  have fun ngas dont do anything stupid
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
'use strict';

const STORE='voidbacon.v2';
const CFG_STORE='voidbacon.cfg';
function loadState(){try{return JSON.parse(localStorage.getItem(STORE))||{};}catch{return{};}}
const state=Object.assign({x:120,y:80,w:980,h:660,open:true,max:false,tab:'combat',restore:null},loadState());
const clamp=(v,mn,mx)=>Math.max(mn,Math.min(mx,v));
const save=()=>localStorage.setItem(STORE,JSON.stringify({x:state.x,y:state.y,w:state.w,h:state.h,open:state.open,max:state.max,tab:state.tab}));

const cfg={
  Esp:false,names:false,healthBars:false,lootEsp:false,barrelEsp:false,gunEsp:false,grenTimer:false,
  lockOn:false,predict:false,magnet:false,smoothAim:false,
  smoothAmount:50,stickyTarget:false,fovEnabled:false,fovSize:80,showFov:false,wallcheck:true,
  xray:false,layerSpoof:false,mapHighlights:false,autoDoor:false,autoLoot:false,
};

const SAVEABLE_BOOLS=['Esp','names','healthBars','lootEsp','barrelEsp','gunEsp','grenTimer',
  'lockOn','predict','magnet','smoothAim','stickyTarget','fovEnabled','showFov','wallcheck',
  'xray','layerSpoof','mapHighlights','autoDoor','autoLoot'];

function saveCfg(){
  const out={};
  for(const k of SAVEABLE_BOOLS)out[k]=cfg[k];
  out.smoothAmount=cfg.smoothAmount;out.fovSize=cfg.fovSize;
  localStorage.setItem(CFG_STORE,JSON.stringify(out));
  toast('Settings saved ✓');
}
function loadCfg(){
  try{
    const saved=JSON.parse(localStorage.getItem(CFG_STORE)||'{}');
    for(const k of SAVEABLE_BOOLS){if(typeof saved[k]==='boolean')cfg[k]=saved[k];}
    if(typeof saved.smoothAmount==='number')cfg.smoothAmount=saved.smoothAmount;
    if(typeof saved.fovSize==='number')cfg.fovSize=saved.fovSize;
  }catch(e){}
}

let _fbRating=5;

async function submitFeedback(text,rating){
  if(!text.trim()){toast('Enter some feedback first');return;}
  const btn=document.getElementById('_vmFbSubmit');
  if(btn){btn.disabled=true;btn.textContent='SENDING...';}
  try{
    const payload={embeds:[{title:`⭐ Feedback — ${rating}/5 stars`,description:text.trim().slice(0,1800),color:rating>=4?0x5bc470:rating>=3?0xffc43c:0xe87a7a,footer:{text:`Void Bacon+ v3.3.3 | ${new Date().toUTCString()}`}}]};
    const hookUrl='https://discord.com/api/webhooks/1506623115633885234/fURly2_CSjiLajkTd4Nt7g1NH-2luzA8W72dtcthdHO3AYVRHnbNHK5oizIQ_HLe37zt';
    if(!hookUrl){toast('No webhook set in script');return;}
    const res=await fetch(hookUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});
    if(res.ok||res.status===204){toast('Feedback sent ✓');const inp=document.getElementById('_vmFbText');if(inp)inp.value='';}
    else toast(`Discord error: ${res.status}`);
  }catch(e){toast('Network error');}
  finally{if(btn){btn.disabled=false;btn.textContent='SEND TO DISCORD ↗';}}
}

const settings={lockOnEnabled:true,magnetEnabled:true,throwerEnabled:true,zoomEnabled:true};
const keybinds={lockOn:'t',magnet:'e',thrower:'q',zoomIn:'=',zoomOut:'-',zoomReset:'0',stickyTarget:'n',layerSpoof:'y'};

const GUN_DATA={
  mp5:{range:100,spread:3,bulletSpeed:85},mac10:{range:50,spread:10,bulletSpeed:75},
  ump9:{range:100,spread:1.5,bulletSpeed:100},vector:{range:46,spread:2.5,bulletSpeed:88},
  vector45:{range:45,spread:4.5,bulletSpeed:82},scorpion:{range:120,spread:4,bulletSpeed:90},
  vss:{range:125,spread:2,bulletSpeed:110},famas:{range:150,spread:1.1,bulletSpeed:110},
  hk416:{range:175,spread:4,bulletSpeed:105},m4a1:{range:165,spread:2,bulletSpeed:98},
  ak47:{range:200,spread:2.5,bulletSpeed:100},scar:{range:175,spread:2,bulletSpeed:108},
  scarssr:{range:200,spread:1.5,bulletSpeed:108},an94:{range:300,spread:1.5,bulletSpeed:110},
  groza:{range:175,spread:5,bulletSpeed:104},grozas:{range:185,spread:3.5,bulletSpeed:106},
  imbel:{range:200,spread:3,bulletSpeed:92},dp28:{range:225,spread:2,bulletSpeed:110},
  bar:{range:275,spread:2,bulletSpeed:114},pkp:{range:200,spread:2.5,bulletSpeed:120},
  m249:{range:220,spread:1.5,bulletSpeed:125},qbb97:{range:200,spread:4,bulletSpeed:118},
  mk12:{range:400,spread:1,bulletSpeed:132},l86:{range:425,spread:1,bulletSpeed:134},
  mosin:{range:500,spread:1,bulletSpeed:178},sv98:{range:520,spread:1,bulletSpeed:182},
  awc:{range:500,spread:0.5,bulletSpeed:136},m39:{range:400,spread:1,bulletSpeed:125},
  svd:{range:425,spread:1,bulletSpeed:127},garand:{range:444,spread:0.4,bulletSpeed:144},
  scout_elite:{range:450,spread:1,bulletSpeed:164},model94:{range:175,spread:1.5,bulletSpeed:156},
  blr:{range:400,spread:1.5,bulletSpeed:160},mkg45:{range:145,spread:3.5,bulletSpeed:126},
  m870:{range:27,spread:10,bulletSpeed:66},m1100:{range:25,spread:25,bulletSpeed:66},
  mp220:{range:27,spread:10,bulletSpeed:66},spas12:{range:27,spread:10,bulletSpeed:66},
  saiga:{range:45,spread:7,bulletSpeed:88},usas:{range:24,spread:7,bulletSpeed:72},
  m1014:{range:60,spread:4,bulletSpeed:118},m9:{range:100,spread:3,bulletSpeed:85},
  m9_dual:{range:100,spread:4,bulletSpeed:85},m93r:{range:100,spread:4,bulletSpeed:85},
  glock:{range:44,spread:12,bulletSpeed:70},p30l:{range:100,spread:2,bulletSpeed:94},
  ot38:{range:125,spread:1.25,bulletSpeed:112},ots38:{range:135,spread:1.2,bulletSpeed:115},
  colt45:{range:110,spread:16,bulletSpeed:106},m1911:{range:88,spread:2,bulletSpeed:80},
  m1a1:{range:88,spread:6,bulletSpeed:80},deagle:{range:120,spread:2.5,bulletSpeed:115},
  deagle_dual:{range:120,spread:3.5,bulletSpeed:115},flare_gun:{range:16,spread:1.25,bulletSpeed:4},
};

const GREN_FUSE={frag:4.0,mirv:4.0,mirv_mini:1.8,smoke:2.5,strobe:13.5,martyr:3.0};
const GREN_BLAST={frag:12,mirv:12,mirv_mini:8,smoke:12,strobe:2.5,martyr:8};
function getGrenFuse(t){if(!t)return 4;const k=t.toLowerCase();for(const[n,f]of Object.entries(GREN_FUSE))if(k.includes(n))return f;return 4;}
function getGrenBlast(t){if(!t)return 12;const k=t.toLowerCase();for(const[n,r]of Object.entries(GREN_BLAST))if(k.includes(n))return r;return 12;}
function lootColor(item){
  const t=(typeof item.type==='string'?item.type:'').toLowerCase();
  if(/awm|deagle|mk_20|sv-98|mosin|l86|nt-16|mirv|strobe|spas|usas|saiga|qbb|pkp|dp-28|m249|helmet03|vest03|pack03/.test(t))return'#ff66ff';
  if(/scar|m416|ak|akm|bar|garand|model_94|mp220|vector|p90|m79|frag|smoke|flare|helmet02|vest02|pack02/.test(t))return'#ffcc44';
  if(/m9|glock|ump|mp5|cz|p30|mac|helmet01|vest01|pack01/.test(t))return'#44aaff';
  return'#cccccc';
}
const MAP_COLORS={container_06:14074643,barn_01:6959775,stone_02:1646367,stone_04:15406938,stone_05:15406938,crate_03:5342557,bunker_storm_01:6959775,bunker_hydra_01:10030546,bunker_crossing_stairs_01b:13571226,bunker_crossing_stairs_01:13571226};
const MAP_SCALES={container_06:1,stone_02:6,barn_01:1,stone_04:6,stone_05:6,crate_03:1.8,bunker_storm_01:1.75,bunker_hydra_01:1.75,bunker_crossing_stairs_01b:2,bunker_crossing_stairs_01:2};

// ============================================================
//  STATE VARS
// ============================================================
const KEY_W=87,KEY_A=65,KEY_S=83,KEY_D=68;
let _nearest=null,_stickyTarget=null;
let _fps=0,_fpsFrames=0,_fpsLast=performance.now(),_ms=0,_msLast=performance.now();
let _magnetKeys={w:false,a:false,s:false,d:false};
let _zoomMultiplier=1.0,_zoomHooked=false,_grenCook=null;
let _baseStud=0,_layerSpoofActive=false,_layerOrig=null,_layerDescriptor=null,_layerKeyHeld=false;
const _thrownGrenades=new Map();
let _smoothSx=null,_smoothSy=null;

// ============================================================
//  AUTO-MAPPER — finds scrambled keys by shape, not by name
//  Runs independently, patches the live getter functions
// ============================================================
const _MAPPER_STORE = 'voidbacon.keymap';
let _lastScanResult = null;

const _isVec2      = v => v && typeof v==='object' && typeof v.x==='number' && typeof v.y==='number';
const _isUnitVec   = v => { if(!_isVec2(v))return false; const m=Math.hypot(v.x,v.y); return m>0.97&&m<1.03; };
const _isWorldPos  = v => { if(!_isVec2(v))return false; return Math.abs(v.x)>5&&Math.abs(v.y)>5&&Math.abs(v.x)<5000&&Math.abs(v.y)<5000; };
const _objKeys     = o => { try{return Object.keys(o);}catch{return[];} };
const _safeGet     = (o,k) => { try{return o[k];}catch{return undefined;} };
const _allProtoKeys= obj => {
  const out=new Set(); let p=obj;
  while(p&&p!==Object.prototype){Object.getOwnPropertyNames(p).forEach(k=>out.add(k));p=Object.getPrototypeOf(p);}
  return[...out];
};
const _poolArr = v => {
  if(!v||typeof v!=='object')return null;
  for(const k of _objKeys(v)){
    const a=_safeGet(v,k);
    if(Array.isArray(a)&&a.length>0&&typeof a[0]==='object'&&a[0].__id!=null)return{key:k,arr:a};
  }
  return null;
};

function runAutoMapper(){
  const app = window.__Re || Object.values(window).find(v=>v&&typeof v==='object'&&'audioManager'in v&&'pixi'in v&&'game'in v);
  if(!app)return{error:'App not found — load the game first'};

  const game=app.game;
  const R={};
  const missing=[];
  const log=[];

  const found=(label,key)=>{ R[label]=key; log.push(`✅ ${label} → ${key}`); return key; };
  const nope =(label)=>{ R[label]=null; missing.push(label); log.push(`❌ ${label} — not found`); return null; };

  // ── GAME LEVEL ──────────────────────────────────────────────
  const barnKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&v.playerPool&&typeof v.playerInfo==='object';});
  barnKey?found('barnKey',barnKey):nope('barnKey');

  const inputKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&(_isVec2(v.mousePos)||_isVec2(v.mouse))&&('mouseButtons'in v||'keys'in v);});
  inputKey?found('inputKey',inputKey):nope('inputKey');

  const meKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&typeof v==='object'&&v.__id!=null&&v.container&&typeof v.active==='boolean'&&typeof v.layer==='number';});
  meKey?found('meKey',meKey):nope('meKey');

  const me=meKey?_safeGet(game,meKey):null;

  let activeIdKey=null;
  if(me?.__id!=null){activeIdKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return typeof v==='number'&&v===me.__id&&k.length<=8;});}
  activeIdKey?found('activeIdKey',activeIdKey):nope('activeIdKey');

  const pixiKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&v.stage&&Array.isArray(v.stage.children);});
  pixiKey?found('pixiKey',pixiKey):nope('pixiKey');

  const lootBarnKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&v.lootPool;});
  lootBarnKey?found('lootBarnKey',lootBarnKey):nope('lootBarnKey');

  const projBarnKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&v.projectilePool;});
  projBarnKey?found('projBarnKey',projBarnKey):nope('projBarnKey');

  const mapKey=_objKeys(game).find(k=>{const v=_safeGet(game,k);return v&&(typeof v.mapName==='string'||v.mapData||typeof v.getGroundSurface==='function');});
  mapKey?found('mapKey',mapKey):nope('mapKey');

  // ── MAP LEVEL ───────────────────────────────────────────────
  const mapObj=mapKey?_safeGet(game,mapKey):null;

  const obstKey=mapObj?_objKeys(mapObj).find(k=>{const p=_poolArr(_safeGet(mapObj,k));return p&&p.arr.some(o=>o&&typeof o.type==='string'&&_isVec2(o.pos));}):null;
  obstKey?found('obstKey',obstKey):nope('obstKey');

  const buildKey=mapObj?_objKeys(mapObj).find(k=>{const p=_poolArr(_safeGet(mapObj,k));return p&&p.arr.some(o=>o&&(o.ceiling||o.imgs||o.ceilingImg));}):null;
  buildKey?found('buildKey',buildKey):nope('buildKey');

  const obstPool=obstKey&&mapObj?_safeGet(mapObj,obstKey):null;
  const poolKey=_poolArr(obstPool)?.key;
  poolKey?found('poolKey',poolKey):nope('poolKey');

  // Also try playerPool arr key
  const barnObj=barnKey?_safeGet(game,barnKey):null;
  const playerPoolKey=_poolArr(barnObj?.playerPool)?.key;
  playerPoolKey?found('playerPoolKey',playerPoolKey):nope('playerPoolKey');

  // ── PLAYER LEVEL ────────────────────────────────────────────
  if(me){
    const pkeys=_allProtoKeys(me);

    const posKey=pkeys.filter(k=>{try{return _isWorldPos(me[k]);}catch{return false;}})
      .filter(k=>!k.toLowerCase().includes('sprite')&&!k.toLowerCase().includes('container'))
      .sort()[0];
    posKey?found('posKey',posKey):nope('posKey');

    const dirKey=pkeys.filter(k=>{try{return _isUnitVec(me[k]);}catch{return false;}})[0];
    dirKey?found('dirKey',dirKey):nope('dirKey');

    const weapKey=pkeys.find(k=>{try{const v=me[k];return typeof v==='string'&&(v==='fists'||/^[a-z0-9_]{1,20}$/.test(v))&&k.toLowerCase().includes('weap');}catch{return false;}})
      ||pkeys.find(k=>{try{const v=me[k];return typeof v==='string'&&v==='fists';}catch{return false;}});
    weapKey?found('weapKey',weapKey):nope('weapKey');

    const throwKey=pkeys.find(k=>{try{const v=me[k];return v==='equip'||v==='cook'||v==='throw';}catch{return false;}});
    throwKey?found('throwKey',throwKey):nope('throwKey');
  }

  R._log=log;
  R._missing=missing;
  R._ok=missing.length===0;
  return R;
}

function applyMapperResult(R){
  if(!R||R.error)return false;
  // Patch the live getter functions used throughout the script
  // These are re-evaluated every call so patching them here is enough
  window.__VB_KEYMAP__ = R;
  localStorage.setItem(_MAPPER_STORE, JSON.stringify(
    Object.fromEntries(Object.entries(R).filter(([k])=>!k.startsWith('_')))
  ));
  _lastScanResult = R;
  return true;
}

function loadSavedKeymap(){
  try{
    const saved=JSON.parse(localStorage.getItem(_MAPPER_STORE)||'null');
    if(saved){_lastScanResult=saved;window.__VB_KEYMAP__=saved;}
  }catch(e){}
}

// ── Getter functions that respect the keymap ──────────────────
function _km(field){ return _lastScanResult?.[field]??null; }

function isAppObj(a){try{return a&&typeof a==='object'&&'audioManager'in a&&'pixi'in a&&'game'in a&&'inputBinds'in a;}catch{return false;}}
function poolArr(p){
  const k=_km('poolKey')||_km('playerPoolKey');
  if(k&&p&&Array.isArray(p[k]))return p[k];
  return p?.m_getPool?.()??p?.m_pool??p?.aTL??p?.cAGTlU??[];
}
function getGame(){return window.__SurvevDev?.game??findRe()?.game??null;}
function getBarn(game){
  const k=_km('barnKey');
  return(k&&game?.[k])||window.__SurvevDev?.barn||game?.gAl||game?.DaWsN||game?.m_playerBarn||null;
}
function getInput(game){
  const k=_km('inputKey');
  return(k&&game?.[k])||window.__SurvevDev?.input||game?.DyO||game?.evqDZ||game?.m_input||findRe()?.input||null;
}
function getMe(game){
  const k=_km('meKey');
  return(k&&game?.[k])||window.__SurvevDev?.me||game?.agncI||game?.zSsh||game?.m_activePlayer||null;
}
function getMyId(game){
  const k=_km('activeIdKey');
  return(k&&game?.[k])||window.__SurvevDev?.myId||game?.pRZlU||game?.zYO||game?.m_activeId||getMe(game)?.__id||null;
}
function getWorldContainer(game){
  const k=_km('pixiKey');
  const pixi=k?game?.[k]:null;
  return(pixi?.stage?.children?.[0])||window.__SurvevDev?.world||game?.qtqkB?.stage?.children?.[0]||game?.WNNSH?.stage?.children?.[0]||game?.m_pixi?.stage?.children?.[0]||null;
}
function getObstacles(game){
  const mapK=_km('mapKey'),obstK=_km('obstKey'),poolK=_km('poolKey');
  const mapObj=mapK?game?.[mapK]:null;
  const pool=obstK&&mapObj?mapObj[obstK]:null;
  if(pool&&poolK&&Array.isArray(pool[poolK]))return pool[poolK];
  return window.__SurvevDev?.obstacles??poolArr(game?.ttHlEc?.ZeD??game?.iNj?.wDx??game?.m_map?.m_obstaclePool)??[];
}
function getBuildingPool(game){
  const mapK=_km('mapKey'),buildK=_km('buildKey'),poolK=_km('poolKey');
  const mapObj=mapK?game?.[mapK]:null;
  const pool=buildK&&mapObj?mapObj[buildK]:null;
  if(pool&&poolK&&Array.isArray(pool[poolK]))return pool[poolK];
  return window.__SurvevDev?.buildings??poolArr(game?.ttHlEc?.nDHYkP??game?.iNj?.FMxekW??game?.m_map?.m_buildingPool)??[];
}
function getPlayers(barn){
  const k=_km('playerPoolKey')||_km('poolKey');
  if(k&&barn?.playerPool&&Array.isArray(barn.playerPool[k]))return barn.playerPool[k];
  return window.__SurvevDev?.players??poolArr(barn?.playerPool)??[];
}
function getLoot(game){
  const lootK=_km('lootBarnKey'),poolK=_km('poolKey');
  const lb=lootK?game?.[lootK]:null;
  if(lb&&poolK&&lb.lootPool&&Array.isArray(lb.lootPool[poolK]))return lb.lootPool[poolK];
  return window.__SurvevDev?.loot??poolArr(game?.rDwHRG?.lootPool??game?.xGaQs?.lootPool??game?.m_lootBarn?.lootPool)??[];
}
function getProjectiles(game){
  const projK=_km('projBarnKey'),poolK=_km('poolKey');
  const pb=projK?game?.[projK]:null;
  if(pb&&poolK&&pb.projectilePool&&Array.isArray(pb.projectilePool[poolK]))return pb.projectilePool[poolK];
  return window.__SurvevDev?.projectiles??poolArr(game?.RLl?.projectilePool??game?.wIXF?.projectilePool??game?.m_projectileBarn?.projectilePool)??[];
}
function getPlayerPos(p){
  const k=_km('posKey');
  if(k&&p&&_isWorldPos(p[k]))return p[k];
  return p?.tGUqfM??p?.kYDJg??p?.rvyyT??p?.dMlpwc??p?.iUBbYp??p?.UrupxR??p?.m_pos??p?.pos??null;
}
function getPlayerDir(p){
  const k=_km('dirKey');
  if(k&&p&&_isUnitVec(p[k]))return p[k];
  return p?.SIpSSa??p?.zeq??p?.EZOCU??p?.rdhj??p?.rob??p?.tvwkM??p?.m_dir??null;
}
function isPlayerDead(p){return p?.m_netData?.m_dead??p?.dead??false;}
function isPlayerDowned(p){return p?.m_netData?.m_downed??p?.downed??false;}
function findRe(){
  if(isAppObj(window.__Re))return window.__Re;
  if(isAppObj(window.__SurvevDev?.app))return window.__SurvevDev.app;
  return null;
}

// Auto-finder inject (same as before)
const inject=document.createElement('script');
inject.textContent=`(function(){
  const origCall=Function.prototype.call;
  function isApp(v){try{return v!=null&&typeof v==='object'&&'audioManager'in v&&'pixi'in v&&'game'in v&&'inputBinds'in v;}catch(e){return false;}}
  function def(o,k,g){try{if(o&&!(k in o))Object.defineProperty(o,k,{configurable:true,enumerable:false,get:g});}catch(e){}}
  function arr(p){return p?.aTL??p?.cAGTlU??p?.m_pool??p?.m_getPool?.()??[];}
  function aliasPool(p){def(p,'aTL',function(){return this.cAGTlU??this.m_pool??this.m_getPool?.()??[];});}
  function aliasPlayer(p){
    if(!p||typeof p!=='object')return;
    def(p,'tGUqfM',function(){return this.rvyyT??this.dMlpwc??this.iUBbYp??this.UrupxR??this.m_pos??this.pos??null;});
    def(p,'kYDJg',function(){return this.tGUqfM;});
    def(p,'SIpSSa',function(){return this.EZOCU??this.rdhj??this.rob??this.tvwkM??this.m_dir??null;});
    def(p,'zeq',function(){return this.SIpSSa;});
  }
  function aliasGame(game){
    if(!game||typeof game!=='object')return false;
    def(game,'gAl',function(){return this.DaWsN??this.m_playerBarn;});
    def(game,'DyO',function(){return this.evqDZ??this.m_input;});
    def(game,'agncI',function(){return this.zSsh??this.m_activePlayer;});
    def(game,'pRZlU',function(){return this.zYO??this.m_activeId;});
    def(game,'qtqkB',function(){return this.WNNSH??this.m_pixi;});
    def(game,'rDwHRG',function(){return this.xGaQs??this.m_lootBarn;});
    def(game,'RLl',function(){return this.wIXF??this.m_projectileBarn;});
    def(game,'ttHlEc',function(){const m=this.iNj??this.m_map;return m?{ZeD:m.wDx??m.m_obstaclePool,nDHYkP:m.FMxekW??m.m_buildingPool}:undefined;});
    aliasPool(game.gAl?.playerPool);aliasPool(game.ttHlEc?.ZeD);aliasPool(game.ttHlEc?.nDHYkP);aliasPool(game.rDwHRG?.lootPool);aliasPool(game.RLl?.projectilePool);
    for(const p of arr(game.gAl?.playerPool))aliasPlayer(p);aliasPlayer(game.agncI);
    try{if(!window.__SurvevDev)Object.defineProperty(window,'__SurvevDev',{configurable:true,get(){return{app:window.__Re,game,input:game.DyO,barn:game.gAl,me:game.agncI,myId:game.pRZlU,pixi:game.qtqkB,world:game.qtqkB?.stage?.children?.[0],players:arr(game.gAl?.playerPool),obstacles:arr(game.ttHlEc?.ZeD),buildings:arr(game.ttHlEc?.nDHYkP),loot:arr(game.rDwHRG?.lootPool),projectiles:arr(game.RLl?.projectilePool)}}});}catch(e){}
    return true;
  }
  function aliasApp(app){if(!isApp(app))return false;window.__Re=app;return aliasGame(app.game);}
  if(!isApp(window.__Re)){
    Function.prototype.call=function(thisArg){
      if(!isApp(window.__Re)&&isApp(thisArg)){window.__Re=thisArg;aliasApp(thisArg);Function.prototype.call=origCall;}
      return Reflect.apply(origCall,this,arguments);
    };
    setTimeout(()=>{if(Function.prototype.call!==origCall)Function.prototype.call=origCall;},30000);
  }
  setInterval(()=>{if(isApp(window.__Re))aliasApp(window.__Re);},250);
})();`;
(document.head||document.documentElement).appendChild(inject);inject.remove();

// ============================================================
//  WALLCHECK
// ============================================================
const _WALL_NAMES=['metal_wall_','brick_wall_','concrete_wall_','stone_wall_','container_wall_','_wall_int_','bank_wall_','barn_wall_','cabin_wall_','hut_wall_','house_wall_','mansion_wall_','police_wall_','shack_wall_','outhouse_wall_','teahouse_wall_','warehouse_wall_','silo_','bollard_','sandbags_','hedgehog'];
const _SOFT_NAMES=['tree_','bush_','brush_','crate_','barrel_','refrigerator_','control_panel_','chest_','case_','oven_','bed_','bookshelf_','couch_','table_','drawers_','window','glass_wall_','locker_','deposit_box_','toilet_'];
function isHardWall(obs){
  if(!obs||obs.dead)return false;if(obs.collidable===false)return false;if(obs.isWall===true)return true;
  const n=obs.type||'';for(const w of _WALL_NAMES)if(n.includes(w))return true;
  for(const s of _SOFT_NAMES)if(n.includes(s))return false;if(obs.destructible===false)return true;return false;
}
function _rayHitsAABB(ax,ay,bx,by,mnX,mnY,mxX,mxY){
  const dx=bx-ax,dy=by-ay;let tmin=0,tmax=1;
  if(Math.abs(dx)<1e-9){if(ax<mnX||ax>mxX)return false;}
  else{let t1=(mnX-ax)/dx,t2=(mxX-ax)/dx;if(t1>t2){const tmp=t1;t1=t2;t2=tmp;}tmin=Math.max(tmin,t1);tmax=Math.min(tmax,t2);if(tmin>tmax)return false;}
  if(Math.abs(dy)<1e-9){if(ay<mnY||ay>mxY)return false;}
  else{let t1=(mnY-ay)/dy,t2=(mxY-ay)/dy;if(t1>t2){const tmp=t1;t1=t2;t2=tmp;}tmin=Math.max(tmin,t1);tmax=Math.min(tmax,t2);if(tmin>tmax)return false;}
  return tmin<tmax;
}
function _rayHitsCircle(ax,ay,bx,by,cx,cy,r){
  const dx=bx-ax,dy=by-ay,len2=dx*dx+dy*dy;if(len2<1e-12)return false;
  const ox=cx-ax,oy=cy-ay,t=Math.max(0,Math.min(1,(ox*dx+oy*dy)/len2));
  const px=ax+t*dx,py=ay+t*dy;return(cx-px)*(cx-px)+(cy-py)*(cy-py)<r*r;
}
function hasLOS(game,myPos,tPos){
  if(!cfg.wallcheck||!myPos||!tPos)return true;
  try{
    const obs=getObstacles(game);if(!obs.length)return true;
    const ax=myPos.x,ay=myPos.y,bx=tPos.x,by=tPos.y;
    for(const o of obs){
      if(!o||!o.active||o.dead||!isHardWall(o))continue;const c=o.collider;if(!c)continue;
      if(c.type===1){if(_rayHitsAABB(ax,ay,bx,by,c.min.x,c.min.y,c.max.x,c.max.y))return false;}
      else if(c.type===0){if(_rayHitsCircle(ax,ay,bx,by,c.pos.x,c.pos.y,c.rad))return false;}
    }
    return true;
  }catch(e){return true;}
}

// ============================================================
//  PREDICTION
// ============================================================
const PRED_HIST=12,PRED_MAX_DT=0.25,PRED_MAX_V=18,PRED_EMA=0.55;
const _predState=new Map();
function _solveIntercept(dx,dy,vx,vy,S){
  const a=vx*vx+vy*vy-S*S,b=2*(dx*vx+dy*vy),c=dx*dx+dy*dy;let t;
  if(Math.abs(a)<1e-6){if(Math.abs(b)<1e-6)return null;t=-c/b;}
  else{const disc=b*b-4*a*c;if(disc<0)return null;const sq=Math.sqrt(disc);const t1=(-b-sq)/(2*a),t2=(-b+sq)/(2*a);const v1=t1>1e-3?t1:Infinity,v2=t2>1e-3?t2:Infinity;t=Math.min(v1,v2);}
  if(!isFinite(t)||t<1e-3||t>3.5)return null;return t;
}
function getPredictedWorldPos(target,myWorldPos,bulletSpeed){
  const id=target.__id,now=performance.now();const cur=getPlayerPos(target);if(!cur)return null;
  if(!_predState.has(id))_predState.set(id,{buf:[],vx:0,vy:0});
  const st=_predState.get(id);st.buf.push({x:cur.x,y:cur.y,t:now});
  if(st.buf.length>PRED_HIST)st.buf.shift();if(st.buf.length<2)return null;
  const recent=[];
  for(let i=1;i<st.buf.length;i++){
    const a=st.buf[i-1],b=st.buf[i],dt=(b.t-a.t)/1000;
    if(dt<=0||dt>PRED_MAX_DT)continue;if(Math.hypot((b.x-a.x)/dt,(b.y-a.y)/dt)>PRED_MAX_V*2.5)continue;
    recent.push({x:b.x,y:b.y,t:b.t/1000});
  }
  if(!recent.length)return null;const N=recent.length;
  let sw=0,swt=0,swt2=0,swx=0,swy=0,swxt=0,swyt=0;
  for(let i=0;i<N;i++){const w=Math.exp((i+1)/N),{x,y,t}=recent[i];sw+=w;swt+=w*t;swt2+=w*t*t;swx+=w*x;swy+=w*y;swxt+=w*x*t;swyt+=w*y*t;}
  const denom=sw*swt2-swt*swt;if(Math.abs(denom)<1e-12)return null;
  let rvx=(sw*swxt-swt*swx)/denom,rvy=(sw*swyt-swt*swy)/denom;
  const spd=Math.hypot(rvx,rvy);if(spd>PRED_MAX_V){const sc=PRED_MAX_V/spd;rvx*=sc;rvy*=sc;}
  st.vx=PRED_EMA*rvx+(1-PRED_EMA)*st.vx;st.vy=PRED_EMA*rvy+(1-PRED_EMA)*st.vy;
  const t=_solveIntercept(cur.x-myWorldPos.x,cur.y-myWorldPos.y,st.vx,st.vy,bulletSpeed);
  if(t===null)return null;return{x:cur.x+st.vx*t,y:cur.y+st.vy*t};
}

// ============================================================
//  AIM / MAGNET / MISC
// ============================================================
const FIRE_GATE_RAD=2.5*(Math.PI/180);
function applyAimLock(tsx,tsy,input){
  if(!input)return;let fx=tsx,fy=tsy;
  if(cfg.smoothAim){
    const f=0.04+(cfg.smoothAmount/100)*0.31;
    if(_smoothSx===null){_smoothSx=input.mousePos.x;_smoothSy=input.mousePos.y;}
    _smoothSx+=(tsx-_smoothSx)*f;_smoothSy+=(tsy-_smoothSy)*f;fx=_smoothSx;fy=_smoothSy;
  }else{_smoothSx=null;_smoothSy=null;}
  input.mousePos.x=fx;input.mousePos.y=fy;
}
function clearMagnetKeys(input){
  if(!input)return;
  if(_magnetKeys.w){input.keys[KEY_W]=false;_magnetKeys.w=false;}
  if(_magnetKeys.a){input.keys[KEY_A]=false;_magnetKeys.a=false;}
  if(_magnetKeys.s){input.keys[KEY_S]=false;_magnetKeys.s=false;}
  if(_magnetKeys.d){input.keys[KEY_D]=false;_magnetKeys.d=false;}
}
function applyMagnet(input,me,msx,msy,tsx,tsy,distStuds){
  if(!input||distStuds>6){clearMagnetKeys(input);return false;}
  const dx=tsx-msx,dy=tsy-msy,len=Math.hypot(dx,dy);
  if(len>0.5){const nx=dx/len,ny=dy/len,thr=0.3;input.keys[KEY_W]=ny<-thr;_magnetKeys.w=input.keys[KEY_W];input.keys[KEY_S]=ny>thr;_magnetKeys.s=input.keys[KEY_S];input.keys[KEY_A]=nx<-thr;_magnetKeys.a=input.keys[KEY_A];input.keys[KEY_D]=nx>thr;_magnetKeys.d=input.keys[KEY_D];}
  input.mousePos.x=tsx;input.mousePos.y=tsy;
  const dir=getPlayerDir(me);
  if(dir&&typeof dir.x==='number'){const ga=Math.atan2(-dir.y,dir.x),ta=Math.atan2(tsy-msy,tsx-msx);let err=Math.abs(ga-ta);if(err>Math.PI)err=Math.PI*2-err;if(err<=FIRE_GATE_RAD){input.mouseButtons[0]=true;setTimeout(()=>{if(input?.mouseButtons)input.mouseButtons[0]=false;},30);}}
  return true;
}
function doLayerSpoof(game){const me=getMe(game);if(!me||typeof me.layer==='undefined')return;try{if(!_layerSpoofActive){_layerOrig=me.layer;_layerDescriptor=Object.getOwnPropertyDescriptor(me,'layer')||Object.getOwnPropertyDescriptor(Object.getPrototypeOf(me),'layer');const spoof=me.layer===0?1:0;Object.defineProperty(me,'layer',{configurable:true,get:()=>spoof,set(){}});if(me.container)me.container.alpha=0.5;_layerSpoofActive=true;}}catch(e){}}
function undoLayerSpoof(game){const me=getMe(game);if(!me)return;try{if(_layerSpoofActive){if(_layerDescriptor)Object.defineProperty(me,'layer',_layerDescriptor);else if(_layerOrig!==null)me.layer=_layerOrig;if(me.container)me.container.alpha=1;_layerSpoofActive=false;_layerOrig=null;_layerDescriptor=null;}}catch(e){_layerSpoofActive=false;}}
let _mapHookInstalled=false;
function installMapHighlights(){if(_mapHookInstalled||!cfg.mapHighlights)return;try{const origSort=Array.prototype.sort;Array.prototype.sort=function(...args){try{if(cfg.mapHighlights&&this.length>0&&this[0]?.obj?.ori!=null){this.forEach(e=>{if(!e||!e.obj)return;const col=MAP_COLORS[e.obj.type];const sc=MAP_SCALES[e.obj.type];if(col!=null&&sc!=null&&e.shapes){e.shapes.forEach(s=>{s.color=col;s.scale=sc;});e.zIdx=999;}});}}catch(e2){}return origSort.apply(this,args);};_mapHookInstalled=true;}catch(e){}}
const AUTO_DOOR_INTERACT_RANGE=3.5;const _doorPressTimes=new Map();
function _doorInRange(o,myPos){if(!o||!myPos)return false;const extra=(o.door?.interactionRad??AUTO_DOOR_INTERACT_RANGE)+1.15;const c=o.collider;if(c?.type===1&&c.min&&c.max){const px=Math.max(c.min.x,Math.min(c.max.x,myPos.x)),py=Math.max(c.min.y,Math.min(c.max.y,myPos.y));return Math.hypot(myPos.x-px,myPos.y-py)<=extra+0.25;}if(c?.type===0&&c.pos){return Math.hypot(c.pos.x-myPos.x,c.pos.y-myPos.y)<=((c.rad??0)+extra+0.25);}const p=o.door?.closedPos??o.pos;return p&&Math.hypot(p.x-myPos.x,p.y-myPos.y)<=extra+1.5;}
function _pressInteract(input,game){if(input?.keys){input.keysOld[70]=false;input.keys[70]=true;setTimeout(()=>{if(input?.keys){input.keys[70]=false;input.keysOld[70]=false;}},90);}try{const uiMgr=Object.values(game||{}).find(v=>v&&typeof v==='object'&&'interactionTouched'in v);if(uiMgr){uiMgr.interactionTouched=true;setTimeout(()=>{if(uiMgr)uiMgr.interactionTouched=false;},90);}}catch(e){}}
let _lastDoorTime=0;
function tickAutoDoor(game,input){if(!cfg.autoDoor||!input)return;const myPos=getPlayerPos(getMe(game));if(!myPos)return;const now=Date.now();for(const o of getObstacles(game)){if(!o?.active||!o.isDoor||!o.pos)continue;if(o.door?.open===true){_doorPressTimes.delete(o.__id);continue;}if(o.door?.locked===true||o.door?.canUse===false)continue;if(!_doorInRange(o,myPos))continue;const last=_doorPressTimes.get(o.__id)??0;if(now-last<260)continue;_doorPressTimes.set(o.__id,now);_lastDoorTime=Date.now();_pressInteract(input,game);break;}}
const LOOT_SKIP=new Set(['gun','melee','chest','helmet','scope','backpack','outfit','perk','xp']);
const AUTO_LOOT_RANGE=2.5,AUTO_LOOT_INTERVAL=150;let _lastLootTime=0;
const _LOOT_CAT={bandage:'heal',healthkit:'heal',soda:'boost',painkiller:'boost',pills:'boost',adren:'boost','9mm':'ammo','762mm':'ammo','556mm':'ammo','50AE':'ammo','12gauge':'ammo','308sub':'ammo','45acp':'ammo','40mm':'ammo',flare:'ammo',frag:'throwable',mirv:'throwable',smoke:'throwable',strobe:'throwable',potato:'throwable',martyr:'throwable',chest01:'chest',chest02:'chest',chest03:'chest',chest04:'chest',helmet01:'helmet',helmet02:'helmet',helmet03:'helmet',helmet04:'helmet','1xscope':'scope','2xscope':'scope','4xscope':'scope','8xscope':'scope','15xscope':'scope',pan:'melee',crowbar:'melee',katana:'melee',fists:'melee',naginata:'melee'};
function _lootCat(type){if(!type)return'gun';if(_LOOT_CAT[type])return _LOOT_CAT[type];const t=type.toLowerCase();if(t.includes('scope'))return'scope';if(t.includes('helmet'))return'helmet';if(t.includes('chest')||t.includes('vest'))return'chest';if(t.includes('pack')||t.includes('backpack'))return'backpack';if(/mm$|gauge$|acp$|sub$|ae$/.test(t))return'ammo';return'gun';}
function tickAutoLoot(game,input){if(!cfg.autoLoot||!input)return;const myPos=getPlayerPos(getMe(game));if(!myPos)return;const now=Date.now();if(now-_lastLootTime<AUTO_LOOT_INTERVAL)return;if(now-_lastDoorTime<500)return;const lootBarnK=_km('lootBarnKey');const lb=lootBarnK?game[lootBarnK]:null;const closest=(lb??game.rDwHRG??game.xGaQs??game.m_lootBarn)?.closestLoot;if(closest?.active&&!LOOT_SKIP.has(_lootCat(closest.type))){_lastLootTime=now;input.keys[70]=true;setTimeout(()=>{if(input?.keys)input.keys[70]=false;},50);return;}let bestDist=AUTO_LOOT_RANGE,bestLoot=null;for(const l of getLoot(game)){if(!l?.active||!l.pos)continue;if(l.ownerId&&l.ownerId!==getMyId(game))continue;if(LOOT_SKIP.has(_lootCat(l.type)))continue;const d=Math.hypot(l.pos.x-myPos.x,l.pos.y-myPos.y);if(d<bestDist){bestDist=d;bestLoot=l;}}if(bestLoot){_lastLootTime=now;input.keys[70]=true;setTimeout(()=>{if(input?.keys)input.keys[70]=false;},50);}}
function hookZoom(){if(_zoomHooked)return true;const game=getGame();if(!game)return false;const player=getMe(game);if(!player)return false;const proto=Object.getPrototypeOf(player);const key=Object.getOwnPropertyNames(proto).find(k=>{try{const f=proto[k]?.toString()??'';return f.includes('mobile')&&f.length<300&&typeof proto[k]==='function';}catch{return false;}});if(!key)return false;const orig=proto[key];proto[key]=function(...a){return orig.apply(this,a)*_zoomMultiplier;};_zoomHooked=true;return true;}
function setZoom(m){if(!settings.zoomEnabled)return;if(!_zoomHooked)hookZoom();_zoomMultiplier=m;updateZoomUI();}
function updateZoomUI(){const el=document.getElementById('_vmZoomVal');if(el)el.textContent=(1/_zoomMultiplier).toFixed(2)+'x';}
const MACRO_STEPS=[inp=>{if(inp?.mouseButtons){inp.mouseButtons[0]=true;setTimeout(()=>{if(inp?.mouseButtons)inp.mouseButtons[0]=false;},25);}},inp=>{if(inp?.keys){inp.keys[51]=true;setTimeout(()=>{if(inp?.keys)inp.keys[51]=false;},25);}},inp=>{if(inp?.keys){inp.keys[52]=true;setTimeout(()=>{if(inp?.keys)inp.keys[52]=false;},25);}}];
const macro={running:false,stepIdx:0,delay:100,cycles:0,_timeout:null,tick(){if(!this.running)return;const inp=getInput(getGame())??null;if(inp)MACRO_STEPS[this.stepIdx](inp);this.stepIdx++;if(this.stepIdx>=MACRO_STEPS.length){this.stepIdx=0;this.cycles++;}this._timeout=setTimeout(()=>this.tick(),Math.max(10,this.delay));},start(){if(!settings.throwerEnabled||this.running)return;this.running=true;this.stepIdx=0;this.cycles=0;this.tick();updateMacroUI();},stop(){this.running=false;clearTimeout(this._timeout);this.stepIdx=0;updateMacroUI();},toggle(){this.running?this.stop():this.start();},setDelay(ms){this.delay=Math.max(10,Math.min(200,ms));const el=document.getElementById('_vmMacroDelay');if(el)el.textContent=this.delay+'ms';const sl=document.getElementById('_vmMacroSlider');if(sl)sl.value=this.delay;}};
window.SurvMacro=macro;
function updateMacroUI(){const el=document.getElementById('_vmtgl-thrower');if(el)el.classList.toggle('on',macro.running);}
function applyXray(buildings){for(const b of buildings){if(!b||!b.active)continue;if(b.ceiling)b.ceiling.fadeAlpha=0;for(const img of(b.imgs||[]))if(img&&img.isCeiling&&img.sprite)img.sprite.alpha=0;}}
function tickXray(game){if(!cfg.xray)return;applyXray(getBuildingPool(game));for(const o of getObstacles(game)){if(!o||!o.active)continue;const t=(typeof o.type==='string'?o.type:'').toLowerCase();if(/^tree|^bush|^hedge|^flower|^vine/.test(t)){if(o.sprite)o.sprite.alpha=0.15;if(o.img?.sprite)o.img.sprite.alpha=0.15;}}}
function restoreXray(game){for(const b of getBuildingPool(game)){if(!b)continue;if(b.ceiling)b.ceiling.fadeAlpha=1;for(const img of(b.imgs||[]))if(img&&img.isCeiling&&img.sprite)img.sprite.alpha=img.sprite.imgAlpha??1;}}
function restoreXrayObs(game){for(const o of getObstacles(game)){if(!o||!o.active)continue;const t=(typeof o.type==='string'?o.type:'').toLowerCase();if(/^tree|^bush|^hedge|^flower|^vine/.test(t)){if(o.sprite)o.sprite.alpha=1;if(o.img?.sprite)o.img.sprite.alpha=1;}}}

// ============================================================
//  STATS
// ============================================================
const modeMap={1:'Solo',2:'Duo',3:'Trio',4:'Squad'};
function timeAgo(d){const s=Math.floor((Date.now()-new Date(d))/1000);return s<60?`${s}s`:s<3600?`${Math.floor(s/60)}m`:s<86400?`${Math.floor(s/3600)}h`:`${Math.floor(s/86400)}d`;}
function fetchStats(slug){
  if(!slug)return;
  const box=document.getElementById('_vmStatsBox'),hist=document.getElementById('_vmHistoryBox');
  if(box)box.innerHTML='<span style="color:#888;font-size:12px">loading...</span>';
  if(hist)hist.innerHTML='<span style="color:#888;font-size:12px">loading...</span>';
  fetch('https://api.survev.io/api/user_stats',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({slug,interval:'alltime',mapIdFilter:'-1'})})
  .then(r=>r.json()).then(d=>{if(!box)return;if(!d||d.banned){box.innerHTML='<span style="color:#e87a7a">not found</span>';return;}const kpg=d.games>0?(d.kills/d.games).toFixed(2):'0.00';const wPct=d.games>0?((d.wins/d.games)*100).toFixed(1):'0.0';box.innerHTML=`<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:6px;text-align:center">${[['WINS',d.wins??0],['KILLS',d.kills??0],['GAMES',d.games??0],['K/G',kpg],['WIN%',wPct+'%']].map(([l,v])=>`<div style="padding:9px 4px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:8px"><div style="font-size:17px;font-weight:700;color:#ebecf0">${v}</div><div style="font-size:9px;color:#7b7f88;letter-spacing:1px;margin-top:2px">${l}</div></div>`).join('')}</div>`;}).catch(()=>{if(box)box.innerHTML='<span style="color:#e87a7a">error</span>';});
  fetch('https://api.survev.io/api/match_history',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({slug,offset:0,count:10,teamModeFilter:7})})
  .then(r=>r.json()).then(matches=>{if(!hist)return;if(!Array.isArray(matches)||!matches.length){hist.innerHTML='<span style="color:#555;font-size:12px">no matches</span>';return;}hist.innerHTML=`<div style="border-radius:9px;overflow:hidden;border:1px solid rgba(255,255,255,.07)"><div style="display:grid;grid-template-columns:55px 55px 1fr 70px 42px;gap:6px;padding:7px 14px;background:rgba(255,255,255,.03)">${['RANK','MODE','KILLS','DMG','AGO'].map(h=>`<span style="font-size:9px;color:#7b7f88;letter-spacing:1px">${h}</span>`).join('')}</div>${matches.map(m=>{const rCol=m.rank===1?'#5bc470':m.rank<=3?'#e8c060':'#555';const mCol=m.team_count===1?'#c8a8ff':m.team_count===2?'#80c8ff':m.team_count===3?'#80e8a0':'#ffb060';return`<div style="display:grid;grid-template-columns:55px 55px 1fr 70px 42px;gap:6px;align-items:center;padding:8px 14px;border-bottom:1px solid rgba(255,255,255,.05);cursor:pointer" onclick="window._vmShowMatch('${m.guid}','${slug}')"><span style="font-size:11px;font-weight:700;color:${rCol}">${m.rank===1?'1st':'#'+m.rank}</span><span style="font-size:11px;font-weight:600;color:${mCol}">${modeMap[m.team_count]||'?'}</span><span style="font-size:11px;color:#9699a3">${m.kills??0}k/${m.team_kills??0}tk</span><span style="font-size:11px;color:#7b7f88">${Math.round((m.damage_dealt??0)/1000*10)/10}k</span><span style="font-size:11px;color:#7b7f88;text-align:right">${timeAgo(m.end_time)}</span></div>`;}).join('')}</div>`;}).catch(()=>{if(hist)hist.innerHTML='<span style="color:#e87a7a">error</span>';});
}
window._vmShowMatch=function(guid,slug){const detail=document.getElementById('_vmMatchDetail');if(!detail)return;detail.style.display='block';detail.innerHTML='<div style="color:#7b7f88;padding:12px;font-size:12px">loading...</div>';fetch('https://api.survev.io/api/match_data',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({gameId:guid})}).then(r=>r.json()).then(players=>{if(!Array.isArray(players)||!players.length){detail.innerHTML='<span style="color:#e87a7a;padding:12px">no data</span>';return;}const rows=players.map(p=>{const isMe=p.slug===slug;const t=Math.round(p.time_alive??0);const rCol=p.rank===1?'#5bc470':p.rank<=3?'#e8c060':'#555';return`<div style="display:grid;grid-template-columns:40px 1fr 36px 64px 64px 56px;gap:5px;align-items:center;padding:7px 14px;border-bottom:1px solid rgba(255,255,255,.05);background:${isMe?'rgba(91,196,112,0.08)':''}"><span style="font-size:11px;font-weight:700;color:${rCol}">#${p.rank}</span><span style="font-size:11px;font-weight:${isMe?700:400};color:${isMe?'#5bc470':'#9699a3'};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${p.slug||p.username||'?'}</span><span style="font-size:11px;color:#9699a3">${p.kills??0}</span><span style="font-size:11px;color:#7b7f88">${Math.round(p.damage_dealt??0)}</span><span style="font-size:11px;color:#7b7f88">${Math.round(p.damage_taken??0)}</span><span style="font-size:11px;color:#7b7f88">${Math.floor(t/60)}:${String(t%60).padStart(2,'0')}</span></div>`;}).join('');detail.innerHTML=`<div style="display:flex;justify-content:space-between;align-items:center;padding:9px 14px;background:rgba(255,255,255,.03);border-bottom:1px solid rgba(255,255,255,.07)"><span style="font-size:10px;color:#7b7f88;letter-spacing:.5px">MATCH DETAIL</span><span style="cursor:pointer;color:#7b7f88;padding:3px 9px" onclick="document.getElementById('_vmMatchDetail').style.display='none'">x</span></div><div style="display:grid;grid-template-columns:40px 1fr 36px 64px 64px 56px;gap:5px;padding:6px 14px;background:rgba(255,255,255,.02)">${['#','PLAYER','K','DEALT','TAKEN','TIME'].map(h=>`<span style="font-size:9px;color:#7b7f88;letter-spacing:.5px">${h}</span>`).join('')}</div><div style="max-height:240px;overflow-y:auto">${rows}</div>`;}).catch(()=>{detail.innerHTML='<span style="color:#e87a7a;padding:12px">error</span>';});};

// ============================================================
//  CSS
// ============================================================
const uiStyle=document.createElement('style');
uiStyle.textContent=`
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root{--ui-shadow:0 40px 120px rgba(0,0,0,.82);--ui-radius:18px;}
#voidUI,#voidUI *{box-sizing:border-box;}
#voidUI{position:fixed;display:none;z-index:999999;font-family:Inter,system-ui,sans-serif;color:#ebecf0;border-radius:var(--ui-radius);overflow:hidden;background:linear-gradient(180deg,rgba(24,25,29,.62),rgba(13,14,17,.80));backdrop-filter:blur(38px) saturate(135%);box-shadow:0 0 0 1px rgba(255,255,255,.05),var(--ui-shadow),inset 0 1px 0 rgba(255,255,255,.04);transform-origin:50% 30%;will-change:left,top,width,height;}
#voidUI.open{display:block;animation:voidPop .22s cubic-bezier(.2,.85,.2,1);}
#voidUI.dragging{user-select:none;cursor:grabbing;}
#voidUI::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:.08;background-image:linear-gradient(rgba(255,255,255,.16) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.16) 1px,transparent 1px);background-size:24px 24px;mask-image:linear-gradient(180deg,rgba(0,0,0,.6),rgba(0,0,0,.15));}
#voidFrame{position:relative;width:100%;height:100%;}
#voidBg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;opacity:.95;}
#cursorLight{position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle 240px at var(--mx,50%) var(--my,20%),rgba(255,255,255,.085),transparent 62%);mix-blend-mode:screen;opacity:.75;}
#voidNoise{position:absolute;inset:0;pointer-events:none;background-image:radial-gradient(circle at 15% 20%,rgba(255,255,255,.03) 0,transparent 22%),radial-gradient(circle at 85% 12%,rgba(255,255,255,.03) 0,transparent 18%),radial-gradient(circle at 70% 80%,rgba(255,255,255,.025) 0,transparent 20%);mix-blend-mode:screen;opacity:.8;}
#voidSidebar{position:absolute;left:0;top:0;bottom:0;width:188px;background:rgba(8,9,11,.58);border-right:1px solid rgba(255,255,255,.05);backdrop-filter:blur(8px);}
#brand{height:52px;display:flex;align-items:center;padding:0 16px;font-size:12px;font-weight:700;letter-spacing:1.6px;border-bottom:1px solid rgba(255,255,255,.04);background:rgba(255,255,255,.02);}
#brand span{color:#fff;opacity:.88;}
#sidebarTabs{position:relative;padding:12px 10px;display:flex;flex-direction:column;gap:4px;}
#tabIndicator{position:absolute;left:10px;top:12px;width:calc(100% - 20px);height:40px;border-radius:11px;background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.035));border:1px solid rgba(255,255,255,.055);box-shadow:inset 0 1px 0 rgba(255,255,255,.04);pointer-events:none;transition:transform .22s cubic-bezier(.2,.85,.2,1);}
.tab{display:flex;align-items:center;justify-content:space-between;height:40px;padding:0 12px;border-radius:11px;color:#7b7f88;cursor:pointer;transition:.18s ease;position:relative;z-index:1;letter-spacing:1.7px;font-size:10px;font-weight:700;}
.tab:hover{color:#fff;transform:translateX(2px);}
.tab.active{color:#fff;}
.tab.active::before{content:"";position:absolute;left:-10px;top:8px;bottom:8px;width:2px;border-radius:2px;background:rgba(255,255,255,.92);}
#shell{position:absolute;left:188px;top:0;right:0;bottom:0;display:flex;flex-direction:column;min-width:0;}
#topbar{height:52px;display:flex;align-items:center;gap:12px;padding:0 14px 0 18px;background:rgba(255,255,255,.028);border-bottom:1px solid rgba(255,255,255,.05);cursor:grab;touch-action:none;min-width:0;}
#topbar:active{cursor:grabbing;}
#headline{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
#headline .title{font-size:13px;font-weight:700;letter-spacing:.8px;}
#headline .sub{font-size:10px;color:#8f929b;letter-spacing:1.6px;}
#topRight{flex:none;display:flex;align-items:center;gap:10px;margin-left:auto;}
#live{display:flex;align-items:center;gap:8px;color:#a5a8b2;font-size:10px;letter-spacing:1.7px;font-weight:700;}
#live .dot{width:7px;height:7px;border-radius:50%;background:#fff;box-shadow:0 0 0 4px rgba(255,255,255,.08),0 0 14px rgba(255,255,255,.35);}
#discordBtn{height:22px;padding:0 10px;border:1px solid rgba(88,101,242,.35);border-radius:999px;background:linear-gradient(180deg,rgba(88,101,242,.92),rgba(66,82,220,.92));color:#fff;font-size:9px;font-weight:800;letter-spacing:1px;cursor:pointer;box-shadow:0 8px 18px rgba(88,101,242,.18),inset 0 1px 0 rgba(255,255,255,.12);transition:transform .18s,filter .18s;}
#discordBtn:hover{transform:translateY(-1px) scale(1.03);filter:brightness(1.08);}
#controls{flex:none;display:flex;align-items:center;gap:8px;}
.ctrl{width:28px;height:28px;border-radius:9px;border:1px solid rgba(255,255,255,.06);background:rgba(255,255,255,.03);color:#eef0f5;cursor:pointer;transition:.18s;display:grid;place-items:center;font-size:12px;}
.ctrl:hover{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.13);transform:translateY(-1px);}
#toolbar{display:flex;align-items:center;gap:10px;padding:9px 14px;border-bottom:1px solid rgba(255,255,255,.045);background:rgba(255,255,255,.012);min-width:0;flex-wrap:wrap;}
#statusChip,#fpsChip,#msChip{padding:6px 10px;border-radius:10px;background:rgba(255,255,255,.025);border:1px solid rgba(255,255,255,.04);color:#7b7f88;font-size:10px;letter-spacing:1.3px;font-weight:700;white-space:nowrap;}
#statusChip{background:rgba(255,255,255,.035);color:#d9dbe2;}
.savecfg-btn{display:flex;align-items:center;justify-content:center;gap:5px;padding:6px 13px;border-radius:10px;background:linear-gradient(180deg,rgba(91,196,112,.18),rgba(60,160,80,.08));border:1px solid rgba(91,196,112,.3);color:#5bc470;font:700 10px Inter,sans-serif;letter-spacing:1px;cursor:pointer;white-space:nowrap;transition:.18s;}
.savecfg-btn:hover{background:linear-gradient(180deg,rgba(91,196,112,.28),rgba(60,160,80,.14));transform:translateY(-1px);}
#pages{position:relative;flex:1;overflow:hidden;min-width:0;}
.page{position:absolute;inset:0;padding:14px 16px;overflow:auto;opacity:0;transform:translateY(8px);pointer-events:none;transition:.18s ease;}
.page.active{opacity:1;transform:translateY(0);pointer-events:auto;}
.pgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;}
.pgrid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;}
.psec{font-size:10px;letter-spacing:2px;color:#7b7f88;text-transform:uppercase;margin-bottom:8px;padding-bottom:5px;border-bottom:1px solid rgba(255,255,255,.06);}
.pcard{border-radius:12px;background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.03));border:1px solid rgba(255,255,255,.06);box-shadow:inset 0 1px 0 rgba(255,255,255,.04);padding:12px 14px;transition:.18s;}
.pcard:hover{border-color:rgba(255,255,255,.12);}
.ptgl{display:flex;align-items:center;justify-content:space-between;height:38px;padding:0 12px;border-radius:10px;background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.03));border:1px solid rgba(255,255,255,.06);cursor:pointer;transition:.18s;}
.ptgl:hover{transform:translateY(-1px);border-color:rgba(255,255,255,.12);}
.ptgl.on{background:linear-gradient(180deg,rgba(255,255,255,.10),rgba(255,255,255,.05));border-color:rgba(255,255,255,.16);}
.ptgl .tlbl{font-size:10px;font-weight:700;letter-spacing:1.5px;color:#7b7f88;}
.ptgl.on .tlbl{color:#fff;}
.ptgl .tpip{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.2);transition:.18s;}
.ptgl.on .tpip{background:#fff;box-shadow:0 0 6px rgba(255,255,255,.5);}
.ptgl.red.on{background:linear-gradient(180deg,rgba(255,80,80,.12),rgba(255,60,60,.06));border-color:rgba(255,80,80,.25);}.ptgl.red.on .tlbl{color:#ff6464;}.ptgl.red.on .tpip{background:#ff6464;box-shadow:0 0 6px rgba(255,100,100,.5);}
.ptgl.yellow.on{background:linear-gradient(180deg,rgba(255,200,60,.12),rgba(255,180,40,.06));border-color:rgba(255,200,60,.25);}.ptgl.yellow.on .tlbl{color:#ffc43c;}.ptgl.yellow.on .tpip{background:#ffc43c;box-shadow:0 0 6px rgba(255,200,60,.5);}
.ptgl.blue.on{background:linear-gradient(180deg,rgba(80,160,255,.12),rgba(60,140,255,.06));border-color:rgba(80,160,255,.25);}.ptgl.blue.on .tlbl{color:#58a8ff;}.ptgl.blue.on .tpip{background:#58a8ff;box-shadow:0 0 6px rgba(80,160,255,.5);}
.ptgl.purple.on{background:linear-gradient(180deg,rgba(168,85,247,.12),rgba(147,51,234,.06));border-color:rgba(168,85,247,.35);}.ptgl.purple.on .tlbl{color:#a855f7;}.ptgl.purple.on .tpip{background:#a855f7;box-shadow:0 0 6px rgba(168,85,247,.5);}
.ptgl.green.on{background:linear-gradient(180deg,rgba(91,196,112,.12),rgba(60,160,80,.06));border-color:rgba(91,196,112,.35);}.ptgl.green.on .tlbl{color:#5bc470;}.ptgl.green.on .tpip{background:#5bc470;box-shadow:0 0 6px rgba(91,196,112,.5);}
.zrow{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);border-radius:10px;gap:8px;}
.zbtn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);border-radius:6px;font:700 14px Inter,sans-serif;color:#eef0f5;width:28px;height:26px;cursor:pointer;transition:all .1s;}
.zbtn:hover{background:rgba(255,255,255,.12);transform:scale(1.08);}
.zval{font-size:13px;font-weight:700;color:#fff;min-width:44px;text-align:center;}
.mslider{width:100%;appearance:none;height:3px;border-radius:999px;background:rgba(255,255,255,.10);outline:none;margin:8px 0;}
.mslider::-webkit-slider-thumb{appearance:none;width:13px;height:13px;border-radius:50%;background:#fff;border:none;box-shadow:0 0 0 3px rgba(255,255,255,.12);cursor:pointer;}
.srow{display:flex;align-items:center;justify-content:space-between;padding:10px 13px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.05);border-radius:9px;margin-bottom:5px;}
.srow-left{display:flex;flex-direction:column;gap:3px;}
.srow-title{font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:#d0d2db;}
.srow-sub{font-size:10px;color:#7b7f88;}
.kbinput{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:5px 12px;font:700 12px Inter,sans-serif;color:#fff;width:60px;text-align:center;cursor:pointer;outline:none;transition:all .15s;}
.kbinput.listening{border-color:rgba(88,101,242,.8);background:rgba(88,101,242,.12);color:#8899ff;}
.step{font-size:11px;color:#9699a3;line-height:1.7;padding:10px 13px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.05);border-radius:9px;margin-bottom:6px;}
.step b{color:#ebecf0;}
.step.warn{background:rgba(255,180,40,.04);border-color:rgba(255,180,40,.12);color:#9a7c24;}
.slugrow{display:flex;gap:7px;margin-bottom:10px;}
.slugrow input{flex:1;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:6px;padding:7px 11px;font:12px Inter,sans-serif;color:#ebecf0;outline:none;}
.slugrow input:focus{border-color:rgba(88,101,242,.6);}
.slugrow button{background:rgba(88,101,242,.85);color:#fff;border:none;border-radius:6px;padding:7px 13px;font:700 10px Inter,sans-serif;cursor:pointer;transition:background .15s;}
.slugrow button:hover{background:rgba(88,101,242,1);}
#_vmMatchDetail{display:none;border-radius:9px;overflow:hidden;border:1px solid rgba(255,255,255,.07);background:rgba(0,0,0,.3);margin-top:10px;}
#toastHost{position:absolute;right:14px;bottom:14px;display:flex;flex-direction:column;gap:8px;pointer-events:none;z-index:10;}
.toast{min-width:160px;max-width:260px;padding:9px 12px;border-radius:10px;background:rgba(14,15,18,.88);border:1px solid rgba(255,255,255,.08);box-shadow:0 20px 50px rgba(0,0,0,.45);color:#eef0f5;font-size:11px;animation:toastIn .18s ease;}
#resizer{position:absolute;right:5px;bottom:5px;width:16px;height:16px;cursor:nwse-resize;border-radius:4px;background:linear-gradient(135deg,transparent 50%,rgba(255,255,255,.26) 50%),linear-gradient(135deg,transparent 68%,rgba(255,255,255,.14) 68%);opacity:.9;}
/* FEEDBACK */
.fb-star{font-size:22px;cursor:pointer;color:#555;transition:color .12s;user-select:none;}
.fb-star.sel{color:#ffc43c;}
.fb-submit-btn{background:linear-gradient(180deg,rgba(91,196,112,.9),rgba(60,160,80,.9));color:#fff;border:none;border-radius:8px;padding:9px 0;font:700 11px Inter,sans-serif;letter-spacing:.8px;cursor:pointer;width:100%;transition:.18s;margin-top:8px;}
.fb-submit-btn:hover{filter:brightness(1.1);transform:translateY(-1px);}
.fb-submit-btn:disabled{opacity:.5;cursor:default;transform:none;filter:none;}
.fb-textarea{width:100%;min-height:90px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:9px 11px;font:12px/1.5 Inter,sans-serif;color:#ebecf0;outline:none;resize:vertical;}
.fb-textarea:focus{border-color:rgba(91,196,112,.5);}
@keyframes voidPop{from{opacity:0;transform:scale(.97) translateY(6px);}to{opacity:1;transform:scale(1) translateY(0);}}
@keyframes toastIn{from{opacity:0;transform:translateY(8px) scale(.98);}to{opacity:1;transform:translateY(0) scale(1);}}
::-webkit-scrollbar{width:8px;}
::-webkit-scrollbar-thumb{background:rgba(255,255,255,.10);border-radius:999px;border:2px solid transparent;background-clip:padding-box;}
::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.16);background-clip:padding-box;}
`;
document.head.appendChild(uiStyle);

// ============================================================
//  HTML
// ============================================================
const ui=document.createElement('div');ui.id='voidUI';
ui.innerHTML=`
<div id="voidFrame">
  <canvas id="voidBg"></canvas>
  <div id="cursorLight"></div>
  <div id="voidNoise"></div>
  <aside id="voidSidebar">
    <div id="brand"><span>Void Bacon+</span></div>
    <div id="sidebarTabs">
      <div id="tabIndicator"></div>
      <div class="tab active" data-tab="combat">COMBAT</div>
      <div class="tab" data-tab="visual">VISUAL</div>
      <div class="tab" data-tab="world">WORLD</div>
      <div class="tab" data-tab="keybinds">KEYS</div>
      <div class="tab" data-tab="stats">STATS</div>
      <div class="tab" data-tab="feedback">FEEDBACK ⭐</div>
      <div class="tab" data-tab="updates">UPDATES</div>
    </div>
  </aside>
  <section id="shell">
    <div id="topbar">
      <div id="headline">
        <div class="title">VOID BACON+ v3.4</div>
        <div class="sub">INSERT / ESC · drag · resize</div>
      </div>
      <div id="topRight">
        <div id="live"><span class="dot"></span> LIVE</div>
        <button id="discordBtn" type="button">DISCORD</button>
      </div>
      <div id="controls">
        <button class="ctrl" data-act="max">&#9634;</button>
        <button class="ctrl" data-act="save">&#10226;</button>
        <button class="ctrl" data-act="close">&#215;</button>
      </div>
    </div>
    <div id="toolbar">
      <div id="statusChip">READY</div>
      <div id="fpsChip">-- FPS</div>
      <div id="msChip">-- MS</div>
      <button class="savecfg-btn" onclick="window._vmSaveCfg()">💾 SAVE SETTINGS</button>
    </div>
    <div id="pages">

      <!-- COMBAT -->
      <div class="page active" data-page="combat">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
          <div>
            <div class="psec">AIM ASSIST</div>
            <div style="display:flex;flex-direction:column;gap:7px;margin-bottom:12px">
              <div class="ptgl red" id="_vmtgl-lockOn" onclick="window._vmFlip('lockOn')"><span class="tlbl">Lock-On [T]</span><div class="tpip"></div></div>
              <div class="ptgl purple" id="_vmtgl-predict" onclick="window._vmFlip('predict')"><span class="tlbl">PREDICT (needs Lock-On)</span><div class="tpip"></div></div>
              <div class="ptgl blue" id="_vmtgl-smoothAim" onclick="window._vmFlip('smoothAim')"><span class="tlbl">Smooth Aim</span><div class="tpip"></div></div>
            </div>
            <div style="padding:10px 12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.05);border-radius:9px;margin-bottom:12px">
              <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px"><span style="font-size:10px;color:#7b7f88;letter-spacing:1.3px">SMOOTHNESS</span><span style="font-size:12px;font-weight:700;color:#ebecf0" id="_vmSmoothVal">50</span></div>
              <input class="mslider" id="_vmSmoothSlider" type="range" min="1" max="100" value="50"/>
              <div style="margin-top:6px;display:flex;flex-direction:column;gap:5px">
                <div class="ptgl green" id="_vmtgl-stickyTarget" onclick="window._vmFlip('stickyTarget')"><span class="tlbl">Sticky Target [N]</span><div class="tpip"></div></div>
                <div class="ptgl" id="_vmtgl-wallcheck" onclick="window._vmFlip('wallcheck')"><span class="tlbl">Wallcheck (AABB+Circle)</span><div class="tpip"></div></div>
              </div>
              <div style="margin-top:6px;display:flex;flex-direction:column;gap:5px">
                <div class="ptgl blue" id="_vmtgl-fovEnabled" onclick="window._vmFlip('fovEnabled')"><span class="tlbl">FOV Limit</span><div class="tpip"></div></div>
                <div style="display:flex;align-items:center;gap:8px;padding:6px 0">
                  <span style="font-size:10px;color:#7b7f88;min-width:36px">FOV</span>
                  <input class="mslider" id="_vmFovSlider" type="range" min="50" max="400" value="80" style="flex:1"/>
                  <span style="font-size:12px;font-weight:700;color:#ebecf0;min-width:32px" id="_vmFovVal">80</span>
                  <div class="ptgl" id="_vmtgl-showFov" onclick="window._vmFlip('showFov')" style="height:28px;padding:0 8px;flex:none"><span class="tlbl" style="font-size:9px">SHOW</span><div class="tpip"></div></div>
                </div>
              </div>
            </div>
            <div class="psec">MOVEMENT</div>
            <div class="ptgl yellow" id="_vmtgl-magnet" onclick="window._vmFlip('magnet')"><span class="tlbl">Magnet [E] move+aim+fire</span><div class="tpip"></div></div>
          </div>
          <div>
            <div class="psec">GRENADES</div>
            <div class="pcard">
              <div class="pgrid" style="margin-bottom:10px">
                <div class="ptgl yellow" id="_vmtgl-grenTimer" onclick="window._vmFlip('grenTimer')"><span class="tlbl">Timer</span><div class="tpip"></div></div>
                <div class="ptgl yellow" id="_vmtgl-thrower" onclick="window._vmMacroTgl()"><span class="tlbl">Thrower [Q]</span><div class="tpip"></div></div>
              </div>
              <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px"><span style="font-size:10px;color:#7b7f88;letter-spacing:1.3px">DELAY</span><span style="font-size:12px;font-weight:700;color:#ebecf0" id="_vmMacroDelay">100ms</span></div>
              <input class="mslider" id="_vmMacroSlider" type="range" min="10" max="200" value="100" step="5"/>
              <div style="font-size:10px;color:#7b7f88;margin-top:6px">Frag/Mirv 4s · Smoke 2.5s · Strobe 13.5s</div>
            </div>
          </div>
        </div>
      </div>

      <!-- VISUAL -->
      <div class="page" data-page="visual">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
          <div>
            <div class="psec">ESP</div>
            <div class="pgrid3" style="margin-bottom:10px">
              <div class="ptgl" id="_vmtgl-Esp" onclick="window._vmFlip('Esp')"><span class="tlbl">Lines</span><div class="tpip"></div></div>
              <div class="ptgl" id="_vmtgl-names" onclick="window._vmFlip('names')"><span class="tlbl">Names</span><div class="tpip"></div></div>
              <div class="ptgl" id="_vmtgl-healthBars" onclick="window._vmFlip('healthBars')"><span class="tlbl">HP</span><div class="tpip"></div></div>
            </div>
            <div class="pgrid" style="margin-bottom:10px">
              <div class="ptgl blue" id="_vmtgl-gunEsp" onclick="window._vmFlip('gunEsp')"><span class="tlbl">Gun Aim</span><div class="tpip"></div></div>
              <div class="ptgl red" id="_vmtgl-barrelEsp" onclick="window._vmFlip('barrelEsp')"><span class="tlbl">Barrels</span><div class="tpip"></div></div>
            </div>
            <div style="font-size:10px;color:#7b7f88;margin-bottom:10px">purple=t3 · gold=t2 · blue=t1 · [W]=wall blocked</div>
            <div class="psec">LOOT</div>
            <div class="ptgl yellow" id="_vmtgl-lootEsp" onclick="window._vmFlip('lootEsp')"><span class="tlbl">Loot ESP</span><div class="tpip"></div></div>
          </div>
          <div>
            <div class="psec">ZOOM</div>
            <div class="zrow" style="margin-bottom:12px">
              <span style="font-size:10px;color:#7b7f88;letter-spacing:1.5px">VIEW</span>
              <div style="display:flex;align-items:center;gap:6px">
                <button class="zbtn" id="_vmZoomIn">+</button>
                <span class="zval" id="_vmZoomVal">1.00x</span>
                <button class="zbtn" id="_vmZoomOut">-</button>
              </div>
              <button class="zbtn" id="_vmZoomReset" style="width:auto;padding:0 9px;font-size:10px">reset</button>
            </div>
            <div class="psec">GLASS</div>
            <div class="pcard">
              <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px"><span style="font-size:10px;color:#7b7f88">OPACITY</span><span style="font-size:12px;font-weight:700;color:#ebecf0" id="_vmTransVal">62%</span></div>
              <input class="mslider" data-vis="trans" type="range" min="18" max="88" value="62"/>
              <div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;margin-bottom:2px"><span style="font-size:10px;color:#7b7f88">BLUR</span><span style="font-size:12px;font-weight:700;color:#ebecf0" id="_vmBlurVal">38</span></div>
              <input class="mslider" data-vis="blur" type="range" min="0" max="60" value="38"/>
              <div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;margin-bottom:2px"><span style="font-size:10px;color:#7b7f88">RADIUS</span><span style="font-size:12px;font-weight:700;color:#ebecf0" id="_vmRadiusVal">18</span></div>
              <input class="mslider" data-vis="radius" type="range" min="8" max="28" value="18"/>
            </div>
          </div>
        </div>
      </div>

      <!-- WORLD -->
      <div class="page" data-page="world">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
          <div>
            <div class="psec">WORLD</div>
            <div style="display:flex;flex-direction:column;gap:7px">
              <div class="ptgl blue" id="_vmtgl-xray" onclick="window._vmFlip('xray')"><span class="tlbl">X-Ray</span><div class="tpip"></div></div>
              <div class="ptgl green" id="_vmtgl-layerSpoof" onclick="window._vmFlip('layerSpoof')"><span class="tlbl">Layer Spoofer [HOLD Y]</span><div class="tpip"></div></div>
              <div class="ptgl" id="_vmtgl-mapHighlights" onclick="window._vmFlip('mapHighlights')"><span class="tlbl">Map Highlights</span><div class="tpip"></div></div>
              <div class="ptgl green" id="_vmtgl-autoLoot" onclick="window._vmFlip('autoLoot')"><span class="tlbl">Auto Loot</span><div class="tpip"></div></div>
              <div class="ptgl green" id="_vmtgl-autoDoor" onclick="window._vmFlip('autoDoor')"><span class="tlbl">Auto Door</span><div class="tpip"></div></div>
            </div>
          </div>
          <div>
            <div class="psec">INFO</div>
            <div style="font-size:11px;color:#7b7f88;line-height:1.7;padding:10px 12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.05);border-radius:9px">
              <b style="color:#ebecf0">Wallcheck</b> AABB+circle ray-cast.<br><br>
              <b style="color:#ebecf0">Sticky [N]</b> locks nearest enemy.<br><br>
              <b style="color:#ebecf0">Magnet [E]</b> at 6 studs: move+aim+fire.<br><br>
              <b style="color:#ebecf0">Layer Spoofer</b> HOLD [Y].<br><br>
              <b style="color:#ebecf0">Auto Loot/Door</b> heals, ammo, doors.
            </div>
          </div>
        </div>
      </div>

      <!-- KEYS -->
      <div class="page" data-page="keybinds">
        <div style="font-size:10px;color:#7b7f88;margin-bottom:10px">Click a key box then press your new key.</div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#ff6464">Lock-On</span><span class="srow-sub">Toggle aim assist</span></div><input class="kbinput" id="_vmKb-lockOn" value="T" readonly onclick="window._vmKbListen('lockOn')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#ffc43c">Magnet</span><span class="srow-sub">Move+aim+fire at 6 studs</span></div><input class="kbinput" id="_vmKb-magnet" value="E" readonly onclick="window._vmKbListen('magnet')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#ffc43c">Thrower</span><span class="srow-sub">Start/stop macro</span></div><input class="kbinput" id="_vmKb-thrower" value="Q" readonly onclick="window._vmKbListen('thrower')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#5bc470">Sticky Target</span><span class="srow-sub">Lock nearest</span></div><input class="kbinput" id="_vmKb-stickyTarget" value="N" readonly onclick="window._vmKbListen('stickyTarget')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#5bc470">Layer Spoofer</span><span class="srow-sub">HOLD</span></div><input class="kbinput" id="_vmKb-layerSpoof" value="Y" readonly onclick="window._vmKbListen('layerSpoof')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#58a8ff">Zoom In</span></div><input class="kbinput" id="_vmKb-zoomIn" value="=" readonly onclick="window._vmKbListen('zoomIn')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#58a8ff">Zoom Out</span></div><input class="kbinput" id="_vmKb-zoomOut" value="-" readonly onclick="window._vmKbListen('zoomOut')"/></div>
        <div class="srow"><div class="srow-left"><span class="srow-title" style="color:#58a8ff">Zoom Reset</span></div><input class="kbinput" id="_vmKb-zoomReset" value="0" readonly onclick="window._vmKbListen('zoomReset')"/></div>
      </div>

      <!-- STATS -->
      <div class="page" data-page="stats">
        <div class="pcard" style="margin-bottom:10px">
          <div class="slugrow"><input id="_vmSlugInput" type="text" placeholder="player slug e.g. fr"/><button id="_vmStatsFetch">GO</button></div>
          <div id="_vmStatsBox" style="text-align:center;padding:5px 0;font-size:11px;color:#7b7f88">enter slug above</div>
        </div>
        <div class="psec" style="margin-bottom:8px">RECENT MATCHES <span style="font-size:9px;color:#7b7f88;font-weight:400;letter-spacing:0">click row for detail</span></div>
        <div id="_vmHistoryBox" style="font-size:11px;color:#7b7f88;text-align:center;padding:5px 0">search above</div>
        <div id="_vmMatchDetail"></div>
      </div>

      <!-- UPDATES -->
      <div class="page" data-page="updates">
        <div class="psec">CHANGELOG</div>
        <div style="display:flex;flex-direction:column;gap:8px">
          <div class="pcard">
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
              <span style="font-size:11px;font-weight:700;color:#ebecf0">v3.4</span>
              <span style="font-size:9px;color:#7b7f88;letter-spacing:1px">LATEST</span>
            </div>
            <div style="font-size:11px;color:#7b7f88;line-height:1.8">
              <span style="color:#5bc470">+</span> Auto-detects scrambled data — no more manual fixing after updates. Script scans the game object by shape on every load and patches itself automatically.<br>
              <span style="color:#5bc470">+</span> Settings now persist across sessions (💾 Save Settings button)<br>
              <span style="color:#5bc470">+</span> Feedback + Updates tabs added
            </div>
          </div>
          <div class="pcard">
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
              <span style="font-size:11px;font-weight:700;color:#ebecf0">v3.3</span>
              <span style="font-size:9px;color:#7b7f88;letter-spacing:1px"></span>
            </div>
            <div style="font-size:11px;color:#7b7f88;line-height:1.8">
              <span style="color:#5bc470">+</span> Rewrote game finder — no more setup snippets, auto-finds via Function.prototype.call hook<br>
              <span style="color:#5bc470">+</span> Proper AABB + Circle ray-cast wallcheck<br>
              <span style="color:#5bc470">+</span> Magnet: move + aim + fire all at once when in range<br>
              <span style="color:#5bc470">+</span> Sticky target stays locked even if closer enemy appears<br>
              <span style="color:#e87a7a">-</span> Removed manual setup tab (no longer needed)
            </div>
          </div>
          <div class="pcard">
            <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
              <span style="font-size:11px;font-weight:700;color:#ebecf0">v3.2</span>
              <span style="font-size:9px;color:#7b7f88;letter-spacing:1px"></span>
            </div>
            <div style="font-size:11px;color:#7b7f88;line-height:1.8">
              <span style="color:#5bc470">+</span> Smooth aim with lerp interpolation<br>
              <span style="color:#5bc470">+</span> Velocity-based prediction for lead shots<br>
              <span style="color:#5bc470">+</span> Auto door + auto loot<br>
              <span style="color:#5bc470">+</span> Layer spoofer (hold Y)<br>
              <span style="color:#5bc470">+</span> Grenade cook timer + thrown grenade blast radius
            </div>
          </div>
        </div>
      </div>

      <!-- FEEDBACK -->
      <div class="page" data-page="feedback">
        <div class="psec">SEND FEEDBACK</div>
        <div class="pcard" style="max-width:520px;margin:0 auto">
          <div style="font-size:11px;color:#7b7f88;margin-bottom:12px;line-height:1.6">Rate the script and leave a bug report or suggestion.</div>
          <div style="margin-bottom:12px">
            <div style="font-size:10px;color:#7b7f88;letter-spacing:1px;margin-bottom:6px">RATING</div>
            <div id="_vmFbStars" style="display:flex;gap:6px">${[1,2,3,4,5].map(v=>`<span class="fb-star sel" data-v="${v}">★</span>`).join('')}</div>
          </div>
          <div style="margin-bottom:6px">
            <div style="font-size:10px;color:#7b7f88;letter-spacing:1px;margin-bottom:6px">YOUR FEEDBACK</div>
            <textarea id="_vmFbText" class="fb-textarea" placeholder="What's working? What's broken? Feature requests?"></textarea>
          </div>
          <button class="fb-submit-btn" id="_vmFbSubmit">SEND TO DISCORD ↗</button>
        </div>
      </div>

    </div><!-- /pages -->
    <div id="toastHost"></div>
    <div id="resizer"></div>
  </section>
</div>`;
document.body.appendChild(ui);

// ============================================================
//  CANVAS / PARTICLES
// ============================================================
const canvas=ui.querySelector('#voidBg'),ctx2d=canvas.getContext('2d');
const toastHost=ui.querySelector('#toastHost');
const tabs=[...ui.querySelectorAll('.tab')],pages=[...ui.querySelectorAll('.page')];
const topbar=ui.querySelector('#topbar'),tabIndicator=ui.querySelector('#tabIndicator');
let particles=[];const dpr=Math.max(1,window.devicePixelRatio||1);
function resizeCanvas(){const w=ui.clientWidth,h=ui.clientHeight;canvas.width=Math.max(1,Math.floor(w*dpr));canvas.height=Math.max(1,Math.floor(h*dpr));canvas.style.width=w+'px';canvas.style.height=h+'px';particles=Array.from({length:54},()=>({a:Math.random()*Math.PI*2,r:(40+Math.random()*120)*3.2,s:(0.002+Math.random()*0.003)/5,p:Math.random()*Math.PI*2,sz:(0.6+Math.random()*1.4)*3.0}));}
function drawParticles(ts){const w=canvas.width/dpr,h=canvas.height/dpr;ctx2d.clearRect(0,0,w,h);for(let x=0;x<w;x+=28){ctx2d.strokeStyle='rgba(255,255,255,.028)';ctx2d.beginPath();ctx2d.moveTo(x,0);ctx2d.lineTo(x,h);ctx2d.stroke();}for(let y=0;y<h;y+=28){ctx2d.strokeStyle='rgba(255,255,255,.028)';ctx2d.beginPath();ctx2d.moveTo(0,y);ctx2d.lineTo(w,y);ctx2d.stroke();}const T=ts*0.00005,U=ts*0.00004,cx=w*0.5,cy=h*0.45;for(let i=0;i<particles.length;i++){const p=particles[i];p.a+=p.s*0.33;const x=cx+Math.cos(p.a+p.p)*p.r+Math.sin(T+i)*18;const y=cy+Math.sin(p.a*1.2+p.p)*(p.r*0.62)+Math.cos(U+i)*12;for(let j=i+1;j<particles.length;j++){const q=particles[j];const qx=cx+Math.cos(q.a+q.p)*q.r+Math.sin(T+j)*18;const qy=cy+Math.sin(q.a*1.2+q.p)*(q.r*0.62)+Math.cos(U+j)*12;const dist=Math.hypot(qx-x,qy-y);if(dist<300){ctx2d.strokeStyle=`rgba(255,255,255,${(1-dist/300)*0.048})`;ctx2d.lineWidth=1;ctx2d.beginPath();ctx2d.moveTo(x,y);ctx2d.lineTo(qx,qy);ctx2d.stroke();}}ctx2d.fillStyle='rgba(255,255,255,.62)';ctx2d.beginPath();ctx2d.arc(x,y,p.sz,0,Math.PI*2);ctx2d.fill();}requestAnimationFrame(drawParticles);}

// ============================================================
//  UI LOGIC
// ============================================================
function toast(msg){const el=document.createElement('div');el.className='toast';el.textContent=msg;toastHost.appendChild(el);setTimeout(()=>el.remove(),1800);}
function setStatus(msg){const el=document.getElementById('statusChip');if(el)el.textContent=msg;}
function applyState(persist=true){
  ui.style.left=state.x+'px';ui.style.top=state.y+'px';ui.style.width=state.w+'px';ui.style.height=state.h+'px';
  ui.classList.toggle('open',state.open);ui.style.display=state.open?'block':'none';
  if(state.max){ui.style.left=Math.max(8,Math.round(window.innerWidth*0.04))+'px';ui.style.top=Math.max(8,Math.round(window.innerHeight*0.05))+'px';ui.style.width=Math.min(window.innerWidth*0.92,1350)+'px';ui.style.height=Math.min(window.innerHeight*0.86,920)+'px';}
  if(persist)save();
}
function setTab(name){
  state.tab=name;
  tabs.forEach(t=>t.classList.toggle('active',t.dataset.tab===name));
  pages.forEach(p=>p.classList.toggle('active',p.dataset.page===name));
  const idx=tabs.findIndex(t=>t.dataset.tab===name);
  if(idx>=0)tabIndicator.style.transform=`translateY(${idx*44}px)`;
  save();
}
function setOpen(on){state.open=on;ui.classList.toggle('open',on);ui.style.display=on?'block':'none';if(on)resizeCanvas();save();}
window._vmSetMax=function(on){if(on===state.max)return;if(on){state.restore={x:state.x,y:state.y,w:state.w,h:state.h};state.max=true;}else{state.max=false;if(state.restore)Object.assign(state,state.restore);state.restore=null;}applyState();resizeCanvas();toast(state.max?'Maximized':'Restored');};
window._vmSaveCfg=function(){saveCfg();};

tabs.forEach(t=>t.addEventListener('click',()=>setTab(t.dataset.tab)));
ui.querySelectorAll('.ctrl').forEach(btn=>{btn.addEventListener('click',()=>{const act=btn.dataset.act;if(act==='close')setOpen(false);if(act==='save'){save();toast('Saved');}if(act==='max')window._vmSetMax(!state.max);});});
ui.querySelector('#discordBtn').addEventListener('click',()=>{window.open('https://discord.gg/rdAtZMsyfZ','_blank','noopener,noreferrer');toast('Opening Discord');});

ui.querySelectorAll('.mslider[data-vis]').forEach(r=>{
  const key=r.dataset.vis;const out=document.getElementById(`_vm${key.charAt(0).toUpperCase()+key.slice(1)}Val`);
  const apply=()=>{if(out)out.textContent=r.value+(key==='trans'?'%':'');if(key==='trans'){const a=+r.value/100;ui.style.background=`linear-gradient(180deg,rgba(24,25,29,${a}),rgba(13,14,17,${Math.min(.92,a+.18)}))`;}if(key==='blur')ui.style.backdropFilter=`blur(${r.value}px) saturate(135%)`;if(key==='radius'){ui.style.borderRadius=r.value+'px';ui.style.setProperty('--ui-radius',r.value+'px');}};
  r.addEventListener('input',apply);apply();
});
document.getElementById('_vmSmoothSlider').addEventListener('input',e=>{cfg.smoothAmount=parseInt(e.target.value);document.getElementById('_vmSmoothVal').textContent=e.target.value;});
document.getElementById('_vmFovSlider').addEventListener('input',e=>{cfg.fovSize=parseInt(e.target.value);document.getElementById('_vmFovVal').textContent=e.target.value;});
document.getElementById('_vmMacroSlider').addEventListener('input',e=>macro.setDelay(parseInt(e.target.value)));
document.getElementById('_vmZoomIn').onclick=()=>{if(settings.zoomEnabled)setZoom(Math.max(0.3,+(_zoomMultiplier-0.15).toFixed(2)));};
document.getElementById('_vmZoomOut').onclick=()=>{if(settings.zoomEnabled)setZoom(Math.min(5.0,+(_zoomMultiplier+0.15).toFixed(2)));};
document.getElementById('_vmZoomReset').onclick=()=>{if(settings.zoomEnabled){_zoomHooked=false;_zoomMultiplier=1.0;hookZoom();updateZoomUI();}};

const slugInput=document.getElementById('_vmSlugInput');
document.getElementById('_vmStatsFetch').onclick=()=>fetchStats(slugInput.value.trim());
slugInput.addEventListener('keydown',e=>{if(e.key==='Enter')fetchStats(slugInput.value.trim());});
setTimeout(()=>{const nameEl=document.getElementById('account-player-name');if(nameEl&&!slugInput.value){const slug=nameEl.textContent.trim().toLowerCase().replace(/\s+/g,'-');const skip=['log-in','create-account','guest','sign-in'];if(slug&&!skip.some(s=>slug.includes(s))){slugInput.value=slug;fetchStats(slug);}}},2000);

// ── FEEDBACK WIRING ──────────────────────────────────────────
ui.querySelectorAll('.fb-star').forEach(star=>{
  star.addEventListener('mouseover',()=>{const v=+star.dataset.v;ui.querySelectorAll('.fb-star').forEach(s=>s.classList.toggle('sel',+s.dataset.v<=v));});
  star.addEventListener('mouseout',()=>{ui.querySelectorAll('.fb-star').forEach(s=>s.classList.toggle('sel',+s.dataset.v<=_fbRating));});
  star.addEventListener('click',()=>{_fbRating=+star.dataset.v;ui.querySelectorAll('.fb-star').forEach(s=>s.classList.toggle('sel',+s.dataset.v<=_fbRating));});
});
document.getElementById('_vmFbSubmit').onclick=()=>{const inp=document.getElementById('_vmFbText');submitFeedback(inp.value,_fbRating);};

// ── AUTO-MAPPER runs automatically when game is found ─────────
function _autoMapperRun(){
  const R=runAutoMapper();
  if(R&&!R.error){
    applyMapperResult(R);
    console.log('[VB] Auto-mapper done:',R._missing.length===0?'all keys found':`${R._missing.length} missing: ${R._missing.join(', ')}`);
  }else{
    console.warn('[VB] Auto-mapper failed:',R?.error??'unknown');
  }
}

window._vmFlip=function(key){
  cfg[key]=!cfg[key];const el=document.getElementById(`_vmtgl-${key}`);if(el)el.classList.toggle('on',cfg[key]);
  if(key==='xray'&&!cfg.xray){const game=getGame();if(game){restoreXray(game);restoreXrayObs(game);}}
  if(key==='magnet'&&!cfg.magnet){const inp=getInput(getGame());if(inp)clearMagnetKeys(inp);}
  if(key==='predict'&&!cfg.predict)_predState.clear();
  if(key==='stickyTarget'&&!cfg.stickyTarget)_stickyTarget=null;
  if(key==='smoothAim'&&!cfg.smoothAim){_smoothSx=null;_smoothSy=null;}
  setStatus(cfg[key]?'ACTIVE':'READY');
};
window._vmMacroTgl=function(){if(!settings.throwerEnabled)return;macro.toggle();const el=document.getElementById('_vmtgl-thrower');if(el)el.classList.toggle('on',macro.running);};
window._vmSetFlip=function(key,enabled){
  settings[key+'Enabled']=enabled;
  if(!enabled){
    if(key==='lockOn'&&cfg.lockOn){cfg.lockOn=false;const el=document.getElementById('_vmtgl-lockOn');if(el)el.classList.remove('on');}
    if(key==='magnet'){if(cfg.magnet){cfg.magnet=false;const el=document.getElementById('_vmtgl-magnet');if(el)el.classList.remove('on');}const inp=getInput(getGame());if(inp)clearMagnetKeys(inp);}
    if(key==='thrower'&&macro.running)macro.stop();
    if(key==='zoom'){_zoomMultiplier=1.0;_zoomHooked=false;hookZoom();updateZoomUI();}
  }
};
let _kbListening=null;
window._vmKbListen=function(name){if(_kbListening){const prev=document.getElementById(`_vmKb-${_kbListening}`);if(prev){prev.classList.remove('listening');prev.value=keybinds[_kbListening].toUpperCase();}}_kbListening=name;const el=document.getElementById(`_vmKb-${name}`);if(el){el.classList.add('listening');el.value='...';}};
document.addEventListener('keydown',function(e){
  if(_kbListening){e.preventDefault();e.stopPropagation();const k=e.key===' '?'Space':e.key.length===1?e.key.toUpperCase():e.key;keybinds[_kbListening]=e.key;const el=document.getElementById(`_vmKb-${_kbListening}`);if(el){el.value=k;el.classList.remove('listening');}_kbListening=null;return;}
  if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA')return;
  if(e.key==='Insert'||e.key==='Escape')setOpen(!state.open);
  const k=e.key.toLowerCase();
  if(k===keybinds.lockOn.toLowerCase()&&settings.lockOnEnabled)window._vmFlip('lockOn');
  if(k===keybinds.magnet.toLowerCase()&&settings.magnetEnabled)window._vmFlip('magnet');
  if(k===keybinds.thrower.toLowerCase()&&settings.throwerEnabled){e.preventDefault();macro.toggle();const el=document.getElementById('_vmtgl-thrower');if(el)el.classList.toggle('on',macro.running);}
  if(k===keybinds.stickyTarget.toLowerCase()){if(cfg.stickyTarget&&_nearest){_stickyTarget=_nearest;toast('Target locked');}else{_stickyTarget=null;}}
  if(k===keybinds.layerSpoof.toLowerCase()&&!e.repeat&&!_layerKeyHeld){_layerKeyHeld=true;if(cfg.layerSpoof){const game=getGame();if(game)doLayerSpoof(game);const el=document.getElementById('_vmtgl-layerSpoof');if(el)el.classList.add('on');toast('Layer spoofed');}}
  if(settings.zoomEnabled){if(k===keybinds.zoomIn.toLowerCase()||e.key==='+'){setZoom(Math.max(0.3,+(_zoomMultiplier-0.15).toFixed(2)));e.preventDefault();}if(k===keybinds.zoomOut.toLowerCase()||e.key==='_'){setZoom(Math.min(5.0,+(_zoomMultiplier+0.15).toFixed(2)));e.preventDefault();}if(k===keybinds.zoomReset.toLowerCase()){_zoomHooked=false;_zoomMultiplier=1.0;hookZoom();updateZoomUI();}}
},true);
document.addEventListener('keyup',function(e){
  if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA')return;
  if(e.key.toLowerCase()===keybinds.layerSpoof.toLowerCase()&&_layerKeyHeld){_layerKeyHeld=false;if(cfg.layerSpoof){const game=getGame();if(game)undoLayerSpoof(game);const el=document.getElementById('_vmtgl-layerSpoof');if(el)el.classList.remove('on');toast('Layer restored');}}
});
document.addEventListener('mousedown',function(e){
  if(_kbListening){e.preventDefault();e.stopPropagation();const btnNames={0:'M1',1:'M3',2:'M2',3:'M4',4:'M5'};const btnKeys={0:'__mouse0',1:'__mouse1',2:'__mouse2',3:'__mouse3',4:'__mouse4'};const label=btnNames[e.button]||`M${e.button+1}`,key=btnKeys[e.button]||`__mouse${e.button}`;keybinds[_kbListening]=key;const el=document.getElementById(`_vmKb-${_kbListening}`);if(el){el.value=label;el.classList.remove('listening');}_kbListening=null;return;}
  const btnKey={0:'__mouse0',1:'__mouse1',2:'__mouse2',3:'__mouse3',4:'__mouse4'}[e.button];if(!btnKey)return;
  if(btnKey===keybinds.lockOn&&settings.lockOnEnabled)window._vmFlip('lockOn');
  if(btnKey===keybinds.magnet&&settings.magnetEnabled)window._vmFlip('magnet');
  if(btnKey===keybinds.thrower&&settings.throwerEnabled){macro.toggle();const el=document.getElementById('_vmtgl-thrower');if(el)el.classList.toggle('on',macro.running);}
  if(btnKey===keybinds.stickyTarget){if(cfg.stickyTarget&&_nearest){_stickyTarget=_nearest;toast('Target locked');}else{_stickyTarget=null;}}
},true);

let drag=false,ox=0,oy=0;
topbar.onmousedown=e=>{if(e.target.closest('.ctrl')||state.max)return;drag=true;const r=ui.getBoundingClientRect();ox=e.clientX-r.left;oy=e.clientY-r.top;ui.classList.add('dragging');};
document.addEventListener('mousemove',e=>{if(!drag)return;state.x=e.clientX-ox;state.y=e.clientY-oy;ui.style.left=state.x+'px';ui.style.top=state.y+'px';});
document.addEventListener('mouseup',()=>{drag=false;ui.classList.remove('dragging');if(!state.max)save();});
const resizer=ui.querySelector('#resizer');
resizer.addEventListener('pointerdown',e=>{if(state.max)return;e.preventDefault();const sx=e.clientX,sy=e.clientY,sw=state.w,sh=state.h;const move=ev=>{state.w=clamp(sw+(ev.clientX-sx),580,window.innerWidth-state.x-12);state.h=clamp(sh+(ev.clientY-sy),420,window.innerHeight-state.y-12);applyState(false);resizeCanvas();};const up=()=>{document.removeEventListener('pointermove',move);document.removeEventListener('pointerup',up);save();};document.addEventListener('pointermove',move);document.addEventListener('pointerup',up);});
document.addEventListener('mousemove',e=>{if(!state.open)return;const r=ui.getBoundingClientRect();ui.style.setProperty('--mx',(e.clientX-r.left)+'px');ui.style.setProperty('--my',(e.clientY-r.top)+'px');});

// ============================================================
//  APPLY LOADED CFG TO UI
// ============================================================
function applyCfgToUI(){
  for(const k of SAVEABLE_BOOLS){const el=document.getElementById(`_vmtgl-${k}`);if(el)el.classList.toggle('on',cfg[k]);}
  const ss=document.getElementById('_vmSmoothSlider');if(ss){ss.value=cfg.smoothAmount;document.getElementById('_vmSmoothVal').textContent=cfg.smoothAmount;}
  const fs=document.getElementById('_vmFovSlider');if(fs){fs.value=cfg.fovSize;document.getElementById('_vmFovVal').textContent=cfg.fovSize;}
}

// ============================================================
//  GAME OVERLAY
// ============================================================
function startOverlay(){
  if(window._vmOverlayRunning)return;window._vmOverlayRunning=true;
  const oc=document.createElement('canvas');oc.style.cssText='position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998;';document.body.appendChild(oc);
  const octx=oc.getContext('2d');const resize=()=>{oc.width=innerWidth;oc.height=innerHeight;};resize();window.addEventListener('resize',resize);
  hookZoom();installMapHighlights();
  (function loop(){
    requestAnimationFrame(loop);octx.clearRect(0,0,oc.width,oc.height);
    _fpsFrames++;const now=performance.now();const delta=now-_msLast;_msLast=now;_ms=Math.round(_ms*0.85+delta*0.15);
    if(now-_fpsLast>=1000){_fps=_fpsFrames;_fpsFrames=0;_fpsLast=now;const el=document.getElementById('fpsChip');if(el){el.textContent=`${_fps} FPS`;el.style.color=_fps>=55?'#5bc470':_fps>=30?'#e8c060':'#e87a7a';}}
    const msEl=document.getElementById('msChip');if(msEl){msEl.textContent=`${_ms} MS`;msEl.style.color=_ms<=16?'#5bc470':_ms<=33?'#e8c060':'#e87a7a';}
    const killEl=document.querySelector('.js-ui-player-kills');if(killEl)setStatus(killEl.textContent?`${killEl.textContent} KILLS`:'READY');
    const game=getGame();if(!game)return;
    if(!game?.initialized){const inp=getInput(game);if(inp&&cfg.magnet)clearMagnetKeys(inp);return;}
    if(!_zoomHooked)hookZoom();tickXray(game);
    const barn=getBarn(game),myId=getMyId(game),input=getInput(game);if(!barn||!myId)return;
    const worldContainer=getWorldContainer(game);const STUD=worldContainer?.scale?.x||30;if(!_baseStud)_baseStud=STUD;
    const players=getPlayers(barn);const myInfo=barn.playerInfo?.[myId];const myGroup=myInfo?.groupId,myTeam=myInfo?.teamId;
    const me=getMe(game);if(!me?.active)return;
    tickAutoDoor(game,input);tickAutoLoot(game,input);
    let mpos;try{mpos=me.container.toGlobal({x:0,y:0});}catch(e){return;}
    const msx=mpos.x,msy=mpos.y,myWorldPos=getPlayerPos(me);
    if(cfg.gunEsp){const aw=(me.weapTypeOld??'').toLowerCase();const isMelee=!aw||aw==='fists'||/knife|axe|katana|pan|bat|crowbar|bowie|kukri|naginata|spear|saw|hook|sword|sickle|hatchet|machete|cleaver|hammer/.test(aw);const dir=getPlayerDir(me);if(!isMelee&&dir&&typeof dir.x==='number'){const gd=GUN_DATA[aw]??{range:150,spread:3};const rangePx=gd.range*STUD,spreadRad=(gd.spread*Math.PI)/180,ca=Math.atan2(-dir.y,dir.x);octx.save();octx.beginPath();octx.moveTo(msx,msy);octx.arc(msx,msy,rangePx,ca-spreadRad,ca+spreadRad);octx.closePath();octx.fillStyle='rgba(255,255,255,0.06)';octx.fill();octx.strokeStyle='rgba(255,255,255,0.55)';octx.lineWidth=1;octx.setLineDash([5,5]);octx.beginPath();octx.moveTo(msx,msy);octx.lineTo(msx+Math.cos(ca-spreadRad)*rangePx,msy+Math.sin(ca-spreadRad)*rangePx);octx.stroke();octx.beginPath();octx.moveTo(msx,msy);octx.lineTo(msx+Math.cos(ca+spreadRad)*rangePx,msy+Math.sin(ca+spreadRad)*rangePx);octx.stroke();octx.setLineDash([]);octx.beginPath();octx.arc(msx,msy,rangePx,ca-spreadRad,ca+spreadRad);octx.strokeStyle='rgba(255,255,255,0.9)';octx.lineWidth=1.5;octx.stroke();octx.restore();}}
    if(cfg.fovEnabled&&cfg.showFov&&cfg.lockOn){const fovPx=cfg.fovSize*STUD;octx.save();octx.beginPath();octx.arc(msx,msy,fovPx,0,Math.PI*2);octx.strokeStyle='rgba(168,85,247,0.35)';octx.lineWidth=1;octx.setLineDash([4,4]);octx.stroke();octx.setLineDash([]);octx.restore();}
    for(const[id] of _predState){if(!players.find(p=>p.__id===id&&p.active&&!isPlayerDead(p)))_predState.delete(id);}
    let activeTarget=null,activeTargetDist=Infinity;
    if(cfg.stickyTarget&&_stickyTarget){const stP=players.find(p=>p.__id===_stickyTarget.p?.__id&&p.active&&!isPlayerDead(p));if(stP&&stP.container?.visible){try{const sp=stP.container.toGlobal({x:0,y:0});activeTarget={p:stP,sx:sp.x,sy:sp.y};activeTargetDist=Math.hypot(sp.x-msx,sp.y-msy);}catch(e){_stickyTarget=null;}}else{_stickyTarget=null;}}
    if(!activeTarget){const fovPx=cfg.fovEnabled?cfg.fovSize*STUD:Infinity;for(const p of players){if(!p.active||p.__id===myId||isPlayerDead(p)||!p.container?.visible)continue;const info=barn.playerInfo?.[p.__id];const isMate=(myGroup>0&&info?.groupId>0&&info.groupId===myGroup)||(myTeam>0&&info?.teamId>0&&info.teamId===myTeam);if(isMate)continue;let spos;try{spos=p.container.toGlobal({x:0,y:0});}catch(e){continue;}const d=Math.hypot(spos.x-msx,spos.y-msy);if(cfg.fovEnabled&&d>fovPx)continue;if(d<activeTargetDist){activeTargetDist=d;activeTarget={p,sx:spos.x,sy:spos.y};}}}
    _nearest=activeTarget;
    let targetHasLOS=true;if(activeTarget&&cfg.wallcheck&&myWorldPos)targetHasLOS=hasLOS(game,myWorldPos,getPlayerPos(activeTarget.p));
    if(cfg.lockOn&&settings.lockOnEnabled&&activeTarget&&input&&(targetHasLOS||!cfg.wallcheck)){let tsx=activeTarget.sx,tsy=activeTarget.sy;if(cfg.predict&&myWorldPos&&worldContainer){const aw=(me.weapTypeOld??'').toLowerCase();const gd=GUN_DATA[aw]??{bulletSpeed:100};const pw=getPredictedWorldPos(activeTarget.p,myWorldPos,gd.bulletSpeed);if(pw){const psx=worldContainer.x+pw.x*STUD,psy=worldContainer.y-pw.y*STUD;const pulse=(Date.now()%700)/700,ripple=Math.sin(pulse*Math.PI*2);octx.save();octx.beginPath();octx.arc(psx,psy,14+ripple*4,0,Math.PI*2);octx.strokeStyle='rgba(168,85,247,0.55)';octx.lineWidth=1.5;octx.globalAlpha=0.6*(1-pulse);octx.stroke();octx.globalAlpha=0.92;octx.beginPath();octx.arc(psx,psy,5,0,Math.PI*2);octx.fillStyle='#a855f7';octx.shadowBlur=12;octx.shadowColor='#a855f7';octx.fill();octx.shadowBlur=0;octx.restore();tsx=psx;tsy=psy;}}applyAimLock(tsx,tsy,input);}
    if(cfg.magnet&&settings.magnetEnabled&&input&&activeTarget){const distStuds=activeTargetDist/STUD;const magnetActive=applyMagnet(input,me,msx,msy,activeTarget.sx,activeTarget.sy,distStuds);if(magnetActive){const pulse=(Date.now()%600)/600;octx.save();octx.globalAlpha=0.6+Math.sin(pulse*Math.PI*2)*0.4;octx.beginPath();octx.arc(activeTarget.sx,activeTarget.sy,10+Math.sin(pulse*Math.PI*2)*4,0,Math.PI*2);octx.strokeStyle='#ffff00';octx.lineWidth=2;octx.stroke();octx.restore();}}else if((!cfg.magnet||!settings.magnetEnabled)&&input){clearMagnetKeys(input);}
    if(cfg.barrelEsp&&worldContainer){const nowMs=Date.now();for(const obj of getObstacles(game)){if(!obj||!obj.active||!obj.pos||obj.dead||obj.exploded||obj.healthT<=0)continue;const t=(typeof obj.type==='string'?obj.type:'').toLowerCase();if(!/^barrel_0[12]|^fuel_barrel/.test(t))continue;const bx=worldContainer.x+obj.pos.x*STUD,by=worldContainer.y-obj.pos.y*STUD;if(bx<-300||bx>innerWidth+300||by<-300||by>innerHeight+300)continue;const hitRad=(obj.collider?.rad??(obj.scale??1)*1.05)*STUD;const blastRad=(/fuel/.test(t)?14:12)*STUD;const pulse=(nowMs%1400)/1400,sin=Math.sin(pulse*Math.PI*2);octx.save();octx.beginPath();octx.arc(bx,by,blastRad,0,Math.PI*2);octx.fillStyle='#ff4400';octx.globalAlpha=0.05+sin*0.03;octx.fill();octx.beginPath();octx.arc(bx,by,blastRad,0,Math.PI*2);octx.strokeStyle='#ff4400';octx.lineWidth=2;octx.globalAlpha=0.5+sin*0.2;octx.stroke();octx.beginPath();octx.arc(bx,by,hitRad+2,0,Math.PI*2);octx.strokeStyle='#ff2200';octx.lineWidth=2.5;octx.globalAlpha=1;octx.stroke();octx.restore();}}
    if(cfg.grenTimer){const nowMs=Date.now();const weapItem=(me.weapTypeOld??'').toLowerCase();const throwState=me.throwableState??'equip';const isHoldingGren=/frag|mirv|smoke|strobe|martyr/.test(weapItem);const isCooking=throwState==='cook',isThrowing=throwState==='throw'||me.anim?.type===3,isCookable=/frag|mirv|martyr/.test(weapItem);if(isCookable&&isCooking&&!_grenCook)_grenCook={cookStart:nowMs,fuse:getGrenFuse(weapItem),typeId:weapItem,remaining:null,thrownAt:null};if(_grenCook&&_grenCook.remaining===null&&(isThrowing||!isHoldingGren)){const cooked=(nowMs-_grenCook.cookStart)/1000;_grenCook.remaining=Math.max(0.15,_grenCook.fuse-cooked);_grenCook.thrownAt=nowMs;}if(_grenCook?.thrownAt&&(nowMs-_grenCook.thrownAt)>800)_grenCook=null;if(!isHoldingGren&&_grenCook&&!_grenCook.thrownAt)_grenCook=null;const projArr=getProjectiles(game);let newThisFrame=0;const newProjs=[];if(Array.isArray(projArr)){for(const proj of projArr){if(!proj?.active)continue;const pt=(typeof proj.type==='string'?proj.type:'').toLowerCase();if(/^potato|^tomato/.test(pt))continue;if(!_thrownGrenades.has(proj)){newThisFrame++;newProjs.push(proj);}}}for(const proj of newProjs){let fuse,typeId;if(newThisFrame>=3){fuse=1.8;typeId='mirv_child';}else if(_grenCook?.remaining!=null){fuse=_grenCook.remaining;typeId=_grenCook.typeId;_grenCook=null;}else{fuse=getGrenFuse(isHoldingGren?weapItem:'');typeId=isHoldingGren?weapItem:'frag';}_thrownGrenades.set(proj,{spawnTime:nowMs,fuse,typeId});}for(const[proj,data]of _thrownGrenades){const age=(nowMs-data.spawnTime)/1000;if(!proj.active||age>data.fuse+0.6)_thrownGrenades.delete(proj);}if(isCookable&&isCooking&&_grenCook&&!_grenCook.thrownAt){const elapsed=(nowMs-_grenCook.cookStart)/1000,remaining=Math.max(0,_grenCook.fuse-elapsed),progress=Math.min(1,elapsed/_grenCook.fuse);const urgent=progress>0.75,col=urgent?'#ff5050':'#ffdc3c';octx.save();const arcR=24;octx.lineWidth=4;octx.beginPath();octx.arc(msx,msy,arcR,-Math.PI/2,-Math.PI/2+Math.PI*2);octx.strokeStyle='rgba(80,80,80,0.4)';octx.stroke();octx.beginPath();octx.arc(msx,msy,arcR,-Math.PI/2,-Math.PI/2+Math.PI*2*progress);octx.strokeStyle=col;octx.stroke();const lx2=msx,ly2=msy+arcR+12;octx.fillStyle='rgba(0,0,0,0.75)';octx.beginPath();octx.roundRect(lx2-44,ly2-10,88,20,5);octx.fill();octx.font='bold 11px monospace';octx.textAlign='center';octx.textBaseline='middle';octx.strokeStyle='rgba(0,0,0,0.9)';octx.lineWidth=3;const cookLabel=`💣 ${remaining.toFixed(1)}s`;octx.strokeText(cookLabel,lx2,ly2);octx.fillStyle=col;octx.fillText(cookLabel,lx2,ly2);if(urgent){const pulse=(nowMs%250)/250;octx.globalAlpha=0.5*(1-pulse);octx.beginPath();octx.arc(msx,msy,arcR+pulse*14,0,Math.PI*2);octx.strokeStyle='#ff3333';octx.lineWidth=2;octx.stroke();}octx.restore();}for(const[proj,data]of _thrownGrenades){if(!proj.active||!proj.pos||!worldContainer)continue;const px=worldContainer.x+proj.pos.x*STUD,py=worldContainer.y-proj.pos.y*STUD;const age=(nowMs-data.spawnTime)/1000,remaining=Math.max(0,data.fuse-age),progress=Math.min(1,age/data.fuse),urgent=remaining<1.0;const blastPx=getGrenBlast(data.typeId)*STUD;octx.save();octx.beginPath();octx.arc(px,py,blastPx,0,Math.PI*2);octx.fillStyle='#ff3333';octx.globalAlpha=0.04+progress*0.10;octx.fill();octx.globalAlpha=0.5+progress*0.4;octx.strokeStyle='#ff3333';octx.lineWidth=1.8;octx.beginPath();octx.arc(px,py,blastPx,0,Math.PI*2);octx.stroke();octx.globalAlpha=1;const arcR=12;octx.lineWidth=3;octx.strokeStyle='rgba(0,0,0,0.7)';octx.beginPath();octx.arc(px,py,arcR,-Math.PI/2,-Math.PI/2+Math.PI*2);octx.stroke();octx.strokeStyle='#ff3333';octx.beginPath();octx.arc(px,py,arcR,-Math.PI/2,-Math.PI/2+Math.PI*2*progress);octx.stroke();octx.font='bold 12px monospace';octx.textAlign='center';octx.textBaseline='middle';octx.strokeStyle='rgba(0,0,0,1)';octx.lineWidth=4;octx.strokeText(remaining.toFixed(1),px,py);octx.fillStyle='#ff3333';octx.fillText(remaining.toFixed(1),px,py);if(urgent){const pulse=(nowMs%200)/200;octx.globalAlpha=0.75*(1-pulse);octx.beginPath();octx.arc(px,py,blastPx*(0.6+pulse*0.4),0,Math.PI*2);octx.strokeStyle='#ff0000';octx.lineWidth=3;octx.stroke();}octx.restore();}}
    for(const p of players){if(!p.active||isPlayerDead(p)||!p.container?.visible)continue;let spos;try{spos=p.container.toGlobal({x:0,y:0});}catch(e){continue;}const sx=spos.x,sy=spos.y,isSelf=p.__id===myId;const info=barn.playerInfo?.[p.__id];const isMate=!isSelf&&((myGroup>0&&info?.groupId>0&&info.groupId===myGroup)||(myTeam>0&&info?.teamId>0&&info.teamId===myTeam));const isLocked=activeTarget&&p.__id===activeTarget.p.__id;const isSticky=_stickyTarget&&p.__id===_stickyTarget.p?.__id;const pHasLOS=isSelf||isMate||!cfg.wallcheck||!myWorldPos?true:hasLOS(game,myWorldPos,getPlayerPos(p));const color=isSelf?'#5bc470':isMate?'#6aabff':'#ff4444';if(cfg.healthBars&&!isSelf){const status=barn.playerStatus?.[p.__id];let hp=typeof status?.health==='number'?status.health:null;if(hp===null&&barn.playerStatus){for(const s of Object.values(barn.playerStatus)){if(s&&s.playerId===p.__id&&typeof s.health==='number'){hp=s.health;break;}}}if(hp!==null&&isMate){const BW=120,BH=12,BX=sx-BW/2,BY=sy-42;const filled=Math.max(0,Math.min(1,hp/100))*BW,hpCol=hp>60?'#5bc470':hp>30?'#e8c060':'#e87a7a';octx.globalAlpha=0.75;octx.fillStyle='rgba(0,0,0,0.55)';octx.beginPath();octx.roundRect(BX-1,BY-1,BW+2,BH+2,4);octx.fill();octx.globalAlpha=0.4;octx.fillStyle='#333';octx.beginPath();octx.roundRect(BX,BY,BW,BH,3);octx.fill();octx.globalAlpha=0.95;octx.fillStyle=hpCol;if(filled>0){octx.beginPath();octx.roundRect(BX,BY,filled,BH,3);octx.fill();}octx.globalAlpha=1;octx.font='bold 11px monospace';octx.textAlign='center';octx.textBaseline='middle';octx.strokeStyle='rgba(0,0,0,0.9)';octx.lineWidth=2.5;octx.strokeText(`${Math.round(hp)}`,BX+BW/2,BY+BH/2);octx.fillStyle='#fff';octx.fillText(`${Math.round(hp)}`,BX+BW/2,BY+BH/2);}else if(isPlayerDowned(p)){octx.save();octx.globalAlpha=0.8;octx.font='bold 10px monospace';octx.textAlign='center';octx.textBaseline='bottom';octx.strokeStyle='rgba(0,0,0,0.8)';octx.lineWidth=3;octx.strokeText('DOWNED',sx,sy-24);octx.fillStyle='#e8c060';octx.fillText('DOWNED',sx,sy-24);octx.restore();}}if(!isSelf){const dist=Math.hypot(sx-msx,sy-msy),alpha=Math.max(0.75,1-dist/2400),studs=Math.round(dist/STUD);if(cfg.Esp){octx.save();octx.beginPath();octx.moveTo(msx,msy);octx.lineTo(sx,sy);octx.setLineDash(isMate?[6,4]:isLocked?[3,3]:[]);octx.strokeStyle=isSticky?'#a855f7':isLocked?'#ff4444':color;octx.lineWidth=isMate?2:2.5;octx.globalAlpha=pHasLOS?alpha:alpha*0.3;octx.stroke();octx.setLineDash([]);if(isSticky){octx.beginPath();octx.arc(sx,sy,14,0,Math.PI*2);octx.strokeStyle='#a855f7';octx.lineWidth=2;octx.globalAlpha=0.7;octx.stroke();}octx.restore();}if(cfg.names){const name=info?.nameTruncated||info?.name||`#${p.__id}`;const tag=isSticky?'[S]':isLocked?'[X]':'';const label=`${name}  ${studs}${tag?' '+tag:''}${!pHasLOS&&!isMate?' [W]':''}`;const lx=(msx+sx)/2,ly=(msy+sy)/2;octx.save();octx.globalAlpha=Math.min(1,alpha+0.2);octx.font='bold 11px monospace';octx.textAlign='center';octx.textBaseline='middle';octx.strokeStyle='rgba(0,0,0,0.8)';octx.lineWidth=3;octx.strokeText(label,lx,ly);octx.fillStyle=isSticky?'#a855f7':isLocked?'#ff4444':color;octx.fillText(label,lx,ly);octx.restore();}}}
    if(cfg.lootEsp&&worldContainer){for(const item of getLoot(game)){if(!item?.active||!item.pos)continue;const t=(typeof item.type==='string'?item.type:'').toLowerCase();const lx=worldContainer.x+item.pos.x*STUD,ly=worldContainer.y-item.pos.y*STUD;if(lx<-50||lx>innerWidth+50||ly<-50||ly>innerHeight+50)continue;const col=lootColor(item),rawR=item.rad!=null?item.rad*STUD:0;const isGun=/gun|rifle|shotgun|pistol|smg|sniper|melee|m9|glock|deagle|mp5|ump|vector|ak|scar|m416|bar|mosin|sv|l86|m249|qbb|pkp|dp|spas|saiga|usas/.test(t);const r=isGun?Math.max(22,rawR*1.25):Math.max(14,rawR);octx.save();octx.beginPath();octx.arc(lx,ly,r,0,Math.PI*2);octx.fillStyle=col;octx.globalAlpha=0.18;octx.fill();octx.beginPath();octx.arc(lx,ly,r,0,Math.PI*2);octx.strokeStyle=col;octx.lineWidth=2.5;octx.globalAlpha=0.95;octx.stroke();octx.restore();}}
  })();
}

// ============================================================
//  WAIT FOR GAME + INIT
// ============================================================
function waitForGame(){
  let tries=0;
  const poll=setInterval(()=>{tries++;const game=getGame();if(game?.initialized){clearInterval(poll);_autoMapperRun();startOverlay();installMapHighlights();return;}if(game)setStatus('LOBBY');if(tries>240){clearInterval(poll);setStatus('NEEDS SETUP – refresh');}},500);
}
function init(){
  loadCfg();
  loadSavedKeymap();
  applyState(false);resizeCanvas();setTab(state.tab);setOpen(state.open);applyCfgToUI();
  requestAnimationFrame(drawParticles);
  window.addEventListener('resize',()=>{if(state.open){applyState(false);resizeCanvas();}});
  const obs=new MutationObserver((_,o)=>{if(document.getElementById('ui-game')){o.disconnect();waitForGame();}});
  if(document.getElementById('ui-game'))waitForGame();else obs.observe(document.body,{childList:true,subtree:true});
}
if(document.readyState==='loading')window.addEventListener('DOMContentLoaded',init);else init();
console.log('%c[Void Bacon+ v3.3.3] ESC=panel | T=lock-on | E=magnet | Q=macro | N=sticky | HOLD-Y=layer | DEBUG tab = auto-mapper','color:#a855f7;font-weight:bold');
})();